- 将显示驱动从 lvgl_st7735s_use 改为 lvgl_st7789_use,在 CMakeLists.txt 和 main.c 中。 - 将 MQTT 警报主题从“topic/alert/esp32_iothome_001”更新为“topic/alert/esp32_iothome_002”。 - 重构 UI 任务,去除周期性屏幕切换逻辑,专注于界面刷新。 - 调整错误处理,使新的显示驱动程序用于错误信息。
652 lines
21 KiB
C
Executable File
652 lines
21 KiB
C
Executable File
#include <stdio.h>
|
||
#include <inttypes.h>
|
||
#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_st7789_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 控制接口
|
||
#include "status_web.h"
|
||
|
||
// 配置宏定义:BH1750 光照传感器是否启用(默认禁用)
|
||
#ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE
|
||
#define CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE 0
|
||
#endif
|
||
|
||
// 配置宏定义:AHT30 温湿度传感器是否启用(默认禁用)
|
||
#ifndef CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE
|
||
#define CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE 0
|
||
#endif
|
||
|
||
// 配置宏定义:BH1750 读取周期(毫秒,默认500ms)
|
||
#ifndef CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS
|
||
#define CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS 500
|
||
#endif
|
||
|
||
// 配置宏定义:AHT30 读取周期(毫秒,默认2000ms)
|
||
#ifndef CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS
|
||
#define CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS 2000
|
||
#endif
|
||
|
||
// 配置宏定义:I2C 是否启用内部上拉电阻(默认启用)
|
||
#ifndef CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP
|
||
#define CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP 1
|
||
#endif
|
||
|
||
// I2C 端口配置
|
||
#define BOTANY_I2C_PORT I2C_NUM_0
|
||
// I2C SCL 引脚
|
||
#define BOTANY_I2C_SCL_GPIO GPIO_NUM_5
|
||
// I2C SDA 引脚
|
||
#define BOTANY_I2C_SDA_GPIO GPIO_NUM_4
|
||
// BH1750 使能标志
|
||
#define BOTANY_BH1750_ENABLE CONFIG_I2C_MASTER_MESSAGER_BH1750_ENABLE
|
||
// AHT30 使能标志
|
||
#define BOTANY_AHT30_ENABLE CONFIG_I2C_MASTER_MESSAGER_AHT30_ENABLE
|
||
// BH1750 读取周期
|
||
#define BOTANY_BH1750_PERIOD_MS CONFIG_I2C_MASTER_MESSAGER_BH1750_READ_PERIOD_MS
|
||
// AHT30 读取周期
|
||
#define BOTANY_AHT30_PERIOD_MS CONFIG_I2C_MASTER_MESSAGER_AHT30_READ_PERIOD_MS
|
||
// I2C 内部上拉使能
|
||
#define BOTANY_I2C_INTERNAL_PULLUP CONFIG_I2C_MASTER_MESSAGER_ENABLE_INTERNAL_PULLUP
|
||
// MQTT 告警主题
|
||
#define BOTANY_MQTT_ALERT_TOPIC "topic/alert/esp32_iothome_002"
|
||
// MQTT 遥测数据上报周期(毫秒)
|
||
#define BOTANY_MQTT_TELEMETRY_PERIOD_MS 5000
|
||
#define BOTANY_STATUS_WEB_PORT 8080
|
||
|
||
// 日志标签
|
||
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];
|
||
// 全局变量:水泵状态(true=开启,false=关闭)
|
||
static bool s_pump_on = false;
|
||
// 全局变量:补光灯状态(true=开启,false=关闭)
|
||
static bool s_light_on = false;
|
||
// 全局变量:自动控制模式使能(true=自动,false=手动)
|
||
static bool s_auto_control_enabled = true;
|
||
static bool s_i2c_ready = false;
|
||
static bool s_soil_sensor_ready = false;
|
||
static uint32_t s_main_loop_counter = 0;
|
||
|
||
/**
|
||
* @brief 发布当前完整状态快照(含阈值)到传感器主题
|
||
*/
|
||
static esp_err_t publish_telemetry_snapshot(void)
|
||
{
|
||
if (!mqtt_control_is_connected())
|
||
{
|
||
return ESP_ERR_INVALID_STATE;
|
||
}
|
||
|
||
auto_ctrl_thresholds_t thresholds = {0};
|
||
auto_ctrl_thresholds_get(&thresholds);
|
||
|
||
char telemetry_payload[256] = {0};
|
||
int len = snprintf(telemetry_payload,
|
||
sizeof(telemetry_payload),
|
||
"{\"temp\":\"%s\",\"hum\":\"%s\",\"soil\":\"%s\",\"lux\":\"%s\",\"pump\":\"%s\",\"light\":\"%s\",\"mode\":\"%s\",\"soil_on\":%.1f,\"soil_off\":%.1f,\"light_on\":%.1f,\"light_off\":%.1f}",
|
||
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",
|
||
thresholds.pump_on_soil_below_pct,
|
||
thresholds.pump_off_soil_above_pct,
|
||
thresholds.light_on_lux_below,
|
||
thresholds.light_off_lux_above);
|
||
if (len <= 0 || len >= (int)sizeof(telemetry_payload))
|
||
{
|
||
return ESP_ERR_INVALID_SIZE;
|
||
}
|
||
|
||
return mqtt_control_publish_sensor(telemetry_payload, 0, 0);
|
||
}
|
||
|
||
static void update_status_web_snapshot(void)
|
||
{
|
||
status_web_snapshot_t snap = {0};
|
||
snprintf(snap.temp, sizeof(snap.temp), "%s", s_air_temp[0] ? s_air_temp : "--");
|
||
snprintf(snap.hum, sizeof(snap.hum), "%s", s_air_hum[0] ? s_air_hum : "--");
|
||
snprintf(snap.soil, sizeof(snap.soil), "%s", s_soil[0] ? s_soil : "--");
|
||
snprintf(snap.lux, sizeof(snap.lux), "%s", s_lux[0] ? s_lux : "--");
|
||
snap.pump_on = s_pump_on;
|
||
snap.light_on = s_light_on;
|
||
snap.auto_mode = s_auto_control_enabled;
|
||
|
||
auto_ctrl_thresholds_t thresholds = {0};
|
||
auto_ctrl_thresholds_get(&thresholds);
|
||
snap.soil_on_threshold = thresholds.pump_on_soil_below_pct;
|
||
snap.soil_off_threshold = thresholds.pump_off_soil_above_pct;
|
||
snap.light_on_threshold = thresholds.light_on_lux_below;
|
||
snap.light_off_threshold = thresholds.light_off_lux_above;
|
||
snap.i2c_ready = s_i2c_ready;
|
||
snap.soil_sensor_ready = s_soil_sensor_ready;
|
||
snap.loop_counter = s_main_loop_counter;
|
||
|
||
esp_err_t ret = status_web_update(&snap);
|
||
if (ret != ESP_OK && ret != ESP_ERR_INVALID_STATE)
|
||
{
|
||
ESP_LOGW(TAG, "status web update failed: %s", esp_err_to_name(ret));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief MQTT 控制命令处理函数
|
||
*
|
||
* 处理来自 MQTT 的控制命令,包括模式切换、阈值更新、水泵和补光灯控制。
|
||
*
|
||
* @param cmd 指向 MQTT 控制命令结构体的指针
|
||
* @param user_ctx 用户上下文(未使用)
|
||
* @return esp_err_t 处理结果
|
||
*/
|
||
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");
|
||
esp_err_t final_ret = ESP_OK;
|
||
bool has_any_control = cmd->has_mode || cmd->has_thresholds || cmd->has_pump || cmd->has_light;
|
||
|
||
// 处理模式切换命令
|
||
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_err_t ret = auto_ctrl_thresholds_set_values(cmd->soil_on_pct,
|
||
cmd->soil_off_pct,
|
||
cmd->light_on_lux,
|
||
cmd->light_off_lux);
|
||
if (ret == ESP_OK)
|
||
{
|
||
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);
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGE(TAG, "设置阈值失败: %s", esp_err_to_name(ret));
|
||
final_ret = ret;
|
||
}
|
||
}
|
||
|
||
// 处理水泵控制命令
|
||
if (cmd->has_pump)
|
||
{
|
||
esp_err_t ret = io_device_control_set_pump(cmd->pump_on);
|
||
if (ret == ESP_OK)
|
||
{
|
||
s_pump_on = cmd->pump_on;
|
||
ESP_LOGI(TAG, "MQTT 控制水泵: %s", cmd->pump_on ? "on" : "off");
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGE(TAG, "MQTT 控制水泵失败: %s", esp_err_to_name(ret));
|
||
final_ret = ret;
|
||
}
|
||
}
|
||
|
||
// 处理补光灯控制命令
|
||
if (cmd->has_light)
|
||
{
|
||
esp_err_t ret = io_device_control_set_light(cmd->light_on);
|
||
if (ret == ESP_OK)
|
||
{
|
||
s_light_on = cmd->light_on;
|
||
ESP_LOGI(TAG, "MQTT 控制补光灯: %s", cmd->light_on ? "on" : "off");
|
||
}
|
||
else
|
||
{
|
||
ESP_LOGE(TAG, "MQTT 控制补光灯失败: %s", esp_err_to_name(ret));
|
||
final_ret = ret;
|
||
}
|
||
}
|
||
|
||
// 任何控制指令处理后都立即上报最新状态(含阈值)作为回复。
|
||
if (has_any_control)
|
||
{
|
||
esp_err_t pub_ret = publish_telemetry_snapshot();
|
||
if (pub_ret != ESP_OK)
|
||
{
|
||
ESP_LOGW(TAG, "控制后立即上报失败: %s", esp_err_to_name(pub_ret));
|
||
if (final_ret == ESP_OK)
|
||
{
|
||
final_ret = pub_ret;
|
||
}
|
||
}
|
||
}
|
||
|
||
return final_ret;
|
||
}
|
||
|
||
/**
|
||
* @brief 将告警指标类型转换为字符串
|
||
*
|
||
* @param metric 告警指标类型
|
||
* @return const char* 对应的字符串表示
|
||
*/
|
||
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";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 将告警状态转换为字符串
|
||
*
|
||
* @param state 告警状态
|
||
* @return const char* 对应的字符串表示
|
||
*/
|
||
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";
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 自动告警 MQTT 回调函数
|
||
*
|
||
* 当自动告警模块触发事件时,通过此函数将告警信息以 JSON 格式发布到 MQTT。
|
||
*
|
||
* @param event 指向告警事件结构体的指针
|
||
* @param user_ctx 用户上下文(未使用)
|
||
*/
|
||
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));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 自动控制逻辑更新函数
|
||
*
|
||
* 根据当前传感器数据和阈值,决定是否需要开启或关闭水泵和补光灯。
|
||
*
|
||
* @param soil_valid 土壤湿度数据是否有效
|
||
* @param soil_moisture_pct 当前土壤湿度百分比
|
||
* @param light_valid 光照数据是否有效
|
||
* @param light_lux 当前光照强度(lux)
|
||
* @param thresholds 指向阈值配置结构体的指针
|
||
* @param pump_on 指向当前水泵状态的指针(输入/输出)
|
||
* @param light_on 指向当前补光灯状态的指针(输入/输出)
|
||
*/
|
||
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));
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief UI 任务函数
|
||
*
|
||
* 负责定期驱动 UI 刷新。
|
||
*
|
||
* @param arg 任务参数(未使用)
|
||
*/
|
||
static void ui_task(void *arg)
|
||
{
|
||
(void)arg;
|
||
|
||
for (;;)
|
||
{
|
||
lvgl_port_lock(0);
|
||
ui_tick();
|
||
|
||
lvgl_port_unlock();
|
||
vTaskDelay(pdMS_TO_TICKS(20));
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief 等待 Wi-Fi 连接成功
|
||
*
|
||
* 在初始化 console 之前,确保 Wi-Fi 已连接成功,最多等待120秒。
|
||
*/
|
||
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");
|
||
}
|
||
|
||
/**
|
||
* @brief 主函数
|
||
*
|
||
* 系统启动入口,初始化所有组件并进入主循环。
|
||
*/
|
||
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_st7789_set_center_text("I2C init failed"));
|
||
}
|
||
else
|
||
{
|
||
i2c_ready = true;
|
||
s_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;
|
||
s_soil_sensor_ready = true;
|
||
}
|
||
|
||
// 按需求:仅在 Wi-Fi 确认连通后再初始化 MQTT和console。
|
||
wait_for_wifi_connected();
|
||
|
||
// 独立状态网页(端口 8080),与配网页面(端口 80)互不干扰。
|
||
ESP_ERROR_CHECK(status_web_start(BOTANY_STATUS_WEB_PORT));
|
||
|
||
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 (;;)
|
||
{
|
||
s_main_loop_counter++;
|
||
|
||
// 预留给 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);
|
||
|
||
update_status_web_snapshot();
|
||
|
||
telemetry_elapsed_ms += 1000;
|
||
if (telemetry_elapsed_ms >= BOTANY_MQTT_TELEMETRY_PERIOD_MS)
|
||
{
|
||
telemetry_elapsed_ms = 0;
|
||
esp_err_t pub_ret = publish_telemetry_snapshot();
|
||
if (pub_ret != ESP_OK && pub_ret != ESP_ERR_INVALID_STATE)
|
||
{
|
||
ESP_LOGW(TAG, "周期状态上报失败: %s", esp_err_to_name(pub_ret));
|
||
}
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
}
|
||
} |