#include #include #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_system.h" #include "esp_log.h" #include "esp_chip_info.h" #include "esp_random.h" #include "wifi_manager.h" #include "ota_server.h" #include "plant_mqtt.h" #include "motor_control.h" #include "scheduler.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 #define APP_VERSION "2.2.0-scheduler" // Test data static int test_moisture_1 = 45; static int test_moisture_2 = 62; // Function prototypes static void print_chip_info(void); // Motor Control Callbacks static void motor_state_change_callback(motor_id_t id, motor_state_t state) { const char *state_str = "unknown"; switch (state) { case MOTOR_STATE_STOPPED: state_str = "off"; break; case MOTOR_STATE_RUNNING: state_str = "on"; break; case MOTOR_STATE_ERROR: state_str = "error"; break; case MOTOR_STATE_COOLDOWN: state_str = "cooldown"; break; } ESP_LOGI(TAG, "Motor %d state changed to: %s", id, state_str); // Publish state change to MQTT if (mqtt_client_is_connected()) { mqtt_publish_pump_state(id, state == MOTOR_STATE_RUNNING); } } static void motor_error_callback(motor_id_t id, const char* error) { ESP_LOGE(TAG, "Motor %d error: %s", id, error); // Publish error to MQTT alert topic if (mqtt_client_is_connected()) { char topic[64]; snprintf(topic, sizeof(topic), "plant_watering/alerts/pump_error/%d", id); mqtt_client_publish(topic, error, MQTT_QOS_1, MQTT_NO_RETAIN); } } // Scheduler callback static void scheduler_trigger_callback(uint8_t pump_id, uint8_t schedule_id, uint32_t duration_ms, uint8_t speed_percent) { ESP_LOGI(TAG, "Schedule %d triggered for pump %d: %lu ms at %d%%", schedule_id, pump_id, duration_ms, speed_percent); // Start the pump with the scheduled parameters esp_err_t ret = motor_start_timed(pump_id, speed_percent, duration_ms); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start pump %d for schedule %d: %s", pump_id, schedule_id, esp_err_to_name(ret)); // Publish error to MQTT if (mqtt_client_is_connected()) { char topic[64]; char msg[128]; snprintf(topic, sizeof(topic), "plant_watering/alerts/schedule_error/%d", pump_id); snprintf(msg, sizeof(msg), "Schedule %d failed: %s", schedule_id, esp_err_to_name(ret)); mqtt_client_publish(topic, msg, MQTT_QOS_1, MQTT_NO_RETAIN); } } else { // Publish schedule execution to MQTT if (mqtt_client_is_connected()) { char topic[64]; char msg[128]; snprintf(topic, sizeof(topic), "plant_watering/schedule/%d/executed", pump_id); snprintf(msg, sizeof(msg), "{\"schedule_id\":%d,\"duration_ms\":%lu,\"speed\":%d}", schedule_id, duration_ms, speed_percent); mqtt_client_publish(topic, msg, MQTT_QOS_0, MQTT_NO_RETAIN); } } } // MQTT Callbacks static void mqtt_connected_callback(void) { ESP_LOGI(TAG, "MQTT Connected - Publishing initial status"); // Publish initial states mqtt_publish_moisture(1, test_moisture_1); mqtt_publish_moisture(2, test_moisture_2); mqtt_publish_pump_state(1, motor_is_running(MOTOR_PUMP_1)); mqtt_publish_pump_state(2, motor_is_running(MOTOR_PUMP_2)); // Subscribe to additional topics static const char* additional_topics[] = { "plant_watering/pump/+/speed", "plant_watering/commands/test_pump/+", "plant_watering/commands/emergency_stop", "plant_watering/commands/test_mode", "plant_watering/commands/holiday_mode", "plant_watering/commands/get_time", "plant_watering/commands/get_schedules", "plant_watering/schedule/+/+/config", "plant_watering/schedule/+/trigger", "plant_watering/schedule/+/get", "plant_watering/schedule/time/set", "plant_watering/settings/+/+", NULL }; for (int i = 0; additional_topics[i] != NULL; i++) { esp_err_t ret = mqtt_client_subscribe(additional_topics[i], MQTT_QOS_1); if (ret == ESP_OK) { ESP_LOGI(TAG, "Subscribed to: %s", additional_topics[i]); } else { ESP_LOGE(TAG, "Failed to subscribe to: %s", additional_topics[i]); } } // Publish motor statistics motor_stats_t stats; for (int i = 1; i <= 2; i++) { if (motor_get_stats(i, &stats) == ESP_OK) { char topic[64]; char data[128]; snprintf(topic, sizeof(topic), "plant_watering/pump/%d/stats", i); snprintf(data, sizeof(data), "{\"total_runtime\":%lu,\"run_count\":%lu,\"last_duration\":%lu}", stats.total_runtime_ms, stats.run_count, stats.last_run_duration_ms); mqtt_client_publish(topic, data, MQTT_QOS_0, MQTT_NO_RETAIN); } } } static void mqtt_disconnected_callback(void) { ESP_LOGW(TAG, "MQTT Disconnected"); } static void mqtt_data_callback(const char* topic, const char* data, int data_len) { ESP_LOGI(TAG, "MQTT Data received on topic: %s", topic); ESP_LOGI(TAG, "Data: %.*s", data_len, data); // Handle pump control commands if (strcmp(topic, TOPIC_PUMP_1_CMD) == 0) { if (strncmp(data, "on", data_len) == 0) { ESP_LOGI(TAG, "Starting pump 1 via MQTT"); esp_err_t ret = motor_start(MOTOR_PUMP_1, MOTOR_DEFAULT_SPEED); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start pump 1: %s", esp_err_to_name(ret)); } } else if (strncmp(data, "off", data_len) == 0) { ESP_LOGI(TAG, "Stopping pump 1 via MQTT"); motor_stop(MOTOR_PUMP_1); } else if (strncmp(data, "pulse", data_len) == 0) { ESP_LOGI(TAG, "Pulse pump 1 for 5 seconds"); motor_start_timed(MOTOR_PUMP_1, MOTOR_DEFAULT_SPEED, 5000); } } else if (strcmp(topic, TOPIC_PUMP_2_CMD) == 0) { if (strncmp(data, "on", data_len) == 0) { ESP_LOGI(TAG, "Starting pump 2 via MQTT"); esp_err_t ret = motor_start(MOTOR_PUMP_2, MOTOR_DEFAULT_SPEED); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start pump 2: %s", esp_err_to_name(ret)); } } else if (strncmp(data, "off", data_len) == 0) { ESP_LOGI(TAG, "Stopping pump 2 via MQTT"); motor_stop(MOTOR_PUMP_2); } else if (strncmp(data, "pulse", data_len) == 0) { ESP_LOGI(TAG, "Pulse pump 2 for 5 seconds"); motor_start_timed(MOTOR_PUMP_2, MOTOR_DEFAULT_SPEED, 5000); } } else if (strcmp(topic, "plant_watering/pump/1/speed") == 0) { int speed = atoi(data); if (speed >= 0 && speed <= 100) { motor_set_speed(MOTOR_PUMP_1, speed); ESP_LOGI(TAG, "Set pump 1 speed to %d%%", speed); } } else if (strcmp(topic, "plant_watering/pump/2/speed") == 0) { int speed = atoi(data); if (speed >= 0 && speed <= 100) { motor_set_speed(MOTOR_PUMP_2, speed); ESP_LOGI(TAG, "Set pump 2 speed to %d%%", speed); } } else if (strcmp(topic, TOPIC_CONFIG) == 0) { ESP_LOGI(TAG, "Configuration update received"); // Parse JSON configuration here } else if (strcmp(topic, "plant_watering/commands/test_pump/1") == 0) { uint32_t duration = atoi(data); 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 <= 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 (strcmp(topic, "plant_watering/commands/holiday_mode") == 0) { if (strncmp(data, "on", data_len) == 0) { scheduler_set_holiday_mode(true); ESP_LOGI(TAG, "Holiday mode enabled - all schedules paused"); } else if (strncmp(data, "off", data_len) == 0) { scheduler_set_holiday_mode(false); ESP_LOGI(TAG, "Holiday mode disabled - schedules resumed"); } } else if (strcmp(topic, "plant_watering/commands/get_time") == 0) { // Publish current time information if (scheduler_is_time_synchronized()) { time_t now = scheduler_get_current_time(); struct tm timeinfo; localtime_r(&now, &timeinfo); char time_str[64]; strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S %Z", &timeinfo); char response[256]; snprintf(response, sizeof(response), "{\"timestamp\":%lld,\"datetime\":\"%s\",\"timezone\":\"%s\",\"synced\":true}", (long long)now, time_str, getenv("TZ") ? getenv("TZ") : "UTC"); mqtt_client_publish("plant_watering/system/time", response, MQTT_QOS_0, MQTT_NO_RETAIN); ESP_LOGI(TAG, "Time: %s", time_str); } else { mqtt_client_publish("plant_watering/system/time", "{\"synced\":false,\"message\":\"Time not synchronized\"}", MQTT_QOS_0, MQTT_NO_RETAIN); ESP_LOGW(TAG, "Time not synchronized"); } } else if (strcmp(topic, "plant_watering/commands/get_schedules") == 0) { // Publish all configured schedules ESP_LOGI(TAG, "Publishing all schedules"); int active_count = 0; // Publish each configured schedule for (int pump = 1; pump <= 2; pump++) { for (int sched = 0; sched < SCHEDULER_MAX_SCHEDULES_PER_PUMP; sched++) { schedule_config_t config; if (scheduler_get_schedule(pump, sched, &config) == ESP_OK) { // Only publish if schedule is configured (not disabled) if (config.type != SCHEDULE_TYPE_DISABLED) { char topic_buf[64]; char json[512]; snprintf(topic_buf, sizeof(topic_buf), "plant_watering/schedule/%d/%d/current", pump, sched); if (scheduler_schedule_to_json(pump, sched, json, sizeof(json)) == ESP_OK) { mqtt_client_publish(topic_buf, json, MQTT_QOS_0, MQTT_NO_RETAIN); if (config.enabled) { active_count++; } } } } } } // Publish summary char summary[256]; snprintf(summary, sizeof(summary), "{\"total_schedules\":%d,\"active_schedules\":%d,\"holiday_mode\":%s,\"time_sync\":%s}", active_count, active_count, scheduler_get_holiday_mode() ? "true" : "false", scheduler_is_time_synchronized() ? "true" : "false"); mqtt_client_publish("plant_watering/schedule/summary", summary, MQTT_QOS_0, MQTT_NO_RETAIN); ESP_LOGI(TAG, "Published %d schedules", active_count); } else if (strncmp(topic, "plant_watering/schedule/", 24) == 0) { // Parse schedule commands if (strcmp(topic, "plant_watering/schedule/time/set") == 0) { // Set system time manually (useful if no NTP) time_t timestamp = atoll(data); // Use atoll for long long if (timestamp > 0) { scheduler_set_time(timestamp); ESP_LOGI(TAG, "System time set to %lld", (long long)timestamp); } } else { int pump_id = 0; int schedule_id = 0; char action[16] = {0}; int parsed = sscanf(topic + 24, "%d/%d/%15s", &pump_id, &schedule_id, action); if (parsed == 2) { // Check if it's a trigger command if (sscanf(topic + 24, "%d/trigger", &pump_id) == 1) { if (pump_id >= 1 && pump_id <= 2) { // Trigger all enabled schedules for this pump ESP_LOGI(TAG, "Manual trigger for pump %d schedules", pump_id); for (int i = 0; i < SCHEDULER_MAX_SCHEDULES_PER_PUMP; i++) { scheduler_trigger_schedule(pump_id, i); } } } // Check if it's a get command for specific pump else if (sscanf(topic + 24, "%d/get", &pump_id) == 1) { if (pump_id >= 1 && pump_id <= 2) { ESP_LOGI(TAG, "Getting schedules for pump %d", pump_id); for (int sched = 0; sched < SCHEDULER_MAX_SCHEDULES_PER_PUMP; sched++) { schedule_config_t config; if (scheduler_get_schedule(pump_id, sched, &config) == ESP_OK && config.type != SCHEDULE_TYPE_DISABLED) { char topic_buf[64]; char json[512]; snprintf(topic_buf, sizeof(topic_buf), "plant_watering/schedule/%d/%d/current", pump_id, sched); if (scheduler_schedule_to_json(pump_id, sched, json, sizeof(json)) == ESP_OK) { mqtt_client_publish(topic_buf, json, MQTT_QOS_0, MQTT_NO_RETAIN); } } } } } } else if (parsed == 3 && strcmp(action, "config") == 0) { // Configure schedule if (pump_id >= 1 && pump_id <= 2 && schedule_id >= 0 && schedule_id < SCHEDULER_MAX_SCHEDULES_PER_PUMP) { esp_err_t ret = scheduler_json_to_schedule(data, pump_id, schedule_id); if (ret == ESP_OK) { ESP_LOGI(TAG, "Updated schedule %d for pump %d", schedule_id, pump_id); // Publish confirmation char response_topic[64]; char response[512]; snprintf(response_topic, sizeof(response_topic), "plant_watering/schedule/%d/%d/status", pump_id, schedule_id); scheduler_schedule_to_json(pump_id, schedule_id, response, sizeof(response)); mqtt_client_publish(response_topic, response, MQTT_QOS_0, MQTT_RETAIN); } else { ESP_LOGE(TAG, "Failed to update schedule: %s", esp_err_to_name(ret)); } } } } } else if (strncmp(topic, "plant_watering/settings/pump/", 29) == 0) { // Parse settings commands int pump_id = 0; char setting[32] = {0}; // Extract pump ID and setting name if (sscanf(topic + 29, "%d/%31s", &pump_id, setting) == 2) { if (pump_id >= 1 && pump_id <= 2) { int value = atoi(data); if (strcmp(setting, "max_runtime") == 0 && value > 0) { motor_set_max_runtime(pump_id, value); ESP_LOGI(TAG, "Set pump %d max runtime to %d ms", pump_id, value); } else if (strcmp(setting, "min_interval") == 0 && value > 0) { motor_set_min_interval(pump_id, value); ESP_LOGI(TAG, "Set pump %d min interval to %d ms", pump_id, value); } else if (strcmp(setting, "min_speed") == 0 && value >= 0 && value <= 100) { motor_set_speed_limits(pump_id, value, 100); ESP_LOGI(TAG, "Set pump %d min speed to %d%%", pump_id, value); } else if (strcmp(setting, "max_speed") == 0 && value > 0 && value <= 100) { motor_set_speed_limits(pump_id, MOTOR_MIN_SPEED, value); ESP_LOGI(TAG, "Set pump %d max speed to %d%%", pump_id, value); } } } } } // WiFi event handler static void wifi_event_handler(wifi_state_t state) { switch (state) { case WIFI_STATE_CONNECTED: ESP_LOGI(TAG, "WiFi connected - starting services"); ota_server_start(); // Start MQTT client if (mqtt_client_start() != ESP_OK) { ESP_LOGE(TAG, "Failed to start MQTT client"); } break; case WIFI_STATE_DISCONNECTED: ESP_LOGW(TAG, "WiFi disconnected - stopping services"); mqtt_client_stop(); ota_server_stop(); break; case WIFI_STATE_ERROR: ESP_LOGE(TAG, "WiFi connection failed"); break; default: break; } } // OTA progress handler static void ota_progress_handler(int percent) { ESP_LOGI(TAG, "OTA Progress: %d%%", percent); } // Task to simulate sensor readings and publish stats static void sensor_simulation_task(void *pvParameters) { TickType_t last_stats_publish = 0; while (1) { // Wait for MQTT connection if (mqtt_client_is_connected()) { // Simulate moisture sensor readings with some variation test_moisture_1 += (esp_random() % 5) - 2; // +/- 2 test_moisture_2 += (esp_random() % 5) - 2; // +/- 2 // Keep values in range if (test_moisture_1 < 0) test_moisture_1 = 0; if (test_moisture_1 > 100) test_moisture_1 = 100; if (test_moisture_2 < 0) test_moisture_2 = 0; if (test_moisture_2 > 100) test_moisture_2 = 100; // Publish sensor data mqtt_publish_moisture(1, test_moisture_1); mqtt_publish_moisture(2, test_moisture_2); ESP_LOGI(TAG, "Published moisture: Sensor1=%d%%, Sensor2=%d%%", test_moisture_1, test_moisture_2); // Publish pump runtime stats every minute if (xTaskGetTickCount() - last_stats_publish > pdMS_TO_TICKS(60000)) { last_stats_publish = xTaskGetTickCount(); for (int i = 1; i <= 2; i++) { // Publish current runtime if running if (motor_is_running(i)) { char topic[64]; char data[32]; snprintf(topic, sizeof(topic), "plant_watering/pump/%d/runtime", i); snprintf(data, sizeof(data), "%lu", motor_get_runtime_ms(i)); mqtt_client_publish(topic, data, MQTT_QOS_0, MQTT_NO_RETAIN); } // Publish cooldown status if (motor_is_cooldown(i)) { char topic[64]; snprintf(topic, sizeof(topic), "plant_watering/pump/%d/cooldown", i); mqtt_client_publish(topic, "true", MQTT_QOS_0, MQTT_NO_RETAIN); } } } } // Update every 10 seconds vTaskDelay(10000 / portTICK_PERIOD_MS); } } // Task to publish schedule status periodically static void schedule_status_task(void *pvParameters) { while (1) { vTaskDelay(pdMS_TO_TICKS(60000)); // Every minute if (mqtt_client_is_connected() && scheduler_is_time_synchronized()) { // Publish scheduler status scheduler_status_t status; if (scheduler_get_status(&status) == ESP_OK) { char status_json[256]; snprintf(status_json, sizeof(status_json), "{\"holiday_mode\":%s,\"time_sync\":%s,\"active_schedules\":%lu,\"time\":%lld}", status.holiday_mode ? "true" : "false", status.time_synchronized ? "true" : "false", status.active_schedules, (long long)scheduler_get_current_time()); mqtt_client_publish("plant_watering/schedule/status", status_json, MQTT_QOS_0, MQTT_RETAIN); } // Publish human-readable time periodically time_t now = scheduler_get_current_time(); struct tm timeinfo; localtime_r(&now, &timeinfo); char time_str[64]; strftime(time_str, sizeof(time_str), "%Y-%m-%d %H:%M:%S %Z", &timeinfo); char time_json[256]; snprintf(time_json, sizeof(time_json), "{\"timestamp\":%lld,\"datetime\":\"%s\",\"day_of_week\":%d,\"hour\":%d,\"minute\":%d}", (long long)now, time_str, timeinfo.tm_wday, timeinfo.tm_hour, timeinfo.tm_min); mqtt_client_publish("plant_watering/system/current_time", time_json, MQTT_QOS_0, MQTT_NO_RETAIN); // Publish all active schedules for (int pump = 1; pump <= 2; pump++) { for (int sched = 0; sched < SCHEDULER_MAX_SCHEDULES_PER_PUMP; sched++) { schedule_config_t config; if (scheduler_get_schedule(pump, sched, &config) == ESP_OK && config.enabled && config.type != SCHEDULE_TYPE_DISABLED) { char topic[64]; char json[512]; snprintf(topic, sizeof(topic), "plant_watering/schedule/%d/%d/status", pump, sched); if (scheduler_schedule_to_json(pump, sched, json, sizeof(json)) == ESP_OK) { mqtt_client_publish(topic, json, MQTT_QOS_0, MQTT_RETAIN); } } } } } } } static void print_chip_info(void) { esp_chip_info_t chip_info; esp_chip_info(&chip_info); ESP_LOGI(TAG, "This is %s chip with %d CPU core(s), WiFi%s%s, ", CONFIG_IDF_TARGET, chip_info.cores, (chip_info.features & CHIP_FEATURE_BT) ? "/BT" : "", (chip_info.features & CHIP_FEATURE_BLE) ? "/BLE" : ""); ESP_LOGI(TAG, "silicon revision %d, ", chip_info.revision); ESP_LOGI(TAG, "Minimum free heap size: %d bytes", esp_get_minimum_free_heap_size()); } void app_main(void) { ESP_LOGI(TAG, "Plant Watering System v%s", APP_VERSION); // Print chip information print_chip_info(); // Print configuration ESP_LOGI(TAG, "Configuration:"); ESP_LOGI(TAG, " MQTT Broker: %s", CONFIG_MQTT_BROKER_URL); ESP_LOGI(TAG, " Moisture threshold low: %d%%", CONFIG_MOISTURE_THRESHOLD_LOW); ESP_LOGI(TAG, " Moisture threshold high: %d%%", CONFIG_MOISTURE_THRESHOLD_HIGH); ESP_LOGI(TAG, " Max watering duration: %d ms", CONFIG_WATERING_MAX_DURATION_MS); ESP_LOGI(TAG, " Min watering interval: %d ms", CONFIG_WATERING_MIN_INTERVAL_MS); // Initialize WiFi manager ESP_ERROR_CHECK(wifi_manager_init()); wifi_manager_register_callback(wifi_event_handler); // Initialize OTA server ESP_ERROR_CHECK(ota_server_init()); ota_server_set_version(APP_VERSION); ota_server_register_progress_callback(ota_progress_handler); // Initialize MQTT client ESP_ERROR_CHECK(mqtt_client_init()); mqtt_client_register_callbacks(mqtt_connected_callback, mqtt_disconnected_callback, mqtt_data_callback); // Initialize Motor Control ESP_ERROR_CHECK(motor_control_init()); motor_register_state_callback(motor_state_change_callback); motor_register_error_callback(motor_error_callback); // 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 // Initialize Scheduler ESP_ERROR_CHECK(scheduler_init()); scheduler_register_trigger_callback(scheduler_trigger_callback); // Start WiFi connection esp_err_t ret = wifi_manager_start(); if (ret != ESP_OK) { ESP_LOGE(TAG, "Failed to start WiFi manager"); } // Create sensor simulation task xTaskCreate(sensor_simulation_task, "sensor_sim", 4096, NULL, 5, NULL); // Create schedule status task xTaskCreate(schedule_status_task, "schedule_status", 4096, NULL, 4, NULL); // Main loop - monitor system status while (1) { ESP_LOGI(TAG, "System Status - WiFi: %s, MQTT: %s, Time: %s, Free heap: %d bytes", wifi_manager_is_connected() ? "Connected" : "Disconnected", mqtt_client_is_connected() ? "Connected" : "Disconnected", scheduler_is_time_synchronized() ? "Synced" : "Not synced", esp_get_free_heap_size()); // Print pump states and runtime if (mqtt_client_is_connected()) { for (int i = 1; i <= 2; i++) { motor_stats_t stats; motor_get_stats(i, &stats); const char *state_str = "OFF"; if (motor_is_running(i)) { state_str = "ON"; } else if (motor_is_cooldown(i)) { state_str = "COOLDOWN"; } ESP_LOGI(TAG, "Pump %d: %s, Total runtime: %lu s, Runs: %lu", i, state_str, stats.total_runtime_ms / 1000, stats.run_count); } // Print scheduler status if (scheduler_is_time_synchronized()) { scheduler_status_t sched_status; if (scheduler_get_status(&sched_status) == ESP_OK) { time_t now = scheduler_get_current_time(); struct tm timeinfo; localtime_r(&now, &timeinfo); char datetime_str[32]; strftime(datetime_str, sizeof(datetime_str), "%Y-%m-%d %H:%M:%S", &timeinfo); ESP_LOGI(TAG, "Scheduler: %d active, Holiday: %s, DateTime: %s", sched_status.active_schedules, sched_status.holiday_mode ? "ON" : "OFF", datetime_str); } } } vTaskDelay(30000 / portTICK_PERIOD_MS); // Every 30 seconds } }