Refactor, state manager

This commit is contained in:
2025-07-09 22:55:35 -06:00
parent f3fb4f4ac8
commit c3bbba4a24
4 changed files with 592 additions and 184 deletions

View File

@ -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 ".")

View File

@ -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()})</script></body></html>";
// 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");

422
main/state_manager.c Normal file
View File

@ -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 <string.h>
#include <stdio.h>
// 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);
}

126
main/state_manager.h Normal file
View File

@ -0,0 +1,126 @@
#ifndef STATE_MANAGER_H
#define STATE_MANAGER_H
#include "esp_err.h"
#include "motor_control.h"
#include <stdbool.h>
/**
* @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