Files
PlantWater/main/main.c

455 lines
18 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 "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.1.0-motor"
// Test data
static int test_moisture_1 = 45;
static int test_moisture_2 = 62;
// 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);
}
}
// 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/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 (strncmp(topic, "plant_watering/settings/pump/", 29) == 0) {
// Parse settings commands like:
// plant_watering/settings/pump/1/max_runtime
// plant_watering/settings/pump/1/min_interval
// plant_watering/settings/pump/1/min_speed
// plant_watering/settings/pump/1/max_speed
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) {
// Get current max speed to validate
motor_stats_t stats;
motor_get_stats(pump_id, &stats);
motor_set_speed_limits(pump_id, value, 100); // Assuming max stays at 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 demonstrate automated watering based on moisture
static void automation_demo_task(void *pvParameters)
{
bool auto_mode = false; // Start with manual mode
while (1) {
if (auto_mode && mqtt_client_is_connected()) {
// Simple threshold-based automation demo
if (test_moisture_1 < CONFIG_MOISTURE_THRESHOLD_LOW) {
if (!motor_is_running(MOTOR_PUMP_1) && !motor_is_cooldown(MOTOR_PUMP_1)) {
ESP_LOGI(TAG, "Auto: Moisture 1 low (%d%%), starting pump 1", test_moisture_1);
motor_start_timed(MOTOR_PUMP_1, MOTOR_DEFAULT_SPEED, 10000); // 10 second watering
}
}
if (test_moisture_2 < CONFIG_MOISTURE_THRESHOLD_LOW) {
if (!motor_is_running(MOTOR_PUMP_2) && !motor_is_cooldown(MOTOR_PUMP_2)) {
ESP_LOGI(TAG, "Auto: Moisture 2 low (%d%%), starting pump 2", test_moisture_2);
motor_start_timed(MOTOR_PUMP_2, MOTOR_DEFAULT_SPEED, 10000); // 10 second watering
}
}
}
vTaskDelay(30000 / portTICK_PERIOD_MS); // Check every 30 seconds
}
}
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, " 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
// 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 automation demo task (disabled by default)
xTaskCreate(automation_demo_task, "automation", 4096, NULL, 4, NULL);
// Main loop - monitor system status
while (1) {
ESP_LOGI(TAG, "System Status - WiFi: %s, MQTT: %s, Free heap: %d bytes",
wifi_manager_is_connected() ? "Connected" : "Disconnected",
mqtt_client_is_connected() ? "Connected" : "Disconnected",
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);
}
}
vTaskDelay(30000 / portTICK_PERIOD_MS); // Every 30 seconds
}
}