Squash of branch setup
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;
|
||||
}
|
||||
}
|
||||
55
main/network/http_handler.h
Normal file
55
main/network/http_handler.h
Normal file
@@ -0,0 +1,55 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "esp_http_client.h"
|
||||
#include "network/wifi_handler.h"
|
||||
|
||||
// forward declare NetworkHandler to avoid circular include with network.h
|
||||
class NetworkHandler;
|
||||
|
||||
// default config values for esp_http_client_config_t
|
||||
// disable Wmissing-field-initializers warning for these structs
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
|
||||
static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG = {
|
||||
.timeout_ms = 10000,
|
||||
};
|
||||
|
||||
static const inline esp_http_client_config_t DEFAULT_HTTP_CLIENT_CONFIG_HTTPS = {
|
||||
.transport_type = HTTP_TRANSPORT_OVER_SSL,
|
||||
//
|
||||
.use_global_ca_store = true,
|
||||
.skip_cert_common_name_check = false,
|
||||
};
|
||||
#pragma GCC diagnostic pop
|
||||
|
||||
// esp http client wrapper with automatic initialization and cleanup
|
||||
class HttpHandler {
|
||||
public:
|
||||
~HttpHandler();
|
||||
|
||||
esp_err_t set_method(esp_http_client_method_t method);
|
||||
esp_err_t set_header(const char* header, const char* value);
|
||||
esp_err_t set_post_field(const char* field, size_t len);
|
||||
//
|
||||
esp_err_t perform_request();
|
||||
// buffer is allocated inside the method, caller must free it
|
||||
void get_body(
|
||||
char*& buffer,
|
||||
int& total_len
|
||||
);
|
||||
|
||||
|
||||
// only NetworkHandler can create HttpHandler instances
|
||||
friend class NetworkHandler;
|
||||
// disable copy constructor and assignment operator
|
||||
HttpHandler(const HttpHandler&) = delete;
|
||||
HttpHandler& operator=(const HttpHandler&) = delete;
|
||||
|
||||
private:
|
||||
// private constructor, only NetworkHandler can create instances
|
||||
HttpHandler(const esp_http_client_config_t&& config, WifiHandler* wifiHandler);
|
||||
|
||||
esp_http_client_handle_t client;
|
||||
// backreference to WifiHandler to ensure WiFi is connected, DO NOT DELETE
|
||||
WifiHandler* wifiHandler;
|
||||
};
|
||||
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(
|
||||
std::unique_ptr<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.get()));
|
||||
}
|
||||
|
||||
27
main/network/network.h
Normal file
27
main/network/network.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
#include <memory>
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "esp_system.h"
|
||||
#include "network/wifi_handler.h"
|
||||
#include "esp_http_client.h"
|
||||
|
||||
// forward declare HttpHandler to avoid circular include with http_handler.h
|
||||
class HttpHandler;
|
||||
|
||||
class NetworkHandler {
|
||||
public:
|
||||
NetworkHandler(
|
||||
std::unique_ptr<WifiHandler> wifiHandler
|
||||
);
|
||||
~NetworkHandler();
|
||||
|
||||
void init(EventGroupHandle_t system_event_group);
|
||||
WifiHandler& get_wifi_handler();
|
||||
// factory method to create HttpHandler instances
|
||||
std::unique_ptr<HttpHandler> get_http_handler(const esp_http_client_config_t&& config);
|
||||
|
||||
|
||||
private:
|
||||
std::unique_ptr<WifiHandler> wifiHandler;
|
||||
bool initialized = false;
|
||||
};
|
||||
172
main/network/udp_client.cpp
Normal file
172
main/network/udp_client.cpp
Normal file
@@ -0,0 +1,172 @@
|
||||
#include "udp_client.h"
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <errno.h>
|
||||
#include "esp_log.h"
|
||||
|
||||
static const char* TAG = "UDPClient";
|
||||
|
||||
UDPClient::UDPClient()
|
||||
: sock_fd_(-1)
|
||||
, remote_port_(0)
|
||||
, configured_(false)
|
||||
, initialized_(false) {
|
||||
memset(&remote_addr_, 0, sizeof(remote_addr_));
|
||||
}
|
||||
|
||||
UDPClient::~UDPClient() {
|
||||
close();
|
||||
}
|
||||
|
||||
esp_err_t UDPClient::init() {
|
||||
if (initialized_) {
|
||||
ESP_LOGW(TAG, "Already initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
sock_fd_ = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
|
||||
if (sock_fd_ < 0) {
|
||||
ESP_LOGE(TAG, "Failed to create socket: errno %d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Set socket to non-blocking mode
|
||||
esp_err_t err = set_nonblocking();
|
||||
if (err != ESP_OK) {
|
||||
::close(sock_fd_);
|
||||
sock_fd_ = -1;
|
||||
return err;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
ESP_LOGI(TAG, "UDP client initialized (fd=%d)", sock_fd_);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t UDPClient::set_nonblocking() {
|
||||
int flags = fcntl(sock_fd_, F_GETFL, 0);
|
||||
if (flags < 0) {
|
||||
ESP_LOGE(TAG, "Failed to get socket flags: errno %d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (fcntl(sock_fd_, F_SETFL, flags | O_NONBLOCK) < 0) {
|
||||
ESP_LOGE(TAG, "Failed to set non-blocking mode: errno %d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t UDPClient::configure(const std::string& ip, uint16_t port) {
|
||||
if (ip.empty() || port == 0) {
|
||||
ESP_LOGE(TAG, "Invalid IP or port");
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
struct in_addr addr;
|
||||
if (inet_pton(AF_INET, ip.c_str(), &addr) != 1) {
|
||||
ESP_LOGE(TAG, "Invalid IP address format: %s", ip.c_str());
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
|
||||
remote_addr_.sin_family = AF_INET;
|
||||
remote_addr_.sin_port = htons(port);
|
||||
remote_addr_.sin_addr = addr;
|
||||
|
||||
remote_ip_ = ip;
|
||||
remote_port_ = port;
|
||||
configured_ = true;
|
||||
|
||||
ESP_LOGI(TAG, "Configured endpoint: %s:%u", ip.c_str(), port);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t UDPClient::send_command(const std::string& command) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "Not initialized");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (!configured_) {
|
||||
ESP_LOGE(TAG, "Endpoint not configured");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ssize_t sent = sendto(sock_fd_, command.c_str(), command.length(), 0,
|
||||
(struct sockaddr*)&remote_addr_, sizeof(remote_addr_));
|
||||
|
||||
if (sent < 0) {
|
||||
ESP_LOGE(TAG, "Failed to send command '%s': errno %d", command.c_str(), errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
ESP_LOGD(TAG, "Sent command: %s (%d bytes)", command.c_str(), (int)sent);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
esp_err_t UDPClient::receive_response(std::string& response, int timeout_ms) {
|
||||
if (!initialized_) {
|
||||
ESP_LOGE(TAG, "Not initialized");
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
// Setup select() for timeout
|
||||
fd_set read_fds;
|
||||
FD_ZERO(&read_fds);
|
||||
FD_SET(sock_fd_, &read_fds);
|
||||
|
||||
struct timeval timeout;
|
||||
struct timeval* timeout_ptr = nullptr;
|
||||
|
||||
if (timeout_ms >= 0) {
|
||||
timeout.tv_sec = timeout_ms / 1000;
|
||||
timeout.tv_usec = (timeout_ms % 1000) * 1000;
|
||||
timeout_ptr = &timeout;
|
||||
}
|
||||
|
||||
int ret = select(sock_fd_ + 1, &read_fds, nullptr, nullptr, timeout_ptr);
|
||||
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "select() failed: errno %d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
if (ret == 0) {
|
||||
ESP_LOGD(TAG, "Receive timeout (%d ms)", timeout_ms);
|
||||
return ESP_ERR_TIMEOUT;
|
||||
}
|
||||
|
||||
// Data is available
|
||||
char buffer[512];
|
||||
struct sockaddr_in from_addr;
|
||||
socklen_t from_len = sizeof(from_addr);
|
||||
|
||||
ssize_t received = recvfrom(sock_fd_, buffer, sizeof(buffer) - 1, 0,
|
||||
(struct sockaddr*)&from_addr, &from_len);
|
||||
|
||||
if (received < 0) {
|
||||
ESP_LOGE(TAG, "recvfrom() failed: errno %d", errno);
|
||||
return ESP_FAIL;
|
||||
}
|
||||
|
||||
buffer[received] = '\0';
|
||||
response = std::string(buffer, received);
|
||||
|
||||
ESP_LOGD(TAG, "Received response: %s (%d bytes)", response.c_str(), (int)received);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
void UDPClient::close() {
|
||||
if (sock_fd_ >= 0) {
|
||||
::close(sock_fd_);
|
||||
ESP_LOGI(TAG, "Socket closed");
|
||||
sock_fd_ = -1;
|
||||
}
|
||||
|
||||
initialized_ = false;
|
||||
configured_ = false;
|
||||
remote_ip_.clear();
|
||||
remote_port_ = 0;
|
||||
}
|
||||
83
main/network/udp_client.h
Normal file
83
main/network/udp_client.h
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <sys/socket.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
#include "esp_err.h"
|
||||
|
||||
/**
|
||||
* @brief UDP client for sending commands and receiving responses
|
||||
*
|
||||
* Implements non-blocking UDP communication with configurable timeouts.
|
||||
* Socket remains open for the lifetime of the instance.
|
||||
*/
|
||||
class UDPClient {
|
||||
public:
|
||||
UDPClient();
|
||||
~UDPClient();
|
||||
|
||||
/**
|
||||
* @brief Initialize UDP socket
|
||||
* @return ESP_OK on success, error code otherwise
|
||||
*/
|
||||
esp_err_t init();
|
||||
|
||||
/**
|
||||
* @brief Configure remote endpoint
|
||||
* @param ip Remote IP address (e.g., "192.168.50.201")
|
||||
* @param port Remote port number (e.g., 4211)
|
||||
* @return ESP_OK on success, ESP_ERR_INVALID_ARG if IP is invalid
|
||||
*/
|
||||
esp_err_t configure(const std::string& ip, uint16_t port);
|
||||
|
||||
/**
|
||||
* @brief Send command to remote endpoint
|
||||
* @param command Command string to send (e.g., "TOGGLE", "STATUS", "MUTE", "UNMUTE")
|
||||
* @return ESP_OK on success, ESP_FAIL if not configured or send failed
|
||||
*/
|
||||
esp_err_t send_command(const std::string& command);
|
||||
|
||||
/**
|
||||
* @brief Receive response from remote endpoint (non-blocking)
|
||||
* @param response Output string for received data
|
||||
* @param timeout_ms Timeout in milliseconds (0 = no wait, -1 = wait forever)
|
||||
* @return ESP_OK on success, ESP_ERR_TIMEOUT on timeout, ESP_FAIL on error
|
||||
*/
|
||||
esp_err_t receive_response(std::string& response, int timeout_ms = 1000);
|
||||
|
||||
/**
|
||||
* @brief Check if client is configured with valid endpoint
|
||||
* @return true if IP and port are configured
|
||||
*/
|
||||
bool is_configured() const { return configured_; }
|
||||
|
||||
/**
|
||||
* @brief Get current remote IP
|
||||
*/
|
||||
std::string get_ip() const { return remote_ip_; }
|
||||
|
||||
/**
|
||||
* @brief Get current remote port
|
||||
*/
|
||||
uint16_t get_port() const { return remote_port_; }
|
||||
|
||||
/**
|
||||
* @brief Close socket and reset configuration
|
||||
*/
|
||||
void close();
|
||||
|
||||
private:
|
||||
int sock_fd_; // Socket file descriptor
|
||||
struct sockaddr_in remote_addr_; // Remote endpoint address
|
||||
std::string remote_ip_; // Remote IP address
|
||||
uint16_t remote_port_; // Remote port number
|
||||
bool configured_; // Whether endpoint is configured
|
||||
bool initialized_; // Whether socket is initialized
|
||||
|
||||
/**
|
||||
* @brief Set socket to non-blocking mode
|
||||
* @return ESP_OK on success
|
||||
*/
|
||||
esp_err_t set_nonblocking();
|
||||
};
|
||||
430
main/network/wifi_handler.cpp
Normal file
430
main/network/wifi_handler.cpp
Normal 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
|
||||
);
|
||||
}
|
||||
57
main/network/wifi_handler.h
Normal file
57
main/network/wifi_handler.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "io/io.h"
|
||||
#include "esp_wifi.h"
|
||||
#include "freertos/event_groups.h"
|
||||
|
||||
#define WIFI_STARTED_BIT (1 << 0)
|
||||
#define WIFI_CONNECTED_BIT (1 << 1)
|
||||
|
||||
|
||||
class WifiHandler {
|
||||
public:
|
||||
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
|
||||
);
|
||||
~WifiHandler();
|
||||
|
||||
esp_err_t init();
|
||||
esp_err_t connect(const std::string& ssid, const std::string& password);
|
||||
esp_err_t connect(const std::string& ssid); // connect using stored password
|
||||
esp_err_t reconnect(); // reconnect to current SSID
|
||||
void disconnect();
|
||||
EventBits_t wait_for_connection(TickType_t ticks_to_wait);
|
||||
// returns list of available networks, caller is responsible for freeing the returned memory
|
||||
// returns nullptr if scan failed
|
||||
esp_err_t scan_networks(
|
||||
wifi_ap_record_t*& ap_records,
|
||||
uint16_t& ap_count
|
||||
);
|
||||
|
||||
static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data);
|
||||
|
||||
private:
|
||||
// prevent copying
|
||||
WifiHandler(const WifiHandler&) = delete;
|
||||
WifiHandler& operator=(const WifiHandler&) = delete;
|
||||
// prevent moving
|
||||
WifiHandler(WifiHandler&& other) = delete;
|
||||
WifiHandler& operator=(WifiHandler&& other) = delete;
|
||||
|
||||
void store_wifi_credentials(const std::string& ssid, const std::string& password);
|
||||
void get_wifi_credentials(std::string& out_ssid, std::string& out_password);
|
||||
|
||||
bool initialized = false;
|
||||
std::unique_ptr<KVStorageHandler> kvs = nullptr;
|
||||
EventGroupHandle_t s_wifi_event_group = 0;
|
||||
SemaphoreHandle_t scan_mutex = nullptr;
|
||||
SemaphoreHandle_t connection_mutex = nullptr;
|
||||
SemaphoreHandle_t credential_mutex = nullptr;
|
||||
// current connected / preferred SSID
|
||||
std::string current_ssid;
|
||||
// prevent auto-reconnect on expected disconnection, e.g. when user calls disconnect()
|
||||
// should be reset to false after connect()
|
||||
bool expect_disconnected = false;
|
||||
};
|
||||
Reference in New Issue
Block a user