feat:新增MQTT控制组件和自动告警系统

- 实现MQTT控制功能,处理水泵和灯光控制指令
- 新增土壤湿度和光照强度自动告警系统,阈值可配置
- 新建MQTT控制、自动告警和阈值管理相关文件
- 更新主应用,集成MQTT和自动控制功能
- 新增传感器数据与控制状态遥测上报
- 引入NVS和应用存储分区配置
This commit is contained in:
Wang Beihong
2026-03-07 02:43:30 +08:00
parent cf3634bebb
commit 5980e171c4
13 changed files with 1279 additions and 77 deletions

View File

@@ -15,6 +15,11 @@
#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
@@ -44,6 +49,8 @@
#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";
@@ -51,6 +58,181 @@ 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)
{
@@ -183,24 +365,52 @@ void app_main(void)
soil_ready = true;
}
// 按需求:仅在 Wi-Fi 确认连通后再初始化 console。
// 按需求:仅在 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)
{
@@ -215,11 +425,59 @@ void app_main(void)
}
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));
}
}