diff --git a/main/network/udp_client.cpp b/main/network/udp_client.cpp index 7929913..804b97c 100644 --- a/main/network/udp_client.cpp +++ b/main/network/udp_client.cpp @@ -19,7 +19,7 @@ UDPClient::~UDPClient() { close(); } -esp_err_t UDPClient::init() { +esp_err_t UDPClient::init(uint16_t local_port) { if (initialized_) { ESP_LOGW(TAG, "Already initialized"); return ESP_OK; @@ -31,6 +31,23 @@ esp_err_t UDPClient::init() { return ESP_FAIL; } + // Bind to local port if specified + if (local_port > 0) { + struct sockaddr_in local_addr; + memset(&local_addr, 0, sizeof(local_addr)); + local_addr.sin_family = AF_INET; + local_addr.sin_addr.s_addr = htonl(INADDR_ANY); + local_addr.sin_port = htons(local_port); + + if (bind(sock_fd_, (struct sockaddr*)&local_addr, sizeof(local_addr)) < 0) { + ESP_LOGE(TAG, "Failed to bind to port %u: errno %d", local_port, errno); + ::close(sock_fd_); + sock_fd_ = -1; + return ESP_FAIL; + } + ESP_LOGI(TAG, "Bound to local port %u", local_port); + } + // Set socket to non-blocking mode esp_err_t err = set_nonblocking(); if (err != ESP_OK) { @@ -95,7 +112,7 @@ esp_err_t UDPClient::send_command(const std::string& command) { } ssize_t sent = sendto(sock_fd_, command.c_str(), command.length(), 0, - (struct sockaddr*)&remote_addr_, sizeof(remote_addr_)); + (struct sockaddr*)&remote_addr_, sizeof(remote_addr_)); if (sent < 0) { ESP_LOGE(TAG, "Failed to send command '%s': errno %d", command.c_str(), errno); @@ -144,7 +161,7 @@ esp_err_t UDPClient::receive_response(std::string& response, int timeout_ms) { socklen_t from_len = sizeof(from_addr); ssize_t received = recvfrom(sock_fd_, buffer, sizeof(buffer) - 1, 0, - (struct sockaddr*)&from_addr, &from_len); + (struct sockaddr*)&from_addr, &from_len); if (received < 0) { ESP_LOGE(TAG, "recvfrom() failed: errno %d", errno); diff --git a/main/network/udp_client.h b/main/network/udp_client.h index 6e9bf49..b3a284b 100644 --- a/main/network/udp_client.h +++ b/main/network/udp_client.h @@ -19,9 +19,10 @@ public: /** * @brief Initialize UDP socket + * @param local_port Local port to bind to (0 = system assigns port) * @return ESP_OK on success, error code otherwise */ - esp_err_t init(); + esp_err_t init(uint16_t local_port = 0); /** * @brief Configure remote endpoint diff --git a/main/network/web_server_handler.cpp b/main/network/web_server_handler.cpp new file mode 100644 index 0000000..f384055 --- /dev/null +++ b/main/network/web_server_handler.cpp @@ -0,0 +1,113 @@ +#include "web_server_handler.h" +#include "esp_log.h" +#include +#include +#include +#include + +static const char* TAG = "WebServerHandler"; + +WebServerHandler::WebServerHandler() { } + +WebServerHandler::~WebServerHandler() { + stop(); +} + +uint16_t WebServerHandler::start(const std::string& auth_key, uint16_t base_port) { + if (server_ != nullptr) { + ESP_LOGW(TAG, "Server already running on port %d", current_port_); + return current_port_; + } + + auth_key_ = auth_key; + + // Try to find a free port + uint16_t port = base_port; + const uint16_t max_attempts = 100; + + for (uint16_t attempt = 0; attempt < max_attempts; attempt++) { + httpd_config_t config = HTTPD_DEFAULT_CONFIG(); + config.server_port = port; + config.ctrl_port = port + 1000; // Control port + config.max_open_sockets = 7; + config.lru_purge_enable = true; + + esp_err_t err = httpd_start(&server_, &config); + + if (err == ESP_OK) { + current_port_ = port; + ESP_LOGI(TAG, "Web server started successfully on port %d", current_port_); + return current_port_; + } else if (err == ESP_ERR_HTTPD_ALLOC_MEM) { + ESP_LOGE(TAG, "Failed to allocate memory for web server"); + return 0; + } else { + // Port likely in use, try next port + ESP_LOGD(TAG, "Port %d in use, trying next port", port); + port++; + } + } + + ESP_LOGE(TAG, "Failed to find free port after %d attempts", max_attempts); + return 0; +} + +esp_err_t WebServerHandler::stop() { + if (server_ == nullptr) { + return ESP_OK; + } + + esp_err_t err = httpd_stop(server_); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to stop web server: %s", esp_err_to_name(err)); + return err; + } + + server_ = nullptr; + current_port_ = 0; + auth_key_.clear(); + + ESP_LOGI(TAG, "Web server stopped"); + return ESP_OK; +} + +esp_err_t WebServerHandler::register_uri_handler(const httpd_uri_t* uri_handler) { + if (server_ == nullptr) { + ESP_LOGE(TAG, "Server not running, cannot register URI handler"); + return ESP_ERR_INVALID_STATE; + } + + esp_err_t err = httpd_register_uri_handler(server_, uri_handler); + if (err != ESP_OK) { + ESP_LOGE(TAG, "Failed to register URI handler: %s", esp_err_to_name(err)); + return err; + } + + return ESP_OK; +} + +bool WebServerHandler::validate_auth(const char* query_string) const { + if (!query_string || auth_key_.empty()) { + return false; + } + + // Look for "auth=" in query string + const char* auth_param = strstr(query_string, "auth="); + if (!auth_param) { + return false; + } + + // Skip "auth=" + auth_param += 5; + + // Find end of auth value (& or end of string) + const char* end = strchr(auth_param, '&'); + size_t auth_len = end ? (size_t)(end - auth_param) : strlen(auth_param); + + // Compare with stored auth key + if (auth_len != auth_key_.length()) { + return false; + } + + return strncmp(auth_param, auth_key_.c_str(), auth_len) == 0; +} diff --git a/main/network/web_server_handler.h b/main/network/web_server_handler.h new file mode 100644 index 0000000..532c5e7 --- /dev/null +++ b/main/network/web_server_handler.h @@ -0,0 +1,42 @@ +#pragma once +#include "esp_http_server.h" +#include "esp_err.h" +#include +#include + +class WebServerHandler { +public: + WebServerHandler(); + ~WebServerHandler(); + + // Start web server, finds a free port starting from base_port + // Returns the actual port used, or 0 on failure + uint16_t start(const std::string& auth_key, uint16_t base_port = 8080); + + // Stop web server + esp_err_t stop(); + + // Check if server is running + bool is_running() const { return server_ != nullptr; } + + // Get the current port + uint16_t get_port() const { return current_port_; } + + // Get the auth key + std::string get_auth_key() const { return auth_key_; } + + // Register a URI handler + esp_err_t register_uri_handler(const httpd_uri_t* uri_handler); + + // Validate auth key from query string + bool validate_auth(const char* query_string) const; + +private: + // Prevent copying + WebServerHandler(const WebServerHandler&) = delete; + WebServerHandler& operator=(const WebServerHandler&) = delete; + + httpd_handle_t server_ = nullptr; + uint16_t current_port_ = 0; + std::string auth_key_; +}; diff --git a/main/network/wifi_handler.cpp b/main/network/wifi_handler.cpp index dd0ba1d..e4b5794 100644 --- a/main/network/wifi_handler.cpp +++ b/main/network/wifi_handler.cpp @@ -539,3 +539,23 @@ EventBits_t WifiHandler::wait_for_connection(TickType_t ticks_to_wait) { ticks_to_wait ); } + +std::string WifiHandler::get_current_ip() const { + esp_netif_t* netif = esp_netif_get_handle_from_ifkey("WIFI_STA_DEF"); + if (!netif) { + ESP_LOGW(TAG, "Failed to get netif handle"); + return ""; + } + + esp_netif_ip_info_t ip_info; + esp_err_t err = esp_netif_get_ip_info(netif, &ip_info); + if (err != ESP_OK) { + ESP_LOGW(TAG, "Failed to get IP info: %s", esp_err_to_name(err)); + return ""; + } + + // Convert IP address to string + char ip_str[16]; + snprintf(ip_str, sizeof(ip_str), IPSTR, IP2STR(&ip_info.ip)); + return std::string(ip_str); +} diff --git a/main/network/wifi_handler.h b/main/network/wifi_handler.h index c0dcf59..b64aef3 100644 --- a/main/network/wifi_handler.h +++ b/main/network/wifi_handler.h @@ -31,6 +31,9 @@ public: uint16_t& ap_count ); + // Get current IP address (empty string if not connected) + std::string get_current_ip() const; + static void wifi_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data); private: