diff --git a/components/CommandManager/CommandManager/commands/device_commands.cpp b/components/CommandManager/CommandManager/commands/device_commands.cpp index 7579b74..ac9e294 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.cpp +++ b/components/CommandManager/CommandManager/commands/device_commands.cpp @@ -128,8 +128,20 @@ CommandResult getLEDDutyCycleCommand(std::shared_ptr registr CommandResult startStreamingCommand() { - activateStreaming(false); // Don't disable setup interfaces by default - return CommandResult::getSuccessResult("Streaming started"); + // since we're trying to kill the serial handler + // 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 registry, std::string_view jsonPayload) diff --git a/components/Helpers/Helpers/main_globals.cpp b/components/Helpers/Helpers/main_globals.cpp index 8078234..da51271 100644 --- a/components/Helpers/Helpers/main_globals.cpp +++ b/components/Helpers/Helpers/main_globals.cpp @@ -1,8 +1,8 @@ #include "main_globals.hpp" #include "esp_log.h" -// Forward declarations -extern void start_video_streaming(void *arg); +// used to force starting the stream setup process via commands +extern void force_activate_streaming(); static bool s_startupCommandReceived = false; bool getStartupCommandReceived() @@ -15,17 +15,6 @@ void setStartupCommandReceived(bool 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 static bool s_startupPaused = false; bool getStartupPaused() @@ -39,14 +28,9 @@ void setStartupPaused(bool startupPaused) } // 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"); - - TaskHandle_t *serialHandle = disableSetup ? g_serial_manager_handle : nullptr; - void *serialTaskHandle = (serialHandle && *serialHandle) ? *serialHandle : nullptr; - - start_video_streaming(serialTaskHandle); + force_activate_streaming(); } // USB handover state diff --git a/components/Helpers/Helpers/main_globals.hpp b/components/Helpers/Helpers/main_globals.hpp index 7fe93c9..2505cca 100644 --- a/components/Helpers/Helpers/main_globals.hpp +++ b/components/Helpers/Helpers/main_globals.hpp @@ -6,14 +6,10 @@ #include "freertos/FreeRTOS.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 -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(); void setStartupCommandReceived(bool startupCommandReceived); diff --git a/components/SerialManager/CMakeLists.txt b/components/SerialManager/CMakeLists.txt index aaaa174..41c18ab 100644 --- a/components/SerialManager/CMakeLists.txt +++ b/components/SerialManager/CMakeLists.txt @@ -1,4 +1,4 @@ idf_component_register(SRCS "SerialManager/SerialManager.cpp" INCLUDE_DIRS "SerialManager" - REQUIRES esp_driver_uart CommandManager ProjectConfig + REQUIRES esp_driver_uart CommandManager ProjectConfig tinyusb ) \ No newline at end of file diff --git a/components/SerialManager/SerialManager/SerialManager.cpp b/components/SerialManager/SerialManager/SerialManager.cpp index 8cfa168..0967794 100644 --- a/components/SerialManager/SerialManager/SerialManager.cpp +++ b/components/SerialManager/SerialManager/SerialManager.cpp @@ -1,6 +1,7 @@ #include "SerialManager.hpp" #include "esp_log.h" #include "main_globals.hpp" +#include "tusb.h" #define BUF_SIZE (1024) @@ -65,10 +66,10 @@ void SerialManager::try_receive() // Notify main that a command was received during startup notify_startup_command_received(); - const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast(this->data))); - const auto resultMessage = result.getResult(); - int written = usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20); - (void)written; // ignore errors if driver already uninstalled + const auto result = this->commandManager->executeFromJson(std::string_view(reinterpret_cast(this->data))); + const auto resultMessage = result.getResult(); + int written = usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20); + (void)written; // ignore errors if driver already uninstalled } } @@ -127,6 +128,22 @@ bool SerialManager::should_send_heartbeat() return wifiConfigs.empty(); } +void SerialManager::shutdown() +{ + // Stop heartbeats; timer will be deleted by main if needed. + // Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB. + esp_err_t err = usb_serial_jtag_driver_uninstall(); + if (err == ESP_OK) + { + ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled"); + } + else if (err != ESP_ERR_INVALID_STATE) + { + 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 void HandleSerialManagerTask(void *pvParameters) { auto const serialManager = static_cast(pvParameters); @@ -150,17 +167,71 @@ void HandleSerialManagerTask(void *pvParameters) } } -void SerialManager::shutdown() +void HandleCDCSerialManagerTask(void *pvParameters) { - // Stop heartbeats; timer will be deleted by main if needed. - // Uninstall the USB Serial JTAG driver to free the internal USB for TinyUSB. - esp_err_t err = usb_serial_jtag_driver_uninstall(); - if (err == ESP_OK) + auto const commandManager = static_cast(pvParameters); + static char buffer[BUF_SIZE]; + auto idx = 0; + + cdc_command_packet_t packet; + while (true) { - ESP_LOGI("[SERIAL]", "usb_serial_jtag driver uninstalled"); + if (xQueueReceive(cdcMessageQueue, &packet, portMAX_DELAY) == pdTRUE) + { + for (auto i = 0; i < packet.len; i++) + { + buffer[idx++] = packet.data[i]; + // if we're at the end of the buffer, try to process the command anyway + // if we've got a new line, we've finished sending the commands, process them + if (idx >= BUF_SIZE || buffer[idx - 1] == '\n' || buffer[idx - 1] == '\r') + { + buffer[idx - 1] = '\0'; + const auto result = commandManager->executeFromJson(std::string_view(reinterpret_cast(buffer))); + const auto resultMessage = result.getResult(); + tud_cdc_write(resultMessage.c_str(), resultMessage.length()); + tud_cdc_write_flush(); + idx = 0; + } + } + } } - else if (err != ESP_ERR_INVALID_STATE) +} + +// tud_cdc_rx_cb is defined as TU_ATTR_WEAK so we can override it, we will be called back if we get some data +// but we don't want to do any processing here since we don't want to risk blocking +// grab the data and send it to a queue, a special task will process it and handle with the command manager +extern "C" void tud_cdc_rx_cb(uint8_t itf) +{ + // we can void the interface number + (void)itf; + cdc_command_packet_t packet; + auto len = tud_cdc_available(); + + if (len > 0) { - ESP_LOGW("[SERIAL]", "usb_serial_jtag_driver_uninstall returned %s", esp_err_to_name(err)); + auto read = tud_cdc_read(packet.data, sizeof(packet.data)); + if (read > 0) + { + // we should be safe here, given that the max buffer size is 64 + packet.len = static_cast(read); + xQueueSend(cdcMessageQueue, &packet, 1); + } } +} + +extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts) +{ + (void)itf; + (void)dtr; + (void)rts; + + ESP_LOGI("[SERIAL]", "CDC line state changed: DTR=%d, RTS=%d", dtr, rts); +} + +void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const *p_line_coding) +{ + (void)itf; + ESP_LOGI("[SERIAL]", "CDC line coding: %" PRIu32 " bps, %d stop bits, %d parity, %d data bits", + p_line_coding->bit_rate, p_line_coding->stop_bits, + p_line_coding->parity, p_line_coding->data_bits); } \ No newline at end of file diff --git a/components/SerialManager/SerialManager/SerialManager.hpp b/components/SerialManager/SerialManager/SerialManager.hpp index a612957..16b00ff 100644 --- a/components/SerialManager/SerialManager/SerialManager.hpp +++ b/components/SerialManager/SerialManager/SerialManager.hpp @@ -18,6 +18,17 @@ #include "esp_vfs_dev.h" #include "esp_mac.h" +extern "C" void tud_cdc_rx_cb(uint8_t itf); +extern "C" void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts); + +extern QueueHandle_t cdcMessageQueue; + +struct cdc_command_packet_t +{ + uint8_t len; + uint8_t data[64]; +}; + class SerialManager { public: @@ -38,4 +49,5 @@ private: }; void HandleSerialManagerTask(void *pvParameters); +void HandleCDCSerialManagerTask(void *pvParameters); #endif \ No newline at end of file diff --git a/components/UVCStream/UVCStream/UVCStream.hpp b/components/UVCStream/UVCStream/UVCStream.hpp index 46ff69e..b0dfb43 100644 --- a/components/UVCStream/UVCStream/UVCStream.hpp +++ b/components/UVCStream/UVCStream/UVCStream.hpp @@ -17,7 +17,8 @@ extern std::shared_ptr cameraHandler; extern std::shared_ptr deviceConfig; #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif const char *get_uvc_device_name(); diff --git a/components/usb_device_uvc/tusb/tusb_config.h b/components/usb_device_uvc/tusb/tusb_config.h index 41aa694..bbcb2da 100644 --- a/components/usb_device_uvc/tusb/tusb_config.h +++ b/components/usb_device_uvc/tusb/tusb_config.h @@ -28,17 +28,18 @@ #include "uvc_frame_config.h" #ifdef __cplusplus -extern "C" { +extern "C" +{ #endif -//--------------------------------------------------------------------+ -// Board Specific Configuration -//--------------------------------------------------------------------+ + //--------------------------------------------------------------------+ + // Board Specific Configuration + //--------------------------------------------------------------------+ #ifdef CONFIG_TINYUSB_RHPORT_HS -# define CFG_TUSB_RHPORT1_MODE OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED +#define CFG_TUSB_RHPORT1_MODE OPT_MODE_DEVICE | OPT_MODE_HIGH_SPEED #else -# define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED +#define CFG_TUSB_RHPORT0_MODE OPT_MODE_DEVICE | OPT_MODE_FULL_SPEED #endif //-------------------------------------------------------------------- @@ -55,20 +56,20 @@ extern "C" { #endif #ifndef CFG_TUSB_OS -#define CFG_TUSB_OS OPT_OS_FREERTOS +#define CFG_TUSB_OS OPT_OS_FREERTOS #endif // Espressif IDF requires "freertos/" prefix in include path #if TU_CHECK_MCU(OPT_MCU_ESP32S2, OPT_MCU_ESP32S3, OPT_MCU_ESP32P4) -#define CFG_TUSB_OS_INC_PATH freertos/ +#define CFG_TUSB_OS_INC_PATH freertos/ #endif #ifndef CFG_TUSB_DEBUG -#define CFG_TUSB_DEBUG 0 +#define CFG_TUSB_DEBUG 0 #endif // Enable Device stack -#define CFG_TUD_ENABLED 1 +#define CFG_TUD_ENABLED 1 /* USB DMA on some MCUs can only access a specific SRAM region with restriction on alignment. * Tinyusb use follows macros to declare transferring memory so that they can be put @@ -82,71 +83,54 @@ extern "C" { #endif #ifndef CFG_TUSB_MEM_ALIGN -#define CFG_TUSB_MEM_ALIGN __attribute__ ((aligned(4))) +#define CFG_TUSB_MEM_ALIGN __attribute__((aligned(4))) #endif -//-------------------------------------------------------------------- -// DEVICE CONFIGURATION -//-------------------------------------------------------------------- + //-------------------------------------------------------------------- + // DEVICE CONFIGURATION + //-------------------------------------------------------------------- #ifndef CFG_TUD_ENDPOINT0_SIZE -#define CFG_TUD_ENDPOINT0_SIZE 64 +#define CFG_TUD_ENDPOINT0_SIZE 64 #endif -//------------- CLASS -------------// + //------------- CLASS -------------// -// The number of video control interfaces -// The number of video streaming interfaces -#if CONFIG_UVC_SUPPORT_TWO_CAM -#define CFG_TUD_VIDEO 2 -#define CFG_TUD_VIDEO_STREAMING 2 -#else -#define CFG_TUD_VIDEO 1 -#define CFG_TUD_VIDEO_STREAMING 1 -#endif +#define CFG_TUD_CDC 1 + +// CDC FIFO size of TX and RX +#define CFG_TUD_CDC_RX_BUFSIZE 256 +#define CFG_TUD_CDC_TX_BUFSIZE 256 + +// CDC Endpoint transfer buffer size, more is faster +#define CFG_TUD_CDC_EP_BUFSIZE 64 + + // The number of video control interfaces + // The number of video streaming interfaces + +#define CFG_TUD_VIDEO 1 +#define CFG_TUD_VIDEO_STREAMING 1 // video streaming endpoint size #ifdef UVC_CAM1_BULK_MODE #if CONFIG_TINYUSB_RHPORT_HS -#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 512 +#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 512 #else -#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 64 +#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 64 #endif #define CFG_TUD_CAM1_VIDEO_STREAMING_BULK 1 #else #define CFG_TUD_CAM1_VIDEO_STREAMING_BULK 0 #if CONFIG_TINYUSB_RHPORT_HS -#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 1023 +#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 1023 #else -#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 512 +#define CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE 512 #endif #endif #define CFG_EXAMPLE_VIDEO_DISABLE_MJPEG (!FORMAT_MJPEG) -#if CONFIG_UVC_SUPPORT_TWO_CAM -#ifdef UVC_CAM2_BULK_MODE -#if CONFIG_TINYUSB_RHPORT_HS -#define CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE 512 -#else -#define CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE 64 -#endif -#define CFG_TUD_CAM2_VIDEO_STREAMING_BULK 1 -#else -#define CFG_TUD_CAM2_VIDEO_STREAMING_BULK 0 -#if CONFIG_TINYUSB_RHPORT_HS -#define CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE 1023 -#else -#define CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE 512 -#endif -#endif -#endif - -#if CONFIG_UVC_SUPPORT_TWO_CAM -#define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE (CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE > CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE?CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE:CFG_TUD_CAM2_VIDEO_STREAMING_EP_BUFSIZE) -#else #define CFG_TUD_VIDEO_STREAMING_EP_BUFSIZE CFG_TUD_CAM1_VIDEO_STREAMING_EP_BUFSIZE -#endif #ifdef __cplusplus } diff --git a/components/usb_device_uvc/tusb/usb_descriptors.c b/components/usb_device_uvc/tusb/usb_descriptors.c index 24dc7c1..315d141 100644 --- a/components/usb_device_uvc/tusb/usb_descriptors.c +++ b/components/usb_device_uvc/tusb/usb_descriptors.c @@ -65,14 +65,18 @@ uint8_t const *tud_descriptor_device_cb(void) // Configuration Descriptor //--------------------------------------------------------------------+ // String descriptor indices used in interface descriptors -#define STRID_LANGID 0 -#define STRID_MANUFACTURER 1 -#define STRID_PRODUCT 2 -#define STRID_SERIAL 3 -#define STRID_UVC_CAM1 4 +#define STRID_LANGID 0 +#define STRID_MANUFACTURER 1 +#define STRID_PRODUCT 2 +#define STRID_SERIAL 3 +#define STRID_UVC_CAM1 4 +// Endpoint numbers for CDC +#define EPNUM_CDC_NOTIF 0x81 +#define EPNUM_CDC_OUT 0x02 +#define EPNUM_CDC_IN 0x82 // Endpoint numbers for UVC video IN endpoints (device -> host) -#define EPNUM_CAM1_VIDEO_IN 0x81 +#define EPNUM_CAM1_VIDEO_IN 0x83 #if CFG_TUD_CAM1_VIDEO_STREAMING_BULK @@ -113,7 +117,7 @@ uint8_t const *tud_descriptor_device_cb(void) #endif // CFG_TUD_CAM1_VIDEO_STREAMING_BULK // Total length of this configuration -#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CAM1_VIDEO_CAPTURE_DESC_LEN) +#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_CAM1_VIDEO_CAPTURE_DESC_LEN) // Full-speed configuration descriptor static uint8_t const desc_fs_configuration[] = { @@ -121,6 +125,7 @@ static uint8_t const desc_fs_configuration[] = { // total_length, attributes, power_mA) // attributes: 0 = bus-powered (default). Add TUSB_DESC_CONFIG_ATT_SELF_POWERED or _REMOTE_WAKEUP if needed. TUD_CONFIG_DESCRIPTOR(1, ITF_NUM_TOTAL, 0, CONFIG_TOTAL_LEN, 0, 500), + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC, 6, EPNUM_CDC_NOTIF, 8, EPNUM_CDC_OUT, EPNUM_CDC_IN, 64), // IAD for Video Control #if CFG_TUD_CAM1_VIDEO_STREAMING_BULK #if CONFIG_UVC_CAM1_MULTI_FRAMESIZE @@ -235,16 +240,16 @@ uint16_t const *tud_descriptor_string_cb(uint8_t index, uint16_t langid) // Note: the 0xEE index string is a Microsoft OS 1.0 Descriptors. // https://docs.microsoft.com/en-us/windows-hardware/drivers/usbcon/microsoft-defined-usb-descriptors - if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) + if (!(index < sizeof(string_desc_arr) / sizeof(string_desc_arr[0]))) { return NULL; } const char *str = string_desc_arr[index]; - // Allow dynamic overrides for specific indices - if (index == STRID_SERIAL) + // Allow dynamic overrides for specific indices + if (index == STRID_SERIAL) str = get_serial_number(); - if (index == STRID_UVC_CAM1) + if (index == STRID_UVC_CAM1) str = get_uvc_device_name(); if (str == NULL) str = string_desc_arr[index]; diff --git a/components/usb_device_uvc/tusb/usb_descriptors.h b/components/usb_device_uvc/tusb/usb_descriptors.h index 42574cb..286a495 100644 --- a/components/usb_device_uvc/tusb/usb_descriptors.h +++ b/components/usb_device_uvc/tusb/usb_descriptors.h @@ -36,13 +36,13 @@ #define UVC_ENTITY_CAP_OUTPUT_TERMINAL 0x02 enum { +#if (CFG_TUD_CDC) + ITF_NUM_CDC, + ITF_NUM_CDC_DATA, +#endif #if (CFG_TUD_VIDEO) ITF_NUM_VIDEO_CONTROL, ITF_NUM_VIDEO_STREAMING, -#if CONFIG_UVC_SUPPORT_TWO_CAM - ITF_NUM_VIDEO_CONTROL_2, - ITF_NUM_VIDEO_STREAMING_2, -#endif #endif ITF_NUM_TOTAL }; diff --git a/main/openiris_main.cpp b/main/openiris_main.cpp index fc9900b..fdc49f5 100644 --- a/main/openiris_main.cpp +++ b/main/openiris_main.cpp @@ -29,9 +29,12 @@ #define BLINK_GPIO (gpio_num_t) CONFIG_LED_BLINK_GPIO #define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_EXTERNAL_GPIO +TaskHandle_t serialManagerHandle; + esp_timer_handle_t timerHandle; QueueHandle_t eventQueue = xQueueCreate(10, sizeof(SystemEvent)); QueueHandle_t ledStateQueue = xQueueCreate(10, sizeof(uint32_t)); +QueueHandle_t cdcMessageQueue = xQueueCreate(3, sizeof(cdc_command_packet_t)); auto *stateManager = new StateManager(eventQueue, ledStateQueue); auto dependencyRegistry = std::make_shared(); @@ -56,6 +59,9 @@ UVCStreamManager uvcStream; auto ledManager = std::make_shared(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue, deviceConfig); auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig); +void startWiFiMode(bool shouldCloseSerialManager); +void startWiredMode(bool shouldCloseSerialManager); + static void initNVSStorage() { esp_err_t ret = nvs_flash_init(); @@ -73,92 +79,34 @@ int websocket_logger(const char *format, va_list 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 -void start_video_streaming(void *arg) -{ - // Get the stored device mode StreamingMode deviceMode = deviceConfig->getDeviceMode(); - - // Check if WiFi is actually connected, not just configured - bool hasWifiCredentials = !deviceConfig->getWifiConfigs().empty() || strcmp(CONFIG_WIFI_SSID, "") != 0; - bool wifiConnected = (wifiManager->GetCurrentWiFiState() == WiFiState_e::WiFiState_Connected); - - if (deviceMode == StreamingMode::UVC) + // if we've changed the mode from auto to something else, we can clean up serial manager + // either the API endpoints or CDC will take care of further configuration + if (deviceMode == StreamingMode::WIFI) { -#ifdef CONFIG_GENERAL_DEFAULT_WIRED_MODE - 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(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 + startWiFiMode(true); } - - if ((deviceMode == StreamingMode::WIFI || deviceMode == StreamingMode::AUTO) && hasWifiCredentials && wifiConnected) + else if (deviceMode == StreamingMode::UVC) { - ESP_LOGI("[MAIN]", "Starting WiFi streaming mode."); - streamServer.startStreamServer(); + startWiredMode(true); + } + 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 { - if (hasWifiCredentials && !wifiConnected) - { - 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]", "Unknown device mode: %d", (int)deviceMode); } - - 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(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 @@ -170,55 +118,7 @@ void startup_timer_callback(void *arg) if (!getStartupCommandReceived() && !getStartupPaused()) { - ESP_LOGI("[MAIN]", "No command received during startup delay, proceeding with automatic mode startup"); - - // 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); - } + launch_streaming(); } else { @@ -237,97 +137,77 @@ void startup_timer_callback(void *arg) 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(DependencyType::project_config, deviceConfig); - dependencyRegistry->registerService(DependencyType::camera_manager, cameraHandler); - dependencyRegistry->registerService(DependencyType::wifi_manager, wifiManager); - dependencyRegistry->registerService(DependencyType::led_manager, ledManager); - // uvc plan - // cleanup the logs - done - // prepare the camera to be initialized with UVC - done? - // debug uvc performance - done + // Delete the timer before it fires + // since we've got called manually + esp_timer_delete(timerHandle); + timerHandle = nullptr; + launch_streaming(); +} - // porting plan: - // port the wifi manager first. - worky!!! - // port the logo - done - // port preferences lib - DONE; prolly temporary - // then port the config - done, needs todos done - // State Management - done - // then port the led manager as this will be fairly easy - done - // then port the mdns stuff - done - // then port the camera manager - done - // then port the streaming stuff (web and uvc) - done - // then add ADHOC and support for more networks in wifi manager - done - // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +void startWiredMode(bool shouldCloseSerialManager) +{ +#ifndef CONFIG_GENERAL_DEFAULT_WIRED_MODE + 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; + startWiFiMode(); +#else + ESP_LOGI("[MAIN]", "Starting UVC streaming mode."); + if (shouldCloseSerialManager) + { + ESP_LOGI("[MAIN]", "Closing serial manager task."); + vTaskDelete(serialManagerHandle); + } - // simplify commands - a simple dependency injection + std::function should do it - DONE - // something like - // template - // void registerService(std::shared_pointer service) - // services[std::type_index(typeid(T))] = service; - // where services is an std::unordered_map>; - // I can then use like std::shared_ptr resolve() { return services[typeid(T)]; } to get it in the command - // which can be like a second parameter of the command, like std::function + ESP_LOGI("[MAIN]", "Shutting down serial manager, CDC will take over in a bit."); + serialManager->shutdown(); - // simplify config - DONE - // here I can decouple the loading, initializing and saving logic from the config class and move - // that into the separate modules, and have the config class only act as a container + ESP_LOGI("[MAIN]", "Serial driver uninstalled"); + // Leaving a small gap for the host to see COM disappear + 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 - // 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 + ESP_LOGI("[MAIN]", "Setting up UVC Streamer"); - // rethink state management - DONE - - // 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 - - // 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(); + esp_err_t ret = uvcStream.setup(); + if (ret != ESP_OK) + { + ESP_LOGE("[MAIN]", "Failed to initialize UVC: %s", esp_err_to_name(ret)); + return; + } + ESP_LOGI("[MAIN]", "Starting CDC Serial Manager Task"); 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, + HandleCDCSerialManagerTask, + "HandleCDCSerialManagerTask", + 1024 * 6, + commandManager.get(), + 1, nullptr); - serialManager->setup(); + ESP_LOGI("[MAIN]", "Starting UVC streaming"); - 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); + 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(); mdnsManager.start(); restAPI->begin(); - cameraHandler->setupCamera(); xTaskCreate( HandleRestAPIPollTask, @@ -336,8 +216,12 @@ extern "C" void app_main(void) restAPI, 1, // it's the rest API, we only serve commands over it so we don't really need a higher priority 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]", "STARTUP: 20-SECOND DELAY MODE ACTIVE"); ESP_LOGI("[MAIN]", "====================================="); @@ -355,5 +239,77 @@ extern "C" void app_main(void) ESP_ERROR_CHECK(esp_timer_start_once(timerHandle, CONFIG_GENERAL_UVC_DELAY * 1000000)); ESP_LOGI("[MAIN]", "Started 20-second startup timer"); ESP_LOGI("[MAIN]", "Send any command within 20 seconds to enter heartbeat mode"); - setSerialManagerHandle(&serialManagerHandle); +} + +extern "C" void app_main(void) +{ + dependencyRegistry->registerService(DependencyType::project_config, deviceConfig); + dependencyRegistry->registerService(DependencyType::camera_manager, cameraHandler); + dependencyRegistry->registerService(DependencyType::wifi_manager, wifiManager); + dependencyRegistry->registerService(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(); + } } \ No newline at end of file