#include "wifi_board.h" #include "display.h" #include "application.h" #include "system_info.h" #include "settings.h" #include "assets/lang_config.h" #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() { // 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_); } } std::string WifiBoard::GetBoardType() { return "wifi"; } void WifiBoard::StartNetwork() { auto& wifi_manager = WifiManager::GetInstance(); // Initialize WiFi manager WifiManagerConfig config; config.ssid_prefix = "Xiaozhi"; config.language = Lang::CODE; wifi_manager.Initialize(config); // 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::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; } // Stop any ongoing connection attempt esp_timer_stop(connect_timer_); WifiManager::GetInstance().StopStation(); StartWifiConfigMode(); } bool WifiBoard::IsInWifiConfigMode() const { return WifiManager::GetInstance().IsConfigMode(); } NetworkInterface* WifiBoard::GetNetwork() { static EspNetwork network; return &network; } const char* WifiBoard::GetNetworkStateIcon() { auto& wifi = WifiManager::GetInstance(); if (wifi.IsConfigMode()) { return FONT_AWESOME_WIFI; } if (!wifi.IsConnected()) { return FONT_AWESOME_WIFI_SLASH; } int rssi = wifi.GetRssi(); if (rssi >= -60) { return FONT_AWESOME_WIFI; } else if (rssi >= -70) { return FONT_AWESOME_WIFI_FAIR; } return FONT_AWESOME_WIFI_WEAK; } std::string WifiBoard::GetBoardJson() { 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"(",)"; } json += R"("mac":")" + SystemInfo::GetMacAddress() + R"("})"; return json; } 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; } WifiManager::GetInstance().SetPowerSaveLevel(wifi_level); } std::string WifiBoard::GetDeviceStatusJson() { auto& board = Board::GetInstance(); auto root = cJSON_CreateObject(); // Audio speaker auto audio_speaker = cJSON_CreateObject(); if (auto codec = board.GetAudioCodec()) { cJSON_AddNumberToObject(audio_speaker, "volume", codec->output_volume()); } cJSON_AddItemToObject(root, "audio_speaker", audio_speaker); // Screen auto screen = cJSON_CreateObject(); if (auto backlight = board.GetBacklight()) { cJSON_AddNumberToObject(screen, "brightness", backlight->brightness()); } 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 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(); cJSON_AddStringToObject(network, "type", "wifi"); 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 temperature float temp = 0.0f; if (board.GetTemperature(temp)) { auto chip = cJSON_CreateObject(); cJSON_AddNumberToObject(chip, "temperature", temp); cJSON_AddItemToObject(root, "chip", chip); } auto str = cJSON_PrintUnformatted(root); std::string result(str); cJSON_free(str); cJSON_Delete(root); return result; }