xiaozhi-esp32/main/boards/common/wifi_board.cc
Xiaoxia 564018c762
Some checks are pending
Build Boards / Determine variants to build (push) Waiting to run
Build Boards / Build ${{ matrix.name }} (push) Blocked by required conditions
Enhance audio feedback mechanism by introducing a flag to play a popup (#1560)
* Enhance audio feedback mechanism by introducing a flag to play a popup sound after transitioning to listening mode. Update Schedule method to accept rvalue references for callbacks. Bump esp-ml307 component version to 3.5.3. Adjust signal strength thresholds in Ml307Board for better accuracy.

* Update esp-wifi-connect component version to 3.0.2 in idf_component.yml

* Adjust Wi-Fi signal strength thresholds in WifiBoard for improved accuracy in network state icon representation.
2025-12-15 12:54:17 +08:00

360 lines
11 KiB
C++

#include "wifi_board.h"
#include "display.h"
#include "application.h"
#include "system_info.h"
#include "settings.h"
#include "assets/lang_config.h"
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_network.h>
#include <esp_log.h>
#include <utility>
#include <font_awesome.h>
#include <wifi_manager.h>
#include <wifi_station.h>
#include <ssid_manager.h>
#include "afsk_demod.h"
#ifdef CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
#include "blufi.h"
#endif
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::Connected:
// Stop timeout timer
esp_timer_stop(connect_timer_);
#ifdef CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
// make sure blufi resources has been released
Blufi::GetInstance().deinit();
#endif
in_config_mode_ = false;
ESP_LOGI(TAG, "Connected to WiFi: %s", data.c_str());
break;
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::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<WifiBoard*>(arg);
ESP_LOGW(TAG, "WiFi connection timeout, entering config mode");
WifiManager::GetInstance().StopStation();
board->StartWifiConfigMode();
}
void WifiBoard::StartWifiConfigMode() {
in_config_mode_ = true;
// Transition to wifi configuring state
Application::GetInstance().SetDeviceState(kDeviceStateWifiConfiguring);
#ifdef CONFIG_USE_HOTSPOT_WIFI_PROVISIONING
auto& wifi_manager = WifiManager::GetInstance();
wifi_manager.StartConfigAp();
// Show config prompt after a short delay
Application::GetInstance().Schedule([&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);
});
#endif
#if CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
auto &blufi = Blufi::GetInstance();
// initialize esp-blufi protocol
blufi.init();
#endif
#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<intptr_t>(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<void*>(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<WifiBoard*>(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 >= -65) {
return FONT_AWESOME_WIFI;
} else if (rssi >= -75) {
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;
}