Refactor the startup flow, cleanup old progress logs, cleanup globals

This commit is contained in:
Lorow
2025-09-03 00:15:25 +02:00
parent 0c9e254aba
commit 68998ad727
6 changed files with 188 additions and 255 deletions
@@ -128,8 +128,20 @@ CommandResult getLEDDutyCycleCommand(std::shared_ptr<DependencyRegistry> registr
CommandResult startStreamingCommand() CommandResult startStreamingCommand()
{ {
activateStreaming(false); // Don't disable setup interfaces by default // since we're trying to kill the serial handler
return CommandResult::getSuccessResult("Streaming started"); // from *inside* the serial handler, we'd deadlock.
// we can just pass nullptr to the vtaskdelete(),
// but then we won't get any response, so we schedule a timer instead
esp_timer_create_args_t args{
.callback = activateStreaming,
.arg = nullptr,
.name = "activateStreaming"};
esp_timer_handle_t activateStreamingTimer;
esp_timer_create(&args, &activateStreamingTimer);
esp_timer_start_once(activateStreamingTimer, pdMS_TO_TICKS(150));
return CommandResult::getSuccessResult("Streaming starting");
} }
CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload) CommandResult switchModeCommand(std::shared_ptr<DependencyRegistry> registry, std::string_view jsonPayload)
+4 -20
View File
@@ -1,8 +1,8 @@
#include "main_globals.hpp" #include "main_globals.hpp"
#include "esp_log.h" #include "esp_log.h"
// Forward declarations // used to force starting the stream setup process via commands
extern void start_video_streaming(void *arg); extern void force_activate_streaming();
static bool s_startupCommandReceived = false; static bool s_startupCommandReceived = false;
bool getStartupCommandReceived() bool getStartupCommandReceived()
@@ -15,17 +15,6 @@ void setStartupCommandReceived(bool startupCommandReceived)
s_startupCommandReceived = startupCommandReceived; s_startupCommandReceived = startupCommandReceived;
} }
static TaskHandle_t *g_serial_manager_handle = nullptr;
TaskHandle_t *getSerialManagerHandle()
{
return g_serial_manager_handle;
}
void setSerialManagerHandle(TaskHandle_t *serialManagerHandle)
{
g_serial_manager_handle = serialManagerHandle;
}
// Global pause state // Global pause state
static bool s_startupPaused = false; static bool s_startupPaused = false;
bool getStartupPaused() bool getStartupPaused()
@@ -39,14 +28,9 @@ void setStartupPaused(bool startupPaused)
} }
// Function to manually activate streaming // Function to manually activate streaming
void activateStreaming(bool disableSetup) void activateStreaming(void *arg)
{ {
ESP_LOGI("[MAIN_GLOBALS]", "Manually activating streaming, disableSetup=%s", disableSetup ? "true" : "false"); force_activate_streaming();
TaskHandle_t *serialHandle = disableSetup ? g_serial_manager_handle : nullptr;
void *serialTaskHandle = (serialHandle && *serialHandle) ? *serialHandle : nullptr;
start_video_streaming(serialTaskHandle);
} }
// USB handover state // USB handover state
+3 -7
View File
@@ -6,14 +6,10 @@
#include "freertos/FreeRTOS.h" #include "freertos/FreeRTOS.h"
#include "freertos/task.h" #include "freertos/task.h"
// Functions for main to set global handles
// Functions to access global handles from components
TaskHandle_t *getSerialManagerHandle();
void setSerialManagerHandle(TaskHandle_t *serialManagerHandle);
// Function to manually activate streaming // Function to manually activate streaming
void activateStreaming(bool disableSetup = false); // designed to be scheduled as a task
// so that the serial manager has time to return the response
void activateStreaming(void *arg);
bool getStartupCommandReceived(); bool getStartupCommandReceived();
void setStartupCommandReceived(bool startupCommandReceived); void setStartupCommandReceived(bool startupCommandReceived);
@@ -132,15 +132,15 @@ void SerialManager::shutdown()
{ {
// Stop heartbeats; timer will be deleted by main if needed. // Stop heartbeats; timer will be deleted by main if needed.
// Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB. // Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB.
// esp_err_t err = usb_serial_jtag_driver_uninstall(); esp_err_t err = usb_serial_jtag_driver_uninstall();
// if (err == ESP_OK) if (err == ESP_OK)
// { {
// ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled"); ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled");
// } }
// else if (err != ESP_ERR_INVALID_STATE) else if (err != ESP_ERR_INVALID_STATE)
// { {
// ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err)); ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err));
// } }
} }
// we can cancel this task once we're in cdc // we can cancel this task once we're in cdc
@@ -18,8 +18,8 @@
#include "esp_vfs_dev.h" #include "esp_vfs_dev.h"
#include "esp_mac.h" #include "esp_mac.h"
void tud_cdc_rx_cb(uint8_t itf); extern "C" void tud_cdc_rx_cb(uint8_t itf);
void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts);
extern QueueHandle_t cdcMessageQueue; extern QueueHandle_t cdcMessageQueue;
@@ -49,5 +49,5 @@ private:
}; };
void HandleSerialManagerTask(void *pvParameters); void HandleSerialManagerTask(void *pvParameters);
void HandleCDCSerialManagerTask(void *pvParameters) void HandleCDCSerialManagerTask(void *pvParameters);
#endif #endif
+155 -214
View File
@@ -29,6 +29,8 @@
#define BLINK_GPIO (gpio_num_t) CONFIG_LED_BLINK_GPIO #define BLINK_GPIO (gpio_num_t) CONFIG_LED_BLINK_GPIO
#define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_EXTERNAL_GPIO #define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_EXTERNAL_GPIO
TaskHandle_t serialManagerHandle;
esp_timer_handle_t timerHandle; esp_timer_handle_t timerHandle;
QueueHandle_t eventQueue = xQueueCreate(10, sizeof(SystemEvent)); QueueHandle_t eventQueue = xQueueCreate(10, sizeof(SystemEvent));
QueueHandle_t ledStateQueue = xQueueCreate(10, sizeof(uint32_t)); QueueHandle_t ledStateQueue = xQueueCreate(10, sizeof(uint32_t));
@@ -57,6 +59,9 @@ UVCStreamManager uvcStream;
auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig); auto ledManager = std::make_shared<LEDManager>(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig);
auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig); auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig);
void startWiFiMode(bool shouldCloseSerialManager);
void startWiredMode(bool shouldCloseSerialManager);
static void initNVSStorage() static void initNVSStorage()
{ {
esp_err_t ret = nvs_flash_init(); esp_err_t ret = nvs_flash_init();
@@ -74,97 +79,34 @@ int websocket_logger(const char *format, va_list args)
return vprintf(format, args); return vprintf(format, args);
} }
void disable_serial_manager_task(TaskHandle_t serialManagerHandle) void launch_streaming()
{ {
vTaskDelete(serialManagerHandle); // Note, when switching and later right away activating UVC mode when we were previously in WiFi or Auto mode, the WiFi
} // utilities will still be running since we've launched them with startAutoMode() -> startWiFiMode()
// we could add detection of this case, but it's probably not worth it since the next start of the device literally won't launch them
// and we're telling folks to just reboot the device anyway
// same case goes for when switching from UVC to WiFi
// New setup flow:
// 1. Device starts in setup mode (AP + Serial active)
// 2. User configures WiFi via serial commands
// 3. Device attempts WiFi connection while maintaining setup interfaces
// 4. Device reports connection status via serial
// 5. User explicitly starts streaming after verifying connectivity
// todo this is getting yeeted lmao
// new flow once more - auto - start the setup mode, we're gonna wait for a decision of what to do, maybe try and detect if we're connected to usb
// uvc mode - start the uvc stream right away, kill webserver and wifi
// wifi mode - start the wifi stream right away
void start_video_streaming(void *arg)
{
// Get the stored device mode
StreamingMode deviceMode = deviceConfig->getDeviceMode(); StreamingMode deviceMode = deviceConfig->getDeviceMode();
// if we've changed the mode from auto to something else, we can clean up serial manager
// Check if WiFi is actually connected, not just configured // either the API endpoints or CDC will take care of further configuration
bool hasWifiCredentials = !deviceConfig->getWifiConfigs().empty() || strcmp(CONFIG_WIFI_SSID, "") != 0; if (deviceMode == StreamingMode::WIFI)
bool wifiConnected = (wifiManager->GetCurrentWiFiState() == WiFiState_e::WiFiState_Connected);
if (deviceMode == StreamingMode::UVC)
{ {
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE startWiFiMode(true);
ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
ESP_LOGI("[MAIN]", "Initializing UVC hardware...");
// If we were given the Serial task handle, stop the task and uninstall the driver
if (arg != nullptr)
{
const auto serialTaskHandle = static_cast<TaskHandle_t>(arg);
vTaskDelete(serialTaskHandle);
ESP_LOGI("[MAIN]", "Serial task deleted before UVC init");
serialManager->shutdown();
ESP_LOGI("[MAIN]", "Serial driver uninstalled");
// Leave a small gap for the host to see COM disappear
vTaskDelay(pdMS_TO_TICKS(200));
setUsbHandoverDone(true);
}
esp_err_t ret = uvcStream.setup();
if (ret != ESP_OK)
{
ESP_LOGE("[MAIN]", "Failed to initialize UVC: %s", esp_err_to_name(ret));
return;
}
uvcStream.start();
ESP_LOGI("[MAIN]", "UVC streaming started");
return; // UVC path complete, do not fall through to WiFi
#else
ESP_LOGE("[MAIN]", "UVC mode selected but the board likely does not support it.");
ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available");
deviceMode = StreamingMode::WIFI;
#endif
} }
else if (deviceMode == StreamingMode::UVC)
if ((deviceMode == StreamingMode::WIFI || deviceMode == StreamingMode::AUTO) && hasWifiCredentials && wifiConnected)
{ {
ESP_LOGI("[MAIN]", "Starting WiFi streaming mode."); startWiredMode(true);
streamServer.startStreamServer(); }
else if (deviceMode == StreamingMode::AUTO)
{
// we're still in auto, the user didn't select anything yet, let's give a bit of time for them to make a choice
ESP_LOGI("[MAIN]", "No mode was selected, staying in AUTO mode. WiFi streaming will be enabled still. \nPlease select another mode if you'd like.");
} }
else else
{ {
if (hasWifiCredentials && !wifiConnected) ESP_LOGI("[MAIN]", "Unknown device mode: %d", (int)deviceMode);
{
ESP_LOGE("[MAIN]", "WiFi credentials configured but not connected. Try connecting first.");
}
else
{
ESP_LOGE("[MAIN]", "No streaming mode available. Please configure WiFi.");
}
return;
} }
ESP_LOGI("[MAIN]", "Streaming started successfully.");
// Optionally disable serial manager after explicit streaming start
if (arg != nullptr)
{
ESP_LOGI("[MAIN]", "Disabling setup interfaces after streaming start.");
const auto serialTaskHandle = static_cast<TaskHandle_t>(arg);
disable_serial_manager_task(serialTaskHandle);
}
}
// Manual streaming activation - no timer needed
void activate_streaming(TaskHandle_t serialTaskHandle = nullptr)
{
start_video_streaming(serialTaskHandle);
} }
// Callback for automatic startup after delay // Callback for automatic startup after delay
@@ -176,55 +118,7 @@ void startup_timer_callback(void *arg)
if (!getStartupCommandReceived() && !getStartupPaused()) if (!getStartupCommandReceived() && !getStartupPaused())
{ {
ESP_LOGI("[MAIN]", "No command received during startup delay, proceeding with automatic mode startup"); launch_streaming();
// Get the stored device mode
StreamingMode deviceMode = deviceConfig->getDeviceMode();
ESP_LOGI("[MAIN]", "Stored device mode: %d", (int)deviceMode);
// Get the serial manager handle to disable it after streaming starts
TaskHandle_t *serialHandle = getSerialManagerHandle();
TaskHandle_t serialTaskHandle = (serialHandle && *serialHandle) ? *serialHandle : nullptr;
if (deviceMode == StreamingMode::WIFI || deviceMode == StreamingMode::AUTO)
{
// For WiFi mode, check if we have credentials and are connected
bool hasWifiCredentials = !deviceConfig->getWifiConfigs().empty() || strcmp(CONFIG_WIFI_SSID, "") != 0;
bool wifiConnected = (wifiManager->GetCurrentWiFiState() == WiFiState_e::WiFiState_Connected);
ESP_LOGI("[MAIN]", "WiFi check - hasCredentials: %s, connected: %s",
hasWifiCredentials ? "true" : "false",
wifiConnected ? "true" : "false");
if (hasWifiCredentials && wifiConnected)
{
ESP_LOGI("[MAIN]", "Starting WiFi streaming automatically");
activate_streaming(serialTaskHandle);
}
else if (hasWifiCredentials && !wifiConnected)
{
ESP_LOGI("[MAIN]", "WiFi credentials exist but not connected, waiting...");
// Could retry connection here
}
else
{
ESP_LOGI("[MAIN]", "No WiFi credentials, staying in setup mode");
}
}
else if (deviceMode == StreamingMode::UVC)
{
#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE
ESP_LOGI("[MAIN]", "Starting UVC streaming automatically");
activate_streaming(serialTaskHandle);
#else
ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_GENERAL_DEFAULT_WIRED_MODE not enabled in build!");
ESP_LOGI("[MAIN]", "Device will stay in setup mode. Enable CONFIG_GENERAL_DEFAULT_WIRED_MODE and rebuild.");
#endif
}
else
{
ESP_LOGI("[MAIN]", "Unknown device mode: %d", (int)deviceMode);
}
} }
else else
{ {
@@ -243,93 +137,50 @@ void startup_timer_callback(void *arg)
timerHandle = nullptr; timerHandle = nullptr;
} }
extern "C" void app_main(void) // Manual streaming activation
// We'll clean up the timer and handle streaming setup if called by a command
void force_activate_streaming()
{ {
dependencyRegistry->registerService<ProjectConfig>(DependencyType::project_config, deviceConfig); // Delete the timer before it fires
dependencyRegistry->registerService<CameraManager>(DependencyType::camera_manager, cameraHandler); // since we've got called manually
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager); esp_timer_delete(timerHandle);
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager); timerHandle = nullptr;
// uvc plan launch_streaming();
// cleanup the logs - done }
// prepare the camera to be initialized with UVC - done?
// debug uvc performance - done
// porting plan: void startWiredMode(bool shouldCloseSerialManager)
// port the wifi manager first. - worky!!! {
// port the logo - done #ifndef CONFIG_GENERAL_DEFAULT_WIRED_MODE
// port preferences lib - DONE; prolly temporary ESP_LOGE("[MAIN]", "UVC mode selected but the board likely does not support it.");
// then port the config - done, needs todos done ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available");
// State Management - done deviceMode = StreamingMode::WIFI;
// then port the led manager as this will be fairly easy - done startWiFiMode();
// then port the mdns stuff - done #else
// then port the camera manager - done ESP_LOGI("[MAIN]", "Starting UVC streaming mode.");
// then port the streaming stuff (web and uvc) - done if (shouldCloseSerialManager)
// then add ADHOC and support for more networks in wifi manager - done {
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ ESP_LOGI("[MAIN]", "Closing serial manager task.");
vTaskDelete(serialManagerHandle);
}
// simplify commands - a simple dependency injection + std::function should do it - DONE ESP_LOGI("[MAIN]", "Shutting down serial manager, CDC will take over in a bit.");
// something like serialManager->shutdown();
// template<typename T>
// void registerService(std::shared_pointer<T> service)
// services[std::type_index(typeid(T))] = service;
// where services is an std::unordered_map<std::type_index, std::shared_pointer<void>>;
// I can then use like std::shared_ptr<T> resolve() { return services[typeid(T)]; } to get it in the command
// which can be like a second parameter of the command, like std::function<void(DiContainer &diContainer, char* jsonPayload)>
// simplify config - DONE ESP_LOGI("[MAIN]", "Serial driver uninstalled");
// here I can decouple the loading, initializing and saving logic from the config class and move // Leaving a small gap for the host to see COM disappear
// that into the separate modules, and have the config class only act as a container vTaskDelay(pdMS_TO_TICKS(200));
setUsbHandoverDone(true);
// rethink led manager - we need to move the state change sending into a queue and rethink the state lighting logic - DONE ESP_LOGI("[MAIN]", "Setting up UVC Streamer");
// also, the entire led manager needs to be moved to a task - DONE
// with that, I couuld use vtaskdelayuntil to advance and display states - DONE
// and with that, I should rethink how state management works - DONE
// rethink state management - DONE esp_err_t ret = uvcStream.setup();
if (ret != ESP_OK)
// port serial manager - DONE {
// instead of the UVCCDC thing - give the board 30s for serial commands and then determine if we should reboot into UVC - DONE ESP_LOGE("[MAIN]", "Failed to initialize UVC: %s", esp_err_to_name(ret));
return;
// add endpoint to check firmware version }
// add firmware version somewhere
// setup CI and building for other boards
// finish todos, overhaul stuff a bit
// esp_log_set_vprintf(&websocket_logger);
Logo::printASCII();
initNVSStorage();
deviceConfig->load();
ledManager->setup();
xTaskCreate(
HandleStateManagerTask,
"HandleLEDDisplayTask",
1024 * 2,
stateManager,
3,
nullptr // it's fine for us not get a handle back, we don't need it
);
xTaskCreate(
HandleLEDDisplayTask,
"HandleLEDDisplayTask",
1024 * 2,
ledManager.get(),
3,
nullptr);
serialManager->setup();
static TaskHandle_t serialManagerHandle = nullptr;
// Pass address of variable so xTaskCreate() stores the actual task handle
xTaskCreate(
HandleSerialManagerTask,
"HandleSerialManagerTask",
1024 * 6,
serialManager,
1, // we only rely on the serial manager during provisioning, we can run it slower
&serialManagerHandle);
ESP_LOGI("[MAIN]", "Starting CDC Serial Manager Task");
xTaskCreate( xTaskCreate(
HandleCDCSerialManagerTask, HandleCDCSerialManagerTask,
"HandleCDCSerialManagerTask", "HandleCDCSerialManagerTask",
@@ -338,10 +189,25 @@ extern "C" void app_main(void)
1, 1,
nullptr); nullptr);
ESP_LOGI("[MAIN]", "Starting UVC streaming");
uvcStream.start();
ESP_LOGI("[MAIN]", "UVC streaming started");
#endif
}
void startWiFiMode(bool shouldCloseSerialManager)
{
ESP_LOGI("[MAIN]", "Starting WiFi streaming mode.");
if (shouldCloseSerialManager)
{
ESP_LOGI("[MAIN]", "Closing serial manager task.");
vTaskDelete(serialManagerHandle);
}
wifiManager->Begin(); wifiManager->Begin();
mdnsManager.start(); mdnsManager.start();
restAPI->begin(); restAPI->begin();
cameraHandler->setupCamera();
xTaskCreate( xTaskCreate(
HandleRestAPIPollTask, HandleRestAPIPollTask,
@@ -350,8 +216,12 @@ extern "C" void app_main(void)
restAPI, restAPI,
1, // it's the rest API, we only serve commands over it so we don't really need a higher priority 1, // it's the rest API, we only serve commands over it so we don't really need a higher priority
nullptr); nullptr);
}
// New flow: Device starts with a 20-second delay before automatic mode startup void startSetupMode()
{
// If we're in an auto mode - Device starts with a 20-second delay before deciding on what to do
// during this time we await any commands
ESP_LOGI("[MAIN]", "====================================="); ESP_LOGI("[MAIN]", "=====================================");
ESP_LOGI("[MAIN]", "STARTUP: 20-SECOND DELAY MODE ACTIVE"); ESP_LOGI("[MAIN]", "STARTUP: 20-SECOND DELAY MODE ACTIVE");
ESP_LOGI("[MAIN]", "====================================="); ESP_LOGI("[MAIN]", "=====================================");
@@ -369,6 +239,77 @@ extern "C" void app_main(void)
ESP_ERROR_CHECK(esp_timer_start_once(timerHandle, CONFIG_GENERAL_UVC_DELAY * 1000000)); ESP_ERROR_CHECK(esp_timer_start_once(timerHandle, CONFIG_GENERAL_UVC_DELAY * 1000000));
ESP_LOGI("[MAIN]", "Started 20-second startup timer"); ESP_LOGI("[MAIN]", "Started 20-second startup timer");
ESP_LOGI("[MAIN]", "Send any command within 20 seconds to enter heartbeat mode"); ESP_LOGI("[MAIN]", "Send any command within 20 seconds to enter heartbeat mode");
// todo make this a tasks context that stores all the handles }
setSerialManagerHandle(&serialManagerHandle);
extern "C" void app_main(void)
{
dependencyRegistry->registerService<ProjectConfig>(DependencyType::project_config, deviceConfig);
dependencyRegistry->registerService<CameraManager>(DependencyType::camera_manager, cameraHandler);
dependencyRegistry->registerService<WiFiManager>(DependencyType::wifi_manager, wifiManager);
dependencyRegistry->registerService<LEDManager>(DependencyType::led_manager, ledManager);
// add endpoint to check firmware version
// add firmware version somewhere
// setup CI and building for other boards
// finish todos, overhaul stuff a bit
// todo - do we need logs over CDC? Or just commands and their results?
// esp_log_set_vprintf(&websocket_logger);
Logo::printASCII();
initNVSStorage();
deviceConfig->load();
ledManager->setup();
xTaskCreate(
HandleStateManagerTask,
"HandleStateManagerTask",
1024 * 2,
stateManager,
3,
nullptr // it's fine for us not get a handle back, we don't need it
);
xTaskCreate(
HandleLEDDisplayTask,
"HandleLEDDisplayTask",
1024 * 2,
ledManager.get(),
3,
nullptr);
cameraHandler->setupCamera();
// let's keep the serial manager running for the duration of the setup
// we'll clean it up later if need be
serialManager->setup();
xTaskCreate(
HandleSerialManagerTask,
"HandleSerialManagerTask",
1024 * 6,
serialManager,
1,
&serialManagerHandle);
StreamingMode mode = deviceConfig->getDeviceMode();
if (mode == StreamingMode::UVC)
{
// in UVC mode we only need to start the bare essentials for UVC
// we don't need any wireless communication, we can shut it down
// todo this would be the perfect place to introduce random delays
// to workaround windows usb bug
startWiredMode(true);
}
else if (mode == StreamingMode::WIFI)
{
// in Wifi mode we only need the wireless communication stuff, nothing else got started
startWiFiMode(true);
}
else
{
// since we're in setup mode, we have to have wireless functionality on,
// so we can do wifi scanning, test connection etc
startWiFiMode(false);
startSetupMode();
}
} }