From b7db68457c63eef5e5fe366d26d55bbc639da557 Mon Sep 17 00:00:00 2001 From: Xiaoxia Date: Tue, 9 Dec 2025 09:24:56 +0800 Subject: [PATCH] v2.1.0: Upgrade esp-wifi-connect to 3.0; New device state machine (#1528) * Upgrade component version * update fonts component version * Handle OTA error code * Update project version to 2.1.0 and add device state machine implementation - Upgrade esp-wifi-connect to 3.0.0, allowing reconfiguring wifi without rebooting - Introduce device state machine with state change notification in new files - Remove obsolete device state event files - Update application logic to utilize new state machine - Minor adjustments in various board implementations for state handling * fix compile errors * Refactor power saving mode implementation to use PowerSaveLevel enumeration - Updated Application class to replace SetPowerSaveMode with SetPowerSaveLevel, allowing for LOW_POWER and PERFORMANCE settings. - Modified various board implementations to align with the new power save level structure. - Ensured consistent handling of power save levels across different board files, enhancing code maintainability and clarity. * Refactor power save level checks across multiple board implementations - Updated the condition for power save level checks in various board files to ensure that the power save timer only wakes up when the level is not set to LOW_POWER. - Improved consistency in handling power save levels, enhancing code clarity and maintainability. * Refactor EnterWifiConfigMode calls in board implementations - Updated calls to EnterWifiConfigMode to use the appropriate instance reference (self or board) across multiple board files. - Improved code consistency and clarity in handling device state during WiFi configuration mode entry. * Add cellular modem event handling and improve network status updates - Introduced new network events for cellular modem operations, including detecting, registration errors, and timeouts. - Enhanced the Application class to handle different network states and update the display status accordingly. - Refactored Ml307Board to implement a callback mechanism for network events, improving modularity and responsiveness. - Updated dual_network_board and board headers to support new network event callbacks, ensuring consistent handling across board implementations. * update esp-wifi-connect version * Update WiFi configuration tool messages across multiple board implementations to clarify user actions --- CMakeLists.txt | 2 +- docs/custom-board.md | 5 +- main/CMakeLists.txt | 2 +- main/application.cc | 890 ++++++++++-------- main/application.h | 115 ++- main/boards/aipi-lite/aipi-lite.cc | 15 +- .../atk-dnesp32s3-box/atk_dnesp32s3_box.cc | 6 +- .../atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc | 12 +- .../atk_dnesp32s3_box2.cc | 12 +- .../atk_dnesp32s3_box2.cc | 12 +- main/boards/atk-dnesp32s3/atk_dnesp32s3.cc | 6 +- .../atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc | 6 +- main/boards/atom-echos3r/atom_echos3r.cc | 6 +- .../atommatrix_echo_base.cc | 6 +- .../atoms3-echo-base/atoms3_echo_base.cc | 6 +- .../atoms3r_cam_m12_echo_base.cc | 1 - .../atoms3r-echo-base/atoms3r_echo_base.cc | 6 +- .../esp32_bread_board_lcd.cc | 6 +- .../bread-compact-esp32/esp32_bread_board.cc | 6 +- .../compact_ml307_board.cc | 6 +- .../compact_wifi_board_lcd.cc | 6 +- .../compact_wifi_board_s3cam.cc | 6 +- .../bread-compact-wifi/compact_wifi_board.cc | 6 +- main/boards/common/afsk_demod.cc | 18 +- main/boards/common/afsk_demod.h | 4 +- main/boards/common/board.h | 32 +- main/boards/common/dual_network_board.cc | 9 +- main/boards/common/dual_network_board.h | 3 +- main/boards/common/ml307_board.cc | 123 ++- main/boards/common/ml307_board.h | 11 +- main/boards/common/wifi_board.cc | 423 +++++---- main/boards/common/wifi_board.h | 53 +- main/boards/df-k10/df_k10_board.cc | 11 +- main/boards/df-s3-ai-cam/df_s3_ai_cam.cc | 6 +- main/boards/doit-s3-aibox/doit_s3_aibox.cc | 10 +- main/boards/du-chatx/du-chatx-wifi.cc | 12 +- main/boards/echoear/EchoEar.cc | 11 +- main/boards/electron-bot/electron_bot.cc | 7 +- main/boards/esp-box-3/esp_box3_board.cc | 6 +- .../boards/esp-box-lite/esp_box_lite_board.cc | 8 +- main/boards/esp-box/esp_box_board.cc | 6 +- main/boards/esp-hi/esp_hi.cc | 9 +- .../esp-p4-function-ev-board.cc | 9 +- .../esp-s3-lcd-ev-board-2.cc | 6 +- .../esp-s3-lcd-ev-board.cc | 6 +- .../boards/esp-sparkbot/esp_sparkbot_board.cc | 6 +- main/boards/esp-spot/esp_spot_board.cc | 9 +- .../esp32-cgc-144/esp32_cgc_144_board.cc | 12 +- main/boards/esp32-cgc/esp32_cgc_board.cc | 6 +- .../esp32-s3-touch-amoled-1.8.cc | 16 +- .../esp32-s3-touch-lcd-1.46.cc | 6 +- .../esp32-s3-touch-lcd-1.85.cc | 6 +- .../esp32-s3-touch-lcd-1.85c.cc | 6 +- .../esp32-s3-touch-lcd-3.5.cc | 16 +- .../esp32s3_korvo2_v3_board.cc | 6 +- .../genjutech-s3-1.54tft.cc | 12 +- main/boards/hu-087/hu_087_board.cc | 233 +++-- main/boards/jiuchuan-s3/jiuchuan_dev_board.cc | 13 +- main/boards/kevin-box-2/kevin_box_board.cc | 5 +- main/boards/kevin-c3/kevin_c3_board.cc | 6 +- .../kevin-sp-v3-dev/kevin-sp-v3_board.cc | 6 +- .../kevin-sp-v4-dev/kevin-sp-v4_board.cc | 6 +- .../kevin_yuying_313lcd.cc | 6 +- .../labplus-ledong-v2/labplus_ledong_v2.cc | 6 +- main/boards/labplus-mpython-v3/mpython_pro.cc | 6 +- .../lichuang-c3-dev/lichuang_c3_dev_board.cc | 6 +- .../boards/lichuang-dev/lichuang_dev_board.cc | 20 +- .../lilygo-t-cameraplus-s3.cc | 17 +- .../lilygo-t-circle-s3/lilygo-t-circle-s3.cc | 16 +- .../lilygo-t-display-p4.cc | 7 +- .../lilygo-t-display-s3-pro-mvsrlora.cc | 16 +- .../boards/m5stack-core-s3/m5stack_core_s3.cc | 13 +- main/boards/m5stack-tab5/m5stack_tab5.cc | 6 +- .../magiclick-2p4/magiclick_2p4_board.cc | 17 +- .../magiclick-2p5/magiclick_2p5_board.cc | 17 +- .../magiclick-c3-v2/magiclick_c3_v2_board.cc | 6 +- .../boards/magiclick-c3/magiclick_c3_board.cc | 6 +- main/boards/minsi-k08-dual/minsi_k08_dual.cc | 12 +- main/boards/mixgo-nova/mixgo-nova.cc | 6 +- .../movecall_cuican_esp32s3.cc | 6 +- .../movecall_moji_esp32s3.cc | 6 +- main/boards/otto-robot/otto_controller.cc | 6 +- main/boards/otto-robot/otto_robot.cc | 9 +- .../sensecap-watcher/sensecap_watcher.cc | 17 +- .../sp-esp32-s3-1.28-box.cc | 20 +- .../sp-esp32-s3-1.54-muma.cc | 18 +- .../surfer-c3-1.14tft/surfer-c3-1.14tft.cc | 12 +- main/boards/taiji-pi-s3/taiji_pi_s3.cc | 7 +- .../esp32-c6-lcd-1.69.cc | 12 +- .../esp32-c6-touch-amoled-1.32.cc | 9 +- .../esp32-c6-touch-amoled-1.43.cc | 6 +- .../esp32-c6-touch-amoled-2.06.cc | 18 +- .../boards/waveshare-p4-nano/esp32-p4-nano.cc | 10 +- .../esp32-p4-wifi6-touch-lcd-4b.cc | 10 +- .../esp32-p4-wifi6-touch-lcd-7b.cc | 10 +- .../esp32-p4-wifi6-touch-lcd-xc.cc | 10 +- .../esp32-s3-audio_board.cc | 6 +- .../waveshare-s3-epaper-1.54.cc | 9 +- .../esp32-s3-touch-amoled-1.32.cc | 9 +- .../esp32-s3-touch-amoled-1.75.cc | 17 +- .../esp32-s3-touch-amoled-2.06.cc | 17 +- .../esp32-s3-touch-lcd-1.83.cc | 17 +- .../waveshare-s3-touch-lcd-3.49.cc | 6 +- .../waveshare-s3-touch-lcd-3.5b.cc | 12 +- .../esp32-s3-touch-lcd-4b.cc | 17 +- .../wireless-tag-wtp4c5mp07s.cc | 10 +- .../xingzhi-cube-0.85tft-ml307.cc | 7 +- .../xingzhi-cube-0.85tft-wifi.cc | 12 +- .../xingzhi-cube-0.96oled-ml307.cc | 12 +- .../xingzhi-cube-0.96oled-wifi.cc | 12 +- .../xingzhi-cube-1.54tft-ml307.cc | 12 +- .../xingzhi-cube-1.54tft-wifi.cc | 12 +- .../boards/xingzhi-metal-1.54-wifi/cst816x.cc | 9 +- .../xingzhi-metal-1.54-wifi.cc | 7 +- main/boards/xmini-c3-4g/xmini_c3_4g_board.cc | 7 +- main/boards/xmini-c3-v3/xmini_c3_board.cc | 14 +- main/boards/xmini-c3/xmini_c3_board.cc | 12 +- main/boards/yunliao-s3/yunliao_s3.cc | 9 +- .../zhengchen-1.54tft-ml307.cc | 12 +- .../zhengchen-1.54tft-wifi.cc | 14 +- main/device_state_event.cc | 46 - main/device_state_event.h | 39 - main/device_state_machine.cc | 161 ++++ main/device_state_machine.h | 83 ++ main/idf_component.yml | 4 +- main/main.cc | 8 +- main/mcp_server.cc | 4 +- main/ota.cc | 13 +- main/ota.h | 3 +- main/protocols/mqtt_protocol.cc | 18 +- main/protocols/mqtt_protocol.h | 5 + 131 files changed, 1986 insertions(+), 1335 deletions(-) delete mode 100644 main/device_state_event.cc delete mode 100644 main/device_state_event.h create mode 100644 main/device_state_machine.cc create mode 100644 main/device_state_machine.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 9f147d27..8dcd6df0 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "2.0.5") +set(PROJECT_VER "2.1.0") # Add this line to disable the specific warning add_compile_options(-Wno-missing-field-initializers) diff --git a/docs/custom-board.md b/docs/custom-board.md index 6349b83d..9e7bd8f7 100644 --- a/docs/custom-board.md +++ b/docs/custom-board.md @@ -197,8 +197,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 825dd877..68d6004e 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -33,7 +33,7 @@ set(SOURCES "audio/audio_codec.cc" "application.cc" "ota.cc" "settings.cc" - "device_state_event.cc" + "device_state_machine.cc" "assets.cc" "main.cc" ) diff --git a/main/application.cc b/main/application.cc index f1e142db..2ab244f1 100644 --- a/main/application.cc +++ b/main/application.cc @@ -20,21 +20,6 @@ #define TAG "Application" -static const char* const STATE_STRINGS[] = { - "unknown", - "starting", - "configuring", - "idle", - "connecting", - "listening", - "speaking", - "upgrading", - "activating", - "audio_testing", - "fatal_error", - "invalid_state" -}; - Application::Application() { event_group_ = xEventGroupCreate(); @@ -69,7 +54,292 @@ Application::~Application() { vEventGroupDelete(event_group_); } +bool Application::SetDeviceState(DeviceState state) { + return state_machine_.TransitionTo(state); +} + +void Application::Initialize() { + auto& board = Board::GetInstance(); + SetDeviceState(kDeviceStateStarting); + + // Setup the display + auto display = board.GetDisplay(); + + // Print board name/version info + display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str()); + + // Setup the audio service + auto codec = board.GetAudioCodec(); + audio_service_.Initialize(codec); + audio_service_.Start(); + + AudioServiceCallbacks callbacks; + callbacks.on_send_queue_available = [this]() { + xEventGroupSetBits(event_group_, MAIN_EVENT_SEND_AUDIO); + }; + callbacks.on_wake_word_detected = [this](const std::string& wake_word) { + xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED); + }; + callbacks.on_vad_change = [this](bool speaking) { + xEventGroupSetBits(event_group_, MAIN_EVENT_VAD_CHANGE); + }; + audio_service_.SetCallbacks(callbacks); + + // Add state change listeners + state_machine_.AddStateChangeListener([this](DeviceState old_state, DeviceState new_state) { + xEventGroupSetBits(event_group_, MAIN_EVENT_STATE_CHANGED); + }); + + // Start the clock timer to update the status bar + esp_timer_start_periodic(clock_timer_handle_, 1000000); + + // Add MCP common tools (only once during initialization) + auto& mcp_server = McpServer::GetInstance(); + mcp_server.AddCommonTools(); + mcp_server.AddUserOnlyTools(); + + // Set network event callback for UI updates and network state handling + board.SetNetworkEventCallback([this](NetworkEvent event, const std::string& data) { + auto display = Board::GetInstance().GetDisplay(); + + switch (event) { + case NetworkEvent::Scanning: + display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000); + xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_DISCONNECTED); + break; + case NetworkEvent::Connecting: { + if (data.empty()) { + // Cellular network - registering without carrier info yet + display->SetStatus(Lang::Strings::REGISTERING_NETWORK); + } else { + // WiFi or cellular with carrier info + std::string msg = Lang::Strings::CONNECT_TO; + msg += data; + msg += "..."; + display->ShowNotification(msg.c_str(), 30000); + } + break; + } + case NetworkEvent::Connected: { + std::string msg = Lang::Strings::CONNECTED_TO; + msg += data; + display->ShowNotification(msg.c_str(), 30000); + xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_CONNECTED); + break; + } + case NetworkEvent::Disconnected: + xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_DISCONNECTED); + break; + case NetworkEvent::WifiConfigModeEnter: + // WiFi config mode enter is handled by WifiBoard internally + break; + case NetworkEvent::WifiConfigModeExit: + // WiFi config mode exit is handled by WifiBoard internally + break; + // Cellular modem specific events + case NetworkEvent::ModemDetecting: + display->SetStatus(Lang::Strings::DETECTING_MODULE); + break; + case NetworkEvent::ModemErrorNoSim: + Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN); + break; + case NetworkEvent::ModemErrorRegDenied: + Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG); + break; + case NetworkEvent::ModemErrorInitFailed: + display->SetStatus(Lang::Strings::DETECTING_MODULE); + display->SetChatMessage("system", Lang::Strings::DETECTING_MODULE); + break; + case NetworkEvent::ModemErrorTimeout: + display->SetStatus(Lang::Strings::REGISTERING_NETWORK); + break; + } + }); + + // Start network asynchronously + board.StartNetwork(); + + // Update the status bar immediately to show the network state + display->UpdateStatusBar(true); +} + +void Application::Run() { + const EventBits_t ALL_EVENTS = + MAIN_EVENT_SCHEDULE | + MAIN_EVENT_SEND_AUDIO | + MAIN_EVENT_WAKE_WORD_DETECTED | + MAIN_EVENT_VAD_CHANGE | + MAIN_EVENT_CLOCK_TICK | + MAIN_EVENT_ERROR | + MAIN_EVENT_NETWORK_CONNECTED | + MAIN_EVENT_NETWORK_DISCONNECTED | + MAIN_EVENT_TOGGLE_CHAT | + MAIN_EVENT_START_LISTENING | + MAIN_EVENT_STOP_LISTENING | + MAIN_EVENT_ACTIVATION_DONE | + MAIN_EVENT_STATE_CHANGED; + + while (true) { + auto bits = xEventGroupWaitBits(event_group_, ALL_EVENTS, pdTRUE, pdFALSE, portMAX_DELAY); + + if (bits & MAIN_EVENT_ERROR) { + SetDeviceState(kDeviceStateIdle); + Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); + } + + if (bits & MAIN_EVENT_NETWORK_CONNECTED) { + HandleNetworkConnectedEvent(); + } + + if (bits & MAIN_EVENT_NETWORK_DISCONNECTED) { + HandleNetworkDisconnectedEvent(); + } + + if (bits & MAIN_EVENT_ACTIVATION_DONE) { + HandleActivationDoneEvent(); + } + + if (bits & MAIN_EVENT_STATE_CHANGED) { + HandleStateChangedEvent(); + } + + if (bits & MAIN_EVENT_TOGGLE_CHAT) { + HandleToggleChatEvent(); + } + + if (bits & MAIN_EVENT_START_LISTENING) { + HandleStartListeningEvent(); + } + + if (bits & MAIN_EVENT_STOP_LISTENING) { + HandleStopListeningEvent(); + } + + if (bits & MAIN_EVENT_SEND_AUDIO) { + while (auto packet = audio_service_.PopPacketFromSendQueue()) { + if (protocol_ && !protocol_->SendAudio(std::move(packet))) { + break; + } + } + } + + if (bits & MAIN_EVENT_WAKE_WORD_DETECTED) { + HandleWakeWordDetectedEvent(); + } + + if (bits & MAIN_EVENT_VAD_CHANGE) { + if (GetDeviceState() == kDeviceStateListening) { + auto led = Board::GetInstance().GetLed(); + led->OnStateChanged(); + } + } + + if (bits & MAIN_EVENT_SCHEDULE) { + std::unique_lock lock(mutex_); + auto tasks = std::move(main_tasks_); + lock.unlock(); + for (auto& task : tasks) { + task(); + } + } + + if (bits & MAIN_EVENT_CLOCK_TICK) { + clock_ticks_++; + auto display = Board::GetInstance().GetDisplay(); + display->UpdateStatusBar(); + + // Print debug info every 10 seconds + if (clock_ticks_ % 10 == 0) { + SystemInfo::PrintHeapStats(); + } + } + } +} + +void Application::HandleNetworkConnectedEvent() { + ESP_LOGI(TAG, "Network connected"); + auto state = GetDeviceState(); + + if (state == kDeviceStateStarting || state == kDeviceStateWifiConfiguring) { + // Network is ready, start activation + SetDeviceState(kDeviceStateActivating); + if (activation_task_handle_ != nullptr) { + ESP_LOGW(TAG, "Activation task already running"); + return; + } + + xTaskCreate([](void* arg) { + Application* app = static_cast(arg); + app->ActivationTask(); + app->activation_task_handle_ = nullptr; + vTaskDelete(NULL); + }, "activation", 4096 * 2, this, 2, &activation_task_handle_); + } + + // Update the status bar immediately to show the network state + auto display = Board::GetInstance().GetDisplay(); + display->UpdateStatusBar(true); +} + +void Application::HandleNetworkDisconnectedEvent() { + // Close current conversation when network disconnected + auto state = GetDeviceState(); + if (state == kDeviceStateConnecting || state == kDeviceStateListening || state == kDeviceStateSpeaking) { + ESP_LOGI(TAG, "Closing audio channel due to network disconnection"); + protocol_->CloseAudioChannel(); + } + + // Update the status bar immediately to show the network state + auto display = Board::GetInstance().GetDisplay(); + display->UpdateStatusBar(true); +} + +void Application::HandleActivationDoneEvent() { + ESP_LOGI(TAG, "Activation done"); + + SystemInfo::PrintHeapStats(); + SetDeviceState(kDeviceStateIdle); + + has_server_time_ = ota_->HasServerTime(); + + auto display = Board::GetInstance().GetDisplay(); + std::string message = std::string(Lang::Strings::VERSION) + ota_->GetCurrentVersion(); + display->ShowNotification(message.c_str()); + display->SetChatMessage("system", ""); + + // Play the success sound to indicate the device is ready + audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS); + + // Release OTA object after activation is complete + ota_.reset(); + auto& board = Board::GetInstance(); + board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER); +} + +void Application::ActivationTask() { + // Create OTA object for activation process + ota_ = std::make_unique(); + + // Check for new assets version + CheckAssetsVersion(); + + // Check for new firmware version + CheckNewVersion(); + + // Initialize the protocol + InitializeProtocol(); + + // Signal completion to main loop + xEventGroupSetBits(event_group_, MAIN_EVENT_ACTIVATION_DONE); +} + void Application::CheckAssetsVersion() { + // Only allow CheckAssetsVersion to be called once + if (assets_version_checked_) { + return; + } + assets_version_checked_ = true; + auto& board = Board::GetInstance(); auto display = board.GetDisplay(); auto& assets = Assets::GetInstance(); @@ -93,7 +363,7 @@ void Application::CheckAssetsVersion() { // Wait for the audio service to be idle for 3 seconds vTaskDelay(pdMS_TO_TICKS(3000)); SetDeviceState(kDeviceStateUpgrading); - board.SetPowerSaveMode(false); + board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE); display->SetChatMessage("system", Lang::Strings::PLEASE_WAIT); bool success = assets.Download(download_url, [display](int progress, size_t speed) -> void { @@ -104,12 +374,13 @@ void Application::CheckAssetsVersion() { }).detach(); }); - board.SetPowerSaveMode(true); + board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER); vTaskDelay(pdMS_TO_TICKS(1000)); if (!success) { Alert(Lang::Strings::ERROR, Lang::Strings::DOWNLOAD_ASSETS_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); vTaskDelay(pdMS_TO_TICKS(2000)); + SetDeviceState(kDeviceStateActivating); return; } } @@ -120,18 +391,17 @@ void Application::CheckAssetsVersion() { display->SetEmotion("microchip_ai"); } -void Application::CheckNewVersion(Ota& ota) { +void Application::CheckNewVersion() { const int MAX_RETRY = 10; int retry_count = 0; - int retry_delay = 10; // 初始重试延迟为10秒 + int retry_delay = 10; // Initial retry delay in seconds auto& board = Board::GetInstance(); while (true) { - SetDeviceState(kDeviceStateActivating); auto display = board.GetDisplay(); display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION); - esp_err_t err = ota.CheckVersion(); + esp_err_t err = ota_->CheckVersion(); if (err != ESP_OK) { retry_count++; if (retry_count >= MAX_RETRY) { @@ -140,7 +410,7 @@ void Application::CheckNewVersion(Ota& ota) { } char error_message[128]; - snprintf(error_message, sizeof(error_message), "code=%d, url=%s", err, ota.GetCheckVersionUrl().c_str()); + snprintf(error_message, sizeof(error_message), "code=%d, url=%s", err, ota_->GetCheckVersionUrl().c_str()); char buffer[256]; snprintf(buffer, sizeof(buffer), Lang::Strings::CHECK_NEW_VERSION_FAILED, retry_delay, error_message); Alert(Lang::Strings::ERROR, buffer, "cloud_slash", Lang::Sounds::OGG_EXCLAMATION); @@ -148,266 +418,64 @@ void Application::CheckNewVersion(Ota& ota) { ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY); for (int i = 0; i < retry_delay; i++) { vTaskDelay(pdMS_TO_TICKS(1000)); - if (device_state_ == kDeviceStateIdle) { + if (GetDeviceState() == kDeviceStateIdle) { break; } } - retry_delay *= 2; // 每次重试后延迟时间翻倍 + retry_delay *= 2; // Double the retry delay continue; } retry_count = 0; - retry_delay = 10; // 重置重试延迟时间 + retry_delay = 10; // Reset retry delay - if (ota.HasNewVersion()) { - if (UpgradeFirmware(ota)) { + if (ota_->HasNewVersion()) { + if (UpgradeFirmware(ota_->GetFirmwareUrl(), ota_->GetFirmwareVersion())) { return; // This line will never be reached after reboot } - // If upgrade failed, continue to normal operation (don't break, just fall through) + // If upgrade failed, continue to normal operation } // No new version, mark the current version as valid - ota.MarkCurrentVersionValid(); - if (!ota.HasActivationCode() && !ota.HasActivationChallenge()) { - xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); + ota_->MarkCurrentVersionValid(); + if (!ota_->HasActivationCode() && !ota_->HasActivationChallenge()) { // Exit the loop if done checking new version break; } display->SetStatus(Lang::Strings::ACTIVATION); // Activation code is shown to the user and waiting for the user to input - if (ota.HasActivationCode()) { - ShowActivationCode(ota.GetActivationCode(), ota.GetActivationMessage()); + if (ota_->HasActivationCode()) { + ShowActivationCode(ota_->GetActivationCode(), ota_->GetActivationMessage()); } // This will block the loop until the activation is done or timeout for (int i = 0; i < 10; ++i) { ESP_LOGI(TAG, "Activating... %d/%d", i + 1, 10); - esp_err_t err = ota.Activate(); + esp_err_t err = ota_->Activate(); if (err == ESP_OK) { - xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); break; } else if (err == ESP_ERR_TIMEOUT) { vTaskDelay(pdMS_TO_TICKS(3000)); } else { vTaskDelay(pdMS_TO_TICKS(10000)); } - if (device_state_ == kDeviceStateIdle) { + if (GetDeviceState() == kDeviceStateIdle) { break; } } } } -void Application::ShowActivationCode(const std::string& code, const std::string& message) { - struct digit_sound { - char digit; - const std::string_view& sound; - }; - static const std::array digit_sounds{{ - digit_sound{'0', Lang::Sounds::OGG_0}, - digit_sound{'1', Lang::Sounds::OGG_1}, - digit_sound{'2', Lang::Sounds::OGG_2}, - digit_sound{'3', Lang::Sounds::OGG_3}, - digit_sound{'4', Lang::Sounds::OGG_4}, - digit_sound{'5', Lang::Sounds::OGG_5}, - digit_sound{'6', Lang::Sounds::OGG_6}, - digit_sound{'7', Lang::Sounds::OGG_7}, - digit_sound{'8', Lang::Sounds::OGG_8}, - digit_sound{'9', Lang::Sounds::OGG_9} - }}; - - // This sentence uses 9KB of SRAM, so we need to wait for it to finish - Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION); - - for (const auto& digit : code) { - auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(), - [digit](const digit_sound& ds) { return ds.digit == digit; }); - if (it != digit_sounds.end()) { - audio_service_.PlaySound(it->sound); - } - } -} - -void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) { - ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message); - auto display = Board::GetInstance().GetDisplay(); - display->SetStatus(status); - display->SetEmotion(emotion); - display->SetChatMessage("system", message); - if (!sound.empty()) { - audio_service_.PlaySound(sound); - } -} - -void Application::DismissAlert() { - if (device_state_ == kDeviceStateIdle) { - auto display = Board::GetInstance().GetDisplay(); - display->SetStatus(Lang::Strings::STANDBY); - display->SetEmotion("neutral"); - display->SetChatMessage("system", ""); - } -} - -void Application::ToggleChatState() { - if (device_state_ == kDeviceStateActivating) { - SetDeviceState(kDeviceStateIdle); - return; - } else if (device_state_ == kDeviceStateWifiConfiguring) { - audio_service_.EnableAudioTesting(true); - SetDeviceState(kDeviceStateAudioTesting); - return; - } else if (device_state_ == kDeviceStateAudioTesting) { - audio_service_.EnableAudioTesting(false); - SetDeviceState(kDeviceStateWifiConfiguring); - return; - } - - if (!protocol_) { - ESP_LOGE(TAG, "Protocol not initialized"); - return; - } - - if (device_state_ == kDeviceStateIdle) { - Schedule([this]() { - if (!protocol_->IsAudioChannelOpened()) { - SetDeviceState(kDeviceStateConnecting); - if (!protocol_->OpenAudioChannel()) { - return; - } - } - - SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); - }); - } else if (device_state_ == kDeviceStateSpeaking) { - Schedule([this]() { - AbortSpeaking(kAbortReasonNone); - }); - } else if (device_state_ == kDeviceStateListening) { - Schedule([this]() { - protocol_->CloseAudioChannel(); - }); - } -} - -void Application::StartListening() { - if (device_state_ == kDeviceStateActivating) { - SetDeviceState(kDeviceStateIdle); - return; - } else if (device_state_ == kDeviceStateWifiConfiguring) { - audio_service_.EnableAudioTesting(true); - SetDeviceState(kDeviceStateAudioTesting); - return; - } - - if (!protocol_) { - ESP_LOGE(TAG, "Protocol not initialized"); - return; - } - - if (device_state_ == kDeviceStateIdle) { - Schedule([this]() { - if (!protocol_->IsAudioChannelOpened()) { - SetDeviceState(kDeviceStateConnecting); - if (!protocol_->OpenAudioChannel()) { - return; - } - } - - SetListeningMode(kListeningModeManualStop); - }); - } else if (device_state_ == kDeviceStateSpeaking) { - Schedule([this]() { - AbortSpeaking(kAbortReasonNone); - SetListeningMode(kListeningModeManualStop); - }); - } -} - -void Application::StopListening() { - if (device_state_ == kDeviceStateAudioTesting) { - audio_service_.EnableAudioTesting(false); - SetDeviceState(kDeviceStateWifiConfiguring); - return; - } - - const std::array valid_states = { - kDeviceStateListening, - kDeviceStateSpeaking, - kDeviceStateIdle, - }; - // If not valid, do nothing - if (std::find(valid_states.begin(), valid_states.end(), device_state_) == valid_states.end()) { - return; - } - - Schedule([this]() { - if (device_state_ == kDeviceStateListening) { - protocol_->SendStopListening(); - SetDeviceState(kDeviceStateIdle); - } - }); -} - -void Application::Start() { +void Application::InitializeProtocol() { auto& board = Board::GetInstance(); - SetDeviceState(kDeviceStateStarting); - - /* Setup the display */ auto display = board.GetDisplay(); - - // Print board name/version info - display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str()); - - /* Setup the audio service */ auto codec = board.GetAudioCodec(); - audio_service_.Initialize(codec); - audio_service_.Start(); - AudioServiceCallbacks callbacks; - callbacks.on_send_queue_available = [this]() { - xEventGroupSetBits(event_group_, MAIN_EVENT_SEND_AUDIO); - }; - callbacks.on_wake_word_detected = [this](const std::string& wake_word) { - xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED); - }; - callbacks.on_vad_change = [this](bool speaking) { - xEventGroupSetBits(event_group_, MAIN_EVENT_VAD_CHANGE); - }; - audio_service_.SetCallbacks(callbacks); - - // Start the main event loop task with priority 3 - xTaskCreate([](void* arg) { - ((Application*)arg)->MainEventLoop(); - vTaskDelete(NULL); - }, "main_event_loop", 2048 * 4, this, 3, &main_event_loop_task_handle_); - - /* Start the clock timer to update the status bar */ - esp_timer_start_periodic(clock_timer_handle_, 1000000); - - /* Wait for the network to be ready */ - board.StartNetwork(); - - // Update the status bar immediately to show the network state - display->UpdateStatusBar(true); - - // Check for new assets version - CheckAssetsVersion(); - - // Check for new firmware version or get the MQTT broker address - Ota ota; - CheckNewVersion(ota); - - // Initialize the protocol display->SetStatus(Lang::Strings::LOADING_PROTOCOL); - // Add MCP common tools before initializing the protocol - auto& mcp_server = McpServer::GetInstance(); - mcp_server.AddCommonTools(); - mcp_server.AddUserOnlyTools(); - - if (ota.HasMqttConfig()) { + if (ota_->HasMqttConfig()) { protocol_ = std::make_unique(); - } else if (ota.HasWebsocketConfig()) { + } else if (ota_->HasWebsocketConfig()) { protocol_ = std::make_unique(); } else { ESP_LOGW(TAG, "No protocol specified in the OTA config, using MQTT"); @@ -422,26 +490,30 @@ void Application::Start() { last_error_message_ = message; xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR); }); + protocol_->OnIncomingAudio([this](std::unique_ptr packet) { - if (device_state_ == kDeviceStateSpeaking) { + if (GetDeviceState() == kDeviceStateSpeaking) { audio_service_.PushPacketToDecodeQueue(std::move(packet)); } }); + protocol_->OnAudioChannelOpened([this, codec, &board]() { - board.SetPowerSaveMode(false); + board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE); if (protocol_->server_sample_rate() != codec->output_sample_rate()) { ESP_LOGW(TAG, "Server sample rate %d does not match device output sample rate %d, resampling may cause distortion", protocol_->server_sample_rate(), codec->output_sample_rate()); } }); + protocol_->OnAudioChannelClosed([this, &board]() { - board.SetPowerSaveMode(true); + board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER); Schedule([this]() { auto display = Board::GetInstance().GetDisplay(); display->SetChatMessage("system", ""); SetDeviceState(kDeviceStateIdle); }); }); + protocol_->OnIncomingJson([this, display](const cJSON* root) { // Parse JSON data auto type = cJSON_GetObjectItem(root, "type"); @@ -450,13 +522,11 @@ void Application::Start() { if (strcmp(state->valuestring, "start") == 0) { Schedule([this]() { aborted_ = false; - if (device_state_ == kDeviceStateIdle || device_state_ == kDeviceStateListening) { - SetDeviceState(kDeviceStateSpeaking); - } + SetDeviceState(kDeviceStateSpeaking); }); } else if (strcmp(state->valuestring, "stop") == 0) { Schedule([this]() { - if (device_state_ == kDeviceStateSpeaking) { + if (GetDeviceState() == kDeviceStateSpeaking) { if (listening_mode_ == kListeningModeManualStop) { SetDeviceState(kDeviceStateIdle); } else { @@ -531,96 +601,164 @@ void Application::Start() { ESP_LOGW(TAG, "Unknown message type: %s", type->valuestring); } }); - bool protocol_started = protocol_->Start(); + + protocol_->Start(); +} - SystemInfo::PrintHeapStats(); - SetDeviceState(kDeviceStateIdle); +void Application::ShowActivationCode(const std::string& code, const std::string& message) { + struct digit_sound { + char digit; + const std::string_view& sound; + }; + static const std::array digit_sounds{{ + digit_sound{'0', Lang::Sounds::OGG_0}, + digit_sound{'1', Lang::Sounds::OGG_1}, + digit_sound{'2', Lang::Sounds::OGG_2}, + digit_sound{'3', Lang::Sounds::OGG_3}, + digit_sound{'4', Lang::Sounds::OGG_4}, + digit_sound{'5', Lang::Sounds::OGG_5}, + digit_sound{'6', Lang::Sounds::OGG_6}, + digit_sound{'7', Lang::Sounds::OGG_7}, + digit_sound{'8', Lang::Sounds::OGG_8}, + digit_sound{'9', Lang::Sounds::OGG_9} + }}; - has_server_time_ = ota.HasServerTime(); - if (protocol_started) { - std::string message = std::string(Lang::Strings::VERSION) + ota.GetCurrentVersion(); - display->ShowNotification(message.c_str()); + // This sentence uses 9KB of SRAM, so we need to wait for it to finish + Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION); + + for (const auto& digit : code) { + auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(), + [digit](const digit_sound& ds) { return ds.digit == digit; }); + if (it != digit_sounds.end()) { + audio_service_.PlaySound(it->sound); + } + } +} + +void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) { + ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message); + auto display = Board::GetInstance().GetDisplay(); + display->SetStatus(status); + display->SetEmotion(emotion); + display->SetChatMessage("system", message); + if (!sound.empty()) { + audio_service_.PlaySound(sound); + } +} + +void Application::DismissAlert() { + if (GetDeviceState() == kDeviceStateIdle) { + auto display = Board::GetInstance().GetDisplay(); + display->SetStatus(Lang::Strings::STANDBY); + display->SetEmotion("neutral"); display->SetChatMessage("system", ""); - // Play the success sound to indicate the device is ready - audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS); } } -// Add a async task to MainLoop -void Application::Schedule(std::function callback) { - { - std::lock_guard lock(mutex_); - main_tasks_.push_back(std::move(callback)); - } - xEventGroupSetBits(event_group_, MAIN_EVENT_SCHEDULE); +void Application::ToggleChatState() { + xEventGroupSetBits(event_group_, MAIN_EVENT_TOGGLE_CHAT); } -// The Main Event Loop controls the chat state and websocket connection -// If other tasks need to access the websocket or chat state, -// they should use Schedule to call this function -void Application::MainEventLoop() { - while (true) { - auto bits = xEventGroupWaitBits(event_group_, MAIN_EVENT_SCHEDULE | - MAIN_EVENT_SEND_AUDIO | - MAIN_EVENT_WAKE_WORD_DETECTED | - MAIN_EVENT_VAD_CHANGE | - MAIN_EVENT_CLOCK_TICK | - MAIN_EVENT_ERROR, pdTRUE, pdFALSE, portMAX_DELAY); +void Application::StartListening() { + xEventGroupSetBits(event_group_, MAIN_EVENT_START_LISTENING); +} - if (bits & MAIN_EVENT_ERROR) { - SetDeviceState(kDeviceStateIdle); - Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); - } +void Application::StopListening() { + xEventGroupSetBits(event_group_, MAIN_EVENT_STOP_LISTENING); +} - if (bits & MAIN_EVENT_SEND_AUDIO) { - while (auto packet = audio_service_.PopPacketFromSendQueue()) { - if (protocol_ && !protocol_->SendAudio(std::move(packet))) { - break; - } +void Application::HandleToggleChatEvent() { + auto state = GetDeviceState(); + + if (state == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); + return; + } else if (state == kDeviceStateWifiConfiguring) { + audio_service_.EnableAudioTesting(true); + SetDeviceState(kDeviceStateAudioTesting); + return; + } else if (state == kDeviceStateAudioTesting) { + audio_service_.EnableAudioTesting(false); + SetDeviceState(kDeviceStateWifiConfiguring); + return; + } + + if (!protocol_) { + ESP_LOGE(TAG, "Protocol not initialized"); + return; + } + + if (state == kDeviceStateIdle) { + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + return; } } - if (bits & MAIN_EVENT_WAKE_WORD_DETECTED) { - OnWakeWordDetected(); - } - - if (bits & MAIN_EVENT_VAD_CHANGE) { - if (device_state_ == kDeviceStateListening) { - auto led = Board::GetInstance().GetLed(); - led->OnStateChanged(); - } - } - - if (bits & MAIN_EVENT_SCHEDULE) { - std::unique_lock lock(mutex_); - auto tasks = std::move(main_tasks_); - lock.unlock(); - for (auto& task : tasks) { - task(); - } - } - - if (bits & MAIN_EVENT_CLOCK_TICK) { - clock_ticks_++; - auto display = Board::GetInstance().GetDisplay(); - display->UpdateStatusBar(); - - // Print the debug info every 10 seconds - if (clock_ticks_ % 10 == 0) { - // SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000)); - // SystemInfo::PrintTaskList(); - SystemInfo::PrintHeapStats(); - } - } + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); + } else if (state == kDeviceStateSpeaking) { + AbortSpeaking(kAbortReasonNone); + } else if (state == kDeviceStateListening) { + protocol_->CloseAudioChannel(); } } -void Application::OnWakeWordDetected() { +void Application::HandleStartListeningEvent() { + auto state = GetDeviceState(); + + if (state == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); + return; + } else if (state == kDeviceStateWifiConfiguring) { + audio_service_.EnableAudioTesting(true); + SetDeviceState(kDeviceStateAudioTesting); + return; + } + + if (!protocol_) { + ESP_LOGE(TAG, "Protocol not initialized"); + return; + } + + if (state == kDeviceStateIdle) { + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + return; + } + } + + SetListeningMode(kListeningModeManualStop); + } else if (state == kDeviceStateSpeaking) { + AbortSpeaking(kAbortReasonNone); + SetListeningMode(kListeningModeManualStop); + } +} + +void Application::HandleStopListeningEvent() { + auto state = GetDeviceState(); + + if (state == kDeviceStateAudioTesting) { + audio_service_.EnableAudioTesting(false); + SetDeviceState(kDeviceStateWifiConfiguring); + return; + } else if (state == kDeviceStateListening) { + if (protocol_) { + protocol_->SendStopListening(); + } + SetDeviceState(kDeviceStateIdle); + } +} + +void Application::HandleWakeWordDetectedEvent() { if (!protocol_) { return; } - if (device_state_ == kDeviceStateIdle) { + auto state = GetDeviceState(); + + if (state == kDeviceStateIdle) { audio_service_.EncodeWakeWord(); if (!protocol_->IsAudioChannelOpened()) { @@ -646,44 +784,24 @@ void Application::OnWakeWordDetected() { // Play the pop up sound to indicate the wake word is detected audio_service_.PlaySound(Lang::Sounds::OGG_POPUP); #endif - } else if (device_state_ == kDeviceStateSpeaking) { + } else if (state == kDeviceStateSpeaking) { AbortSpeaking(kAbortReasonWakeWordDetected); - } else if (device_state_ == kDeviceStateActivating) { + } else if (state == kDeviceStateActivating) { + // Restart the activation check if the wake word is detected during activation SetDeviceState(kDeviceStateIdle); } } -void Application::AbortSpeaking(AbortReason reason) { - ESP_LOGI(TAG, "Abort speaking"); - aborted_ = true; - if (protocol_) { - protocol_->SendAbortSpeaking(reason); - } -} - -void Application::SetListeningMode(ListeningMode mode) { - listening_mode_ = mode; - SetDeviceState(kDeviceStateListening); -} - -void Application::SetDeviceState(DeviceState state) { - if (device_state_ == state) { - return; - } - +void Application::HandleStateChangedEvent() { + DeviceState new_state = state_machine_.GetState(); clock_ticks_ = 0; - auto previous_state = device_state_; - device_state_ = state; - ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); - - // Send the state change event - DeviceStateEventManager::GetInstance().PostStateChangeEvent(previous_state, state); auto& board = Board::GetInstance(); auto display = board.GetDisplay(); auto led = board.GetLed(); led->OnStateChanged(); - switch (state) { + + switch (new_state) { case kDeviceStateUnknown: case kDeviceStateIdle: display->SetStatus(Lang::Strings::STANDBY); @@ -718,12 +836,37 @@ void Application::SetDeviceState(DeviceState state) { } audio_service_.ResetDecoder(); break; + case kDeviceStateWifiConfiguring: + audio_service_.EnableVoiceProcessing(false); + audio_service_.EnableWakeWordDetection(false); + break; default: // Do nothing break; } } +void Application::Schedule(std::function callback) { + { + std::lock_guard lock(mutex_); + main_tasks_.push_back(std::move(callback)); + } + xEventGroupSetBits(event_group_, MAIN_EVENT_SCHEDULE); +} + +void Application::AbortSpeaking(AbortReason reason) { + ESP_LOGI(TAG, "Abort speaking"); + aborted_ = true; + if (protocol_) { + protocol_->SendAbortSpeaking(reason); + } +} + +void Application::SetListeningMode(ListeningMode mode) { + listening_mode_ = mode; + SetDeviceState(kDeviceStateListening); +} + void Application::Reboot() { ESP_LOGI(TAG, "Rebooting..."); // Disconnect the audio channel @@ -737,34 +880,33 @@ void Application::Reboot() { esp_restart(); } -bool Application::UpgradeFirmware(Ota& ota, const std::string& url) { +bool Application::UpgradeFirmware(const std::string& url, const std::string& version) { auto& board = Board::GetInstance(); auto display = board.GetDisplay(); - - // Use provided URL or get from OTA object - std::string upgrade_url = url.empty() ? ota.GetFirmwareUrl() : url; - std::string version_info = url.empty() ? ota.GetFirmwareVersion() : "(Manual upgrade)"; - + + std::string upgrade_url = url; + std::string version_info = version.empty() ? "(Manual upgrade)" : version; + // Close audio channel if it's open if (protocol_ && protocol_->IsAudioChannelOpened()) { ESP_LOGI(TAG, "Closing audio channel before firmware upgrade"); protocol_->CloseAudioChannel(); } ESP_LOGI(TAG, "Starting firmware upgrade from URL: %s", upgrade_url.c_str()); - + Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE); vTaskDelay(pdMS_TO_TICKS(3000)); SetDeviceState(kDeviceStateUpgrading); - + std::string message = std::string(Lang::Strings::NEW_VERSION) + version_info; display->SetChatMessage("system", message.c_str()); - board.SetPowerSaveMode(false); + board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE); audio_service_.Stop(); vTaskDelay(pdMS_TO_TICKS(1000)); - bool upgrade_success = ota.StartUpgradeFromUrl(upgrade_url, [display](int progress, size_t speed) { + bool upgrade_success = Ota::Upgrade(upgrade_url, [display](int progress, size_t speed) { std::thread([display, progress, speed]() { char buffer[32]; snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); @@ -776,7 +918,7 @@ bool Application::UpgradeFirmware(Ota& ota, const std::string& url) { // Upgrade failed, restart audio service and continue running ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation..."); audio_service_.Start(); // Restart audio service - board.SetPowerSaveMode(true); // Restore power save mode + board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER); // Restore power save level Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION); vTaskDelay(pdMS_TO_TICKS(3000)); return false; @@ -795,7 +937,9 @@ void Application::WakeWordInvoke(const std::string& wake_word) { return; } - if (device_state_ == kDeviceStateIdle) { + auto state = GetDeviceState(); + + if (state == kDeviceStateIdle) { audio_service_.EncodeWakeWord(); if (!protocol_->IsAudioChannelOpened()) { @@ -820,11 +964,11 @@ void Application::WakeWordInvoke(const std::string& wake_word) { // Play the pop up sound to indicate the wake word is detected audio_service_.PlaySound(Lang::Sounds::OGG_POPUP); #endif - } else if (device_state_ == kDeviceStateSpeaking) { + } else if (state == kDeviceStateSpeaking) { Schedule([this]() { AbortSpeaking(kAbortReasonNone); }); - } else if (device_state_ == kDeviceStateListening) { + } else if (state == kDeviceStateListening) { Schedule([this]() { if (protocol_) { protocol_->CloseAudioChannel(); @@ -834,7 +978,7 @@ void Application::WakeWordInvoke(const std::string& wake_word) { } bool Application::CanEnterSleepMode() { - if (device_state_ != kDeviceStateIdle) { + if (GetDeviceState() != kDeviceStateIdle) { return false; } @@ -851,18 +995,12 @@ bool Application::CanEnterSleepMode() { } void Application::SendMcpMessage(const std::string& payload) { - if (protocol_ == nullptr) { - return; - } - - // Make sure you are using main thread to send MCP message - if (xTaskGetCurrentTaskHandle() == main_event_loop_task_handle_) { - protocol_->SendMcpMessage(payload); - } else { - Schedule([this, payload = std::move(payload)]() { + // Always schedule to run in main task for thread safety + Schedule([this, payload = std::move(payload)]() { + if (protocol_) { protocol_->SendMcpMessage(payload); - }); - } + } + }); } void Application::SetAecMode(AecMode mode) { @@ -894,4 +1032,16 @@ void Application::SetAecMode(AecMode mode) { void Application::PlaySound(const std::string_view& sound) { audio_service_.PlaySound(sound); -} \ No newline at end of file +} + +void Application::ResetProtocol() { + Schedule([this]() { + // Close audio channel if opened + if (protocol_ && protocol_->IsAudioChannelOpened()) { + protocol_->CloseAudioChannel(); + } + // Reset protocol + protocol_.reset(); + }); +} + diff --git a/main/application.h b/main/application.h index bff4e9e9..212eaf68 100644 --- a/main/application.h +++ b/main/application.h @@ -14,16 +14,23 @@ #include "protocol.h" #include "ota.h" #include "audio_service.h" -#include "device_state_event.h" +#include "device_state.h" +#include "device_state_machine.h" - -#define MAIN_EVENT_SCHEDULE (1 << 0) -#define MAIN_EVENT_SEND_AUDIO (1 << 1) -#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2) -#define MAIN_EVENT_VAD_CHANGE (1 << 3) -#define MAIN_EVENT_ERROR (1 << 4) -#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5) -#define MAIN_EVENT_CLOCK_TICK (1 << 6) +// Main event bits +#define MAIN_EVENT_SCHEDULE (1 << 0) +#define MAIN_EVENT_SEND_AUDIO (1 << 1) +#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2) +#define MAIN_EVENT_VAD_CHANGE (1 << 3) +#define MAIN_EVENT_ERROR (1 << 4) +#define MAIN_EVENT_ACTIVATION_DONE (1 << 5) +#define MAIN_EVENT_CLOCK_TICK (1 << 6) +#define MAIN_EVENT_NETWORK_CONNECTED (1 << 7) +#define MAIN_EVENT_NETWORK_DISCONNECTED (1 << 8) +#define MAIN_EVENT_TOGGLE_CHAT (1 << 9) +#define MAIN_EVENT_START_LISTENING (1 << 10) +#define MAIN_EVENT_STOP_LISTENING (1 << 11) +#define MAIN_EVENT_STATE_CHANGED (1 << 12) enum AecMode { @@ -38,31 +45,80 @@ public: static Application instance; return instance; } - // 删除拷贝构造函数和赋值运算符 + // Delete copy constructor and assignment operator Application(const Application&) = delete; Application& operator=(const Application&) = delete; - void Start(); - void MainEventLoop(); - DeviceState GetDeviceState() const { return device_state_; } + /** + * Initialize the application + * This sets up display, audio, network callbacks, etc. + * Network connection starts asynchronously. + */ + void Initialize(); + + /** + * Run the main event loop + * This function runs in the main task and never returns. + * It handles all events including network, state changes, and user interactions. + */ + void Run(); + + DeviceState GetDeviceState() const { return state_machine_.GetState(); } bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); } + + /** + * Request state transition + * Returns true if transition was successful + */ + bool SetDeviceState(DeviceState state); + + /** + * Schedule a callback to be executed in the main task + */ void Schedule(std::function callback); - void SetDeviceState(DeviceState state); + + /** + * Alert with status, message, emotion and optional sound + */ void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = ""); void DismissAlert(); + void AbortSpeaking(AbortReason reason); + + /** + * Toggle chat state (event-based, thread-safe) + * Sends MAIN_EVENT_TOGGLE_CHAT to be handled in Run() + */ void ToggleChatState(); + + /** + * Start listening (event-based, thread-safe) + * Sends MAIN_EVENT_START_LISTENING to be handled in Run() + */ void StartListening(); + + /** + * Stop listening (event-based, thread-safe) + * Sends MAIN_EVENT_STOP_LISTENING to be handled in Run() + */ void StopListening(); + void Reboot(); void WakeWordInvoke(const std::string& wake_word); - bool UpgradeFirmware(Ota& ota, const std::string& url = ""); + bool UpgradeFirmware(const std::string& url, const std::string& version = ""); bool CanEnterSleepMode(); void SendMcpMessage(const std::string& payload); void SetAecMode(AecMode mode); AecMode GetAecMode() const { return aec_mode_; } void PlaySound(const std::string_view& sound); AudioService& GetAudioService() { return audio_service_; } + + /** + * Reset protocol resources (thread-safe) + * Can be called from any task to release resources allocated after network connected + * This includes closing audio channel, resetting protocol and ota objects + */ + void ResetProtocol(); private: Application(); @@ -73,23 +129,42 @@ private: std::unique_ptr protocol_; EventGroupHandle_t event_group_ = nullptr; esp_timer_handle_t clock_timer_handle_ = nullptr; - volatile DeviceState device_state_ = kDeviceStateUnknown; + DeviceStateMachine state_machine_; ListeningMode listening_mode_ = kListeningModeAutoStop; AecMode aec_mode_ = kAecOff; std::string last_error_message_; AudioService audio_service_; + std::unique_ptr ota_; bool has_server_time_ = false; bool aborted_ = false; + bool assets_version_checked_ = false; int clock_ticks_ = 0; - TaskHandle_t check_new_version_task_handle_ = nullptr; - TaskHandle_t main_event_loop_task_handle_ = nullptr; + TaskHandle_t activation_task_handle_ = nullptr; - void OnWakeWordDetected(); - void CheckNewVersion(Ota& ota); + + // Event handlers + void HandleStateChangedEvent(); + void HandleToggleChatEvent(); + void HandleStartListeningEvent(); + void HandleStopListeningEvent(); + void HandleNetworkConnectedEvent(); + void HandleNetworkDisconnectedEvent(); + void HandleActivationDoneEvent(); + void HandleWakeWordDetectedEvent(); + + // Activation task (runs in background) + void ActivationTask(); + + // Helper methods void CheckAssetsVersion(); + void CheckNewVersion(); + void InitializeProtocol(); void ShowActivationCode(const std::string& code, const std::string& message); void SetListeningMode(ListeningMode mode); + + // State change handler called by state machine + void OnStateChanged(DeviceState old_state, DeviceState new_state); }; diff --git a/main/boards/aipi-lite/aipi-lite.cc b/main/boards/aipi-lite/aipi-lite.cc index b808812a..a257dfe3 100644 --- a/main/boards/aipi-lite/aipi-lite.cc +++ b/main/boards/aipi-lite/aipi-lite.cc @@ -7,7 +7,6 @@ #include #include #include -#include #include "application.h" #include "button.h" @@ -136,9 +135,9 @@ class AIPILite : public WifiBoard { boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -154,7 +153,7 @@ class AIPILite : public WifiBoard { app.SetDeviceState(kDeviceStateWifiConfiguring); // 重置WiFi配置以确保进入配网模式 - ResetWifiConfiguration(); + EnterWifiConfigMode(); }); power_button_.OnClick([this]() { power_save_timer_->WakeUp(); }); @@ -236,11 +235,11 @@ class AIPILite : public WifiBoard { return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc index 13759495..c025389f 100644 --- a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc +++ b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc @@ -8,7 +8,6 @@ #include "led/single_led.h" #include "i2c_device.h" -#include #include #include #include @@ -244,8 +243,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc b/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc index ce0bc43a..21c6e9d5 100644 --- a/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc +++ b/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc @@ -13,7 +13,6 @@ #include "i2c_device.h" #include #include -#include #include #include @@ -227,8 +226,9 @@ private: middle_button_.OnLongPress([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { @@ -377,11 +377,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc b/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc index 4a906db7..7e888a77 100644 --- a/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc +++ b/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc @@ -13,7 +13,6 @@ #include "i2c_device.h" #include #include -#include #include #include @@ -280,9 +279,10 @@ private: auto& app = Application::GetInstance(); if (self->GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { auto& wifi_board = static_cast(self->GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } @@ -463,11 +463,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc b/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc index acc2e216..55e297f1 100644 --- a/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc +++ b/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc @@ -13,7 +13,6 @@ #include "i2c_device.h" #include #include -#include #include #include @@ -262,8 +261,9 @@ private: auto self = static_cast(usr_data); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; } if (self->power_status_ == kDeviceBatterySupply) { @@ -442,11 +442,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc index 3f9ade04..cd9b7598 100644 --- a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc +++ b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc @@ -12,7 +12,6 @@ #include #include #include -#include #define TAG "atk_dnesp32s3" @@ -87,8 +86,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc b/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc index 1ae96a1e..76f5b399 100644 --- a/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc +++ b/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc @@ -9,7 +9,6 @@ #include "driver/gpio.h" #include "assets/lang_config.h" -#include #include #include #include @@ -60,8 +59,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/atom-echos3r/atom_echos3r.cc b/main/boards/atom-echos3r/atom_echos3r.cc index bb9c8e4f..ec5b61bb 100644 --- a/main/boards/atom-echos3r/atom_echos3r.cc +++ b/main/boards/atom-echos3r/atom_echos3r.cc @@ -8,7 +8,6 @@ #include #include -#include #define TAG "AtomEchoS3R" @@ -57,8 +56,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc b/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc index 5bfd3ccf..97ae7e3f 100644 --- a/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc +++ b/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc @@ -7,7 +7,6 @@ #include #include -#include #include "led/circular_strip.h" #define TAG "XX+EchoBase" @@ -91,8 +90,9 @@ private: ESP_LOGI(TAG, " ===>>> face_button_.OnClick "); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/atoms3-echo-base/atoms3_echo_base.cc b/main/boards/atoms3-echo-base/atoms3_echo_base.cc index 64e592e6..22060763 100644 --- a/main/boards/atoms3-echo-base/atoms3_echo_base.cc +++ b/main/boards/atoms3-echo-base/atoms3_echo_base.cc @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -180,8 +179,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc b/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc index b74f0f0c..b9f6233f 100644 --- a/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc +++ b/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc @@ -8,7 +8,6 @@ #include #include -#include #include "esp32_camera.h" #define TAG "AtomS3R CAM/M12 + EchoBase" diff --git a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc index 3407828c..b2ca5652 100644 --- a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc +++ b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -258,8 +257,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc b/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc index 5574786c..8cf457e0 100644 --- a/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc +++ b/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc @@ -7,7 +7,6 @@ #include "config.h" #include "led/single_led.h" -#include #include #include #include @@ -138,10 +137,11 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } gpio_set_level(BUILTIN_LED_GPIO, 1); diff --git a/main/boards/bread-compact-esp32/esp32_bread_board.cc b/main/boards/bread-compact-esp32/esp32_bread_board.cc index 31c08c86..31b6e7d1 100644 --- a/main/boards/bread-compact-esp32/esp32_bread_board.cc +++ b/main/boards/bread-compact-esp32/esp32_bread_board.cc @@ -9,7 +9,6 @@ #include "led/single_led.h" #include "display/oled_display.h" -#include #include #include #include @@ -106,10 +105,11 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } gpio_set_level(BUILTIN_LED_GPIO, 1); diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index e004b550..15b25f4e 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -14,7 +14,6 @@ #include #include #include -#include #define TAG "CompactMl307Board" @@ -96,10 +95,11 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } app.ToggleChatState(); diff --git a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc index 5e0263e4..431cc43c 100644 --- a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc +++ b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc @@ -9,7 +9,6 @@ #include "lamp_controller.h" #include "led/single_led.h" -#include #include #include #include @@ -126,8 +125,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc b/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc index 70e95635..98b76f47 100644 --- a/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc +++ b/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc @@ -10,7 +10,6 @@ #include "led/single_led.h" #include "esp32_camera.h" -#include #include #include #include @@ -173,8 +172,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index fca4d1ca..04d71910 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -10,7 +10,6 @@ #include "led/single_led.h" #include "assets/lang_config.h" -#include #include #include #include @@ -104,8 +103,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/common/afsk_demod.cc b/main/boards/common/afsk_demod.cc index 9e040de0..0fe41ea7 100644 --- a/main/boards/common/afsk_demod.cc +++ b/main/boards/common/afsk_demod.cc @@ -3,6 +3,7 @@ #include #include "esp_log.h" #include "display.h" +#include "ssid_manager.h" #ifndef M_PI #define M_PI 3.14159265358979323846 @@ -13,7 +14,7 @@ namespace audio_wifi_config static const char *kLogTag = "AUDIO_WIFI_CONFIG"; void ReceiveWifiCredentialsFromAudio(Application *app, - WifiConfigurationAp *wifi_ap, + WifiManager *wifi_manager, Display *display, size_t input_channels ) @@ -90,13 +91,16 @@ namespace audio_wifi_config continue; } - if (wifi_ap->ConnectToWifi(wifi_ssid, wifi_password)) { - wifi_ap->Save(wifi_ssid, wifi_password); // Save WiFi credentials - esp_restart(); // Restart device to apply new WiFi configuration - } else { - ESP_LOGE(kLogTag, "Failed to connect to WiFi with received credentials"); - } + // Save WiFi credentials using SsidManager + auto& ssid_manager = SsidManager::GetInstance(); + ssid_manager.AddSsid(wifi_ssid, wifi_password); + ESP_LOGI(kLogTag, "WiFi credentials saved successfully"); + + // Exit config mode (triggers ConfigModeExit event) + wifi_manager->StopConfigAp(); + data_buffer.decoded_text.reset(); // Clear processed data + return; // Exit the function } } vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay diff --git a/main/boards/common/afsk_demod.h b/main/boards/common/afsk_demod.h index 87b3805d..4ac0ab6d 100644 --- a/main/boards/common/afsk_demod.h +++ b/main/boards/common/afsk_demod.h @@ -6,7 +6,7 @@ #include #include #include -#include "wifi_configuration_ap.h" +#include "wifi_manager.h" #include "application.h" // Audio signal processing constants for WiFi configuration via audio @@ -19,7 +19,7 @@ const size_t kWindowSize = 64; namespace audio_wifi_config { // Main function to receive WiFi credentials through audio signal - void ReceiveWifiCredentialsFromAudio(Application *app, WifiConfigurationAp *wifi_ap, Display *display, + void ReceiveWifiCredentialsFromAudio(Application *app, WifiManager *wifi_manager, Display *display, size_t input_channels = 1); /** diff --git a/main/boards/common/board.h b/main/boards/common/board.h index 935fed0a..19d3f2f5 100644 --- a/main/boards/common/board.h +++ b/main/boards/common/board.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include "led/led.h" @@ -13,6 +14,34 @@ #include "camera.h" #include "assets.h" +/** + * Network events for unified callback + */ +enum class NetworkEvent { + Scanning, // Network is scanning (WiFi scanning, etc.) + Connecting, // Network is connecting (data: SSID/network name) + Connected, // Network connected successfully (data: SSID/network name) + Disconnected, // Network disconnected + WifiConfigModeEnter, // Entered WiFi configuration mode + WifiConfigModeExit, // Exited WiFi configuration mode + // Cellular modem specific events + ModemDetecting, // Detecting modem (baud rate, module type) + ModemErrorNoSim, // No SIM card detected + ModemErrorRegDenied, // Network registration denied + ModemErrorInitFailed, // Modem initialization failed + ModemErrorTimeout // Operation timeout +}; + +// Power save level enumeration +enum class PowerSaveLevel { + LOW_POWER, // Maximum power saving (lowest power consumption) + BALANCED, // Medium power saving (balanced) + PERFORMANCE, // No power saving (maximum power consumption / full performance) +}; + +// Network event callback type (event, data) +// data contains additional info like SSID for Connecting/Connected events +using NetworkEventCallback = std::function; void* create_board(); class AudioCodec; @@ -46,10 +75,11 @@ public: virtual Camera* GetCamera(); virtual NetworkInterface* GetNetwork() = 0; virtual void StartNetwork() = 0; + virtual void SetNetworkEventCallback(NetworkEventCallback callback) { (void)callback; } virtual const char* GetNetworkStateIcon() = 0; virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging); virtual std::string GetSystemInfoJson(); - virtual void SetPowerSaveMode(bool enabled) = 0; + virtual void SetPowerSaveLevel(PowerSaveLevel level) = 0; virtual std::string GetBoardJson() = 0; virtual std::string GetDeviceStatusJson() = 0; }; diff --git a/main/boards/common/dual_network_board.cc b/main/boards/common/dual_network_board.cc index e6fe9641..7253e518 100644 --- a/main/boards/common/dual_network_board.cc +++ b/main/boards/common/dual_network_board.cc @@ -72,6 +72,11 @@ void DualNetworkBoard::StartNetwork() { current_board_->StartNetwork(); } +void DualNetworkBoard::SetNetworkEventCallback(NetworkEventCallback callback) { + // Forward the callback to the current board + current_board_->SetNetworkEventCallback(std::move(callback)); +} + NetworkInterface* DualNetworkBoard::GetNetwork() { return current_board_->GetNetwork(); } @@ -80,8 +85,8 @@ const char* DualNetworkBoard::GetNetworkStateIcon() { return current_board_->GetNetworkStateIcon(); } -void DualNetworkBoard::SetPowerSaveMode(bool enabled) { - current_board_->SetPowerSaveMode(enabled); +void DualNetworkBoard::SetPowerSaveLevel(PowerSaveLevel level) { + current_board_->SetPowerSaveLevel(level); } std::string DualNetworkBoard::GetBoardJson() { diff --git a/main/boards/common/dual_network_board.h b/main/boards/common/dual_network_board.h index efd88a99..8bc9ec8e 100644 --- a/main/boards/common/dual_network_board.h +++ b/main/boards/common/dual_network_board.h @@ -49,9 +49,10 @@ public: // 重写Board接口 virtual std::string GetBoardType() override; virtual void StartNetwork() override; + virtual void SetNetworkEventCallback(NetworkEventCallback callback) override; virtual NetworkInterface* GetNetwork() override; virtual const char* GetNetworkStateIcon() override; - virtual void SetPowerSaveMode(bool enabled) override; + virtual void SetPowerSaveLevel(PowerSaveLevel level) override; virtual std::string GetBoardJson() override; virtual std::string GetDeviceStatusJson() override; }; diff --git a/main/boards/common/ml307_board.cc b/main/boards/common/ml307_board.cc index 63cf09c9..2db9695d 100644 --- a/main/boards/common/ml307_board.cc +++ b/main/boards/common/ml307_board.cc @@ -6,11 +6,19 @@ #include #include +#include +#include #include #include +#include static const char *TAG = "Ml307Board"; +// Maximum retry count for modem detection +static constexpr int MODEM_DETECT_MAX_RETRIES = 30; +// Maximum retry count for network registration +static constexpr int NETWORK_REG_MAX_RETRIES = 6; + Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) { } @@ -18,47 +26,106 @@ std::string Ml307Board::GetBoardType() { return "ml307"; } -void Ml307Board::StartNetwork() { - auto& application = Application::GetInstance(); - auto display = Board::GetInstance().GetDisplay(); - display->SetStatus(Lang::Strings::DETECTING_MODULE); +void Ml307Board::SetNetworkEventCallback(NetworkEventCallback callback) { + network_event_callback_ = std::move(callback); +} - while (true) { +void Ml307Board::OnNetworkEvent(NetworkEvent event, const std::string& data) { + switch (event) { + case NetworkEvent::ModemDetecting: + ESP_LOGI(TAG, "Detecting modem..."); + break; + case NetworkEvent::Connecting: + ESP_LOGI(TAG, "Registering network..."); + break; + case NetworkEvent::Connected: + ESP_LOGI(TAG, "Network connected"); + break; + case NetworkEvent::Disconnected: + ESP_LOGW(TAG, "Network disconnected"); + break; + case NetworkEvent::ModemErrorNoSim: + ESP_LOGE(TAG, "No SIM card detected"); + break; + case NetworkEvent::ModemErrorRegDenied: + ESP_LOGE(TAG, "Network registration denied"); + break; + case NetworkEvent::ModemErrorInitFailed: + ESP_LOGE(TAG, "Modem initialization failed"); + break; + case NetworkEvent::ModemErrorTimeout: + ESP_LOGE(TAG, "Operation timeout"); + break; + default: + break; + } + + // Notify external callback if set + if (network_event_callback_) { + network_event_callback_(event, data); + } +} + +void Ml307Board::NetworkTask() { + auto& application = Application::GetInstance(); + + // Notify modem detection started + OnNetworkEvent(NetworkEvent::ModemDetecting); + + // Try to detect modem with retry limit + int detect_retries = 0; + while (detect_retries < MODEM_DETECT_MAX_RETRIES) { modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600); if (modem_ != nullptr) { break; } + detect_retries++; vTaskDelay(pdMS_TO_TICKS(1000)); } + if (modem_ == nullptr) { + ESP_LOGE(TAG, "Failed to detect modem after %d retries", MODEM_DETECT_MAX_RETRIES); + OnNetworkEvent(NetworkEvent::ModemErrorInitFailed); + return; + } + + ESP_LOGI(TAG, "Modem detected successfully"); + + // Set up network state change callback + // Note: Don't call GetCarrierName() here as it sends AT command and will block ReceiveTask modem_->OnNetworkStateChanged([this, &application](bool network_ready) { if (network_ready) { - ESP_LOGI(TAG, "Network is ready"); + OnNetworkEvent(NetworkEvent::Connected); } else { - ESP_LOGE(TAG, "Network is down"); - auto device_state = application.GetDeviceState(); - if (device_state == kDeviceStateListening || device_state == kDeviceStateSpeaking) { - application.Schedule([this, &application]() { - application.SetDeviceState(kDeviceStateIdle); - }); - } + OnNetworkEvent(NetworkEvent::Disconnected); } }); - // Wait for network ready - display->SetStatus(Lang::Strings::REGISTERING_NETWORK); - while (true) { + // Notify network registration started + OnNetworkEvent(NetworkEvent::Connecting); + + // Wait for network ready with retry limit + int reg_retries = 0; + while (reg_retries < NETWORK_REG_MAX_RETRIES) { auto result = modem_->WaitForNetworkReady(); - if (result == NetworkStatus::ErrorInsertPin) { - application.Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN); - } else if (result == NetworkStatus::ErrorRegistrationDenied) { - application.Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG); - } else { + if (result == NetworkStatus::Ready) { break; + } else if (result == NetworkStatus::ErrorInsertPin) { + OnNetworkEvent(NetworkEvent::ModemErrorNoSim); + } else if (result == NetworkStatus::ErrorRegistrationDenied) { + OnNetworkEvent(NetworkEvent::ModemErrorRegDenied); + } else if (result == NetworkStatus::ErrorTimeout) { + OnNetworkEvent(NetworkEvent::ModemErrorTimeout); } + reg_retries++; vTaskDelay(pdMS_TO_TICKS(10000)); } + if (!modem_->network_ready()) { + ESP_LOGE(TAG, "Failed to register network after %d retries", NETWORK_REG_MAX_RETRIES); + return; + } + // Print the ML307 modem information std::string module_revision = modem_->GetModuleRevision(); std::string imei = modem_->GetImei(); @@ -68,6 +135,15 @@ void Ml307Board::StartNetwork() { ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str()); } +void Ml307Board::StartNetwork() { + // Create network initialization task and return immediately + xTaskCreate([](void* arg) { + Ml307Board* board = static_cast(arg); + board->NetworkTask(); + vTaskDelete(NULL); + }, "ml307_net", 4096, this, 5, NULL); +} + NetworkInterface* Ml307Board::GetNetwork() { return modem_.get(); } @@ -106,8 +182,9 @@ std::string Ml307Board::GetBoardJson() { return board_json; } -void Ml307Board::SetPowerSaveMode(bool enabled) { - // TODO: Implement power save mode for ML307 +void Ml307Board::SetPowerSaveLevel(PowerSaveLevel level) { + // TODO: Implement power save level for ML307 + (void)level; } std::string Ml307Board::GetDeviceStatusJson() { diff --git a/main/boards/common/ml307_board.h b/main/boards/common/ml307_board.h index 22dddf99..6e669918 100644 --- a/main/boards/common/ml307_board.h +++ b/main/boards/common/ml307_board.h @@ -12,16 +12,25 @@ protected: gpio_num_t tx_pin_; gpio_num_t rx_pin_; gpio_num_t dtr_pin_; + NetworkEventCallback network_event_callback_; virtual std::string GetBoardJson() override; + // Internal helper to trigger network event callback + void OnNetworkEvent(NetworkEvent event, const std::string& data = ""); + + // Network initialization task (runs in FreeRTOS task) + static void NetworkTaskEntry(void* arg); + void NetworkTask(); + public: Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC); virtual std::string GetBoardType() override; virtual void StartNetwork() override; + virtual void SetNetworkEventCallback(NetworkEventCallback callback) override; virtual NetworkInterface* GetNetwork() override; virtual const char* GetNetworkStateIcon() override; - virtual void SetPowerSaveMode(bool enabled) override; + virtual void SetPowerSaveLevel(PowerSaveLevel level) override; virtual AudioCodec* GetAudioCodec() override { return nullptr; } virtual std::string GetDeviceStatusJson() override; }; diff --git a/main/boards/common/wifi_board.cc b/main/boards/common/wifi_board.cc index 20465abf..9edbcdcd 100644 --- a/main/boards/common/wifi_board.cc +++ b/main/boards/common/wifi_board.cc @@ -10,21 +10,35 @@ #include #include #include +#include #include +#include #include -#include #include #include "afsk_demod.h" static const char *TAG = "WifiBoard"; +// Connection timeout in seconds +static constexpr int CONNECT_TIMEOUT_SEC = 60; + WifiBoard::WifiBoard() { - Settings settings("wifi", true); - wifi_config_mode_ = settings.GetInt("force_ap") == 1; - if (wifi_config_mode_) { - ESP_LOGI(TAG, "force_ap is set to 1, reset to 0"); - settings.SetInt("force_ap", 0); + // Create connection timeout timer + esp_timer_create_args_t timer_args = { + .callback = OnWifiConnectTimeout, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "wifi_connect_timer", + .skip_unhandled_events = true + }; + esp_timer_create(&timer_args, &connect_timer_); +} + +WifiBoard::~WifiBoard() { + if (connect_timer_) { + esp_timer_stop(connect_timer_); + esp_timer_delete(connect_timer_); } } @@ -32,87 +46,190 @@ std::string WifiBoard::GetBoardType() { return "wifi"; } -void WifiBoard::EnterWifiConfigMode() { - auto& application = Application::GetInstance(); - application.SetDeviceState(kDeviceStateWifiConfiguring); - - auto& wifi_ap = WifiConfigurationAp::GetInstance(); - wifi_ap.SetLanguage(Lang::CODE); - wifi_ap.SetSsidPrefix("Xiaozhi"); - wifi_ap.Start(); - - // Wait 1.5 seconds to display board information - vTaskDelay(pdMS_TO_TICKS(1500)); - - // Display WiFi configuration AP SSID and web server URL - std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT; - hint += wifi_ap.GetSsid(); - hint += Lang::Strings::ACCESS_VIA_BROWSER; - hint += wifi_ap.GetWebServerUrl(); +void WifiBoard::StartNetwork() { + auto& wifi_manager = WifiManager::GetInstance(); - // Announce WiFi configuration prompt - application.Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG); - - #if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING - auto display = Board::GetInstance().GetDisplay(); - auto codec = Board::GetInstance().GetAudioCodec(); - int channel = 1; - if (codec) { - channel = codec->input_channels(); - } - ESP_LOGI(TAG, "Start receiving WiFi credentials from audio, input channels: %d", channel); - audio_wifi_config::ReceiveWifiCredentialsFromAudio(&application, &wifi_ap, display, channel); - #endif + // Initialize WiFi manager + WifiManagerConfig config; + config.ssid_prefix = "Xiaozhi"; + config.language = Lang::CODE; + wifi_manager.Initialize(config); - // Wait forever until reset after configuration - while (true) { - vTaskDelay(pdMS_TO_TICKS(10000)); + // Set unified event callback - forward to NetworkEvent with SSID data + wifi_manager.SetEventCallback([this, &wifi_manager](WifiEvent event) { + std::string ssid = wifi_manager.GetSsid(); + switch (event) { + case WifiEvent::Scanning: + OnNetworkEvent(NetworkEvent::Scanning); + break; + case WifiEvent::Connecting: + OnNetworkEvent(NetworkEvent::Connecting, ssid); + break; + case WifiEvent::Connected: + OnNetworkEvent(NetworkEvent::Connected, ssid); + break; + case WifiEvent::Disconnected: + OnNetworkEvent(NetworkEvent::Disconnected); + break; + case WifiEvent::ConfigModeEnter: + OnNetworkEvent(NetworkEvent::WifiConfigModeEnter); + break; + case WifiEvent::ConfigModeExit: + OnNetworkEvent(NetworkEvent::WifiConfigModeExit); + break; + } + }); + + // Try to connect or enter config mode + TryWifiConnect(); +} + +void WifiBoard::TryWifiConnect() { + auto& ssid_manager = SsidManager::GetInstance(); + bool have_ssid = !ssid_manager.GetSsidList().empty(); + + if (have_ssid) { + // Start connection attempt with timeout + ESP_LOGI(TAG, "Starting WiFi connection attempt"); + esp_timer_start_once(connect_timer_, CONNECT_TIMEOUT_SEC * 1000000ULL); + WifiManager::GetInstance().StartStation(); + } else { + // No SSID configured, enter config mode + // Wait for the board version to be shown + vTaskDelay(pdMS_TO_TICKS(1500)); + StartWifiConfigMode(); } } -void WifiBoard::StartNetwork() { - // User can press BOOT button while starting to enter WiFi configuration mode - if (wifi_config_mode_) { - EnterWifiConfigMode(); +void WifiBoard::OnNetworkEvent(NetworkEvent event, const std::string& data) { + switch (event) { + case NetworkEvent::Scanning: + ESP_LOGI(TAG, "WiFi scanning"); + break; + case NetworkEvent::Connecting: + ESP_LOGI(TAG, "WiFi connecting to %s", data.c_str()); + break; + case NetworkEvent::Connected: + // Stop timeout timer + esp_timer_stop(connect_timer_); + in_config_mode_ = false; + ESP_LOGI(TAG, "Connected to WiFi: %s", data.c_str()); + break; + case NetworkEvent::Disconnected: + ESP_LOGW(TAG, "WiFi disconnected"); + break; + case NetworkEvent::WifiConfigModeEnter: + ESP_LOGI(TAG, "WiFi config mode entered"); + in_config_mode_ = true; + break; + case NetworkEvent::WifiConfigModeExit: + ESP_LOGI(TAG, "WiFi config mode exited"); + in_config_mode_ = false; + // Try to connect with the new credentials + TryWifiConnect(); + break; + default: + break; + + } + + // Notify external callback if set + if (network_event_callback_) { + network_event_callback_(event, data); + } +} + +void WifiBoard::SetNetworkEventCallback(NetworkEventCallback callback) { + network_event_callback_ = std::move(callback); +} + +void WifiBoard::OnWifiConnectTimeout(void* arg) { + auto* board = static_cast(arg); + ESP_LOGW(TAG, "WiFi connection timeout, entering config mode"); + + WifiManager::GetInstance().StopStation(); + board->StartWifiConfigMode(); +} + +void WifiBoard::StartWifiConfigMode() { + in_config_mode_ = true; + auto& wifi_manager = WifiManager::GetInstance(); + + // Transition to wifi configuring state + Application::GetInstance().SetDeviceState(kDeviceStateWifiConfiguring); + + wifi_manager.StartConfigAp(); + + // Show config prompt after a short delay + Application::GetInstance().Schedule([this, &wifi_manager]() { + std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT; + hint += wifi_manager.GetApSsid(); + hint += Lang::Strings::ACCESS_VIA_BROWSER; + hint += wifi_manager.GetApWebUrl(); + + Application::GetInstance().Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG); + }); + +#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING + // Start acoustic provisioning task + auto codec = Board::GetInstance().GetAudioCodec(); + int channel = codec ? codec->input_channels() : 1; + ESP_LOGI(TAG, "Starting acoustic WiFi provisioning, channels: %d", channel); + + xTaskCreate([](void* arg) { + auto ch = reinterpret_cast(arg); + auto& app = Application::GetInstance(); + auto& wifi = WifiManager::GetInstance(); + auto disp = Board::GetInstance().GetDisplay(); + audio_wifi_config::ReceiveWifiCredentialsFromAudio(&app, &wifi, disp, ch); + vTaskDelete(NULL); + }, "acoustic_wifi", 4096, reinterpret_cast(channel), 2, NULL); +#endif +} + +void WifiBoard::EnterWifiConfigMode() { + ESP_LOGI(TAG, "EnterWifiConfigMode called"); + GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE); + + auto& app = Application::GetInstance(); + auto state = app.GetDeviceState(); + + if (state == kDeviceStateSpeaking || state == kDeviceStateListening || state == kDeviceStateIdle) { + // Reset protocol (close audio channel, reset protocol) + Application::GetInstance().ResetProtocol(); + + xTaskCreate([](void* arg) { + auto* board = static_cast(arg); + + // Wait for 1 second to allow speaking to finish gracefully + vTaskDelay(pdMS_TO_TICKS(1000)); + + // Stop any ongoing connection attempt + esp_timer_stop(board->connect_timer_); + WifiManager::GetInstance().StopStation(); + + // Enter config mode + board->StartWifiConfigMode(); + + vTaskDelete(NULL); + }, "wifi_cfg_delay", 4096, this, 2, NULL); + return; + } + + if (state != kDeviceStateStarting) { + ESP_LOGE(TAG, "EnterWifiConfigMode called but device state is not starting or speaking, device state: %d", state); return; } - // If no WiFi SSID is configured, enter WiFi configuration mode - auto& ssid_manager = SsidManager::GetInstance(); - auto ssid_list = ssid_manager.GetSsidList(); - if (ssid_list.empty()) { - wifi_config_mode_ = true; - EnterWifiConfigMode(); - return; - } + // Stop any ongoing connection attempt + esp_timer_stop(connect_timer_); + WifiManager::GetInstance().StopStation(); + + StartWifiConfigMode(); +} - auto& wifi_station = WifiStation::GetInstance(); - wifi_station.OnScanBegin([this]() { - auto display = Board::GetInstance().GetDisplay(); - display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000); - }); - wifi_station.OnConnect([this](const std::string& ssid) { - auto display = Board::GetInstance().GetDisplay(); - std::string notification = Lang::Strings::CONNECT_TO; - notification += ssid; - notification += "..."; - display->ShowNotification(notification.c_str(), 30000); - }); - wifi_station.OnConnected([this](const std::string& ssid) { - auto display = Board::GetInstance().GetDisplay(); - std::string notification = Lang::Strings::CONNECTED_TO; - notification += ssid; - display->ShowNotification(notification.c_str(), 30000); - }); - wifi_station.Start(); - - // Try to connect to WiFi, if failed, launch the WiFi configuration AP - if (!wifi_station.WaitForConnected(60 * 1000)) { - wifi_station.Stop(); - wifi_config_mode_ = true; - EnterWifiConfigMode(); - return; - } +bool WifiBoard::IsInWifiConfigMode() const { + return WifiManager::GetInstance().IsConfigMode(); } NetworkInterface* WifiBoard::GetNetwork() { @@ -121,147 +238,111 @@ NetworkInterface* WifiBoard::GetNetwork() { } const char* WifiBoard::GetNetworkStateIcon() { - if (wifi_config_mode_) { + auto& wifi = WifiManager::GetInstance(); + + if (wifi.IsConfigMode()) { return FONT_AWESOME_WIFI; } - auto& wifi_station = WifiStation::GetInstance(); - if (!wifi_station.IsConnected()) { + if (!wifi.IsConnected()) { return FONT_AWESOME_WIFI_SLASH; } - int8_t rssi = wifi_station.GetRssi(); + + int rssi = wifi.GetRssi(); if (rssi >= -60) { return FONT_AWESOME_WIFI; } else if (rssi >= -70) { return FONT_AWESOME_WIFI_FAIR; - } else { - return FONT_AWESOME_WIFI_WEAK; } + return FONT_AWESOME_WIFI_WEAK; } std::string WifiBoard::GetBoardJson() { - // Set the board type for OTA - auto& wifi_station = WifiStation::GetInstance(); - std::string board_json = R"({)"; - board_json += R"("type":")" + std::string(BOARD_TYPE) + R"(",)"; - board_json += R"("name":")" + std::string(BOARD_NAME) + R"(",)"; - if (!wifi_config_mode_) { - board_json += R"("ssid":")" + wifi_station.GetSsid() + R"(",)"; - board_json += R"("rssi":)" + std::to_string(wifi_station.GetRssi()) + R"(,)"; - board_json += R"("channel":)" + std::to_string(wifi_station.GetChannel()) + R"(,)"; - board_json += R"("ip":")" + wifi_station.GetIpAddress() + R"(",)"; + auto& wifi = WifiManager::GetInstance(); + std::string json = R"({"type":")" + std::string(BOARD_TYPE) + R"(",)"; + json += R"("name":")" + std::string(BOARD_NAME) + R"(",)"; + + if (!wifi.IsConfigMode()) { + json += R"("ssid":")" + wifi.GetSsid() + R"(",)"; + json += R"("rssi":)" + std::to_string(wifi.GetRssi()) + R"(,)"; + json += R"("channel":)" + std::to_string(wifi.GetChannel()) + R"(,)"; + json += R"("ip":")" + wifi.GetIpAddress() + R"(",)"; } - board_json += R"("mac":")" + SystemInfo::GetMacAddress() + R"(")"; - board_json += R"(})"; - return board_json; + + json += R"("mac":")" + SystemInfo::GetMacAddress() + R"("})"; + return json; } -void WifiBoard::SetPowerSaveMode(bool enabled) { - auto& wifi_station = WifiStation::GetInstance(); - wifi_station.SetPowerSaveMode(enabled); -} - -void WifiBoard::ResetWifiConfiguration() { - // Set a flag and reboot the device to enter the network configuration mode - { - Settings settings("wifi", true); - settings.SetInt("force_ap", 1); +void WifiBoard::SetPowerSaveLevel(PowerSaveLevel level) { + WifiPowerSaveLevel wifi_level; + switch (level) { + case PowerSaveLevel::LOW_POWER: + wifi_level = WifiPowerSaveLevel::LOW_POWER; + break; + case PowerSaveLevel::BALANCED: + wifi_level = WifiPowerSaveLevel::BALANCED; + break; + case PowerSaveLevel::PERFORMANCE: + default: + wifi_level = WifiPowerSaveLevel::PERFORMANCE; + break; } - GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE); - vTaskDelay(pdMS_TO_TICKS(1000)); - // Reboot the device - esp_restart(); + WifiManager::GetInstance().SetPowerSaveLevel(wifi_level); } std::string WifiBoard::GetDeviceStatusJson() { - /* - * Return device status JSON - * - * The returned JSON structure is as follows: - * { - * "audio_speaker": { - * "volume": 70 - * }, - * "screen": { - * "brightness": 100, - * "theme": "light" - * }, - * "battery": { - * "level": 50, - * "charging": true - * }, - * "network": { - * "type": "wifi", - * "ssid": "Xiaozhi", - * "rssi": -60 - * }, - * "chip": { - * "temperature": 25 - * } - * } - */ auto& board = Board::GetInstance(); auto root = cJSON_CreateObject(); // Audio speaker auto audio_speaker = cJSON_CreateObject(); - auto audio_codec = board.GetAudioCodec(); - if (audio_codec) { - cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume()); + if (auto codec = board.GetAudioCodec()) { + cJSON_AddNumberToObject(audio_speaker, "volume", codec->output_volume()); } cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); - // Screen brightness - auto backlight = board.GetBacklight(); + // Screen auto screen = cJSON_CreateObject(); - if (backlight) { + if (auto backlight = board.GetBacklight()) { cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); } - auto display = board.GetDisplay(); - if (display && display->height() > 64) { // For LCD display only - auto theme = display->GetTheme(); - if (theme != nullptr) { + if (auto display = board.GetDisplay(); display && display->height() > 64) { + if (auto theme = display->GetTheme()) { cJSON_AddStringToObject(screen, "theme", theme->name().c_str()); } } cJSON_AddItemToObject(root, "screen", screen); // Battery - int battery_level = 0; - bool charging = false; - bool discharging = false; - if (board.GetBatteryLevel(battery_level, charging, discharging)) { - cJSON* battery = cJSON_CreateObject(); - cJSON_AddNumberToObject(battery, "level", battery_level); + int level = 0; + bool charging = false, discharging = false; + if (board.GetBatteryLevel(level, charging, discharging)) { + auto battery = cJSON_CreateObject(); + cJSON_AddNumberToObject(battery, "level", level); cJSON_AddBoolToObject(battery, "charging", charging); cJSON_AddItemToObject(root, "battery", battery); } // Network + auto& wifi = WifiManager::GetInstance(); auto network = cJSON_CreateObject(); - auto& wifi_station = WifiStation::GetInstance(); cJSON_AddStringToObject(network, "type", "wifi"); - cJSON_AddStringToObject(network, "ssid", wifi_station.GetSsid().c_str()); - int rssi = wifi_station.GetRssi(); - if (rssi >= -60) { - cJSON_AddStringToObject(network, "signal", "strong"); - } else if (rssi >= -70) { - cJSON_AddStringToObject(network, "signal", "medium"); - } else { - cJSON_AddStringToObject(network, "signal", "weak"); - } + cJSON_AddStringToObject(network, "ssid", wifi.GetSsid().c_str()); + int rssi = wifi.GetRssi(); + const char* signal = rssi >= -60 ? "strong" : (rssi >= -70 ? "medium" : "weak"); + cJSON_AddStringToObject(network, "signal", signal); cJSON_AddItemToObject(root, "network", network); - // Chip - float esp32temp = 0.0f; - if (board.GetTemperature(esp32temp)) { + // Chip temperature + float temp = 0.0f; + if (board.GetTemperature(temp)) { auto chip = cJSON_CreateObject(); - cJSON_AddNumberToObject(chip, "temperature", esp32temp); + cJSON_AddNumberToObject(chip, "temperature", temp); cJSON_AddItemToObject(root, "chip", chip); } - auto json_str = cJSON_PrintUnformatted(root); - std::string json(json_str); - cJSON_free(json_str); + auto str = cJSON_PrintUnformatted(root); + std::string result(str); + cJSON_free(str); cJSON_Delete(root); - return json; + return result; } diff --git a/main/boards/common/wifi_board.h b/main/boards/common/wifi_board.h index c84cf0f1..7ee973c4 100644 --- a/main/boards/common/wifi_board.h +++ b/main/boards/common/wifi_board.h @@ -2,23 +2,68 @@ #define WIFI_BOARD_H #include "board.h" +#include +#include +#include class WifiBoard : public Board { protected: - bool wifi_config_mode_ = false; - void EnterWifiConfigMode(); + esp_timer_handle_t connect_timer_ = nullptr; + bool in_config_mode_ = false; + NetworkEventCallback network_event_callback_ = nullptr; + virtual std::string GetBoardJson() override; + /** + * Handle network event (called from WiFi manager callbacks) + * @param event The network event type + * @param data Additional data (e.g., SSID for Connecting/Connected events) + */ + void OnNetworkEvent(NetworkEvent event, const std::string& data = ""); + + /** + * Start WiFi connection attempt + */ + void TryWifiConnect(); + + /** + * Enter WiFi configuration mode + */ + void StartWifiConfigMode(); + + /** + * WiFi connection timeout callback + */ + static void OnWifiConnectTimeout(void* arg); + public: WifiBoard(); + virtual ~WifiBoard(); + virtual std::string GetBoardType() override; + + /** + * Start network connection asynchronously + * This function returns immediately. Network events are notified through the callback set by SetNetworkEventCallback(). + */ virtual void StartNetwork() override; + virtual NetworkInterface* GetNetwork() override; + virtual void SetNetworkEventCallback(NetworkEventCallback callback) override; virtual const char* GetNetworkStateIcon() override; - virtual void SetPowerSaveMode(bool enabled) override; - virtual void ResetWifiConfiguration(); + virtual void SetPowerSaveLevel(PowerSaveLevel level) override; virtual AudioCodec* GetAudioCodec() override { return nullptr; } virtual std::string GetDeviceStatusJson() override; + + /** + * Enter WiFi configuration mode (thread-safe, can be called from any task) + */ + void EnterWifiConfigMode(); + + /** + * Check if in WiFi config mode + */ + bool IsInWifiConfigMode() const; }; #endif // WIFI_BOARD_H diff --git a/main/boards/df-k10/df_k10_board.cc b/main/boards/df-k10/df_k10_board.cc index ab3e9e66..de344b2b 100644 --- a/main/boards/df-k10/df_k10_board.cc +++ b/main/boards/df-k10/df_k10_board.cc @@ -15,7 +15,6 @@ #include #include #include -#include #include "esp_io_expander_tca95xx_16bit.h" @@ -119,8 +118,9 @@ private: iot_button_register_cb(btn_a, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; } app.ToggleChatState(); }, this); @@ -149,8 +149,9 @@ private: iot_button_register_cb(btn_b, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; } app.ToggleChatState(); }, this); diff --git a/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc b/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc index fbdef6c2..c82bcaaf 100644 --- a/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc +++ b/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc @@ -7,7 +7,6 @@ #include "esp32_camera.h" #include "led/gpio_led.h" -#include #include #include #include @@ -22,8 +21,9 @@ class DfrobotEsp32S3AiCam : public WifiBoard { void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/doit-s3-aibox/doit_s3_aibox.cc b/main/boards/doit-s3-aibox/doit_s3_aibox.cc index 54fd9dfe..65e37a92 100644 --- a/main/boards/doit-s3-aibox/doit_s3_aibox.cc +++ b/main/boards/doit-s3-aibox/doit_s3_aibox.cc @@ -5,7 +5,6 @@ #include "button.h" #include "config.h" #include "led/gpio_led.h" -#include #include #include #include @@ -36,8 +35,9 @@ private: check_time = 0; } auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -46,13 +46,13 @@ private: ESP_LOGI(TAG, "DoubleClick times %d", click_times); if(click_times==3) { click_times = 0; - ResetWifiConfiguration(); + EnterWifiConfigMode(); } }); boot_button_.OnLongPress([this]() { if(click_times>=3) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); } else { click_times = 0; check_time = 0; diff --git a/main/boards/du-chatx/du-chatx-wifi.cc b/main/boards/du-chatx/du-chatx-wifi.cc index d9eea670..85530a80 100644 --- a/main/boards/du-chatx/du-chatx-wifi.cc +++ b/main/boards/du-chatx/du-chatx-wifi.cc @@ -9,7 +9,6 @@ #include "power_manager.h" #include "power_save_timer.h" -#include #include #include #include @@ -105,8 +104,9 @@ private: boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -153,11 +153,11 @@ public: level = power_manager_->GetBatteryLevel(); return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/echoear/EchoEar.cc b/main/boards/echoear/EchoEar.cc index 9e5f5c7e..8bf923f4 100644 --- a/main/boards/echoear/EchoEar.cc +++ b/main/boards/echoear/EchoEar.cc @@ -7,7 +7,6 @@ #include "config.h" #include "backlight.h" -#include #include #include @@ -475,9 +474,8 @@ private: auto touch_event = touchpad->CheckTouchEvent(); if (touch_event == Cst816s::TOUCH_RELEASE) { - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + board.EnterWifiConfigMode(); } else { app.ToggleChatState(); } @@ -567,9 +565,10 @@ private: { boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode"); - ResetWifiConfiguration(); + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/electron-bot/electron_bot.cc b/main/boards/electron-bot/electron_bot.cc index 8b733fc1..90d305f2 100644 --- a/main/boards/electron-bot/electron_bot.cc +++ b/main/boards/electron-bot/electron_bot.cc @@ -5,7 +5,6 @@ #include #include #include -#include #include "application.h" #include "codecs/no_audio_codec.h" @@ -76,9 +75,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index 7e1c6003..a5fa19a8 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -12,7 +12,6 @@ #include #include #include -#include #define TAG "EspBox3Board" @@ -74,8 +73,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp-box-lite/esp_box_lite_board.cc b/main/boards/esp-box-lite/esp_box_lite_board.cc index ca7dd6a9..05d88a12 100644 --- a/main/boards/esp-box-lite/esp_box_lite_board.cc +++ b/main/boards/esp-box-lite/esp_box_lite_board.cc @@ -12,7 +12,6 @@ #include #include #include -#include #define TAG "EspBoxBoardLite" @@ -98,9 +97,10 @@ private: void TogleState() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; + } app.ToggleChatState(); } diff --git a/main/boards/esp-box/esp_box_board.cc b/main/boards/esp-box/esp_box_board.cc index 4dcbca8e..4cbf71e4 100644 --- a/main/boards/esp-box/esp_box_board.cc +++ b/main/boards/esp-box/esp_box_board.cc @@ -10,7 +10,6 @@ #include #include #include -#include #define TAG "EspBoxBoard" @@ -72,8 +71,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp-hi/esp_hi.cc b/main/boards/esp-hi/esp_hi.cc index 0da56a48..fdfcebf0 100644 --- a/main/boards/esp-hi/esp_hi.cc +++ b/main/boards/esp-hi/esp_hi.cc @@ -4,7 +4,6 @@ #include "button.h" #include "config.h" #include "mcp_server.h" -#include #include #include #include @@ -23,7 +22,7 @@ #include "servo_dog_ctrl.h" #include "led_strip.h" #include "driver/rmt_tx.h" -#include "device_state_event.h" +#include "device_state.h" #include "sdkconfig.h" @@ -171,8 +170,10 @@ private: boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc b/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc index 82c00cfb..0a6405a8 100644 --- a/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc +++ b/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc @@ -12,7 +12,6 @@ #include "config.h" #include "esp32_camera.h" -#include #include #include #include @@ -81,10 +80,12 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); } void InitializeTouch() diff --git a/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc b/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc index 8dc65850..ffe2411e 100644 --- a/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc +++ b/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc @@ -8,7 +8,6 @@ #include "config.h" -#include #include #include #include "esp_lcd_gc9503.h" @@ -153,8 +152,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc b/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc index 990a3e3b..56e8950b 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc +++ b/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc @@ -8,7 +8,6 @@ #include "config.h" -#include #include #include #include "esp_lcd_gc9503.h" @@ -153,8 +152,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/main/boards/esp-sparkbot/esp_sparkbot_board.cc index e55f84e0..cef94861 100644 --- a/main/boards/esp-sparkbot/esp_sparkbot_board.cc +++ b/main/boards/esp-sparkbot/esp_sparkbot_board.cc @@ -7,7 +7,6 @@ #include "mcp_server.h" #include "settings.h" -#include #include #include #include @@ -80,8 +79,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp-spot/esp_spot_board.cc b/main/boards/esp-spot/esp_spot_board.cc index 9cc62947..d6931be9 100644 --- a/main/boards/esp-spot/esp_spot_board.cc +++ b/main/boards/esp-spot/esp_spot_board.cc @@ -17,7 +17,6 @@ #include "config.h" #include "sleep_timer.h" #include "wifi_board.h" -#include "wifi_station.h" #ifdef IMU_INT_GPIO #include @@ -217,7 +216,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { HandleUserActivity(); - ResetWifiConfiguration(); + EnterWifiConfigMode(); }); key_button_.OnClick([this]() { @@ -394,11 +393,11 @@ public: return &audio_codec; } - virtual void SetPowerSaveMode(bool enabled) override { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { if (sleep_timer_) { - sleep_timer_->SetEnabled(enabled); + sleep_timer_->SetEnabled(level == PowerSaveLevel::LOW_POWER); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { diff --git a/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc b/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc index 25963c0a..085b8152 100644 --- a/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc +++ b/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc @@ -11,7 +11,6 @@ #include "led/single_led.h" #include "assets/lang_config.h" -#include #include #include #include @@ -124,8 +123,9 @@ void InitializePowerManager() { boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -181,11 +181,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/esp32-cgc/esp32_cgc_board.cc b/main/boards/esp32-cgc/esp32_cgc_board.cc index 6b82b013..951e4813 100644 --- a/main/boards/esp32-cgc/esp32_cgc_board.cc +++ b/main/boards/esp32-cgc/esp32_cgc_board.cc @@ -9,7 +9,6 @@ #include "lamp_controller.h" #include "led/single_led.h" -#include #include #include #include @@ -127,8 +126,9 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc index 111f61fd..b0f28f1a 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc @@ -11,7 +11,6 @@ #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" -#include #include #include @@ -189,8 +188,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -274,10 +274,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -324,11 +324,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc index 90843e62..4592b3c4 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc +++ b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc @@ -10,7 +10,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -169,8 +168,9 @@ private: iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; } app.ToggleChatState(); }, this); diff --git a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc index ecdc7a3f..16d4e9a6 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc @@ -10,7 +10,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -385,8 +384,9 @@ private: iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; } app.ToggleChatState(); }, this); diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc index cf5c1727..6706879b 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc @@ -10,7 +10,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -358,8 +357,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc index 9c2debb9..66e49dc8 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc +++ b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc @@ -11,7 +11,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -290,8 +289,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -301,10 +301,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -358,11 +358,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual Camera* GetCamera() override { diff --git a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc index 920ddc54..460d746c 100644 --- a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc +++ b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc @@ -13,7 +13,6 @@ #include #include #include -#include #include "esp32_camera.h" #define TAG "esp32s3_korvo2_v3" @@ -241,8 +240,9 @@ private: boot_button_.OnClick([this]() {}); boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc index 99794fb0..2d7ed922 100644 --- a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc +++ b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc @@ -10,7 +10,6 @@ #include #include #include -#include #include #include #include @@ -109,8 +108,9 @@ private: boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -248,11 +248,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/hu-087/hu_087_board.cc b/main/boards/hu-087/hu_087_board.cc index e6d55baf..a4ad5ce7 100644 --- a/main/boards/hu-087/hu_087_board.cc +++ b/main/boards/hu-087/hu_087_board.cc @@ -2,7 +2,6 @@ #include #include #include -#include #include "application.h" #include "assets/lang_config.h" @@ -19,134 +18,134 @@ #define TAG "Hu087Board" class Hu087Board : public WifiBoard { - private: - i2c_master_bus_handle_t display_i2c_bus_; - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - Display* display_ = nullptr; - Button touch_button_; + private: + i2c_master_bus_handle_t display_i2c_bus_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + Display* display_ = nullptr; + Button touch_button_; - void InitializeDisplayI2c() { - i2c_master_bus_config_t bus_config = { - .i2c_port = (i2c_port_t)0, - .sda_io_num = DISPLAY_SDA_PIN, - .scl_io_num = DISPLAY_SCL_PIN, - .clk_source = I2C_CLK_SRC_DEFAULT, - .glitch_ignore_cnt = 7, - .intr_priority = 0, - .trans_queue_depth = 0, - .flags = - { - .enable_internal_pullup = 1, - }, - }; - ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); - } - - void InitializeSsd1306Display() { - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = - { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK( - esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(DISPLAY_HEIGHT), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK( - esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - display_ = new NoDisplay(); - return; + void InitializeDisplayI2c() { + i2c_master_bus_config_t bus_config = { + .i2c_port = (i2c_port_t)0, + .sda_io_num = DISPLAY_SDA_PIN, + .scl_io_num = DISPLAY_SCL_PIN, + .clk_source = I2C_CLK_SRC_DEFAULT, + .glitch_ignore_cnt = 7, + .intr_priority = 0, + .trans_queue_depth = 0, + .flags = + { + .enable_internal_pullup = 1, + }, + }; + ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); } - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + void InitializeSsd1306Display() { + // SSD1306 config + esp_lcd_panel_io_i2c_config_t io_config = { + .dev_addr = 0x3C, + .on_color_trans_done = nullptr, + .user_ctx = nullptr, + .control_phase_bytes = 1, + .dc_bit_offset = 6, + .lcd_cmd_bits = 8, + .lcd_param_bits = 8, + .flags = + { + .dc_low_on_data = 0, + .disable_control_phase = 0, + }, + .scl_speed_hz = 400 * 1000, + }; - display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, - DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - } + ESP_ERROR_CHECK( + esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_)); - void initializeAmpCtrl() { - gpio_config_t io_conf = { - .pin_bit_mask = (1ULL << AUDIO_I2S_SPK_GPIO_CTLR), // Select GPIO 2 - .mode = GPIO_MODE_OUTPUT, // Set as output - .pull_up_en = GPIO_PULLUP_ENABLE, // Disable pull-up - .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down - .intr_type = GPIO_INTR_DISABLE // Disable interrupts + ESP_LOGI(TAG, "Install SSD1306 driver"); + esp_lcd_panel_dev_config_t panel_config = {}; + panel_config.reset_gpio_num = -1; + panel_config.bits_per_pixel = 1; + + esp_lcd_panel_ssd1306_config_t ssd1306_config = { + .height = static_cast(DISPLAY_HEIGHT), + }; + panel_config.vendor_config = &ssd1306_config; + + ESP_ERROR_CHECK( + esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); + ESP_LOGI(TAG, "SSD1306 driver installed"); + + // Reset the display + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + if (esp_lcd_panel_init(panel_) != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize display"); + display_ = new NoDisplay(); + return; + } + + // Set the display to on + ESP_LOGI(TAG, "Turning display on"); + ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); + + display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); + } + + void initializeAmpCtrl() { + gpio_config_t io_conf = { + .pin_bit_mask = (1ULL << AUDIO_I2S_SPK_GPIO_CTLR), // Select GPIO 2 + .mode = GPIO_MODE_OUTPUT, // Set as output + .pull_up_en = GPIO_PULLUP_ENABLE, // Disable pull-up + .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down + .intr_type = GPIO_INTR_DISABLE // Disable interrupts + }; + gpio_config(&io_conf); + gpio_set_level(AUDIO_I2S_SPK_GPIO_CTLR, 1); }; - gpio_config(&io_conf); - gpio_set_level(AUDIO_I2S_SPK_GPIO_CTLR, 1); - }; - void InitializeButtons() { - touch_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } - app.ToggleChatState(); - }); + void InitializeButtons() { + touch_button_.OnClick([this]() { + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; + } + app.ToggleChatState(); + }); - touch_button_.OnLongPress([this]() { - auto codec = GetAudioCodec(); - auto volume = codec->output_volume() + 10; - if (volume > 100) { - volume = 100; - } // Need to implement logic to lower volume - codec->SetOutputVolume(volume); - GetDisplay()->ShowNotification(Lang::Strings::VOLUME + - std::to_string(volume)); - }); - } + touch_button_.OnLongPress([this]() { + auto codec = GetAudioCodec(); + auto volume = codec->output_volume() + 10; + if (volume > 100) { + volume = 100; + } // Need to implement logic to lower volume + codec->SetOutputVolume(volume); + GetDisplay()->ShowNotification(Lang::Strings::VOLUME + + std::to_string(volume)); + }); + } public: - Hu087Board() : touch_button_(TOUCH_BUTTON_GPIO) { - InitializeDisplayI2c(); - InitializeSsd1306Display(); - InitializeButtons(); - initializeAmpCtrl(); // Could control the amp ctrl pin throught voice - // detection i guess - } + Hu087Board() : touch_button_(TOUCH_BUTTON_GPIO) { + InitializeDisplayI2c(); + InitializeSsd1306Display(); + InitializeButtons(); + initializeAmpCtrl(); // Could control the amp ctrl pin throught voice + // detection i guess + } - virtual AudioCodec* GetAudioCodec() override { - static NoAudioCodecSimplex audio_codec( - AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, - AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, - AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_RIGHT, AUDIO_I2S_MIC_GPIO_SCK, - AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); - return &audio_codec; - } + virtual AudioCodec* GetAudioCodec() override { + static NoAudioCodecSimplex audio_codec( + AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, + AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, + AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_RIGHT, AUDIO_I2S_MIC_GPIO_SCK, + AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); + return &audio_codec; + } - virtual Display* GetDisplay() override { return display_; } + virtual Display* GetDisplay() override { return display_; } }; DECLARE_BOARD(Hu087Board); diff --git a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc index f60700f9..d54a7f6c 100644 --- a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc +++ b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc @@ -10,7 +10,6 @@ #include #include #include -#include #include "led/single_led.h" #include "assets/lang_config.h" #include "esp_lcd_panel_gc9301.h" @@ -230,11 +229,11 @@ private: } }); // 电源键三击:重置WiFi - pwr_button_.OnMultipleClick([this]() - { + pwr_button_.OnMultipleClick([this]() { ESP_LOGI(TAG, "Power button triple click: 重置WiFi"); power_save_timer_->WakeUp(); - ResetWifiConfiguration(); }, 3); + EnterWifiConfigMode(); + }, 3); wifi_button.OnPressDown([this]() { @@ -372,11 +371,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index 94e7e176..0e068a05 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -9,7 +9,6 @@ #include "axp2101.h" #include "assets/lang_config.h" -#include #include #include #include @@ -173,10 +172,10 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); } } }); diff --git a/main/boards/kevin-c3/kevin_c3_board.cc b/main/boards/kevin-c3/kevin_c3_board.cc index df7e82c2..6ebff33b 100644 --- a/main/boards/kevin-c3/kevin_c3_board.cc +++ b/main/boards/kevin-c3/kevin_c3_board.cc @@ -6,7 +6,6 @@ #include "led/circular_strip.h" #include "led_strip_control.h" -#include #include #include #include @@ -47,8 +46,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc index 11920b5b..782504e7 100644 --- a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc +++ b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc @@ -12,7 +12,6 @@ #include #include #include -#include #include "esp32_camera.h" #define TAG "kevin-sp-v3" @@ -39,8 +38,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc index d96fcdae..597001c5 100644 --- a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc +++ b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc @@ -10,7 +10,6 @@ #include #include #include -#include #include "esp32_camera.h" #define TAG "kevin-sp-v4" @@ -54,8 +53,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc b/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc index 19f2f06c..e116f84d 100644 --- a/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc +++ b/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc @@ -7,7 +7,6 @@ #include "config.h" -#include #include #include #include "esp_lcd_gc9503.h" @@ -123,8 +122,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc b/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc index e1f7d884..1ddf5732 100644 --- a/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc +++ b/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc @@ -11,7 +11,6 @@ #include #include #include -#include #define TAG "labplus_ledong_v2" @@ -56,8 +55,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/labplus-mpython-v3/mpython_pro.cc b/main/boards/labplus-mpython-v3/mpython_pro.cc index 3b9d1499..69c2eee9 100644 --- a/main/boards/labplus-mpython-v3/mpython_pro.cc +++ b/main/boards/labplus-mpython-v3/mpython_pro.cc @@ -11,7 +11,6 @@ #include #include #include -#include #define TAG "mpython_v3" @@ -53,8 +52,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc index 988246d9..f728ed8e 100644 --- a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc +++ b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc @@ -10,7 +10,6 @@ #include #include #include -#include #define TAG "LichuangC3DevBoard" @@ -51,8 +50,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index fb431a2c..42f82884 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -7,12 +7,12 @@ #include "config.h" #include "i2c_device.h" #include "esp32_camera.h" +#include "mcp_server.h" #include #include #include #include -#include #include #include #include @@ -107,8 +107,10 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -245,6 +247,17 @@ private: camera_ = new Esp32Camera(video_config); } + void InitializeTools() { + auto &mcp_server = McpServer::GetInstance(); + mcp_server.AddTool("self.system.reconfigure_wifi", + "End this conversation and enter WiFi configuration mode.\n" + "**CAUTION** You must ask the user to confirm this action.", + PropertyList(), [this](const PropertyList& properties) { + EnterWifiConfigMode(); + return true; + }); + } + public: LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); @@ -253,6 +266,7 @@ public: InitializeTouch(); InitializeButtons(); InitializeCamera(); + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc index 8e1424f4..98ebac36 100644 --- a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc +++ b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc @@ -14,7 +14,6 @@ #include #include #include -#include #define TAG "LilygoTCameraPlusS3Board" @@ -209,11 +208,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; + } app.ToggleChatState(); }); key1_button_.OnClick([this]() { @@ -325,11 +326,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual Backlight* GetBacklight() override { diff --git a/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc b/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc index e40878ff..795898b9 100644 --- a/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc +++ b/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc @@ -9,7 +9,6 @@ #include #include -#include #include #include #include "esp_lcd_gc9d01n.h" @@ -199,11 +198,12 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; + } app.ToggleChatState(); }); } @@ -238,11 +238,11 @@ public: return display_; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual Backlight* GetBacklight() override { diff --git a/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc b/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc index b65a3799..1ba35eb0 100644 --- a/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc +++ b/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc @@ -9,7 +9,6 @@ #include "esp_lcd_mipi_dsi.h" #include "esp_ldo_regulator.h" -#include #include #include #include @@ -269,8 +268,10 @@ public: void AppToggleChatState(void){ auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); } diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc b/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc index f0a4aeda..b18d27af 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc @@ -9,7 +9,6 @@ #include #include -#include #include #include #include "esp_lcd_st7796.h" @@ -223,11 +222,12 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); - } power_save_timer_->WakeUp(); + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; + } app.ToggleChatState(); }); } @@ -263,11 +263,11 @@ public: return display_; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual Backlight* GetBacklight() override { diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc index 9b45f077..4f4c7ff7 100644 --- a/main/boards/m5stack-core-s3/m5stack_core_s3.cc +++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc @@ -9,7 +9,6 @@ #include #include -#include #include #include #include @@ -214,9 +213,9 @@ private: // 只有短触才触发 if (touch_duration < TOUCH_THRESHOLD_MS) { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); } @@ -381,11 +380,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual Backlight *GetBacklight() override { diff --git a/main/boards/m5stack-tab5/m5stack_tab5.cc b/main/boards/m5stack-tab5/m5stack_tab5.cc index 1aa99679..69a33849 100644 --- a/main/boards/m5stack-tab5/m5stack_tab5.cc +++ b/main/boards/m5stack-tab5/m5stack_tab5.cc @@ -19,7 +19,6 @@ #include #include #include -#include #include "i2c_device.h" #include "esp_lcd_touch_gt911.h" #include "esp_lcd_touch_st7123.h" @@ -150,8 +149,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - // ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/magiclick-2p4/magiclick_2p4_board.cc b/main/boards/magiclick-2p4/magiclick_2p4_board.cc index 770884b0..16c8fc1c 100644 --- a/main/boards/magiclick-2p4/magiclick_2p4_board.cc +++ b/main/boards/magiclick-2p4/magiclick_2p4_board.cc @@ -8,7 +8,6 @@ #include "assets/lang_config.h" #include -#include #include #include #include @@ -108,8 +107,9 @@ private: void InitializeButtons() { main_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); main_button_.OnPressDown([this]() { @@ -123,8 +123,9 @@ private: left_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; @@ -259,11 +260,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/magiclick-2p5/magiclick_2p5_board.cc b/main/boards/magiclick-2p5/magiclick_2p5_board.cc index 1db21fa6..ce73a97e 100644 --- a/main/boards/magiclick-2p5/magiclick_2p5_board.cc +++ b/main/boards/magiclick-2p5/magiclick_2p5_board.cc @@ -8,7 +8,6 @@ #include "assets/lang_config.h" #include -#include #include #include #include @@ -146,17 +145,11 @@ private: main_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); - Disable4GModule(); + wifi_board.EnterWifiConfigMode(); } - } else if(GetNetworkType() == NetworkType::ML307) { - - Enable4GModule(); - // stop WiFi - esp_wifi_stop(); } }); main_button_.OnDoubleClick([this]() { @@ -315,11 +308,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc index 19f564fb..bdf3afc1 100644 --- a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc +++ b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc @@ -7,7 +7,6 @@ #include "config.h" #include "power_save_timer.h" -#include #include #include #include @@ -131,8 +130,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/magiclick-c3/magiclick_c3_board.cc b/main/boards/magiclick-c3/magiclick_c3_board.cc index 373e661b..4df269eb 100644 --- a/main/boards/magiclick-c3/magiclick_c3_board.cc +++ b/main/boards/magiclick-c3/magiclick_c3_board.cc @@ -7,7 +7,6 @@ #include "config.h" #include "power_save_timer.h" -#include #include #include #include @@ -95,8 +94,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); boot_button_.OnPressDown([this]() { diff --git a/main/boards/minsi-k08-dual/minsi_k08_dual.cc b/main/boards/minsi-k08-dual/minsi_k08_dual.cc index 8fa0bbaf..460f28be 100644 --- a/main/boards/minsi-k08-dual/minsi_k08_dual.cc +++ b/main/boards/minsi-k08-dual/minsi_k08_dual.cc @@ -13,7 +13,6 @@ #include "assets/lang_config.h" #include "power_manager.h" -#include #include #include #include @@ -128,10 +127,11 @@ private: boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } app.ToggleChatState(); @@ -235,11 +235,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/mixgo-nova/mixgo-nova.cc b/main/boards/mixgo-nova/mixgo-nova.cc index fdaaea32..5d586ec5 100644 --- a/main/boards/mixgo-nova/mixgo-nova.cc +++ b/main/boards/mixgo-nova/mixgo-nova.cc @@ -12,7 +12,6 @@ #include #include #include -#include #define TAG "MIXGO_NOVA" @@ -55,8 +54,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc b/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc index 00ba5bc8..ce72cc5a 100644 --- a/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc +++ b/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc @@ -6,7 +6,6 @@ #include "config.h" #include "led/single_led.h" -#include #include #include #include @@ -82,8 +81,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc index 61329772..07e6d814 100644 --- a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc +++ b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc @@ -6,7 +6,6 @@ #include "config.h" #include "led/single_led.h" -#include #include #include #include @@ -102,8 +101,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/otto-robot/otto_controller.cc b/main/boards/otto-robot/otto_controller.cc index cb041013..96791d45 100644 --- a/main/boards/otto-robot/otto_controller.cc +++ b/main/boards/otto-robot/otto_controller.cc @@ -16,7 +16,7 @@ #include "power_manager.h" #include "sdkconfig.h" #include "settings.h" -#include +#include #define TAG "OttoController" @@ -818,8 +818,8 @@ public: mcp_server.AddTool("self.otto.get_ip", "获取机器人WiFi IP地址", PropertyList(), [](const PropertyList& properties) -> ReturnValue { - auto& wifi_station = WifiStation::GetInstance(); - std::string ip = wifi_station.GetIpAddress(); + auto& wifi = WifiManager::GetInstance(); + std::string ip = wifi.GetIpAddress(); if (ip.empty()) { return "{\"ip\":\"\",\"connected\":false}"; } diff --git a/main/boards/otto-robot/otto_robot.cc b/main/boards/otto-robot/otto_robot.cc index 210e3904..bf892c71 100644 --- a/main/boards/otto-robot/otto_robot.cc +++ b/main/boards/otto-robot/otto_robot.cc @@ -5,9 +5,6 @@ #include #include #include -#include -#include -#include #include "application.h" #include "codecs/no_audio_codec.h" @@ -174,9 +171,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/sensecap-watcher/sensecap_watcher.cc b/main/boards/sensecap-watcher/sensecap_watcher.cc index c6a00dd4..5a1273a7 100644 --- a/main/boards/sensecap-watcher/sensecap_watcher.cc +++ b/main/boards/sensecap-watcher/sensecap_watcher.cc @@ -20,7 +20,6 @@ #include #include #include -#include #include #include #include @@ -263,11 +262,13 @@ private: iot_button_register_cb(btns, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); - auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - self->ResetWifiConfiguration(); - } self->power_save_timer_->WakeUp(); + + auto& app = Application::GetInstance(); + if (app.GetDeviceState() == kDeviceStateStarting) { + self->EnterWifiConfigMode(); + return; + } app.ToggleChatState(); }, this); @@ -625,11 +626,11 @@ public: return &led; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override { diff --git a/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc b/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc index 77364b9d..0f5eb45f 100644 --- a/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc +++ b/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc @@ -6,7 +6,6 @@ #include "config.h" #include "led/single_led.h" #include "assets/lang_config.h" -#include #include #include #include @@ -241,9 +240,10 @@ private: // 只有短触才触发 if (touch_duration < TOUCH_THRESHOLD_MS) { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board->ResetWifiConfiguration(); + // During startup (before connected), pressing touch enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + board->EnterWifiConfigMode(); + return; } app.ToggleChatState(); } @@ -365,8 +365,10 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -471,11 +473,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc b/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc index 436cc346..b56c3b8a 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc +++ b/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc @@ -6,7 +6,6 @@ #include "config.h" #include "led/single_led.h" #include "assets/lang_config.h" -#include #include #include #include "system_reset.h" @@ -166,9 +165,9 @@ private: // 只有短触才触发 if (touch_duration < TOUCH_THRESHOLD_MS) { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + board.EnterWifiConfigMode(); + return; } app.ToggleChatState(); } @@ -251,8 +250,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -314,11 +314,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc b/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc index cad05c65..7952ece9 100644 --- a/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc +++ b/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc @@ -11,7 +11,6 @@ #include #include #include -#include #include #include "power_save_timer.h" @@ -106,8 +105,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -198,11 +198,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/taiji-pi-s3/taiji_pi_s3.cc b/main/boards/taiji-pi-s3/taiji_pi_s3.cc index 05876c4d..06635a79 100644 --- a/main/boards/taiji-pi-s3/taiji_pi_s3.cc +++ b/main/boards/taiji-pi-s3/taiji_pi_s3.cc @@ -8,7 +8,6 @@ #include #include #include -#include #include #include #include @@ -503,9 +502,9 @@ private: // 只有短触才触发 if (touch_duration < TOUCH_THRESHOLD_MS) { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && - !WifiStation::GetInstance().IsConnected()) { - board.ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + board.EnterWifiConfigMode(); + return; } app.ToggleChatState(); } diff --git a/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc b/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc index bebe9747..ce8fdf2e 100644 --- a/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc +++ b/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc @@ -9,7 +9,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -159,8 +158,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -243,11 +243,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/waveshare-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc b/main/boards/waveshare-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc index 4c5516e4..0664b79f 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc +++ b/main/boards/waveshare-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc @@ -12,7 +12,6 @@ #include #include #include -#include #define TAG "waveshare_c6_amoled_1_32" @@ -105,8 +104,10 @@ class CustomBoard : public WifiBoard { void InitializeButtons() { boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -172,7 +173,7 @@ class CustomBoard : public WifiBoard { }); mcp_server.AddTool("self.disp.network", "重新配网", PropertyList(), [this](const PropertyList &) -> ReturnValue { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } diff --git a/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc b/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc index 42cd4b9f..0c1a7dda 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc +++ b/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc @@ -8,7 +8,6 @@ #include #include #include -#include #include "esp_lcd_sh8601.h" #include "display/lcd_display.h" #include "esp_io_expander_tca9554.h" @@ -153,8 +152,9 @@ private: void InitializeButtons() { //接入锂电池时,可长按PWR开机/关机 boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } }); diff --git a/main/boards/waveshare-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc b/main/boards/waveshare-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc index 2e2bd71e..c9e5c14d 100644 --- a/main/boards/waveshare-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc +++ b/main/boards/waveshare-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc @@ -11,7 +11,6 @@ #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" -#include #include #include @@ -185,8 +184,10 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -244,10 +245,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -302,12 +303,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/waveshare-p4-nano/esp32-p4-nano.cc b/main/boards/waveshare-p4-nano/esp32-p4-nano.cc index 9a87981e..5389b9cf 100644 --- a/main/boards/waveshare-p4-nano/esp32-p4-nano.cc +++ b/main/boards/waveshare-p4-nano/esp32-p4-nano.cc @@ -17,7 +17,6 @@ #include "esp_lcd_mipi_dsi.h" #include "esp_lcd_jd9365_10_1.h" -#include #include #include #include @@ -221,10 +220,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); } public: diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc index 68a29473..711930d5 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc @@ -16,7 +16,6 @@ #include "esp_lcd_st7703.h" -#include #include #include #include @@ -174,10 +173,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); } public: diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-7b/esp32-p4-wifi6-touch-lcd-7b.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-7b/esp32-p4-wifi6-touch-lcd-7b.cc index ebc12fc4..61f83ffd 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-7b/esp32-p4-wifi6-touch-lcd-7b.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-7b/esp32-p4-wifi6-touch-lcd-7b.cc @@ -16,7 +16,6 @@ #include "esp_lcd_ek79007.h" -#include #include #include #include @@ -195,10 +194,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); } public: diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc index 94cff602..dc3ed4e7 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc @@ -16,7 +16,6 @@ #include "esp_lcd_jd9365_10_1.h" #include "config.h" -#include #include #include #include @@ -176,10 +175,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); } public: diff --git a/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc b/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc index 85948c65..d2969e25 100644 --- a/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc +++ b/main/boards/waveshare-s3-audio-board/esp32-s3-audio_board.cc @@ -10,7 +10,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -148,8 +147,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/waveshare-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc b/main/boards/waveshare-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc index a0c940b8..f801d27e 100644 --- a/main/boards/waveshare-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc +++ b/main/boards/waveshare-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc @@ -3,7 +3,6 @@ #include #include #include -#include "wifi_station.h" #include "application.h" #include "button.h" #include "codecs/es8311_audio_codec.h" @@ -42,8 +41,10 @@ class CustomBoard : public WifiBoard { void InitializeButtons() { boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -60,7 +61,7 @@ class CustomBoard : public WifiBoard { void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.disp.network", "重新配网", PropertyList(), [this](const PropertyList &) -> ReturnValue { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } diff --git a/main/boards/waveshare-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc b/main/boards/waveshare-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc index 5f8099ed..148a243f 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc +++ b/main/boards/waveshare-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc @@ -12,7 +12,6 @@ #include #include #include -#include #define TAG "waveshare_s3_amoled_1_32" @@ -105,8 +104,10 @@ class CustomBoard : public WifiBoard { void InitializeButtons() { boot_button_.OnClick([this]() { auto &app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -172,7 +173,7 @@ class CustomBoard : public WifiBoard { }); mcp_server.AddTool("self.disp.network", "重新配网", PropertyList(), [this](const PropertyList &) -> ReturnValue { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc index 9053154b..f0c3c314 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc +++ b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc @@ -11,7 +11,6 @@ #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" -#include #include #include @@ -199,8 +198,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -289,10 +289,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -349,12 +349,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc b/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc index 15facfcb..a8855210 100644 --- a/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc +++ b/main/boards/waveshare-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc @@ -11,7 +11,6 @@ #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" -#include #include #include @@ -186,8 +185,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -276,10 +276,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -335,12 +335,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/waveshare-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc b/main/boards/waveshare-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc index efcd631c..dd466cd7 100644 --- a/main/boards/waveshare-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc +++ b/main/boards/waveshare-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc @@ -9,7 +9,6 @@ #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" -#include #include #include @@ -106,8 +105,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -189,10 +189,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -250,12 +250,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/waveshare-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc b/main/boards/waveshare-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc index bd1e3c7a..870fed91 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc +++ b/main/boards/waveshare-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc @@ -10,7 +10,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -135,8 +134,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc b/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc index 2b89f944..e52dd8fa 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc @@ -10,7 +10,6 @@ #include "i2c_device.h" #include #include -#include #include #include #include @@ -260,8 +259,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -353,11 +353,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } #endif }; diff --git a/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc b/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc index bef2b55b..5b65e79d 100644 --- a/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc +++ b/main/boards/waveshare-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc @@ -11,7 +11,6 @@ #include "power_save_timer.h" #include "axp2101.h" #include "i2c_device.h" -#include #include #include @@ -242,8 +241,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -292,10 +292,10 @@ private: void InitializeTools() { auto &mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.system.reconfigure_wifi", - "Reboot the device and enter WiFi configuration mode.\n" + "End this conversation and enter WiFi configuration mode.\n" "**CAUTION** You must ask the user to confirm this action.", PropertyList(), [this](const PropertyList& properties) { - ResetWifiConfiguration(); + EnterWifiConfigMode(); return true; }); } @@ -420,12 +420,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) - { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc b/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc index 28250fb3..2cef78bb 100644 --- a/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc +++ b/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc @@ -12,7 +12,6 @@ #include "esp_lcd_ek79007.h" -#include #include #include #include @@ -169,10 +168,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } - app.ToggleChatState(); }); + app.ToggleChatState(); + }); } void InitializeSdCard() { diff --git a/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc b/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc index 2d2f36e6..0dac3b1a 100644 --- a/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc +++ b/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -229,11 +228,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - Ml307Board::SetPowerSaveMode(enabled); + Ml307Board::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc b/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc index 37d56e4b..3b2a0b3f 100644 --- a/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc +++ b/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -141,8 +140,9 @@ private: boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -233,11 +233,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc index 18f7a01a..3ba9643a 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc @@ -16,7 +16,6 @@ #include #include #include -#include #define TAG "XINGZHI_CUBE_0_96OLED_ML307" @@ -134,10 +133,11 @@ private: power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } app.ToggleChatState(); @@ -223,11 +223,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc index d2f06851..27bf76a4 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc @@ -10,7 +10,6 @@ #include "power_save_timer.h" #include "../xingzhi-cube-1.54tft-wifi/power_manager.h" -#include #include #include @@ -134,8 +133,9 @@ private: boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -214,11 +214,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc index 72aff953..bd33932e 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -82,10 +81,11 @@ private: power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } app.ToggleChatState(); @@ -201,11 +201,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc index 8827b37e..e7c41dfd 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -81,8 +80,9 @@ private: boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -190,11 +190,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc b/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc index 4879c13a..cbe64a7c 100644 --- a/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc +++ b/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc @@ -3,9 +3,7 @@ #include "application.h" #include "display.h" #include "assets/lang_config.h" -// #include "audio_codec.h" #include "wifi_board.h" -#include #include "power_save_timer.h" #include "codecs/es8311_audio_codec.h" #include // 用于std::max/std::min @@ -128,11 +126,12 @@ void Cst816x::touchpad_daemon(void* arg) { switch (current_event.type) { case TouchEventType::SINGLE_CLICK: if (current_event.x == 40) { - board.SetPowerSaveMode(false); + board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { auto& wifi_board = static_cast(Board::GetInstance()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } app.ToggleChatState(); } else if (current_event.x == 20) { // 20,600 单击:音量+ diff --git a/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc b/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc index 1ca4aa61..c1467caf 100644 --- a/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc +++ b/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc @@ -16,7 +16,6 @@ #include "assets/lang_config.h" #include #include -#include #include "cst816x.h" #define TAG "XINGZHI_METAL_1_54_WIFI" @@ -191,11 +190,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc b/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc index c89a0cb9..634ebbe9 100644 --- a/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc +++ b/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc @@ -11,7 +11,6 @@ #include "adc_battery_monitor.h" #include "press_to_talk_mcp_tool.h" -#include #include #include #include @@ -193,11 +192,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { sleep_timer_->WakeUp(); } - Ml307Board::SetPowerSaveMode(enabled); + Ml307Board::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xmini-c3-v3/xmini_c3_board.cc b/main/boards/xmini-c3-v3/xmini_c3_board.cc index 2462ad0b..af267648 100644 --- a/main/boards/xmini-c3-v3/xmini_c3_board.cc +++ b/main/boards/xmini-c3-v3/xmini_c3_board.cc @@ -11,7 +11,7 @@ #include "adc_battery_monitor.h" #include "press_to_talk_mcp_tool.h" -#include +#include #include #include #include @@ -128,8 +128,10 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + // During startup (before connected), pressing BOOT enters config mode without reboot + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { app.ToggleChatState(); @@ -188,11 +190,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index 4707d858..11d4a2ea 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -10,7 +10,6 @@ #include "power_save_timer.h" #include "press_to_talk_mcp_tool.h" -#include #include #include #include @@ -115,8 +114,9 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) { app.ToggleChatState(); @@ -171,11 +171,11 @@ public: return &audio_codec; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/yunliao-s3/yunliao_s3.cc b/main/boards/yunliao-s3/yunliao_s3.cc index f50ed648..81f07aad 100644 --- a/main/boards/yunliao-s3/yunliao_s3.cc +++ b/main/boards/yunliao-s3/yunliao_s3.cc @@ -10,7 +10,6 @@ #include "assets/lang_config.h" #include #include -#include #define TAG "YunliaoS3" @@ -85,7 +84,7 @@ private: ESP_LOGI(TAG, "Button OnThreeClick"); if (GetNetworkType() == NetworkType::WIFI) { auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); } },3); boot_button_.OnLongPress([this]() { @@ -201,11 +200,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc b/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc index 48845a1e..e0563719 100644 --- a/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc +++ b/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -77,10 +76,11 @@ private: power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); if (GetNetworkType() == NetworkType::WIFI) { - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { + if (app.GetDeviceState() == kDeviceStateStarting) { // cast to WifiBoard auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.ResetWifiConfiguration(); + wifi_board.EnterWifiConfigMode(); + return; } } app.ToggleChatState(); @@ -200,11 +200,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - DualNetworkBoard::SetPowerSaveMode(enabled); + DualNetworkBoard::SetPowerSaveLevel(level); } }; diff --git a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc index 282a6286..5f7c305d 100644 --- a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc +++ b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc @@ -12,7 +12,6 @@ #include #include -#include #include #include @@ -81,8 +80,9 @@ private: boot_button_.OnClick([this]() { power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { - ResetWifiConfiguration(); + if (app.GetDeviceState() == kDeviceStateStarting) { + EnterWifiConfigMode(); + return; } app.ToggleChatState(); }); @@ -98,7 +98,7 @@ private: app.SetDeviceState(kDeviceStateWifiConfiguring); // 重置WiFi配置以确保进入配网模式 - ResetWifiConfiguration(); + EnterWifiConfigMode(); }); volume_up_button_.OnClick([this]() { @@ -217,11 +217,11 @@ public: return true; } - virtual void SetPowerSaveMode(bool enabled) override { - if (!enabled) { + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } - WifiBoard::SetPowerSaveMode(enabled); + WifiBoard::SetPowerSaveLevel(level); } }; diff --git a/main/device_state_event.cc b/main/device_state_event.cc deleted file mode 100644 index 81e2be22..00000000 --- a/main/device_state_event.cc +++ /dev/null @@ -1,46 +0,0 @@ -#include "device_state_event.h" - -ESP_EVENT_DEFINE_BASE(XIAOZHI_STATE_EVENTS); - -DeviceStateEventManager& DeviceStateEventManager::GetInstance() { - static DeviceStateEventManager instance; - return instance; -} - -void DeviceStateEventManager::RegisterStateChangeCallback(std::function callback) { - std::lock_guard lock(mutex_); - callbacks_.push_back(callback); -} - -void DeviceStateEventManager::PostStateChangeEvent(DeviceState previous_state, DeviceState current_state) { - device_state_event_data_t event_data = { - .previous_state = previous_state, - .current_state = current_state - }; - esp_event_post(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, &event_data, sizeof(event_data), portMAX_DELAY); -} - -std::vector> DeviceStateEventManager::GetCallbacks() { - std::lock_guard lock(mutex_); - return callbacks_; -} - -DeviceStateEventManager::DeviceStateEventManager() { - esp_err_t err = esp_event_loop_create_default(); - if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) { - ESP_ERROR_CHECK(err); - } - - ESP_ERROR_CHECK(esp_event_handler_register(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, - [](void* handler_args, esp_event_base_t base, int32_t id, void* event_data) { - auto* data = static_cast(event_data); - auto& manager = DeviceStateEventManager::GetInstance(); - for (const auto& callback : manager.GetCallbacks()) { - callback(data->previous_state, data->current_state); - } - }, nullptr)); -} - -DeviceStateEventManager::~DeviceStateEventManager() { - esp_event_handler_unregister(XIAOZHI_STATE_EVENTS, XIAOZHI_STATE_CHANGED_EVENT, nullptr); -} \ No newline at end of file diff --git a/main/device_state_event.h b/main/device_state_event.h deleted file mode 100644 index 03e27134..00000000 --- a/main/device_state_event.h +++ /dev/null @@ -1,39 +0,0 @@ -#ifndef _DEVICE_STATE_EVENT_H_ -#define _DEVICE_STATE_EVENT_H_ - -#include -#include -#include -#include -#include "device_state.h" - -ESP_EVENT_DECLARE_BASE(XIAOZHI_STATE_EVENTS); - -enum { - XIAOZHI_STATE_CHANGED_EVENT, -}; - -struct device_state_event_data_t { - DeviceState previous_state; - DeviceState current_state; -}; - -class DeviceStateEventManager { -public: - static DeviceStateEventManager& GetInstance(); - DeviceStateEventManager(const DeviceStateEventManager&) = delete; - DeviceStateEventManager& operator=(const DeviceStateEventManager&) = delete; - - void RegisterStateChangeCallback(std::function callback); - void PostStateChangeEvent(DeviceState previous_state, DeviceState current_state); - std::vector> GetCallbacks(); - -private: - DeviceStateEventManager(); - ~DeviceStateEventManager(); - - std::vector> callbacks_; - std::mutex mutex_; -}; - -#endif // _DEVICE_STATE_EVENT_H_ \ No newline at end of file diff --git a/main/device_state_machine.cc b/main/device_state_machine.cc new file mode 100644 index 00000000..30581de6 --- /dev/null +++ b/main/device_state_machine.cc @@ -0,0 +1,161 @@ +#include "device_state_machine.h" + +#include +#include + +static const char* TAG = "StateMachine"; + +// State name strings for logging +static const char* const STATE_STRINGS[] = { + "unknown", + "starting", + "wifi_configuring", + "idle", + "connecting", + "listening", + "speaking", + "upgrading", + "activating", + "audio_testing", + "fatal_error", + "invalid_state" +}; + +DeviceStateMachine::DeviceStateMachine() { +} + +const char* DeviceStateMachine::GetStateName(DeviceState state) { + if (state >= 0 && state <= kDeviceStateFatalError) { + return STATE_STRINGS[state]; + } + return STATE_STRINGS[kDeviceStateFatalError + 1]; +} + +bool DeviceStateMachine::IsValidTransition(DeviceState from, DeviceState to) const { + // Allow transition to the same state (no-op) + if (from == to) { + return true; + } + + // Define valid state transitions based on the state diagram + switch (from) { + case kDeviceStateUnknown: + // Can only go to starting + return to == kDeviceStateStarting; + + case kDeviceStateStarting: + // Can go to wifi configuring or activating + return to == kDeviceStateWifiConfiguring || + to == kDeviceStateActivating; + + case kDeviceStateWifiConfiguring: + // Can go to activating (after wifi connected) or audio testing + return to == kDeviceStateActivating || + to == kDeviceStateAudioTesting; + + case kDeviceStateAudioTesting: + // Can go back to wifi configuring + return to == kDeviceStateWifiConfiguring; + + case kDeviceStateActivating: + // Can go to upgrading, idle, or back to wifi configuring (on error) + return to == kDeviceStateUpgrading || + to == kDeviceStateIdle || + to == kDeviceStateWifiConfiguring; + + case kDeviceStateUpgrading: + // Can go to idle (upgrade failed) or activating + return to == kDeviceStateIdle || + to == kDeviceStateActivating; + + case kDeviceStateIdle: + // Can go to connecting, listening (manual mode), speaking, activating, upgrading, or wifi configuring + return to == kDeviceStateConnecting || + to == kDeviceStateListening || + to == kDeviceStateSpeaking || + to == kDeviceStateActivating || + to == kDeviceStateUpgrading || + to == kDeviceStateWifiConfiguring; + + case kDeviceStateConnecting: + // Can go to idle (failed) or listening (success) + return to == kDeviceStateIdle || + to == kDeviceStateListening; + + case kDeviceStateListening: + // Can go to speaking or idle + return to == kDeviceStateSpeaking || + to == kDeviceStateIdle; + + case kDeviceStateSpeaking: + // Can go to listening or idle + return to == kDeviceStateListening || + to == kDeviceStateIdle; + + case kDeviceStateFatalError: + // Cannot transition out of fatal error + return false; + + default: + return false; + } +} + +bool DeviceStateMachine::CanTransitionTo(DeviceState target) const { + return IsValidTransition(current_state_.load(), target); +} + +bool DeviceStateMachine::TransitionTo(DeviceState new_state) { + DeviceState old_state = current_state_.load(); + + // No-op if already in the target state + if (old_state == new_state) { + return true; + } + + // Validate transition + if (!IsValidTransition(old_state, new_state)) { + ESP_LOGW(TAG, "Invalid state transition: %s -> %s", + GetStateName(old_state), GetStateName(new_state)); + return false; + } + + // Perform transition + current_state_.store(new_state); + ESP_LOGI(TAG, "State: %s -> %s", + GetStateName(old_state), GetStateName(new_state)); + + // Notify callback + NotifyStateChange(old_state, new_state); + return true; +} + +int DeviceStateMachine::AddStateChangeListener(StateCallback callback) { + std::lock_guard lock(mutex_); + int id = next_listener_id_++; + listeners_.emplace_back(id, std::move(callback)); + return id; +} + +void DeviceStateMachine::RemoveStateChangeListener(int listener_id) { + std::lock_guard lock(mutex_); + listeners_.erase( + std::remove_if(listeners_.begin(), listeners_.end(), + [listener_id](const auto& p) { return p.first == listener_id; }), + listeners_.end()); +} + +void DeviceStateMachine::NotifyStateChange(DeviceState old_state, DeviceState new_state) { + std::vector callbacks_copy; + { + std::lock_guard lock(mutex_); + callbacks_copy.reserve(listeners_.size()); + for (const auto& [id, cb] : listeners_) { + callbacks_copy.push_back(cb); + } + } + + for (const auto& cb : callbacks_copy) { + cb(old_state, new_state); + } +} diff --git a/main/device_state_machine.h b/main/device_state_machine.h new file mode 100644 index 00000000..9566fdeb --- /dev/null +++ b/main/device_state_machine.h @@ -0,0 +1,83 @@ +#ifndef DEVICE_STATE_MACHINE_H +#define DEVICE_STATE_MACHINE_H + +#include +#include +#include +#include + +#include "device_state.h" + +/** + * DeviceStateMachine - Manages device state transitions with validation + * + * This class ensures strict state transition rules and provides a callback mechanism + * for components to react to state changes. + */ +class DeviceStateMachine { +public: + DeviceStateMachine(); + ~DeviceStateMachine() = default; + + // Delete copy constructor and assignment operator + DeviceStateMachine(const DeviceStateMachine&) = delete; + DeviceStateMachine& operator=(const DeviceStateMachine&) = delete; + + /** + * Get the current device state + */ + DeviceState GetState() const { return current_state_.load(); } + + /** + * Attempt to transition to a new state + * @param new_state The target state + * @return true if transition was successful, false if invalid transition + */ + bool TransitionTo(DeviceState new_state); + + /** + * Check if transition to target state is valid from current state + */ + bool CanTransitionTo(DeviceState target) const; + + /** + * State change callback type + * Parameters: old_state, new_state + */ + using StateCallback = std::function; + + /** + * Add a state change listener (observer pattern) + * Callback is invoked in the context of the caller of TransitionTo() + * @return listener id for removal + */ + int AddStateChangeListener(StateCallback callback); + + /** + * Remove a state change listener by id + */ + void RemoveStateChangeListener(int listener_id); + + /** + * Get state name string for logging + */ + static const char* GetStateName(DeviceState state); + +private: + std::atomic current_state_{kDeviceStateUnknown}; + std::vector> listeners_; + int next_listener_id_{0}; + std::mutex mutex_; + + /** + * Check if transition from source to target is valid + */ + bool IsValidTransition(DeviceState from, DeviceState to) const; + + /** + * Notify callback of state change + */ + void NotifyStateChange(DeviceState old_state, DeviceState new_state); +}; + +#endif // DEVICE_STATE_MACHINE_H diff --git a/main/idf_component.yml b/main/idf_component.yml index 658d51fb..863ffe64 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -18,9 +18,9 @@ dependencies: espressif/esp_io_expander_tca9554: ==2.0.0 espressif/esp_lcd_panel_io_additions: ^1.0.1 78/esp_lcd_nv3023: ~1.0.0 - 78/esp-wifi-connect: ~2.6.2 + 78/esp-wifi-connect: ~3.0.1 78/esp-opus-encoder: ~2.4.1 - 78/esp-ml307: ~3.5.1 + 78/esp-ml307: ~3.5.2 78/xiaozhi-fonts: ~1.5.5 espressif/led_strip: ~3.0.1 espressif/esp_codec_dev: ~1.5 diff --git a/main/main.cc b/main/main.cc index 7ceda913..0d4e5e1d 100755 --- a/main/main.cc +++ b/main/main.cc @@ -14,9 +14,6 @@ extern "C" void app_main(void) { - // Initialize the default event loop - ESP_ERROR_CHECK(esp_event_loop_create_default()); - // Initialize NVS flash for WiFi configuration esp_err_t ret = nvs_flash_init(); if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { @@ -26,7 +23,8 @@ extern "C" void app_main(void) } ESP_ERROR_CHECK(ret); - // Launch the application + // Initialize and run the application auto& app = Application::GetInstance(); - app.Start(); + app.Initialize(); + app.Run(); // This function runs the main event loop and never returns } diff --git a/main/mcp_server.cc b/main/mcp_server.cc index 949e5d29..f09d3a33 100644 --- a/main/mcp_server.cc +++ b/main/mcp_server.cc @@ -159,9 +159,7 @@ void McpServer::AddUserOnlyTools() { auto& app = Application::GetInstance(); app.Schedule([url, &app]() { - auto ota = std::make_unique(); - - bool success = app.UpgradeFirmware(*ota, url); + bool success = app.UpgradeFirmware(url); if (!success) { ESP_LOGE(TAG, "Firmware upgrade failed"); } diff --git a/main/ota.cc b/main/ota.cc index 0f3e29c1..768ba665 100644 --- a/main/ota.cc +++ b/main/ota.cc @@ -261,7 +261,7 @@ void Ota::MarkCurrentVersionValid() { } } -bool Ota::Upgrade(const std::string& firmware_url) { +bool Ota::Upgrade(const std::string& firmware_url, std::function callback) { ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str()); esp_ota_handle_t update_handle = 0; auto update_partition = esp_ota_get_next_update_partition(NULL); @@ -308,8 +308,8 @@ bool Ota::Upgrade(const std::string& firmware_url) { if (esp_timer_get_time() - last_calc_time >= 1000000 || ret == 0) { size_t progress = total_read * 100 / content_length; ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %uB/s", progress, total_read, content_length, recent_read); - if (upgrade_callback_) { - upgrade_callback_(progress, recent_read); + if (callback) { + callback(progress, recent_read); } last_calc_time = esp_timer_get_time(); recent_read = 0; @@ -368,14 +368,9 @@ bool Ota::Upgrade(const std::string& firmware_url) { } bool Ota::StartUpgrade(std::function callback) { - upgrade_callback_ = callback; - return Upgrade(firmware_url_); + return Upgrade(firmware_url_, callback); } -bool Ota::StartUpgradeFromUrl(const std::string& url, std::function callback) { - upgrade_callback_ = callback; - return Upgrade(url); -} std::vector Ota::ParseVersion(const std::string& version) { std::vector versionNumbers; diff --git a/main/ota.h b/main/ota.h index 45649207..1e5d6878 100644 --- a/main/ota.h +++ b/main/ota.h @@ -21,7 +21,7 @@ public: bool HasActivationCode() { return has_activation_code_; } bool HasServerTime() { return has_server_time_; } bool StartUpgrade(std::function callback); - bool StartUpgradeFromUrl(const std::string& url, std::function callback); + static bool Upgrade(const std::string& firmware_url, std::function callback); void MarkCurrentVersionValid(); const std::string& GetFirmwareVersion() const { return firmware_version_; } @@ -48,7 +48,6 @@ private: std::string serial_number_; int activation_timeout_ms_ = 30000; - bool Upgrade(const std::string& firmware_url); std::function upgrade_callback_; std::vector ParseVersion(const std::string& version); bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion); diff --git a/main/protocols/mqtt_protocol.cc b/main/protocols/mqtt_protocol.cc index 3c8fc8dd..e2dec435 100644 --- a/main/protocols/mqtt_protocol.cc +++ b/main/protocols/mqtt_protocol.cc @@ -20,8 +20,11 @@ MqttProtocol::MqttProtocol() { auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateIdle) { ESP_LOGI(TAG, "Reconnecting to MQTT server"); - app.Schedule([protocol]() { - protocol->StartMqttClient(false); + auto alive = protocol->alive_; // Capture alive flag + app.Schedule([protocol, alive]() { + if (*alive) { + protocol->StartMqttClient(false); + } }); } }, @@ -32,6 +35,10 @@ MqttProtocol::MqttProtocol() { MqttProtocol::~MqttProtocol() { ESP_LOGI(TAG, "MqttProtocol deinit"); + + // Mark as dead first to prevent any pending scheduled tasks from executing + *alive_ = false; + if (reconnect_timer_ != nullptr) { esp_timer_stop(reconnect_timer_); esp_timer_delete(reconnect_timer_); @@ -109,8 +116,11 @@ bool MqttProtocol::StartMqttClient(bool report_error) { auto session_id = cJSON_GetObjectItem(root, "session_id"); ESP_LOGI(TAG, "Received goodbye message, session_id: %s", session_id ? session_id->valuestring : "null"); if (session_id == nullptr || session_id_ == session_id->valuestring) { - Application::GetInstance().Schedule([this]() { - CloseAudioChannel(); + auto alive = alive_; // Capture alive flag + Application::GetInstance().Schedule([this, alive]() { + if (*alive) { + CloseAudioChannel(); + } }); } } else if (on_incoming_json_ != nullptr) { diff --git a/main/protocols/mqtt_protocol.h b/main/protocols/mqtt_protocol.h index 0b7b20a8..aadb9484 100644 --- a/main/protocols/mqtt_protocol.h +++ b/main/protocols/mqtt_protocol.h @@ -15,6 +15,8 @@ #include #include #include +#include +#include #define MQTT_PING_INTERVAL_SECONDS 90 #define MQTT_RECONNECT_INTERVAL_MS 60000 @@ -33,6 +35,9 @@ public: bool IsAudioChannelOpened() const override; private: + // Alive flag for safe scheduled callbacks - set to false in destructor + std::shared_ptr> alive_ = std::make_shared>(true); + EventGroupHandle_t event_group_handle_; std::string publish_topic_;