diff --git a/components/CameraManager/CameraManager/CameraManager.cpp b/components/CameraManager/CameraManager/CameraManager.cpp index 511a48d..1774395 100644 --- a/components/CameraManager/CameraManager/CameraManager.cpp +++ b/components/CameraManager/CameraManager/CameraManager.cpp @@ -80,10 +80,10 @@ void CameraManager::setupCameraPinout() .pixel_format = PIXFORMAT_JPEG, // YUV422,GRAYSCALE,RGB565,JPEG .frame_size = FRAMESIZE_240X240, // QQVGA-UXGA, For ESP32, do not use sizes above QVGA when not JPEG. The performance of the ESP32-S series has improved a lot, but JPEG mode always gives better frame rates. - .jpeg_quality = 7, // 0-63, for OV series camera sensors, lower number means higher quality - .fb_count = 2, // 3 // When jpeg mode is used, if fb_count more than one, the driver will work in continuous mode. - .fb_location = CAMERA_FB_IN_PSRAM, // maybe it cannot put them fully in psram? - .grab_mode = CAMERA_GRAB_WHEN_EMPTY, // CAMERA_GRAB_LATEST + .jpeg_quality = 10, // 0-63, for OV series camera sensors, lower number means higher quality + .fb_count = 2, // Use 2 for streaming to balance memory and performance + .fb_location = CAMERA_FB_IN_PSRAM, // Use PSRAM for frame buffers + .grab_mode = CAMERA_GRAB_LATEST, // Always grab the latest frame for streaming }; } diff --git a/components/CommandManager/CMakeLists.txt b/components/CommandManager/CMakeLists.txt index 30a6234..16e8afc 100644 --- a/components/CommandManager/CMakeLists.txt +++ b/components/CommandManager/CMakeLists.txt @@ -7,8 +7,9 @@ idf_component_register( "CommandManager/commands/config_commands.cpp" "CommandManager/commands/mdns_commands.cpp" "CommandManager/commands/device_commands.cpp" + "CommandManager/commands/scan_commands.cpp" INCLUDE_DIRS "CommandManager" "CommandManager/commands" - REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks + REQUIRES ProjectConfig cJSON CameraManager OpenIrisTasks wifiManager Helpers ) \ No newline at end of file diff --git a/components/CommandManager/CommandManager/CommandManager.cpp b/components/CommandManager/CommandManager/CommandManager.cpp index 9d981f1..64cba54 100644 --- a/components/CommandManager/CommandManager/CommandManager.cpp +++ b/components/CommandManager/CommandManager/CommandManager.cpp @@ -1,7 +1,9 @@ #include "CommandManager.hpp" +#include std::unordered_map commandTypeMap = { {"ping", CommandType::PING}, + {"pause", CommandType::PAUSE}, {"set_wifi", CommandType::SET_WIFI}, {"update_wifi", CommandType::UPDATE_WIFI}, {"set_streaming_mode", CommandType::SET_STREAMING_MODE}, @@ -15,6 +17,12 @@ std::unordered_map commandTypeMap = { {"get_config", CommandType::GET_CONFIG}, {"reset_config", CommandType::RESET_CONFIG}, {"restart_device", CommandType::RESTART_DEVICE}, + {"scan_networks", CommandType::SCAN_NETWORKS}, + {"start_streaming", CommandType::START_STREAMING}, + {"get_wifi_status", CommandType::GET_WIFI_STATUS}, + {"connect_wifi", CommandType::CONNECT_WIFI}, + {"switch_mode", CommandType::SWITCH_MODE}, + {"get_device_mode", CommandType::GET_DEVICE_MODE}, }; std::function CommandManager::createCommand(const CommandType type, std::string_view json) const { @@ -22,6 +30,23 @@ std::function CommandManager::createCommand(const CommandType t { case CommandType::PING: return { PingCommand }; + case CommandType::PAUSE: + return [json] { + PausePayload payload; + cJSON* root = cJSON_Parse(std::string(json).c_str()); + if (root) { + cJSON* pauseItem = cJSON_GetObjectItem(root, "pause"); + if (pauseItem && cJSON_IsBool(pauseItem)) { + payload.pause = cJSON_IsTrue(pauseItem); + } else { + payload.pause = true; // Default to pause if not specified + } + cJSON_Delete(root); + } else { + payload.pause = true; // Default to pause if parsing fails + } + return PauseCommand(payload); + }; case CommandType::SET_STREAMING_MODE: return [this, json] {return setDeviceModeCommand(this->registry, json); }; case CommandType::UPDATE_OTA_CREDENTIALS: @@ -48,6 +73,18 @@ std::function CommandManager::createCommand(const CommandType t return [this, json] { return resetConfigCommand(this->registry, json); }; case CommandType::RESTART_DEVICE: return restartDeviceCommand; + case CommandType::SCAN_NETWORKS: + return [this] { return scanNetworksCommand(this->registry); }; + case CommandType::START_STREAMING: + return startStreamingCommand; + case CommandType::GET_WIFI_STATUS: + return [this] { return getWiFiStatusCommand(this->registry); }; + case CommandType::CONNECT_WIFI: + return [this] { return connectWiFiCommand(this->registry); }; + case CommandType::SWITCH_MODE: + return [this, json] { return switchModeCommand(this->registry, json); }; + case CommandType::GET_DEVICE_MODE: + return [this] { return getDeviceModeCommand(this->registry); }; default: return nullptr; } @@ -98,11 +135,15 @@ CommandResult CommandManager::executeFromJson(const std::string_view json) const cJSON_AddItemToArray(responses, response); } - const auto jsonString = cJSON_Print(responseDocument); + char* jsonString = cJSON_Print(responseDocument); cJSON_Delete(responseDocument); cJSON_Delete(parsedJson); - return CommandResult::getSuccessResult(jsonString); + // Return the JSON response directly without wrapping it + // The responseDocument already contains the proper format: {"results": [...]} + CommandResult result = CommandResult::getRawJsonResult(jsonString); + free(jsonString); + return result; } CommandResult CommandManager::executeFromType(const CommandType type, const std::string_view json) const diff --git a/components/CommandManager/CommandManager/CommandManager.hpp b/components/CommandManager/CommandManager/CommandManager.hpp index eea0477..02231e3 100644 --- a/components/CommandManager/CommandManager/CommandManager.hpp +++ b/components/CommandManager/CommandManager/CommandManager.hpp @@ -17,12 +17,14 @@ #include "commands/mdns_commands.hpp" #include "commands/wifi_commands.hpp" #include "commands/device_commands.hpp" +#include "commands/scan_commands.hpp" #include enum class CommandType { None, PING, + PAUSE, SET_WIFI, UPDATE_OTA_CREDENTIALS, SET_STREAMING_MODE, @@ -36,6 +38,12 @@ enum class CommandType GET_CONFIG, RESET_CONFIG, RESTART_DEVICE, + SCAN_NETWORKS, + START_STREAMING, + GET_WIFI_STATUS, + CONNECT_WIFI, + SWITCH_MODE, + GET_DEVICE_MODE, }; class CommandManager diff --git a/components/CommandManager/CommandManager/CommandResult.hpp b/components/CommandManager/CommandManager/CommandResult.hpp index f033954..b73fc8a 100644 --- a/components/CommandManager/CommandManager/CommandResult.hpp +++ b/components/CommandManager/CommandManager/CommandResult.hpp @@ -3,34 +3,52 @@ #include #include +#include class CommandResult { -private: +public: enum class Status { SUCCESS, FAILURE, }; +private: Status status; std::string message; +public: CommandResult(std::string message, const Status status) { this->status = status; + + // Escape quotes and backslashes in the message for JSON + std::string escapedMessage = message; + size_t pos = 0; + // First escape backslashes + while ((pos = escapedMessage.find('\\', pos)) != std::string::npos) { + escapedMessage.replace(pos, 1, "\\\\"); + pos += 2; + } + // Then escape quotes + pos = 0; + while ((pos = escapedMessage.find('"', pos)) != std::string::npos) { + escapedMessage.replace(pos, 1, "\\\""); + pos += 2; + } + if (status == Status::SUCCESS) { // we gotta do it this way, because if we define it as { "result": " {} " } it crashes the compiler, lol - this->message = std::format("{}\"result\":\" {} \"{}", "{", message, "}"); + this->message = std::format("{}\"result\":\"{}\"{}", "{", escapedMessage, "}"); } else { - this->message = std::format("{}\"error\":\" {} \"{}", "{", message, "}"); + this->message = std::format("{}\"error\":\"{}\"{}", "{", escapedMessage, "}"); } } -public: bool isSuccess() const { return status == Status::SUCCESS; } static CommandResult getSuccessResult(const std::string &message) @@ -42,8 +60,17 @@ public: { return CommandResult(message, Status::FAILURE); } + + // Create a result that returns raw JSON without wrapper + static CommandResult getRawJsonResult(const std::string &jsonMessage) + { + CommandResult result("", Status::SUCCESS); + result.message = jsonMessage; + return result; + } std::string getResult() const { return this->message; } + }; #endif \ No newline at end of file diff --git a/components/CommandManager/CommandManager/CommandSchema.hpp b/components/CommandManager/CommandManager/CommandSchema.hpp index 991b928..72e5440 100644 --- a/components/CommandManager/CommandManager/CommandSchema.hpp +++ b/components/CommandManager/CommandManager/CommandSchema.hpp @@ -57,4 +57,9 @@ struct RestartCameraPayload : BasePayload { bool mode; }; + +struct PausePayload : BasePayload +{ + bool pause; +}; #endif \ No newline at end of file diff --git a/components/CommandManager/CommandManager/DependencyRegistry.hpp b/components/CommandManager/CommandManager/DependencyRegistry.hpp index 2a5e378..48b6ee8 100644 --- a/components/CommandManager/CommandManager/DependencyRegistry.hpp +++ b/components/CommandManager/CommandManager/DependencyRegistry.hpp @@ -7,7 +7,8 @@ enum class DependencyType { project_config, - camera_manager + camera_manager, + wifi_manager }; class DependencyRegistry diff --git a/components/CommandManager/CommandManager/commands/device_commands.cpp b/components/CommandManager/CommandManager/commands/device_commands.cpp index 6f9b552..fa5d390 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.cpp +++ b/components/CommandManager/CommandManager/commands/device_commands.cpp @@ -2,6 +2,8 @@ #include #include +#include "esp_timer.h" +#include // Implementation inspired by SummerSigh work, initial PR opened in openiris repo, adapted to this rewrite CommandResult setDeviceModeCommand(std::shared_ptr registry, std::string_view jsonPayload) { @@ -64,3 +66,66 @@ CommandResult restartDeviceCommand() { OpenIrisTasks::ScheduleRestart(2000); return CommandResult::getSuccessResult("Device restarted"); } + +CommandResult startStreamingCommand() { + activateStreaming(false); // Don't disable setup interfaces by default + return CommandResult::getSuccessResult("Streaming started"); +} + +CommandResult switchModeCommand(std::shared_ptr registry, std::string_view jsonPayload) { + const auto parsedJson = cJSON_Parse(jsonPayload.data()); + if (parsedJson == nullptr) { + return CommandResult::getErrorResult("Invalid payload"); + } + + const auto modeObject = cJSON_GetObjectItem(parsedJson, "mode"); + if (modeObject == nullptr) { + return CommandResult::getErrorResult("Invalid payload - missing mode"); + } + + const char* modeStr = modeObject->valuestring; + StreamingMode newMode; + + ESP_LOGI("[DEVICE_COMMANDS]", "Switch mode command received with mode: %s", modeStr); + + if (strcmp(modeStr, "uvc") == 0 || strcmp(modeStr, "UVC") == 0) { + newMode = StreamingMode::UVC; + } else if (strcmp(modeStr, "wifi") == 0 || strcmp(modeStr, "WiFi") == 0 || strcmp(modeStr, "WIFI") == 0) { + newMode = StreamingMode::WIFI; + } else if (strcmp(modeStr, "auto") == 0 || strcmp(modeStr, "AUTO") == 0) { + newMode = StreamingMode::AUTO; + } else { + return CommandResult::getErrorResult("Invalid mode - use 'uvc', 'wifi', or 'auto'"); + } + + const auto projectConfig = registry->resolve(DependencyType::project_config); + ESP_LOGI("[DEVICE_COMMANDS]", "Setting device mode to: %d", (int)newMode); + projectConfig->setDeviceMode(newMode); + + cJSON_Delete(parsedJson); + + return CommandResult::getSuccessResult("Device mode switched, restart to apply"); +} + +CommandResult getDeviceModeCommand(std::shared_ptr registry) { + const auto projectConfig = registry->resolve(DependencyType::project_config); + StreamingMode currentMode = projectConfig->getDeviceMode(); + + const char* modeStr = "unknown"; + switch (currentMode) { + case StreamingMode::UVC: + modeStr = "UVC"; + break; + case StreamingMode::WIFI: + modeStr = "WiFi"; + break; + case StreamingMode::AUTO: + modeStr = "Auto"; + break; + } + + char result[100]; + sprintf(result, "{\"mode\":\"%s\",\"value\":%d}", modeStr, (int)currentMode); + + return CommandResult::getSuccessResult(result); +} diff --git a/components/CommandManager/CommandManager/commands/device_commands.hpp b/components/CommandManager/CommandManager/commands/device_commands.hpp index 2532624..5685f13 100644 --- a/components/CommandManager/CommandManager/commands/device_commands.hpp +++ b/components/CommandManager/CommandManager/commands/device_commands.hpp @@ -6,4 +6,10 @@ CommandResult setDeviceModeCommand(std::shared_ptr registry, CommandResult updateOTACredentialsCommand(std::shared_ptr registry, std::string_view jsonPayload); -CommandResult restartDeviceCommand(); \ No newline at end of file +CommandResult restartDeviceCommand(); + +CommandResult startStreamingCommand(); + +CommandResult switchModeCommand(std::shared_ptr registry, std::string_view jsonPayload); + +CommandResult getDeviceModeCommand(std::shared_ptr registry); \ No newline at end of file diff --git a/components/CommandManager/CommandManager/commands/scan_commands.cpp b/components/CommandManager/CommandManager/commands/scan_commands.cpp new file mode 100644 index 0000000..054a621 --- /dev/null +++ b/components/CommandManager/CommandManager/commands/scan_commands.cpp @@ -0,0 +1,38 @@ +#include "scan_commands.hpp" +#include "cJSON.h" +#include "esp_log.h" +#include + +CommandResult scanNetworksCommand(std::shared_ptr registry) { + auto wifiManager = registry->resolve(DependencyType::wifi_manager); + if (!wifiManager) { + return CommandResult::getErrorResult("WiFiManager not available"); + } + + auto networks = wifiManager->ScanNetworks(); + + cJSON *root = cJSON_CreateObject(); + cJSON *networksArray = cJSON_CreateArray(); + cJSON_AddItemToObject(root, "networks", networksArray); + + for (const auto& network : networks) { + cJSON *networkObject = cJSON_CreateObject(); + cJSON_AddStringToObject(networkObject, "ssid", network.ssid.c_str()); + cJSON_AddNumberToObject(networkObject, "channel", network.channel); + cJSON_AddNumberToObject(networkObject, "rssi", network.rssi); + char mac_str[18]; + sprintf(mac_str, "%02x:%02x:%02x:%02x:%02x:%02x", + network.mac[0], network.mac[1], network.mac[2], + network.mac[3], network.mac[4], network.mac[5]); + cJSON_AddStringToObject(networkObject, "mac_address", mac_str); + cJSON_AddNumberToObject(networkObject, "auth_mode", network.auth_mode); + cJSON_AddItemToArray(networksArray, networkObject); + } + + char *json_string = cJSON_PrintUnformatted(root); + printf("%s\n", json_string); + cJSON_Delete(root); + free(json_string); + + return CommandResult::getSuccessResult("Networks scanned"); +} \ No newline at end of file diff --git a/components/CommandManager/CommandManager/commands/scan_commands.hpp b/components/CommandManager/CommandManager/commands/scan_commands.hpp new file mode 100644 index 0000000..7e7df86 --- /dev/null +++ b/components/CommandManager/CommandManager/commands/scan_commands.hpp @@ -0,0 +1,10 @@ +#ifndef SCAN_COMMANDS_HPP +#define SCAN_COMMANDS_HPP + +#include "../CommandResult.hpp" +#include "../DependencyRegistry.hpp" +#include + +CommandResult scanNetworksCommand(std::shared_ptr registry); + +#endif \ No newline at end of file diff --git a/components/CommandManager/CommandManager/commands/simple_commands.cpp b/components/CommandManager/CommandManager/commands/simple_commands.cpp index b1fc7b9..909568a 100644 --- a/components/CommandManager/CommandManager/commands/simple_commands.cpp +++ b/components/CommandManager/CommandManager/commands/simple_commands.cpp @@ -1,6 +1,25 @@ #include "simple_commands.hpp" +#include "main_globals.hpp" +#include "esp_log.h" + +static const char* TAG = "SimpleCommands"; CommandResult PingCommand() { return CommandResult::getSuccessResult("pong"); }; + +CommandResult PauseCommand(const PausePayload& payload) +{ + ESP_LOGI(TAG, "Pause command received: %s", payload.pause ? "true" : "false"); + + startupPaused = payload.pause; + + if (payload.pause) { + ESP_LOGI(TAG, "Startup paused - device will remain in configuration mode"); + return CommandResult::getSuccessResult("Startup paused"); + } else { + ESP_LOGI(TAG, "Startup resumed"); + return CommandResult::getSuccessResult("Startup resumed"); + } +}; diff --git a/components/CommandManager/CommandManager/commands/simple_commands.hpp b/components/CommandManager/CommandManager/commands/simple_commands.hpp index 0883da9..9db7ea9 100644 --- a/components/CommandManager/CommandManager/commands/simple_commands.hpp +++ b/components/CommandManager/CommandManager/commands/simple_commands.hpp @@ -3,7 +3,9 @@ #include #include "CommandResult.hpp" +#include "CommandSchema.hpp" CommandResult PingCommand(); +CommandResult PauseCommand(const PausePayload& payload); #endif \ No newline at end of file diff --git a/components/CommandManager/CommandManager/commands/wifi_commands.cpp b/components/CommandManager/CommandManager/commands/wifi_commands.cpp index 5f671f9..8ca0fcf 100644 --- a/components/CommandManager/CommandManager/commands/wifi_commands.cpp +++ b/components/CommandManager/CommandManager/commands/wifi_commands.cpp @@ -1,4 +1,5 @@ #include "wifi_commands.hpp" +#include "esp_netif.h" std::optional parseSetWiFiCommandPayload(std::string_view jsonPayload) { @@ -223,3 +224,79 @@ CommandResult updateAPWiFiCommand(std::shared_ptr registry, return CommandResult::getSuccessResult("Config updated"); } + +CommandResult getWiFiStatusCommand(std::shared_ptr registry) { + auto wifiManager = registry->resolve(DependencyType::wifi_manager); + auto projectConfig = registry->resolve(DependencyType::project_config); + + // Get current WiFi state + auto wifiState = wifiManager->GetCurrentWiFiState(); + auto networks = projectConfig->getWifiConfigs(); + + cJSON* statusJson = cJSON_CreateObject(); + + // Add WiFi state + const char* stateStr = "unknown"; + switch(wifiState) { + case WiFiState_e::WiFiState_NotInitialized: + stateStr = "not_initialized"; + break; + case WiFiState_e::WiFiState_Initialized: + stateStr = "initialized"; + break; + case WiFiState_e::WiFiState_ReadyToConnect: + stateStr = "ready"; + break; + case WiFiState_e::WiFiState_Connecting: + stateStr = "connecting"; + break; + case WiFiState_e::WiFiState_WaitingForIp: + stateStr = "waiting_for_ip"; + break; + case WiFiState_e::WiFiState_Connected: + stateStr = "connected"; + break; + case WiFiState_e::WiFiState_Disconnected: + stateStr = "disconnected"; + break; + case WiFiState_e::WiFiState_Error: + stateStr = "error"; + break; + } + cJSON_AddStringToObject(statusJson, "status", stateStr); + cJSON_AddNumberToObject(statusJson, "networks_configured", networks.size()); + + // Add IP info if connected + if (wifiState == WiFiState_e::WiFiState_Connected) { + // Get IP address from ESP32 + esp_netif_ip_info_t ip_info; + esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + if (netif && esp_netif_get_ip_info(netif, &ip_info) == ESP_OK) { + char ip_str[16]; + sprintf(ip_str, IPSTR, IP2STR(&ip_info.ip)); + cJSON_AddStringToObject(statusJson, "ip_address", ip_str); + } + } + + char* statusString = cJSON_PrintUnformatted(statusJson); + std::string result(statusString); + free(statusString); + cJSON_Delete(statusJson); + + return CommandResult::getSuccessResult(result); +} + +CommandResult connectWiFiCommand(std::shared_ptr registry) { + auto wifiManager = registry->resolve(DependencyType::wifi_manager); + auto projectConfig = registry->resolve(DependencyType::project_config); + + auto networks = projectConfig->getWifiConfigs(); + if (networks.empty()) { + return CommandResult::getErrorResult("No WiFi networks configured"); + } + + // Trigger WiFi connection attempt + wifiManager->TryConnectToStoredNetworks(); + + return CommandResult::getSuccessResult("WiFi connection attempt started"); +} diff --git a/components/CommandManager/CommandManager/commands/wifi_commands.hpp b/components/CommandManager/CommandManager/commands/wifi_commands.hpp index e344427..f98245d 100644 --- a/components/CommandManager/CommandManager/commands/wifi_commands.hpp +++ b/components/CommandManager/CommandManager/commands/wifi_commands.hpp @@ -1,4 +1,6 @@ #include +#include +#include #include #include #include @@ -18,3 +20,6 @@ CommandResult updateWiFiCommand(std::shared_ptr registry, st std::optional parseUpdateAPWiFiCommandPayload(std::string_view jsonPayload); CommandResult updateAPWiFiCommand(std::shared_ptr registry, std::string_view jsonPayload); + +CommandResult getWiFiStatusCommand(std::shared_ptr registry); +CommandResult connectWiFiCommand(std::shared_ptr registry); diff --git a/components/Helpers/CMakeLists.txt b/components/Helpers/CMakeLists.txt index 238bb60..5d592c6 100644 --- a/components/Helpers/CMakeLists.txt +++ b/components/Helpers/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "Helpers/helpers.cpp" +idf_component_register(SRCS "Helpers/helpers.cpp" "Helpers/main_globals.cpp" INCLUDE_DIRS "Helpers" REQUIRES esp_timer ) \ No newline at end of file diff --git a/components/Helpers/Helpers/main_globals.cpp b/components/Helpers/Helpers/main_globals.cpp new file mode 100644 index 0000000..8ed665d --- /dev/null +++ b/components/Helpers/Helpers/main_globals.cpp @@ -0,0 +1,40 @@ +#include "main_globals.hpp" +#include "esp_log.h" + +// Forward declarations +extern void start_video_streaming(void *arg); + +// Global variables to be set by main +static esp_timer_handle_t* g_streaming_timer_handle = nullptr; +static TaskHandle_t* g_serial_manager_handle = nullptr; + +// Functions for main to set the global handles +void setStreamingTimerHandle(esp_timer_handle_t* handle) { + g_streaming_timer_handle = handle; +} + +void setSerialManagerHandle(TaskHandle_t* handle) { + g_serial_manager_handle = handle; +} + +// Functions for components to access the handles +esp_timer_handle_t* getStreamingTimerHandle() { + return g_streaming_timer_handle; +} + +TaskHandle_t* getSerialManagerHandle() { + return g_serial_manager_handle; +} + +// Global pause state +bool startupPaused = false; + +// Function to manually activate streaming +void activateStreaming(bool disableSetup) { + 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); +} \ No newline at end of file diff --git a/components/Helpers/Helpers/main_globals.hpp b/components/Helpers/Helpers/main_globals.hpp new file mode 100644 index 0000000..d13f7fc --- /dev/null +++ b/components/Helpers/Helpers/main_globals.hpp @@ -0,0 +1,28 @@ +#pragma once +#ifndef MAIN_GLOBALS_HPP +#define MAIN_GLOBALS_HPP + +#include "esp_timer.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" + +// Functions for main to set global handles +void setStreamingTimerHandle(esp_timer_handle_t* handle); +void setSerialManagerHandle(TaskHandle_t* handle); + +// Functions to access global handles from components +esp_timer_handle_t* getStreamingTimerHandle(); +TaskHandle_t* getSerialManagerHandle(); + +// Function to manually activate streaming +void activateStreaming(bool disableSetup = false); + +// Function to notify that a command was received during startup +extern void notify_startup_command_received(); + +// Global variables for startup state +extern bool startupCommandReceived; +extern esp_timer_handle_t startupTimerHandle; +extern bool startupPaused; + +#endif \ No newline at end of file diff --git a/components/ProjectConfig/ProjectConfig/Models.hpp b/components/ProjectConfig/ProjectConfig/Models.hpp index f931775..d2a01c1 100644 --- a/components/ProjectConfig/ProjectConfig/Models.hpp +++ b/components/ProjectConfig/ProjectConfig/Models.hpp @@ -8,6 +8,7 @@ #include #include "sdkconfig.h" #include +#include "esp_log.h" struct BaseConfigModel { @@ -31,11 +32,14 @@ struct DeviceMode_t : BaseConfigModel { explicit DeviceMode_t( Preferences *pref) : BaseConfigModel(pref), mode(StreamingMode::AUTO){} void load() { - this->mode = static_cast(this->pref->getInt("mode", 0)); + int stored_mode = this->pref->getInt("mode", 0); + this->mode = static_cast(stored_mode); + ESP_LOGI("DeviceMode", "Loaded device mode: %d", stored_mode); } void save() const { this->pref->putInt("mode", static_cast(this->mode)); + ESP_LOGI("DeviceMode", "Saved device mode: %d", static_cast(this->mode)); } }; @@ -182,6 +186,9 @@ struct WiFiConfig_t : BaseConfigModel this->password = this->pref->getString(("password" + iter_str).c_str(), ""); this->channel = this->pref->getUInt(("channel" + iter_str).c_str()); this->power = this->pref->getUInt(("power" + iter_str).c_str()); + + ESP_LOGI("WiFiConfig", "Loaded network %d: name=%s, ssid=%s, channel=%d", + index, this->name.c_str(), this->ssid.c_str(), this->channel); }; void save() const { @@ -193,6 +200,9 @@ struct WiFiConfig_t : BaseConfigModel this->pref->putString(("password" + iter_str).c_str(), this->password.c_str()); this->pref->putUInt(("channel" + iter_str).c_str(), this->channel); this->pref->putUInt(("power" + iter_str).c_str(), this->power); + + ESP_LOGI("WiFiConfig", "Saved network %d: name=%s, ssid=%s, channel=%d", + this->index, this->name.c_str(), this->ssid.c_str(), this->channel); }; std::string toRepresentation() diff --git a/components/ProjectConfig/ProjectConfig/ProjectConfig.cpp b/components/ProjectConfig/ProjectConfig/ProjectConfig.cpp index eb42bdc..ae7b189 100644 --- a/components/ProjectConfig/ProjectConfig/ProjectConfig.cpp +++ b/components/ProjectConfig/ProjectConfig/ProjectConfig.cpp @@ -147,6 +147,8 @@ void ProjectConfig::setWifiConfig(const std::string &networkName, it->password = password; it->channel = channel; it->power = power; + // Save the updated network immediately + it->save(); return; } @@ -155,6 +157,9 @@ void ProjectConfig::setWifiConfig(const std::string &networkName, ESP_LOGI(CONFIGURATION_TAG, "No networks, We're adding a new network"); this->config.networks.emplace_back(this->pref, static_cast(0), networkName, ssid, password, channel, power); + // Save the new network immediately + this->config.networks.back().save(); + saveNetworkCount(this->pref, 1); return; } @@ -168,6 +173,9 @@ void ProjectConfig::setWifiConfig(const std::string &networkName, uint8_t last_index = getNetworkCount(this->pref); this->config.networks.emplace_back(this->pref, last_index, networkName, ssid, password, channel, power); + // Save the new network immediately + this->config.networks.back().save(); + saveNetworkCount(this->pref, static_cast(this->config.networks.size())); } else { @@ -212,6 +220,7 @@ void ProjectConfig::setAPWifiConfig(const std::string &ssid, void ProjectConfig::setDeviceMode(const StreamingMode deviceMode) { this->config.device_mode.mode = deviceMode; + this->config.device_mode.save(); // Save immediately } //********************************************************************************************************************** @@ -249,6 +258,10 @@ TrackerConfig_t &ProjectConfig::getTrackerConfig() return this->config; } -DeviceMode_t &ProjectConfig::getDeviceMode() { +DeviceMode_t &ProjectConfig::getDeviceModeConfig() { return this->config.device_mode; +} + +StreamingMode ProjectConfig::getDeviceMode() { + return this->config.device_mode.mode; } \ No newline at end of file diff --git a/components/ProjectConfig/ProjectConfig/ProjectConfig.hpp b/components/ProjectConfig/ProjectConfig/ProjectConfig.hpp index da59033..b256c22 100644 --- a/components/ProjectConfig/ProjectConfig/ProjectConfig.hpp +++ b/components/ProjectConfig/ProjectConfig/ProjectConfig.hpp @@ -31,7 +31,7 @@ public: bool reset(); DeviceConfig_t &getDeviceConfig(); - DeviceMode_t &getDeviceMode(); + DeviceMode_t &getDeviceModeConfig(); CameraConfig_t &getCameraConfig(); std::vector &getWifiConfigs(); AP_WiFiConfig_t &getAPWifiConfig(); @@ -61,6 +61,7 @@ public: uint8_t channel); void setWiFiTxPower(uint8_t power); void setDeviceMode(StreamingMode deviceMode); + StreamingMode getDeviceMode(); private: Preferences *pref; diff --git a/components/SerialManager/CMakeLists.txt b/components/SerialManager/CMakeLists.txt index 0cd7215..aaaa174 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 + REQUIRES esp_driver_uart CommandManager ProjectConfig ) \ No newline at end of file diff --git a/components/SerialManager/SerialManager/SerialManager.cpp b/components/SerialManager/SerialManager/SerialManager.cpp index 93d6a3e..d1c60c9 100644 --- a/components/SerialManager/SerialManager/SerialManager.cpp +++ b/components/SerialManager/SerialManager/SerialManager.cpp @@ -1,8 +1,11 @@ #include "SerialManager.hpp" +#include "esp_log.h" +#include "main_globals.hpp" #define BUF_SIZE (1024) -SerialManager::SerialManager(std::shared_ptr commandManager, esp_timer_handle_t *timerHandle) : commandManager(commandManager), timerHandle(timerHandle) { +SerialManager::SerialManager(std::shared_ptr commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr deviceConfig) + : commandManager(commandManager), timerHandle(timerHandle), deviceConfig(deviceConfig) { this->data = static_cast(malloc(BUF_SIZE)); this->temp_data = static_cast(malloc(256)); } @@ -20,9 +23,6 @@ void SerialManager::try_receive() int current_position = 0; int len = usb_serial_jtag_read_bytes(this->temp_data, 256, 1000 / 20); - if (len) { - esp_timer_stop(*timerHandle); - } // since we've got something on the serial port // we gotta keep reading until we've got the whole message while (len) @@ -38,19 +38,73 @@ void SerialManager::try_receive() data[current_position] = '\0'; current_position = 0; + // 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(); usb_serial_jtag_write_bytes(resultMessage.c_str(), resultMessage.length(), 1000 / 20); - esp_timer_start_once(*timerHandle, 30000000); // 30s } } +void SerialManager::send_heartbeat() +{ + // Get the MAC address as unique identifier + uint8_t mac[6]; + esp_read_mac(mac, ESP_MAC_WIFI_STA); + + // Format as serial number string + char serial_number[18]; + sprintf(serial_number, "%02X%02X%02X%02X%02X%02X", + mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + + // Create heartbeat JSON with serial number + char heartbeat[128]; + sprintf(heartbeat, "{\"heartbeat\":\"openiris_setup_mode\",\"serial\":\"%s\"}\n", serial_number); + + usb_serial_jtag_write_bytes(heartbeat, strlen(heartbeat), 1000 / 20); +} + +bool SerialManager::should_send_heartbeat() +{ + // Always send heartbeat during startup delay or if no WiFi configured + extern bool startupCommandReceived; + extern esp_timer_handle_t startupTimerHandle; + + // If startup timer is still running, always send heartbeat + if (startupTimerHandle != nullptr) { + return true; + } + + // If in heartbeat mode after startup, continue sending + if (startupCommandReceived) { + return true; + } + + // Otherwise, only send if no WiFi credentials configured + const auto wifiConfigs = deviceConfig->getWifiConfigs(); + return wifiConfigs.empty(); +} + void HandleSerialManagerTask(void *pvParameters) { auto const serialManager = static_cast(pvParameters); + TickType_t lastHeartbeat = xTaskGetTickCount(); + const TickType_t heartbeatInterval = pdMS_TO_TICKS(2000); // 2 second heartbeat while (true) { serialManager->try_receive(); + + // Send heartbeat every 2 seconds, but only if no WiFi credentials are set + TickType_t currentTime = xTaskGetTickCount(); + if ((currentTime - lastHeartbeat) >= heartbeatInterval) { + if (serialManager->should_send_heartbeat()) { + serialManager->send_heartbeat(); + } + lastHeartbeat = currentTime; + } + + vTaskDelay(pdMS_TO_TICKS(50)); // Small delay to prevent busy waiting } } \ No newline at end of file diff --git a/components/SerialManager/SerialManager/SerialManager.hpp b/components/SerialManager/SerialManager/SerialManager.hpp index e3fca9a..a8c92c9 100644 --- a/components/SerialManager/SerialManager/SerialManager.hpp +++ b/components/SerialManager/SerialManager/SerialManager.hpp @@ -6,6 +6,7 @@ #include #include #include +#include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/queue.h" @@ -15,17 +16,21 @@ #include "driver/usb_serial_jtag.h" #include "esp_vfs_usb_serial_jtag.h" #include "esp_vfs_dev.h" +#include "esp_mac.h" class SerialManager { public: - explicit SerialManager(std::shared_ptr commandManager, esp_timer_handle_t *timerHandle); + explicit SerialManager(std::shared_ptr commandManager, esp_timer_handle_t *timerHandle, std::shared_ptr deviceConfig); void setup(); void try_receive(); + void send_heartbeat(); + bool should_send_heartbeat(); private: std::shared_ptr commandManager; esp_timer_handle_t *timerHandle; + std::shared_ptr deviceConfig; uint8_t *data; uint8_t *temp_data; }; diff --git a/components/StreamServer/StreamServer/StreamServer.cpp b/components/StreamServer/StreamServer/StreamServer.cpp index cf666f6..c40f5cc 100644 --- a/components/StreamServer/StreamServer/StreamServer.cpp +++ b/components/StreamServer/StreamServer/StreamServer.cpp @@ -38,8 +38,11 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req) if (!fb) { - ESP_LOGE(STREAM_SERVER_TAG, "Camera capture failed with resposne %s", esp_err_to_name(response)); + ESP_LOGE(STREAM_SERVER_TAG, "Camera capture failed"); response = ESP_FAIL; + // Don't break immediately, try to recover + vTaskDelay(pdMS_TO_TICKS(10)); + continue; } else { @@ -70,10 +73,15 @@ esp_err_t StreamHelpers::stream(httpd_req_t *req) } if (response != ESP_OK) break; - long request_end = Helpers::getTimeInMillis(); - long latency = (request_end - last_request_time); - last_request_time = request_end; - ESP_LOGI(STREAM_SERVER_TAG, "Size: %uKB, Time: %lims (%lifps)\n", _jpg_buf_len / 1024, latency, 1000 / latency); + + // Only log every 100 frames to reduce overhead + static int frame_count = 0; + if (++frame_count % 100 == 0) { + long request_end = Helpers::getTimeInMillis(); + long latency = (request_end - last_request_time); + last_request_time = request_end; + ESP_LOGI(STREAM_SERVER_TAG, "Size: %uKB, Time: %lims (%lifps)", _jpg_buf_len / 1024, latency, 1000 / latency); + } } last_frame = 0; return response; @@ -93,8 +101,9 @@ esp_err_t StreamServer::startStreamServer() config.max_uri_handlers = 10; config.server_port = STREAM_SERVER_PORT; config.ctrl_port = STREAM_SERVER_PORT; - config.recv_wait_timeout = 100; - config.send_wait_timeout = 100; + config.recv_wait_timeout = 5; // 5 seconds for receiving + config.send_wait_timeout = 5; // 5 seconds for sending + config.lru_purge_enable = true; // Enable LRU purge for better connection handling httpd_uri_t stream_page = { .uri = "/", diff --git a/components/UVCStream/UVCStream/UVCStream.cpp b/components/UVCStream/UVCStream/UVCStream.cpp index d5667d0..e4ad8a7 100644 --- a/components/UVCStream/UVCStream/UVCStream.cpp +++ b/components/UVCStream/UVCStream/UVCStream.cpp @@ -149,5 +149,12 @@ esp_err_t UVCStreamManager::setup() } ESP_LOGI(UVC_STREAM_TAG, "Initialized UVC Device"); + return ESP_OK; +} + +esp_err_t UVCStreamManager::start() +{ + ESP_LOGI(UVC_STREAM_TAG, "Starting UVC streaming"); + // UVC device is already initialized in setup(), just log that we're starting return ESP_OK; } \ No newline at end of file diff --git a/components/UVCStream/UVCStream/UVCStream.hpp b/components/UVCStream/UVCStream/UVCStream.hpp index 614972b..9048569 100644 --- a/components/UVCStream/UVCStream/UVCStream.hpp +++ b/components/UVCStream/UVCStream/UVCStream.hpp @@ -54,6 +54,7 @@ class UVCStreamManager public: esp_err_t setup(); + esp_err_t start(); }; #endif // UVCSTREAM_HPP diff --git a/components/wifiManager/CMakeLists.txt b/components/wifiManager/CMakeLists.txt index 37fcbe2..bb241da 100644 --- a/components/wifiManager/CMakeLists.txt +++ b/components/wifiManager/CMakeLists.txt @@ -1,4 +1,4 @@ -idf_component_register(SRCS "wifiManager/wifiManager.cpp" +idf_component_register(SRCS "wifiManager/wifiManager.cpp" "wifiManager/WiFiScanner.cpp" INCLUDE_DIRS "wifiManager" REQUIRES esp_wifi nvs_flash esp_event esp_netif lwip StateManager ProjectConfig ) \ No newline at end of file diff --git a/components/wifiManager/wifiManager/WiFiScanner.cpp b/components/wifiManager/wifiManager/WiFiScanner.cpp new file mode 100644 index 0000000..8e7f171 --- /dev/null +++ b/components/wifiManager/wifiManager/WiFiScanner.cpp @@ -0,0 +1,205 @@ +#include "WiFiScanner.hpp" +#include + +WiFiScanner::WiFiScanner() {} + +void WiFiScanner::scanResultCallback(void* arg, esp_event_base_t event_base, + int32_t event_id, void* event_data) { + auto* scanner = static_cast(arg); + if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) { + uint16_t ap_count = 0; + esp_wifi_scan_get_ap_num(&ap_count); + + if (ap_count == 0) { + ESP_LOGI(TAG, "No access points found"); + return; + } + + wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count]; + ESP_ERROR_CHECK(esp_wifi_scan_get_ap_records(&ap_count, ap_records)); + + scanner->networks.clear(); + for (uint16_t i = 0; i < ap_count; i++) { + WiFiNetwork network; + network.ssid = std::string(reinterpret_cast(ap_records[i].ssid)); + network.channel = ap_records[i].primary; + network.rssi = ap_records[i].rssi; + memcpy(network.mac, ap_records[i].bssid, 6); + network.auth_mode = ap_records[i].authmode; + + scanner->networks.push_back(network); + } + + delete[] ap_records; + ESP_LOGI(TAG, "Found %d access points", ap_count); + } +} + +std::vector WiFiScanner::scanNetworks() { + std::vector scan_results; + + // Check if WiFi is initialized + wifi_mode_t mode; + esp_err_t err = esp_wifi_get_mode(&mode); + if (err == ESP_ERR_WIFI_NOT_INIT) { + ESP_LOGE(TAG, "WiFi not initialized"); + return scan_results; + } + + + // Give WiFi more time to be ready + vTaskDelay(pdMS_TO_TICKS(500)); + + // Stop any ongoing scan + esp_wifi_scan_stop(); + + // Try sequential channel scanning as a workaround + bool try_sequential_scan = true; // Enable sequential scan + + if (!try_sequential_scan) { + // Normal all-channel scan + wifi_scan_config_t scan_config = { + .ssid = nullptr, + .bssid = nullptr, + .channel = 0, // 0 means scan all channels + .show_hidden = true, + .scan_type = WIFI_SCAN_TYPE_ACTIVE, // Active scan + .scan_time = { + .active = { + .min = 120, // Min per channel + .max = 300 // Max per channel + }, + .passive = 360 + }, + .home_chan_dwell_time = 0, // 0 for default + .channel_bitmap = 0 // 0 for all channels + }; + + err = esp_wifi_scan_start(&scan_config, false); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to start scan: %s", esp_err_to_name(err)); + return scan_results; + } + } else { + // Sequential channel scan - scan each channel individually + std::vector all_records; + + for (uint8_t ch = 1; ch <= 13; ch++) { + wifi_scan_config_t scan_config = { + .ssid = nullptr, + .bssid = nullptr, + .channel = ch, // Specific channel + .show_hidden = true, + .scan_type = WIFI_SCAN_TYPE_ACTIVE, + .scan_time = { + .active = { + .min = 100, + .max = 200 + }, + .passive = 300 + }, + .home_chan_dwell_time = 0, + .channel_bitmap = 0 + }; + + err = esp_wifi_scan_start(&scan_config, true); // Blocking scan + if (err == ESP_OK) { + uint16_t ch_count = 0; + esp_wifi_scan_get_ap_num(&ch_count); + if (ch_count > 0) { + wifi_ap_record_t* ch_records = new wifi_ap_record_t[ch_count]; + if (esp_wifi_scan_get_ap_records(&ch_count, ch_records) == ESP_OK) { + for (uint16_t i = 0; i < ch_count; i++) { + all_records.push_back(ch_records[i]); + } + } + delete[] ch_records; + } + } + vTaskDelay(pdMS_TO_TICKS(50)); + } + + // Process all collected records + for (const auto& record : all_records) { + WiFiNetwork network; + network.ssid = std::string(reinterpret_cast(record.ssid)); + network.channel = record.primary; + network.rssi = record.rssi; + memcpy(network.mac, record.bssid, 6); + network.auth_mode = record.authmode; + scan_results.push_back(network); + } + + // Skip the normal result processing + return scan_results; + } + + // Wait for scan completion with timeout + int timeout_ms = 15000; // 15 second timeout + int elapsed_ms = 0; + + while (elapsed_ms < timeout_ms) { + uint16_t temp_count = 0; + esp_err_t count_err = esp_wifi_scan_get_ap_num(&temp_count); + + if (count_err == ESP_OK) { + // Wait a bit longer after finding networks to ensure scan is complete + if (temp_count > 0 && elapsed_ms > 5000) { + break; + } + } + + vTaskDelay(pdMS_TO_TICKS(200)); + elapsed_ms += 200; + } + + if (elapsed_ms >= timeout_ms) { + ESP_LOGE(TAG, "Scan timeout after %d ms", timeout_ms); + esp_wifi_scan_stop(); + return scan_results; + } + + // Get scan results + uint16_t ap_count = 0; + esp_wifi_scan_get_ap_num(&ap_count); + + if (ap_count == 0) { + ESP_LOGI(TAG, "No access points found"); + return scan_results; + } + + wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count]; + err = esp_wifi_scan_get_ap_records(&ap_count, ap_records); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to get scan records: %s", esp_err_to_name(err)); + delete[] ap_records; + return scan_results; + } + + // Build the results vector and track channels found + bool channels_found[15] = {false}; // Track channels 0-14 + + for (uint16_t i = 0; i < ap_count; i++) { + WiFiNetwork network; + network.ssid = std::string(reinterpret_cast(ap_records[i].ssid)); + network.channel = ap_records[i].primary; + network.rssi = ap_records[i].rssi; + memcpy(network.mac, ap_records[i].bssid, 6); + network.auth_mode = ap_records[i].authmode; + + if (network.channel <= 14) { + channels_found[network.channel] = true; + } + + scan_results.push_back(network); + } + + + delete[] ap_records; + ESP_LOGI(TAG, "Found %d access points", ap_count); + + // Also update the member variable for compatibility + networks = scan_results; + + return scan_results; +} \ No newline at end of file diff --git a/components/wifiManager/wifiManager/WiFiScanner.hpp b/components/wifiManager/wifiManager/WiFiScanner.hpp new file mode 100644 index 0000000..acb4d6e --- /dev/null +++ b/components/wifiManager/wifiManager/WiFiScanner.hpp @@ -0,0 +1,29 @@ +#pragma once +#ifndef WIFI_SCANNER_HPP +#define WIFI_SCANNER_HPP + +#include +#include +#include "esp_wifi.h" +#include "esp_log.h" + +struct WiFiNetwork { + std::string ssid; + uint8_t channel; + int8_t rssi; + uint8_t mac[6]; + wifi_auth_mode_t auth_mode; +}; + +class WiFiScanner { +public: + WiFiScanner(); + std::vector scanNetworks(); + static void scanResultCallback(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); + +private: + static constexpr char const* TAG = "WiFiScanner"; + std::vector networks; +}; + +#endif \ No newline at end of file diff --git a/components/wifiManager/wifiManager/wifiManager.cpp b/components/wifiManager/wifiManager/wifiManager.cpp index 616b383..ffbae5c 100644 --- a/components/wifiManager/wifiManager/wifiManager.cpp +++ b/components/wifiManager/wifiManager/wifiManager.cpp @@ -2,6 +2,10 @@ static auto WIFI_MANAGER_TAG = "[WIFI_MANAGER]"; +// Define the global variables declared as extern in the header +int s_retry_num = 0; +EventGroupHandle_t s_wifi_event_group; + void WiFiManagerHelpers::event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data) { @@ -15,6 +19,9 @@ void WiFiManagerHelpers::event_handler(void *arg, esp_event_base_t event_base, } else if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_DISCONNECTED) { + wifi_event_sta_disconnected_t* disconnected = (wifi_event_sta_disconnected_t*) event_data; + ESP_LOGI(WIFI_MANAGER_TAG, "Disconnect reason: %d", disconnected->reason); + if (s_retry_num < EXAMPLE_ESP_MAXIMUM_RETRY) { esp_wifi_connect(); @@ -37,19 +44,70 @@ void WiFiManagerHelpers::event_handler(void *arg, esp_event_base_t event_base, } } -WiFiManager::WiFiManager(std::shared_ptr deviceConfig, QueueHandle_t eventQueue, StateManager *stateManager) : deviceConfig(deviceConfig), eventQueue(eventQueue), stateManager(stateManager) {} +WiFiManager::WiFiManager(std::shared_ptr deviceConfig, QueueHandle_t eventQueue, StateManager *stateManager) + : deviceConfig(deviceConfig), eventQueue(eventQueue), stateManager(stateManager), wifiScanner(std::make_unique()) {} void WiFiManager::SetCredentials(const char *ssid, const char *password) { - memcpy(_wifi_cfg.sta.ssid, ssid, std::min(strlen(ssid), sizeof(_wifi_cfg.sta.ssid))); - - memcpy(_wifi_cfg.sta.password, password, std::min(strlen(password), sizeof(_wifi_cfg.sta.password))); + // Clear the config first + memset(&_wifi_cfg, 0, sizeof(_wifi_cfg)); + + // Copy SSID with null termination + size_t ssid_len = std::min(strlen(ssid), sizeof(_wifi_cfg.sta.ssid) - 1); + memcpy(_wifi_cfg.sta.ssid, ssid, ssid_len); + _wifi_cfg.sta.ssid[ssid_len] = '\0'; + + // Copy password with null termination + size_t pass_len = std::min(strlen(password), sizeof(_wifi_cfg.sta.password) - 1); + memcpy(_wifi_cfg.sta.password, password, pass_len); + _wifi_cfg.sta.password[pass_len] = '\0'; + + // Set other required fields + // Use open auth if no password, otherwise allow any WPA variant + if (strlen(password) == 0) { + _wifi_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN; + } else { + // IMPORTANT: Set threshold to WEP to accept ANY security mode >= WEP + // This allows WPA, WPA2, WPA3, etc. The driver will negotiate the highest common mode + _wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WEP; + } + + // CRITICAL: Disable PMF completely - this often causes handshake timeouts + _wifi_cfg.sta.pmf_cfg.capable = false; + _wifi_cfg.sta.pmf_cfg.required = false; + + // IMPORTANT: Set scan method to ALL channels + _wifi_cfg.sta.scan_method = WIFI_ALL_CHANNEL_SCAN; + _wifi_cfg.sta.bssid_set = 0; // Don't use specific BSSID + _wifi_cfg.sta.channel = 0; // Scan all channels + + // Additional settings that might help with compatibility + _wifi_cfg.sta.listen_interval = 0; // Default listen interval + _wifi_cfg.sta.sort_method = WIFI_CONNECT_AP_BY_SIGNAL; // Connect to strongest signal + + // IMPORTANT: For WPA/WPA2 Personal networks + _wifi_cfg.sta.threshold.rssi = -127; // Accept any signal strength + _wifi_cfg.sta.sae_pwe_h2e = WPA3_SAE_PWE_UNSPECIFIED; // Let driver decide SAE mode + + // Log what we're trying to connect to with detailed debugging + ESP_LOGI(WIFI_MANAGER_TAG, "Setting credentials for SSID: '%s' (length: %d)", ssid, (int)strlen(ssid)); + ESP_LOGI(WIFI_MANAGER_TAG, "Password: '%s' (length: %d)", password, (int)strlen(password)); + ESP_LOGI(WIFI_MANAGER_TAG, "Auth mode: %d, PMF capable: %d", + _wifi_cfg.sta.threshold.authmode, _wifi_cfg.sta.pmf_cfg.capable); + + // Print hex dump of SSID to catch any hidden characters + ESP_LOG_BUFFER_HEX_LEVEL(WIFI_MANAGER_TAG, _wifi_cfg.sta.ssid, strlen((char*)_wifi_cfg.sta.ssid), ESP_LOG_INFO); + + // Print hex dump of password to catch any hidden characters + ESP_LOG_BUFFER_HEX_LEVEL(WIFI_MANAGER_TAG, _wifi_cfg.sta.password, strlen((char*)_wifi_cfg.sta.password), ESP_LOG_INFO); } void WiFiManager::ConnectWithHardcodedCredentials() { SystemEvent event = {EventSource::WIFI, WiFiState_e::WiFiState_ReadyToConnect}; this->SetCredentials(CONFIG_WIFI_SSID, CONFIG_WIFI_PASSWORD); + esp_wifi_stop(); + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &_wifi_cfg)); xQueueSend(this->eventQueue, &event, 10); @@ -103,16 +161,38 @@ void WiFiManager::ConnectWithStoredCredentials() return; } + // Stop WiFi once before the loop + esp_wifi_stop(); + vTaskDelay(pdMS_TO_TICKS(100)); + + // Ensure we're in STA mode + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + for (const auto& network : networks) { - xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT); + // Reset retry counter for each network attempt + s_retry_num = 0; + xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT | WIFI_CONNECTED_BIT); this->SetCredentials(network.ssid.c_str(), network.password.c_str()); - // we need to update the config after every credential change + // Update config without stopping WiFi again + ESP_LOGI(WIFI_MANAGER_TAG, "Attempting to connect to SSID: '%s'", network.ssid.c_str()); + + // Double-check the actual config being sent to WiFi driver + ESP_LOGI(WIFI_MANAGER_TAG, "Final SSID in config: '%s' (len: %d)", + (char*)_wifi_cfg.sta.ssid, (int)strlen((char*)_wifi_cfg.sta.ssid)); + ESP_LOGI(WIFI_MANAGER_TAG, "Final password in config: '%s' (len: %d)", + (char*)_wifi_cfg.sta.password, (int)strlen((char*)_wifi_cfg.sta.password)); + ESP_ERROR_CHECK(esp_wifi_set_config(WIFI_IF_STA, &_wifi_cfg)); xQueueSend(this->eventQueue, &event, 10); - esp_wifi_start(); + // Start WiFi if not already started + esp_err_t start_err = esp_wifi_start(); + if (start_err != ESP_OK && start_err != ESP_ERR_WIFI_STATE) { + ESP_LOGE(WIFI_MANAGER_TAG, "Failed to start WiFi: %s", esp_err_to_name(start_err)); + continue; + } event.value = WiFiState_e::WiFiState_Connecting; xQueueSend(this->eventQueue, &event, 10); @@ -121,19 +201,23 @@ void WiFiManager::ConnectWithStoredCredentials() WIFI_CONNECTED_BIT | WIFI_FAIL_BIT, pdFALSE, pdFALSE, - portMAX_DELAY); + pdMS_TO_TICKS(30000)); // 30 second timeout instead of portMAX_DELAY if (bits & WIFI_CONNECTED_BIT) { - ESP_LOGI(WIFI_MANAGER_TAG, "connected to ap SSID:%p password:%p", - _wifi_cfg.sta.ssid, _wifi_cfg.sta.password); + ESP_LOGI(WIFI_MANAGER_TAG, "connected to ap SSID:%s", + network.ssid.c_str()); event.value = WiFiState_e::WiFiState_Connected; xQueueSend(this->eventQueue, &event, 10); return; } - ESP_LOGE(WIFI_MANAGER_TAG, "Failed to connect to SSID:%p, password:%p, trying next stored network", - _wifi_cfg.sta.ssid, _wifi_cfg.sta.password); + ESP_LOGE(WIFI_MANAGER_TAG, "Failed to connect to SSID:%s, trying next stored network", + network.ssid.c_str()); + + // Disconnect before trying next network + esp_wifi_disconnect(); + vTaskDelay(pdMS_TO_TICKS(100)); } event.value = WiFiState_e::WiFiState_Error; @@ -165,6 +249,109 @@ void WiFiManager::SetupAccessPoint() ESP_LOGI(WIFI_MANAGER_TAG, "AP started."); } +std::vector WiFiManager::ScanNetworks() { + wifi_mode_t current_mode; + esp_err_t err = esp_wifi_get_mode(¤t_mode); + + if (err == ESP_ERR_WIFI_NOT_INIT) { + ESP_LOGE(WIFI_MANAGER_TAG, "WiFi not initialized for scanning"); + return std::vector(); + } + + // If we're in AP-only mode, we need STA interface for scanning + if (current_mode == WIFI_MODE_AP) { + ESP_LOGI(WIFI_MANAGER_TAG, "AP mode detected, checking for STA interface"); + + // Check if STA netif already exists + esp_netif_t* sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + bool sta_netif_exists = (sta_netif != nullptr); + + if (!sta_netif_exists) { + ESP_LOGI(WIFI_MANAGER_TAG, "Creating STA interface for scanning"); + sta_netif = esp_netif_create_default_wifi_sta(); + } + + ESP_LOGI(WIFI_MANAGER_TAG, "Switching to APSTA mode for scanning"); + err = esp_wifi_set_mode(WIFI_MODE_APSTA); + if (err != ESP_OK) { + ESP_LOGE(WIFI_MANAGER_TAG, "Failed to set APSTA mode: %s", esp_err_to_name(err)); + if (!sta_netif_exists && sta_netif) { + esp_netif_destroy(sta_netif); + } + return std::vector(); + } + + // Configure STA with empty config to prevent auto-connect + wifi_config_t empty_config = {}; + esp_wifi_set_config(WIFI_IF_STA, &empty_config); + + // Ensure STA is disconnected and not trying to connect + esp_wifi_disconnect(); + vTaskDelay(pdMS_TO_TICKS(500)); + + // Longer delay for mode to stabilize and enable all channels + vTaskDelay(pdMS_TO_TICKS(1500)); + + // Perform scan + auto networks = wifiScanner->scanNetworks(); + + // Restore AP-only mode + ESP_LOGI(WIFI_MANAGER_TAG, "Restoring AP-only mode"); + esp_wifi_set_mode(WIFI_MODE_AP); + + // Clean up STA interface if we created it + if (!sta_netif_exists && sta_netif) { + esp_netif_destroy(sta_netif); + } + + return networks; + } + + // If already in STA or APSTA mode, scan directly + return wifiScanner->scanNetworks(); +} + +WiFiState_e WiFiManager::GetCurrentWiFiState() { + return this->stateManager->GetWifiState(); +} + +void WiFiManager::TryConnectToStoredNetworks() { + ESP_LOGI(WIFI_MANAGER_TAG, "Manual WiFi connection attempt requested"); + + // Check current WiFi mode + wifi_mode_t current_mode; + esp_err_t err = esp_wifi_get_mode(¤t_mode); + if (err != ESP_OK) { + ESP_LOGE(WIFI_MANAGER_TAG, "Failed to get WiFi mode: %s", esp_err_to_name(err)); + return; + } + + // If in AP mode, we need to properly transition to STA mode + if (current_mode == WIFI_MODE_AP || current_mode == WIFI_MODE_APSTA) { + ESP_LOGI(WIFI_MANAGER_TAG, "Currently in AP mode, transitioning to STA mode"); + + // Stop WiFi first + esp_wifi_stop(); + vTaskDelay(pdMS_TO_TICKS(100)); + + // Check if STA interface exists, create if needed + esp_netif_t* sta_netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + if (sta_netif == nullptr) { + ESP_LOGI(WIFI_MANAGER_TAG, "Creating STA interface"); + sta_netif = esp_netif_create_default_wifi_sta(); + } + + // Set to STA mode + ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); + vTaskDelay(pdMS_TO_TICKS(100)); + } + + // Reset retry counter and clear all event bits + s_retry_num = 0; + xEventGroupClearBits(s_wifi_event_group, WIFI_FAIL_BIT | WIFI_CONNECTED_BIT); + this->ConnectWithStoredCredentials(); +} + void WiFiManager::Begin() { s_wifi_event_group = xEventGroupCreate(); @@ -176,6 +363,18 @@ void WiFiManager::Begin() wifi_init_config_t esp_wifi_init_config = WIFI_INIT_CONFIG_DEFAULT(); ESP_ERROR_CHECK(esp_wifi_init(&esp_wifi_init_config)); + // Set WiFi country to enable all channels (1-14) + wifi_country_t country_config = { + .cc = "JP", // Japan allows channels 1-14 (most permissive) + .schan = 1, + .nchan = 14, + .max_tx_power = 20, + .policy = WIFI_COUNTRY_POLICY_AUTO + }; + ESP_ERROR_CHECK(esp_wifi_set_country(&country_config)); + + + ESP_ERROR_CHECK(esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WiFiManagerHelpers::event_handler, @@ -188,8 +387,8 @@ void WiFiManager::Begin() &instance_got_ip)); _wifi_cfg = {}; - _wifi_cfg.sta.threshold.authmode = WIFI_AUTH_WEP; - _wifi_cfg.sta.pmf_cfg.capable = true; + _wifi_cfg.sta.threshold.authmode = WIFI_AUTH_OPEN; // Start with open, will be set properly by SetCredentials + _wifi_cfg.sta.pmf_cfg.capable = false; // Disable PMF by default _wifi_cfg.sta.pmf_cfg.required = false; ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA)); diff --git a/components/wifiManager/wifiManager/wifiManager.hpp b/components/wifiManager/wifiManager/wifiManager.hpp index b96e8fa..8a78826 100644 --- a/components/wifiManager/wifiManager/wifiManager.hpp +++ b/components/wifiManager/wifiManager/wifiManager.hpp @@ -7,17 +7,20 @@ #include #include #include +#include "WiFiScanner.hpp" #include "esp_event.h" #include "esp_wifi.h" #include "esp_log.h" +#include "freertos/FreeRTOS.h" +#include "freertos/task.h" #define EXAMPLE_ESP_MAXIMUM_RETRY 3 #define WIFI_CONNECTED_BIT BIT0 #define WIFI_FAIL_BIT BIT1 -static int s_retry_num = 0; -static EventGroupHandle_t s_wifi_event_group; +extern int s_retry_num; +extern EventGroupHandle_t s_wifi_event_group; namespace WiFiManagerHelpers { @@ -34,6 +37,7 @@ private: StateManager *stateManager; wifi_init_config_t _wifi_init_cfg = WIFI_INIT_CONFIG_DEFAULT(); wifi_config_t _wifi_cfg = {}; + std::unique_ptr wifiScanner; esp_event_handler_instance_t instance_any_id; esp_event_handler_instance_t instance_got_ip; @@ -48,6 +52,9 @@ private: public: WiFiManager(std::shared_ptr deviceConfig, QueueHandle_t eventQueue, StateManager *stateManager); void Begin(); + std::vector ScanNetworks(); + WiFiState_e GetCurrentWiFiState(); + void TryConnectToStoredNetworks(); }; #endif \ No newline at end of file diff --git a/dependencies.lock b/dependencies.lock index c56cb65..c7e209c 100644 --- a/dependencies.lock +++ b/dependencies.lock @@ -53,7 +53,7 @@ dependencies: idf: source: type: idf - version: 5.3.2 + version: 5.3.3 direct_dependencies: - espressif/cmake_utilities - espressif/esp32-camera diff --git a/main/openiris_main.cpp b/main/openiris_main.cpp index e5e9744..a837f63 100644 --- a/main/openiris_main.cpp +++ b/main/openiris_main.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #ifdef CONFIG_WIRED_MODE #include @@ -29,8 +30,10 @@ #define CONFIG_LED_C_PIN_GPIO (gpio_num_t) CONFIG_LED_C_PIN esp_timer_handle_t timerHandle; +esp_timer_handle_t startupTimerHandle; QueueHandle_t eventQueue = xQueueCreate(10, sizeof(SystemEvent)); QueueHandle_t ledStateQueue = xQueueCreate(10, sizeof(uint32_t)); +bool startupCommandReceived = false; auto *stateManager = new StateManager(eventQueue, ledStateQueue); auto dependencyRegistry = std::make_shared(); @@ -39,8 +42,8 @@ auto commandManager = std::make_shared(dependencyRegistry); WebSocketLogger webSocketLogger; Preferences preferences; -std::shared_ptr deviceConfig = std::make_shared(&preferences); -WiFiManager wifiManager(deviceConfig, eventQueue, stateManager); +auto deviceConfig = std::make_shared(&preferences); +auto wifiManager = std::make_shared(deviceConfig, eventQueue, stateManager); MDNSManager mdnsManager(deviceConfig, eventQueue); std::shared_ptr cameraHandler = std::make_shared(deviceConfig, eventQueue); @@ -53,7 +56,7 @@ UVCStreamManager uvcStream; #endif auto *ledManager = new LEDManager(BLINK_GPIO, CONFIG_LED_C_PIN_GPIO, ledStateQueue); -auto *serialManager = new SerialManager(commandManager, &timerHandle); +auto *serialManager = new SerialManager(commandManager, &timerHandle, deviceConfig); static void initNVSStorage() { esp_err_t ret = nvs_flash_init(); @@ -73,58 +76,151 @@ void disable_serial_manager_task(TaskHandle_t serialManagerHandle) { vTaskDelete(serialManagerHandle); } -// the idea here is pretty simple. -// After setting everything up, we start a 30s timer with this as a callback -// if we get anything on the serial, we stop the timer and reset it after the commands are done -// this is done to ensure the user has enough time to configure the board if need be -void start_video_streaming(void *arg) { - // if we're in auto-mode, we can decide which streaming helper to start based on the - // presence of Wi-Fi credentials - ESP_LOGI("[MAIN]", "Setup window expired, starting streaming services, quitting serial manager."); - switch (deviceConfig->getDeviceMode().mode) { - case StreamingMode::AUTO: - if (!deviceConfig->getWifiConfigs().empty() || strcmp(CONFIG_WIFI_SSID, "") != 0) { - // todo make sure the server runs on a separate core - ESP_LOGI("[MAIN]", "WiFi setup detected, starting WiFi streaming."); - streamServer.startStreamServer(); - } else { - ESP_LOGI("[MAIN]", "UVC setup detected, starting UVC streaming."); - uvcStream.setup(); +// 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) { +#ifdef CONFIG_WIRED_MODE + ESP_LOGI("[MAIN]", "Starting UVC streaming mode."); + // Initialize UVC if not already done + static bool uvcInitialized = false; + if (!uvcInitialized) { + ESP_LOGI("[MAIN]", "Initializing UVC hardware..."); + esp_err_t ret = uvcStream.setup(); + if (ret != ESP_OK) { + ESP_LOGE("[MAIN]", "Failed to initialize UVC: %s", esp_err_to_name(ret)); + return; } - break; - case StreamingMode::UVC: - ESP_LOGI("[MAIN]", "Device set to UVC Mode, starting UVC streaming."); - uvcStream.setup(); - break; - case StreamingMode::WIFI: - ESP_LOGI("[MAIN]", "Device set to Wi-Fi mode, starting WiFi streaming."); - streamServer.startStreamServer(); - break; + uvcInitialized = true; + } + uvcStream.start(); +#else + ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_WIRED_MODE not enabled in build!"); + ESP_LOGI("[MAIN]", "Falling back to WiFi mode if credentials available"); + deviceMode = StreamingMode::WIFI; +#endif } - const auto serialTaskHandle = static_cast(arg); - disable_serial_manager_task(serialTaskHandle); + if ((deviceMode == StreamingMode::WIFI || deviceMode == StreamingMode::AUTO) && hasWifiCredentials && wifiConnected) { + ESP_LOGI("[MAIN]", "Starting WiFi streaming mode."); + streamServer.startStreamServer(); + } 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]", "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); + } } -esp_timer_handle_t createStartVideoStreamingTimer(void *pvParameter) { - esp_timer_handle_t handle; - const esp_timer_create_args_t args = { - .callback = &start_video_streaming, - .arg = pvParameter, - .name = "startVideoStreaming" - }; +// Manual streaming activation - no timer needed +void activate_streaming(TaskHandle_t serialTaskHandle = nullptr) +{ + start_video_streaming(serialTaskHandle); +} - if (const auto result = esp_timer_create(&args, &handle); result != ESP_OK) { - ESP_LOGE("[MAIN]", "Failed to create timer: %s", esp_err_to_name(result)); +// Callback for automatic startup after delay +void startup_timer_callback(void* arg) +{ + ESP_LOGI("[MAIN]", "Startup timer fired, startupCommandReceived=%s, startupPaused=%s", + startupCommandReceived ? "true" : "false", + startupPaused ? "true" : "false"); + + if (!startupCommandReceived && !startupPaused) { + 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_WIRED_MODE + ESP_LOGI("[MAIN]", "Starting UVC streaming automatically"); + activate_streaming(serialTaskHandle); +#else + ESP_LOGE("[MAIN]", "UVC mode selected but CONFIG_WIRED_MODE not enabled in build!"); + ESP_LOGI("[MAIN]", "Device will stay in setup mode. Enable CONFIG_WIRED_MODE and rebuild."); +#endif + } + else { + ESP_LOGI("[MAIN]", "Unknown device mode: %d", (int)deviceMode); + } + } else { + if (startupPaused) { + ESP_LOGI("[MAIN]", "Startup paused, staying in heartbeat mode"); + } else { + ESP_LOGI("[MAIN]", "Command received during startup, staying in heartbeat mode"); + } } - return handle; + // Delete the timer after it fires + esp_timer_delete(startupTimerHandle); + startupTimerHandle = nullptr; +} + +// Function to notify that a command was received during startup +void notify_startup_command_received() +{ + startupCommandReceived = true; + + // Cancel the startup timer if it's still running + if (startupTimerHandle != nullptr) { + esp_timer_stop(startupTimerHandle); + esp_timer_delete(startupTimerHandle); + startupTimerHandle = nullptr; + ESP_LOGI("[MAIN]", "Startup timer cancelled, staying in heartbeat mode"); + } } extern "C" void app_main(void) { - TaskHandle_t *serialManagerHandle = nullptr; dependencyRegistry->registerService(DependencyType::project_config, deviceConfig); dependencyRegistry->registerService(DependencyType::camera_manager, cameraHandler); + dependencyRegistry->registerService(DependencyType::wifi_manager, wifiManager); // uvc plan // cleanup the logs - done // prepare the camera to be initialized with UVC - done? @@ -197,19 +293,23 @@ extern "C" void app_main(void) { deviceConfig->load(); serialManager->setup(); + static TaskHandle_t serialManagerHandle = nullptr; xTaskCreate( HandleSerialManagerTask, "HandleSerialManagerTask", 1024 * 6, serialManager, 1, // we only rely on the serial manager during provisioning, we can run it slower - serialManagerHandle); + &serialManagerHandle); - wifiManager.Begin(); + wifiManager->Begin(); mdnsManager.start(); restAPI->begin(); cameraHandler->setupCamera(); + // Don't initialize UVC here - wait for the timer to decide + // UVC will be initialized when streaming actually starts + xTaskCreate( HandleRestAPIPollTask, "HandleRestAPIPollTask", @@ -218,8 +318,44 @@ extern "C" void app_main(void) { 1, // it's the rest API, we only serve commands over it so we don't really need a higher priority nullptr); - timerHandle = createStartVideoStreamingTimer(serialManagerHandle); - if (timerHandle != nullptr) { - esp_timer_start_once(timerHandle, 30000000); // 30s + // New flow: Device starts with a 20-second delay before automatic mode startup + ESP_LOGI("[MAIN]", "====================================="); + ESP_LOGI("[MAIN]", "STARTUP: 20-SECOND DELAY MODE ACTIVE"); + ESP_LOGI("[MAIN]", "====================================="); + ESP_LOGI("[MAIN]", "Device will wait 20 seconds for commands..."); + + // Get the stored device mode + StreamingMode deviceMode = deviceConfig->getDeviceMode(); + ESP_LOGI("[MAIN]", "Stored device mode: %s (value: %d)", + deviceMode == StreamingMode::UVC ? "UVC" : + deviceMode == StreamingMode::WIFI ? "WiFi" : + "Auto", (int)deviceMode); + + // If WiFi credentials exist, attempt connection but stay in setup mode + if (!deviceConfig->getWifiConfigs().empty()) { + ESP_LOGI("[MAIN]", "WiFi credentials found, attempting connection in background"); + // WiFi connection happens in wifiManager->Begin() above } + + // Reset startup state + startupCommandReceived = false; + + // Create a one-shot timer for 20 seconds + const esp_timer_create_args_t startup_timer_args = { + .callback = &startup_timer_callback, + .arg = nullptr, + .dispatch_method = ESP_TIMER_TASK, + .name = "startup_timer", + .skip_unhandled_events = false + }; + + ESP_ERROR_CHECK(esp_timer_create(&startup_timer_args, &startupTimerHandle)); + ESP_ERROR_CHECK(esp_timer_start_once(startupTimerHandle, 20000000)); // 20 seconds in microseconds + + ESP_LOGI("[MAIN]", "Started 20-second startup timer"); + ESP_LOGI("[MAIN]", "Send any command within 20 seconds to enter heartbeat mode"); + + // Set global handles for component access + setStreamingTimerHandle(&timerHandle); + setSerialManagerHandle(&serialManagerHandle); } diff --git a/sdkconfig b/sdkconfig index 1d2a05b..89fd862 100644 --- a/sdkconfig +++ b/sdkconfig @@ -1,6 +1,6 @@ # # Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.3.2 Project Configuration +# Espressif IoT Development Framework (ESP-IDF) 5.3.3 Project Configuration # CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 @@ -64,6 +64,7 @@ CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y CONFIG_SOC_PM_SUPPORTED=y +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y CONFIG_SOC_XTAL_SUPPORT_40M=y CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y @@ -101,6 +102,7 @@ CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64 +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 @@ -203,6 +205,7 @@ CONFIG_SOC_RTCIO_PIN_COUNT=22 CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y CONFIG_SOC_SDM_GROUPS=y CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 CONFIG_SOC_SDM_CLK_SUPPORT_APB=y @@ -243,6 +246,8 @@ CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 CONFIG_SOC_TOUCH_SENSOR_VERSION=2 CONFIG_SOC_TOUCH_SENSOR_NUM=15 CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y @@ -366,7 +371,7 @@ CONFIG_IDF_TOOLCHAIN="gcc" CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET="esp32s3" -CONFIG_IDF_INIT_VERSION="5.3.2" +CONFIG_IDF_INIT_VERSION="5.3.3" CONFIG_IDF_TARGET_ESP32S3=y CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 @@ -628,7 +633,12 @@ CONFIG_APPTRACE_LOCK_ENABLE=y # Bluetooth # # CONFIG_BT_ENABLED is not set -CONFIG_BT_ALARM_MAX_NUM=50 + +# +# Common Options +# +# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set +# end of Common Options # end of Bluetooth # @@ -652,6 +662,7 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # Legacy ADC Driver Configuration # # CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set # # Legacy ADC Calibration Configuration @@ -664,42 +675,49 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # Legacy MCPWM Driver Configurations # # CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy MCPWM Driver Configurations # # Legacy Timer Group Driver Configurations # # CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy Timer Group Driver Configurations # # Legacy RMT Driver Configurations # # CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy RMT Driver Configurations # # Legacy I2S Driver Configurations # # CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy I2S Driver Configurations # # Legacy PCNT Driver Configurations # # CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy PCNT Driver Configurations # # Legacy SDM Driver Configurations # # CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy SDM Driver Configurations # # Legacy Temperature Sensor Driver Configurations # # CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy Temperature Sensor Driver Configurations # end of Driver Configurations @@ -759,6 +777,7 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y # CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set # CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y # CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set # end of ESP-Driver:GPTimer Configurations @@ -877,6 +896,13 @@ CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y CONFIG_ESP_GDBSTUB_MAX_TASKS=32 # end of GDB Stub +# +# ESP HID +# +CONFIG_ESPHID_TASK_SIZE_BT=2048 +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + # # ESP HTTP client # @@ -1051,6 +1077,7 @@ CONFIG_ESP_PHY_RF_CAL_PARTIAL=y # CONFIG_ESP_PHY_RF_CAL_FULL is not set CONFIG_ESP_PHY_CALIBRATION_MODE=0 # CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set # end of PHY # @@ -1251,9 +1278,9 @@ CONFIG_ESP_WIFI_ENABLED=y CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +# CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER is not set CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0 CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 -CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32 CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y # CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 @@ -1263,7 +1290,6 @@ CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y CONFIG_ESP_WIFI_TX_BA_WIN=6 CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y CONFIG_ESP_WIFI_RX_BA_WIN=6 -# CONFIG_ESP_WIFI_AMSDU_TX_ENABLED is not set CONFIG_ESP_WIFI_NVS_ENABLED=y CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y # CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set @@ -1434,7 +1460,6 @@ CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 CONFIG_HAL_WDT_USE_ROM_IMPL=y CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y -# CONFIG_HAL_ECDSA_GEN_SIG_CM is not set # end of Hardware Abstraction Layer (HAL) and Low Level (LL) # @@ -1836,25 +1861,10 @@ CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y # CONFIG_OPENTHREAD_ENABLED is not set # -# Thread Operational Dataset +# OpenThread Spinel # -CONFIG_OPENTHREAD_NETWORK_NAME="OpenThread-ESP" -CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX="fd00:db8:a0:0::/64" -CONFIG_OPENTHREAD_NETWORK_CHANNEL=15 -CONFIG_OPENTHREAD_NETWORK_PANID=0x1234 -CONFIG_OPENTHREAD_NETWORK_EXTPANID="dead00beef00cafe" -CONFIG_OPENTHREAD_NETWORK_MASTERKEY="00112233445566778899aabbccddeeff" -CONFIG_OPENTHREAD_NETWORK_PSKC="104810e2315100afd6bc9215a6bfac53" -# end of Thread Operational Dataset - -CONFIG_OPENTHREAD_XTAL_ACCURACY=130 # CONFIG_OPENTHREAD_SPINEL_ONLY is not set -CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE=y - -# -# Thread Address Query Config -# -# end of Thread Address Query Config +# end of OpenThread Spinel # end of OpenThread # @@ -1863,6 +1873,7 @@ CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y # end of Protocomm # @@ -2093,75 +2104,6 @@ CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y # CONFIG_WIFI_PROV_STA_FAST_SCAN is not set # end of Wi-Fi Provisioning Manager -# -# USB Device UVC -# -CONFIG_TUSB_VID=0x303A -CONFIG_TUSB_PID=0x8000 -CONFIG_TUSB_MANUFACTURER="ETVR" -CONFIG_TUSB_PRODUCT="OpenIris Camera" -CONFIG_TUSB_SERIAL_NUM="12345678" -# CONFIG_UVC_SUPPORT_TWO_CAM is not set - -# -# USB Cam1 Config -# -CONFIG_FORMAT_MJPEG_CAM1=y -# CONFIG_FORMAT_H264_CAM1 is not set -# CONFIG_FORMAT_UNCOMPR_CAM1 is not set -# CONFIG_UVC_MODE_ISOC_CAM1 is not set -CONFIG_UVC_MODE_BULK_CAM1=y -CONFIG_FRAMESIZE_QVGA=y -# CONFIG_FRAMESIZE_HVGA is not set -# CONFIG_FRAMESIZE_VGA is not set -# CONFIG_FRAMESIZE_SVGA is not set -# CONFIG_FRAMESIZE_HD is not set -# CONFIG_FRAMESIZE_FHD is not set -CONFIG_UVC_CAM1_FRAMERATE=90 -CONFIG_UVC_CAM1_FRAMESIZE_WIDTH=240 -CONFIG_UVC_CAM1_FRAMESIZE_HEIGT=240 -CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y -# end of USB Cam1 Config - -# -# UVC_MULTI_FRAME_CONFIG -# - -# -# FRAME_SIZE_1 -# -CONFIG_UVC_MULTI_FRAME_WIDTH_1=240 -CONFIG_UVC_MULTI_FRAME_HEIGHT_1=240 -CONFIG_UVC_MULTI_FRAME_FPS_1=90 -# end of FRAME_SIZE_1 - -# -# FRAME_SIZE_2 -# -CONFIG_UVC_MULTI_FRAME_WIDTH_2=240 -CONFIG_UVC_MULTI_FRAME_HEIGHT_2=240 -CONFIG_UVC_MULTI_FRAME_FPS_2=90 -# end of FRAME_SIZE_2 - -# -# FRAME_SIZE_3 -# -CONFIG_UVC_MULTI_FRAME_WIDTH_3=240 -CONFIG_UVC_MULTI_FRAME_HEIGHT_3=240 -CONFIG_UVC_MULTI_FRAME_FPS_3=90 -# end of FRAME_SIZE_3 -# end of UVC_MULTI_FRAME_CONFIG - -# -# UVC Task Config -# -CONFIG_UVC_TINYUSB_TASK_PRIORITY=3 -CONFIG_UVC_TINYUSB_TASK_CORE=-1 -CONFIG_UVC_CAM1_TASK_PRIORITY=2 -CONFIG_UVC_CAM1_TASK_CORE=-1 -# end of UVC Task Config -# end of USB Device UVC - # # CMake Utilities # @@ -2233,6 +2175,75 @@ CONFIG_MDNS_PREDEF_NETIF_AP=y CONFIG_MDNS_PREDEF_NETIF_ETH=y # end of MDNS Predefined interfaces # end of mDNS + +# +# USB Device UVC +# +CONFIG_TUSB_VID=0x303A +CONFIG_TUSB_PID=0x8000 +CONFIG_TUSB_MANUFACTURER="ETVR" +CONFIG_TUSB_PRODUCT="OpenIris Camera" +CONFIG_TUSB_SERIAL_NUM="12345678" +# CONFIG_UVC_SUPPORT_TWO_CAM is not set + +# +# USB Cam1 Config +# +CONFIG_FORMAT_MJPEG_CAM1=y +# CONFIG_FORMAT_H264_CAM1 is not set +# CONFIG_FORMAT_UNCOMPR_CAM1 is not set +# CONFIG_UVC_MODE_ISOC_CAM1 is not set +CONFIG_UVC_MODE_BULK_CAM1=y +CONFIG_FRAMESIZE_QVGA=y +# CONFIG_FRAMESIZE_HVGA is not set +# CONFIG_FRAMESIZE_VGA is not set +# CONFIG_FRAMESIZE_SVGA is not set +# CONFIG_FRAMESIZE_HD is not set +# CONFIG_FRAMESIZE_FHD is not set +CONFIG_UVC_CAM1_FRAMERATE=90 +CONFIG_UVC_CAM1_FRAMESIZE_WIDTH=240 +CONFIG_UVC_CAM1_FRAMESIZE_HEIGT=240 +CONFIG_UVC_CAM1_MULTI_FRAMESIZE=y +# end of USB Cam1 Config + +# +# UVC_MULTI_FRAME_CONFIG +# + +# +# FRAME_SIZE_1 +# +CONFIG_UVC_MULTI_FRAME_WIDTH_1=240 +CONFIG_UVC_MULTI_FRAME_HEIGHT_1=240 +CONFIG_UVC_MULTI_FRAME_FPS_1=60 +# end of FRAME_SIZE_1 + +# +# FRAME_SIZE_2 +# +CONFIG_UVC_MULTI_FRAME_WIDTH_2=240 +CONFIG_UVC_MULTI_FRAME_HEIGHT_2=240 +CONFIG_UVC_MULTI_FRAME_FPS_2=60 +# end of FRAME_SIZE_2 + +# +# FRAME_SIZE_3 +# +CONFIG_UVC_MULTI_FRAME_WIDTH_3=240 +CONFIG_UVC_MULTI_FRAME_HEIGHT_3=240 +CONFIG_UVC_MULTI_FRAME_FPS_3=60 +# end of FRAME_SIZE_3 +# end of UVC_MULTI_FRAME_CONFIG + +# +# UVC Task Config +# +CONFIG_UVC_TINYUSB_TASK_PRIORITY=4 +CONFIG_UVC_TINYUSB_TASK_CORE=-1 +CONFIG_UVC_CAM1_TASK_PRIORITY=3 +CONFIG_UVC_CAM1_TASK_CORE=-1 +# end of UVC Task Config +# end of USB Device UVC # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set @@ -2326,7 +2337,6 @@ CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y CONFIG_ESP32S3_DEBUG_OCDAWARE=y CONFIG_BROWNOUT_DET=y CONFIG_ESP32S3_BROWNOUT_DET=y -CONFIG_ESP32S3_BROWNOUT_DET=y CONFIG_BROWNOUT_DET_LVL_SEL_7=y CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y # CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set @@ -2349,17 +2359,14 @@ CONFIG_ESP32_WIFI_ENABLED=y CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y +# CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER is not set CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 -CONFIG_ESP32_WIFI_CACHE_TX_BUFFER_NUM=32 # CONFIG_ESP32_WIFI_CSI_ENABLED is not set CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y CONFIG_ESP32_WIFI_TX_BA_WIN=6 CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y -CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y CONFIG_ESP32_WIFI_RX_BA_WIN=6 -CONFIG_ESP32_WIFI_RX_BA_WIN=6 -# CONFIG_ESP32_WIFI_AMSDU_TX_ENABLED is not set CONFIG_ESP32_WIFI_NVS_ENABLED=y CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y # CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set diff --git a/sdkconfig.old b/sdkconfig.old index 2259232..125ee3c 100644 --- a/sdkconfig.old +++ b/sdkconfig.old @@ -1,6 +1,6 @@ # # Automatically generated file. DO NOT EDIT. -# Espressif IoT Development Framework (ESP-IDF) 5.3.2 Project Configuration +# Espressif IoT Development Framework (ESP-IDF) 5.3.3 Project Configuration # CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000 CONFIG_SOC_MPU_REGIONS_MAX_NUM=8 @@ -64,6 +64,7 @@ CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y CONFIG_SOC_PM_SUPPORTED=y +CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y CONFIG_SOC_XTAL_SUPPORT_40M=y CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y @@ -101,6 +102,7 @@ CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y CONFIG_SOC_CPU_BREAKPOINTS_NUM=2 CONFIG_SOC_CPU_WATCHPOINTS_NUM=2 CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=64 +CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16 CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096 CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16 CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100 @@ -203,6 +205,7 @@ CONFIG_SOC_RTCIO_PIN_COUNT=22 CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y +CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y CONFIG_SOC_SDM_GROUPS=y CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8 CONFIG_SOC_SDM_CLK_SUPPORT_APB=y @@ -243,6 +246,8 @@ CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54 CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32 +CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16 CONFIG_SOC_TOUCH_SENSOR_VERSION=2 CONFIG_SOC_TOUCH_SENSOR_NUM=15 CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y @@ -366,7 +371,7 @@ CONFIG_IDF_TOOLCHAIN="gcc" CONFIG_IDF_TARGET_ARCH_XTENSA=y CONFIG_IDF_TARGET_ARCH="xtensa" CONFIG_IDF_TARGET="esp32s3" -CONFIG_IDF_INIT_VERSION="5.3.2" +CONFIG_IDF_INIT_VERSION="5.3.3" CONFIG_IDF_TARGET_ESP32S3=y CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009 @@ -544,11 +549,10 @@ CONFIG_ENV_GPIO_RANGE_MAX=48 CONFIG_ENV_GPIO_IN_RANGE_MAX=48 CONFIG_ENV_GPIO_OUT_RANGE_MAX=48 CONFIG_BLINK_GPIO=38 -# CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL is not set +CONFIG_SUPPORTS_EXTERNAL_LED_CONTROL=y CONFIG_LED_C_PIN=1 CONFIG_BLINK_PERIOD=1000 -CONFIG_ILLUMINATOR_PIN=1 -CONFIG_WIRED_MODE=y +# CONFIG_WIRED_MODE is not set CONFIG_MDNS_HOSTNAME="openiristracker" CONFIG_WIFI_SSID="" CONFIG_WIFI_PASSWORD="" @@ -629,7 +633,12 @@ CONFIG_APPTRACE_LOCK_ENABLE=y # Bluetooth # # CONFIG_BT_ENABLED is not set -CONFIG_BT_ALARM_MAX_NUM=50 + +# +# Common Options +# +# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set +# end of Common Options # end of Bluetooth # @@ -653,6 +662,7 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # Legacy ADC Driver Configuration # # CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set # # Legacy ADC Calibration Configuration @@ -665,42 +675,49 @@ CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y # Legacy MCPWM Driver Configurations # # CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy MCPWM Driver Configurations # # Legacy Timer Group Driver Configurations # # CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy Timer Group Driver Configurations # # Legacy RMT Driver Configurations # # CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy RMT Driver Configurations # # Legacy I2S Driver Configurations # # CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy I2S Driver Configurations # # Legacy PCNT Driver Configurations # # CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy PCNT Driver Configurations # # Legacy SDM Driver Configurations # # CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy SDM Driver Configurations # # Legacy Temperature Sensor Driver Configurations # # CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set +# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set # end of Legacy Temperature Sensor Driver Configurations # end of Driver Configurations @@ -760,6 +777,7 @@ CONFIG_ESP_ERR_TO_NAME_LOOKUP=y CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y # CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set # CONFIG_GPTIMER_ISR_IRAM_SAFE is not set +CONFIG_GPTIMER_OBJ_CACHE_SAFE=y # CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set # end of ESP-Driver:GPTimer Configurations @@ -878,6 +896,13 @@ CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y CONFIG_ESP_GDBSTUB_MAX_TASKS=32 # end of GDB Stub +# +# ESP HID +# +CONFIG_ESPHID_TASK_SIZE_BT=2048 +CONFIG_ESPHID_TASK_SIZE_BLE=4096 +# end of ESP HID + # # ESP HTTP client # @@ -1052,6 +1077,7 @@ CONFIG_ESP_PHY_RF_CAL_PARTIAL=y # CONFIG_ESP_PHY_RF_CAL_FULL is not set CONFIG_ESP_PHY_CALIBRATION_MODE=0 # CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set +# CONFIG_ESP_PHY_RECORD_USED_TIME is not set # end of PHY # @@ -1252,9 +1278,9 @@ CONFIG_ESP_WIFI_ENABLED=y CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=10 CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32 CONFIG_ESP_WIFI_STATIC_TX_BUFFER=y +# CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER is not set CONFIG_ESP_WIFI_TX_BUFFER_TYPE=0 CONFIG_ESP_WIFI_STATIC_TX_BUFFER_NUM=16 -CONFIG_ESP_WIFI_CACHE_TX_BUFFER_NUM=32 CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER=y # CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER is not set CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=0 @@ -1264,7 +1290,6 @@ CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y CONFIG_ESP_WIFI_TX_BA_WIN=6 CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y CONFIG_ESP_WIFI_RX_BA_WIN=6 -# CONFIG_ESP_WIFI_AMSDU_TX_ENABLED is not set CONFIG_ESP_WIFI_NVS_ENABLED=y CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y # CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set @@ -1435,7 +1460,6 @@ CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2 CONFIG_HAL_WDT_USE_ROM_IMPL=y CONFIG_HAL_SPI_MASTER_FUNC_IN_IRAM=y CONFIG_HAL_SPI_SLAVE_FUNC_IN_IRAM=y -# CONFIG_HAL_ECDSA_GEN_SIG_CM is not set # end of Hardware Abstraction Layer (HAL) and Low Level (LL) # @@ -1837,25 +1861,10 @@ CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y # CONFIG_OPENTHREAD_ENABLED is not set # -# Thread Operational Dataset +# OpenThread Spinel # -CONFIG_OPENTHREAD_NETWORK_NAME="OpenThread-ESP" -CONFIG_OPENTHREAD_MESH_LOCAL_PREFIX="fd00:db8:a0:0::/64" -CONFIG_OPENTHREAD_NETWORK_CHANNEL=15 -CONFIG_OPENTHREAD_NETWORK_PANID=0x1234 -CONFIG_OPENTHREAD_NETWORK_EXTPANID="dead00beef00cafe" -CONFIG_OPENTHREAD_NETWORK_MASTERKEY="00112233445566778899aabbccddeeff" -CONFIG_OPENTHREAD_NETWORK_PSKC="104810e2315100afd6bc9215a6bfac53" -# end of Thread Operational Dataset - -CONFIG_OPENTHREAD_XTAL_ACCURACY=130 # CONFIG_OPENTHREAD_SPINEL_ONLY is not set -CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE=y - -# -# Thread Address Query Config -# -# end of Thread Address Query Config +# end of OpenThread Spinel # end of OpenThread # @@ -1864,6 +1873,7 @@ CONFIG_OPENTHREAD_RX_ON_WHEN_IDLE=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y +CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y # end of Protocomm # @@ -2237,3 +2247,195 @@ CONFIG_UVC_CAM1_TASK_CORE=-1 # end of Component config # CONFIG_IDF_EXPERIMENTAL_FEATURES is not set + +# Deprecated options for backward compatibility +# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set +# CONFIG_NO_BLOBS is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_NONE is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set +CONFIG_LOG_BOOTLOADER_LEVEL_INFO=y +# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set +# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set +CONFIG_LOG_BOOTLOADER_LEVEL=3 +# CONFIG_APP_ROLLBACK_ENABLE is not set +# CONFIG_FLASH_ENCRYPTION_ENABLED is not set +CONFIG_FLASHMODE_QIO=y +# CONFIG_FLASHMODE_QOUT is not set +# CONFIG_FLASHMODE_DIO is not set +# CONFIG_FLASHMODE_DOUT is not set +CONFIG_MONITOR_BAUD=115200 +# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set +# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set +# CONFIG_OPTIMIZATION_LEVEL_RELEASE is not set +# CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE is not set +CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y +# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set +# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set +CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2 +# CONFIG_CXX_EXCEPTIONS is not set +CONFIG_STACK_CHECK_NONE=y +# CONFIG_STACK_CHECK_NORM is not set +# CONFIG_STACK_CHECK_STRONG is not set +# CONFIG_STACK_CHECK_ALL is not set +# CONFIG_WARN_WRITE_STRINGS is not set +# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set +CONFIG_ESP32_APPTRACE_DEST_NONE=y +CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y +# CONFIG_EXTERNAL_COEX_ENABLE is not set +# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set +# CONFIG_MCPWM_ISR_IN_IRAM is not set +# CONFIG_EVENT_LOOP_PROFILING is not set +CONFIG_POST_EVENTS_FROM_ISR=y +CONFIG_POST_EVENTS_FROM_IRAM_ISR=y +CONFIG_GDBSTUB_SUPPORT_TASKS=y +CONFIG_GDBSTUB_MAX_TASKS=32 +# CONFIG_OTA_ALLOW_HTTP is not set +CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000 +CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set +# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set +CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024 +CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y +# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set +CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20 +CONFIG_ESP32_PHY_MAX_TX_POWER=20 +# CONFIG_REDUCE_PHY_TX_POWER is not set +# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set +CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y +CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y +CONFIG_ESP32S3_SPIRAM_SUPPORT=y +CONFIG_DEFAULT_PSRAM_CLK_IO=30 +CONFIG_DEFAULT_PSRAM_CS_IO=26 +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set +# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160 is not set +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y +CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240 +CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32 +CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=2304 +CONFIG_MAIN_TASK_STACK_SIZE=3584 +CONFIG_CONSOLE_UART_DEFAULT=y +# CONFIG_CONSOLE_UART_CUSTOM is not set +# CONFIG_CONSOLE_UART_NONE is not set +# CONFIG_ESP_CONSOLE_UART_NONE is not set +CONFIG_CONSOLE_UART=y +CONFIG_CONSOLE_UART_NUM=0 +CONFIG_CONSOLE_UART_BAUDRATE=115200 +CONFIG_INT_WDT=y +CONFIG_INT_WDT_TIMEOUT_MS=300 +CONFIG_INT_WDT_CHECK_CPU1=y +CONFIG_TASK_WDT=y +CONFIG_ESP_TASK_WDT=y +# CONFIG_TASK_WDT_PANIC is not set +CONFIG_TASK_WDT_TIMEOUT_S=5 +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y +CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y +# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set +CONFIG_ESP32S3_DEBUG_OCDAWARE=y +CONFIG_BROWNOUT_DET=y +CONFIG_ESP32S3_BROWNOUT_DET=y +CONFIG_BROWNOUT_DET_LVL_SEL_7=y +CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y +# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set +# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set +# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set +CONFIG_BROWNOUT_DET_LVL=7 +CONFIG_ESP32S3_BROWNOUT_DET_LVL=7 +CONFIG_IPC_TASK_STACK_SIZE=1280 +CONFIG_TIMER_TASK_STACK_SIZE=3584 +CONFIG_ESP32_WIFI_ENABLED=y +CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=10 +CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=32 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER=y +# CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER is not set +CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=0 +CONFIG_ESP32_WIFI_STATIC_TX_BUFFER_NUM=16 +# CONFIG_ESP32_WIFI_CSI_ENABLED is not set +CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y +CONFIG_ESP32_WIFI_TX_BA_WIN=6 +CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y +CONFIG_ESP32_WIFI_RX_BA_WIN=6 +CONFIG_ESP32_WIFI_NVS_ENABLED=y +CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y +# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set +CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752 +CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32 +CONFIG_ESP32_WIFI_IRAM_OPT=y +CONFIG_ESP32_WIFI_RX_IRAM_OPT=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y +CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y +CONFIG_WPA_MBEDTLS_CRYPTO=y +CONFIG_WPA_MBEDTLS_TLS_CLIENT=y +# CONFIG_WPA_WAPI_PSK is not set +# CONFIG_WPA_SUITE_B_192 is not set +# CONFIG_WPA_11KV_SUPPORT is not set +# CONFIG_WPA_MBO_SUPPORT is not set +# CONFIG_WPA_DPP_SUPPORT is not set +# CONFIG_WPA_11R_SUPPORT is not set +# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set +# CONFIG_WPA_WPS_STRICT is not set +# CONFIG_WPA_DEBUG_PRINT is not set +# CONFIG_WPA_TESTING_OPTIONS is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set +# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set +CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y +CONFIG_TIMER_TASK_PRIORITY=1 +CONFIG_TIMER_TASK_STACK_DEPTH=2048 +CONFIG_TIMER_QUEUE_LENGTH=10 +# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set +# CONFIG_HAL_ASSERTION_SILIENT is not set +# CONFIG_L2_TO_L3_COPY is not set +CONFIG_ESP_GRATUITOUS_ARP=y +CONFIG_GARP_TMR_INTERVAL=60 +CONFIG_TCPIP_RECVMBOX_SIZE=32 +CONFIG_TCP_MAXRTX=12 +CONFIG_TCP_SYNMAXRTX=12 +CONFIG_TCP_MSS=1440 +CONFIG_TCP_MSL=60000 +CONFIG_TCP_SND_BUF_DEFAULT=5760 +CONFIG_TCP_WND_DEFAULT=5760 +CONFIG_TCP_RECVMBOX_SIZE=6 +CONFIG_TCP_QUEUE_OOSEQ=y +CONFIG_TCP_OVERSIZE_MSS=y +# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set +# CONFIG_TCP_OVERSIZE_DISABLE is not set +CONFIG_UDP_RECVMBOX_SIZE=6 +CONFIG_TCPIP_TASK_STACK_SIZE=3072 +CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y +# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set +# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set +CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF +# CONFIG_PPP_SUPPORT is not set +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_SYSTIMER=y +CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y +# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_SYSTIMER is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set +# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set +CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5 +CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072 +CONFIG_ESP32_PTHREAD_STACK_MIN=768 +CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set +# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set +CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1 +CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread" +CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set +# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set +CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y +CONFIG_SUPPORT_TERMIOS=y +CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1 +# End of deprecated options diff --git a/tools/openiris_setup.py b/tools/openiris_setup.py new file mode 100644 index 0000000..e4996eb --- /dev/null +++ b/tools/openiris_setup.py @@ -0,0 +1,816 @@ +#!/usr/bin/env python3 +""" +OpenIris Setup CLI Tool + +This tool automatically discovers OpenIris devices via heartbeat, +allows WiFi configuration, and monitors the device logs. +""" + +import json +import time +import threading +import argparse +import sys +from typing import Dict, List, Optional, Tuple +import serial +import serial.tools.list_ports +from dataclasses import dataclass + + +@dataclass +class WiFiNetwork: + ssid: str + channel: int + rssi: int + mac_address: str + auth_mode: int + + @property + def security_type(self) -> str: + """Convert auth_mode to human readable string""" + auth_modes = { + 0: "Open", + 1: "WEP", + 2: "WPA PSK", + 3: "WPA2 PSK", + 4: "WPA WPA2 PSK", + 5: "WPA2 Enterprise", + 6: "WPA3 PSK", + 7: "WPA2 WPA3 PSK" + } + return auth_modes.get(self.auth_mode, f"Unknown ({self.auth_mode})") + + +class OpenIrisDevice: + def __init__(self, port: str, serial_number: str, debug: bool = False): + + self.port = port + self.serial_number = serial_number + self.connection: Optional[serial.Serial] = None + self.networks: List[WiFiNetwork] = [] + self.debug = debug + + def connect(self) -> bool: + """Connect to the device""" + try: + self.connection = serial.Serial( + port=self.port, + baudrate=115200, + timeout=1, + write_timeout=1 + ) + print(f"✅ Connected to device {self.serial_number} on {self.port}") + + # Immediately send pause command to keep device in setup mode + print("⏸️ Pausing device startup...") + # Use shorter timeout for pause command since device is just starting up + pause_response = self.send_command("pause", {"pause": True}, timeout=5) + if "error" not in pause_response and pause_response.get("results"): + print("✅ Device paused in setup mode") + elif "error" in pause_response and pause_response["error"] == "Command timeout": + # Even if we timeout, the command likely worked (as seen in logs) + print("✅ Device pause command sent (startup logs may have obscured response)") + else: + print(f"⚠️ Pause status uncertain: {pause_response}") + + return True + except Exception as e: + print(f"❌ Failed to connect to {self.port}: {e}") + return False + + def disconnect(self): + """Disconnect from the device""" + if self.connection and self.connection.is_open: + # Optionally unpause the device before disconnecting + print("Resuming device startup...") + self.send_command("pause", {"pause": False}) + + self.connection.close() + print(f"🔌 Disconnected from {self.port}") + + def send_command(self, command: str, params: Dict = None, timeout: int = None) -> Dict: + """Send a command to the device and wait for response""" + if not self.connection or not self.connection.is_open: + return {"error": "Not connected"} + + cmd_obj = {"commands": [{"command": command}]} + if params: + cmd_obj["commands"][0]["data"] = params + + cmd_json = json.dumps(cmd_obj) + '\n' + + try: + # Clear any pending data + self.connection.reset_input_buffer() + + # Send command + print(f"📤 Sending: {cmd_json.strip()}") + self.connection.write(cmd_json.encode()) + + # For scan_networks command, handle special case + if command == "scan_networks": + # Use provided timeout or default to 30 seconds for scan + scan_timeout = timeout if timeout is not None else 30 + return self._handle_scan_response(scan_timeout) + + # Wait for response (skip heartbeats and logs) + start_time = time.time() + response_buffer = "" + + # Use provided timeout or default to 15 seconds + cmd_timeout = timeout if timeout is not None else 15 + while time.time() - start_time < cmd_timeout: + try: + if self.connection.in_waiting: + data = self.connection.read(self.connection.in_waiting).decode('utf-8', errors='ignore') + response_buffer += data + + # Show raw data for debugging + if self.debug and data.strip(): + print(f"📡 Raw: {repr(data)}") + print(f"📝 Buffer: {repr(response_buffer[-200:])}") + + # Clean buffer and look for JSON + import re + + # Remove ANSI escape sequences + clean_buffer = re.sub(r'\x1b\[[0-9;]*m', '', response_buffer) + clean_buffer = clean_buffer.replace('\r', '') + + # Look for JSON objects - handle both single-line and multi-line + # Try to find complete JSON objects + start_idx = clean_buffer.find('{') + while start_idx >= 0: + # Count braces to find complete JSON + brace_count = 0 + end_idx = -1 + + for i in range(start_idx, len(clean_buffer)): + if clean_buffer[i] == '{': + brace_count += 1 + elif clean_buffer[i] == '}': + brace_count -= 1 + if brace_count == 0: + end_idx = i + 1 + break + + if end_idx > start_idx: + json_str = clean_buffer[start_idx:end_idx] + + # Try to parse any complete JSON object + try: + # Clean up the JSON + clean_json = json_str.replace('\t', ' ').replace('\n', ' ').replace('\r', '') + clean_json = re.sub(r'\s+', ' ', clean_json) + + response = json.loads(clean_json) + + # Return if this is a command response with results + if "results" in response: + return response + + except json.JSONDecodeError: + pass + + # Look for next JSON object + start_idx = clean_buffer.find('{', end_idx) + else: + # No complete JSON found yet + break + else: + time.sleep(0.1) + except Exception as e: + print(f"⚠️ Exception: {e}") + continue + + return {"error": "Command timeout"} + except Exception as e: + return {"error": f"Communication error: {e}"} + + def _handle_scan_response(self, timeout: int = 30) -> Dict: + """Handle scan_networks command response which outputs raw JSON first""" + start_time = time.time() + response_buffer = "" + + while time.time() - start_time < timeout: # Configurable timeout for scan + if self.connection.in_waiting: + data = self.connection.read(self.connection.in_waiting).decode('utf-8', errors='ignore') + response_buffer += data + + # Look for WiFi networks JSON directly (new format) + # The scan command now outputs JSON directly followed by command result + if '{"networks":[' in response_buffer: + import re + + # Look for the networks JSON pattern that appears first + networks_pattern = r'\{"networks":\[.*?\]\}' + matches = re.findall(networks_pattern, response_buffer, re.DOTALL) + + for match in matches: + try: + # Parse the networks JSON directly + networks_data = json.loads(match) + if "networks" in networks_data: + # Return in the expected format for compatibility + return {"results": [json.dumps({"result": match})]} + except json.JSONDecodeError: + continue + + # Also check if we have the command result indicating completion + if '{"results":' in response_buffer and '"Networks scanned"' in response_buffer: + # We've received the completion message, parse any networks found + import re + networks_pattern = r'\{"networks":\[.*?\]\}' + matches = re.findall(networks_pattern, response_buffer, re.DOTALL) + + for match in matches: + try: + networks_data = json.loads(match) + if "networks" in networks_data: + return {"results": [json.dumps({"result": match})]} + except json.JSONDecodeError: + continue + + # If we get here, scan completed but no networks found + return {"results": [json.dumps({"result": '{"networks":[]}'})]} + else: + time.sleep(0.1) + + return {"error": "Scan timeout"} + + def scan_networks(self, timeout: int = 30) -> bool: + """Scan for WiFi networks""" + print(f"🔍 Scanning for WiFi networks (this may take up to {timeout} seconds)...") + response = self.send_command("scan_networks", timeout=timeout) + + if "error" in response: + print(f"❌ Scan failed: {response['error']}") + return False + + try: + # Parse the nested JSON response + results = response.get("results", []) + if not results: + print("❌ No scan results received") + return False + + # The result is JSON-encoded string inside the response + result_data = json.loads(results[0]) + networks_data = json.loads(result_data["result"]) + + self.networks = [] + channels_found = set() + + for net in networks_data.get("networks", []): + network = WiFiNetwork( + ssid=net["ssid"], + channel=net["channel"], + rssi=net["rssi"], + mac_address=net["mac_address"], + auth_mode=net["auth_mode"] + ) + self.networks.append(network) + channels_found.add(net["channel"]) + + # Sort networks by RSSI (strongest first) + self.networks.sort(key=lambda x: x.rssi, reverse=True) + + print(f"✅ Found {len(self.networks)} networks on channels: {sorted(channels_found)}") + return True + + except Exception as e: + print(f"❌ Failed to parse scan results: {e}") + return False + + def set_wifi(self, ssid: str, password: str) -> bool: + """Configure WiFi credentials""" + print(f"🔧 Setting WiFi credentials for '{ssid}'...") + + params = { + "name": "main", + "ssid": ssid, + "password": password, + "channel": 0, + "power": 0 + } + + response = self.send_command("set_wifi", params) + + if "error" in response: + print(f"❌ WiFi setup failed: {response['error']}") + return False + + print("✅ WiFi credentials set successfully") + return True + + def get_wifi_status(self) -> Dict: + """Get current WiFi connection status""" + response = self.send_command("get_wifi_status") + + if "error" in response: + print(f"❌ Failed to get WiFi status: {response['error']}") + return {} + + try: + # Parse the nested JSON response + results = response.get("results", []) + if results: + result_data = json.loads(results[0]) + # The result is a JSON-encoded string, need to decode it + status_json = result_data["result"] + + # First, unescape the JSON string properly + # Replace escaped backslashes and quotes + status_json = status_json.replace('\\\\', '\\') + status_json = status_json.replace('\\"', '"') + + # Now parse the cleaned JSON + status_data = json.loads(status_json) + return status_data + except Exception as e: + print(f"❌ Failed to parse WiFi status: {e}") + # Try to show raw response for debugging + if "results" in response and response["results"]: + print(f"📝 Raw result: {response['results'][0]}") + + return {} + + def connect_wifi(self) -> bool: + """Attempt to connect to configured WiFi""" + print("🔗 Attempting WiFi connection...") + response = self.send_command("connect_wifi") + + if "error" in response: + print(f"❌ WiFi connection failed: {response['error']}") + return False + + print("✅ WiFi connection attempt started") + return True + + def start_streaming(self) -> bool: + """Start streaming mode""" + print("🚀 Starting streaming mode...") + response = self.send_command("start_streaming") + + if "error" in response: + print(f"❌ Failed to start streaming: {response['error']}") + return False + + print("✅ Streaming mode started") + return True + + def switch_mode(self, mode: str) -> bool: + """Switch device mode between WiFi, UVC, and Auto""" + print(f"🔄 Switching device mode to '{mode}'...") + + params = {"mode": mode} + response = self.send_command("switch_mode", params) + + if "error" in response: + print(f"❌ Failed to switch mode: {response['error']}") + return False + + print(f"✅ Device mode switched to '{mode}' successfully!") + print("🔄 Please restart the device for changes to take effect") + return True + + def get_device_mode(self) -> str: + """Get current device mode""" + response = self.send_command("get_device_mode") + + if "error" in response: + print(f"❌ Failed to get device mode: {response['error']}") + return "unknown" + + try: + results = response.get("results", []) + if results: + result_data = json.loads(results[0]) + mode_data = json.loads(result_data["result"]) + return mode_data.get("mode", "unknown") + except Exception as e: + print(f"❌ Failed to parse mode response: {e}") + return "unknown" + + def monitor_logs(self): + """Monitor device logs until interrupted""" + print("📋 Monitoring device logs (Press Ctrl+C to exit)...") + print("-" * 60) + + if not self.connection or not self.connection.is_open: + print("❌ Not connected to device") + return + + try: + while True: + try: + if self.connection.in_waiting > 0: + line = self.connection.readline().decode().strip() + if line: + # Skip JSON command responses, show raw logs + if not (line.startswith('{') and line.endswith('}')): + print(line) + elif "heartbeat" not in line: + # Show non-heartbeat JSON responses + print(f"📡 {line}") + else: + time.sleep(0.1) # Small delay to prevent busy waiting + except Exception: + continue + except KeyboardInterrupt: + print("\n🛑 Log monitoring stopped") + + +class OpenIrisDiscovery: + def __init__(self): + self.devices: Dict[str, OpenIrisDevice] = {} + self.discovery_active = False + + def discover_devices(self, timeout: int = 3) -> List[OpenIrisDevice]: + """Discover OpenIris devices via heartbeat - ultra-fast concurrent scanning""" + print(f"⚡ Fast-scanning for OpenIris devices...") + + # Get all serial ports + ports = list(serial.tools.list_ports.comports()) + if not ports: + print("❌ No serial ports found") + return [] + + # Prioritize likely ESP32 USB ports for faster detection + priority_ports = [] + other_ports = [] + + for port in ports: + # Common ESP32 USB-to-serial descriptions + desc_lower = (port.description or "").lower() + # Include generic "USB Serial Device" which is common on Windows + if any(keyword in desc_lower for keyword in + ["cp210", "ch340", "ftdi", "esp32", "silicon labs", "usb-serial", "usb serial", "usb serial device"]): + priority_ports.append(port) + else: + other_ports.append(port) + + # Check priority ports first, then others + sorted_ports = priority_ports + other_ports + + if priority_ports: + print(f"📡 Checking {len(sorted_ports)} ports ({len(priority_ports)} prioritized USB serial ports)...") + else: + print(f"📡 Checking {len(sorted_ports)} serial ports...") + + discovered = {} + lock = threading.Lock() + threads = [] + + def check_port_fast(port_info): + """Check a single port for OpenIris heartbeat - optimized for speed""" + try: + # Initial connection timeout - 500ms + ser = serial.Serial(port_info.device, 115200, timeout=0.5) + ser.reset_input_buffer() + + # Wait up to 2 seconds for heartbeat + start_time = time.time() + while time.time() - start_time < 2.0: + try: + # Read timeout - 200ms + ser.timeout = 10 + if ser.in_waiting > 0: + line = ser.readline() + if line: + try: + data = json.loads(line.decode().strip()) + if (data.get("heartbeat") == "openiris_setup_mode" and + "serial" in data): + serial_num = data["serial"] + with lock: + if serial_num not in discovered: + device = OpenIrisDevice(port_info.device, serial_num, debug=False) + discovered[serial_num] = device + print(f"💓 Found {serial_num} on {port_info.device}") + # Return immediately to stop checking this port + ser.close() + return True + except (json.JSONDecodeError, UnicodeDecodeError): + pass + else: + time.sleep(0.05) # Very short sleep + except Exception: + pass + + ser.close() + except Exception: + # Port not available or not the right device + pass + return False + + # Start concurrent port checking + for port in sorted_ports: + thread = threading.Thread(target=check_port_fast, args=(port,)) + thread.daemon = True + thread.start() + threads.append(thread) + + # Wait for threads to complete or timeout + timeout_time = time.time() + timeout + for thread in threads: + remaining = timeout_time - time.time() + if remaining > 0: + thread.join(timeout=remaining) + + # If we found at least one device, return immediately + if discovered: + break + + devices = list(discovered.values()) + + if devices: + print(f"✅ Found {len(devices)} OpenIris device(s)") + else: + print("❌ No OpenIris devices found in {:.1f} seconds".format(time.time() - (timeout_time - timeout))) + print("💡 Device has 20-second setup window after power on") + + return devices + + def _check_port(self, port: str, discovered: Dict, timeout: int): + """Check a single port for OpenIris heartbeat""" + try: + with serial.Serial(port, 115200, timeout=1) as ser: + start_time = time.time() + + while time.time() - start_time < timeout: + try: + line = ser.readline().decode().strip() + if line: + try: + data = json.loads(line) + if (data.get("heartbeat") == "openiris_setup_mode" and + "serial" in data): + + serial_num = data["serial"] + if serial_num not in discovered: + discovered[serial_num] = OpenIrisDevice(port, serial_num, debug=False) + print(f"💓 Found device {serial_num} on {port}") + return + except json.JSONDecodeError: + continue + except Exception: + continue + except Exception: + # Port not available or not a serial device + pass + + +def display_networks(networks: List[WiFiNetwork]): + """Display available WiFi networks in a formatted table""" + if not networks: + print("❌ No networks available") + return + + print("\n📡 Available WiFi Networks:") + print("-" * 85) + print(f"{'#':<3} {'SSID':<32} {'Channel':<8} {'Signal':<20} {'Security':<15}") + print("-" * 85) + + # Networks are already sorted by signal strength from scan_networks + for i, network in enumerate(networks, 1): + # Create signal strength visualization + signal_bars = "▓" * min(5, max(0, (network.rssi + 100) // 10)) + signal_str = f"{signal_bars:<5} ({network.rssi} dBm)" + + # Format SSID (show hidden networks as ) + ssid_display = network.ssid if network.ssid else "" + + print(f"{i:<3} {ssid_display:<32} {network.channel:<8} {signal_str:<20} {network.security_type:<15}") + + print("-" * 85) + + # Show channel distribution + channels = {} + for net in networks: + channels[net.channel] = channels.get(net.channel, 0) + 1 + + print(f"\n📊 Channel distribution: ", end="") + for ch in sorted(channels.keys()): + print(f"Ch{ch}: {channels[ch]} networks ", end="") + print() + + +def main(): + parser = argparse.ArgumentParser(description="OpenIris Setup CLI Tool") + parser.add_argument("--timeout", type=int, default=3, + help="Discovery timeout in seconds (default: 3)") + parser.add_argument("--port", type=str, + help="Skip discovery and connect directly to specified port") + parser.add_argument("--scan-timeout", type=int, default=30, + help="WiFi scan timeout in seconds (default: 30)") + parser.add_argument("--no-auto", action="store_true", + help="Don't auto-connect to first device found") + parser.add_argument("--debug", action="store_true", + help="Show debug output including raw serial data") + args = parser.parse_args() + + print("🔧 OpenIris Setup Tool") + print("=" * 50) + + device = None + + try: + if args.port: + # Connect directly to specified port + print(f"📡 Connecting directly to {args.port}...") + device = OpenIrisDevice(args.port, "direct", debug=args.debug) + if not device.connect(): + return 1 + else: + # Fast device discovery + discovery = OpenIrisDiscovery() + devices = discovery.discover_devices(args.timeout) + + if not devices: + print("\n❌ No OpenIris devices found automatically") + print("\n💡 Troubleshooting:") + print(" - Make sure device is connected via USB") + print(" - Device must be powered on within last 20 seconds") + print(" - Try specifying port manually with --port") + + # Show available ports to help user + print("\n📋 Available serial ports:") + all_ports = list(serial.tools.list_ports.comports()) + for p in all_ports: + print(f" - {p.device}: {p.description}") + + # Offer manual port entry + manual_port = input("\n🔌 Enter serial port manually (e.g. COM15, /dev/ttyUSB0) or press Enter to exit: ").strip() + if manual_port: + device = OpenIrisDevice(manual_port, "manual", debug=args.debug) + if not device.connect(): + return 1 + else: + return 1 + else: + # Auto-connect to first device found (unless disabled) + if len(devices) == 1 or not args.no_auto: + device = devices[0] + device.debug = args.debug # Set debug mode + print(f"\n🎯 Auto-connecting to {device.serial_number}...") + if not device.connect(): + return 1 + else: + # Multiple devices found with no-auto flag + print("\n🔢 Multiple devices found. Select one:") + for i, dev in enumerate(devices, 1): + print(f" {i}. {dev.serial_number} on {dev.port}") + + while True: + try: + choice = int(input("\nEnter device number: ")) - 1 + if 0 <= choice < len(devices): + device = devices[choice] + break + else: + print("❌ Invalid selection") + except ValueError: + print("❌ Please enter a number") + + # Connect to selected device + device.debug = args.debug # Set debug mode + if not device.connect(): + return 1 + + # Main interaction loop + while True: + print("\n🔧 Setup Options:") + print("1. 🔍 Scan for WiFi networks") + print("2. 📡 Show available networks") + print("3. 🔐 Configure WiFi") + print("4. 📶 Check WiFi status") + print("5. 🔗 Connect to WiFi") + print("6. 🚀 Start streaming mode") + print("7. 🔄 Switch device mode (WiFi/UVC/Auto)") + print("8. 📋 Monitor logs") + print("9. 🚪 Exit") + + choice = input("\nSelect option (1-9): ").strip() + + if choice == "1": + # Ask if user wants custom timeout + custom = input("Use custom scan timeout? (y/N): ").strip().lower() + if custom == 'y': + try: + timeout = int(input("Enter timeout in seconds (5-120): ")) + if 5 <= timeout <= 120: + device.scan_networks(timeout) + else: + print("❌ Timeout must be between 5 and 120 seconds") + device.scan_networks(args.scan_timeout) + except ValueError: + print("❌ Invalid timeout, using default") + device.scan_networks(args.scan_timeout) + else: + device.scan_networks(args.scan_timeout) + + elif choice == "2": + display_networks(device.networks) + + elif choice == "3": + if not device.networks: + print("❌ No networks available. Please scan first.") + continue + + display_networks(device.networks) + + while True: + try: + net_choice = input("\nEnter network number (or 'back'): ").strip() + if net_choice.lower() == 'back': + break + + net_idx = int(net_choice) - 1 + sorted_networks = sorted(device.networks, key=lambda x: x.rssi, reverse=True) + + if 0 <= net_idx < len(sorted_networks): + selected_network = sorted_networks[net_idx] + + print(f"\n🔐 Selected: {selected_network.ssid}") + print(f"Security: {selected_network.security_type}") + + if selected_network.auth_mode == 0: # Open network + password = "" + print("🔓 Open network - no password required") + else: + password = input("Enter WiFi password: ") + + if device.set_wifi(selected_network.ssid, password): + print("✅ WiFi configured successfully!") + print("💡 Next steps:") + print(" 4. Check WiFi status") + print(" 5. Connect to WiFi (if needed)") + print(" 6. Start streaming when connected") + break + else: + print("❌ Invalid network number") + except ValueError: + print("❌ Please enter a number or 'back'") + + elif choice == "4": + # Check WiFi status + status = device.get_wifi_status() + if status: + print(f"📶 WiFi Status: {status.get('status', 'unknown')}") + print(f"📡 Networks configured: {status.get('networks_configured', 0)}") + if status.get('ip_address'): + print(f"🌐 IP Address: {status['ip_address']}") + else: + print("❌ Unable to get WiFi status") + + elif choice == "5": + # Attempt WiFi connection + device.connect_wifi() + print("🕰️ Wait a few seconds then check status (option 4)") + + elif choice == "6": + device.start_streaming() + print("🚀 Streaming started! Use option 8 to monitor logs.") + + elif choice == "7": + # Switch device mode + current_mode = device.get_device_mode() + print(f"\n📍 Current device mode: {current_mode}") + print("\n🔄 Select new device mode:") + print("1. WiFi - Stream over WiFi connection") + print("2. UVC - Stream as USB webcam") + print("3. Auto - Automatic mode selection") + + mode_choice = input("\nSelect mode (1-3): ").strip() + + if mode_choice == "1": + device.switch_mode("wifi") + elif mode_choice == "2": + device.switch_mode("uvc") + elif mode_choice == "3": + device.switch_mode("auto") + else: + print("❌ Invalid mode selection") + + elif choice == "8": + device.monitor_logs() + + elif choice == "9": + break + + else: + print("❌ Invalid option") + + except KeyboardInterrupt: + print("\n🛑 Setup interrupted") + + finally: + if device: + device.disconnect() + + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tools/requirements.txt b/tools/requirements.txt new file mode 100644 index 0000000..29284eb --- /dev/null +++ b/tools/requirements.txt @@ -0,0 +1 @@ +pyserial>=3.5 \ No newline at end of file diff --git a/tools/setup.bat b/tools/setup.bat new file mode 100644 index 0000000..0da4843 --- /dev/null +++ b/tools/setup.bat @@ -0,0 +1,7 @@ +@echo off +echo Installing OpenIris Setup Tool dependencies... +pip install -r requirements.txt +echo. +echo Setup complete! Run the tool with: +echo python openiris_setup.py +pause \ No newline at end of file diff --git a/tools/setup.sh b/tools/setup.sh new file mode 100644 index 0000000..f70dc19 --- /dev/null +++ b/tools/setup.sh @@ -0,0 +1,6 @@ +#!/bin/bash +echo "Installing OpenIris Setup Tool dependencies..." +pip3 install -r requirements.txt +echo "" +echo "Setup complete! Run the tool with:" +echo "python3 openiris_setup.py" \ No newline at end of file diff --git a/tools/wifi_scanner.py b/tools/wifi_scanner.py new file mode 100644 index 0000000..d5397f6 --- /dev/null +++ b/tools/wifi_scanner.py @@ -0,0 +1,177 @@ +import serial +import time +import json +from typing import List, Dict + +class ESPWiFiScanner: + def __init__(self, port: str, baudrate: int = 115200): + self.port = port + self.baudrate = baudrate + self.serial = None + + def connect(self) -> bool: + try: + self.serial = serial.Serial( + port=self.port, + baudrate=self.baudrate, + timeout=1 + ) + return True + except serial.SerialException as e: + print(f"Error connecting to ESP32: {e}") + return False + + def scan_networks(self, timeout_seconds: int = 30) -> List[Dict]: + if not self.serial: + print("Not connected to ESP32") + return [] + + self.serial.reset_input_buffer() + + command = '{"commands":[{"command":"scan_networks","data":{}}]}\n' + self.serial.write(command.encode()) + + timeout_start = time.time() + response_buffer = "" + + while time.time() - timeout_start < timeout_seconds: + if self.serial.in_waiting: + data = self.serial.read(self.serial.in_waiting).decode('utf-8', errors='ignore') + response_buffer += data + + # Look for WiFi networks JSON directly (new format) + # The scan command now outputs JSON directly followed by command result + if '{"networks":[' in response_buffer: + import re + + # Look for the networks JSON pattern that appears first + networks_pattern = r'\{"networks":\[.*?\]\}' + matches = re.findall(networks_pattern, response_buffer, re.DOTALL) + + for match in matches: + try: + # Parse the networks JSON directly + networks_data = json.loads(match) + if "networks" in networks_data: + return networks_data["networks"] + except json.JSONDecodeError: + continue + + # Also check if we have the command result indicating completion + if '{"results":' in response_buffer and '"Networks scanned"' in response_buffer: + # We've received the completion message, parse any networks found + import re + networks_pattern = r'\{"networks":\[.*?\]\}' + matches = re.findall(networks_pattern, response_buffer, re.DOTALL) + + for match in matches: + try: + networks_data = json.loads(match) + if "networks" in networks_data: + return networks_data["networks"] + except json.JSONDecodeError: + continue + + # If we get here, scan completed but no networks found + return [] + else: + time.sleep(0.1) + + print("Failed to receive clean JSON response. Raw data:") + print("=" * 50) + print(response_buffer) + print("=" * 50) + return [] + + def close(self): + if self.serial: + self.serial.close() + +def main(): + import sys + import argparse + + parser = argparse.ArgumentParser(description='ESP32 WiFi Scanner') + parser.add_argument('port', nargs='?', default='COM15', help='Serial port (default: COM9)') + parser.add_argument('-t', '--timeout', type=int, default=30, + help='Scan timeout in seconds (default: 30)') + args = parser.parse_args() + + scanner = ESPWiFiScanner(args.port) + + if scanner.connect(): + print(f"Connected to ESP32 on {args.port}") + print(f"Scanning for WiFi networks (timeout: {args.timeout} seconds)...") + start_time = time.time() + networks = scanner.scan_networks(args.timeout) + scan_time = time.time() - start_time + + if networks: + # Sort by RSSI (strongest first) + networks.sort(key=lambda x: x.get('rssi', -100), reverse=True) + + print(f"\n✅ Found {len(networks)} WiFi Networks in {scan_time:.1f} seconds:") + print("{:<32} | {:<7} | {:<15} | {:<17} | {:<9}".format( + "SSID", "Channel", "Signal", "MAC Address", "Security" + )) + print("-" * 85) + + # Track channels found + channels_found = set() + auth_modes = { + 0: "Open", + 1: "WEP", + 2: "WPA-PSK", + 3: "WPA2-PSK", + 4: "WPA/WPA2", + 5: "WPA2-Enterprise", + 6: "WPA3-PSK", + 7: "WPA2/WPA3", + 8: "WAPI-PSK" + } + + for network in networks: + ssid = network.get('ssid', '') + if not ssid: + ssid = "" + + channel = network.get('channel', 0) + channels_found.add(channel) + + # Create signal strength visualization + rssi = network.get('rssi', -100) + signal_bars = "▓" * min(5, max(0, (rssi + 100) // 10)) + signal_str = f"{signal_bars:<5} ({rssi} dBm)" + + auth_mode = network.get('auth_mode', 0) + security = auth_modes.get(auth_mode, f"Type {auth_mode}") + + print("{:<32} | {:<7} | {:<15} | {:<17} | {:<9}".format( + ssid[:32], # Truncate long SSIDs + channel, + signal_str, + network.get('mac_address', '?'), + security + )) + + # Show channel distribution + print(f"\n📊 Channels detected: {sorted(channels_found)}") + channel_counts = {} + for net in networks: + ch = net.get('channel', 0) + channel_counts[ch] = channel_counts.get(ch, 0) + 1 + + print("📊 Channel distribution: ", end="") + for ch in sorted(channel_counts.keys()): + print(f"Ch{ch}: {channel_counts[ch]} networks ", end="") + print() + + else: + print("❌ No networks found or scan failed") + + scanner.close() + else: + print(f"Failed to connect to ESP32 on {args.port}") + +if __name__ == "__main__": + main() \ No newline at end of file