#include #include #include "sdkconfig.h" #include "freertos/FreeRTOS.h" #include "freertos/task.h" #include "esp_check.h" #include "esp_log.h" #include "wifi-connect.h" #include "lvgl_st7735s_use.h" #include "i2c_master_messager.h" #include "io_device_control.h" #include "console_simple_init.h" // 提供 console_cmd_user_register 和 console_cmd_all_register #include "console_user_cmds.h" #include "capactive_soil_moisture_sensor_V2.0.h" #include "ui.h" // 使用EEZStudio提供的ui组件,便于后续扩展 #include "esp_lvgl_port.h" #include "vars.h" // 定义全局变量接口 #include "auto_ctrl_thresholds.h" #include "auto_alerts.h" #include "mqtt_control.h" // MQTT 控制接口 #ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE #define CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE 0 #endif #ifndef CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE #define CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE 0 #endif #ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS #define CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS 500 #endif #ifndef CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS #define CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS 2000 #endif #ifndef CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP #define CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP 1 #endif #define BOTANY_I2C_PORT I2C_NUM_0 #define BOTANY_I2C_SCL_GPIO GPIO_NUM_5 #define BOTANY_I2C_SDA_GPIO GPIO_NUM_4 #define BOTANY_BH1750_ENABLE CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE #define BOTANY_AHT30_ENABLE CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE #define BOTANY_BH1750_PERIOD_MS CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS #define BOTANY_AHT30_PERIOD_MS CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS #define BOTANY_I2C_INTERNAL_PULLUP CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP #define BOTANY_MQTT_ALERT_TOPIC "topic/alert/esp32_iothome_001" #define BOTANY_MQTT_TELEMETRY_PERIOD_MS 5000 static const char *TAG = "main"; static char s_air_temp[16]; static char s_air_hum[16]; static char s_soil[16]; static char s_lux[16]; static bool s_pump_on = false; static bool s_light_on = false; static bool s_auto_control_enabled = true; static esp_err_t mqtt_control_command_handler(const mqtt_control_command_t *cmd, void *user_ctx) { (void)user_ctx; ESP_RETURN_ON_FALSE(cmd != NULL, ESP_ERR_INVALID_ARG, TAG, "cmd is null"); if (cmd->has_mode) { s_auto_control_enabled = cmd->auto_mode; ESP_LOGI(TAG, "MQTT 控制模式切换: %s", s_auto_control_enabled ? "auto" : "manual"); } if (cmd->has_thresholds) { ESP_RETURN_ON_ERROR(auto_ctrl_thresholds_set_values(cmd->soil_on_pct, cmd->soil_off_pct, cmd->light_on_lux, cmd->light_off_lux), TAG, "设置阈值失败"); ESP_LOGI(TAG, "MQTT 更新阈值: soil_on=%.1f soil_off=%.1f light_on=%.1f light_off=%.1f", cmd->soil_on_pct, cmd->soil_off_pct, cmd->light_on_lux, cmd->light_off_lux); } if (cmd->has_pump) { ESP_RETURN_ON_ERROR(io_device_control_set_pump(cmd->pump_on), TAG, "MQTT 控制水泵失败"); s_pump_on = cmd->pump_on; ESP_LOGI(TAG, "MQTT 控制水泵: %s", cmd->pump_on ? "on" : "off"); } if (cmd->has_light) { ESP_RETURN_ON_ERROR(io_device_control_set_light(cmd->light_on), TAG, "MQTT 控制补光灯失败"); s_light_on = cmd->light_on; ESP_LOGI(TAG, "MQTT 控制补光灯: %s", cmd->light_on ? "on" : "off"); } return ESP_OK; } static const char *alert_metric_text(auto_alert_metric_t metric) { switch (metric) { case AUTO_ALERT_METRIC_SOIL_MOISTURE: return "soil"; case AUTO_ALERT_METRIC_LIGHT_INTENSITY: return "light"; default: return "unknown"; } } static const char *alert_state_text(auto_alert_state_t state) { switch (state) { case AUTO_ALERT_STATE_NORMAL: return "normal"; case AUTO_ALERT_STATE_ALARM: return "alarm"; default: return "unknown"; } } static void auto_alert_mqtt_callback(const auto_alert_event_t *event, void *user_ctx) { (void)user_ctx; if (event == NULL) { return; } // 使用明文发送报警简单的 JSON 字符串,格式示例:{"metric":"soil","state":"alarm"} char payload[64] = {0}; int len = snprintf(payload, sizeof(payload), "{\"metric\":\"%s\",\"state\":\"%s\"}", alert_metric_text(event->metric), alert_state_text(event->state)); if (len <= 0 || len >= (int)sizeof(payload)) { return; } if (!mqtt_control_is_connected()) { return; } esp_err_t ret = mqtt_control_publish(BOTANY_MQTT_ALERT_TOPIC, payload, 1, 0); if (ret != ESP_OK) { ESP_LOGE(TAG, "告警 MQTT 发布失败: %s", esp_err_to_name(ret)); } } static void auto_control_update(bool soil_valid, float soil_moisture_pct, bool light_valid, float light_lux, const auto_ctrl_thresholds_t *thresholds, bool *pump_on, bool *light_on) { bool desired_pump = *pump_on; bool desired_light = *light_on; if (soil_valid) { if (!desired_pump && soil_moisture_pct < thresholds->pump_on_soil_below_pct) { desired_pump = true; } else if (desired_pump && soil_moisture_pct > thresholds->pump_off_soil_above_pct) { desired_pump = false; } } if (light_valid) { if (!desired_light && light_lux < thresholds->light_on_lux_below) { desired_light = true; } else if (desired_light && light_lux > thresholds->light_off_lux_above) { desired_light = false; } } if (desired_pump != *pump_on) { esp_err_t ret = io_device_control_set_pump(desired_pump); if (ret == ESP_OK) { *pump_on = desired_pump; ESP_LOGI(TAG, "自动控制: 水泵%s (土壤湿度=%.1f%%)", desired_pump ? "开启" : "关闭", soil_moisture_pct); } else { ESP_LOGE(TAG, "自动控制: 水泵控制失败: %s", esp_err_to_name(ret)); } } if (desired_light != *light_on) { esp_err_t ret = io_device_control_set_light(desired_light); if (ret == ESP_OK) { *light_on = desired_light; ESP_LOGI(TAG, "自动控制: 补光灯%s (光照=%.1f lux)", desired_light ? "开启" : "关闭", light_lux); } else { ESP_LOGE(TAG, "自动控制: 补光灯控制失败: %s", esp_err_to_name(ret)); } } } static void ui_task(void *arg) { (void)arg; uint32_t elapsed_ms = 0; enum ScreensEnum current = SCREEN_ID_TEMPERATURE; const uint32_t switch_period_ms = 3000; // 每3秒切一次 for (;;) { lvgl_port_lock(0); ui_tick(); elapsed_ms += 20; if (elapsed_ms >= switch_period_ms) { elapsed_ms = 0; // 下一个页面:1->2->3->4->1 if (current >= _SCREEN_ID_LAST) { current = _SCREEN_ID_FIRST; } else { current = (enum ScreensEnum)(current + 1); } loadScreen(current); } lvgl_port_unlock(); vTaskDelay(pdMS_TO_TICKS(20)); } } static void wait_for_wifi_connected(void) { const uint32_t timeout_s = 120; uint32_t elapsed_half_s = 0; ESP_LOGI(TAG, "等待 Wi-Fi 连接成功后再初始化 console..."); while (wifi_connect_get_status() != WIFI_CONNECT_STATUS_CONNECTED) { if (elapsed_half_s >= (timeout_s * 2)) { ESP_LOGW(TAG, "等待 Wi-Fi 超时(%" PRIu32 "s),继续初始化 console", timeout_s); return; } vTaskDelay(pdMS_TO_TICKS(500)); elapsed_half_s++; // 每 5 秒打印一次等待状态,避免日志刷屏。 if ((elapsed_half_s % 10) == 0) { ESP_LOGI(TAG, "仍在等待 Wi-Fi 连接(%" PRIu32 "s)", elapsed_half_s / 2); } } ESP_LOGI(TAG, "Wi-Fi 已连接,开始初始化 console"); } void app_main(void) { // 初始化 Wi-Fi 配网组件,支持长按按键进入配网 ESP_ERROR_CHECK(wifi_connect_init()); printf("设备启动完成:长按按键进入配网模式,手机连接 ESP32-* 后访问 http://192.168.4.1\n"); // 启动 LVGL 演示程序,显示简单的界面 ESP_ERROR_CHECK(start_lvgl_demo()); // 初始化 UI 组件(需在 LVGL 锁内进行对象创建) lvgl_port_lock(0); ui_init(); lvgl_port_unlock(); BaseType_t ui_task_ok = xTaskCreate(ui_task, "ui_task", 4096, NULL, 5, NULL); ESP_ERROR_CHECK(ui_task_ok == pdPASS ? ESP_OK : ESP_FAIL); // 初始化 IO 设备控制组件(GPIO1 水泵,GPIO10 光照,高电平有效) ESP_ERROR_CHECK(io_device_control_init()); i2c_master_messager_config_t i2c_cfg = { .i2c_port = BOTANY_I2C_PORT, .scl_io_num = BOTANY_I2C_SCL_GPIO, .sda_io_num = BOTANY_I2C_SDA_GPIO, .i2c_enable_internal_pullup = BOTANY_I2C_INTERNAL_PULLUP, .bh1750_enable = BOTANY_BH1750_ENABLE, .aht30_enable = BOTANY_AHT30_ENABLE, .bh1750_read_period_ms = BOTANY_BH1750_PERIOD_MS, .aht30_read_period_ms = BOTANY_AHT30_PERIOD_MS, .bh1750_mode = BH1750_CONTINUE_1LX_RES, }; bool i2c_ready = false; esp_err_t ret = i2c_master_messager_init(&i2c_cfg); if (ret == ESP_OK) { ret = i2c_master_messager_start(); } if (ret != ESP_OK) { ESP_LOGE(TAG, "I2C 传感器管理启动失败: %s", esp_err_to_name(ret)); ESP_LOGW(TAG, "请检查 I2C 引脚/上拉电阻/端口占用情况,系统将继续运行但不采集传感器"); ESP_ERROR_CHECK(lvgl_st7735s_set_center_text("I2C init failed")); } else { i2c_ready = true; } // 初始化电容式土壤湿度传感器(GPIO0 / ADC1_CH0)。 bool soil_ready = false; cap_soil_sensor_config_t soil_cfg = { .unit = CAP_SOIL_SENSOR_DEFAULT_UNIT, .channel = CAP_SOIL_SENSOR_DEFAULT_CHANNEL, .atten = ADC_ATTEN_DB_12, .bitwidth = ADC_BITWIDTH_DEFAULT, // 标定值来自当前实测:空气中约 3824,水中约 1463。 .air_raw = 3824, .water_raw = 1463, }; ret = cap_soil_sensor_init(&soil_cfg); if (ret != ESP_OK) { ESP_LOGE(TAG, "土壤湿度传感器初始化失败: %s", esp_err_to_name(ret)); } else { soil_ready = true; } // 按需求:仅在 Wi-Fi 确认连通后再初始化 MQTT和console。 wait_for_wifi_connected(); ESP_ERROR_CHECK(mqtt_control_register_command_handler(mqtt_control_command_handler, NULL)); ESP_ERROR_CHECK(mqtt_control_start()); // 启动 MQTT 客户端 ESP_ERROR_CHECK(console_cmd_init()); ESP_ERROR_CHECK(console_user_cmds_register()); ESP_ERROR_CHECK(console_cmd_all_register()); // 可选:自动注册插件命令 ESP_ERROR_CHECK(console_cmd_start()); auto_ctrl_thresholds_init_defaults(); auto_alerts_init(); ESP_ERROR_CHECK(auto_alerts_register_callback(auto_alert_mqtt_callback, NULL)); auto_ctrl_thresholds_t thresholds = {0}; auto_ctrl_thresholds_get(&thresholds); uint32_t telemetry_elapsed_ms = 0; ESP_LOGI(TAG, "自动控制阈值: pump_on<%.1f%%, pump_off>%.1f%%, light_on<%.1flux, light_off>%.1flux", thresholds.pump_on_soil_below_pct, thresholds.pump_off_soil_above_pct, thresholds.light_on_lux_below, thresholds.light_off_lux_above); for (;;) { // 预留给 MQTT 回调动态更新阈值:每个周期读取最新配置。 auto_ctrl_thresholds_get(&thresholds); bool soil_valid = false; float soil_moisture_pct = 0.0f; cap_soil_sensor_data_t soil_data = {0}; if (soil_ready && cap_soil_sensor_read(&soil_data) == ESP_OK) { // 读取成功 soil_valid = true; soil_moisture_pct = soil_data.moisture_percent; snprintf(s_soil, sizeof(s_soil), "%.0f", soil_data.moisture_percent); set_var_soil_moisture(s_soil); } bool light_valid = false; float light_lux = 0.0f; i2c_master_messager_data_t sensor_data = {0}; if (i2c_ready && i2c_master_messager_get_data(&sensor_data) == ESP_OK) { // 读取成功 if (sensor_data.aht30.valid) { snprintf(s_air_temp, sizeof(s_air_temp), "%.1f", sensor_data.aht30.temperature_c); set_var_air_temperature(s_air_temp); snprintf(s_air_hum, sizeof(s_air_hum), "%.1f", sensor_data.aht30.humidity_rh); set_var_air_humidity(s_air_hum); } if (sensor_data.bh1750.valid) { light_valid = true; light_lux = sensor_data.bh1750.lux; snprintf(s_lux, sizeof(s_lux), "%.0f", sensor_data.bh1750.lux); set_var_light_intensity(s_lux); } } if (s_auto_control_enabled) { auto_control_update(soil_valid, soil_moisture_pct, light_valid, light_lux, &thresholds, &s_pump_on, &s_light_on); } // 预留给 MQTT:回调注册后可在此处收到边沿告警事件并发布。 auto_alerts_evaluate(soil_valid, soil_moisture_pct, light_valid, light_lux, &thresholds); telemetry_elapsed_ms += 1000; if (telemetry_elapsed_ms >= BOTANY_MQTT_TELEMETRY_PERIOD_MS) { telemetry_elapsed_ms = 0; if (mqtt_control_is_connected()) { char telemetry_payload[128] = {0}; int len = snprintf(telemetry_payload, sizeof(telemetry_payload), "{\"temp\":\"%s\",\"hum\":\"%s\",\"soil\":\"%s\",\"lux\":\"%s\",\"pump\":\"%s\",\"light\":\"%s\",\"mode\":\"%s\"}", s_air_temp, s_air_hum, s_soil, s_lux, s_pump_on ? "on" : "off", s_light_on ? "on" : "off", s_auto_control_enabled ? "auto" : "manual"); if (len > 0 && len < (int)sizeof(telemetry_payload)) { esp_err_t pub_ret = mqtt_control_publish_sensor(telemetry_payload, 0, 0); if (pub_ret != ESP_OK) { ESP_LOGW(TAG, "传感器上报失败: %s", esp_err_to_name(pub_ret)); } } } } vTaskDelay(pdMS_TO_TICKS(1000)); } }