#include "wifi_handler.h" #include "esp_wifi.h" #include "esp_event.h" #include "freertos/event_groups.h" #include "esp_log.h" #include "freertos/semphr.h" #include "common/semaphore_guard.h" static const char* TAG = "WifiHandler"; static const char* WIFI_SSID_KEY = "wifi_ssid"; static const char* WIFI_PASSWORD_KEY = "wifi_password"; 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 KVStorageHandler* kvs ) : kvs(kvs) { this->s_wifi_event_group = xEventGroupCreate(); this->scan_mutex = xSemaphoreCreateMutex(); this->connection_mutex = xSemaphoreCreateMutex(); } // Move constructor: transfer ownership of resources WifiHandler::WifiHandler(WifiHandler&& other) noexcept : initialized(other.initialized), kvs(other.kvs), s_wifi_event_group(other.s_wifi_event_group), scan_mutex(other.scan_mutex), connection_mutex(other.connection_mutex), current_ssid(other.current_ssid), expect_disconnected(other.expect_disconnected) { other.kvs = nullptr; other.initialized = false; other.s_wifi_event_group = 0; other.scan_mutex = nullptr; other.connection_mutex = nullptr; other.current_ssid = nullptr; other.expect_disconnected = false; } 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) { delete[] this->current_ssid; } vSemaphoreDelete(this->scan_mutex); vSemaphoreDelete(this->connection_mutex); } } void WifiHandler::init() { if (this->initialized) { ESP_LOGW(TAG, "Already initialized, skipping"); return; } // get WiFi credentials from KV storage if available char* ssid = nullptr; char* password = nullptr; this->get_wifi_credentials(ssid, password); if (ssid && password) { ESP_LOGI(TAG, "Found stored WiFi credentials, connecting to SSID: %s", ssid); esp_err_t 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"); } delete[] ssid; delete[] password; // TODO: setup WiFi event handlers // TODO: add auto-reconnect logic // initialized = true; } esp_err_t WifiHandler::connect(const char* ssid, const char* 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) { delete[] this->current_ssid; } size_t ssid_len = strlen(ssid); this->current_ssid = new char[ssid_len + 1]; strncpy(this->current_ssid, ssid, ssid_len + 1); this->current_ssid[ssid_len] = '\0'; // wifi_config_t wifi_config = {}; strncpy((char*)wifi_config.sta.ssid, this->current_ssid, sizeof(wifi_config.sta.ssid)); wifi_config.sta.ssid[sizeof(wifi_config.sta.ssid) - 1] = '\0'; strncpy((char*)wifi_config.sta.password, password, 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); 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 this->kvs->put(WIFI_SSID_KEY, this->current_ssid); // store password under key derived from SSID char* password_key = this->build_password_key(this->current_ssid); this->kvs->put(password_key, password); delete[] password_key; // set connected bit on successful connection xEventGroupSetBits( this->s_wifi_event_group, WIFI_CONNECTED_BIT ); return ESP_OK; } esp_err_t WifiHandler::connect(const char* ssid) { char* stored_ssid = nullptr; char* stored_password = nullptr; this->get_wifi_credentials(stored_ssid, stored_password); if (!stored_ssid || strcmp(stored_ssid, ssid) != 0) { ESP_LOGE(TAG, "No stored credentials for SSID: %s", ssid); delete[] stored_ssid; delete[] stored_password; return ESP_FAIL; } esp_err_t err = this->connect(stored_ssid, stored_password ? stored_password : ""); delete[] stored_ssid; delete[] stored_password; return err; } esp_err_t WifiHandler::reconnect() { if (!this->current_ssid) { 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(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) { ESP_LOGI(TAG, "Station started, attempting to connect to SSID: %s", self->current_ssid); self->reconnect(); } 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(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 // char* WifiHandler::build_password_key(const char* ssid) { // `{WIFI_PASSWORD_KEY}_{ssid}` size_t password_key_len = strlen(WIFI_PASSWORD_KEY) + 1 + strlen(ssid) + 1; char* password_key_buff = new char[password_key_len]; snprintf(password_key_buff, password_key_len, "%s_%s", WIFI_PASSWORD_KEY, ssid); return password_key_buff; } void WifiHandler::get_wifi_credentials(char*& ssid, char*& password) { if (!kvs) { ESP_LOGW(TAG, "KVStorageHandler not set, cannot get WiFi credentials"); return; } ssid = kvs->get(WIFI_SSID_KEY); if (!ssid) { ssid = nullptr; password = nullptr; return; } // password is from KV storage, may be nullptr char* password_key = this->build_password_key(ssid); password = kvs->get(password_key); delete[] password_key; } 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 ); }