From a801caaae6e7a26a6981492c0675d7a0440cb8b4 Mon Sep 17 00:00:00 2001 From: GW_MC <72297530+GWMCwing@users.noreply.github.com> Date: Mon, 19 Jan 2026 20:44:28 +0800 Subject: [PATCH] feat: implement HttpHandler and WifiHandler classes for network management --- main/network/http_handler.cpp | 51 ++++++ main/network/network.cpp | 32 ++++ main/network/wifi_handler.cpp | 301 ++++++++++++++++++++++++++++++++++ 3 files changed, 384 insertions(+) create mode 100644 main/network/http_handler.cpp create mode 100644 main/network/network.cpp create mode 100644 main/network/wifi_handler.cpp diff --git a/main/network/http_handler.cpp b/main/network/http_handler.cpp new file mode 100644 index 0000000..00fd7e8 --- /dev/null +++ b/main/network/http_handler.cpp @@ -0,0 +1,51 @@ +#include "network/http_handler.h" +#include "esp_http_client.h" +#include "esp_log.h" +#include "string.h" + +HttpHandler::HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler) + : wifiHandler(wifiHandler) { + this->client = esp_http_client_init(&config); +} + +HttpHandler::~HttpHandler() { + if (this->client) { + esp_http_client_cleanup(this->client); + } +} + +esp_err_t HttpHandler::set_method(esp_http_client_method_t method) { + return esp_http_client_set_method(this->client, method); +} + +esp_err_t HttpHandler::set_header(const char* header, const char* value) { + return esp_http_client_set_header(this->client, header, value); +} + +esp_err_t HttpHandler::set_post_field(const char* field, size_t len) { + return esp_http_client_set_post_field(this->client, field, len); +} + +esp_err_t HttpHandler::perform_request() { + return esp_http_client_perform(this->client); +} + +void HttpHandler::get_body( + char*& buffer, + int& total_len +) { + total_len = esp_http_client_get_content_length(this->client); + buffer = new char[total_len + 1]; // +1 for null-terminator + if (buffer) { + int read_len = esp_http_client_read(this->client, buffer, total_len); + if (read_len >= 0) { + buffer[read_len] = '\0'; // null-terminate + } else { + delete[] buffer; + buffer = nullptr; + total_len = 0; + } + } else { + total_len = 0; + } +} diff --git a/main/network/network.cpp b/main/network/network.cpp new file mode 100644 index 0000000..432d6a7 --- /dev/null +++ b/main/network/network.cpp @@ -0,0 +1,32 @@ +#include "esp_log.h" +#include "network/network.h" +#include "network/http_handler.h" +#include "common/constants.h" + +NetworkHandler::NetworkHandler( + WifiHandler&& wifiHandler +) : wifiHandler(std::move(wifiHandler)) { } + +NetworkHandler::~NetworkHandler() { } + +void NetworkHandler::init(EventGroupHandle_t system_event_group) { + if (this->initialized) { + ESP_LOGW("NetworkHandler", "Already initialized, skipping"); + return; + } + this->wifiHandler.init(); + this->initialized = true; + xEventGroupSetBits( + system_event_group, + NETWORK_READY_BIT + ); +} + +WifiHandler& NetworkHandler::get_wifi_handler() { + return this->wifiHandler; +} + +std::unique_ptr NetworkHandler::get_http_handler(const esp_http_client_config_t&& config) { + return std::unique_ptr(new HttpHandler(std::move(config), &this->wifiHandler)); +} + diff --git a/main/network/wifi_handler.cpp b/main/network/wifi_handler.cpp new file mode 100644 index 0000000..4a13241 --- /dev/null +++ b/main/network/wifi_handler.cpp @@ -0,0 +1,301 @@ +#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 + ); +}