From 683162dff9b70550c5c3280cd2558d158b5de72e Mon Sep 17 00:00:00 2001 From: "deepsource-autofix[bot]" <62050782+deepsource-autofix[bot]@users.noreply.github.com> Date: Mon, 3 Nov 2025 19:45:52 +0000 Subject: [PATCH] style: format code with ClangFormat This commit fixes the style issues introduced in f891924 according to the output from ClangFormat. Details: None --- src/NonBlockingTimer.h | 120 +++--- src/StateManager.h | 110 +++--- src/adaptive_buffer.cpp | 186 +++++----- src/adaptive_buffer.h | 38 +- src/config.h | 33 +- src/config_validator.h | 683 ++++++++++++++++++----------------- src/debug_mode.cpp | 43 +-- src/debug_mode.h | 38 +- src/i2s_audio.cpp | 364 +++++++++---------- src/i2s_audio.h | 46 +-- src/logger.cpp | 139 +++---- src/logger.h | 36 +- src/main.cpp | 761 +++++++++++++++++++------------------- src/network.cpp | 783 +++++++++++++++++++--------------------- src/network.h | 142 ++++---- src/serial_command.cpp | 494 ++++++++++++------------- src/serial_command.h | 38 +- 17 files changed, 1994 insertions(+), 2060 deletions(-) diff --git a/src/NonBlockingTimer.h b/src/NonBlockingTimer.h index 33d2cce..63a3fde 100644 --- a/src/NonBlockingTimer.h +++ b/src/NonBlockingTimer.h @@ -3,94 +3,68 @@ #include -class NonBlockingTimer -{ +class NonBlockingTimer { private: - unsigned long previousMillis; - unsigned long interval; - bool isRunning; - bool autoReset; + unsigned long previousMillis; + unsigned long interval; + bool isRunning; + bool autoReset; public: - NonBlockingTimer(unsigned long intervalMs = 1000, bool autoResetEnabled = true) - : previousMillis(0), interval(intervalMs), isRunning(false), autoReset(autoResetEnabled) {} + NonBlockingTimer(unsigned long intervalMs = 1000, + bool autoResetEnabled = true) + : previousMillis(0), interval(intervalMs), isRunning(false), + autoReset(autoResetEnabled) {} - void setInterval(unsigned long intervalMs) - { - interval = intervalMs; - } + void setInterval(unsigned long intervalMs) { interval = intervalMs; } - void start() - { - previousMillis = millis(); - isRunning = true; - } + void start() { + previousMillis = millis(); + isRunning = true; + } - void startExpired() - { - // Start timer in already-expired state for immediate first trigger - previousMillis = millis() - interval - 1; - isRunning = true; - } + void startExpired() { + // Start timer in already-expired state for immediate first trigger + previousMillis = millis() - interval - 1; + isRunning = true; + } - void stop() - { - isRunning = false; - } + void stop() { isRunning = false; } - void reset() - { - previousMillis = millis(); - } + void reset() { previousMillis = millis(); } - bool check() - { - if (!isRunning) - return false; - - unsigned long currentMillis = millis(); - if (currentMillis - previousMillis >= interval) - { - if (autoReset) - { - previousMillis = currentMillis; - } - else - { - isRunning = false; - } - return true; - } - return false; - } + bool check() { + if (!isRunning) + return false; - bool isExpired() - { - if (!isRunning) - return false; - return (millis() - previousMillis >= interval); + unsigned long currentMillis = millis(); + if (currentMillis - previousMillis >= interval) { + if (autoReset) { + previousMillis = currentMillis; + } else { + isRunning = false; + } + return true; } + return false; + } - unsigned long getElapsed() - { - return millis() - previousMillis; - } + bool isExpired() { + if (!isRunning) + return false; + return (millis() - previousMillis >= interval); + } - unsigned long getRemaining() - { - unsigned long elapsed = getElapsed(); - return (elapsed >= interval) ? 0 : (interval - elapsed); - } + unsigned long getElapsed() { return millis() - previousMillis; } - bool getIsRunning() const - { - return isRunning; - } + unsigned long getRemaining() { + unsigned long elapsed = getElapsed(); + return (elapsed >= interval) ? 0 : (interval - elapsed); + } - unsigned long getInterval() const - { - return interval; - } + bool getIsRunning() const { return isRunning; } + + unsigned long getInterval() const { return interval; } }; #endif \ No newline at end of file diff --git a/src/StateManager.h b/src/StateManager.h index c935457..6f2cd60 100644 --- a/src/StateManager.h +++ b/src/StateManager.h @@ -5,79 +5,77 @@ #include enum class SystemState { - INITIALIZING, - CONNECTING_WIFI, - CONNECTING_SERVER, - CONNECTED, - DISCONNECTED, - ERROR, - MAINTENANCE + INITIALIZING, + CONNECTING_WIFI, + CONNECTING_SERVER, + CONNECTED, + DISCONNECTED, + ERROR, + MAINTENANCE }; class StateManager { private: - SystemState currentState; - SystemState previousState; - unsigned long stateEnterTime; - std::function stateChangeCallback; + SystemState currentState; + SystemState previousState; + unsigned long stateEnterTime; + std::function stateChangeCallback; public: - StateManager() : currentState(SystemState::INITIALIZING), - previousState(SystemState::INITIALIZING), - stateEnterTime(millis()) {} + StateManager() + : currentState(SystemState::INITIALIZING), + previousState(SystemState::INITIALIZING), stateEnterTime(millis()) {} - void setState(SystemState newState) { - if (newState != currentState) { - previousState = currentState; - currentState = newState; - stateEnterTime = millis(); - - if (stateChangeCallback) { - stateChangeCallback(previousState, currentState); - } - } - } + void setState(SystemState newState) { + if (newState != currentState) { + previousState = currentState; + currentState = newState; + stateEnterTime = millis(); - SystemState getState() const { - return currentState; + if (stateChangeCallback) { + stateChangeCallback(previousState, currentState); + } } + } - SystemState getPreviousState() const { - return previousState; - } + SystemState getState() const { return currentState; } - unsigned long getStateDuration() const { - return millis() - stateEnterTime; - } + SystemState getPreviousState() const { return previousState; } - bool isInState(SystemState state) const { - return currentState == state; - } + unsigned long getStateDuration() const { return millis() - stateEnterTime; } - bool hasStateTimedOut(unsigned long timeoutMs) const { - return getStateDuration() >= timeoutMs; - } + bool isInState(SystemState state) const { return currentState == state; } - void onStateChange(std::function callback) { - stateChangeCallback = callback; - } + bool hasStateTimedOut(unsigned long timeoutMs) const { + return getStateDuration() >= timeoutMs; + } - String stateToString(SystemState state) const { - switch (state) { - case SystemState::INITIALIZING: return "INITIALIZING"; - case SystemState::CONNECTING_WIFI: return "CONNECTING_WIFI"; - case SystemState::CONNECTING_SERVER: return "CONNECTING_SERVER"; - case SystemState::CONNECTED: return "CONNECTED"; - case SystemState::DISCONNECTED: return "DISCONNECTED"; - case SystemState::ERROR: return "ERROR"; - case SystemState::MAINTENANCE: return "MAINTENANCE"; - default: return "UNKNOWN"; - } - } + void onStateChange(std::function callback) { + stateChangeCallback = callback; + } - String getCurrentStateString() const { - return stateToString(currentState); + String stateToString(SystemState state) const { + switch (state) { + case SystemState::INITIALIZING: + return "INITIALIZING"; + case SystemState::CONNECTING_WIFI: + return "CONNECTING_WIFI"; + case SystemState::CONNECTING_SERVER: + return "CONNECTING_SERVER"; + case SystemState::CONNECTED: + return "CONNECTED"; + case SystemState::DISCONNECTED: + return "DISCONNECTED"; + case SystemState::ERROR: + return "ERROR"; + case SystemState::MAINTENANCE: + return "MAINTENANCE"; + default: + return "UNKNOWN"; } + } + + String getCurrentStateString() const { return stateToString(currentState); } }; #endif \ No newline at end of file diff --git a/src/adaptive_buffer.cpp b/src/adaptive_buffer.cpp index 9b3f526..82f7163 100644 --- a/src/adaptive_buffer.cpp +++ b/src/adaptive_buffer.cpp @@ -8,127 +8,105 @@ int32_t AdaptiveBuffer::last_rssi = -100; uint32_t AdaptiveBuffer::adjustment_count = 0; unsigned long AdaptiveBuffer::last_adjustment_time = 0; -void AdaptiveBuffer::initialize(size_t base_size) -{ - base_buffer_size = base_size; - current_buffer_size = base_size; - LOG_INFO("Adaptive Buffer initialized with base size: %u bytes", base_size); +void AdaptiveBuffer::initialize(size_t base_size) { + base_buffer_size = base_size; + current_buffer_size = base_size; + LOG_INFO("Adaptive Buffer initialized with base size: %u bytes", base_size); } -size_t AdaptiveBuffer::calculateBufferSize(int32_t rssi) -{ - // RSSI-based buffer sizing for network reliability - // Design principle: Weak signal = more packet loss/retransmission = need LARGER buffers - // Strong signal = reliable transmission = can use smaller buffers to save RAM - // - // RSSI to buffer size mapping (inversely proportional to signal strength): - // Strong signal (-50 to -60): 50% = base_size * 0.5 (reliable, minimal buffering) - // Good signal (-60 to -70): 75% = base_size * 0.75 - // Acceptable (-70 to -80): 100% = base_size (normal operation) - // Weak (-80 to -90): 120% = base_size * 1.2 (needs extra buffering) - // Very weak (<-90): 150% = base_size * 1.5 (maximum buffering for reliability) - // - // Note: We cap at 150% to avoid excessive memory usage on long-term weak signals - - size_t new_size; - - if (rssi >= -60) - { - // Strong signal - can use smaller buffer to save RAM - new_size = (base_buffer_size * 50) / 100; - } - else if (rssi >= -70) - { - // Good signal - 75% buffer - new_size = (base_buffer_size * 75) / 100; - } - else if (rssi >= -80) - { - // Acceptable signal - use full buffer - new_size = base_buffer_size; - } - else if (rssi >= -90) - { - // Weak signal - INCREASE buffer to absorb jitter - new_size = (base_buffer_size * 120) / 100; - } - else - { - // Very weak signal - MAXIMIZE buffer for reliability - new_size = (base_buffer_size * 150) / 100; - } - - // Ensure minimum size (256 bytes) - if (new_size < 256) - { - new_size = 256; - } - - return new_size; +size_t AdaptiveBuffer::calculateBufferSize(int32_t rssi) { + // RSSI-based buffer sizing for network reliability + // Design principle: Weak signal = more packet loss/retransmission = need + // LARGER buffers Strong signal = reliable transmission = can use smaller + // buffers to save RAM + // + // RSSI to buffer size mapping (inversely proportional to signal strength): + // Strong signal (-50 to -60): 50% = base_size * 0.5 (reliable, minimal + // buffering) Good signal (-60 to -70): 75% = base_size * 0.75 + // Acceptable (-70 to -80): 100% = base_size (normal operation) + // Weak (-80 to -90): 120% = base_size * 1.2 (needs extra + // buffering) Very weak (<-90): 150% = base_size * 1.5 (maximum + // buffering for reliability) + // + // Note: We cap at 150% to avoid excessive memory usage on long-term weak + // signals + + size_t new_size; + + if (rssi >= -60) { + // Strong signal - can use smaller buffer to save RAM + new_size = (base_buffer_size * 50) / 100; + } else if (rssi >= -70) { + // Good signal - 75% buffer + new_size = (base_buffer_size * 75) / 100; + } else if (rssi >= -80) { + // Acceptable signal - use full buffer + new_size = base_buffer_size; + } else if (rssi >= -90) { + // Weak signal - INCREASE buffer to absorb jitter + new_size = (base_buffer_size * 120) / 100; + } else { + // Very weak signal - MAXIMIZE buffer for reliability + new_size = (base_buffer_size * 150) / 100; + } + + // Ensure minimum size (256 bytes) + if (new_size < 256) { + new_size = 256; + } + + return new_size; } -void AdaptiveBuffer::updateBufferSize(int32_t rssi) -{ - last_rssi = rssi; +void AdaptiveBuffer::updateBufferSize(int32_t rssi) { + last_rssi = rssi; - // Only adjust if minimum interval passed (5 seconds) - unsigned long now = millis(); - if (now - last_adjustment_time < 5000) - { - return; - } + // Only adjust if minimum interval passed (5 seconds) + unsigned long now = millis(); + if (now - last_adjustment_time < 5000) { + return; + } - size_t new_size = calculateBufferSize(rssi); + size_t new_size = calculateBufferSize(rssi); - // Only log if size changed significantly (>10%) - if (new_size != current_buffer_size) - { - int change_pct = ((int)new_size - (int)current_buffer_size) * 100 / (int)current_buffer_size; + // Only log if size changed significantly (>10%) + if (new_size != current_buffer_size) { + int change_pct = ((int)new_size - (int)current_buffer_size) * 100 / + (int)current_buffer_size; - if (abs(change_pct) >= 10) - { - LOG_DEBUG("Buffer size adjusted: %u → %u bytes (%d%%) for RSSI %d dBm", - current_buffer_size, new_size, change_pct, rssi); + if (abs(change_pct) >= 10) { + LOG_DEBUG("Buffer size adjusted: %u → %u bytes (%d%%) for RSSI %d dBm", + current_buffer_size, new_size, change_pct, rssi); - current_buffer_size = new_size; - adjustment_count++; - last_adjustment_time = now; - } + current_buffer_size = new_size; + adjustment_count++; + last_adjustment_time = now; } + } } -size_t AdaptiveBuffer::getBufferSize() -{ - return current_buffer_size; -} +size_t AdaptiveBuffer::getBufferSize() { return current_buffer_size; } -uint8_t AdaptiveBuffer::getEfficiencyScore() -{ - // Score based on how close buffer size is to optimal for current signal - // 100 = perfect match, capped at 100 to prevent overflow - // - // This calculates what percentage of the optimal buffer we're currently using. - // Higher is better - means we're well-matched to current network conditions. +uint8_t AdaptiveBuffer::getEfficiencyScore() { + // Score based on how close buffer size is to optimal for current signal + // 100 = perfect match, capped at 100 to prevent overflow + // + // This calculates what percentage of the optimal buffer we're currently + // using. Higher is better - means we're well-matched to current network + // conditions. - size_t optimal_size = calculateBufferSize(last_rssi); + size_t optimal_size = calculateBufferSize(last_rssi); - if (optimal_size == 0) - { - return 0; // Safety check - } + if (optimal_size == 0) { + return 0; // Safety check + } - uint16_t raw_score = (current_buffer_size * 100) / optimal_size; + uint16_t raw_score = (current_buffer_size * 100) / optimal_size; - // Cap at 100 to prevent overflow in uint8_t and to reflect perfection - return (raw_score > 100) ? 100 : (uint8_t)raw_score; + // Cap at 100 to prevent overflow in uint8_t and to reflect perfection + return (raw_score > 100) ? 100 : (uint8_t)raw_score; } -int32_t AdaptiveBuffer::getLastRSSI() -{ - return last_rssi; -} +int32_t AdaptiveBuffer::getLastRSSI() { return last_rssi; } -uint32_t AdaptiveBuffer::getAdjustmentCount() -{ - return adjustment_count; -} +uint32_t AdaptiveBuffer::getAdjustmentCount() { return adjustment_count; } diff --git a/src/adaptive_buffer.h b/src/adaptive_buffer.h index 3b5e2f5..5cf0626 100644 --- a/src/adaptive_buffer.h +++ b/src/adaptive_buffer.h @@ -6,31 +6,31 @@ // Adaptive buffer sizing based on WiFi signal strength class AdaptiveBuffer { public: - // Initialize with base buffer size - static void initialize(size_t base_size = 4096); + // Initialize with base buffer size + static void initialize(size_t base_size = 4096); - // Update buffer size based on WiFi RSSI - static void updateBufferSize(int32_t rssi); + // Update buffer size based on WiFi RSSI + static void updateBufferSize(int32_t rssi); - // Get current buffer size - static size_t getBufferSize(); + // Get current buffer size + static size_t getBufferSize(); - // Get buffer efficiency score (0-100) - static uint8_t getEfficiencyScore(); + // Get buffer efficiency score (0-100) + static uint8_t getEfficiencyScore(); - // Get statistics - static int32_t getLastRSSI(); - static uint32_t getAdjustmentCount(); + // Get statistics + static int32_t getLastRSSI(); + static uint32_t getAdjustmentCount(); private: - static size_t base_buffer_size; - static size_t current_buffer_size; - static int32_t last_rssi; - static uint32_t adjustment_count; - static unsigned long last_adjustment_time; - - // Calculate appropriate buffer size for signal strength - static size_t calculateBufferSize(int32_t rssi); + static size_t base_buffer_size; + static size_t current_buffer_size; + static int32_t last_rssi; + static uint32_t adjustment_count; + static unsigned long last_adjustment_time; + + // Calculate appropriate buffer size for signal strength + static size_t calculateBufferSize(int32_t rssi); }; #endif // ADAPTIVE_BUFFER_H diff --git a/src/config.h b/src/config.h index f9519ce..52a11c6 100644 --- a/src/config.h +++ b/src/config.h @@ -22,16 +22,20 @@ #define SERVER_RECONNECT_MIN 5000 // milliseconds #define SERVER_RECONNECT_MAX 60000 // milliseconds #define SERVER_BACKOFF_JITTER_PCT 20 // percent jitter on backoff (0-100) -#define TCP_WRITE_TIMEOUT 5000 // milliseconds - timeout for send operations -#define TCP_RECEIVE_TIMEOUT 10000 // milliseconds - timeout for receive operations (primarily for protocol compliance) +#define TCP_WRITE_TIMEOUT 5000 // milliseconds - timeout for send operations +#define TCP_RECEIVE_TIMEOUT \ + 10000 // milliseconds - timeout for receive operations (primarily for protocol + // compliance) -// TCP chunk size MUST match server's TCP_CHUNK_SIZE expectation for proper streaming -// Server (receiver.py) expects 19200 bytes per chunk: +// TCP chunk size MUST match server's TCP_CHUNK_SIZE expectation for proper +// streaming Server (receiver.py) expects 19200 bytes per chunk: // - 9600 samples × 2 bytes/sample = 19200 bytes // - Duration: 9600 samples ÷ 16000 Hz = 0.6 seconds = 600ms of audio // - Data rate: 19200 bytes ÷ 0.6 sec = 32000 bytes/sec = 32 KB/sec -// This aligns with server's SO_RCVBUF=65536 and socket receive loop optimization -#define TCP_CHUNK_SIZE 19200 // bytes per write() chunk - MUST match server receiver.py +// This aligns with server's SO_RCVBUF=65536 and socket receive loop +// optimization +#define TCP_CHUNK_SIZE \ + 19200 // bytes per write() chunk - MUST match server receiver.py // ===== Board Detection ===== #ifdef ARDUINO_SEEED_XIAO_ESP32S3 @@ -73,15 +77,19 @@ #define STATS_PRINT_INTERVAL 300000 // 5 minutes // ===== System Initialization & Timeouts ===== -#define SERIAL_INIT_DELAY 1000 // milliseconds - delay after serial init -#define GRACEFUL_SHUTDOWN_DELAY 100 // milliseconds - delay between shutdown steps -#define ERROR_RECOVERY_DELAY 5000 // milliseconds - delay before recovery attempt -#define TASK_YIELD_DELAY 1 // milliseconds - delay in main loop for background tasks +#define SERIAL_INIT_DELAY 1000 // milliseconds - delay after serial init +#define GRACEFUL_SHUTDOWN_DELAY \ + 100 // milliseconds - delay between shutdown steps +#define ERROR_RECOVERY_DELAY \ + 5000 // milliseconds - delay before recovery attempt +#define TASK_YIELD_DELAY \ + 1 // milliseconds - delay in main loop for background tasks // ===== TCP Keepalive Configuration ===== #define TCP_KEEPALIVE_IDLE 5 // seconds - idle time before keepalive probe #define TCP_KEEPALIVE_INTERVAL 5 // seconds - interval between keepalive probes -#define TCP_KEEPALIVE_COUNT 3 // count - number of keepalive probes before disconnect +#define TCP_KEEPALIVE_COUNT \ + 3 // count - number of keepalive probes before disconnect // ===== Logger Configuration ===== #define LOGGER_BUFFER_SIZE 256 // bytes - circular buffer for log messages @@ -89,7 +97,8 @@ #define LOGGER_BURST_MAX 60 // maximum burst of logs allowed // ===== Watchdog Configuration ===== -#define WATCHDOG_TIMEOUT_SEC 60 // seconds - watchdog timeout (aligned with connection operations) +#define WATCHDOG_TIMEOUT_SEC \ + 60 // seconds - watchdog timeout (aligned with connection operations) // ===== Task Priorities ===== #define TASK_PRIORITY_HIGH 5 // reserved for critical tasks diff --git a/src/config_validator.h b/src/config_validator.h index 16937f0..0b71e58 100644 --- a/src/config_validator.h +++ b/src/config_validator.h @@ -12,337 +12,366 @@ */ class ConfigValidator { public: - /** - * Validate all critical configuration parameters - * @return true if all validations passed, false if critical validation failed - */ - static bool validateAll() { - bool all_valid = true; - - LOG_INFO("=== Starting Configuration Validation ==="); - - // Validate WiFi configuration - if (!validateWiFiConfig()) { - all_valid = false; - } - - // Validate server configuration - if (!validateServerConfig()) { - all_valid = false; - } - - // Validate I2S configuration - if (!validateI2SConfig()) { - all_valid = false; - } - - // Validate timing configuration - if (!validateTimingConfig()) { - all_valid = false; - } - - // Validate memory thresholds - if (!validateMemoryThresholds()) { - all_valid = false; - } - - // Validate watchdog configuration - if (!validateWatchdogConfig()) { - all_valid = false; - } - - if (all_valid) { - LOG_INFO("✓ All configuration validations passed"); - } else { - LOG_CRITICAL("✗ Configuration validation FAILED - critical settings missing"); - } - - LOG_INFO("=== Configuration Validation Complete ==="); - - return all_valid; + /** + * Validate all critical configuration parameters + * @return true if all validations passed, false if critical validation failed + */ + static bool validateAll() { + bool all_valid = true; + + LOG_INFO("=== Starting Configuration Validation ==="); + + // Validate WiFi configuration + if (!validateWiFiConfig()) { + all_valid = false; + } + + // Validate server configuration + if (!validateServerConfig()) { + all_valid = false; + } + + // Validate I2S configuration + if (!validateI2SConfig()) { + all_valid = false; + } + + // Validate timing configuration + if (!validateTimingConfig()) { + all_valid = false; + } + + // Validate memory thresholds + if (!validateMemoryThresholds()) { + all_valid = false; + } + + // Validate watchdog configuration + if (!validateWatchdogConfig()) { + all_valid = false; + } + + if (all_valid) { + LOG_INFO("✓ All configuration validations passed"); + } else { + LOG_CRITICAL( + "✗ Configuration validation FAILED - critical settings missing"); } + LOG_INFO("=== Configuration Validation Complete ==="); + + return all_valid; + } + private: - /** - * Validate WiFi configuration - * Checks: SSID and password not empty - */ - static bool validateWiFiConfig() { - bool valid = true; - - LOG_INFO("Checking WiFi configuration..."); - - // Check SSID - if (strlen(WIFI_SSID) == 0) { - LOG_ERROR("WiFi SSID is empty - must configure WIFI_SSID in config.h"); - valid = false; - } else { - LOG_INFO(" ✓ WiFi SSID configured"); - } - - // Check password - if (strlen(WIFI_PASSWORD) == 0) { - LOG_ERROR("WiFi password is empty - must configure WIFI_PASSWORD in config.h"); - valid = false; - } else { - LOG_INFO(" ✓ WiFi password configured"); - } - - // Validate retry parameters - if (WIFI_RETRY_DELAY <= 0) { - LOG_WARN("WIFI_RETRY_DELAY is 0 or negative - using default 500ms"); - } else { - LOG_INFO(" ✓ WiFi retry delay: %u ms", WIFI_RETRY_DELAY); - } - - if (WIFI_MAX_RETRIES <= 0) { - LOG_WARN("WIFI_MAX_RETRIES is 0 or negative - using default 20"); - } else { - LOG_INFO(" ✓ WiFi max retries: %u", WIFI_MAX_RETRIES); - } - - if (WIFI_TIMEOUT <= 0) { - LOG_WARN("WIFI_TIMEOUT is 0 or negative - using default 30000ms"); - } else { - LOG_INFO(" ✓ WiFi timeout: %u ms", WIFI_TIMEOUT); - } - - return valid; - } - - /** - * Validate server configuration - * Checks: HOST and PORT not empty, valid port number - */ - static bool validateServerConfig() { - bool valid = true; - - LOG_INFO("Checking server configuration..."); - - // Check HOST - if (strlen(SERVER_HOST) == 0) { - LOG_ERROR("Server HOST is empty - must configure SERVER_HOST in config.h"); - valid = false; - } else { - LOG_INFO(" ✓ Server HOST configured: %s", SERVER_HOST); - } - - // Check PORT - if (SERVER_PORT <= 0 || SERVER_PORT > 65535) { - LOG_ERROR("Server PORT (%d) is invalid - must be 1-65535", SERVER_PORT); - valid = false; - } else { - LOG_INFO(" ✓ Server PORT configured: %d", SERVER_PORT); - } - - // Validate reconnection timeouts - if (SERVER_RECONNECT_MIN <= 0) { - LOG_WARN("SERVER_RECONNECT_MIN is %u ms - should be > 0", SERVER_RECONNECT_MIN); - } else if (SERVER_RECONNECT_MIN < 1000) { - LOG_WARN("SERVER_RECONNECT_MIN (%u ms) is very short - minimum recommended is 1000ms", SERVER_RECONNECT_MIN); - } else { - LOG_INFO(" ✓ Server reconnect min: %u ms", SERVER_RECONNECT_MIN); - } - - if (SERVER_RECONNECT_MAX <= 0) { - LOG_WARN("SERVER_RECONNECT_MAX is %u ms - should be > 0", SERVER_RECONNECT_MAX); - } else if (SERVER_RECONNECT_MAX < SERVER_RECONNECT_MIN) { - LOG_ERROR("SERVER_RECONNECT_MAX (%u ms) cannot be less than SERVER_RECONNECT_MIN (%u ms)", - SERVER_RECONNECT_MAX, SERVER_RECONNECT_MIN); - valid = false; - } else { - LOG_INFO(" ✓ Server reconnect max: %u ms", SERVER_RECONNECT_MAX); - } - - if (TCP_WRITE_TIMEOUT <= 0) { - LOG_WARN("TCP_WRITE_TIMEOUT is %u ms - should be > 0", TCP_WRITE_TIMEOUT); - } else if (TCP_WRITE_TIMEOUT < 1000) { - LOG_WARN("TCP_WRITE_TIMEOUT (%u ms) is very short", TCP_WRITE_TIMEOUT); - } else { - LOG_INFO(" ✓ TCP write timeout: %u ms", TCP_WRITE_TIMEOUT); - } - - return valid; - } - - /** - * Validate I2S configuration - * Checks: Valid sample rate, buffer sizes, DMA parameters - */ - static bool validateI2SConfig() { - bool valid = true; - - LOG_INFO("Checking I2S configuration..."); - - if (I2S_SAMPLE_RATE <= 0) { - LOG_ERROR("I2S_SAMPLE_RATE must be > 0, got %u", I2S_SAMPLE_RATE); - valid = false; - } else if (I2S_SAMPLE_RATE < 8000 || I2S_SAMPLE_RATE > 48000) { - LOG_WARN("I2S_SAMPLE_RATE (%u Hz) outside typical range (8000-48000)", I2S_SAMPLE_RATE); - } else { - LOG_INFO(" ✓ I2S sample rate: %u Hz", I2S_SAMPLE_RATE); - } - - if (I2S_BUFFER_SIZE <= 0) { - LOG_ERROR("I2S_BUFFER_SIZE must be > 0, got %u", I2S_BUFFER_SIZE); - valid = false; - } else if ((I2S_BUFFER_SIZE & (I2S_BUFFER_SIZE - 1)) != 0) { - LOG_WARN("I2S_BUFFER_SIZE (%u) is not a power of 2", I2S_BUFFER_SIZE); - } else { - LOG_INFO(" ✓ I2S buffer size: %u bytes", I2S_BUFFER_SIZE); - } - - if (I2S_DMA_BUF_COUNT <= 0) { - LOG_ERROR("I2S_DMA_BUF_COUNT must be > 0, got %u", I2S_DMA_BUF_COUNT); - valid = false; - } else if (I2S_DMA_BUF_COUNT < 2) { - LOG_WARN("I2S_DMA_BUF_COUNT (%u) should be >= 2", I2S_DMA_BUF_COUNT); - } else { - LOG_INFO(" ✓ I2S DMA buffer count: %u", I2S_DMA_BUF_COUNT); - } - - if (I2S_DMA_BUF_LEN <= 0) { - LOG_ERROR("I2S_DMA_BUF_LEN must be > 0, got %u", I2S_DMA_BUF_LEN); - valid = false; - } else { - LOG_INFO(" ✓ I2S DMA buffer length: %u", I2S_DMA_BUF_LEN); - } - - if (I2S_MAX_READ_RETRIES <= 0) { - LOG_WARN("I2S_MAX_READ_RETRIES is %u - should be > 0", I2S_MAX_READ_RETRIES); - } else { - LOG_INFO(" ✓ I2S max read retries: %u", I2S_MAX_READ_RETRIES); - } - - return valid; - } - - /** - * Validate timing configuration - * Checks: Check intervals are reasonable - */ - static bool validateTimingConfig() { - bool valid = true; - - LOG_INFO("Checking timing configuration..."); - - if (MEMORY_CHECK_INTERVAL <= 0) { - LOG_WARN("MEMORY_CHECK_INTERVAL is %u ms - should be > 0", MEMORY_CHECK_INTERVAL); - } else if (MEMORY_CHECK_INTERVAL < 5000) { - LOG_WARN("MEMORY_CHECK_INTERVAL (%u ms) is very frequent", MEMORY_CHECK_INTERVAL); - } else { - LOG_INFO(" ✓ Memory check interval: %u ms", MEMORY_CHECK_INTERVAL); - } - - if (RSSI_CHECK_INTERVAL <= 0) { - LOG_WARN("RSSI_CHECK_INTERVAL is %u ms - should be > 0", RSSI_CHECK_INTERVAL); - } else { - LOG_INFO(" ✓ RSSI check interval: %u ms", RSSI_CHECK_INTERVAL); - } - - if (STATS_PRINT_INTERVAL <= 0) { - LOG_WARN("STATS_PRINT_INTERVAL is %u ms - should be > 0", STATS_PRINT_INTERVAL); - } else { - LOG_INFO(" ✓ Stats print interval: %u ms", STATS_PRINT_INTERVAL); - } - - return valid; - } - - /** - * Validate memory thresholds - * Checks: Thresholds are logical (critical < warning) and non-zero - */ - static bool validateMemoryThresholds() { - bool valid = true; - - LOG_INFO("Checking memory thresholds..."); - - if (MEMORY_CRITICAL_THRESHOLD <= 0) { - LOG_ERROR("MEMORY_CRITICAL_THRESHOLD must be > 0, got %u bytes", MEMORY_CRITICAL_THRESHOLD); - valid = false; - } else { - LOG_INFO(" ✓ Memory critical threshold: %u bytes", MEMORY_CRITICAL_THRESHOLD); - } - - if (MEMORY_WARN_THRESHOLD <= 0) { - LOG_ERROR("MEMORY_WARN_THRESHOLD must be > 0, got %u bytes", MEMORY_WARN_THRESHOLD); - valid = false; - } else { - LOG_INFO(" ✓ Memory warn threshold: %u bytes", MEMORY_WARN_THRESHOLD); - } - - if (MEMORY_CRITICAL_THRESHOLD >= MEMORY_WARN_THRESHOLD) { - LOG_ERROR("MEMORY_CRITICAL_THRESHOLD (%u) must be < MEMORY_WARN_THRESHOLD (%u)", - MEMORY_CRITICAL_THRESHOLD, MEMORY_WARN_THRESHOLD); - valid = false; - } else { - LOG_INFO(" ✓ Memory threshold hierarchy correct"); - } - - if (RSSI_WEAK_THRESHOLD > 0) { - LOG_WARN("RSSI_WEAK_THRESHOLD (%d) is positive - should be negative dBm value", RSSI_WEAK_THRESHOLD); - } else if (RSSI_WEAK_THRESHOLD > -20) { - LOG_WARN("RSSI_WEAK_THRESHOLD (%d dBm) is very strong - typical range is -80 to -50", RSSI_WEAK_THRESHOLD); - } else { - LOG_INFO(" ✓ RSSI weak threshold: %d dBm", RSSI_WEAK_THRESHOLD); - } - - if (MAX_CONSECUTIVE_FAILURES <= 0) { - LOG_WARN("MAX_CONSECUTIVE_FAILURES is %u - should be > 0", MAX_CONSECUTIVE_FAILURES); - } else { - LOG_INFO(" ✓ Max consecutive failures: %u", MAX_CONSECUTIVE_FAILURES); - } - - return valid; - } - - /** - * Validate watchdog configuration - * Ensures watchdog timeout is compatible with operation timeouts - */ - static bool validateWatchdogConfig() { - bool valid = true; - - LOG_INFO("Checking watchdog configuration..."); - - if (WATCHDOG_TIMEOUT_SEC <= 0) { - LOG_ERROR("WATCHDOG_TIMEOUT_SEC must be > 0, got %u seconds", WATCHDOG_TIMEOUT_SEC); - valid = false; - } else if (WATCHDOG_TIMEOUT_SEC < 30) { - LOG_WARN("WATCHDOG_TIMEOUT_SEC (%u sec) is short - recommend >= 30 seconds", WATCHDOG_TIMEOUT_SEC); - } else { - LOG_INFO(" ✓ Watchdog timeout: %u seconds", WATCHDOG_TIMEOUT_SEC); - } - - // Verify watchdog timeout doesn't conflict with WiFi timeout - uint32_t wifi_timeout_sec = WIFI_TIMEOUT / 1000; - if (WATCHDOG_TIMEOUT_SEC <= wifi_timeout_sec) { - LOG_WARN("WATCHDOG_TIMEOUT_SEC (%u) <= WIFI_TIMEOUT (%u sec) - watchdog may reset during WiFi connection", - WATCHDOG_TIMEOUT_SEC, wifi_timeout_sec); - } else { - LOG_INFO(" ✓ Watchdog timeout compatible with WiFi timeout"); - } - - // Verify watchdog timeout doesn't conflict with error recovery delay - uint32_t error_delay_sec = ERROR_RECOVERY_DELAY / 1000; - if (WATCHDOG_TIMEOUT_SEC <= error_delay_sec) { - LOG_ERROR("WATCHDOG_TIMEOUT_SEC (%u) <= ERROR_RECOVERY_DELAY (%u sec) - watchdog will reset during error recovery", - WATCHDOG_TIMEOUT_SEC, error_delay_sec); - valid = false; - } else { - LOG_INFO(" ✓ Watchdog timeout compatible with error recovery delay"); - } - - // Verify watchdog is long enough for state operations - // Typical operations: WiFi ~25s, I2S read ~1ms, TCP write ~100ms - if (WATCHDOG_TIMEOUT_SEC < (wifi_timeout_sec + 5)) { - LOG_WARN("WATCHDOG_TIMEOUT_SEC (%u) is close to WIFI_TIMEOUT (%u sec) - margin may be tight", - WATCHDOG_TIMEOUT_SEC, wifi_timeout_sec); - } - - return valid; + /** + * Validate WiFi configuration + * Checks: SSID and password not empty + */ + static bool validateWiFiConfig() { + bool valid = true; + + LOG_INFO("Checking WiFi configuration..."); + + // Check SSID + if (strlen(WIFI_SSID) == 0) { + LOG_ERROR("WiFi SSID is empty - must configure WIFI_SSID in config.h"); + valid = false; + } else { + LOG_INFO(" ✓ WiFi SSID configured"); + } + + // Check password + if (strlen(WIFI_PASSWORD) == 0) { + LOG_ERROR( + "WiFi password is empty - must configure WIFI_PASSWORD in config.h"); + valid = false; + } else { + LOG_INFO(" ✓ WiFi password configured"); + } + + // Validate retry parameters + if (WIFI_RETRY_DELAY <= 0) { + LOG_WARN("WIFI_RETRY_DELAY is 0 or negative - using default 500ms"); + } else { + LOG_INFO(" ✓ WiFi retry delay: %u ms", WIFI_RETRY_DELAY); + } + + if (WIFI_MAX_RETRIES <= 0) { + LOG_WARN("WIFI_MAX_RETRIES is 0 or negative - using default 20"); + } else { + LOG_INFO(" ✓ WiFi max retries: %u", WIFI_MAX_RETRIES); + } + + if (WIFI_TIMEOUT <= 0) { + LOG_WARN("WIFI_TIMEOUT is 0 or negative - using default 30000ms"); + } else { + LOG_INFO(" ✓ WiFi timeout: %u ms", WIFI_TIMEOUT); + } + + return valid; + } + + /** + * Validate server configuration + * Checks: HOST and PORT not empty, valid port number + */ + static bool validateServerConfig() { + bool valid = true; + + LOG_INFO("Checking server configuration..."); + + // Check HOST + if (strlen(SERVER_HOST) == 0) { + LOG_ERROR( + "Server HOST is empty - must configure SERVER_HOST in config.h"); + valid = false; + } else { + LOG_INFO(" ✓ Server HOST configured: %s", SERVER_HOST); + } + + // Check PORT + if (SERVER_PORT <= 0 || SERVER_PORT > 65535) { + LOG_ERROR("Server PORT (%d) is invalid - must be 1-65535", SERVER_PORT); + valid = false; + } else { + LOG_INFO(" ✓ Server PORT configured: %d", SERVER_PORT); + } + + // Validate reconnection timeouts + if (SERVER_RECONNECT_MIN <= 0) { + LOG_WARN("SERVER_RECONNECT_MIN is %u ms - should be > 0", + SERVER_RECONNECT_MIN); + } else if (SERVER_RECONNECT_MIN < 1000) { + LOG_WARN("SERVER_RECONNECT_MIN (%u ms) is very short - minimum " + "recommended is 1000ms", + SERVER_RECONNECT_MIN); + } else { + LOG_INFO(" ✓ Server reconnect min: %u ms", SERVER_RECONNECT_MIN); + } + + if (SERVER_RECONNECT_MAX <= 0) { + LOG_WARN("SERVER_RECONNECT_MAX is %u ms - should be > 0", + SERVER_RECONNECT_MAX); + } else if (SERVER_RECONNECT_MAX < SERVER_RECONNECT_MIN) { + LOG_ERROR("SERVER_RECONNECT_MAX (%u ms) cannot be less than " + "SERVER_RECONNECT_MIN (%u ms)", + SERVER_RECONNECT_MAX, SERVER_RECONNECT_MIN); + valid = false; + } else { + LOG_INFO(" ✓ Server reconnect max: %u ms", SERVER_RECONNECT_MAX); + } + + if (TCP_WRITE_TIMEOUT <= 0) { + LOG_WARN("TCP_WRITE_TIMEOUT is %u ms - should be > 0", TCP_WRITE_TIMEOUT); + } else if (TCP_WRITE_TIMEOUT < 1000) { + LOG_WARN("TCP_WRITE_TIMEOUT (%u ms) is very short", TCP_WRITE_TIMEOUT); + } else { + LOG_INFO(" ✓ TCP write timeout: %u ms", TCP_WRITE_TIMEOUT); + } + + return valid; + } + + /** + * Validate I2S configuration + * Checks: Valid sample rate, buffer sizes, DMA parameters + */ + static bool validateI2SConfig() { + bool valid = true; + + LOG_INFO("Checking I2S configuration..."); + + if (I2S_SAMPLE_RATE <= 0) { + LOG_ERROR("I2S_SAMPLE_RATE must be > 0, got %u", I2S_SAMPLE_RATE); + valid = false; + } else if (I2S_SAMPLE_RATE < 8000 || I2S_SAMPLE_RATE > 48000) { + LOG_WARN("I2S_SAMPLE_RATE (%u Hz) outside typical range (8000-48000)", + I2S_SAMPLE_RATE); + } else { + LOG_INFO(" ✓ I2S sample rate: %u Hz", I2S_SAMPLE_RATE); + } + + if (I2S_BUFFER_SIZE <= 0) { + LOG_ERROR("I2S_BUFFER_SIZE must be > 0, got %u", I2S_BUFFER_SIZE); + valid = false; + } else if ((I2S_BUFFER_SIZE & (I2S_BUFFER_SIZE - 1)) != 0) { + LOG_WARN("I2S_BUFFER_SIZE (%u) is not a power of 2", I2S_BUFFER_SIZE); + } else { + LOG_INFO(" ✓ I2S buffer size: %u bytes", I2S_BUFFER_SIZE); + } + + if (I2S_DMA_BUF_COUNT <= 0) { + LOG_ERROR("I2S_DMA_BUF_COUNT must be > 0, got %u", I2S_DMA_BUF_COUNT); + valid = false; + } else if (I2S_DMA_BUF_COUNT < 2) { + LOG_WARN("I2S_DMA_BUF_COUNT (%u) should be >= 2", I2S_DMA_BUF_COUNT); + } else { + LOG_INFO(" ✓ I2S DMA buffer count: %u", I2S_DMA_BUF_COUNT); + } + + if (I2S_DMA_BUF_LEN <= 0) { + LOG_ERROR("I2S_DMA_BUF_LEN must be > 0, got %u", I2S_DMA_BUF_LEN); + valid = false; + } else { + LOG_INFO(" ✓ I2S DMA buffer length: %u", I2S_DMA_BUF_LEN); + } + + if (I2S_MAX_READ_RETRIES <= 0) { + LOG_WARN("I2S_MAX_READ_RETRIES is %u - should be > 0", + I2S_MAX_READ_RETRIES); + } else { + LOG_INFO(" ✓ I2S max read retries: %u", I2S_MAX_READ_RETRIES); + } + + return valid; + } + + /** + * Validate timing configuration + * Checks: Check intervals are reasonable + */ + static bool validateTimingConfig() { + bool valid = true; + + LOG_INFO("Checking timing configuration..."); + + if (MEMORY_CHECK_INTERVAL <= 0) { + LOG_WARN("MEMORY_CHECK_INTERVAL is %u ms - should be > 0", + MEMORY_CHECK_INTERVAL); + } else if (MEMORY_CHECK_INTERVAL < 5000) { + LOG_WARN("MEMORY_CHECK_INTERVAL (%u ms) is very frequent", + MEMORY_CHECK_INTERVAL); + } else { + LOG_INFO(" ✓ Memory check interval: %u ms", MEMORY_CHECK_INTERVAL); } + + if (RSSI_CHECK_INTERVAL <= 0) { + LOG_WARN("RSSI_CHECK_INTERVAL is %u ms - should be > 0", + RSSI_CHECK_INTERVAL); + } else { + LOG_INFO(" ✓ RSSI check interval: %u ms", RSSI_CHECK_INTERVAL); + } + + if (STATS_PRINT_INTERVAL <= 0) { + LOG_WARN("STATS_PRINT_INTERVAL is %u ms - should be > 0", + STATS_PRINT_INTERVAL); + } else { + LOG_INFO(" ✓ Stats print interval: %u ms", STATS_PRINT_INTERVAL); + } + + return valid; + } + + /** + * Validate memory thresholds + * Checks: Thresholds are logical (critical < warning) and non-zero + */ + static bool validateMemoryThresholds() { + bool valid = true; + + LOG_INFO("Checking memory thresholds..."); + + if (MEMORY_CRITICAL_THRESHOLD <= 0) { + LOG_ERROR("MEMORY_CRITICAL_THRESHOLD must be > 0, got %u bytes", + MEMORY_CRITICAL_THRESHOLD); + valid = false; + } else { + LOG_INFO(" ✓ Memory critical threshold: %u bytes", + MEMORY_CRITICAL_THRESHOLD); + } + + if (MEMORY_WARN_THRESHOLD <= 0) { + LOG_ERROR("MEMORY_WARN_THRESHOLD must be > 0, got %u bytes", + MEMORY_WARN_THRESHOLD); + valid = false; + } else { + LOG_INFO(" ✓ Memory warn threshold: %u bytes", MEMORY_WARN_THRESHOLD); + } + + if (MEMORY_CRITICAL_THRESHOLD >= MEMORY_WARN_THRESHOLD) { + LOG_ERROR( + "MEMORY_CRITICAL_THRESHOLD (%u) must be < MEMORY_WARN_THRESHOLD (%u)", + MEMORY_CRITICAL_THRESHOLD, MEMORY_WARN_THRESHOLD); + valid = false; + } else { + LOG_INFO(" ✓ Memory threshold hierarchy correct"); + } + + if (RSSI_WEAK_THRESHOLD > 0) { + LOG_WARN( + "RSSI_WEAK_THRESHOLD (%d) is positive - should be negative dBm value", + RSSI_WEAK_THRESHOLD); + } else if (RSSI_WEAK_THRESHOLD > -20) { + LOG_WARN("RSSI_WEAK_THRESHOLD (%d dBm) is very strong - typical range is " + "-80 to -50", + RSSI_WEAK_THRESHOLD); + } else { + LOG_INFO(" ✓ RSSI weak threshold: %d dBm", RSSI_WEAK_THRESHOLD); + } + + if (MAX_CONSECUTIVE_FAILURES <= 0) { + LOG_WARN("MAX_CONSECUTIVE_FAILURES is %u - should be > 0", + MAX_CONSECUTIVE_FAILURES); + } else { + LOG_INFO(" ✓ Max consecutive failures: %u", MAX_CONSECUTIVE_FAILURES); + } + + return valid; + } + + /** + * Validate watchdog configuration + * Ensures watchdog timeout is compatible with operation timeouts + */ + static bool validateWatchdogConfig() { + bool valid = true; + + LOG_INFO("Checking watchdog configuration..."); + + if (WATCHDOG_TIMEOUT_SEC <= 0) { + LOG_ERROR("WATCHDOG_TIMEOUT_SEC must be > 0, got %u seconds", + WATCHDOG_TIMEOUT_SEC); + valid = false; + } else if (WATCHDOG_TIMEOUT_SEC < 30) { + LOG_WARN( + "WATCHDOG_TIMEOUT_SEC (%u sec) is short - recommend >= 30 seconds", + WATCHDOG_TIMEOUT_SEC); + } else { + LOG_INFO(" ✓ Watchdog timeout: %u seconds", WATCHDOG_TIMEOUT_SEC); + } + + // Verify watchdog timeout doesn't conflict with WiFi timeout + uint32_t wifi_timeout_sec = WIFI_TIMEOUT / 1000; + if (WATCHDOG_TIMEOUT_SEC <= wifi_timeout_sec) { + LOG_WARN("WATCHDOG_TIMEOUT_SEC (%u) <= WIFI_TIMEOUT (%u sec) - watchdog " + "may reset during WiFi connection", + WATCHDOG_TIMEOUT_SEC, wifi_timeout_sec); + } else { + LOG_INFO(" ✓ Watchdog timeout compatible with WiFi timeout"); + } + + // Verify watchdog timeout doesn't conflict with error recovery delay + uint32_t error_delay_sec = ERROR_RECOVERY_DELAY / 1000; + if (WATCHDOG_TIMEOUT_SEC <= error_delay_sec) { + LOG_ERROR("WATCHDOG_TIMEOUT_SEC (%u) <= ERROR_RECOVERY_DELAY (%u sec) - " + "watchdog will reset during error recovery", + WATCHDOG_TIMEOUT_SEC, error_delay_sec); + valid = false; + } else { + LOG_INFO(" ✓ Watchdog timeout compatible with error recovery delay"); + } + + // Verify watchdog is long enough for state operations + // Typical operations: WiFi ~25s, I2S read ~1ms, TCP write ~100ms + if (WATCHDOG_TIMEOUT_SEC < (wifi_timeout_sec + 5)) { + LOG_WARN("WATCHDOG_TIMEOUT_SEC (%u) is close to WIFI_TIMEOUT (%u sec) - " + "margin may be tight", + WATCHDOG_TIMEOUT_SEC, wifi_timeout_sec); + } + + return valid; + } }; #endif // CONFIG_VALIDATOR_H diff --git a/src/debug_mode.cpp b/src/debug_mode.cpp index 7f6eff6..832123c 100644 --- a/src/debug_mode.cpp +++ b/src/debug_mode.cpp @@ -6,37 +6,34 @@ bool RuntimeDebugContext::enabled = false; int RuntimeDebugContext::level = 0; void RuntimeDebugContext::setEnabled(bool enable) { - enabled = enable; - if (enable) { - LOG_INFO("Runtime debug output enabled"); - } else { - LOG_INFO("Runtime debug output disabled"); - } + enabled = enable; + if (enable) { + LOG_INFO("Runtime debug output enabled"); + } else { + LOG_INFO("Runtime debug output disabled"); + } } -bool RuntimeDebugContext::isEnabled() { - return enabled; -} +bool RuntimeDebugContext::isEnabled() { return enabled; } void RuntimeDebugContext::setLevel(int new_level) { - level = new_level; - LOG_INFO("Runtime debug level set to %d", level); + level = new_level; + LOG_INFO("Runtime debug level set to %d", level); } -int RuntimeDebugContext::getLevel() { - return level; -} +int RuntimeDebugContext::getLevel() { return level; } -void RuntimeDebugContext::log(const char* fmt, ...) { - if (!enabled) return; +void RuntimeDebugContext::log(const char *fmt, ...) { + if (!enabled) + return; - va_list args; - va_start(args, fmt); + va_list args; + va_start(args, fmt); - // Simple implementation - just print to serial if enabled - char buffer[256]; - vsnprintf(buffer, sizeof(buffer), fmt, args); - Serial.println(buffer); + // Simple implementation - just print to serial if enabled + char buffer[256]; + vsnprintf(buffer, sizeof(buffer), fmt, args); + Serial.println(buffer); - va_end(args); + va_end(args); } diff --git a/src/debug_mode.h b/src/debug_mode.h index 2939461..e93a7a7 100644 --- a/src/debug_mode.h +++ b/src/debug_mode.h @@ -3,54 +3,54 @@ // Debug level control #ifndef DEBUG_LEVEL - #define DEBUG_LEVEL 1 // 0=OFF, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE +#define DEBUG_LEVEL 1 // 0=OFF, 1=ERROR, 2=WARN, 3=INFO, 4=DEBUG, 5=VERBOSE #endif // Compile-time debug macros #if DEBUG_LEVEL >= 1 - #define DEBUG_ERROR(fmt, ...) LOG_ERROR(fmt, ##__VA_ARGS__) +#define DEBUG_ERROR(fmt, ...) LOG_ERROR(fmt, ##__VA_ARGS__) #else - #define DEBUG_ERROR(fmt, ...) +#define DEBUG_ERROR(fmt, ...) #endif #if DEBUG_LEVEL >= 2 - #define DEBUG_WARN(fmt, ...) LOG_WARN(fmt, ##__VA_ARGS__) +#define DEBUG_WARN(fmt, ...) LOG_WARN(fmt, ##__VA_ARGS__) #else - #define DEBUG_WARN(fmt, ...) +#define DEBUG_WARN(fmt, ...) #endif #if DEBUG_LEVEL >= 3 - #define DEBUG_INFO(fmt, ...) LOG_INFO(fmt, ##__VA_ARGS__) +#define DEBUG_INFO(fmt, ...) LOG_INFO(fmt, ##__VA_ARGS__) #else - #define DEBUG_INFO(fmt, ...) +#define DEBUG_INFO(fmt, ...) #endif #if DEBUG_LEVEL >= 4 - #define DEBUG_LOG(fmt, ...) LOG_DEBUG(fmt, ##__VA_ARGS__) +#define DEBUG_LOG(fmt, ...) LOG_DEBUG(fmt, ##__VA_ARGS__) #else - #define DEBUG_LOG(fmt, ...) +#define DEBUG_LOG(fmt, ...) #endif #if DEBUG_LEVEL >= 5 - #define DEBUG_VERBOSE(fmt, ...) LOG_DEBUG(fmt, ##__VA_ARGS__) +#define DEBUG_VERBOSE(fmt, ...) LOG_DEBUG(fmt, ##__VA_ARGS__) #else - #define DEBUG_VERBOSE(fmt, ...) +#define DEBUG_VERBOSE(fmt, ...) #endif // Runtime debug context - allows toggling debug output without recompiling class RuntimeDebugContext { public: - static void setEnabled(bool enable); - static bool isEnabled(); - static void setLevel(int level); - static int getLevel(); + static void setEnabled(bool enable); + static bool isEnabled(); + static void setLevel(int level); + static int getLevel(); - // Conditional logging based on runtime settings - static void log(const char* fmt, ...); + // Conditional logging based on runtime settings + static void log(const char *fmt, ...); private: - static bool enabled; - static int level; + static bool enabled; + static int level; }; #endif // DEBUG_MODE_H diff --git a/src/i2s_audio.cpp b/src/i2s_audio.cpp index 9674825..82da115 100644 --- a/src/i2s_audio.cpp +++ b/src/i2s_audio.cpp @@ -8,225 +8,225 @@ uint32_t I2SAudio::transient_errors = 0; uint32_t I2SAudio::permanent_errors = 0; bool I2SAudio::initialize() { - LOG_INFO("Initializing I2S audio driver..."); - - // I2S configuration using legacy Arduino-ESP32 API - i2s_config_t i2s_config = { - .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), - .sample_rate = I2S_SAMPLE_RATE, - .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, - .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, - .communication_format = I2S_COMM_FORMAT_STAND_I2S, // Use non-deprecated constant - .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, - .dma_buf_count = I2S_DMA_BUF_COUNT, - .dma_buf_len = I2S_DMA_BUF_LEN, - .use_apll = true, // Better clock stability - .tx_desc_auto_clear = false, - .fixed_mclk = 0 // Auto-calculate - }; - - // I2S pin configuration - i2s_pin_config_t pin_config = { - .bck_io_num = I2S_SCK_PIN, - .ws_io_num = I2S_WS_PIN, - .data_out_num = I2S_PIN_NO_CHANGE, - .data_in_num = I2S_SD_PIN - }; - - // Install I2S driver - esp_err_t result = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + LOG_INFO("Initializing I2S audio driver..."); + + // I2S configuration using legacy Arduino-ESP32 API + i2s_config_t i2s_config = { + .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_RX), + .sample_rate = I2S_SAMPLE_RATE, + .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT, + .channel_format = I2S_CHANNEL_FMT_ONLY_LEFT, + .communication_format = + I2S_COMM_FORMAT_STAND_I2S, // Use non-deprecated constant + .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1, + .dma_buf_count = I2S_DMA_BUF_COUNT, + .dma_buf_len = I2S_DMA_BUF_LEN, + .use_apll = true, // Better clock stability + .tx_desc_auto_clear = false, + .fixed_mclk = 0 // Auto-calculate + }; + + // I2S pin configuration + i2s_pin_config_t pin_config = {.bck_io_num = I2S_SCK_PIN, + .ws_io_num = I2S_WS_PIN, + .data_out_num = I2S_PIN_NO_CHANGE, + .data_in_num = I2S_SD_PIN}; + + // Install I2S driver + esp_err_t result = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); + if (result != ESP_OK) { + LOG_ERROR("I2S driver install failed (APLL on): %d", result); + // Retry without APLL as fallback for boards where APLL fails + i2s_config.use_apll = false; + result = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); if (result != ESP_OK) { - LOG_ERROR("I2S driver install failed (APLL on): %d", result); - // Retry without APLL as fallback for boards where APLL fails - i2s_config.use_apll = false; - result = i2s_driver_install(I2S_PORT, &i2s_config, 0, NULL); - if (result != ESP_OK) { - LOG_ERROR("I2S driver install failed (APLL off): %d", result); - return false; - } else { - LOG_WARN("I2S initialized without APLL - clock stability reduced"); - } + LOG_ERROR("I2S driver install failed (APLL off): %d", result); + return false; + } else { + LOG_WARN("I2S initialized without APLL - clock stability reduced"); } + } - // Set I2S pin configuration - result = i2s_set_pin(I2S_PORT, &pin_config); - if (result != ESP_OK) { - LOG_ERROR("I2S pin configuration failed: %d", result); - i2s_driver_uninstall(I2S_PORT); - return false; - } + // Set I2S pin configuration + result = i2s_set_pin(I2S_PORT, &pin_config); + if (result != ESP_OK) { + LOG_ERROR("I2S pin configuration failed: %d", result); + i2s_driver_uninstall(I2S_PORT); + return false; + } - // Clear DMA buffer to remove initialization noise - i2s_zero_dma_buffer(I2S_PORT); + // Clear DMA buffer to remove initialization noise + i2s_zero_dma_buffer(I2S_PORT); - is_initialized = true; - consecutive_errors = 0; - LOG_INFO("I2S audio driver initialized successfully"); - return true; + is_initialized = true; + consecutive_errors = 0; + LOG_INFO("I2S audio driver initialized successfully"); + return true; } void I2SAudio::cleanup() { - if (!is_initialized) return; - - LOG_INFO("Cleaning up I2S audio driver..."); - i2s_stop(I2S_PORT); - i2s_driver_uninstall(I2S_PORT); - is_initialized = false; - LOG_INFO("I2S audio driver cleaned up"); + if (!is_initialized) + return; + + LOG_INFO("Cleaning up I2S audio driver..."); + i2s_stop(I2S_PORT); + i2s_driver_uninstall(I2S_PORT); + is_initialized = false; + LOG_INFO("I2S audio driver cleaned up"); } -bool I2SAudio::readData(uint8_t* buffer, size_t buffer_size, size_t* bytes_read) { - if (!is_initialized) { - LOG_ERROR("I2S not initialized"); - return false; +bool I2SAudio::readData(uint8_t *buffer, size_t buffer_size, + size_t *bytes_read) { + if (!is_initialized) { + LOG_ERROR("I2S not initialized"); + return false; + } + + esp_err_t result = + i2s_read(I2S_PORT, buffer, buffer_size, bytes_read, pdMS_TO_TICKS(1000)); + + if (result != ESP_OK) { + // Classify error type for better recovery strategy + I2SErrorType error_type = classifyError(result); + total_errors++; + + if (error_type == I2SErrorType::TRANSIENT) { + transient_errors++; + LOG_WARN("I2S read transient error (%d) - retry may succeed", result); + } else if (error_type == I2SErrorType::PERMANENT) { + permanent_errors++; + LOG_ERROR("I2S read permanent error (%d) - reinitialization recommended", + result); + } else { + LOG_ERROR("I2S read fatal error (%d) - recovery unlikely", result); } - esp_err_t result = i2s_read(I2S_PORT, buffer, buffer_size, bytes_read, pdMS_TO_TICKS(1000)); + consecutive_errors++; + return false; + } - if (result != ESP_OK) { - // Classify error type for better recovery strategy - I2SErrorType error_type = classifyError(result); - total_errors++; - - if (error_type == I2SErrorType::TRANSIENT) { - transient_errors++; - LOG_WARN("I2S read transient error (%d) - retry may succeed", result); - } else if (error_type == I2SErrorType::PERMANENT) { - permanent_errors++; - LOG_ERROR("I2S read permanent error (%d) - reinitialization recommended", result); - } else { - LOG_ERROR("I2S read fatal error (%d) - recovery unlikely", result); - } + if (*bytes_read == 0) { + LOG_WARN("I2S read returned 0 bytes"); + total_errors++; + transient_errors++; // Zero bytes is typically transient (no data ready) + consecutive_errors++; + return false; + } - consecutive_errors++; - return false; - } + // Reset error counter on successful read + consecutive_errors = 0; + return true; +} - if (*bytes_read == 0) { - LOG_WARN("I2S read returned 0 bytes"); - total_errors++; - transient_errors++; // Zero bytes is typically transient (no data ready) - consecutive_errors++; - return false; +bool I2SAudio::readDataWithRetry(uint8_t *buffer, size_t buffer_size, + size_t *bytes_read, int max_retries) { + for (int attempt = 0; attempt < max_retries; attempt++) { + if (readData(buffer, buffer_size, bytes_read)) { + if (attempt > 0) { + LOG_INFO("I2S read succeeded on attempt %d", attempt + 1); + } + return true; } - // Reset error counter on successful read - consecutive_errors = 0; - return true; -} + LOG_WARN("I2S read attempt %d/%d failed", attempt + 1, max_retries); -bool I2SAudio::readDataWithRetry(uint8_t* buffer, size_t buffer_size, size_t* bytes_read, int max_retries) { - for (int attempt = 0; attempt < max_retries; attempt++) { + // Check if we need to reinitialize due to persistent errors + if (consecutive_errors > MAX_CONSECUTIVE_FAILURES) { + LOG_CRITICAL( + "Too many consecutive I2S errors - attempting reinitialization"); + if (reinitialize()) { + LOG_INFO("I2S reinitialized successfully, retrying read"); + // Try one more time after reinitialize if (readData(buffer, buffer_size, bytes_read)) { - if (attempt > 0) { - LOG_INFO("I2S read succeeded on attempt %d", attempt + 1); - } - return true; + return true; } - - LOG_WARN("I2S read attempt %d/%d failed", attempt + 1, max_retries); - - // Check if we need to reinitialize due to persistent errors - if (consecutive_errors > MAX_CONSECUTIVE_FAILURES) { - LOG_CRITICAL("Too many consecutive I2S errors - attempting reinitialization"); - if (reinitialize()) { - LOG_INFO("I2S reinitialized successfully, retrying read"); - // Try one more time after reinitialize - if (readData(buffer, buffer_size, bytes_read)) { - return true; - } - } - } - - delay(10); // Brief pause before retry + } } - return false; + delay(10); // Brief pause before retry + } + + return false; } bool I2SAudio::reinitialize() { - LOG_INFO("Reinitializing I2S..."); - cleanup(); - delay(100); - bool result = initialize(); - if (result) { - consecutive_errors = 0; - } - return result; + LOG_INFO("Reinitializing I2S..."); + cleanup(); + delay(100); + bool result = initialize(); + if (result) { + consecutive_errors = 0; + } + return result; } // ===== Error Classification & Health Checks ===== I2SErrorType I2SAudio::classifyError(esp_err_t error) { - switch (error) { - case ESP_OK: - return I2SErrorType::NONE; - - // Transient errors (temporary, likely recoverable with retry) - case ESP_ERR_NO_MEM: - // Memory pressure - may recover - return I2SErrorType::TRANSIENT; - - case ESP_ERR_INVALID_STATE: - // Driver in wrong state - may recover with delay - return I2SErrorType::TRANSIENT; - - case ESP_ERR_TIMEOUT: - // Timeout waiting for data - likely temporary - return I2SErrorType::TRANSIENT; - - // Permanent errors (reinitialization needed) - case ESP_ERR_INVALID_ARG: - // Invalid parameter - configuration issue - return I2SErrorType::PERMANENT; - - case ESP_ERR_NOT_FOUND: - // I2S port not found - hardware issue - return I2SErrorType::PERMANENT; - - case ESP_FAIL: - // Generic failure - try reinitialization - return I2SErrorType::PERMANENT; - - // Fatal errors (cannot recover) - default: - return I2SErrorType::FATAL; - } + switch (error) { + case ESP_OK: + return I2SErrorType::NONE; + + // Transient errors (temporary, likely recoverable with retry) + case ESP_ERR_NO_MEM: + // Memory pressure - may recover + return I2SErrorType::TRANSIENT; + + case ESP_ERR_INVALID_STATE: + // Driver in wrong state - may recover with delay + return I2SErrorType::TRANSIENT; + + case ESP_ERR_TIMEOUT: + // Timeout waiting for data - likely temporary + return I2SErrorType::TRANSIENT; + + // Permanent errors (reinitialization needed) + case ESP_ERR_INVALID_ARG: + // Invalid parameter - configuration issue + return I2SErrorType::PERMANENT; + + case ESP_ERR_NOT_FOUND: + // I2S port not found - hardware issue + return I2SErrorType::PERMANENT; + + case ESP_FAIL: + // Generic failure - try reinitialization + return I2SErrorType::PERMANENT; + + // Fatal errors (cannot recover) + default: + return I2SErrorType::FATAL; + } } bool I2SAudio::healthCheck() { - if (!is_initialized) { - LOG_WARN("I2S health check: not initialized"); - return false; - } + if (!is_initialized) { + LOG_WARN("I2S health check: not initialized"); + return false; + } - // Check if too many consecutive errors indicate health issue - if (consecutive_errors > (MAX_CONSECUTIVE_FAILURES / 2)) { - LOG_WARN("I2S health check: %d consecutive errors detected", consecutive_errors); - return false; - } + // Check if too many consecutive errors indicate health issue + if (consecutive_errors > (MAX_CONSECUTIVE_FAILURES / 2)) { + LOG_WARN("I2S health check: %d consecutive errors detected", + consecutive_errors); + return false; + } - // Verify error rates are acceptable - // If permanent errors > 20% of total, something is wrong - if (total_errors > 100 && (permanent_errors * 100 / total_errors) > 20) { - LOG_ERROR("I2S health check: high permanent error rate (%u%% of %u total)", - permanent_errors * 100 / total_errors, total_errors); - return false; - } + // Verify error rates are acceptable + // If permanent errors > 20% of total, something is wrong + if (total_errors > 100 && (permanent_errors * 100 / total_errors) > 20) { + LOG_ERROR("I2S health check: high permanent error rate (%u%% of %u total)", + permanent_errors * 100 / total_errors, total_errors); + return false; + } - LOG_DEBUG("I2S health check: OK (total:%u, transient:%u, permanent:%u)", - total_errors, transient_errors, permanent_errors); - return true; + LOG_DEBUG("I2S health check: OK (total:%u, transient:%u, permanent:%u)", + total_errors, transient_errors, permanent_errors); + return true; } -uint32_t I2SAudio::getErrorCount() { - return total_errors; -} +uint32_t I2SAudio::getErrorCount() { return total_errors; } -uint32_t I2SAudio::getTransientErrorCount() { - return transient_errors; -} +uint32_t I2SAudio::getTransientErrorCount() { return transient_errors; } -uint32_t I2SAudio::getPermanentErrorCount() { - return permanent_errors; -} +uint32_t I2SAudio::getPermanentErrorCount() { return permanent_errors; } diff --git a/src/i2s_audio.h b/src/i2s_audio.h index a338e86..a513790 100644 --- a/src/i2s_audio.h +++ b/src/i2s_audio.h @@ -1,40 +1,42 @@ #ifndef I2S_AUDIO_H #define I2S_AUDIO_H -#include -#include "driver/i2s.h" #include "config.h" +#include "driver/i2s.h" +#include // I2S Audio Error Classification enum class I2SErrorType { - NONE, // No error - TRANSIENT, // Temporary error, retry likely to succeed - PERMANENT, // Permanent error, reinitialization needed - FATAL // Cannot recover, reboot required + NONE, // No error + TRANSIENT, // Temporary error, retry likely to succeed + PERMANENT, // Permanent error, reinitialization needed + FATAL // Cannot recover, reboot required }; // I2S Audio management class I2SAudio { public: - static bool initialize(); - static void cleanup(); - static bool readData(uint8_t* buffer, size_t buffer_size, size_t* bytes_read); - static bool readDataWithRetry(uint8_t* buffer, size_t buffer_size, size_t* bytes_read, int max_retries = I2S_MAX_READ_RETRIES); - static bool reinitialize(); + static bool initialize(); + static void cleanup(); + static bool readData(uint8_t *buffer, size_t buffer_size, size_t *bytes_read); + static bool readDataWithRetry(uint8_t *buffer, size_t buffer_size, + size_t *bytes_read, + int max_retries = I2S_MAX_READ_RETRIES); + static bool reinitialize(); - // Health check and error classification - static bool healthCheck(); - static I2SErrorType classifyError(esp_err_t error); - static uint32_t getErrorCount(); - static uint32_t getTransientErrorCount(); - static uint32_t getPermanentErrorCount(); + // Health check and error classification + static bool healthCheck(); + static I2SErrorType classifyError(esp_err_t error); + static uint32_t getErrorCount(); + static uint32_t getTransientErrorCount(); + static uint32_t getPermanentErrorCount(); private: - static bool is_initialized; - static int consecutive_errors; - static uint32_t total_errors; - static uint32_t transient_errors; - static uint32_t permanent_errors; + static bool is_initialized; + static int consecutive_errors; + static uint32_t total_errors; + static uint32_t transient_errors; + static uint32_t permanent_errors; }; #endif // I2S_AUDIO_H diff --git a/src/logger.cpp b/src/logger.cpp index 17b2fa7..ffe3254 100644 --- a/src/logger.cpp +++ b/src/logger.cpp @@ -4,99 +4,78 @@ LogLevel Logger::min_level = LOG_INFO; -const char *Logger::level_names[] = { - "DEBUG", - "INFO", - "WARN", - "ERROR", - "CRITICAL"}; +const char *Logger::level_names[] = {"DEBUG", "INFO", "WARN", "ERROR", + "CRITICAL"}; static float _logger_tokens = 0.0f; static uint32_t _logger_last_refill_ms = 0; static uint32_t _logger_suppressed = 0; -static inline void logger_refill_tokens() -{ - uint32_t now = millis(); - if (_logger_last_refill_ms == 0) - { - _logger_last_refill_ms = now; - _logger_tokens = LOGGER_BURST_MAX; - return; - } - uint32_t elapsed = now - _logger_last_refill_ms; - if (elapsed == 0) - return; - float rate_per_ms = (float)LOGGER_MAX_LINES_PER_SEC / 1000.0f; - _logger_tokens += elapsed * rate_per_ms; - if (_logger_tokens > (float)LOGGER_BURST_MAX) - _logger_tokens = (float)LOGGER_BURST_MAX; +static inline void logger_refill_tokens() { + uint32_t now = millis(); + if (_logger_last_refill_ms == 0) { _logger_last_refill_ms = now; + _logger_tokens = LOGGER_BURST_MAX; + return; + } + uint32_t elapsed = now - _logger_last_refill_ms; + if (elapsed == 0) + return; + float rate_per_ms = (float)LOGGER_MAX_LINES_PER_SEC / 1000.0f; + _logger_tokens += elapsed * rate_per_ms; + if (_logger_tokens > (float)LOGGER_BURST_MAX) + _logger_tokens = (float)LOGGER_BURST_MAX; + _logger_last_refill_ms = now; } -void Logger::init(LogLevel level) -{ - min_level = level; - Serial.begin(115200); - delay(1000); - _logger_tokens = LOGGER_BURST_MAX; - _logger_last_refill_ms = millis(); - _logger_suppressed = 0; +void Logger::init(LogLevel level) { + min_level = level; + Serial.begin(115200); + delay(1000); + _logger_tokens = LOGGER_BURST_MAX; + _logger_last_refill_ms = millis(); + _logger_suppressed = 0; } -void Logger::log(LogLevel level, const char *file, int line, const char *fmt, ...) -{ - if (level < min_level) - return; +void Logger::log(LogLevel level, const char *file, int line, const char *fmt, + ...) { + if (level < min_level) + return; - logger_refill_tokens(); - if (_logger_tokens < 1.0f) - { - // Rate limited: drop message and count it - _logger_suppressed++; - return; - } + logger_refill_tokens(); + if (_logger_tokens < 1.0f) { + // Rate limited: drop message and count it + _logger_suppressed++; + return; + } - // If there were suppressed messages and we have enough budget, report once - if (_logger_suppressed > 0 && _logger_tokens >= 2.0f) - { - _logger_tokens -= 1.0f; - Serial.printf("[%6lu] [%-8s] [Heap:%6u] %s (%s:%d)\n", - millis() / 1000, - "INFO", - ESP.getFreeHeap(), - "[logger] Suppressed messages due to rate limiting", - "logger", - 0); - _logger_tokens -= 1.0f; - Serial.printf("[%6lu] [%-8s] [Heap:%6u] Suppressed count: %u (%s:%d)\n", - millis() / 1000, - "INFO", - ESP.getFreeHeap(), - _logger_suppressed, - "logger", - 0); - _logger_suppressed = 0; - } + // If there were suppressed messages and we have enough budget, report once + if (_logger_suppressed > 0 && _logger_tokens >= 2.0f) { + _logger_tokens -= 1.0f; + Serial.printf("[%6lu] [%-8s] [Heap:%6u] %s (%s:%d)\n", millis() / 1000, + "INFO", ESP.getFreeHeap(), + "[logger] Suppressed messages due to rate limiting", "logger", + 0); + _logger_tokens -= 1.0f; + Serial.printf("[%6lu] [%-8s] [Heap:%6u] Suppressed count: %u (%s:%d)\n", + millis() / 1000, "INFO", ESP.getFreeHeap(), + _logger_suppressed, "logger", 0); + _logger_suppressed = 0; + } - char buffer[256]; - va_list args; - va_start(args, fmt); - vsnprintf(buffer, sizeof(buffer), fmt, args); - va_end(args); + char buffer[256]; + va_list args; + va_start(args, fmt); + vsnprintf(buffer, sizeof(buffer), fmt, args); + va_end(args); - // Extract filename from path - const char *filename = strrchr(file, '/'); - if (!filename) - filename = strrchr(file, '\\'); - filename = filename ? filename + 1 : file; + // Extract filename from path + const char *filename = strrchr(file, '/'); + if (!filename) + filename = strrchr(file, '\\'); + filename = filename ? filename + 1 : file; - _logger_tokens -= 1.0f; - Serial.printf("[%6lu] [%-8s] [Heap:%6u] %s (%s:%d)\n", - millis() / 1000, - level_names[level], - ESP.getFreeHeap(), - buffer, - filename, - line); + _logger_tokens -= 1.0f; + Serial.printf("[%6lu] [%-8s] [Heap:%6u] %s (%s:%d)\n", millis() / 1000, + level_names[level], ESP.getFreeHeap(), buffer, filename, line); } diff --git a/src/logger.h b/src/logger.h index 4a14bbf..167596d 100644 --- a/src/logger.h +++ b/src/logger.h @@ -4,29 +4,35 @@ #include enum LogLevel { - LOG_DEBUG = 0, - LOG_INFO = 1, - LOG_WARN = 2, - LOG_ERROR = 3, - LOG_CRITICAL = 4 + LOG_DEBUG = 0, + LOG_INFO = 1, + LOG_WARN = 2, + LOG_ERROR = 3, + LOG_CRITICAL = 4 }; class Logger { private: - static LogLevel min_level; - static const char* level_names[]; + static LogLevel min_level; + static const char *level_names[]; public: - static void init(LogLevel level = LOG_INFO); - static void setLevel(LogLevel level) { min_level = level; } - static void log(LogLevel level, const char* file, int line, const char* fmt, ...); + static void init(LogLevel level = LOG_INFO); + static void setLevel(LogLevel level) { min_level = level; } + static void log(LogLevel level, const char *file, int line, const char *fmt, + ...); }; // Convenience macros -#define LOG_DEBUG(fmt, ...) Logger::log(LOG_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define LOG_INFO(fmt, ...) Logger::log(LOG_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define LOG_WARN(fmt, ...) Logger::log(LOG_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define LOG_ERROR(fmt, ...) Logger::log(LOG_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) -#define LOG_CRITICAL(fmt, ...) Logger::log(LOG_CRITICAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_DEBUG(fmt, ...) \ + Logger::log(LOG_DEBUG, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_INFO(fmt, ...) \ + Logger::log(LOG_INFO, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_WARN(fmt, ...) \ + Logger::log(LOG_WARN, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_ERROR(fmt, ...) \ + Logger::log(LOG_ERROR, __FILE__, __LINE__, fmt, ##__VA_ARGS__) +#define LOG_CRITICAL(fmt, ...) \ + Logger::log(LOG_CRITICAL, __FILE__, __LINE__, fmt, ##__VA_ARGS__) #endif // LOGGER_H diff --git a/src/main.cpp b/src/main.cpp index eadea7a..adeb512 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,14 +1,14 @@ -#include -#include +#include "NonBlockingTimer.h" +#include "StateManager.h" #include "config.h" -#include "logger.h" +#include "config_validator.h" +#include "esp_task_wdt.h" #include "i2s_audio.h" +#include "logger.h" #include "network.h" -#include "StateManager.h" -#include "NonBlockingTimer.h" -#include "config_validator.h" #include "serial_command.h" -#include "esp_task_wdt.h" +#include +#include // ===== Function Declarations ===== void gracefulShutdown(); @@ -16,90 +16,96 @@ void setupOTA(); // ===== Global State Management ===== StateManager systemState; -static uint8_t audio_buffer[I2S_BUFFER_SIZE]; // Static buffer to avoid heap fragmentation -static bool ota_initialized = false; // Track OTA initialization status +static uint8_t + audio_buffer[I2S_BUFFER_SIZE]; // Static buffer to avoid heap fragmentation +static bool ota_initialized = false; // Track OTA initialization status // ===== Statistics ===== struct SystemStats { - uint64_t total_bytes_sent; - uint32_t i2s_errors; - unsigned long uptime_start; - - // Memory tracking for leak detection - uint32_t peak_heap; - uint32_t min_heap; - uint32_t last_heap; - unsigned long last_memory_check; - int32_t heap_trend; // +1 = increasing, -1 = decreasing, 0 = stable - - void init() { - total_bytes_sent = 0; - i2s_errors = 0; - uptime_start = millis(); - - uint32_t current_heap = ESP.getFreeHeap(); - peak_heap = current_heap; - min_heap = current_heap; - last_heap = current_heap; - last_memory_check = millis(); - heap_trend = 0; + uint64_t total_bytes_sent; + uint32_t i2s_errors; + unsigned long uptime_start; + + // Memory tracking for leak detection + uint32_t peak_heap; + uint32_t min_heap; + uint32_t last_heap; + unsigned long last_memory_check; + int32_t heap_trend; // +1 = increasing, -1 = decreasing, 0 = stable + + void init() { + total_bytes_sent = 0; + i2s_errors = 0; + uptime_start = millis(); + + uint32_t current_heap = ESP.getFreeHeap(); + peak_heap = current_heap; + min_heap = current_heap; + last_heap = current_heap; + last_memory_check = millis(); + heap_trend = 0; + } + + void updateMemoryStats() { + uint32_t current_heap = ESP.getFreeHeap(); + + // Update peak and minimum + if (current_heap > peak_heap) + peak_heap = current_heap; + if (current_heap < min_heap) + min_heap = current_heap; + + // Detect heap trend (potential memory leak) + if (current_heap < last_heap - 1000) { + heap_trend = -1; // Decreasing - potential leak + } else if (current_heap > last_heap + 1000) { + heap_trend = 1; // Increasing - memory recovered + } else { + heap_trend = 0; // Stable } - void updateMemoryStats() { - uint32_t current_heap = ESP.getFreeHeap(); - - // Update peak and minimum - if (current_heap > peak_heap) peak_heap = current_heap; - if (current_heap < min_heap) min_heap = current_heap; - - // Detect heap trend (potential memory leak) - if (current_heap < last_heap - 1000) { - heap_trend = -1; // Decreasing - potential leak - } else if (current_heap > last_heap + 1000) { - heap_trend = 1; // Increasing - memory recovered - } else { - heap_trend = 0; // Stable - } - - last_heap = current_heap; - last_memory_check = millis(); + last_heap = current_heap; + last_memory_check = millis(); + } + + void printStats() { + updateMemoryStats(); // Update memory trend before printing + + unsigned long uptime_sec = (millis() - uptime_start) / 1000; + uint32_t current_heap = ESP.getFreeHeap(); + + LOG_INFO("=== System Statistics ==="); + LOG_INFO("Uptime: %lu seconds (%.1f hours)", uptime_sec, + uptime_sec / 3600.0); + LOG_INFO("Data sent: %llu bytes (%.2f MB)", total_bytes_sent, + total_bytes_sent / 1048576.0); + LOG_INFO("WiFi reconnects: %u", NetworkManager::getWiFiReconnectCount()); + LOG_INFO("Server reconnects: %u", + NetworkManager::getServerReconnectCount()); + LOG_INFO("I2S errors: %u (total: %u, transient: %u, permanent: %u)", + i2s_errors, I2SAudio::getErrorCount(), + I2SAudio::getTransientErrorCount(), + I2SAudio::getPermanentErrorCount()); + LOG_INFO("TCP errors: %u", NetworkManager::getTCPErrorCount()); + + // Memory statistics + LOG_INFO("--- Memory Statistics ---"); + LOG_INFO("Current heap: %u bytes", current_heap); + LOG_INFO("Peak heap: %u bytes", peak_heap); + LOG_INFO("Min heap: %u bytes", min_heap); + LOG_INFO("Heap range: %u bytes", peak_heap - min_heap); + + // Detect potential memory leak + if (heap_trend == -1) { + LOG_WARN("Memory trend: DECREASING (potential leak)"); + } else if (heap_trend == 1) { + LOG_INFO("Memory trend: INCREASING (recovered)"); + } else { + LOG_INFO("Memory trend: STABLE"); } - void printStats() { - updateMemoryStats(); // Update memory trend before printing - - unsigned long uptime_sec = (millis() - uptime_start) / 1000; - uint32_t current_heap = ESP.getFreeHeap(); - - LOG_INFO("=== System Statistics ==="); - LOG_INFO("Uptime: %lu seconds (%.1f hours)", uptime_sec, uptime_sec / 3600.0); - LOG_INFO("Data sent: %llu bytes (%.2f MB)", total_bytes_sent, total_bytes_sent / 1048576.0); - LOG_INFO("WiFi reconnects: %u", NetworkManager::getWiFiReconnectCount()); - LOG_INFO("Server reconnects: %u", NetworkManager::getServerReconnectCount()); - LOG_INFO("I2S errors: %u (total: %u, transient: %u, permanent: %u)", - i2s_errors, I2SAudio::getErrorCount(), - I2SAudio::getTransientErrorCount(), - I2SAudio::getPermanentErrorCount()); - LOG_INFO("TCP errors: %u", NetworkManager::getTCPErrorCount()); - - // Memory statistics - LOG_INFO("--- Memory Statistics ---"); - LOG_INFO("Current heap: %u bytes", current_heap); - LOG_INFO("Peak heap: %u bytes", peak_heap); - LOG_INFO("Min heap: %u bytes", min_heap); - LOG_INFO("Heap range: %u bytes", peak_heap - min_heap); - - // Detect potential memory leak - if (heap_trend == -1) { - LOG_WARN("Memory trend: DECREASING (potential leak)"); - } else if (heap_trend == 1) { - LOG_INFO("Memory trend: INCREASING (recovered)"); - } else { - LOG_INFO("Memory trend: STABLE"); - } - - LOG_INFO("========================"); - } + LOG_INFO("========================"); + } } stats; // ===== Timers ===== @@ -108,347 +114,346 @@ NonBlockingTimer statsPrintTimer(STATS_PRINT_INTERVAL, true); // ===== Memory Monitoring ===== void checkMemoryHealth() { - if (!memoryCheckTimer.check()) return; - - // Update memory tracking statistics - stats.updateMemoryStats(); - - uint32_t free_heap = ESP.getFreeHeap(); - - if (free_heap < MEMORY_CRITICAL_THRESHOLD) { - LOG_CRITICAL("Critical low memory: %u bytes - system may crash", free_heap); - // Consider restarting if critically low - if (free_heap < MEMORY_CRITICAL_THRESHOLD / 2) { - LOG_CRITICAL("Memory critically low - initiating graceful restart"); - gracefulShutdown(); - ESP.restart(); - } - } else if (free_heap < MEMORY_WARN_THRESHOLD) { - LOG_WARN("Memory low: %u bytes", free_heap); - } + if (!memoryCheckTimer.check()) + return; + + // Update memory tracking statistics + stats.updateMemoryStats(); - // Warn about potential memory leak - if (stats.heap_trend == -1) { - LOG_WARN("Memory usage trending downward (potential leak detected)"); + uint32_t free_heap = ESP.getFreeHeap(); + + if (free_heap < MEMORY_CRITICAL_THRESHOLD) { + LOG_CRITICAL("Critical low memory: %u bytes - system may crash", free_heap); + // Consider restarting if critically low + if (free_heap < MEMORY_CRITICAL_THRESHOLD / 2) { + LOG_CRITICAL("Memory critically low - initiating graceful restart"); + gracefulShutdown(); + ESP.restart(); } + } else if (free_heap < MEMORY_WARN_THRESHOLD) { + LOG_WARN("Memory low: %u bytes", free_heap); + } + + // Warn about potential memory leak + if (stats.heap_trend == -1) { + LOG_WARN("Memory usage trending downward (potential leak detected)"); + } } // ===== State Change Callback ===== void onStateChange(SystemState from, SystemState to) { - LOG_INFO("State transition: %s → %s", - systemState.stateToString(from).c_str(), - systemState.stateToString(to).c_str()); + LOG_INFO("State transition: %s → %s", systemState.stateToString(from).c_str(), + systemState.stateToString(to).c_str()); } // ===== Graceful Shutdown ===== void gracefulShutdown() { - LOG_INFO("========================================"); - LOG_INFO("Initiating graceful shutdown..."); - LOG_INFO("========================================"); + LOG_INFO("========================================"); + LOG_INFO("Initiating graceful shutdown..."); + LOG_INFO("========================================"); - // Print final statistics - stats.printStats(); + // Print final statistics + stats.printStats(); - // Close TCP connection - if (NetworkManager::isServerConnected()) { - LOG_INFO("Closing server connection..."); - NetworkManager::disconnectFromServer(); - delay(GRACEFUL_SHUTDOWN_DELAY); - } + // Close TCP connection + if (NetworkManager::isServerConnected()) { + LOG_INFO("Closing server connection..."); + NetworkManager::disconnectFromServer(); + delay(GRACEFUL_SHUTDOWN_DELAY); + } - // Stop I2S audio - LOG_INFO("Stopping I2S audio..."); - I2SAudio::cleanup(); + // Stop I2S audio + LOG_INFO("Stopping I2S audio..."); + I2SAudio::cleanup(); - // Disconnect WiFi - LOG_INFO("Disconnecting WiFi..."); - WiFi.disconnect(true); - delay(GRACEFUL_SHUTDOWN_DELAY); + // Disconnect WiFi + LOG_INFO("Disconnecting WiFi..."); + WiFi.disconnect(true); + delay(GRACEFUL_SHUTDOWN_DELAY); - LOG_INFO("Shutdown complete. Ready for restart."); - delay(1000); + LOG_INFO("Shutdown complete. Ready for restart."); + delay(1000); } // ===== OTA Setup ===== void setupOTA() { - if (ota_initialized) { - return; // Already initialized + if (ota_initialized) { + return; // Already initialized + } + + // Set hostname for network identification + ArduinoOTA.setHostname("ESP32-AudioStreamer"); + + // Optional: Set password for OTA security (uncomment to enable) + // ArduinoOTA.setPassword("your_ota_password"); + + // Set port (default is 3232) + ArduinoOTA.setPort(3232); + + // Configure OTA event handlers + ArduinoOTA.onStart([]() { + String type; + if (ArduinoOTA.getCommand() == U_FLASH) { + type = "sketch"; + } else { // U_SPIFFS + type = "filesystem"; } - // Set hostname for network identification - ArduinoOTA.setHostname("ESP32-AudioStreamer"); - - // Optional: Set password for OTA security (uncomment to enable) - // ArduinoOTA.setPassword("your_ota_password"); - - // Set port (default is 3232) - ArduinoOTA.setPort(3232); - - // Configure OTA event handlers - ArduinoOTA.onStart([]() { - String type; - if (ArduinoOTA.getCommand() == U_FLASH) { - type = "sketch"; - } else { // U_SPIFFS - type = "filesystem"; - } - - LOG_INFO("========================================"); - LOG_INFO("OTA Update Started"); - LOG_INFO("Type: %s", type.c_str()); - LOG_INFO("========================================"); - - // Stop audio streaming during update - I2SAudio::cleanup(); - - // Disconnect from server to free resources - NetworkManager::disconnectFromServer(); - }); - - ArduinoOTA.onEnd([]() { - LOG_INFO("========================================"); - LOG_INFO("OTA Update Complete"); - LOG_INFO("Rebooting..."); - LOG_INFO("========================================"); - }); - - ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { - static unsigned int lastPercent = 0; - unsigned int percent = (progress / (total / 100)); - - // Log progress every 10% to avoid flooding logs - if (percent != lastPercent && percent % 10 == 0) { - LOG_INFO("OTA Progress: %u%%", percent); - lastPercent = percent; - } - }); - - ArduinoOTA.onError([](ota_error_t error) { - LOG_ERROR("========================================"); - LOG_ERROR("OTA Update Failed"); - - if (error == OTA_AUTH_ERROR) { - LOG_ERROR("Error: Authentication Failed"); - } else if (error == OTA_BEGIN_ERROR) { - LOG_ERROR("Error: Begin Failed"); - } else if (error == OTA_CONNECT_ERROR) { - LOG_ERROR("Error: Connect Failed"); - } else if (error == OTA_RECEIVE_ERROR) { - LOG_ERROR("Error: Receive Failed"); - } else if (error == OTA_END_ERROR) { - LOG_ERROR("Error: End Failed"); - } else { - LOG_ERROR("Error: Unknown (%u)", error); - } - - LOG_ERROR("========================================"); - - // Try to recover by restarting after a delay - delay(5000); - ESP.restart(); - }); - - // Start OTA service - ArduinoOTA.begin(); - ota_initialized = true; - LOG_INFO("========================================"); - LOG_INFO("OTA Update Service Started"); - LOG_INFO("Hostname: ESP32-AudioStreamer"); - LOG_INFO("IP Address: %s", WiFi.localIP().toString().c_str()); - LOG_INFO("Port: 3232"); + LOG_INFO("OTA Update Started"); + LOG_INFO("Type: %s", type.c_str()); LOG_INFO("========================================"); -} -// ===== Setup ===== -void setup() { - // Initialize logger (align with compile-time DEBUG_LEVEL) - LogLevel bootLogLevel = LOG_INFO; - #if DEBUG_LEVEL >= 4 - bootLogLevel = LOG_DEBUG; - #elif DEBUG_LEVEL == 3 - bootLogLevel = LOG_INFO; - #elif DEBUG_LEVEL == 2 - bootLogLevel = LOG_WARN; - #elif DEBUG_LEVEL == 1 - bootLogLevel = LOG_ERROR; - #else - bootLogLevel = LOG_CRITICAL; - #endif - Logger::init(bootLogLevel); + // Stop audio streaming during update + I2SAudio::cleanup(); + + // Disconnect from server to free resources + NetworkManager::disconnectFromServer(); + }); + + ArduinoOTA.onEnd([]() { LOG_INFO("========================================"); - LOG_INFO("ESP32 Audio Streamer Starting Up"); - LOG_INFO("Board: %s", BOARD_NAME); - LOG_INFO("Version: 2.0 (Reliability-Enhanced)"); + LOG_INFO("OTA Update Complete"); + LOG_INFO("Rebooting..."); LOG_INFO("========================================"); + }); + + ArduinoOTA.onProgress([](unsigned int progress, unsigned int total) { + static unsigned int lastPercent = 0; + unsigned int percent = (progress / (total / 100)); - // Initialize statistics - stats.init(); - - // Validate configuration before proceeding - if (!ConfigValidator::validateAll()) { - LOG_CRITICAL("Configuration validation failed - cannot start system"); - LOG_CRITICAL("Please check config.h and fix the issues listed above"); - systemState.setState(SystemState::ERROR); - while (1) { - delay(ERROR_RECOVERY_DELAY); - LOG_CRITICAL("Waiting for configuration fix..."); - } + // Log progress every 10% to avoid flooding logs + if (percent != lastPercent && percent % 10 == 0) { + LOG_INFO("OTA Progress: %u%%", percent); + lastPercent = percent; } + }); + + ArduinoOTA.onError([](ota_error_t error) { + LOG_ERROR("========================================"); + LOG_ERROR("OTA Update Failed"); + + if (error == OTA_AUTH_ERROR) { + LOG_ERROR("Error: Authentication Failed"); + } else if (error == OTA_BEGIN_ERROR) { + LOG_ERROR("Error: Begin Failed"); + } else if (error == OTA_CONNECT_ERROR) { + LOG_ERROR("Error: Connect Failed"); + } else if (error == OTA_RECEIVE_ERROR) { + LOG_ERROR("Error: Receive Failed"); + } else if (error == OTA_END_ERROR) { + LOG_ERROR("Error: End Failed"); + } else { + LOG_ERROR("Error: Unknown (%u)", error); + } + + LOG_ERROR("========================================"); + + // Try to recover by restarting after a delay + delay(5000); + ESP.restart(); + }); + + // Start OTA service + ArduinoOTA.begin(); + ota_initialized = true; - // Initialize state manager with callback - systemState.onStateChange(onStateChange); - systemState.setState(SystemState::INITIALIZING); - - // Initialize I2S - if (!I2SAudio::initialize()) { - LOG_CRITICAL("I2S initialization failed - cannot continue"); - systemState.setState(SystemState::ERROR); - while (1) { - delay(1000); - } + LOG_INFO("========================================"); + LOG_INFO("OTA Update Service Started"); + LOG_INFO("Hostname: ESP32-AudioStreamer"); + LOG_INFO("IP Address: %s", WiFi.localIP().toString().c_str()); + LOG_INFO("Port: 3232"); + LOG_INFO("========================================"); +} + +// ===== Setup ===== +void setup() { + // Initialize logger (align with compile-time DEBUG_LEVEL) + LogLevel bootLogLevel = LOG_INFO; +#if DEBUG_LEVEL >= 4 + bootLogLevel = LOG_DEBUG; +#elif DEBUG_LEVEL == 3 + bootLogLevel = LOG_INFO; +#elif DEBUG_LEVEL == 2 + bootLogLevel = LOG_WARN; +#elif DEBUG_LEVEL == 1 + bootLogLevel = LOG_ERROR; +#else + bootLogLevel = LOG_CRITICAL; +#endif + Logger::init(bootLogLevel); + LOG_INFO("========================================"); + LOG_INFO("ESP32 Audio Streamer Starting Up"); + LOG_INFO("Board: %s", BOARD_NAME); + LOG_INFO("Version: 2.0 (Reliability-Enhanced)"); + LOG_INFO("========================================"); + + // Initialize statistics + stats.init(); + + // Validate configuration before proceeding + if (!ConfigValidator::validateAll()) { + LOG_CRITICAL("Configuration validation failed - cannot start system"); + LOG_CRITICAL("Please check config.h and fix the issues listed above"); + systemState.setState(SystemState::ERROR); + while (1) { + delay(ERROR_RECOVERY_DELAY); + LOG_CRITICAL("Waiting for configuration fix..."); } + } + + // Initialize state manager with callback + systemState.onStateChange(onStateChange); + systemState.setState(SystemState::INITIALIZING); + + // Initialize I2S + if (!I2SAudio::initialize()) { + LOG_CRITICAL("I2S initialization failed - cannot continue"); + systemState.setState(SystemState::ERROR); + while (1) { + delay(1000); + } + } - // Initialize network - NetworkManager::initialize(); + // Initialize network + NetworkManager::initialize(); - // Initialize serial command handler - SerialCommandHandler::initialize(); + // Initialize serial command handler + SerialCommandHandler::initialize(); - // Start memory and stats timers - memoryCheckTimer.start(); - statsPrintTimer.start(); + // Start memory and stats timers + memoryCheckTimer.start(); + statsPrintTimer.start(); - // Move to WiFi connection state - systemState.setState(SystemState::CONNECTING_WIFI); + // Move to WiFi connection state + systemState.setState(SystemState::CONNECTING_WIFI); - // Initialize and configure watchdog to a safe timeout - // Ensure timeout comfortably exceeds WiFi timeouts and recovery delays - esp_task_wdt_init(WATCHDOG_TIMEOUT_SEC, true); - esp_task_wdt_add(NULL); + // Initialize and configure watchdog to a safe timeout + // Ensure timeout comfortably exceeds WiFi timeouts and recovery delays + esp_task_wdt_init(WATCHDOG_TIMEOUT_SEC, true); + esp_task_wdt_add(NULL); - LOG_INFO("Setup complete - entering main loop"); + LOG_INFO("Setup complete - entering main loop"); } // ===== Main Loop with State Machine ===== void loop() { - // Feed watchdog timer - esp_task_wdt_reset(); + // Feed watchdog timer + esp_task_wdt_reset(); + + // Handle OTA updates (must be called frequently) + ArduinoOTA.handle(); + + // Process serial commands (non-blocking) + SerialCommandHandler::processCommands(); - // Handle OTA updates (must be called frequently) - ArduinoOTA.handle(); + // Handle WiFi connection (non-blocking) + NetworkManager::handleWiFiConnection(); - // Process serial commands (non-blocking) - SerialCommandHandler::processCommands(); + // Monitor WiFi quality + NetworkManager::monitorWiFiQuality(); - // Handle WiFi connection (non-blocking) - NetworkManager::handleWiFiConnection(); + // Check memory health + checkMemoryHealth(); - // Monitor WiFi quality - NetworkManager::monitorWiFiQuality(); + // Print statistics periodically + if (statsPrintTimer.check()) { + stats.printStats(); + } + + // State machine + switch (systemState.getState()) { + case SystemState::INITIALIZING: + // Should not reach here after setup + systemState.setState(SystemState::CONNECTING_WIFI); + break; + + case SystemState::CONNECTING_WIFI: + if (NetworkManager::isWiFiConnected()) { + LOG_INFO("WiFi connected - IP: %s", WiFi.localIP().toString().c_str()); + + // Initialize OTA once WiFi is connected + setupOTA(); + + systemState.setState(SystemState::CONNECTING_SERVER); + } else if (systemState.hasStateTimedOut(WIFI_TIMEOUT)) { + LOG_ERROR("WiFi connection timeout"); + systemState.setState(SystemState::ERROR); + } + break; + + case SystemState::CONNECTING_SERVER: + if (!NetworkManager::isWiFiConnected()) { + LOG_WARN("WiFi lost while connecting to server"); + systemState.setState(SystemState::CONNECTING_WIFI); + break; + } - // Check memory health - checkMemoryHealth(); + if (NetworkManager::connectToServer()) { + systemState.setState(SystemState::CONNECTED); + } + // Timeout handled by exponential backoff in NetworkManager + break; + + case SystemState::CONNECTED: { + // Verify WiFi is still connected + if (!NetworkManager::isWiFiConnected()) { + LOG_WARN("WiFi lost during streaming"); + NetworkManager::disconnectFromServer(); + systemState.setState(SystemState::CONNECTING_WIFI); + break; + } - // Print statistics periodically - if (statsPrintTimer.check()) { - stats.printStats(); + // Verify server connection + if (!NetworkManager::isServerConnected()) { + LOG_WARN("Server connection lost"); + systemState.setState(SystemState::CONNECTING_SERVER); + break; } - // State machine - switch (systemState.getState()) { - case SystemState::INITIALIZING: - // Should not reach here after setup - systemState.setState(SystemState::CONNECTING_WIFI); - break; - - case SystemState::CONNECTING_WIFI: - if (NetworkManager::isWiFiConnected()) { - LOG_INFO("WiFi connected - IP: %s", WiFi.localIP().toString().c_str()); - - // Initialize OTA once WiFi is connected - setupOTA(); - - systemState.setState(SystemState::CONNECTING_SERVER); - } else if (systemState.hasStateTimedOut(WIFI_TIMEOUT)) { - LOG_ERROR("WiFi connection timeout"); - systemState.setState(SystemState::ERROR); - } - break; - - case SystemState::CONNECTING_SERVER: - if (!NetworkManager::isWiFiConnected()) { - LOG_WARN("WiFi lost while connecting to server"); - systemState.setState(SystemState::CONNECTING_WIFI); - break; - } - - if (NetworkManager::connectToServer()) { - systemState.setState(SystemState::CONNECTED); - } - // Timeout handled by exponential backoff in NetworkManager - break; - - case SystemState::CONNECTED: - { - // Verify WiFi is still connected - if (!NetworkManager::isWiFiConnected()) { - LOG_WARN("WiFi lost during streaming"); - NetworkManager::disconnectFromServer(); - systemState.setState(SystemState::CONNECTING_WIFI); - break; - } - - // Verify server connection - if (!NetworkManager::isServerConnected()) { - LOG_WARN("Server connection lost"); - systemState.setState(SystemState::CONNECTING_SERVER); - break; - } - - // Read audio data with retry - size_t bytes_read = 0; - if (I2SAudio::readDataWithRetry(audio_buffer, I2S_BUFFER_SIZE, &bytes_read)) { - // Send data to server - if (NetworkManager::writeData(audio_buffer, bytes_read)) { - stats.total_bytes_sent += bytes_read; - } else { - // Write failed - let NetworkManager handle reconnection - LOG_WARN("Data transmission failed"); - systemState.setState(SystemState::CONNECTING_SERVER); - } - } else { - // I2S read failed even after retries - stats.i2s_errors++; - LOG_ERROR("I2S read failed after retries"); - - // If too many consecutive errors, may need to reinitialize - // (handled internally by I2SAudio) - } - - // Small delay to allow background tasks - delay(TASK_YIELD_DELAY); - } - break; - - case SystemState::DISCONNECTED: - // Attempt to reconnect - systemState.setState(SystemState::CONNECTING_SERVER); - break; - - case SystemState::ERROR: - LOG_ERROR("System in error state - attempting recovery..."); - delay(ERROR_RECOVERY_DELAY); - - // Try to recover - NetworkManager::disconnectFromServer(); - systemState.setState(SystemState::CONNECTING_WIFI); - break; - - case SystemState::MAINTENANCE: - // Reserved for future use (e.g., firmware updates) - LOG_INFO("System in maintenance mode"); - delay(ERROR_RECOVERY_DELAY); - break; + // Read audio data with retry + size_t bytes_read = 0; + if (I2SAudio::readDataWithRetry(audio_buffer, I2S_BUFFER_SIZE, + &bytes_read)) { + // Send data to server + if (NetworkManager::writeData(audio_buffer, bytes_read)) { + stats.total_bytes_sent += bytes_read; + } else { + // Write failed - let NetworkManager handle reconnection + LOG_WARN("Data transmission failed"); + systemState.setState(SystemState::CONNECTING_SERVER); + } + } else { + // I2S read failed even after retries + stats.i2s_errors++; + LOG_ERROR("I2S read failed after retries"); + + // If too many consecutive errors, may need to reinitialize + // (handled internally by I2SAudio) } + + // Small delay to allow background tasks + delay(TASK_YIELD_DELAY); + } break; + + case SystemState::DISCONNECTED: + // Attempt to reconnect + systemState.setState(SystemState::CONNECTING_SERVER); + break; + + case SystemState::ERROR: + LOG_ERROR("System in error state - attempting recovery..."); + delay(ERROR_RECOVERY_DELAY); + + // Try to recover + NetworkManager::disconnectFromServer(); + systemState.setState(SystemState::CONNECTING_WIFI); + break; + + case SystemState::MAINTENANCE: + // Reserved for future use (e.g., firmware updates) + LOG_INFO("System in maintenance mode"); + delay(ERROR_RECOVERY_DELAY); + break; + } } \ No newline at end of file diff --git a/src/network.cpp b/src/network.cpp index a12e3ca..75426a4 100644 --- a/src/network.cpp +++ b/src/network.cpp @@ -1,98 +1,91 @@ #include "network.h" -#include "logger.h" #include "esp_task_wdt.h" -#include +#include "logger.h" +#include #include +#include #include -#include // Helper macro for setsockopt with error checking -#define SET_SOCKOPT(fd, level, optname, value) \ - do \ - { \ - if (setsockopt(fd, level, optname, &(value), sizeof(value)) != 0) \ - { \ - LOG_WARN("Failed to set socket option %s: errno=%d", #optname, errno); \ - } \ - } while (0) +#define SET_SOCKOPT(fd, level, optname, value) \ + do { \ + if (setsockopt(fd, level, optname, &(value), sizeof(value)) != 0) { \ + LOG_WARN("Failed to set socket option %s: errno=%d", #optname, errno); \ + } \ + } while (0) // Helper macro for struct timeval setsockopt -#define SET_SOCKOPT_TIMEVAL(fd, level, optname, tv) \ - do \ - { \ - if (setsockopt(fd, level, optname, &(tv), sizeof(tv)) != 0) \ - { \ - LOG_WARN("Failed to set socket option %s: errno=%d", #optname, errno); \ - } \ - } while (0) +#define SET_SOCKOPT_TIMEVAL(fd, level, optname, tv) \ + do { \ + if (setsockopt(fd, level, optname, &(tv), sizeof(tv)) != 0) { \ + LOG_WARN("Failed to set socket option %s: errno=%d", #optname, errno); \ + } \ + } while (0) // Simple LCG for jitter generation (no to keep footprint small) static uint32_t _nb_rng = 2166136261u; -static inline uint32_t nb_rand() -{ - _nb_rng = _nb_rng * 1664525u + 1013904223u; - return _nb_rng; +static inline uint32_t nb_rand() { + _nb_rng = _nb_rng * 1664525u + 1013904223u; + return _nb_rng; } -static inline unsigned long apply_jitter(unsigned long base_ms) -{ +static inline unsigned long apply_jitter(unsigned long base_ms) { #if SERVER_BACKOFF_JITTER_PCT > 0 - uint32_t r = nb_rand(); - - // Calculate jitter range with safety check for negative values and overflow protection - int32_t jitter_range = (int32_t)((uint64_t)base_ms * SERVER_BACKOFF_JITTER_PCT / 100); - if (jitter_range < 0) - { - jitter_range = 0; // Safety: prevent negative range - } - - // Apply random jitter within [-jitter_range, +jitter_range] - // Use safe cast to prevent integer overflow in modulo operation - uint32_t jitter_span = (2u * (uint32_t)jitter_range) + 1u; - int32_t jitter = (int32_t)(r % jitter_span) - jitter_range; - - // Apply jitter and bounds-check the result - long with_jitter = (long)base_ms + jitter; - if (with_jitter < (long)SERVER_RECONNECT_MIN) - { - with_jitter = SERVER_RECONNECT_MIN; - } - if ((unsigned long)with_jitter > SERVER_RECONNECT_MAX) - { - with_jitter = SERVER_RECONNECT_MAX; - } - - return (unsigned long)with_jitter; + uint32_t r = nb_rand(); + + // Calculate jitter range with safety check for negative values and overflow + // protection + int32_t jitter_range = + (int32_t)((uint64_t)base_ms * SERVER_BACKOFF_JITTER_PCT / 100); + if (jitter_range < 0) { + jitter_range = 0; // Safety: prevent negative range + } + + // Apply random jitter within [-jitter_range, +jitter_range] + // Use safe cast to prevent integer overflow in modulo operation + uint32_t jitter_span = (2u * (uint32_t)jitter_range) + 1u; + int32_t jitter = (int32_t)(r % jitter_span) - jitter_range; + + // Apply jitter and bounds-check the result + long with_jitter = (long)base_ms + jitter; + if (with_jitter < (long)SERVER_RECONNECT_MIN) { + with_jitter = SERVER_RECONNECT_MIN; + } + if ((unsigned long)with_jitter > SERVER_RECONNECT_MAX) { + with_jitter = SERVER_RECONNECT_MAX; + } + + return (unsigned long)with_jitter; #else - return base_ms; + return base_ms; #endif } // ExponentialBackoff implementation -ExponentialBackoff::ExponentialBackoff(unsigned long min_ms, unsigned long max_ms) - : min_delay(min_ms), max_delay(max_ms), current_delay(min_ms), consecutive_failures(0) {} - -unsigned long ExponentialBackoff::getNextDelay() -{ - if (consecutive_failures > 0) - { - current_delay = min(current_delay * 2, max_delay); - } - consecutive_failures++; - // Apply jitter to avoid sync storms - return apply_jitter(current_delay); +ExponentialBackoff::ExponentialBackoff(unsigned long min_ms, + unsigned long max_ms) + : min_delay(min_ms), max_delay(max_ms), current_delay(min_ms), + consecutive_failures(0) {} + +unsigned long ExponentialBackoff::getNextDelay() { + if (consecutive_failures > 0) { + current_delay = min(current_delay * 2, max_delay); + } + consecutive_failures++; + // Apply jitter to avoid sync storms + return apply_jitter(current_delay); } -void ExponentialBackoff::reset() -{ - consecutive_failures = 0; - current_delay = min_delay; +void ExponentialBackoff::reset() { + consecutive_failures = 0; + current_delay = min_delay; } // NetworkManager static members bool NetworkManager::server_connected = false; unsigned long NetworkManager::last_successful_write = 0; NonBlockingTimer NetworkManager::wifi_retry_timer(WIFI_RETRY_DELAY, true); -NonBlockingTimer NetworkManager::server_retry_timer(SERVER_RECONNECT_MIN, false); +NonBlockingTimer NetworkManager::server_retry_timer(SERVER_RECONNECT_MIN, + false); NonBlockingTimer NetworkManager::rssi_check_timer(RSSI_CHECK_INTERVAL, true); ExponentialBackoff NetworkManager::server_backoff; WiFiClient NetworkManager::client; @@ -107,429 +100,385 @@ unsigned long NetworkManager::tcp_state_change_time = 0; unsigned long NetworkManager::tcp_connection_established_time = 0; uint32_t NetworkManager::tcp_state_changes = 0; -void NetworkManager::initialize() -{ - LOG_INFO("Initializing network..."); +void NetworkManager::initialize() { + LOG_INFO("Initializing network..."); - // Initialize adaptive buffer management - AdaptiveBuffer::initialize(I2S_BUFFER_SIZE); + // Initialize adaptive buffer management + AdaptiveBuffer::initialize(I2S_BUFFER_SIZE); - // Configure WiFi for reliability - WiFi.mode(WIFI_STA); - WiFi.setAutoReconnect(true); - WiFi.setSleep(false); // Prevent power-save disconnects - WiFi.persistent(false); // Reduce flash wear + // Configure WiFi for reliability + WiFi.mode(WIFI_STA); + WiFi.setAutoReconnect(true); + WiFi.setSleep(false); // Prevent power-save disconnects + WiFi.persistent(false); // Reduce flash wear // Configure static IP if enabled #ifdef USE_STATIC_IP - IPAddress local_IP(STATIC_IP); - IPAddress gateway(GATEWAY_IP); - IPAddress subnet(SUBNET_MASK); - IPAddress dns(DNS_IP); - if (WiFi.config(local_IP, gateway, subnet, dns)) - { - LOG_INFO("Static IP configured: %s", local_IP.toString().c_str()); - } - else - { - LOG_ERROR("Static IP configuration failed - falling back to DHCP"); - } + IPAddress local_IP(STATIC_IP); + IPAddress gateway(GATEWAY_IP); + IPAddress subnet(SUBNET_MASK); + IPAddress dns(DNS_IP); + if (WiFi.config(local_IP, gateway, subnet, dns)) { + LOG_INFO("Static IP configured: %s", local_IP.toString().c_str()); + } else { + LOG_ERROR("Static IP configuration failed - falling back to DHCP"); + } #endif - // Start WiFi connection - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - wifi_retry_timer.start(); - wifi_retry_count = 0; + // Start WiFi connection + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + wifi_retry_timer.start(); + wifi_retry_count = 0; - // Start server retry timer in expired state for immediate first connection attempt - // This avoids unnecessary 5-second startup delay - server_retry_timer.startExpired(); + // Start server retry timer in expired state for immediate first connection + // attempt This avoids unnecessary 5-second startup delay + server_retry_timer.startExpired(); - LOG_INFO("Network initialization started"); + LOG_INFO("Network initialization started"); } -void NetworkManager::handleWiFiConnection() -{ - // If already connected, just return - if (WiFi.status() == WL_CONNECTED) - { - if (wifi_retry_count > 0) - { - // Just connected after retries - LOG_INFO("WiFi connected after %d attempts", wifi_retry_count); - wifi_reconnect_count++; - wifi_retry_count = 0; - } - return; +void NetworkManager::handleWiFiConnection() { + // If already connected, just return + if (WiFi.status() == WL_CONNECTED) { + if (wifi_retry_count > 0) { + // Just connected after retries + LOG_INFO("WiFi connected after %d attempts", wifi_retry_count); + wifi_reconnect_count++; + wifi_retry_count = 0; } + return; + } - // Not connected - handle reconnection with non-blocking timer - if (!wifi_retry_timer.check()) - { - return; // Not time to retry yet - } - - // Feed watchdog to prevent resets during connection - esp_task_wdt_reset(); + // Not connected - handle reconnection with non-blocking timer + if (!wifi_retry_timer.check()) { + return; // Not time to retry yet + } - if (wifi_retry_count == 0) - { - LOG_WARN("WiFi disconnected - attempting reconnection..."); - WiFi.begin(WIFI_SSID, WIFI_PASSWORD); - server_connected = false; - client.stop(); - } + // Feed watchdog to prevent resets during connection + esp_task_wdt_reset(); - wifi_retry_count++; - - if (wifi_retry_count > WIFI_MAX_RETRIES) - { - // Enter safe backoff mode instead of rebooting; keep serial alive - unsigned long backoff = 1000UL * (wifi_retry_count - WIFI_MAX_RETRIES); - if (backoff > 30000UL) - backoff = 30000UL; - // Add small jitter to avoid herd reconnects - backoff = apply_jitter(backoff); - LOG_CRITICAL("WiFi connection failed after %d attempts - backing off %lu ms (no reboot)", WIFI_MAX_RETRIES, backoff); - wifi_retry_timer.setInterval(backoff); - wifi_retry_timer.start(); - wifi_retry_count = WIFI_MAX_RETRIES; // clamp to avoid overflow - return; - } + if (wifi_retry_count == 0) { + LOG_WARN("WiFi disconnected - attempting reconnection..."); + WiFi.begin(WIFI_SSID, WIFI_PASSWORD); + server_connected = false; + client.stop(); + } + + wifi_retry_count++; + + if (wifi_retry_count > WIFI_MAX_RETRIES) { + // Enter safe backoff mode instead of rebooting; keep serial alive + unsigned long backoff = 1000UL * (wifi_retry_count - WIFI_MAX_RETRIES); + if (backoff > 30000UL) + backoff = 30000UL; + // Add small jitter to avoid herd reconnects + backoff = apply_jitter(backoff); + LOG_CRITICAL("WiFi connection failed after %d attempts - backing off %lu " + "ms (no reboot)", + WIFI_MAX_RETRIES, backoff); + wifi_retry_timer.setInterval(backoff); + wifi_retry_timer.start(); + wifi_retry_count = WIFI_MAX_RETRIES; // clamp to avoid overflow + return; + } } -bool NetworkManager::isWiFiConnected() -{ - return WiFi.status() == WL_CONNECTED; -} +bool NetworkManager::isWiFiConnected() { return WiFi.status() == WL_CONNECTED; } -void NetworkManager::monitorWiFiQuality() -{ - if (!rssi_check_timer.check()) - return; - if (!isWiFiConnected()) - return; +void NetworkManager::monitorWiFiQuality() { + if (!rssi_check_timer.check()) + return; + if (!isWiFiConnected()) + return; - int32_t rssi = WiFi.RSSI(); + int32_t rssi = WiFi.RSSI(); - // Update adaptive buffer based on signal strength - AdaptiveBuffer::updateBufferSize(rssi); + // Update adaptive buffer based on signal strength + AdaptiveBuffer::updateBufferSize(rssi); - if (rssi < RSSI_WEAK_THRESHOLD) - { - LOG_WARN("Weak WiFi signal: %d dBm - increasing buffer, avoiding forced disconnect", rssi); - // No forced disconnect; rely on natural link loss and adaptive buffering - } - else if (rssi < -70) - { - LOG_WARN("WiFi signal degraded: %d dBm", rssi); - } + if (rssi < RSSI_WEAK_THRESHOLD) { + LOG_WARN("Weak WiFi signal: %d dBm - increasing buffer, avoiding forced " + "disconnect", + rssi); + // No forced disconnect; rely on natural link loss and adaptive buffering + } else if (rssi < -70) { + LOG_WARN("WiFi signal degraded: %d dBm", rssi); + } } -bool NetworkManager::connectToServer() -{ - if (!isWiFiConnected()) - { - return false; - } +bool NetworkManager::connectToServer() { + if (!isWiFiConnected()) { + return false; + } - // Check if it's time to retry (using exponential backoff) - if (!server_retry_timer.isExpired()) - { - return false; - } + // Check if it's time to retry (using exponential backoff) + if (!server_retry_timer.isExpired()) { + return false; + } - // Update state to CONNECTING - updateTCPState(TCPConnectionState::CONNECTING); - - LOG_INFO("Attempting to connect to server %s:%d (attempt %d)...", - SERVER_HOST, SERVER_PORT, server_backoff.getFailureCount() + 1); - - // Feed watchdog during connection attempt - esp_task_wdt_reset(); - - if (client.connect(SERVER_HOST, SERVER_PORT)) - { - LOG_INFO("Server connection established"); - server_connected = true; - last_successful_write = millis(); - server_backoff.reset(); - server_reconnect_count++; - - // Update state to CONNECTED - updateTCPState(TCPConnectionState::CONNECTED); - - // Configure TCP socket options for low-latency audio streaming - int sockfd = client.fd(); - if (sockfd >= 0) - { - // TCP_NODELAY: Disable Nagle's algorithm for low-latency streaming - // Server expects immediate audio chunks without buffering delays - // This matches server's receiver.py configuration: conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - int nodelay = 1; - SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_NODELAY, nodelay); - - // TCP keepalive: Detect stale connections - int keepAlive = 1; - int keepIdle = TCP_KEEPALIVE_IDLE; // seconds - int keepInterval = TCP_KEEPALIVE_INTERVAL; // seconds - int keepCount = TCP_KEEPALIVE_COUNT; // count - - SET_SOCKOPT(sockfd, SOL_SOCKET, SO_KEEPALIVE, keepAlive); - SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, keepIdle); - SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, keepInterval); - SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_KEEPCNT, keepCount); - - // Set send timeout to avoid indefinite blocking writes - struct timeval snd_to; - snd_to.tv_sec = TCP_WRITE_TIMEOUT / 1000; - snd_to.tv_usec = (TCP_WRITE_TIMEOUT % 1000) * 1000; - SET_SOCKOPT_TIMEVAL(sockfd, SOL_SOCKET, SO_SNDTIMEO, snd_to); - - LOG_DEBUG("TCP socket options configured: TCP_NODELAY=1, keepalive enabled, send timeout=%dms", TCP_WRITE_TIMEOUT); - LOG_INFO("Audio streaming configured: %d bytes per chunk (%.0fms at 16kHz)", TCP_CHUNK_SIZE, (float)TCP_CHUNK_SIZE / 32.0f); - } - - return true; - } - else - { - LOG_ERROR("Server connection failed"); - server_connected = false; + // Update state to CONNECTING + updateTCPState(TCPConnectionState::CONNECTING); - // Update state to ERROR - handleTCPError("connectToServer"); + LOG_INFO("Attempting to connect to server %s:%d (attempt %d)...", SERVER_HOST, + SERVER_PORT, server_backoff.getFailureCount() + 1); - // Set next retry time with exponential backoff + jitter - unsigned long next_delay = server_backoff.getNextDelay(); - server_retry_timer.setInterval(next_delay); - server_retry_timer.start(); + // Feed watchdog during connection attempt + esp_task_wdt_reset(); - LOG_INFO("Next server connection attempt in %lu ms", next_delay); - return false; + if (client.connect(SERVER_HOST, SERVER_PORT)) { + LOG_INFO("Server connection established"); + server_connected = true; + last_successful_write = millis(); + server_backoff.reset(); + server_reconnect_count++; + + // Update state to CONNECTED + updateTCPState(TCPConnectionState::CONNECTED); + + // Configure TCP socket options for low-latency audio streaming + int sockfd = client.fd(); + if (sockfd >= 0) { + // TCP_NODELAY: Disable Nagle's algorithm for low-latency streaming + // Server expects immediate audio chunks without buffering delays + // This matches server's receiver.py configuration: + // conn.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) + int nodelay = 1; + SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_NODELAY, nodelay); + + // TCP keepalive: Detect stale connections + int keepAlive = 1; + int keepIdle = TCP_KEEPALIVE_IDLE; // seconds + int keepInterval = TCP_KEEPALIVE_INTERVAL; // seconds + int keepCount = TCP_KEEPALIVE_COUNT; // count + + SET_SOCKOPT(sockfd, SOL_SOCKET, SO_KEEPALIVE, keepAlive); + SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_KEEPIDLE, keepIdle); + SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_KEEPINTVL, keepInterval); + SET_SOCKOPT(sockfd, IPPROTO_TCP, TCP_KEEPCNT, keepCount); + + // Set send timeout to avoid indefinite blocking writes + struct timeval snd_to; + snd_to.tv_sec = TCP_WRITE_TIMEOUT / 1000; + snd_to.tv_usec = (TCP_WRITE_TIMEOUT % 1000) * 1000; + SET_SOCKOPT_TIMEVAL(sockfd, SOL_SOCKET, SO_SNDTIMEO, snd_to); + + LOG_DEBUG("TCP socket options configured: TCP_NODELAY=1, keepalive " + "enabled, send timeout=%dms", + TCP_WRITE_TIMEOUT); + LOG_INFO( + "Audio streaming configured: %d bytes per chunk (%.0fms at 16kHz)", + TCP_CHUNK_SIZE, (float)TCP_CHUNK_SIZE / 32.0f); } -} -void NetworkManager::disconnectFromServer() -{ - if (server_connected || client.connected()) - { - // Update state to CLOSING - updateTCPState(TCPConnectionState::CLOSING); + return true; + } else { + LOG_ERROR("Server connection failed"); + server_connected = false; - LOG_INFO("Disconnecting from server"); - client.stop(); - server_connected = false; + // Update state to ERROR + handleTCPError("connectToServer"); - // Update state to DISCONNECTED - updateTCPState(TCPConnectionState::DISCONNECTED); - } -} + // Set next retry time with exponential backoff + jitter + unsigned long next_delay = server_backoff.getNextDelay(); + server_retry_timer.setInterval(next_delay); + server_retry_timer.start(); -bool NetworkManager::isServerConnected() -{ - // Double-check: our flag AND actual connection state - if (server_connected && !client.connected()) - { - LOG_WARN("Server connection lost unexpectedly"); - server_connected = false; - server_retry_timer.setInterval(SERVER_RECONNECT_MIN); - server_retry_timer.start(); - } - return server_connected; + LOG_INFO("Next server connection attempt in %lu ms", next_delay); + return false; + } } -WiFiClient &NetworkManager::getClient() -{ - return client; -} +void NetworkManager::disconnectFromServer() { + if (server_connected || client.connected()) { + // Update state to CLOSING + updateTCPState(TCPConnectionState::CLOSING); -bool NetworkManager::writeData(const uint8_t *data, size_t length) -{ - if (!isServerConnected()) - { - return false; - } + LOG_INFO("Disconnecting from server"); + client.stop(); + server_connected = false; - // Diagnostic: Log first transmission to verify audio stream starts - static bool first_transmission = true; - if (first_transmission && length > 0) - { - LOG_INFO("Starting audio transmission: first chunk is %u bytes (%.0fms of audio)", (unsigned)length, (float)length / 32.0f); - first_transmission = false; - } + // Update state to DISCONNECTED + updateTCPState(TCPConnectionState::DISCONNECTED); + } +} - // Write in chunks to minimize long blocking writes and respect SO_SNDTIMEO - size_t total_sent = 0; - while (total_sent < length) - { - size_t chunk = min((size_t)TCP_CHUNK_SIZE, length - total_sent); - size_t sent = client.write(data + total_sent, chunk); - if (sent == 0) - { - LOG_ERROR("TCP write returned 0 (timeout or error) after %u/%u bytes", (unsigned)total_sent, (unsigned)length); - handleTCPError("writeData"); - if (millis() - last_successful_write > TCP_WRITE_TIMEOUT) - { - LOG_ERROR("TCP write timeout - closing stale connection"); - disconnectFromServer(); - } - return false; - } - total_sent += sent; +bool NetworkManager::isServerConnected() { + // Double-check: our flag AND actual connection state + if (server_connected && !client.connected()) { + LOG_WARN("Server connection lost unexpectedly"); + server_connected = false; + server_retry_timer.setInterval(SERVER_RECONNECT_MIN); + server_retry_timer.start(); + } + return server_connected; +} + +WiFiClient &NetworkManager::getClient() { return client; } + +bool NetworkManager::writeData(const uint8_t *data, size_t length) { + if (!isServerConnected()) { + return false; + } + + // Diagnostic: Log first transmission to verify audio stream starts + static bool first_transmission = true; + if (first_transmission && length > 0) { + LOG_INFO("Starting audio transmission: first chunk is %u bytes (%.0fms of " + "audio)", + (unsigned)length, (float)length / 32.0f); + first_transmission = false; + } + + // Write in chunks to minimize long blocking writes and respect SO_SNDTIMEO + size_t total_sent = 0; + while (total_sent < length) { + size_t chunk = min((size_t)TCP_CHUNK_SIZE, length - total_sent); + size_t sent = client.write(data + total_sent, chunk); + if (sent == 0) { + LOG_ERROR("TCP write returned 0 (timeout or error) after %u/%u bytes", + (unsigned)total_sent, (unsigned)length); + handleTCPError("writeData"); + if (millis() - last_successful_write > TCP_WRITE_TIMEOUT) { + LOG_ERROR("TCP write timeout - closing stale connection"); + disconnectFromServer(); + } + return false; } + total_sent += sent; + } - last_successful_write = millis(); - return true; + last_successful_write = millis(); + return true; } -uint32_t NetworkManager::getWiFiReconnectCount() -{ - return wifi_reconnect_count; +uint32_t NetworkManager::getWiFiReconnectCount() { + return wifi_reconnect_count; } -uint32_t NetworkManager::getServerReconnectCount() -{ - return server_reconnect_count; +uint32_t NetworkManager::getServerReconnectCount() { + return server_reconnect_count; } -uint32_t NetworkManager::getTCPErrorCount() -{ - return tcp_error_count; -} +uint32_t NetworkManager::getTCPErrorCount() { return tcp_error_count; } // ===== TCP Connection State Machine Implementation ===== // Helper function: Convert TCP connection state enum to human-readable string -static const char *getTCPStateName(TCPConnectionState state) -{ - switch (state) - { - case TCPConnectionState::DISCONNECTED: - return "DISCONNECTED"; - case TCPConnectionState::CONNECTING: - return "CONNECTING"; - case TCPConnectionState::CONNECTED: - return "CONNECTED"; - case TCPConnectionState::ERROR: - return "ERROR"; - case TCPConnectionState::CLOSING: - return "CLOSING"; - default: - return "UNKNOWN"; - } +static const char *getTCPStateName(TCPConnectionState state) { + switch (state) { + case TCPConnectionState::DISCONNECTED: + return "DISCONNECTED"; + case TCPConnectionState::CONNECTING: + return "CONNECTING"; + case TCPConnectionState::CONNECTED: + return "CONNECTED"; + case TCPConnectionState::ERROR: + return "ERROR"; + case TCPConnectionState::CLOSING: + return "CLOSING"; + default: + return "UNKNOWN"; + } } -void NetworkManager::updateTCPState(TCPConnectionState new_state) -{ - if (tcp_state != new_state) - { - TCPConnectionState old_state = tcp_state; - tcp_state = new_state; - tcp_state_change_time = millis(); - tcp_state_changes++; - - // Log state transition using helper function - LOG_INFO("TCP state transition: %s → %s", getTCPStateName(old_state), getTCPStateName(new_state)); - - // Update connection established time when entering CONNECTED state - if (new_state == TCPConnectionState::CONNECTED) - { - tcp_connection_established_time = millis(); - } +void NetworkManager::updateTCPState(TCPConnectionState new_state) { + if (tcp_state != new_state) { + TCPConnectionState old_state = tcp_state; + tcp_state = new_state; + tcp_state_change_time = millis(); + tcp_state_changes++; + + // Log state transition using helper function + LOG_INFO("TCP state transition: %s → %s", getTCPStateName(old_state), + getTCPStateName(new_state)); + + // Update connection established time when entering CONNECTED state + if (new_state == TCPConnectionState::CONNECTED) { + tcp_connection_established_time = millis(); } + } } -void NetworkManager::handleTCPError(const char *error_source) -{ - tcp_error_count++; - LOG_ERROR("TCP error from %s", error_source); - updateTCPState(TCPConnectionState::ERROR); - - // ERROR state recovery: The next call to connectToServer() will attempt reconnection - // with exponential backoff. The ERROR state is a transient state that leads to either: - // 1. DISCONNECTED (if connection is lost) → next connection attempt resets to CONNECTING - // 2. CONNECTED (if error is recovered) → normal operation resumes - // 3. Another ERROR (if connection remains problematic) → exponential backoff continues - // - // The system does NOT get stuck in ERROR state due to the polling-based reconnection - // logic in connectToServer() which continuously attempts to re-establish the connection. +void NetworkManager::handleTCPError(const char *error_source) { + tcp_error_count++; + LOG_ERROR("TCP error from %s", error_source); + updateTCPState(TCPConnectionState::ERROR); + + // ERROR state recovery: The next call to connectToServer() will attempt + // reconnection with exponential backoff. The ERROR state is a transient state + // that leads to either: + // 1. DISCONNECTED (if connection is lost) → next connection attempt resets to + // CONNECTING + // 2. CONNECTED (if error is recovered) → normal operation resumes + // 3. Another ERROR (if connection remains problematic) → exponential backoff + // continues + // + // The system does NOT get stuck in ERROR state due to the polling-based + // reconnection logic in connectToServer() which continuously attempts to + // re-establish the connection. } -bool NetworkManager::validateConnection() -{ - // Validate that connection state matches actual TCP connection - bool is_actually_connected = client.connected(); - bool state_says_connected = (tcp_state == TCPConnectionState::CONNECTED); - - if (state_says_connected && !is_actually_connected) - { - LOG_WARN("TCP state mismatch: state=CONNECTED but client.connected()=false"); - updateTCPState(TCPConnectionState::DISCONNECTED); - return false; - } - - if (!state_says_connected && is_actually_connected) - { - LOG_WARN("TCP state mismatch: state != CONNECTED but client.connected()=true"); - updateTCPState(TCPConnectionState::CONNECTED); - return true; - } +bool NetworkManager::validateConnection() { + // Validate that connection state matches actual TCP connection + bool is_actually_connected = client.connected(); + bool state_says_connected = (tcp_state == TCPConnectionState::CONNECTED); + + if (state_says_connected && !is_actually_connected) { + LOG_WARN( + "TCP state mismatch: state=CONNECTED but client.connected()=false"); + updateTCPState(TCPConnectionState::DISCONNECTED); + return false; + } + + if (!state_says_connected && is_actually_connected) { + LOG_WARN( + "TCP state mismatch: state != CONNECTED but client.connected()=true"); + updateTCPState(TCPConnectionState::CONNECTED); + return true; + } - return is_actually_connected; + return is_actually_connected; } -TCPConnectionState NetworkManager::getTCPState() -{ - validateConnection(); // Synchronize state with actual connection - return tcp_state; +TCPConnectionState NetworkManager::getTCPState() { + validateConnection(); // Synchronize state with actual connection + return tcp_state; } -bool NetworkManager::isTCPConnecting() -{ - return tcp_state == TCPConnectionState::CONNECTING; +bool NetworkManager::isTCPConnecting() { + return tcp_state == TCPConnectionState::CONNECTING; } -bool NetworkManager::isTCPConnected() -{ - validateConnection(); // Synchronize before returning - return tcp_state == TCPConnectionState::CONNECTED; +bool NetworkManager::isTCPConnected() { + validateConnection(); // Synchronize before returning + return tcp_state == TCPConnectionState::CONNECTED; } -bool NetworkManager::isTCPError() -{ - return tcp_state == TCPConnectionState::ERROR; +bool NetworkManager::isTCPError() { + return tcp_state == TCPConnectionState::ERROR; } -unsigned long NetworkManager::getTimeSinceLastWrite() -{ - return millis() - last_successful_write; +unsigned long NetworkManager::getTimeSinceLastWrite() { + return millis() - last_successful_write; } -unsigned long NetworkManager::getConnectionUptime() -{ - if (tcp_state != TCPConnectionState::CONNECTED) - { - return 0; - } - return millis() - tcp_connection_established_time; +unsigned long NetworkManager::getConnectionUptime() { + if (tcp_state != TCPConnectionState::CONNECTED) { + return 0; + } + return millis() - tcp_connection_established_time; } -uint32_t NetworkManager::getTCPStateChangeCount() -{ - return tcp_state_changes; -} +uint32_t NetworkManager::getTCPStateChangeCount() { return tcp_state_changes; } // ===== Adaptive Buffer Management ===== -void NetworkManager::updateAdaptiveBuffer() -{ - if (!isWiFiConnected()) - return; - AdaptiveBuffer::updateBufferSize(WiFi.RSSI()); +void NetworkManager::updateAdaptiveBuffer() { + if (!isWiFiConnected()) + return; + AdaptiveBuffer::updateBufferSize(WiFi.RSSI()); } -size_t NetworkManager::getAdaptiveBufferSize() -{ - return AdaptiveBuffer::getBufferSize(); +size_t NetworkManager::getAdaptiveBufferSize() { + return AdaptiveBuffer::getBufferSize(); } diff --git a/src/network.h b/src/network.h index c82174b..3cb40f4 100644 --- a/src/network.h +++ b/src/network.h @@ -1,97 +1,97 @@ #ifndef NETWORK_H #define NETWORK_H -#include -#include -#include "config.h" #include "NonBlockingTimer.h" #include "adaptive_buffer.h" +#include "config.h" +#include +#include // TCP Connection states enum class TCPConnectionState { - DISCONNECTED, // Not connected, ready for new attempt - CONNECTING, // Connection attempt in progress - CONNECTED, // Active connection (data can flow) - ERROR, // Connection error detected - CLOSING // Graceful disconnect in progress + DISCONNECTED, // Not connected, ready for new attempt + CONNECTING, // Connection attempt in progress + CONNECTED, // Active connection (data can flow) + ERROR, // Connection error detected + CLOSING // Graceful disconnect in progress }; // Exponential backoff for reconnection attempts class ExponentialBackoff { private: - unsigned long min_delay; - unsigned long max_delay; - unsigned long current_delay; - int consecutive_failures; + unsigned long min_delay; + unsigned long max_delay; + unsigned long current_delay; + int consecutive_failures; public: - ExponentialBackoff(unsigned long min_ms = SERVER_RECONNECT_MIN, - unsigned long max_ms = SERVER_RECONNECT_MAX); + ExponentialBackoff(unsigned long min_ms = SERVER_RECONNECT_MIN, + unsigned long max_ms = SERVER_RECONNECT_MAX); - unsigned long getNextDelay(); - void reset(); - int getFailureCount() const { return consecutive_failures; } + unsigned long getNextDelay(); + void reset(); + int getFailureCount() const { return consecutive_failures; } }; // Network management with reliability features class NetworkManager { public: - static void initialize(); - static void handleWiFiConnection(); - static bool isWiFiConnected(); - static void monitorWiFiQuality(); - - // Adaptive buffer management - static void updateAdaptiveBuffer(); - static size_t getAdaptiveBufferSize(); - - // Server connection management - static bool connectToServer(); - static void disconnectFromServer(); - static bool isServerConnected(); - static WiFiClient& getClient(); - - // TCP connection state management - static TCPConnectionState getTCPState(); - static bool isTCPConnecting(); - static bool isTCPConnected(); - static bool isTCPError(); - static unsigned long getTimeSinceLastWrite(); - static unsigned long getConnectionUptime(); - - // TCP write with timeout detection - static bool writeData(const uint8_t* data, size_t length); - - // Statistics - static uint32_t getWiFiReconnectCount(); - static uint32_t getServerReconnectCount(); - static uint32_t getTCPErrorCount(); - static uint32_t getTCPStateChangeCount(); + static void initialize(); + static void handleWiFiConnection(); + static bool isWiFiConnected(); + static void monitorWiFiQuality(); + + // Adaptive buffer management + static void updateAdaptiveBuffer(); + static size_t getAdaptiveBufferSize(); + + // Server connection management + static bool connectToServer(); + static void disconnectFromServer(); + static bool isServerConnected(); + static WiFiClient &getClient(); + + // TCP connection state management + static TCPConnectionState getTCPState(); + static bool isTCPConnecting(); + static bool isTCPConnected(); + static bool isTCPError(); + static unsigned long getTimeSinceLastWrite(); + static unsigned long getConnectionUptime(); + + // TCP write with timeout detection + static bool writeData(const uint8_t *data, size_t length); + + // Statistics + static uint32_t getWiFiReconnectCount(); + static uint32_t getServerReconnectCount(); + static uint32_t getTCPErrorCount(); + static uint32_t getTCPStateChangeCount(); private: - // Connection state tracking - static TCPConnectionState tcp_state; - static unsigned long tcp_state_change_time; - static unsigned long tcp_connection_established_time; - static uint32_t tcp_state_changes; - - // Error recovery - static void handleTCPError(const char* error_source); - static void updateTCPState(TCPConnectionState new_state); - static bool validateConnection(); - - static bool server_connected; - static unsigned long last_successful_write; - static NonBlockingTimer wifi_retry_timer; - static NonBlockingTimer server_retry_timer; - static NonBlockingTimer rssi_check_timer; - static ExponentialBackoff server_backoff; - static WiFiClient client; - - static uint32_t wifi_reconnect_count; - static uint32_t server_reconnect_count; - static uint32_t tcp_error_count; - static int wifi_retry_count; + // Connection state tracking + static TCPConnectionState tcp_state; + static unsigned long tcp_state_change_time; + static unsigned long tcp_connection_established_time; + static uint32_t tcp_state_changes; + + // Error recovery + static void handleTCPError(const char *error_source); + static void updateTCPState(TCPConnectionState new_state); + static bool validateConnection(); + + static bool server_connected; + static unsigned long last_successful_write; + static NonBlockingTimer wifi_retry_timer; + static NonBlockingTimer server_retry_timer; + static NonBlockingTimer rssi_check_timer; + static ExponentialBackoff server_backoff; + static WiFiClient client; + + static uint32_t wifi_reconnect_count; + static uint32_t server_reconnect_count; + static uint32_t tcp_error_count; + static int wifi_retry_count; }; #endif // NETWORK_H \ No newline at end of file diff --git a/src/serial_command.cpp b/src/serial_command.cpp index 7251117..d4773bd 100644 --- a/src/serial_command.cpp +++ b/src/serial_command.cpp @@ -1,299 +1,307 @@ #include "serial_command.h" +#include "StateManager.h" +#include "i2s_audio.h" #include "logger.h" #include "network.h" -#include "i2s_audio.h" -#include "StateManager.h" // Static member initialization -char SerialCommandHandler::command_buffer[SerialCommandHandler::BUFFER_SIZE] = {0}; +char SerialCommandHandler::command_buffer[SerialCommandHandler::BUFFER_SIZE] = { + 0}; size_t SerialCommandHandler::buffer_index = 0; // Declare external state manager (from main.cpp) extern StateManager systemState; void SerialCommandHandler::initialize() { - LOG_INFO("Serial Command Handler initialized"); - LOG_INFO("Type 'HELP' for available commands"); + LOG_INFO("Serial Command Handler initialized"); + LOG_INFO("Type 'HELP' for available commands"); } void SerialCommandHandler::processCommands() { - // Check if data is available on serial - if (!Serial.available()) { - return; + // Check if data is available on serial + if (!Serial.available()) { + return; + } + + // Read one character + char c = Serial.read(); + + // Handle backspace + if (c == '\b' || c == 0x7F) { + if (buffer_index > 0) { + buffer_index--; + Serial.write('\b'); + Serial.write(' '); + Serial.write('\b'); } - - // Read one character - char c = Serial.read(); - - // Handle backspace - if (c == '\b' || c == 0x7F) { - if (buffer_index > 0) { - buffer_index--; - Serial.write('\b'); - Serial.write(' '); - Serial.write('\b'); + return; + } + + // Handle newline (end of command) + if (c == '\r' || c == '\n') { + if (buffer_index > 0) { + command_buffer[buffer_index] = '\0'; + Serial.println(); // Echo newline + + // Convert to uppercase for case-insensitive comparison + for (size_t i = 0; i < buffer_index; i++) { + command_buffer[i] = toupper(command_buffer[i]); + } + + // Parse and execute command + char *cmd = command_buffer; + char *args = nullptr; + char *space = strchr(command_buffer, ' '); + if (space != nullptr) { + *space = '\0'; // Null-terminate the command + args = space + 1; + } + + if (cmd != nullptr) { + if (strcmp(cmd, "STATUS") == 0) { + handleStatusCommand(); + } else if (strcmp(cmd, "CONFIG") == 0) { + handleConfigCommand(args); + } else if (strcmp(cmd, "RESTART") == 0) { + handleRestartCommand(); + } else if (strcmp(cmd, "DISCONNECT") == 0) { + handleDisconnectCommand(); + } else if (strcmp(cmd, "CONNECT") == 0) { + handleConnectCommand(); + } else if (strcmp(cmd, "STATS") == 0) { + handleStatsCommand(); + } else if (strcmp(cmd, "HEALTH") == 0) { + handleHealthCommand(); + } else if (strcmp(cmd, "HELP") == 0) { + handleHelpCommand(); + } else { + LOG_ERROR("Unknown command: %s", cmd); + handleHelpCommand(); } - return; - } + } - // Handle newline (end of command) - if (c == '\r' || c == '\n') { - if (buffer_index > 0) { - command_buffer[buffer_index] = '\0'; - Serial.println(); // Echo newline - - // Convert to uppercase for case-insensitive comparison - for (size_t i = 0; i < buffer_index; i++) { - command_buffer[i] = toupper(command_buffer[i]); - } - - // Parse and execute command - char* cmd = command_buffer; - char* args = nullptr; - char* space = strchr(command_buffer, ' '); - if (space != nullptr) { - *space = '\0'; // Null-terminate the command - args = space + 1; - } - - if (cmd != nullptr) { - if (strcmp(cmd, "STATUS") == 0) { - handleStatusCommand(); - } else if (strcmp(cmd, "CONFIG") == 0) { - handleConfigCommand(args); - } else if (strcmp(cmd, "RESTART") == 0) { - handleRestartCommand(); - } else if (strcmp(cmd, "DISCONNECT") == 0) { - handleDisconnectCommand(); - } else if (strcmp(cmd, "CONNECT") == 0) { - handleConnectCommand(); - } else if (strcmp(cmd, "STATS") == 0) { - handleStatsCommand(); - } else if (strcmp(cmd, "HEALTH") == 0) { - handleHealthCommand(); - } else if (strcmp(cmd, "HELP") == 0) { - handleHelpCommand(); - } else { - LOG_ERROR("Unknown command: %s", cmd); - handleHelpCommand(); - } - } - - clearBuffer(); - } - return; - } - - // Handle regular characters - if (buffer_index < BUFFER_SIZE - 1) { - command_buffer[buffer_index++] = c; - Serial.write(c); // Echo character + clearBuffer(); } + return; + } + + // Handle regular characters + if (buffer_index < BUFFER_SIZE - 1) { + command_buffer[buffer_index++] = c; + Serial.write(c); // Echo character + } } void SerialCommandHandler::handleStatusCommand() { - LOG_INFO("========== SYSTEM STATUS =========="); - printStatus(); - LOG_INFO("==================================="); + LOG_INFO("========== SYSTEM STATUS =========="); + printStatus(); + LOG_INFO("==================================="); } void SerialCommandHandler::printStatus() { - // WiFi Status - if (NetworkManager::isWiFiConnected()) { - LOG_INFO("WiFi: CONNECTED (%s)", WiFi.localIP().toString().c_str()); - LOG_INFO("WiFi Signal: %d dBm", WiFi.RSSI()); - } else { - LOG_INFO("WiFi: DISCONNECTED"); - } - - // Server/TCP Status - TCPConnectionState tcp_state = NetworkManager::getTCPState(); - const char* state_name = "UNKNOWN"; - switch (tcp_state) { - case TCPConnectionState::DISCONNECTED: - state_name = "DISCONNECTED"; - break; - case TCPConnectionState::CONNECTING: - state_name = "CONNECTING"; - break; - case TCPConnectionState::CONNECTED: - state_name = "CONNECTED"; - LOG_INFO("TCP Connection uptime: %lu ms", NetworkManager::getConnectionUptime()); - break; - case TCPConnectionState::ERROR: - state_name = "ERROR"; - break; - case TCPConnectionState::CLOSING: - state_name = "CLOSING"; - break; - } - LOG_INFO("TCP State: %s", state_name); - LOG_INFO("Server: %s:%d", SERVER_HOST, SERVER_PORT); - - // System State - LOG_INFO("System State: %s", systemState.stateToString(systemState.getState()).c_str()); - - // Memory Status - uint32_t free_heap = ESP.getFreeHeap(); - LOG_INFO("Free Memory: %u bytes (%.1f KB)", free_heap, free_heap / 1024.0); - - // Statistics - LOG_INFO("WiFi Reconnects: %u", NetworkManager::getWiFiReconnectCount()); - LOG_INFO("Server Reconnects: %u", NetworkManager::getServerReconnectCount()); - LOG_INFO("TCP Errors: %u", NetworkManager::getTCPErrorCount()); - LOG_INFO("TCP State Changes: %u", NetworkManager::getTCPStateChangeCount()); + // WiFi Status + if (NetworkManager::isWiFiConnected()) { + LOG_INFO("WiFi: CONNECTED (%s)", WiFi.localIP().toString().c_str()); + LOG_INFO("WiFi Signal: %d dBm", WiFi.RSSI()); + } else { + LOG_INFO("WiFi: DISCONNECTED"); + } + + // Server/TCP Status + TCPConnectionState tcp_state = NetworkManager::getTCPState(); + const char *state_name = "UNKNOWN"; + switch (tcp_state) { + case TCPConnectionState::DISCONNECTED: + state_name = "DISCONNECTED"; + break; + case TCPConnectionState::CONNECTING: + state_name = "CONNECTING"; + break; + case TCPConnectionState::CONNECTED: + state_name = "CONNECTED"; + LOG_INFO("TCP Connection uptime: %lu ms", + NetworkManager::getConnectionUptime()); + break; + case TCPConnectionState::ERROR: + state_name = "ERROR"; + break; + case TCPConnectionState::CLOSING: + state_name = "CLOSING"; + break; + } + LOG_INFO("TCP State: %s", state_name); + LOG_INFO("Server: %s:%d", SERVER_HOST, SERVER_PORT); + + // System State + LOG_INFO("System State: %s", + systemState.stateToString(systemState.getState()).c_str()); + + // Memory Status + uint32_t free_heap = ESP.getFreeHeap(); + LOG_INFO("Free Memory: %u bytes (%.1f KB)", free_heap, free_heap / 1024.0); + + // Statistics + LOG_INFO("WiFi Reconnects: %u", NetworkManager::getWiFiReconnectCount()); + LOG_INFO("Server Reconnects: %u", NetworkManager::getServerReconnectCount()); + LOG_INFO("TCP Errors: %u", NetworkManager::getTCPErrorCount()); + LOG_INFO("TCP State Changes: %u", NetworkManager::getTCPStateChangeCount()); } -void SerialCommandHandler::handleConfigCommand(const char* args) { - if (args == nullptr) { - LOG_ERROR("CONFIG: Missing arguments"); - LOG_INFO("Usage: CONFIG [SHOW|SET ]"); - return; - } +void SerialCommandHandler::handleConfigCommand(const char *args) { + if (args == nullptr) { + LOG_ERROR("CONFIG: Missing arguments"); + LOG_INFO("Usage: CONFIG [SHOW|SET ]"); + return; + } - char args_copy[64]; - strncpy(args_copy, args, sizeof(args_copy) - 1); - args_copy[sizeof(args_copy) - 1] = '\0'; + char args_copy[64]; + strncpy(args_copy, args, sizeof(args_copy) - 1); + args_copy[sizeof(args_copy) - 1] = '\0'; - char* subcmd = strtok(args_copy, " "); - if (subcmd == nullptr) return; + char *subcmd = strtok(args_copy, " "); + if (subcmd == nullptr) + return; - for (size_t i = 0; subcmd[i]; i++) { - subcmd[i] = toupper(subcmd[i]); - } + for (size_t i = 0; subcmd[i]; i++) { + subcmd[i] = toupper(subcmd[i]); + } - if (strcmp(subcmd, "SHOW") == 0) { - LOG_INFO("========== CONFIG PARAMETERS =========="); - LOG_INFO("WiFi SSID: %s", WIFI_SSID); - LOG_INFO("Server: %s:%d", SERVER_HOST, SERVER_PORT); - LOG_INFO("I2S Sample Rate: %d Hz", I2S_SAMPLE_RATE); - LOG_INFO("I2S Buffer Size: %d bytes", I2S_BUFFER_SIZE); - LOG_INFO("Memory Warning Threshold: %d bytes", MEMORY_WARN_THRESHOLD); - LOG_INFO("Memory Critical Threshold: %d bytes", MEMORY_CRITICAL_THRESHOLD); - LOG_INFO("========================================"); - } else { - LOG_ERROR("Unknown CONFIG subcommand: %s", subcmd); - } + if (strcmp(subcmd, "SHOW") == 0) { + LOG_INFO("========== CONFIG PARAMETERS =========="); + LOG_INFO("WiFi SSID: %s", WIFI_SSID); + LOG_INFO("Server: %s:%d", SERVER_HOST, SERVER_PORT); + LOG_INFO("I2S Sample Rate: %d Hz", I2S_SAMPLE_RATE); + LOG_INFO("I2S Buffer Size: %d bytes", I2S_BUFFER_SIZE); + LOG_INFO("Memory Warning Threshold: %d bytes", MEMORY_WARN_THRESHOLD); + LOG_INFO("Memory Critical Threshold: %d bytes", MEMORY_CRITICAL_THRESHOLD); + LOG_INFO("========================================"); + } else { + LOG_ERROR("Unknown CONFIG subcommand: %s", subcmd); + } } void SerialCommandHandler::handleRestartCommand() { - LOG_CRITICAL("Restarting system in 3 seconds..."); - delay(3000); - ESP.restart(); + LOG_CRITICAL("Restarting system in 3 seconds..."); + delay(3000); + ESP.restart(); } void SerialCommandHandler::handleDisconnectCommand() { - LOG_INFO("Disconnecting from server..."); - NetworkManager::disconnectFromServer(); - LOG_INFO("Disconnected"); + LOG_INFO("Disconnecting from server..."); + NetworkManager::disconnectFromServer(); + LOG_INFO("Disconnected"); } void SerialCommandHandler::handleConnectCommand() { - LOG_INFO("Attempting to connect to server..."); - if (NetworkManager::connectToServer()) { - LOG_INFO("Connected successfully"); - } else { - LOG_WARN("Connection attempt scheduled (check exponential backoff)"); - } + LOG_INFO("Attempting to connect to server..."); + if (NetworkManager::connectToServer()) { + LOG_INFO("Connected successfully"); + } else { + LOG_WARN("Connection attempt scheduled (check exponential backoff)"); + } } void SerialCommandHandler::handleStatsCommand() { - LOG_INFO("========== DETAILED STATISTICS =========="); - LOG_INFO("Uptime: %lu seconds", millis() / 1000); - - // Memory stats - uint32_t free_heap = ESP.getFreeHeap(); - uint32_t heap_size = ESP.getHeapSize(); - LOG_INFO("Heap - Free: %u bytes, Total: %u bytes", free_heap, heap_size); - LOG_INFO("Heap - Used: %u bytes (%.1f%%)", heap_size - free_heap, - (heap_size - free_heap) * 100.0 / heap_size); - - // I2S stats - LOG_INFO("I2S Total Errors: %u", I2SAudio::getErrorCount()); - LOG_INFO("I2S Transient Errors: %u", I2SAudio::getTransientErrorCount()); - LOG_INFO("I2S Permanent Errors: %u", I2SAudio::getPermanentErrorCount()); - - // Network stats - LOG_INFO("WiFi Reconnects: %u", NetworkManager::getWiFiReconnectCount()); - LOG_INFO("Server Reconnects: %u", NetworkManager::getServerReconnectCount()); - LOG_INFO("TCP Errors: %u", NetworkManager::getTCPErrorCount()); - - // Connection info - if (NetworkManager::isTCPConnected()) { - LOG_INFO("Time Since Last Write: %lu ms", NetworkManager::getTimeSinceLastWrite()); - LOG_INFO("Connection Uptime: %lu ms", NetworkManager::getConnectionUptime()); - } - - LOG_INFO("========================================="); + LOG_INFO("========== DETAILED STATISTICS =========="); + LOG_INFO("Uptime: %lu seconds", millis() / 1000); + + // Memory stats + uint32_t free_heap = ESP.getFreeHeap(); + uint32_t heap_size = ESP.getHeapSize(); + LOG_INFO("Heap - Free: %u bytes, Total: %u bytes", free_heap, heap_size); + LOG_INFO("Heap - Used: %u bytes (%.1f%%)", heap_size - free_heap, + (heap_size - free_heap) * 100.0 / heap_size); + + // I2S stats + LOG_INFO("I2S Total Errors: %u", I2SAudio::getErrorCount()); + LOG_INFO("I2S Transient Errors: %u", I2SAudio::getTransientErrorCount()); + LOG_INFO("I2S Permanent Errors: %u", I2SAudio::getPermanentErrorCount()); + + // Network stats + LOG_INFO("WiFi Reconnects: %u", NetworkManager::getWiFiReconnectCount()); + LOG_INFO("Server Reconnects: %u", NetworkManager::getServerReconnectCount()); + LOG_INFO("TCP Errors: %u", NetworkManager::getTCPErrorCount()); + + // Connection info + if (NetworkManager::isTCPConnected()) { + LOG_INFO("Time Since Last Write: %lu ms", + NetworkManager::getTimeSinceLastWrite()); + LOG_INFO("Connection Uptime: %lu ms", + NetworkManager::getConnectionUptime()); + } + + LOG_INFO("========================================="); } void SerialCommandHandler::handleHealthCommand() { - LOG_INFO("========== SYSTEM HEALTH CHECK =========="); - printHealth(); - LOG_INFO("========================================="); + LOG_INFO("========== SYSTEM HEALTH CHECK =========="); + printHealth(); + LOG_INFO("========================================="); } void SerialCommandHandler::printHealth() { - // WiFi health - if (NetworkManager::isWiFiConnected()) { - int32_t rssi = WiFi.RSSI(); - LOG_INFO("✓ WiFi Connected - Signal: %d dBm", rssi); - if (rssi < -80) { - LOG_WARN(" ⚠ Weak signal - consider relocating device"); - } - } else { - LOG_ERROR("✗ WiFi Not Connected"); - } - - // TCP health - if (NetworkManager::isTCPConnected()) { - LOG_INFO("✓ TCP Connected - Uptime: %lu ms", NetworkManager::getConnectionUptime()); - } else { - LOG_ERROR("✗ TCP Not Connected - State: %d", (int)NetworkManager::getTCPState()); - } - - // Memory health - uint32_t free_heap = ESP.getFreeHeap(); - if (free_heap > 50000) { - LOG_INFO("✓ Memory Healthy - Free: %u bytes", free_heap); - } else if (free_heap > 20000) { - LOG_WARN("⚠ Memory Low - Free: %u bytes", free_heap); - } else { - LOG_ERROR("✗ Memory Critical - Free: %u bytes", free_heap); - } - - // I2S health - if (I2SAudio::healthCheck()) { - LOG_INFO("✓ I2S Healthy - Errors: %u", I2SAudio::getErrorCount()); - } else { - LOG_ERROR("✗ I2S Unhealthy - Consider reinitialization"); - } - - // System state - SystemState state = systemState.getState(); - if (state == SystemState::CONNECTED) { - LOG_INFO("✓ System State: CONNECTED"); - } else if (state == SystemState::ERROR) { - LOG_ERROR("✗ System State: ERROR"); - } else { - LOG_WARN("⚠ System State: %s", systemState.stateToString(state).c_str()); + // WiFi health + if (NetworkManager::isWiFiConnected()) { + int32_t rssi = WiFi.RSSI(); + LOG_INFO("✓ WiFi Connected - Signal: %d dBm", rssi); + if (rssi < -80) { + LOG_WARN(" ⚠ Weak signal - consider relocating device"); } + } else { + LOG_ERROR("✗ WiFi Not Connected"); + } + + // TCP health + if (NetworkManager::isTCPConnected()) { + LOG_INFO("✓ TCP Connected - Uptime: %lu ms", + NetworkManager::getConnectionUptime()); + } else { + LOG_ERROR("✗ TCP Not Connected - State: %d", + (int)NetworkManager::getTCPState()); + } + + // Memory health + uint32_t free_heap = ESP.getFreeHeap(); + if (free_heap > 50000) { + LOG_INFO("✓ Memory Healthy - Free: %u bytes", free_heap); + } else if (free_heap > 20000) { + LOG_WARN("⚠ Memory Low - Free: %u bytes", free_heap); + } else { + LOG_ERROR("✗ Memory Critical - Free: %u bytes", free_heap); + } + + // I2S health + if (I2SAudio::healthCheck()) { + LOG_INFO("✓ I2S Healthy - Errors: %u", I2SAudio::getErrorCount()); + } else { + LOG_ERROR("✗ I2S Unhealthy - Consider reinitialization"); + } + + // System state + SystemState state = systemState.getState(); + if (state == SystemState::CONNECTED) { + LOG_INFO("✓ System State: CONNECTED"); + } else if (state == SystemState::ERROR) { + LOG_ERROR("✗ System State: ERROR"); + } else { + LOG_WARN("⚠ System State: %s", systemState.stateToString(state).c_str()); + } } void SerialCommandHandler::handleHelpCommand() { - LOG_INFO("========== AVAILABLE COMMANDS =========="); - LOG_INFO("STATUS - Show current system status"); - LOG_INFO("STATS - Show detailed statistics"); - LOG_INFO("HEALTH - Perform system health check"); - LOG_INFO("CONFIG SHOW - Display configuration"); - LOG_INFO("CONNECT - Attempt to connect to server"); - LOG_INFO("DISCONNECT - Disconnect from server"); - LOG_INFO("RESTART - Restart the system"); - LOG_INFO("HELP - Show this help message"); - LOG_INFO("========================================="); + LOG_INFO("========== AVAILABLE COMMANDS =========="); + LOG_INFO("STATUS - Show current system status"); + LOG_INFO("STATS - Show detailed statistics"); + LOG_INFO("HEALTH - Perform system health check"); + LOG_INFO("CONFIG SHOW - Display configuration"); + LOG_INFO("CONNECT - Attempt to connect to server"); + LOG_INFO("DISCONNECT - Disconnect from server"); + LOG_INFO("RESTART - Restart the system"); + LOG_INFO("HELP - Show this help message"); + LOG_INFO("========================================="); } void SerialCommandHandler::clearBuffer() { - memset(command_buffer, 0, BUFFER_SIZE); - buffer_index = 0; + memset(command_buffer, 0, BUFFER_SIZE); + buffer_index = 0; } diff --git a/src/serial_command.h b/src/serial_command.h index 23db8bf..6b836e0 100644 --- a/src/serial_command.h +++ b/src/serial_command.h @@ -9,29 +9,29 @@ class NetworkManager; // Serial command handler for runtime control class SerialCommandHandler { public: - static void initialize(); - static void processCommands(); + static void initialize(); + static void processCommands(); private: - static const size_t BUFFER_SIZE = 128; - static char command_buffer[BUFFER_SIZE]; - static size_t buffer_index; + static const size_t BUFFER_SIZE = 128; + static char command_buffer[BUFFER_SIZE]; + static size_t buffer_index; - // Command handlers - static void handleStatusCommand(); - static void handleConfigCommand(const char* args); - static void handleRestartCommand(); - static void handleDisconnectCommand(); - static void handleConnectCommand(); - static void handleStatsCommand(); - static void handleHealthCommand(); - static void handleHelpCommand(); + // Command handlers + static void handleStatusCommand(); + static void handleConfigCommand(const char *args); + static void handleRestartCommand(); + static void handleDisconnectCommand(); + static void handleConnectCommand(); + static void handleStatsCommand(); + static void handleHealthCommand(); + static void handleHelpCommand(); - // Utility functions - static void printStatus(); - static void printHealth(); - static void clearBuffer(); - static char* getNextToken(char* str, const char* delim); + // Utility functions + static void printStatus(); + static void printHealth(); + static void clearBuffer(); + static char *getNextToken(char *str, const char *delim); }; #endif // SERIAL_COMMAND_H