684 lines
29 KiB
C
684 lines
29 KiB
C
#include <stdio.h>
|
|
#include <string.h>
|
|
#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
|
|
}
|
|
} |