From f3fb4f4ac86d89a4ce8a88ea0f1f5cca78eab103 Mon Sep 17 00:00:00 2001 From: Stephen Minakian Date: Wed, 9 Jul 2025 17:29:27 -0600 Subject: [PATCH] Refactor motor control --- main/CMakeLists.txt | 4 +- main/maxxfan-controller.c | 532 +++++++------------------------------- main/motor_control.c | 411 +++++++++++++++++++++++++++++ main/motor_control.h | 166 ++++++++++++ 4 files changed, 670 insertions(+), 443 deletions(-) create mode 100644 main/motor_control.c create mode 100644 main/motor_control.h diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 436a1be..b02e8fd 100755 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,2 +1,2 @@ -idf_component_register(SRCS "maxxfan-controller.c" - INCLUDE_DIRS ".") +idf_component_register(SRCS "maxxfan-controller.c" "motor_control.c" + INCLUDE_DIRS ".") \ No newline at end of file diff --git a/main/maxxfan-controller.c b/main/maxxfan-controller.c index ca94ed3..cd23629 100755 --- a/main/maxxfan-controller.c +++ b/main/maxxfan-controller.c @@ -3,7 +3,6 @@ #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "freertos/event_groups.h" -#include "freertos/timers.h" #include "esp_system.h" #include "esp_wifi.h" #include "esp_event.h" @@ -12,66 +11,16 @@ #include "esp_task_wdt.h" #include "nvs_flash.h" #include "nvs.h" -#include "driver/gpio.h" -#include "driver/ledc.h" #include "cJSON.h" -// Project configuration +// Project modules #include "config.h" +#include "motor_control.h" // WiFi event group static EventGroupHandle_t s_wifi_event_group; static int s_retry_num = 0; -// Motor control -typedef enum { - MOTOR_OFF, - MOTOR_EXHAUST, - MOTOR_INTAKE -} motor_mode_t; - -typedef enum { - MOTOR_STATE_IDLE, // Motor is off or running normally - MOTOR_STATE_RAMPING, // Motor is ramping up/down - MOTOR_STATE_STOPPING, // Motor is stopping for direction change - MOTOR_STATE_COOLDOWN, // Motor is in cooldown period - MOTOR_STATE_RESTARTING // Motor is restarting after cooldown -} motor_state_enum_t; - -typedef struct { - motor_mode_t mode; - motor_mode_t pending_mode; // Mode to switch to after cooldown - int target_speed; - int pending_speed; // Speed to set after cooldown - int current_speed; - motor_state_enum_t state; - bool ramping; - TimerHandle_t ramp_timer; - TimerHandle_t cooldown_timer; - uint32_t cooldown_remaining_ms; // For status reporting - - // State preservation - motor_mode_t last_on_mode; // Last non-OFF mode for ON button - int last_on_speed; // Last non-zero speed for ON button - bool user_turned_off; // Track if user manually turned off -} motor_state_t; - -static motor_state_t motor_state = { - .mode = MOTOR_OFF, - .pending_mode = MOTOR_OFF, - .target_speed = 0, - .pending_speed = 0, - .current_speed = 0, - .state = MOTOR_STATE_IDLE, - .ramping = false, - .ramp_timer = NULL, - .cooldown_timer = NULL, - .cooldown_remaining_ms = 0, - .last_on_mode = MOTOR_EXHAUST, // Default to exhaust for ON button - .last_on_speed = 50, // Default to 50% for ON button - .user_turned_off = false -}; - // HTTP server handle static httpd_handle_t server = NULL; @@ -136,14 +85,9 @@ static const char* html_page = "document.addEventListener('DOMContentLoaded',function(){getStatus();startUpdates()})"; // Forward declarations -static void motor_ramp_timer_callback(TimerHandle_t xTimer); -static void motor_cooldown_timer_callback(TimerHandle_t xTimer); -static void apply_motor_pwm(int speed_percent); -static void start_motor_operation(motor_mode_t mode, int speed_percent); 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); -static void save_last_on_state(motor_mode_t mode, int speed); // Initialize watchdog timer void init_watchdog(void) { @@ -194,29 +138,36 @@ static esp_err_t save_motor_state_to_nvs(void) { 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", - motor_state.mode, motor_state.target_speed, - motor_state.last_on_mode, motor_state.last_on_speed, - motor_state.user_turned_off ? "YES" : "NO"); + 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)motor_state.mode); + 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)motor_state.target_speed); + 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)motor_state.last_on_mode); + 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)motor_state.last_on_speed); + 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, motor_state.user_turned_off ? 1 : 0); + err = nvs_set_u8(nvs_handle, NVS_KEY_POWER_STATE, user_turned_off ? 1 : 0); } if (err == ESP_OK) { @@ -266,13 +217,13 @@ static esp_err_t load_motor_state_from_nvs(void) { 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; - motor_state.last_on_mode = (motor_mode_t)stored_last_mode; - motor_state.last_on_speed = stored_last_speed; - motor_state.user_turned_off = (stored_power_state == 1); + // 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, motor_state.last_on_mode, motor_state.last_on_speed, - motor_state.user_turned_off ? "YES" : "NO"); + 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(); @@ -293,34 +244,28 @@ static esp_err_t load_motor_state_from_nvs(void) { 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", motor_state.user_turned_off ? "YES" : "NO"); + 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_state.mode = MOTOR_OFF; - motor_state.target_speed = 0; - motor_state.current_speed = 0; - motor_state.user_turned_off = false; // Reset user off flag - } else if (motor_state.user_turned_off) { + // 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_state.mode = MOTOR_OFF; - motor_state.target_speed = 0; - motor_state.current_speed = 0; + // 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); - motor_state.mode = (motor_mode_t)stored_mode; - motor_state.target_speed = stored_speed; - motor_state.current_speed = 0; // Always start ramping from 0 + + // 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_state.mode = MOTOR_OFF; - motor_state.target_speed = 0; - motor_state.current_speed = 0; + // Motor module is already initialized in OFF state, no action needed } } else { ESP_LOGI(SYSTEM_TAG, "No saved state found, using defaults"); @@ -331,16 +276,6 @@ static esp_err_t load_motor_state_from_nvs(void) { return err; } -// Save the last ON state (for ON button functionality) -static void save_last_on_state(motor_mode_t mode, int speed) { - if (mode != MOTOR_OFF && speed > 0) { - motor_state.last_on_mode = mode; - motor_state.last_on_speed = speed; - ESP_LOGI(SYSTEM_TAG, "Last ON state updated: %s @ %d%%", - mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", speed); - } -} - // WiFi event handler static void event_handler(void* arg, esp_event_base_t event_base, int32_t event_id, void* event_data) @@ -364,305 +299,6 @@ static void event_handler(void* arg, esp_event_base_t event_base, } } -void configure_gpio_pins(void) -{ - ESP_LOGI(SYSTEM_TAG, "Configuring GPIO pins..."); - - uint64_t pin_mask = (1ULL << LED_PIN) | - (1ULL << MOTOR_R_EN) | - (1ULL << MOTOR_L_EN); - - gpio_config_t io_conf = { - .pin_bit_mask = pin_mask, - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE - }; - - gpio_config(&io_conf); - - gpio_set_level(LED_PIN, 0); - gpio_set_level(MOTOR_R_EN, 0); - gpio_set_level(MOTOR_L_EN, 0); - - ESP_LOGI(SYSTEM_TAG, "GPIO pins configured"); -} - -void configure_pwm(void) -{ - ESP_LOGI(SYSTEM_TAG, "Configuring PWM..."); - - ledc_timer_config_t timer_conf = { - .speed_mode = PWM_SPEED_MODE, - .timer_num = PWM_TIMER, - .duty_resolution = PWM_RESOLUTION, - .freq_hz = PWM_FREQUENCY, - .clk_cfg = LEDC_AUTO_CLK - }; - ledc_timer_config(&timer_conf); - - ledc_channel_config_t channel_conf = { - .channel = PWM_R_CHANNEL, - .duty = 0, - .gpio_num = PWM_R_PIN, - .speed_mode = PWM_SPEED_MODE, - .hpoint = 0, - .timer_sel = PWM_TIMER - }; - ledc_channel_config(&channel_conf); - - channel_conf.channel = PWM_L_CHANNEL; - channel_conf.gpio_num = PWM_L_PIN; - ledc_channel_config(&channel_conf); - - ESP_LOGI(SYSTEM_TAG, "PWM configured"); -} - -// Apply PWM to motor based on current mode and speed -static void apply_motor_pwm(int speed_percent) { - // Clamp speed to valid range using config macro - speed_percent = CLAMP_SPEED(speed_percent); - - uint32_t duty = SPEED_TO_DUTY(speed_percent); - - if (motor_state.mode == MOTOR_OFF || speed_percent == 0) { - gpio_set_level(LED_PIN, 0); - gpio_set_level(MOTOR_R_EN, 0); - gpio_set_level(MOTOR_L_EN, 0); - ledc_set_duty(PWM_SPEED_MODE, PWM_R_CHANNEL, 0); - ledc_set_duty(PWM_SPEED_MODE, PWM_L_CHANNEL, 0); - ledc_update_duty(PWM_SPEED_MODE, PWM_R_CHANNEL); - ledc_update_duty(PWM_SPEED_MODE, PWM_L_CHANNEL); - - } else if (motor_state.mode == MOTOR_EXHAUST) { - gpio_set_level(LED_PIN, 1); - gpio_set_level(MOTOR_R_EN, 1); - gpio_set_level(MOTOR_L_EN, 1); - ledc_set_duty(PWM_SPEED_MODE, PWM_R_CHANNEL, duty); - ledc_set_duty(PWM_SPEED_MODE, PWM_L_CHANNEL, 0); - ledc_update_duty(PWM_SPEED_MODE, PWM_R_CHANNEL); - ledc_update_duty(PWM_SPEED_MODE, PWM_L_CHANNEL); - - } else if (motor_state.mode == MOTOR_INTAKE) { - gpio_set_level(LED_PIN, 1); - gpio_set_level(MOTOR_R_EN, 1); - gpio_set_level(MOTOR_L_EN, 1); - ledc_set_duty(PWM_SPEED_MODE, PWM_R_CHANNEL, 0); - ledc_set_duty(PWM_SPEED_MODE, PWM_L_CHANNEL, duty); - ledc_update_duty(PWM_SPEED_MODE, PWM_R_CHANNEL); - ledc_update_duty(PWM_SPEED_MODE, PWM_L_CHANNEL); - } -} - -// Motor ramp timer callback -static void motor_ramp_timer_callback(TimerHandle_t xTimer) { - if (motor_state.state != MOTOR_STATE_RAMPING) { - return; - } - - int speed_diff = motor_state.target_speed - motor_state.current_speed; - - if (abs(speed_diff) <= RAMP_STEP_SIZE) { - // Close enough to target, finish ramping - motor_state.current_speed = motor_state.target_speed; - motor_state.ramping = false; - motor_state.state = MOTOR_STATE_IDLE; - - // Stop the timer - xTimerStop(motor_state.ramp_timer, 0); - - ESP_LOGI(SYSTEM_TAG, "Ramping complete - Final speed: %d%%", motor_state.current_speed); - } else { - // Continue ramping - if (speed_diff > 0) { - motor_state.current_speed += RAMP_STEP_SIZE; - } else { - motor_state.current_speed -= RAMP_STEP_SIZE; - } - - MOTOR_LOGD(SYSTEM_TAG, "Ramping: %d%% (target: %d%%)", motor_state.current_speed, motor_state.target_speed); - } - - apply_motor_pwm(motor_state.current_speed); -} - -// Motor cooldown timer callback -static void motor_cooldown_timer_callback(TimerHandle_t xTimer) { - ESP_LOGI(SYSTEM_TAG, "Cooldown complete - Starting motor in %s mode at %d%%", - motor_state.pending_mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", - motor_state.pending_speed); - - // Reset cooldown tracking - motor_state.cooldown_remaining_ms = 0; - - // Start the motor in the pending mode - start_motor_operation(motor_state.pending_mode, motor_state.pending_speed); -} - -// Update cooldown remaining time (called periodically) -static void update_cooldown_time(void) { - if (motor_state.state == MOTOR_STATE_COOLDOWN && motor_state.cooldown_remaining_ms > 0) { - if (motor_state.cooldown_remaining_ms >= STATUS_UPDATE_INTERVAL_MS) { - motor_state.cooldown_remaining_ms -= STATUS_UPDATE_INTERVAL_MS; - } else { - motor_state.cooldown_remaining_ms = 0; - } - } -} - -// Start motor operation (internal function) -static void start_motor_operation(motor_mode_t mode, int speed_percent) { - // Clamp speed using config macro - speed_percent = CLAMP_SPEED(speed_percent); - - motor_state.mode = mode; - motor_state.target_speed = speed_percent; - motor_state.state = MOTOR_STATE_RAMPING; - motor_state.ramping = true; - - if (mode == MOTOR_OFF || speed_percent == 0) { - // Immediate stop - motor_state.current_speed = 0; - motor_state.target_speed = 0; - motor_state.state = MOTOR_STATE_IDLE; - motor_state.ramping = false; - apply_motor_pwm(0); - ESP_LOGI(SYSTEM_TAG, "Motor stopped immediately"); - } else { - // Save last ON state for future ON button use - save_last_on_state(mode, speed_percent); - - // Start from minimum speed if currently off - if (motor_state.current_speed == 0) { - int start_speed = (speed_percent < MIN_MOTOR_SPEED) ? speed_percent : MIN_MOTOR_SPEED; - motor_state.current_speed = start_speed; - apply_motor_pwm(start_speed); - ESP_LOGI(SYSTEM_TAG, "Motor starting at %d%%, ramping to %d%%", start_speed, speed_percent); - } - - // Start ramping if needed - if (motor_state.current_speed != motor_state.target_speed) { - xTimerStart(motor_state.ramp_timer, 0); - } else { - motor_state.state = MOTOR_STATE_IDLE; - motor_state.ramping = false; - } - } - - // Save state to NVS after any change - save_motor_state_to_nvs(); -} - -// Initialize motor ramping system -void init_motor_ramping(void) { - motor_state.ramp_timer = xTimerCreate( - "MotorRampTimer", // Timer name - pdMS_TO_TICKS(RAMP_STEP_MS), // Timer period - pdTRUE, // Auto-reload - (void*)0, // Timer ID - motor_ramp_timer_callback // Callback function - ); - - motor_state.cooldown_timer = xTimerCreate( - "MotorCooldownTimer", // Timer name - pdMS_TO_TICKS(DIRECTION_CHANGE_COOLDOWN_MS), // Timer period - pdFALSE, // One-shot - (void*)0, // Timer ID - motor_cooldown_timer_callback // Callback function - ); - - if (motor_state.ramp_timer == NULL || motor_state.cooldown_timer == NULL) { - ESP_LOGE(SYSTEM_TAG, "Failed to create motor timers"); - } else { - ESP_LOGI(SYSTEM_TAG, "Motor control system initialized with direction change safety"); - } -} - -void set_motor_speed(motor_mode_t mode, int speed_percent) -{ - // Clamp speed to valid range using config macro - speed_percent = CLAMP_SPEED(speed_percent); - - ESP_LOGI(SYSTEM_TAG, "Motor command: %s - Speed: %d%% (Current mode: %s, Current speed: %d%%, State: %d)", - mode == MOTOR_OFF ? "OFF" : (mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE"), - speed_percent, - motor_state.mode == MOTOR_OFF ? "OFF" : (motor_state.mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE"), - motor_state.current_speed, - motor_state.state); - - // Track if user manually turned off - if (mode == MOTOR_OFF && motor_state.mode != MOTOR_OFF) { - motor_state.user_turned_off = true; - ESP_LOGI(SYSTEM_TAG, "User manually turned OFF - will stay off after restart"); - } else if (mode != MOTOR_OFF) { - motor_state.user_turned_off = false; - ESP_LOGI(SYSTEM_TAG, "Motor turned ON - will resume after power loss"); - } - - // If we're in cooldown, update the pending command - if (motor_state.state == MOTOR_STATE_COOLDOWN) { - motor_state.pending_mode = mode; - motor_state.pending_speed = speed_percent; - ESP_LOGI(SYSTEM_TAG, "Motor in cooldown - command queued for execution"); - save_motor_state_to_nvs(); // Save the pending state - return; - } - - // Check if this is a direction change that requires cooldown using config macro - bool requires_cooldown = false; - if (motor_state.current_speed > 0 && motor_state.mode != MOTOR_OFF) { - requires_cooldown = IS_DIRECTION_CHANGE(motor_state.mode, mode); - } - - if (requires_cooldown) { - ESP_LOGI(SYSTEM_TAG, "Direction change detected - initiating safety cooldown sequence"); - - // Stop any current ramping - if (motor_state.ramping) { - xTimerStop(motor_state.ramp_timer, 0); - motor_state.ramping = false; - } - - // Stop the motor immediately - motor_state.mode = MOTOR_OFF; - motor_state.current_speed = 0; - motor_state.target_speed = 0; - motor_state.state = MOTOR_STATE_COOLDOWN; - motor_state.cooldown_remaining_ms = DIRECTION_CHANGE_COOLDOWN_MS; - apply_motor_pwm(0); - - // Store the pending command - motor_state.pending_mode = mode; - motor_state.pending_speed = speed_percent; - - // Start cooldown timer - xTimerStart(motor_state.cooldown_timer, 0); - - ESP_LOGI(SYSTEM_TAG, "Motor stopped for direction change - %d second cooldown started", - DIRECTION_CHANGE_COOLDOWN_MS / 1000); - - // Save state including pending command - save_motor_state_to_nvs(); - } else { - // No direction change required, proceed normally - - // Stop any current ramping - if (motor_state.ramping) { - xTimerStop(motor_state.ramp_timer, 0); - motor_state.ramping = false; - } - - // Stop cooldown timer if running - if (motor_state.state == MOTOR_STATE_COOLDOWN) { - xTimerStop(motor_state.cooldown_timer, 0); - motor_state.cooldown_remaining_ms = 0; - } - - start_motor_operation(mode, speed_percent); - } -} - // Helper function to set CORS headers static void set_cors_headers(httpd_req_t *req) { httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*"); @@ -684,11 +320,17 @@ static esp_err_t root_get_handler(httpd_req_t *req) static esp_err_t status_get_handler(httpd_req_t *req) { // Update cooldown time before reporting - update_cooldown_time(); + motor_update_cooldown_time(); - ESP_LOGI(SYSTEM_TAG, "Status request - Mode: %d, Current: %d%%, Target: %d%%, State: %d, Ramping: %s", - motor_state.mode, motor_state.current_speed, motor_state.target_speed, - motor_state.state, motor_state.ramping ? "YES" : "NO"); + // 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); + + ESP_LOGI(SYSTEM_TAG, "Status request - Mode: %s, Current: %d%%, Target: %d%%, State: %s, Ramping: %s", + motor_mode_to_string(state->mode), state->current_speed, state->target_speed, + motor_state_to_string(state->state), state->ramping ? "YES" : "NO"); set_cors_headers(req); httpd_resp_set_type(req, "application/json"); @@ -696,11 +338,11 @@ static esp_err_t status_get_handler(httpd_req_t *req) cJSON *json = cJSON_CreateObject(); const char* mode_str = "off"; - if (motor_state.mode == MOTOR_EXHAUST) mode_str = "exhaust"; - else if (motor_state.mode == MOTOR_INTAKE) mode_str = "intake"; + if (state->mode == MOTOR_EXHAUST) mode_str = "exhaust"; + else if (state->mode == MOTOR_INTAKE) mode_str = "intake"; const char* state_str = "idle"; - switch (motor_state.state) { + switch (state->state) { case MOTOR_STATE_RAMPING: state_str = "ramping"; break; case MOTOR_STATE_STOPPING: state_str = "stopping"; break; case MOTOR_STATE_COOLDOWN: state_str = "cooldown"; break; @@ -709,25 +351,25 @@ static esp_err_t status_get_handler(httpd_req_t *req) } const char* last_on_mode_str = "exhaust"; - if (motor_state.last_on_mode == MOTOR_INTAKE) last_on_mode_str = "intake"; + if (last_on_mode == MOTOR_INTAKE) last_on_mode_str = "intake"; cJSON_AddStringToObject(json, "mode", mode_str); - cJSON_AddNumberToObject(json, "current_speed", motor_state.current_speed); - cJSON_AddNumberToObject(json, "target_speed", motor_state.target_speed); + cJSON_AddNumberToObject(json, "current_speed", state->current_speed); + cJSON_AddNumberToObject(json, "target_speed", state->target_speed); cJSON_AddStringToObject(json, "state", state_str); - cJSON_AddBoolToObject(json, "ramping", motor_state.ramping); - cJSON_AddNumberToObject(json, "cooldown_remaining", motor_state.cooldown_remaining_ms); + cJSON_AddBoolToObject(json, "ramping", state->ramping); + cJSON_AddNumberToObject(json, "cooldown_remaining", state->cooldown_remaining_ms); cJSON_AddStringToObject(json, "last_on_mode", last_on_mode_str); - cJSON_AddNumberToObject(json, "last_on_speed", motor_state.last_on_speed); + cJSON_AddNumberToObject(json, "last_on_speed", last_on_speed); // Add pending command info if in cooldown - if (motor_state.state == MOTOR_STATE_COOLDOWN) { + if (state->state == MOTOR_STATE_COOLDOWN) { const char* pending_mode_str = "off"; - if (motor_state.pending_mode == MOTOR_EXHAUST) pending_mode_str = "exhaust"; - else if (motor_state.pending_mode == MOTOR_INTAKE) pending_mode_str = "intake"; + if (state->pending_mode == MOTOR_EXHAUST) pending_mode_str = "exhaust"; + else if (state->pending_mode == MOTOR_INTAKE) pending_mode_str = "intake"; cJSON_AddStringToObject(json, "pending_mode", pending_mode_str); - cJSON_AddNumberToObject(json, "pending_speed", motor_state.pending_speed); + cJSON_AddNumberToObject(json, "pending_speed", state->pending_speed); } char *json_string = cJSON_Print(json); @@ -799,10 +441,14 @@ static esp_err_t fan_post_handler(httpd_req_t *req) // Handle special "ON" command - resume last settings if (strcmp(mode_str, "on") == 0) { - mode = motor_state.last_on_mode; - speed = motor_state.last_on_speed; - ESP_LOGI(SYSTEM_TAG, "ON button pressed - resuming %s @ %d%%", - mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", speed); + ESP_LOGI(SYSTEM_TAG, "ON button pressed - resuming last state"); + motor_resume_last_state(); + + // Save state after ON button + save_motor_state_to_nvs(); + + cJSON_Delete(json); + return status_get_handler(req); } else if (strcmp(mode_str, "exhaust") == 0) { mode = MOTOR_EXHAUST; } else if (strcmp(mode_str, "intake") == 0) { @@ -810,7 +456,10 @@ static esp_err_t fan_post_handler(httpd_req_t *req) } ESP_LOGI(SYSTEM_TAG, "HTTP Request: mode=%s, speed=%d", mode_str, speed); - set_motor_speed(mode, speed); + motor_set_speed(mode, speed); + + // Save state after any motor command + save_motor_state_to_nvs(); cJSON_Delete(json); @@ -962,14 +611,15 @@ void app_main(void) // Initialize watchdog timer init_watchdog(); - // Configure hardware - configure_gpio_pins(); - configure_pwm(); + // Initialize motor control system + ESP_LOGI(SYSTEM_TAG, "Initializing motor control system..."); + ret = motor_control_init(); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to initialize motor control: %s", esp_err_to_name(ret)); + return; + } - // Initialize motor ramping system - init_motor_ramping(); - - // Load saved state from NVS + // Load saved state from NVS and potentially restore motor state ESP_LOGI(SYSTEM_TAG, "Loading saved state..."); load_motor_state_from_nvs(); @@ -979,35 +629,35 @@ void app_main(void) // Start HTTP server start_webserver(); - // Restore motor state if needed (after WiFi is connected and server is running) - ESP_LOGI(SYSTEM_TAG, "=== MOTOR STATE RESTORATION ==="); - ESP_LOGI(SYSTEM_TAG, "Current motor state: mode=%d, target=%d%%, current=%d%%", - motor_state.mode, motor_state.target_speed, motor_state.current_speed); + // Report final motor state after initialization + const motor_state_t* final_state = motor_get_state(); + ESP_LOGI(SYSTEM_TAG, "=== MOTOR STATE AFTER INITIALIZATION ==="); + 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 (motor_state.mode != MOTOR_OFF && motor_state.target_speed > 0) { - ESP_LOGI(SYSTEM_TAG, "Restoring motor state: %s @ %d%%", - motor_state.mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", - motor_state.target_speed); - - // Start the motor with current settings - motor_state.current_speed = 0; // Start from 0 and ramp up - start_motor_operation(motor_state.mode, motor_state.target_speed); - ESP_LOGI(SYSTEM_TAG, "Motor restoration initiated"); + if (final_state->mode != MOTOR_OFF && final_state->target_speed > 0) { + 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, "No motor state to restore - staying OFF"); + ESP_LOGI(SYSTEM_TAG, "Motor remains OFF"); } - ESP_LOGI(SYSTEM_TAG, "==============================="); + 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: 10-second cooldown for direction changes"); + 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, "Open your browser and go to: http://[ESP32_IP_ADDRESS]"); ESP_LOGI(SYSTEM_TAG, "Check the monitor output above for your IP address"); - // Main loop - reset watchdog periodically + // Main loop - reset watchdog periodically and update motor cooldown while (1) { feed_watchdog(); + + // Update motor cooldown time for status reporting + motor_update_cooldown_time(); + vTaskDelay(pdMS_TO_TICKS(WATCHDOG_FEED_INTERVAL_MS)); } } \ No newline at end of file diff --git a/main/motor_control.c b/main/motor_control.c new file mode 100644 index 0000000..5226b8c --- /dev/null +++ b/main/motor_control.c @@ -0,0 +1,411 @@ +#include "motor_control.h" +#include "config.h" +#include "esp_log.h" +#include "driver/gpio.h" +#include "driver/ledc.h" +#include + +// Private variables +static motor_state_t motor_state = { + .mode = MOTOR_OFF, + .pending_mode = MOTOR_OFF, + .target_speed = 0, + .pending_speed = 0, + .current_speed = 0, + .state = MOTOR_STATE_IDLE, + .ramping = false, + .ramp_timer = NULL, + .cooldown_timer = NULL, + .cooldown_remaining_ms = 0, + .last_on_mode = MOTOR_EXHAUST, // Default to exhaust for ON button + .last_on_speed = 50, // Default to 50% for ON button + .user_turned_off = false +}; + +// Forward declarations for private functions +static void apply_motor_pwm(int speed_percent); +static void start_motor_operation(motor_mode_t mode, int speed_percent); +static void save_last_on_state(motor_mode_t mode, int speed); +static void motor_ramp_timer_callback(TimerHandle_t xTimer); +static void motor_cooldown_timer_callback(TimerHandle_t xTimer); + +// Private function: Apply PWM to motor based on current mode and speed +static void apply_motor_pwm(int speed_percent) { + // Clamp speed to valid range using config macro + speed_percent = CLAMP_SPEED(speed_percent); + + uint32_t duty = SPEED_TO_DUTY(speed_percent); + + if (motor_state.mode == MOTOR_OFF || speed_percent == 0) { + gpio_set_level(LED_PIN, 0); + gpio_set_level(MOTOR_R_EN, 0); + gpio_set_level(MOTOR_L_EN, 0); + ledc_set_duty(PWM_SPEED_MODE, PWM_R_CHANNEL, 0); + ledc_set_duty(PWM_SPEED_MODE, PWM_L_CHANNEL, 0); + ledc_update_duty(PWM_SPEED_MODE, PWM_R_CHANNEL); + ledc_update_duty(PWM_SPEED_MODE, PWM_L_CHANNEL); + + } else if (motor_state.mode == MOTOR_EXHAUST) { + gpio_set_level(LED_PIN, 1); + gpio_set_level(MOTOR_R_EN, 1); + gpio_set_level(MOTOR_L_EN, 1); + ledc_set_duty(PWM_SPEED_MODE, PWM_R_CHANNEL, duty); + ledc_set_duty(PWM_SPEED_MODE, PWM_L_CHANNEL, 0); + ledc_update_duty(PWM_SPEED_MODE, PWM_R_CHANNEL); + ledc_update_duty(PWM_SPEED_MODE, PWM_L_CHANNEL); + + } else if (motor_state.mode == MOTOR_INTAKE) { + gpio_set_level(LED_PIN, 1); + gpio_set_level(MOTOR_R_EN, 1); + gpio_set_level(MOTOR_L_EN, 1); + ledc_set_duty(PWM_SPEED_MODE, PWM_R_CHANNEL, 0); + ledc_set_duty(PWM_SPEED_MODE, PWM_L_CHANNEL, duty); + ledc_update_duty(PWM_SPEED_MODE, PWM_R_CHANNEL); + ledc_update_duty(PWM_SPEED_MODE, PWM_L_CHANNEL); + } +} + +// Private function: Motor ramp timer callback +static void motor_ramp_timer_callback(TimerHandle_t xTimer) { + if (motor_state.state != MOTOR_STATE_RAMPING) { + return; + } + + int speed_diff = motor_state.target_speed - motor_state.current_speed; + + if (abs(speed_diff) <= RAMP_STEP_SIZE) { + // Close enough to target, finish ramping + motor_state.current_speed = motor_state.target_speed; + motor_state.ramping = false; + motor_state.state = MOTOR_STATE_IDLE; + + // Stop the timer + xTimerStop(motor_state.ramp_timer, 0); + + ESP_LOGI(SYSTEM_TAG, "Ramping complete - Final speed: %d%%", motor_state.current_speed); + } else { + // Continue ramping + if (speed_diff > 0) { + motor_state.current_speed += RAMP_STEP_SIZE; + } else { + motor_state.current_speed -= RAMP_STEP_SIZE; + } + + MOTOR_LOGD(SYSTEM_TAG, "Ramping: %d%% (target: %d%%)", motor_state.current_speed, motor_state.target_speed); + } + + apply_motor_pwm(motor_state.current_speed); +} + +// Private function: Motor cooldown timer callback +static void motor_cooldown_timer_callback(TimerHandle_t xTimer) { + ESP_LOGI(SYSTEM_TAG, "Cooldown complete - Starting motor in %s mode at %d%%", + motor_state.pending_mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", + motor_state.pending_speed); + + // Reset cooldown tracking + motor_state.cooldown_remaining_ms = 0; + + // Start the motor in the pending mode + start_motor_operation(motor_state.pending_mode, motor_state.pending_speed); +} + +// Private function: Save the last ON state (for ON button functionality) +static void save_last_on_state(motor_mode_t mode, int speed) { + if (mode != MOTOR_OFF && speed > 0) { + motor_state.last_on_mode = mode; + motor_state.last_on_speed = speed; + ESP_LOGI(SYSTEM_TAG, "Last ON state updated: %s @ %d%%", + mode == MOTOR_EXHAUST ? "EXHAUST" : "INTAKE", speed); + } +} + +// Private function: Start motor operation (internal function) +static void start_motor_operation(motor_mode_t mode, int speed_percent) { + // Clamp speed using config macro + speed_percent = CLAMP_SPEED(speed_percent); + + motor_state.mode = mode; + motor_state.target_speed = speed_percent; + motor_state.state = MOTOR_STATE_RAMPING; + motor_state.ramping = true; + + if (mode == MOTOR_OFF || speed_percent == 0) { + // Immediate stop + motor_state.current_speed = 0; + motor_state.target_speed = 0; + motor_state.state = MOTOR_STATE_IDLE; + motor_state.ramping = false; + apply_motor_pwm(0); + ESP_LOGI(SYSTEM_TAG, "Motor stopped immediately"); + } else { + // Save last ON state for future ON button use + save_last_on_state(mode, speed_percent); + + // Start from minimum speed if currently off + if (motor_state.current_speed == 0) { + int start_speed = (speed_percent < MIN_MOTOR_SPEED) ? speed_percent : MIN_MOTOR_SPEED; + motor_state.current_speed = start_speed; + apply_motor_pwm(start_speed); + ESP_LOGI(SYSTEM_TAG, "Motor starting at %d%%, ramping to %d%%", start_speed, speed_percent); + } + + // Start ramping if needed + if (motor_state.current_speed != motor_state.target_speed) { + xTimerStart(motor_state.ramp_timer, 0); + } else { + motor_state.state = MOTOR_STATE_IDLE; + motor_state.ramping = false; + } + } +} + +// Public API Implementation + +esp_err_t motor_control_init(void) { + ESP_LOGI(SYSTEM_TAG, "Initializing motor control system..."); + + // Configure GPIO pins + ESP_LOGI(SYSTEM_TAG, "Configuring GPIO pins..."); + + uint64_t pin_mask = (1ULL << LED_PIN) | + (1ULL << MOTOR_R_EN) | + (1ULL << MOTOR_L_EN); + + gpio_config_t io_conf = { + .pin_bit_mask = pin_mask, + .mode = GPIO_MODE_OUTPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE + }; + + esp_err_t ret = gpio_config(&io_conf); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to configure GPIO pins: %s", esp_err_to_name(ret)); + return ret; + } + + // Set initial pin states + gpio_set_level(LED_PIN, 0); + gpio_set_level(MOTOR_R_EN, 0); + gpio_set_level(MOTOR_L_EN, 0); + + ESP_LOGI(SYSTEM_TAG, "GPIO pins configured"); + + // Configure PWM + ESP_LOGI(SYSTEM_TAG, "Configuring PWM..."); + + ledc_timer_config_t timer_conf = { + .speed_mode = PWM_SPEED_MODE, + .timer_num = PWM_TIMER, + .duty_resolution = PWM_RESOLUTION, + .freq_hz = PWM_FREQUENCY, + .clk_cfg = LEDC_AUTO_CLK + }; + ret = ledc_timer_config(&timer_conf); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to configure PWM timer: %s", esp_err_to_name(ret)); + return ret; + } + + ledc_channel_config_t channel_conf = { + .channel = PWM_R_CHANNEL, + .duty = 0, + .gpio_num = PWM_R_PIN, + .speed_mode = PWM_SPEED_MODE, + .hpoint = 0, + .timer_sel = PWM_TIMER + }; + ret = ledc_channel_config(&channel_conf); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to configure PWM right channel: %s", esp_err_to_name(ret)); + return ret; + } + + channel_conf.channel = PWM_L_CHANNEL; + channel_conf.gpio_num = PWM_L_PIN; + ret = ledc_channel_config(&channel_conf); + if (ret != ESP_OK) { + ESP_LOGE(SYSTEM_TAG, "Failed to configure PWM left channel: %s", esp_err_to_name(ret)); + return ret; + } + + ESP_LOGI(SYSTEM_TAG, "PWM configured"); + + // Create timers + motor_state.ramp_timer = xTimerCreate( + "MotorRampTimer", // Timer name + pdMS_TO_TICKS(RAMP_STEP_MS), // Timer period + pdTRUE, // Auto-reload + (void*)0, // Timer ID + motor_ramp_timer_callback // Callback function + ); + + motor_state.cooldown_timer = xTimerCreate( + "MotorCooldownTimer", // Timer name + pdMS_TO_TICKS(DIRECTION_CHANGE_COOLDOWN_MS), // Timer period + pdFALSE, // One-shot + (void*)0, // Timer ID + motor_cooldown_timer_callback // Callback function + ); + + if (motor_state.ramp_timer == NULL || motor_state.cooldown_timer == NULL) { + ESP_LOGE(SYSTEM_TAG, "Failed to create motor timers"); + return ESP_FAIL; + } + + ESP_LOGI(SYSTEM_TAG, "Motor control system initialized with direction change safety"); + return ESP_OK; +} + +void motor_set_speed(motor_mode_t mode, int speed_percent) { + // Clamp speed to valid range using config macro + speed_percent = CLAMP_SPEED(speed_percent); + + ESP_LOGI(SYSTEM_TAG, "Motor command: %s - Speed: %d%% (Current mode: %s, Current speed: %d%%, State: %s)", + motor_mode_to_string(mode), speed_percent, + motor_mode_to_string(motor_state.mode), motor_state.current_speed, + motor_state_to_string(motor_state.state)); + + // Track if user manually turned off + if (mode == MOTOR_OFF && motor_state.mode != MOTOR_OFF) { + motor_state.user_turned_off = true; + ESP_LOGI(SYSTEM_TAG, "User manually turned OFF - will stay off after restart"); + } else if (mode != MOTOR_OFF) { + motor_state.user_turned_off = false; + ESP_LOGI(SYSTEM_TAG, "Motor turned ON - will resume after power loss"); + } + + // If we're in cooldown, update the pending command + if (motor_state.state == MOTOR_STATE_COOLDOWN) { + motor_state.pending_mode = mode; + motor_state.pending_speed = speed_percent; + ESP_LOGI(SYSTEM_TAG, "Motor in cooldown - command queued for execution"); + return; + } + + // Check if this is a direction change that requires cooldown using config macro + bool requires_cooldown = false; + if (motor_state.current_speed > 0 && motor_state.mode != MOTOR_OFF) { + requires_cooldown = IS_DIRECTION_CHANGE(motor_state.mode, mode); + } + + if (requires_cooldown) { + ESP_LOGI(SYSTEM_TAG, "Direction change detected - initiating safety cooldown sequence"); + + // Stop any current ramping + if (motor_state.ramping) { + xTimerStop(motor_state.ramp_timer, 0); + motor_state.ramping = false; + } + + // Stop the motor immediately + motor_state.mode = MOTOR_OFF; + motor_state.current_speed = 0; + motor_state.target_speed = 0; + motor_state.state = MOTOR_STATE_COOLDOWN; + motor_state.cooldown_remaining_ms = DIRECTION_CHANGE_COOLDOWN_MS; + apply_motor_pwm(0); + + // Store the pending command + motor_state.pending_mode = mode; + motor_state.pending_speed = speed_percent; + + // Start cooldown timer + xTimerStart(motor_state.cooldown_timer, 0); + + ESP_LOGI(SYSTEM_TAG, "Motor stopped for direction change - %d second cooldown started", + DIRECTION_CHANGE_COOLDOWN_MS / 1000); + } else { + // No direction change required, proceed normally + + // Stop any current ramping + if (motor_state.ramping) { + xTimerStop(motor_state.ramp_timer, 0); + motor_state.ramping = false; + } + + // Stop cooldown timer if running + if (motor_state.state == MOTOR_STATE_COOLDOWN) { + xTimerStop(motor_state.cooldown_timer, 0); + motor_state.cooldown_remaining_ms = 0; + } + + start_motor_operation(mode, speed_percent); + } +} + +const motor_state_t* motor_get_state(void) { + return &motor_state; +} + +void motor_update_cooldown_time(void) { + if (motor_state.state == MOTOR_STATE_COOLDOWN && motor_state.cooldown_remaining_ms > 0) { + if (motor_state.cooldown_remaining_ms >= STATUS_UPDATE_INTERVAL_MS) { + motor_state.cooldown_remaining_ms -= STATUS_UPDATE_INTERVAL_MS; + } else { + motor_state.cooldown_remaining_ms = 0; + } + } +} + +const char* motor_mode_to_string(motor_mode_t mode) { + switch (mode) { + case MOTOR_OFF: return "OFF"; + case MOTOR_EXHAUST: return "EXHAUST"; + case MOTOR_INTAKE: return "INTAKE"; + default: return "UNKNOWN"; + } +} + +const char* motor_state_to_string(motor_state_enum_t state) { + switch (state) { + case MOTOR_STATE_IDLE: return "IDLE"; + case MOTOR_STATE_RAMPING: return "RAMPING"; + case MOTOR_STATE_STOPPING: return "STOPPING"; + case MOTOR_STATE_COOLDOWN: return "COOLDOWN"; + case MOTOR_STATE_RESTARTING: return "RESTARTING"; + default: return "UNKNOWN"; + } +} + +bool motor_is_ramping(void) { + return motor_state.ramping; +} + +bool motor_is_in_cooldown(void) { + return motor_state.state == MOTOR_STATE_COOLDOWN; +} + +uint32_t motor_get_cooldown_remaining(void) { + return motor_state.cooldown_remaining_ms; +} + +void motor_set_last_on_state(motor_mode_t mode, int speed) { + if (mode != MOTOR_OFF && IS_VALID_SPEED(speed) && speed > 0) { + motor_state.last_on_mode = mode; + motor_state.last_on_speed = speed; + ESP_LOGI(SYSTEM_TAG, "Last ON state set: %s @ %d%%", + motor_mode_to_string(mode), speed); + } +} + +void motor_get_last_on_state(motor_mode_t* mode, int* speed) { + if (mode) *mode = motor_state.last_on_mode; + if (speed) *speed = motor_state.last_on_speed; +} + +void motor_resume_last_state(void) { + ESP_LOGI(SYSTEM_TAG, "Resuming last state: %s @ %d%%", + motor_mode_to_string(motor_state.last_on_mode), motor_state.last_on_speed); + motor_set_speed(motor_state.last_on_mode, motor_state.last_on_speed); +} + +void motor_set_user_turned_off(bool turned_off) { + motor_state.user_turned_off = turned_off; +} + +bool motor_get_user_turned_off(void) { + return motor_state.user_turned_off; +} \ No newline at end of file diff --git a/main/motor_control.h b/main/motor_control.h new file mode 100644 index 0000000..3573c47 --- /dev/null +++ b/main/motor_control.h @@ -0,0 +1,166 @@ +#ifndef MOTOR_CONTROL_H +#define MOTOR_CONTROL_H + +#include "freertos/FreeRTOS.h" +#include "freertos/timers.h" +#include "esp_err.h" +#include + +// Motor mode enumeration +typedef enum { + MOTOR_OFF, + MOTOR_EXHAUST, + MOTOR_INTAKE +} motor_mode_t; + +// Motor state enumeration +typedef enum { + MOTOR_STATE_IDLE, // Motor is off or running normally + MOTOR_STATE_RAMPING, // Motor is ramping up/down + MOTOR_STATE_STOPPING, // Motor is stopping for direction change + MOTOR_STATE_COOLDOWN, // Motor is in cooldown period + MOTOR_STATE_RESTARTING // Motor is restarting after cooldown +} motor_state_enum_t; + +// Motor state structure +typedef struct { + motor_mode_t mode; + motor_mode_t pending_mode; // Mode to switch to after cooldown + int target_speed; + int pending_speed; // Speed to set after cooldown + int current_speed; + motor_state_enum_t state; + bool ramping; + TimerHandle_t ramp_timer; + TimerHandle_t cooldown_timer; + uint32_t cooldown_remaining_ms; // For status reporting + + // State preservation + motor_mode_t last_on_mode; // Last non-OFF mode for ON button + int last_on_speed; // Last non-zero speed for ON button + bool user_turned_off; // Track if user manually turned off +} motor_state_t; + +// Public API functions + +/** + * @brief Initialize the motor control system + * + * Sets up GPIO pins, PWM channels, and creates FreeRTOS timers for ramping and cooldown. + * Must be called before any other motor control functions. + * + * @return ESP_OK on success, ESP_FAIL on error + */ +esp_err_t motor_control_init(void); + +/** + * @brief Set motor speed and mode + * + * Controls the motor with automatic ramping and direction change safety. + * Handles cooldown periods when changing directions to prevent mechanical stress. + * + * @param mode Motor mode (MOTOR_OFF, MOTOR_EXHAUST, MOTOR_INTAKE) + * @param speed_percent Speed percentage (0-100) + */ +void motor_set_speed(motor_mode_t mode, int speed_percent); + +/** + * @brief Get current motor state + * + * Returns a pointer to the current motor state structure for status reporting. + * The returned pointer should not be modified directly. + * + * @return Pointer to motor_state_t structure + */ +const motor_state_t* motor_get_state(void); + +/** + * @brief Update cooldown time tracking + * + * Should be called periodically (e.g., every 1 second) to update the + * cooldown_remaining_ms field for status reporting. + */ +void motor_update_cooldown_time(void); + +/** + * @brief Get motor mode as string + * + * @param mode Motor mode enum value + * @return String representation of the mode + */ +const char* motor_mode_to_string(motor_mode_t mode); + +/** + * @brief Get motor state as string + * + * @param state Motor state enum value + * @return String representation of the state + */ +const char* motor_state_to_string(motor_state_enum_t state); + +/** + * @brief Check if motor is currently ramping + * + * @return true if motor is ramping, false otherwise + */ +bool motor_is_ramping(void); + +/** + * @brief Check if motor is in cooldown + * + * @return true if motor is in cooldown, false otherwise + */ +bool motor_is_in_cooldown(void); + +/** + * @brief Get cooldown remaining time in milliseconds + * + * @return Remaining cooldown time in milliseconds, 0 if not in cooldown + */ +uint32_t motor_get_cooldown_remaining(void); + +/** + * @brief Set the "last on" state for the ON button functionality + * + * This is called automatically when the motor is turned on, but can be + * called manually to set the default state for the ON button. + * + * @param mode Motor mode (should be MOTOR_EXHAUST or MOTOR_INTAKE) + * @param speed Speed percentage (1-100) + */ +void motor_set_last_on_state(motor_mode_t mode, int speed); + +/** + * @brief Get the "last on" state + * + * @param mode Pointer to store the last on mode + * @param speed Pointer to store the last on speed + */ +void motor_get_last_on_state(motor_mode_t* mode, int* speed); + +/** + * @brief Resume last motor state (ON button functionality) + * + * Sets the motor to the last known good state (mode and speed). + * This is typically called when the user presses an "ON" button. + */ +void motor_resume_last_state(void); + +/** + * @brief Set user turned off flag + * + * Tracks whether the user manually turned off the motor. + * This affects state restoration behavior after power loss. + * + * @param turned_off true if user manually turned off, false otherwise + */ +void motor_set_user_turned_off(bool turned_off); + +/** + * @brief Get user turned off flag + * + * @return true if user manually turned off, false otherwise + */ +bool motor_get_user_turned_off(void); + +#endif // MOTOR_CONTROL_H \ No newline at end of file