feat: implement HttpHandler and WifiHandler classes for network management
This commit is contained in:
51
main/network/http_handler.cpp
Normal file
51
main/network/http_handler.cpp
Normal file
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
32
main/network/network.cpp
Normal file
32
main/network/network.cpp
Normal file
@@ -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<HttpHandler> NetworkHandler::get_http_handler(const esp_http_client_config_t&& config) {
|
||||||
|
return std::unique_ptr<HttpHandler>(new HttpHandler(std::move(config), &this->wifiHandler));
|
||||||
|
}
|
||||||
|
|
||||||
301
main/network/wifi_handler.cpp
Normal file
301
main/network/wifi_handler.cpp
Normal file
@@ -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<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) {
|
||||||
|
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<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
|
||||||
|
//
|
||||||
|
|
||||||
|
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
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user