Squash of branch setup

This commit is contained in:
GW_MC
2026-01-27 19:15:44 +08:00
parent 64fe528abc
commit 3ce135a028
66 changed files with 10798 additions and 0 deletions

View File

@@ -0,0 +1,430 @@
#include "wifi_handler.h"
#include "esp_wifi.h"
#include "esp_event.h"
#include "esp_netif.h"
#include "freertos/event_groups.h"
#include "esp_log.h"
#include "freertos/semphr.h"
#include "common/semaphore_guard.h"
#include "cJSON.h"
static const char* TAG = "WifiHandler";
static const char* WIFI_SSID_KEY = "ssid";
static const char* WIFI_PASSWORD_STORE_KEY = "psw";
WifiHandler::WifiHandler(
// this handler is used to store/retrieve WiFi credentials
// should have a unique namespace for WiFi credentials
// it will be owned by WifiHandler and deleted in its destructor
std::unique_ptr<KVStorageHandler> kvs
) : kvs(std::move(kvs)) {
this->s_wifi_event_group = xEventGroupCreate();
if (!this->s_wifi_event_group) {
ESP_LOGE(TAG, "Failed to create WiFi event group");
}
this->scan_mutex = xSemaphoreCreateMutex();
if (!this->scan_mutex) {
ESP_LOGE(TAG, "Failed to create scan mutex");
}
this->connection_mutex = xSemaphoreCreateMutex();
if (!this->connection_mutex) {
ESP_LOGE(TAG, "Failed to create connection mutex");
}
this->credential_mutex = xSemaphoreCreateMutex();
if (!this->credential_mutex) {
ESP_LOGE(TAG, "Failed to create credential mutex");
}
if (this->kvs == nullptr) {
ESP_LOGW(TAG, "KVStorageHandler is null, WiFi credentials will not be stored");
} else {
this->kvs->init(nullptr);
}
}
WifiHandler::~WifiHandler() {
if (this->initialized) {
esp_wifi_stop();
// Check if it should be called
esp_wifi_deinit();
vEventGroupDelete(this->s_wifi_event_group);
if (!this->current_ssid.empty()) {
this->current_ssid.clear();
}
vSemaphoreDelete(this->scan_mutex);
vSemaphoreDelete(this->connection_mutex);
esp_event_handler_unregister(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiHandler::wifi_event_handler);
esp_event_handler_unregister(IP_EVENT, IP_EVENT_STA_GOT_IP, &WifiHandler::wifi_event_handler);
this->initialized = false;
// unique_ptr will automatically delete the object
this->kvs = nullptr;
}
}
esp_err_t WifiHandler::init() {
if (this->initialized) {
ESP_LOGW(TAG, "Already initialized, skipping");
return ESP_OK;
}
esp_err_t err;
// initialize TCP/IP stack and default event loop
err = esp_netif_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_netif_init failed: %s", esp_err_to_name(err));
return err;
}
err = esp_event_loop_create_default();
if (err != ESP_OK && err != ESP_ERR_INVALID_STATE) {
ESP_LOGE(TAG, "esp_event_loop_create_default failed: %s", esp_err_to_name(err));
return err;
}
// create default WiFi station
esp_netif_create_default_wifi_sta();
// init WiFi driver
wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
err = esp_wifi_init(&cfg);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_wifi_init failed: %s", esp_err_to_name(err));
return err;
}
// register event handlers for WiFi and IP events
err = esp_event_handler_register(WIFI_EVENT, ESP_EVENT_ANY_ID, &WifiHandler::wifi_event_handler, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_event_handler_register failed: %s", esp_err_to_name(err));
return err;
}
err = esp_event_handler_register(IP_EVENT, IP_EVENT_STA_GOT_IP, &WifiHandler::wifi_event_handler, this);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_event_handler_register failed: %s", esp_err_to_name(err));
return err;
}
err = esp_wifi_set_mode(WIFI_MODE_STA);
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_wifi_set_mode failed: %s", esp_err_to_name(err));
return err;
}
err = esp_wifi_start();
if (err != ESP_OK) {
ESP_LOGE(TAG, "esp_wifi_start failed: %s", esp_err_to_name(err));
return err;
}
// get WiFi credentials from KV storage if available
std::string ssid;
std::string password;
this->get_wifi_credentials(ssid, password);
// If KV storage didn't provide credentials, allow build-time injected values
// via compile-time defines BUILD_WIFI_SSID and BUILD_WIFI_PASSWORD.
#if defined(BUILD_WIFI_SSID) and defined(BUILD_WIFI_PASSWORD)
if (ssid.empty()) {
ssid = std::string(BUILD_WIFI_SSID);
ESP_LOGI(TAG, "Using build-time injected WiFi SSID");
}
if (password.empty()) {
password = std::string(BUILD_WIFI_PASSWORD);
ESP_LOGI(TAG, "Using build-time injected WiFi password");
}
#endif
if (!ssid.empty() && !password.empty()) {
ESP_LOGI(TAG, "Found stored WiFi credentials, connecting to SSID: %s", ssid.c_str());
err = this->connect(ssid, password);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to connect to stored WiFi credentials: %s", esp_err_to_name(err));
}
} else {
ESP_LOGI(TAG, "No stored WiFi credentials found, not connecting");
}
initialized = true;
return ESP_OK;
}
esp_err_t WifiHandler::connect(const std::string& ssid, const std::string& password) {
SemaphoreGuard guard(this->connection_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take connection mutex");
return ESP_FAIL;
}
expect_disconnected = false;
if (!this->current_ssid.empty()) {
this->current_ssid.clear();
}
this->current_ssid = ssid;
//
wifi_config_t wifi_config = {};
strncpy((char*)wifi_config.sta.ssid, this->current_ssid.c_str(), sizeof(wifi_config.sta.ssid));
wifi_config.sta.ssid[sizeof(wifi_config.sta.ssid) - 1] = '\0';
strncpy((char*)wifi_config.sta.password, password.c_str(), sizeof(wifi_config.sta.password));
wifi_config.sta.password[sizeof(wifi_config.sta.password) - 1] = '\0';
// set auth mode to WPA2_PSK minimum
wifi_config.sta.threshold.authmode = WIFI_AUTH_WPA2_PSK;
ESP_LOGI(TAG, "Connecting to SSID: %s", this->current_ssid.c_str());
esp_err_t err = esp_wifi_set_config(wifi_interface_t::WIFI_IF_STA, &wifi_config);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to set WiFi config: %s", esp_err_to_name(err));
return err;
}
err = esp_wifi_connect();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initiate WiFi connection: %s", esp_err_to_name(err));
return err;
}
// store credentials after successful connection attempt
this->store_wifi_credentials(this->current_ssid, password);
return ESP_OK;
}
esp_err_t WifiHandler::connect(const std::string& ssid) {
std::string stored_ssid;
std::string stored_password;
this->get_wifi_credentials(stored_ssid, stored_password);
if (stored_ssid.empty() || stored_ssid != ssid) {
ESP_LOGE(TAG, "No stored credentials for SSID: %s", ssid.c_str());
return ESP_FAIL;
}
esp_err_t err = this->connect(stored_ssid, stored_password);
return err;
}
esp_err_t WifiHandler::reconnect() {
if (this->current_ssid.empty()) {
ESP_LOGE(TAG, "No current SSID set, cannot reconnect");
return ESP_FAIL;
}
return this->connect(this->current_ssid);
}
void WifiHandler::disconnect() {
SemaphoreGuard guard(this->connection_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take connection mutex");
return;
}
expect_disconnected = true;
esp_wifi_disconnect();
xEventGroupClearBits(
this->s_wifi_event_group,
WIFI_CONNECTED_BIT
);
}
esp_err_t WifiHandler::scan_networks(
wifi_ap_record_t*& ap_records,
uint16_t& ap_count
) {
SemaphoreGuard guard(this->scan_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take scan mutex");
return ESP_FAIL;
}
ap_records = nullptr;
ap_count = 0;
// start scan
esp_err_t err = esp_wifi_scan_start(nullptr, true); // block until done
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
return err;
}
// get number of APs found
uint16_t ap_count_local = 0;
err = esp_wifi_scan_get_ap_num(&ap_count_local);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get number of APs found: %s", esp_err_to_name(err));
return err;
}
wifi_ap_record_t* ap_records_local = new wifi_ap_record_t[ap_count_local];
err = esp_wifi_scan_get_ap_records(&ap_count_local, ap_records_local);
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to get AP records: %s", esp_err_to_name(err));
delete[] ap_records_local;
return err;
}
ap_records = ap_records_local;
ap_count = ap_count_local;
return ESP_OK;
}
void WifiHandler::wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) {
WifiHandler* self = static_cast<WifiHandler*>(arg);
if (self == nullptr) {
ESP_LOGE(TAG, "wifi_event_handler received null WifiHandler pointer");
return;
}
switch (event_id) {
case WIFI_EVENT_STA_START:
// When the station starts, attempt to connect
ESP_LOGI(TAG, "WIFI_EVENT_STA_START");
if (!self->expect_disconnected && !self->current_ssid.empty()) {
ESP_LOGI(TAG, "Station started, attempting to connect to SSID: %s", self->current_ssid.c_str());
self->reconnect();
}
// set the event bit to indicate started
xEventGroupSetBits(
self->s_wifi_event_group,
WIFI_STARTED_BIT
);
break;
case WIFI_EVENT_STA_DISCONNECTED:
ESP_LOGI(TAG, "WIFI_EVENT_STA_DISCONNECTED");
if (!self->expect_disconnected) {
ESP_LOGI(TAG, "Unexpected disconnection, attempting to reconnect");
self->reconnect();
}
xEventGroupClearBits(
self->s_wifi_event_group,
WIFI_CONNECTED_BIT
);
break;
case IP_EVENT_STA_GOT_IP:
{
ip_event_got_ip_t* event = static_cast<ip_event_got_ip_t*>(event_data);
ESP_LOGI(TAG, "WIFI_EVENT_STA_GOT_IP: " IPSTR, IP2STR(&event->ip_info.ip));
xEventGroupSetBits(
self->s_wifi_event_group,
WIFI_CONNECTED_BIT
);
break;
}
default:
ESP_LOGW(TAG, "Unhandled WiFi event: %d", event_id);
break;
}
}
//
// private methods
//
void WifiHandler::store_wifi_credentials(const std::string& ssid, const std::string& password) {
if (!kvs) {
ESP_LOGW(TAG, "KVStorageHandler not set, cannot store WiFi credentials");
return;
}
SemaphoreGuard guard(this->credential_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take credential mutex");
return;
}
// store the password according to the JSON structure
std::string password_key_store = kvs->get(WIFI_PASSWORD_STORE_KEY);
cJSON* json = nullptr;
if (password_key_store.empty()) {
// create new JSON object
json = cJSON_CreateObject();
} else {
// parse existing JSON
json = cJSON_Parse(password_key_store.c_str());
if (json == nullptr) {
ESP_LOGE(TAG, "Failed to parse existing WiFi password JSON, creating new");
json = cJSON_CreateObject();
}
}
cJSON* credentials = cJSON_GetObjectItem(json, "credentials");
if (credentials == nullptr || !cJSON_IsObject(credentials)) {
// create credentials object if it doesn't exist
credentials = cJSON_CreateObject();
cJSON_AddItemToObject(json, "credentials", credentials);
}
// create SSID object
cJSON* ssid_item = cJSON_CreateObject();
// add password field
cJSON_AddStringToObject(ssid_item, "password", password.c_str());
// add SSID object to credentials
cJSON_AddItemToObject(credentials, ssid.c_str(), ssid_item);
// store updated JSON string
char* updated_json_str = cJSON_PrintUnformatted(json);
if (updated_json_str) {
kvs->put(WIFI_PASSWORD_STORE_KEY, std::string(updated_json_str));
cJSON_free(updated_json_str);
}
cJSON_Delete(json);
}
void WifiHandler::get_wifi_credentials(std::string& out_ssid, std::string& out_password) {
if (!kvs) {
ESP_LOGW(TAG, "KVStorageHandler not set, cannot get WiFi credentials");
return;
}
SemaphoreGuard guard(this->credential_mutex);
// wait up to 5 seconds to take the mutex
if (!guard.take(5000 / portTICK_PERIOD_MS)) {
ESP_LOGE(TAG, "Failed to take credential mutex");
return;
}
out_ssid = kvs->get(WIFI_SSID_KEY);
if (out_ssid.empty()) {
out_ssid = "";
out_password = "";
return;
}
// password is from KV storage, may be nullptr
std::string password_key_store = kvs->get(WIFI_PASSWORD_STORE_KEY);
if (password_key_store.empty()) {
out_password = "";
return;
}
// parse from json
cJSON* json = cJSON_Parse(password_key_store.c_str());
if (json == nullptr) {
ESP_LOGE(TAG, "Failed to parse WiFi password JSON");
out_password = "";
return;
}
cJSON* credentials = cJSON_GetObjectItem(json, "credentials");
if (credentials == nullptr || !cJSON_IsObject(credentials)) {
ESP_LOGE(TAG, "WiFi password JSON does not contain valid 'credentials' object");
out_password = "";
cJSON_Delete(json);
return;
}
// get the ssid value
cJSON* ssid_item = cJSON_GetObjectItem(credentials, out_ssid.c_str());
if (ssid_item == nullptr || !cJSON_IsObject(ssid_item)) {
ESP_LOGE(TAG, "WiFi password JSON does not contain valid SSID field for SSID: %s", out_ssid.c_str());
out_password = "";
cJSON_Delete(json);
return;
}
cJSON* password = cJSON_GetObjectItem(ssid_item, "password");
if (password == nullptr || !cJSON_IsString(password)) {
ESP_LOGE(TAG, "WiFi password JSON does not contain valid 'password' field for SSID: %s", out_ssid.c_str());
out_password = "";
cJSON_Delete(json);
return;
}
out_password = password->valuestring;
cJSON_Delete(json);
}
EventBits_t WifiHandler::wait_for_connection(TickType_t ticks_to_wait) {
return xEventGroupWaitBits(
s_wifi_event_group,
WIFI_CONNECTED_BIT,
pdFALSE,
pdTRUE,
ticks_to_wait
);
}