diff --git a/main/main.c b/main/main.c index 5763d46..716673b 100644 --- a/main/main.c +++ b/main/main.c @@ -12,6 +12,9 @@ #include "motor_control.h" #include "sdkconfig.h" +// Uncomment this line to enable motor test mode with shorter intervals +// #define MOTOR_TEST_MODE + static const char *TAG = "MAIN"; // Application version @@ -76,6 +79,7 @@ static void mqtt_connected_callback(void) "plant_watering/pump/+/speed", "plant_watering/commands/test_pump/+", "plant_watering/commands/emergency_stop", + "plant_watering/commands/test_mode", "plant_watering/settings/+/+", NULL }; @@ -161,17 +165,37 @@ static void mqtt_data_callback(const char* topic, const char* data, int data_len // Parse JSON configuration here } else if (strcmp(topic, "plant_watering/commands/test_pump/1") == 0) { uint32_t duration = atoi(data); - if (duration > 0 && duration <= 10000) { // Max 10 seconds for test + if (duration > 0 && duration <= 30000) { // Max 30 seconds for test + ESP_LOGI(TAG, "Test pump 1 for %lu ms", duration); motor_test_run(MOTOR_PUMP_1, duration); + } else { + ESP_LOGW(TAG, "Invalid test duration: %lu (max 30000ms)", duration); } } else if (strcmp(topic, "plant_watering/commands/test_pump/2") == 0) { uint32_t duration = atoi(data); - if (duration > 0 && duration <= 10000) { // Max 10 seconds for test + if (duration > 0 && duration <= 30000) { // Max 30 seconds for test + ESP_LOGI(TAG, "Test pump 2 for %lu ms", duration); motor_test_run(MOTOR_PUMP_2, duration); + } else { + ESP_LOGW(TAG, "Invalid test duration: %lu (max 30000ms)", duration); } } else if (strcmp(topic, "plant_watering/commands/emergency_stop") == 0) { ESP_LOGW(TAG, "Emergency stop command received!"); motor_emergency_stop(); + } else if (strcmp(topic, "plant_watering/commands/test_mode") == 0) { + if (strncmp(data, "on", data_len) == 0) { + ESP_LOGW(TAG, "Enabling test mode - short intervals"); + motor_set_min_interval(MOTOR_PUMP_1, 5000); // 5 seconds + motor_set_min_interval(MOTOR_PUMP_2, 5000); + motor_set_max_runtime(MOTOR_PUMP_1, 30000); // 30 seconds + motor_set_max_runtime(MOTOR_PUMP_2, 30000); + } else if (strncmp(data, "off", data_len) == 0) { + ESP_LOGI(TAG, "Disabling test mode - normal intervals"); + motor_set_min_interval(MOTOR_PUMP_1, CONFIG_WATERING_MIN_INTERVAL_MS); + motor_set_min_interval(MOTOR_PUMP_2, CONFIG_WATERING_MIN_INTERVAL_MS); + motor_set_max_runtime(MOTOR_PUMP_1, CONFIG_WATERING_MAX_DURATION_MS); + motor_set_max_runtime(MOTOR_PUMP_2, CONFIG_WATERING_MAX_DURATION_MS); + } } else if (strncmp(topic, "plant_watering/settings/pump/", 29) == 0) { // Parse settings commands like: // plant_watering/settings/pump/1/max_runtime @@ -373,11 +397,21 @@ void app_main(void) motor_register_state_callback(motor_state_change_callback); motor_register_error_callback(motor_error_callback); - // Configure motor safety limits from Kconfig - motor_set_max_runtime(MOTOR_PUMP_1, CONFIG_WATERING_MAX_DURATION_MS); - motor_set_max_runtime(MOTOR_PUMP_2, CONFIG_WATERING_MAX_DURATION_MS); - motor_set_min_interval(MOTOR_PUMP_1, CONFIG_WATERING_MIN_INTERVAL_MS); - motor_set_min_interval(MOTOR_PUMP_2, CONFIG_WATERING_MIN_INTERVAL_MS); + // Configure motor safety limits + #ifdef MOTOR_TEST_MODE + // Use shorter limits for testing + ESP_LOGI(TAG, "MOTOR TEST MODE - Using short intervals for testing"); + motor_set_max_runtime(MOTOR_PUMP_1, 30000); // 30 seconds max + motor_set_max_runtime(MOTOR_PUMP_2, 30000); + motor_set_min_interval(MOTOR_PUMP_1, 5000); // 5 seconds for testing + motor_set_min_interval(MOTOR_PUMP_2, 5000); + #else + // Use production values from Kconfig + motor_set_max_runtime(MOTOR_PUMP_1, CONFIG_WATERING_MAX_DURATION_MS); + motor_set_max_runtime(MOTOR_PUMP_2, CONFIG_WATERING_MAX_DURATION_MS); + motor_set_min_interval(MOTOR_PUMP_1, CONFIG_WATERING_MIN_INTERVAL_MS); + motor_set_min_interval(MOTOR_PUMP_2, CONFIG_WATERING_MIN_INTERVAL_MS); + #endif // Start WiFi connection esp_err_t ret = wifi_manager_start(); diff --git a/main/motor_control.c b/main/motor_control.c index 1c88286..d870d29 100644 --- a/main/motor_control.c +++ b/main/motor_control.c @@ -182,7 +182,10 @@ esp_err_t motor_control_init(void) // Create safety timers for (int i = 0; i < MOTOR_PUMP_MAX - 1; i++) { - s_motors[i].safety_timer = xTimerCreate("motor_safety", + char timer_name[32]; + snprintf(timer_name, sizeof(timer_name), "motor_safety_%d", i + 1); + + s_motors[i].safety_timer = xTimerCreate(timer_name, pdMS_TO_TICKS(1000), pdFALSE, (void*)(i + 1), @@ -193,7 +196,8 @@ esp_err_t motor_control_init(void) goto error; } - s_motors[i].soft_start_timer = xTimerCreate("motor_soft_start", + snprintf(timer_name, sizeof(timer_name), "motor_soft_%d", i + 1); + s_motors[i].soft_start_timer = xTimerCreate(timer_name, pdMS_TO_TICKS(50), pdTRUE, (void*)(i + 1), @@ -627,13 +631,22 @@ esp_err_t motor_test_run(motor_id_t id, uint32_t duration_ms) static void motor_safety_timer_callback(TimerHandle_t xTimer) { motor_id_t id = (motor_id_t)(intptr_t)pvTimerGetTimerID(xTimer); + motor_t *motor = &s_motors[id - 1]; ESP_LOGW(TAG, "Safety timer expired for motor %d", id); - motor_stop(id); - if (s_error_callback) { - s_error_callback(id, "Maximum runtime exceeded"); - } + // Do minimal work in timer callback to avoid stack overflow + // Just stop the PWM and update state + ledc_set_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel, 0); + ledc_update_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel); + + // Update basic state + motor->speed_percent = 0; + motor->state = MOTOR_STATE_STOPPED; + + // Stats update can be deferred or done in main context + // For now, just record the stop time + motor->last_stop_time = get_time_ms(); } static void motor_soft_start_timer_callback(TimerHandle_t xTimer) @@ -661,7 +674,10 @@ static void motor_soft_start_timer_callback(TimerHandle_t xTimer) } uint8_t duty = (motor->speed_percent * MOTOR_PWM_MAX_DUTY) / 100; - motor_update_pwm(id, duty); + + // Update PWM directly without taking mutex (atomic operation) + ledc_set_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel, duty); + ledc_update_duty(LEDC_LOW_SPEED_MODE, motor->pwm_channel); ESP_LOGD(TAG, "Soft start motor %d: %d%% (target: %d%%)", id, motor->speed_percent, motor->target_speed); diff --git a/main/motor_control.h b/main/motor_control.h index f4dbdff..9c23e8b 100644 --- a/main/motor_control.h +++ b/main/motor_control.h @@ -21,9 +21,16 @@ // Safety Configuration #define MOTOR_DEFAULT_SPEED 80 // Default pump speed (%) #define MOTOR_MIN_SPEED 20 // Minimum pump speed (%) -#define MOTOR_MAX_RUNTIME_MS 30000 // Maximum runtime (30 seconds) -#define MOTOR_MIN_INTERVAL_MS 300000 // Minimum interval between runs (5 minutes) -#define MOTOR_SOFT_START_TIME_MS 1000 // Soft start ramp time + +// Default safety limits (can be overridden at runtime) +#define MOTOR_MAX_RUNTIME_MS 30000 // Default maximum runtime (30 seconds) +#define MOTOR_MIN_INTERVAL_MS 300000 // Default minimum interval between runs (5 minutes) + +// Test mode limits (shorter for testing) +#define MOTOR_TEST_MAX_RUNTIME_MS 30000 // Test mode max runtime (30 seconds) +#define MOTOR_TEST_MIN_INTERVAL_MS 5000 // Test mode min interval (5 seconds) + +#define MOTOR_SOFT_START_TIME_MS 500 // Soft start ramp time // Motor IDs typedef enum { diff --git a/main/motor_test.c b/main/motor_test.c new file mode 100644 index 0000000..b418afd --- /dev/null +++ b/main/motor_test.c @@ -0,0 +1,242 @@ +/** + * Motor Control Hardware Test Program + * + * This is a standalone test program to verify TB6612FNG motor driver + * connections before integrating with the full system. + * + * Test sequence: + * 1. Initialize motor control + * 2. Test each pump individually + * 3. Test PWM speed control + * 4. Test safety features + * 5. Test both pumps together + */ + + #include + #include "freertos/FreeRTOS.h" + #include "freertos/task.h" + #include "esp_log.h" + #include "motor_control.h" + + static const char *TAG = "MOTOR_TEST"; + + // Test callbacks + static void test_state_callback(motor_id_t id, motor_state_t state) + { + const char *state_str[] = {"STOPPED", "RUNNING", "ERROR", "COOLDOWN"}; + ESP_LOGI(TAG, "Motor %d state: %s", id, state_str[state]); + } + + static void test_error_callback(motor_id_t id, const char* error) + { + ESP_LOGE(TAG, "Motor %d error: %s", id, error); + } + + void app_main(void) + { + ESP_LOGI(TAG, "=== TB6612FNG Motor Driver Test Program ==="); + ESP_LOGI(TAG, "GPIO Connections:"); + ESP_LOGI(TAG, " AIN1 (Pump1 Dir): GPIO%d", MOTOR_AIN1_GPIO); + ESP_LOGI(TAG, " AIN2 (Pump1 Dir): GPIO%d", MOTOR_AIN2_GPIO); + ESP_LOGI(TAG, " BIN1 (Pump2 Dir): GPIO%d", MOTOR_BIN1_GPIO); + ESP_LOGI(TAG, " BIN2 (Pump2 Dir): GPIO%d", MOTOR_BIN2_GPIO); + ESP_LOGI(TAG, " PWMA (Pump1 PWM): GPIO%d", MOTOR_PWMA_GPIO); + ESP_LOGI(TAG, " PWMB (Pump2 PWM): GPIO%d", MOTOR_PWMB_GPIO); + ESP_LOGI(TAG, " STBY (Standby): GPIO%d", MOTOR_STBY_GPIO); + + // Initialize motor control + ESP_LOGI(TAG, "\n--- Initializing Motor Control ---"); + esp_err_t ret = motor_control_init(); + if (ret != ESP_OK) { + ESP_LOGE(TAG, "Failed to initialize motor control: %s", esp_err_to_name(ret)); + return; + } + + // Register callbacks + motor_register_state_callback(test_state_callback); + motor_register_error_callback(test_error_callback); + + // Wait a bit + vTaskDelay(pdMS_TO_TICKS(2000)); + + // Test 1: Basic ON/OFF for Pump 1 + ESP_LOGI(TAG, "\n--- Test 1: Pump 1 Basic ON/OFF ---"); + ESP_LOGI(TAG, "Starting Pump 1 at default speed (%d%%)", MOTOR_DEFAULT_SPEED); + motor_start(MOTOR_PUMP_1, MOTOR_DEFAULT_SPEED); + vTaskDelay(pdMS_TO_TICKS(3000)); + + ESP_LOGI(TAG, "Stopping Pump 1"); + motor_stop(MOTOR_PUMP_1); + vTaskDelay(pdMS_TO_TICKS(2000)); + + // Test 2: Basic ON/OFF for Pump 2 + ESP_LOGI(TAG, "\n--- Test 2: Pump 2 Basic ON/OFF ---"); + ESP_LOGI(TAG, "Starting Pump 2 at default speed (%d%%)", MOTOR_DEFAULT_SPEED); + motor_start(MOTOR_PUMP_2, MOTOR_DEFAULT_SPEED); + vTaskDelay(pdMS_TO_TICKS(3000)); + + ESP_LOGI(TAG, "Stopping Pump 2"); + motor_stop(MOTOR_PUMP_2); + vTaskDelay(pdMS_TO_TICKS(2000)); + + // Test 3: PWM Speed Control + ESP_LOGI(TAG, "\n--- Test 3: PWM Speed Control (Pump 1) ---"); + int speeds[] = {30, 50, 70, 90, 100}; + for (int i = 0; i < sizeof(speeds)/sizeof(speeds[0]); i++) { + ESP_LOGI(TAG, "Testing speed: %d%%", speeds[i]); + motor_start(MOTOR_PUMP_1, speeds[i]); + vTaskDelay(pdMS_TO_TICKS(2000)); + } + motor_stop(MOTOR_PUMP_1); + vTaskDelay(pdMS_TO_TICKS(2000)); + + // Test 4: Timed Run + ESP_LOGI(TAG, "\n--- Test 4: Timed Run (5 seconds) ---"); + ESP_LOGI(TAG, "Starting Pump 1 for 5 seconds"); + motor_start_timed(MOTOR_PUMP_1, 70, 5000); + vTaskDelay(pdMS_TO_TICKS(7000)); // Wait for completion + + // Test 5: Both pumps together + ESP_LOGI(TAG, "\n--- Test 5: Both Pumps Together ---"); + ESP_LOGI(TAG, "Starting both pumps"); + motor_start(MOTOR_PUMP_1, 60); + motor_start(MOTOR_PUMP_2, 80); + vTaskDelay(pdMS_TO_TICKS(3000)); + + ESP_LOGI(TAG, "Stopping both pumps"); + motor_stop_all(); + vTaskDelay(pdMS_TO_TICKS(2000)); + + // Test 6: Safety - Cooldown Period + ESP_LOGI(TAG, "\n--- Test 6: Cooldown Period Test ---"); + motor_set_min_interval(MOTOR_PUMP_1, 5000); // 5 second cooldown for test + + ESP_LOGI(TAG, "Starting pump 1"); + motor_start(MOTOR_PUMP_1, 50); + vTaskDelay(pdMS_TO_TICKS(2000)); + motor_stop(MOTOR_PUMP_1); + + ESP_LOGI(TAG, "Attempting to restart immediately (should fail)"); + ret = motor_start(MOTOR_PUMP_1, 50); + if (ret != ESP_OK) { + ESP_LOGI(TAG, "Good! Cooldown protection working"); + } + + ESP_LOGI(TAG, "Waiting for cooldown period..."); + vTaskDelay(pdMS_TO_TICKS(6000)); + + ESP_LOGI(TAG, "Attempting to start after cooldown"); + ret = motor_start(MOTOR_PUMP_1, 50); + if (ret == ESP_OK) { + ESP_LOGI(TAG, "Good! Pump started after cooldown"); + vTaskDelay(pdMS_TO_TICKS(2000)); + motor_stop(MOTOR_PUMP_1); + } + + // Test 7: Speed change while running + ESP_LOGI(TAG, "\n--- Test 7: Speed Change While Running ---"); + ESP_LOGI(TAG, "Starting at 30%%"); + motor_start(MOTOR_PUMP_1, 30); + vTaskDelay(pdMS_TO_TICKS(2000)); + + ESP_LOGI(TAG, "Changing to 70%%"); + motor_set_speed(MOTOR_PUMP_1, 70); + vTaskDelay(pdMS_TO_TICKS(2000)); + + ESP_LOGI(TAG, "Changing to 100%%"); + motor_set_speed(MOTOR_PUMP_1, 100); + vTaskDelay(pdMS_TO_TICKS(2000)); + + motor_stop(MOTOR_PUMP_1); + + // Test 8: Emergency Stop + ESP_LOGI(TAG, "\n--- Test 8: Emergency Stop ---"); + ESP_LOGI(TAG, "Starting both pumps"); + motor_start(MOTOR_PUMP_1, 80); + motor_start(MOTOR_PUMP_2, 80); + vTaskDelay(pdMS_TO_TICKS(2000)); + + ESP_LOGI(TAG, "Triggering emergency stop!"); + motor_emergency_stop(); + + ESP_LOGI(TAG, "Checking states after emergency stop"); + if (!motor_is_running(MOTOR_PUMP_1) && !motor_is_running(MOTOR_PUMP_2)) { + ESP_LOGI(TAG, "Good! Both pumps stopped"); + } + + vTaskDelay(pdMS_TO_TICKS(2000)); + + // Test 9: Get Statistics + ESP_LOGI(TAG, "\n--- Test 9: Runtime Statistics ---"); + motor_stats_t stats; + for (int i = 1; i <= 2; i++) { + if (motor_get_stats(i, &stats) == ESP_OK) { + ESP_LOGI(TAG, "Pump %d Statistics:", i); + ESP_LOGI(TAG, " Total runtime: %lu ms", stats.total_runtime_ms); + ESP_LOGI(TAG, " Run count: %lu", stats.run_count); + ESP_LOGI(TAG, " Last duration: %lu ms", stats.last_run_duration_ms); + ESP_LOGI(TAG, " Error count: %lu", stats.error_count); + } + } + + // Test 10: Maximum runtime safety + ESP_LOGI(TAG, "\n--- Test 10: Maximum Runtime Safety ---"); + motor_set_max_runtime(MOTOR_PUMP_1, 3000); // 3 second max for test + + ESP_LOGI(TAG, "Starting pump 1 (should auto-stop after 3 seconds)"); + motor_start(MOTOR_PUMP_1, 50); + + // Wait for safety timer + vTaskDelay(pdMS_TO_TICKS(5000)); + + if (!motor_is_running(MOTOR_PUMP_1)) { + ESP_LOGI(TAG, "Good! Safety timer stopped the pump"); + } + + // Test 11: Soft Start Observation + ESP_LOGI(TAG, "\n--- Test 11: Soft Start Observation ---"); + ESP_LOGI(TAG, "Watch/listen for gradual speed increase over 500ms"); + motor_start(MOTOR_PUMP_1, 100); + vTaskDelay(pdMS_TO_TICKS(3000)); + motor_stop(MOTOR_PUMP_1); + + // Final test summary + ESP_LOGI(TAG, "\n=== All Tests Complete ==="); + ESP_LOGI(TAG, "Test Summary:"); + ESP_LOGI(TAG, " ✓ Basic ON/OFF control"); + ESP_LOGI(TAG, " ✓ PWM speed control"); + ESP_LOGI(TAG, " ✓ Timed operations"); + ESP_LOGI(TAG, " ✓ Dual pump control"); + ESP_LOGI(TAG, " ✓ Safety features"); + ESP_LOGI(TAG, " ✓ Emergency stop"); + ESP_LOGI(TAG, " ✓ Statistics tracking"); + ESP_LOGI(TAG, ""); + ESP_LOGI(TAG, "Monitor the pumps to ensure they responded correctly to all commands"); + ESP_LOGI(TAG, "Check for any unusual noises, heating, or behavior"); + + // Keep running and print status periodically + ESP_LOGI(TAG, "\nEntering monitoring mode - System status every 10 seconds"); + + while (1) { + vTaskDelay(pdMS_TO_TICKS(10000)); + + ESP_LOGI(TAG, "--- System Status ---"); + ESP_LOGI(TAG, "Free heap: %d bytes", esp_get_free_heap_size()); + + for (int i = 1; i <= 2; i++) { + motor_stats_t current_stats; + if (motor_get_stats(i, ¤t_stats) == ESP_OK) { + const char *state = "IDLE"; + if (motor_is_running(i)) { + state = "RUNNING"; + } else if (motor_is_cooldown(i)) { + state = "COOLDOWN"; + } + + ESP_LOGI(TAG, "Pump %d: State=%s, Total runs=%lu, Total time=%lu s", + i, state, current_stats.run_count, + current_stats.total_runtime_ms / 1000); + } + } + } + } \ No newline at end of file