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
This commit is contained in:
Xiaoxia
2025-12-09 09:24:56 +08:00
committed by GitHub
parent 11c79a7003
commit b7db68457c
131 changed files with 1986 additions and 1335 deletions

View File

@ -3,6 +3,7 @@
#include <algorithm>
#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

View File

@ -6,7 +6,7 @@
#include <memory>
#include <optional>
#include <cmath>
#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);
/**

View File

@ -6,6 +6,7 @@
#include <mqtt.h>
#include <udp.h>
#include <string>
#include <functional>
#include <network_interface.h>
#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(NetworkEvent event, const std::string& data)>;
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;
};

View File

@ -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() {

View File

@ -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;
};

View File

@ -6,11 +6,19 @@
#include <esp_log.h>
#include <esp_timer.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <font_awesome.h>
#include <opus_encoder.h>
#include <utility>
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<Ml307Board*>(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() {

View File

@ -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;
};

View File

@ -10,21 +10,35 @@
#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 <wifi_configuration_ap.h>
#include <ssid_manager.h>
#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<WifiBoard*>(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<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;
}
// 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;
}

View File

@ -2,23 +2,68 @@
#define WIFI_BOARD_H
#include "board.h"
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <esp_timer.h>
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