From c3bbba4a24fc07a36effba47e4c592c3b4c041d1 Mon Sep 17 00:00:00 2001 From: Stephen Minakian Date: Wed, 9 Jul 2025 22:55:35 -0600 Subject: [PATCH] Refactor, state manager --- main/CMakeLists.txt | 2 +- main/maxxfan-controller.c | 226 ++++---------------- main/state_manager.c | 422 ++++++++++++++++++++++++++++++++++++++ main/state_manager.h | 126 ++++++++++++ 4 files changed, 592 insertions(+), 184 deletions(-) create mode 100644 main/state_manager.c create mode 100644 main/state_manager.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index b02e8fd..2270210 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,2 @@ -idf_component_register(SRCS "maxxfan-controller.c" "motor_control.c" +idf_component_register(SRCS "maxxfan-controller.c" "motor_control.c" "state_manager.c" INCLUDE_DIRS ".") \ No newline at end of file diff --git a/main/maxxfan-controller.c b/main/maxxfan-controller.c index cd23629..18ec64a 100755 --- a/main/maxxfan-controller.c +++ b/main/maxxfan-controller.c @@ -9,13 +9,13 @@ #include "esp_log.h" #include "esp_http_server.h" #include "esp_task_wdt.h" -#include "nvs_flash.h" #include "nvs.h" #include "cJSON.h" // Project modules #include "config.h" #include "motor_control.h" +#include "state_manager.h" // WiFi event group static EventGroupHandle_t s_wifi_event_group; @@ -84,11 +84,6 @@ static const char* html_page = "function startUpdates(){if(updateInterval)clearInterval(updateInterval);updateInterval=setInterval(getStatus,1000)}" "document.addEventListener('DOMContentLoaded',function(){getStatus();startUpdates()})"; -// Forward declarations -static esp_err_t save_motor_state_to_nvs(void); -static esp_err_t load_motor_state_from_nvs(void); -static bool is_watchdog_reset(void); - // Initialize watchdog timer void init_watchdog(void) { ESP_LOGI(SYSTEM_TAG, "Setting up watchdog monitoring..."); @@ -117,165 +112,6 @@ void feed_watchdog(void) { } } -// Check if this was a watchdog reset -static bool is_watchdog_reset(void) { - esp_reset_reason_t reset_reason = esp_reset_reason(); - - // Only consider TASK_WDT and INT_WDT as true watchdog resets - // ESP_RST_WDT can be triggered by power disconnection, so we exclude it - return (reset_reason == ESP_RST_TASK_WDT || - reset_reason == ESP_RST_INT_WDT); -} - -// Save motor state to NVS -static esp_err_t save_motor_state_to_nvs(void) { - nvs_handle_t nvs_handle; - esp_err_t err; - - err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); - if (err != ESP_OK) { - ESP_LOGE(SYSTEM_TAG, "Error opening NVS handle: %s", esp_err_to_name(err)); - return err; - } - - // Get current motor state - const motor_state_t* state = motor_get_state(); - motor_mode_t last_on_mode; - int last_on_speed; - motor_get_last_on_state(&last_on_mode, &last_on_speed); - bool user_turned_off = motor_get_user_turned_off(); - - ESP_LOGI(SYSTEM_TAG, "=== SAVING STATE TO NVS ==="); - ESP_LOGI(SYSTEM_TAG, "Mode: %d, Speed: %d%%, Last ON: %d@%d%%, User OFF: %s", - state->mode, state->target_speed, - last_on_mode, last_on_speed, - user_turned_off ? "YES" : "NO"); - - // Save current motor state - err = nvs_set_u8(nvs_handle, NVS_KEY_MODE, (uint8_t)state->mode); - if (err == ESP_OK) { - err = nvs_set_u8(nvs_handle, NVS_KEY_SPEED, (uint8_t)state->target_speed); - } - - // Save last ON state - if (err == ESP_OK) { - err = nvs_set_u8(nvs_handle, NVS_KEY_LAST_ON_MODE, (uint8_t)last_on_mode); - } - if (err == ESP_OK) { - err = nvs_set_u8(nvs_handle, NVS_KEY_LAST_ON_SPEED, (uint8_t)last_on_speed); - } - - // Save power state (whether user turned off manually) - if (err == ESP_OK) { - err = nvs_set_u8(nvs_handle, NVS_KEY_POWER_STATE, user_turned_off ? 1 : 0); - } - - if (err == ESP_OK) { - err = nvs_commit(nvs_handle); - if (err == ESP_OK) { - ESP_LOGI(SYSTEM_TAG, "✓ Motor state successfully saved to NVS"); - } else { - ESP_LOGE(SYSTEM_TAG, "✗ NVS commit failed: %s", esp_err_to_name(err)); - } - } else { - ESP_LOGE(SYSTEM_TAG, "✗ Error saving to NVS: %s", esp_err_to_name(err)); - } - ESP_LOGI(SYSTEM_TAG, "==========================="); - - nvs_close(nvs_handle); - return err; -} - -// Load motor state from NVS -static esp_err_t load_motor_state_from_nvs(void) { - nvs_handle_t nvs_handle; - esp_err_t err; - - err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); - if (err != ESP_OK) { - ESP_LOGI(SYSTEM_TAG, "NVS not found, using default state"); - return ESP_ERR_NVS_NOT_FOUND; - } - - uint8_t stored_mode = 0; - uint8_t stored_speed = 0; - uint8_t stored_last_mode = 1; // Default to MOTOR_EXHAUST - uint8_t stored_last_speed = 50; - uint8_t stored_power_state = 0; - - // Load current motor state - err = nvs_get_u8(nvs_handle, NVS_KEY_MODE, &stored_mode); - if (err == ESP_OK) { - nvs_get_u8(nvs_handle, NVS_KEY_SPEED, &stored_speed); - nvs_get_u8(nvs_handle, NVS_KEY_LAST_ON_MODE, &stored_last_mode); - nvs_get_u8(nvs_handle, NVS_KEY_LAST_ON_SPEED, &stored_last_speed); - nvs_get_u8(nvs_handle, NVS_KEY_POWER_STATE, &stored_power_state); - - // Validate ranges using config macros - if (stored_mode > MOTOR_INTAKE) stored_mode = MOTOR_OFF; - if (!IS_VALID_SPEED(stored_speed)) stored_speed = 0; - if (stored_last_mode < MOTOR_EXHAUST || stored_last_mode > MOTOR_INTAKE) stored_last_mode = MOTOR_EXHAUST; - if (!IS_VALID_SPEED(stored_last_speed)) stored_last_speed = 50; - - // Set the last ON state in motor control module - motor_set_last_on_state((motor_mode_t)stored_last_mode, stored_last_speed); - motor_set_user_turned_off(stored_power_state == 1); - - ESP_LOGI(SYSTEM_TAG, "Loaded state from NVS - Mode: %d, Speed: %d%%, Last ON: %d@%d%%, User OFF: %s", - stored_mode, stored_speed, stored_last_mode, stored_last_speed, - stored_power_state ? "YES" : "NO"); - - // Check reset reason to decide whether to restore state - bool was_watchdog_reset = is_watchdog_reset(); - esp_reset_reason_t reset_reason = esp_reset_reason(); - - ESP_LOGI(SYSTEM_TAG, "=== RESET ANALYSIS ==="); - ESP_LOGI(SYSTEM_TAG, "Reset reason: %d", reset_reason); - ESP_LOGI(SYSTEM_TAG, "Reset reason name: %s", - reset_reason == ESP_RST_POWERON ? "POWERON" : - reset_reason == ESP_RST_EXT ? "EXTERNAL" : - reset_reason == ESP_RST_SW ? "SOFTWARE" : - reset_reason == ESP_RST_PANIC ? "PANIC" : - reset_reason == ESP_RST_INT_WDT ? "INT_WDT" : - reset_reason == ESP_RST_TASK_WDT ? "TASK_WDT" : - reset_reason == ESP_RST_WDT ? "WDT" : - reset_reason == ESP_RST_DEEPSLEEP ? "DEEPSLEEP" : - reset_reason == ESP_RST_BROWNOUT ? "BROWNOUT" : - reset_reason == ESP_RST_SDIO ? "SDIO" : "UNKNOWN"); - ESP_LOGI(SYSTEM_TAG, "Watchdog reset: %s", was_watchdog_reset ? "YES" : "NO"); - ESP_LOGI(SYSTEM_TAG, "Stored mode: %d, speed: %d", stored_mode, stored_speed); - ESP_LOGI(SYSTEM_TAG, "User turned off: %s", stored_power_state ? "YES" : "NO"); - ESP_LOGI(SYSTEM_TAG, "===================="); - - // Store the restored state for potential motor restoration - if (was_watchdog_reset) { - // True watchdog reset (TASK_WDT or INT_WDT) - don't restore state, start fresh - ESP_LOGI(SYSTEM_TAG, "⚠️ TRUE watchdog reset detected - starting in OFF state for safety"); - // Motor module is already initialized in OFF state, no action needed - } else if (stored_power_state) { - // User manually turned off - stay off - ESP_LOGI(SYSTEM_TAG, "🔒 User had turned off manually - staying OFF"); - // Motor module is already initialized in OFF state, no action needed - } else if (stored_mode != MOTOR_OFF && stored_speed > 0) { - // Normal power loss or general WDT (which can be power-related) - restore previous state - ESP_LOGI(SYSTEM_TAG, "🔋 Power restored - will resume previous state: %s @ %d%%", - stored_mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", stored_speed); - - // Set the motor to the restored state (will be applied after initialization) - motor_set_speed((motor_mode_t)stored_mode, stored_speed); - } else { - ESP_LOGI(SYSTEM_TAG, "❌ No valid state to restore (mode=%d, speed=%d)", stored_mode, stored_speed); - // Motor module is already initialized in OFF state, no action needed - } - } else { - ESP_LOGI(SYSTEM_TAG, "No saved state found, using defaults"); - err = ESP_ERR_NVS_NOT_FOUND; - } - - nvs_close(nvs_handle); - return err; -} - // WiFi event handler static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) @@ -444,8 +280,11 @@ static esp_err_t fan_post_handler(httpd_req_t *req) ESP_LOGI(SYSTEM_TAG, "ON button pressed - resuming last state"); motor_resume_last_state(); - // Save state after ON button - save_motor_state_to_nvs(); + // Save state after ON button using state manager + esp_err_t save_result = state_manager_save(); + if (save_result != ESP_OK) { + ESP_LOGW(SYSTEM_TAG, "Failed to save state after ON button: %s", esp_err_to_name(save_result)); + } cJSON_Delete(json); return status_get_handler(req); @@ -458,8 +297,11 @@ static esp_err_t fan_post_handler(httpd_req_t *req) ESP_LOGI(SYSTEM_TAG, "HTTP Request: mode=%s, speed=%d", mode_str, speed); motor_set_speed(mode, speed); - // Save state after any motor command - save_motor_state_to_nvs(); + // Save state after any motor command using state manager + esp_err_t save_result = state_manager_save(); + if (save_result != ESP_OK) { + ESP_LOGW(SYSTEM_TAG, "Failed to save state after motor command: %s", esp_err_to_name(save_result)); + } cJSON_Delete(json); @@ -600,13 +442,13 @@ void app_main(void) { ESP_LOGI(SYSTEM_TAG, "Starting Maxxfan HTTP Controller with State Preservation!"); - // Initialize NVS - esp_err_t ret = nvs_flash_init(); - if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { - ESP_ERROR_CHECK(nvs_flash_erase()); - ret = nvs_flash_init(); + // Initialize state manager (includes NVS initialization) + ESP_LOGI(SYSTEM_TAG, "Initializing state manager..."); + esp_err_t ret = state_manager_init(); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to initialize state manager: %s", esp_err_to_name(ret)); + return; } - ESP_ERROR_CHECK(ret); // Initialize watchdog timer init_watchdog(); @@ -619,9 +461,16 @@ void app_main(void) return; } - // Load saved state from NVS and potentially restore motor state - ESP_LOGI(SYSTEM_TAG, "Loading saved state..."); - load_motor_state_from_nvs(); + // Load saved state and restore motor if appropriate + ESP_LOGI(SYSTEM_TAG, "Loading saved state and applying restoration logic..."); + esp_err_t load_result = state_manager_load_and_restore(); + if (load_result == ESP_OK) { + ESP_LOGI(SYSTEM_TAG, "✓ State loaded and restoration logic applied"); + } else if (load_result == ESP_ERR_NVS_NOT_FOUND) { + ESP_LOGI(SYSTEM_TAG, "ℹ️ No saved state found, using defaults"); + } else { + ESP_LOGW(SYSTEM_TAG, "⚠️ Failed to load state: %s", esp_err_to_name(load_result)); + } ESP_LOGI(SYSTEM_TAG, "Connecting to WiFi network: %s", WIFI_SSID); wifi_init_sta(); @@ -629,25 +478,36 @@ void app_main(void) // Start HTTP server start_webserver(); - // Report final motor state after initialization + // Report final system state after initialization const motor_state_t* final_state = motor_get_state(); - ESP_LOGI(SYSTEM_TAG, "=== MOTOR STATE AFTER INITIALIZATION ==="); + ESP_LOGI(SYSTEM_TAG, "=== SYSTEM INITIALIZATION COMPLETE ==="); + ESP_LOGI(SYSTEM_TAG, "Reset Reason: %s", state_manager_get_reset_reason_string()); + ESP_LOGI(SYSTEM_TAG, "Watchdog Reset: %s", state_manager_was_watchdog_reset() ? "YES" : "NO"); ESP_LOGI(SYSTEM_TAG, "Final motor state: mode=%s, target=%d%%, current=%d%%, state=%s", motor_mode_to_string(final_state->mode), final_state->target_speed, final_state->current_speed, motor_state_to_string(final_state->state)); if (final_state->mode != MOTOR_OFF && final_state->target_speed > 0) { - ESP_LOGI(SYSTEM_TAG, "Motor restored to: %s @ %d%%", + ESP_LOGI(SYSTEM_TAG, "🔄 Motor restored to: %s @ %d%%", motor_mode_to_string(final_state->mode), final_state->target_speed); } else { - ESP_LOGI(SYSTEM_TAG, "Motor remains OFF"); + ESP_LOGI(SYSTEM_TAG, "⏸️ Motor remains OFF"); } - ESP_LOGI(SYSTEM_TAG, "======================================="); + + motor_mode_t last_on_mode; + int last_on_speed; + motor_get_last_on_state(&last_on_mode, &last_on_speed); + ESP_LOGI(SYSTEM_TAG, "Last ON state: %s @ %d%%", + motor_mode_to_string(last_on_mode), last_on_speed); + ESP_LOGI(SYSTEM_TAG, "User turned off: %s", motor_get_user_turned_off() ? "YES" : "NO"); + ESP_LOGI(SYSTEM_TAG, "Saved state exists: %s", state_manager_has_saved_state() ? "YES" : "NO"); + ESP_LOGI(SYSTEM_TAG, "====================================="); ESP_LOGI(SYSTEM_TAG, "=== Enhanced Maxxfan Controller Ready! ==="); ESP_LOGI(SYSTEM_TAG, "Features: State Preservation, Direction Safety, Motor Ramping, ON Button"); ESP_LOGI(SYSTEM_TAG, "Safety: %d-second cooldown for direction changes", DIRECTION_CHANGE_COOLDOWN_MS / 1000); ESP_LOGI(SYSTEM_TAG, "Memory: Remembers settings after power loss (except watchdog resets)"); + ESP_LOGI(SYSTEM_TAG, "State Manager: Enhanced NVS operations with validation and recovery"); ESP_LOGI(SYSTEM_TAG, "Open your browser and go to: http://[ESP32_IP_ADDRESS]"); ESP_LOGI(SYSTEM_TAG, "Check the monitor output above for your IP address"); diff --git a/main/state_manager.c b/main/state_manager.c new file mode 100644 index 0000000..7ef0fbc --- /dev/null +++ b/main/state_manager.c @@ -0,0 +1,422 @@ +#include "state_manager.h" +#include "config.h" +#include "motor_control.h" +#include "esp_log.h" +#include "esp_system.h" +#include "nvs_flash.h" +#include "nvs.h" +#include +#include + +// Private functions +static esp_err_t validate_and_clamp_values(uint8_t* mode, uint8_t* speed, uint8_t* last_mode, uint8_t* last_speed); +static void log_reset_analysis(void); +static esp_err_t should_restore_state(bool* should_restore, motor_mode_t stored_mode, int stored_speed); + +// Validate and clamp loaded values to safe ranges +static esp_err_t validate_and_clamp_values(uint8_t* mode, uint8_t* speed, uint8_t* last_mode, uint8_t* last_speed) { + // Validate and clamp motor mode + if (*mode > MOTOR_INTAKE) { + ESP_LOGW(SYSTEM_TAG, "Invalid stored mode %d, clamping to OFF", *mode); + *mode = MOTOR_OFF; + } + + // Validate and clamp speed + if (!IS_VALID_SPEED(*speed)) { + ESP_LOGW(SYSTEM_TAG, "Invalid stored speed %d, clamping to 0", *speed); + *speed = 0; + } + + // Validate and clamp last ON mode + if (*last_mode < MOTOR_EXHAUST || *last_mode > MOTOR_INTAKE) { + ESP_LOGW(SYSTEM_TAG, "Invalid stored last mode %d, defaulting to EXHAUST", *last_mode); + *last_mode = MOTOR_EXHAUST; + } + + // Validate and clamp last ON speed + if (!IS_VALID_SPEED(*last_speed) || *last_speed == 0) { + ESP_LOGW(SYSTEM_TAG, "Invalid stored last speed %d, defaulting to 50", *last_speed); + *last_speed = 50; + } + + return ESP_OK; +} + +// Log detailed reset analysis +static void log_reset_analysis(void) { + esp_reset_reason_t reset_reason = esp_reset_reason(); + bool was_watchdog = state_manager_was_watchdog_reset(); + + ESP_LOGI(SYSTEM_TAG, "=== RESET ANALYSIS ==="); + ESP_LOGI(SYSTEM_TAG, "Reset reason code: %d", reset_reason); + ESP_LOGI(SYSTEM_TAG, "Reset reason: %s", state_manager_get_reset_reason_string()); + ESP_LOGI(SYSTEM_TAG, "Watchdog reset: %s", was_watchdog ? "YES" : "NO"); + ESP_LOGI(SYSTEM_TAG, "===================="); +} + +// Determine if state should be restored based on reset reason and user preferences +static esp_err_t should_restore_state(bool* should_restore, motor_mode_t stored_mode, int stored_speed) { + if (!should_restore) return ESP_ERR_INVALID_ARG; + + *should_restore = false; + + bool was_watchdog = state_manager_was_watchdog_reset(); + bool user_turned_off = motor_get_user_turned_off(); + + if (was_watchdog) { + ESP_LOGI(SYSTEM_TAG, "⚠️ TRUE watchdog reset detected - starting in OFF state for safety"); + return ESP_OK; + } + + if (user_turned_off) { + ESP_LOGI(SYSTEM_TAG, "🔒 User had turned off manually - staying OFF"); + return ESP_OK; + } + + if (stored_mode != MOTOR_OFF && stored_speed > 0) { + ESP_LOGI(SYSTEM_TAG, "🔋 Power restored - will resume previous state: %s @ %d%%", + motor_mode_to_string(stored_mode), stored_speed); + *should_restore = true; + return ESP_OK; + } + + ESP_LOGI(SYSTEM_TAG, "❌ No valid state to restore (mode=%s, speed=%d)", + motor_mode_to_string(stored_mode), stored_speed); + return ESP_OK; +} + +// Public API Implementation + +esp_err_t state_manager_init(void) { + ESP_LOGI(SYSTEM_TAG, "Initializing state manager..."); + + esp_err_t ret = nvs_flash_init(); + if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { + ESP_LOGW(SYSTEM_TAG, "NVS partition was truncated, erasing..."); + ESP_ERROR_CHECK(nvs_flash_erase()); + ret = nvs_flash_init(); + } + + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to initialize NVS: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(SYSTEM_TAG, "State manager initialized successfully"); + return ESP_OK; +} + +esp_err_t state_manager_save(void) { + nvs_handle_t nvs_handle; + esp_err_t err; + + err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Error opening NVS handle: %s", esp_err_to_name(err)); + return err; + } + + // Get current motor state + const motor_state_t* state = motor_get_state(); + motor_mode_t last_on_mode; + int last_on_speed; + motor_get_last_on_state(&last_on_mode, &last_on_speed); + bool user_turned_off = motor_get_user_turned_off(); + + ESP_LOGI(SYSTEM_TAG, "=== SAVING STATE TO NVS ==="); + ESP_LOGI(SYSTEM_TAG, "Mode: %s, Speed: %d%%, Last ON: %s@%d%%, User OFF: %s", + motor_mode_to_string(state->mode), state->target_speed, + motor_mode_to_string(last_on_mode), last_on_speed, + user_turned_off ? "YES" : "NO"); + + // Save current motor state + err = nvs_set_u8(nvs_handle, NVS_KEY_MODE, (uint8_t)state->mode); + if (err == ESP_OK) { + err = nvs_set_u8(nvs_handle, NVS_KEY_SPEED, (uint8_t)state->target_speed); + } + + // Save last ON state + if (err == ESP_OK) { + err = nvs_set_u8(nvs_handle, NVS_KEY_LAST_ON_MODE, (uint8_t)last_on_mode); + } + if (err == ESP_OK) { + err = nvs_set_u8(nvs_handle, NVS_KEY_LAST_ON_SPEED, (uint8_t)last_on_speed); + } + + // Save power state (whether user turned off manually) + if (err == ESP_OK) { + err = nvs_set_u8(nvs_handle, NVS_KEY_POWER_STATE, user_turned_off ? 1 : 0); + } + + if (err == ESP_OK) { + err = nvs_commit(nvs_handle); + if (err == ESP_OK) { + ESP_LOGI(SYSTEM_TAG, "✓ Motor state successfully saved to NVS"); + } else { + ESP_LOGE(SYSTEM_TAG, "✗ NVS commit failed: %s", esp_err_to_name(err)); + } + } else { + ESP_LOGE(SYSTEM_TAG, "✗ Error saving to NVS: %s", esp_err_to_name(err)); + } + ESP_LOGI(SYSTEM_TAG, "==========================="); + + nvs_close(nvs_handle); + return err; +} + +esp_err_t state_manager_load_and_restore(void) { + nvs_handle_t nvs_handle; + esp_err_t err; + + err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + if (err != ESP_OK) { + ESP_LOGI(SYSTEM_TAG, "NVS not found, using default state"); + return ESP_ERR_NVS_NOT_FOUND; + } + + uint8_t stored_mode = 0; + uint8_t stored_speed = 0; + uint8_t stored_last_mode = MOTOR_EXHAUST; // Default to MOTOR_EXHAUST + uint8_t stored_last_speed = 50; + uint8_t stored_power_state = 0; + + // Load current motor state + err = nvs_get_u8(nvs_handle, NVS_KEY_MODE, &stored_mode); + if (err == ESP_OK) { + nvs_get_u8(nvs_handle, NVS_KEY_SPEED, &stored_speed); + nvs_get_u8(nvs_handle, NVS_KEY_LAST_ON_MODE, &stored_last_mode); + nvs_get_u8(nvs_handle, NVS_KEY_LAST_ON_SPEED, &stored_last_speed); + nvs_get_u8(nvs_handle, NVS_KEY_POWER_STATE, &stored_power_state); + + // Validate and clamp all values + validate_and_clamp_values(&stored_mode, &stored_speed, &stored_last_mode, &stored_last_speed); + + // Set the loaded state in motor control module + motor_set_last_on_state((motor_mode_t)stored_last_mode, stored_last_speed); + motor_set_user_turned_off(stored_power_state == 1); + + ESP_LOGI(SYSTEM_TAG, "Loaded state from NVS - Mode: %s, Speed: %d%%, Last ON: %s@%d%%, User OFF: %s", + motor_mode_to_string((motor_mode_t)stored_mode), stored_speed, + motor_mode_to_string((motor_mode_t)stored_last_mode), stored_last_speed, + stored_power_state ? "YES" : "NO"); + + // Log reset analysis + log_reset_analysis(); + + // Determine if we should restore the motor state + bool should_restore = false; + should_restore_state(&should_restore, (motor_mode_t)stored_mode, stored_speed); + + if (should_restore) { + // Restore the motor to its previous state + motor_set_speed((motor_mode_t)stored_mode, stored_speed); + } + // Note: If should_restore is false, motor remains in default OFF state + + } else { + ESP_LOGI(SYSTEM_TAG, "No saved state found, using defaults"); + err = ESP_ERR_NVS_NOT_FOUND; + } + + nvs_close(nvs_handle); + return err; +} + +bool state_manager_was_watchdog_reset(void) { + esp_reset_reason_t reset_reason = esp_reset_reason(); + + // Only consider TASK_WDT and INT_WDT as true watchdog resets + // ESP_RST_WDT can be triggered by power disconnection, so we exclude it + return (reset_reason == ESP_RST_TASK_WDT || + reset_reason == ESP_RST_INT_WDT); +} + +const char* state_manager_get_reset_reason_string(void) { + esp_reset_reason_t reset_reason = esp_reset_reason(); + + switch (reset_reason) { + case ESP_RST_POWERON: return "POWER_ON"; + case ESP_RST_EXT: return "EXTERNAL"; + case ESP_RST_SW: return "SOFTWARE"; + case ESP_RST_PANIC: return "PANIC"; + case ESP_RST_INT_WDT: return "INT_WDT"; + case ESP_RST_TASK_WDT: return "TASK_WDT"; + case ESP_RST_WDT: return "WDT"; + case ESP_RST_DEEPSLEEP: return "DEEPSLEEP"; + case ESP_RST_BROWNOUT: return "BROWNOUT"; + case ESP_RST_SDIO: return "SDIO"; + default: return "UNKNOWN"; + } +} + +esp_err_t state_manager_clear_all(void) { + nvs_handle_t nvs_handle; + esp_err_t err; + + err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Error opening NVS handle for clear: %s", esp_err_to_name(err)); + return err; + } + + ESP_LOGI(SYSTEM_TAG, "Clearing all saved state from NVS..."); + + err = nvs_erase_all(nvs_handle); + if (err == ESP_OK) { + err = nvs_commit(nvs_handle); + if (err == ESP_OK) { + ESP_LOGI(SYSTEM_TAG, "✓ All state cleared from NVS"); + } else { + ESP_LOGE(SYSTEM_TAG, "✗ Failed to commit NVS clear: %s", esp_err_to_name(err)); + } + } else { + ESP_LOGE(SYSTEM_TAG, "✗ Failed to clear NVS: %s", esp_err_to_name(err)); + } + + nvs_close(nvs_handle); + return err; +} + +esp_err_t state_manager_save_last_on_state(motor_mode_t mode, int speed) { + if (mode == MOTOR_OFF || !IS_VALID_SPEED(speed) || speed == 0) { + ESP_LOGW(SYSTEM_TAG, "Invalid last ON state: mode=%s, speed=%d", + motor_mode_to_string(mode), speed); + return ESP_ERR_INVALID_ARG; + } + + nvs_handle_t nvs_handle; + esp_err_t err; + + err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Error opening NVS handle: %s", esp_err_to_name(err)); + return err; + } + + // Save only the last ON state + err = nvs_set_u8(nvs_handle, NVS_KEY_LAST_ON_MODE, (uint8_t)mode); + if (err == ESP_OK) { + err = nvs_set_u8(nvs_handle, NVS_KEY_LAST_ON_SPEED, (uint8_t)speed); + } + + if (err == ESP_OK) { + err = nvs_commit(nvs_handle); + } + + nvs_close(nvs_handle); + + if (err == ESP_OK) { + ESP_LOGI(SYSTEM_TAG, "Last ON state saved: %s @ %d%%", + motor_mode_to_string(mode), speed); + } else { + ESP_LOGE(SYSTEM_TAG, "Failed to save last ON state: %s", esp_err_to_name(err)); + } + + return err; +} + +esp_err_t state_manager_load_last_on_state(motor_mode_t* mode, int* speed) { + if (!mode || !speed) return ESP_ERR_INVALID_ARG; + + nvs_handle_t nvs_handle; + esp_err_t err; + + err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + if (err != ESP_OK) { + return ESP_ERR_NVS_NOT_FOUND; + } + + uint8_t stored_mode = MOTOR_EXHAUST; + uint8_t stored_speed = 50; + + err = nvs_get_u8(nvs_handle, NVS_KEY_LAST_ON_MODE, &stored_mode); + if (err == ESP_OK) { + nvs_get_u8(nvs_handle, NVS_KEY_LAST_ON_SPEED, &stored_speed); + + // Validate values + if (stored_mode < MOTOR_EXHAUST || stored_mode > MOTOR_INTAKE) { + stored_mode = MOTOR_EXHAUST; + } + if (!IS_VALID_SPEED(stored_speed) || stored_speed == 0) { + stored_speed = 50; + } + + *mode = (motor_mode_t)stored_mode; + *speed = stored_speed; + } + + nvs_close(nvs_handle); + return err; +} + +bool state_manager_get_user_turned_off(void) { + return motor_get_user_turned_off(); +} + +esp_err_t state_manager_set_user_turned_off(bool turned_off) { + motor_set_user_turned_off(turned_off); + + // Save just this flag to NVS + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READWRITE, &nvs_handle); + if (err != ESP_OK) { + return err; + } + + err = nvs_set_u8(nvs_handle, NVS_KEY_POWER_STATE, turned_off ? 1 : 0); + if (err == ESP_OK) { + err = nvs_commit(nvs_handle); + } + + nvs_close(nvs_handle); + return err; +} + +esp_err_t state_manager_get_debug_info(char* info_buffer, size_t buffer_size) { + if (!info_buffer || buffer_size == 0) return ESP_ERR_INVALID_ARG; + + const motor_state_t* state = motor_get_state(); + motor_mode_t last_on_mode; + int last_on_speed; + motor_get_last_on_state(&last_on_mode, &last_on_speed); + + int written = snprintf(info_buffer, buffer_size, + "=== STATE MANAGER DEBUG INFO ===\n" + "Reset Reason: %s\n" + "Watchdog Reset: %s\n" + "Current Mode: %s\n" + "Current Speed: %d%%\n" + "Target Speed: %d%%\n" + "Motor State: %s\n" + "Last ON: %s @ %d%%\n" + "User Turned Off: %s\n" + "Has Saved State: %s\n" + "===============================", + state_manager_get_reset_reason_string(), + state_manager_was_watchdog_reset() ? "YES" : "NO", + motor_mode_to_string(state->mode), + state->current_speed, + state->target_speed, + motor_state_to_string(state->state), + motor_mode_to_string(last_on_mode), + last_on_speed, + motor_get_user_turned_off() ? "YES" : "NO", + state_manager_has_saved_state() ? "YES" : "NO" + ); + + return (written < buffer_size) ? ESP_OK : ESP_ERR_INVALID_SIZE; +} + +bool state_manager_has_saved_state(void) { + nvs_handle_t nvs_handle; + esp_err_t err = nvs_open(NVS_NAMESPACE, NVS_READONLY, &nvs_handle); + if (err != ESP_OK) { + return false; + } + + uint8_t dummy; + err = nvs_get_u8(nvs_handle, NVS_KEY_MODE, &dummy); + nvs_close(nvs_handle); + + return (err == ESP_OK); +} \ No newline at end of file diff --git a/main/state_manager.h b/main/state_manager.h new file mode 100644 index 0000000..a1c5e4a --- /dev/null +++ b/main/state_manager.h @@ -0,0 +1,126 @@ +#ifndef STATE_MANAGER_H +#define STATE_MANAGER_H + +#include "esp_err.h" +#include "motor_control.h" +#include + +/** + * @brief Initialize the state manager + * + * Sets up NVS flash and prepares for state operations. + * Must be called before any other state manager functions. + * + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t state_manager_init(void); + +/** + * @brief Save current motor state to NVS + * + * Saves the current motor mode, speed, last ON state, and user preferences + * to non-volatile storage for persistence across power cycles. + * + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t state_manager_save(void); + +/** + * @brief Load motor state from NVS and apply restoration logic + * + * Loads saved state from NVS and determines whether to restore the motor + * based on reset reason and user preferences. Handles: + * - Power loss recovery + * - Watchdog reset detection + * - User manual shutoff preference + * + * @return ESP_OK if state was loaded and applied, ESP_ERR_NVS_NOT_FOUND if no saved state + */ +esp_err_t state_manager_load_and_restore(void); + +/** + * @brief Check if the last reset was due to a watchdog timeout + * + * Distinguishes between true watchdog resets (software issues) and + * other resets like power loss or external reset. + * + * @return true if reset was due to watchdog timeout, false otherwise + */ +bool state_manager_was_watchdog_reset(void); + +/** + * @brief Get human-readable reset reason string + * + * @return String describing the reset reason + */ +const char* state_manager_get_reset_reason_string(void); + +/** + * @brief Clear all saved state from NVS + * + * Removes all motor state data from NVS. Useful for factory reset + * or debugging purposes. + * + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t state_manager_clear_all(void); + +/** + * @brief Save only the "last ON" state to NVS + * + * Saves just the last known good motor state for the ON button functionality. + * This is lighter weight than saving the full state. + * + * @param mode Motor mode (MOTOR_EXHAUST or MOTOR_INTAKE) + * @param speed Speed percentage (1-100) + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t state_manager_save_last_on_state(motor_mode_t mode, int speed); + +/** + * @brief Load only the "last ON" state from NVS + * + * @param mode Pointer to store the loaded mode + * @param speed Pointer to store the loaded speed + * @return ESP_OK if loaded successfully, ESP_ERR_NVS_NOT_FOUND if not found + */ +esp_err_t state_manager_load_last_on_state(motor_mode_t* mode, int* speed); + +/** + * @brief Check if user manually turned off the motor + * + * Used to determine restoration behavior - if user manually turned off, + * the motor should stay off after power restoration. + * + * @return true if user manually turned off, false otherwise + */ +bool state_manager_get_user_turned_off(void); + +/** + * @brief Set the user turned off flag + * + * @param turned_off true if user manually turned off, false otherwise + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t state_manager_set_user_turned_off(bool turned_off); + +/** + * @brief Get detailed state information for debugging + * + * Provides comprehensive information about the saved state and + * reset conditions for troubleshooting. + * + * @param info_buffer Buffer to store the information string + * @param buffer_size Size of the info buffer + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t state_manager_get_debug_info(char* info_buffer, size_t buffer_size); + +/** + * @brief Check if NVS contains any saved motor state + * + * @return true if saved state exists, false otherwise + */ +bool state_manager_has_saved_state(void); + +#endif // STATE_MANAGER_H \ No newline at end of file