Files
Smart-granary-code/main/main.cpp

821 lines
28 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 文件: main/main.cpp
* 角色: 系统主流程与任务调度入口
* 说明:
* - 本文件用于实现当前模块的核心功能或接口定义。
* - 修改前请先确认该模块与其它任务/外设之间的数据流关系。
* - 涉及协议与硬件时,优先保持现有接口兼容,避免联调回归。
*/
#include <stdio.h>
#include <time.h>
#include <string.h>
#include <math.h>
#include "wifi-connect.h"
#include "esp_lvgl_port.h"
#include "lvgl_st7789_use.h"
#include "ui.h"
#include "vars.h"
#include "relay_ctrl.h"
#include "esp_err.h"
#include "esp_log.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/semphr.h"
#include "esp_task_wdt.h"
#include "sntp_time.h"
#include "esp_mac.h"
#include "esp_system.h"
#include "esp_efuse.h"
#include "esp_efuse_table.h"
#include "esp_timer.h"
#include "sntp_time.h"
#include "bh1750_use.h"
#include "aht30.h"
#include "MQ-2.h"
#include "JW01.h"
#include "human_door.h"
#include "fire_sensor.h"
#include "hx711.hpp"
#include "su-03t.h"
#include "agri_env.h"
#include "cJSON.h"
#define TAG "MAIN"
#define CO2_SPOILAGE_THRESHOLD_PPM 1500.0f
#define FIRE_DANGER_THRESHOLD_PERCENT 35.0f
static const gpio_num_t kClockPin = GPIO_NUM_13;
static const gpio_num_t kDataPin = GPIO_NUM_14;
#define HX711_TARE_SAMPLES 24
#define HX711_READ_TIMEOUT_MS 350
#define HX711_REFRESH_MS 120
#define HX711_COUNTS_PER_GRAM 430.0f
#define HX711_ZERO_DEADBAND_G 2.0f
#define HX711_FILTER_ALPHA 0.15f
#define HX711_STABLE_BAND_G 0.30f
#define HX711_UNLOCK_DELTA_G 2.00f
#define HX711_UPDATE_MIN_STEP_G 0.05f
#define HX711_STABLE_SAMPLES 15
#define TWDT_NORMAL_TIMEOUT_MS 5000
#define TWDT_UI_INIT_TIMEOUT_MS 120000
typedef struct
{
char time_str[32];
float lux;
float temp;
float humidity;
float gas_percent;
float tvoc;
float hcho;
float co2;
float ice_weight;
float fire_percent;
bool fire_danger;
bool human_present;
bool door_closed;
bool fan_on;
bool light_on;
bool cool_on;
bool hot_on;
uint8_t su03t_last_msgno;
uint32_t su03t_rx_count;
// ======== 新增:阈值与模式配置 ========
bool auto_mode; // 自动/手动模式true=自动false=手动
float th_temp_h; // 制冷阈值(温度高于此值开启制冷)
float th_temp_l; // 制热阈值(温度低于此值开启制热)
float th_hum_h; // 湿度排风阈值
float th_gas_h; // 烟雾/有害气体排风阈值
} env_data_t;
static env_data_t s_env_data;
static SemaphoreHandle_t s_env_data_lock = NULL;
static volatile bool s_ui_ready = false;
/* 函数: app_mqtt_cmd_handler
* 作用: 解析并处理远端下发的 MQTT 配置项和手动控制指令
*/
static void app_mqtt_cmd_handler(const char *topic, const char *payload, int len)
{
// 如果不是下发的命令主题则忽略(可根据需要支持通配符判断,这里简单处理)
// 将 payload 拷贝并追加 \0 变为合法字符串
char *json_str = (char*)malloc(len + 1);
if (!json_str) return;
memcpy(json_str, payload, len);
json_str[len] = '\0';
cJSON *root = cJSON_Parse(json_str);
if (root)
{
bool auto_mode = false; // 默认手动
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
// 1. 解析模式
cJSON *item = cJSON_GetObjectItem(root, "mode");
if (item && cJSON_IsString(item)) {
if (strcmp(item->valuestring, "auto") == 0) s_env_data.auto_mode = true;
else if (strcmp(item->valuestring, "manual") == 0) s_env_data.auto_mode = false;
}
// 2. 解析阈值配置
item = cJSON_GetObjectItem(root, "th_temp_h");
if (item && cJSON_IsNumber(item)) s_env_data.th_temp_h = item->valuedouble;
item = cJSON_GetObjectItem(root, "th_temp_l");
if (item && cJSON_IsNumber(item)) s_env_data.th_temp_l = item->valuedouble;
item = cJSON_GetObjectItem(root, "th_hum_h");
if (item && cJSON_IsNumber(item)) s_env_data.th_hum_h = item->valuedouble;
item = cJSON_GetObjectItem(root, "th_gas_h");
if (item && cJSON_IsNumber(item)) s_env_data.th_gas_h = item->valuedouble;
auto_mode = s_env_data.auto_mode;
xSemaphoreGive(s_env_data_lock);
}
// 3. 在手动模式下响应远程控制
if (!auto_mode)
{
cJSON *item = cJSON_GetObjectItem(root, "fan");
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_1, cJSON_IsTrue(item));
item = cJSON_GetObjectItem(root, "light");
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_2, cJSON_IsTrue(item));
item = cJSON_GetObjectItem(root, "cool");
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_3, cJSON_IsTrue(item));
item = cJSON_GetObjectItem(root, "hot");
if (item && cJSON_IsBool(item)) relay_ctrl_set(RELAY_CTRL_ID_4, cJSON_IsTrue(item));
}
cJSON_Delete(root);
}
free(json_str);
}
/* 函数: reconfigure_twdt
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void reconfigure_twdt(uint32_t timeout_ms, uint32_t idle_core_mask)
{
const esp_task_wdt_config_t twdt_cfg = {
.timeout_ms = timeout_ms,
.idle_core_mask = idle_core_mask,
.trigger_panic = true,
};
esp_err_t ret = esp_task_wdt_reconfigure(&twdt_cfg);
if (ret != ESP_OK)
{
ESP_LOGW(TAG, "TWDT reconfigure failed: %s", esp_err_to_name(ret));
}
}
/* 函数: wait_for_wifi_connected
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static bool wait_for_wifi_connected(TickType_t timeout_ticks)
{
const TickType_t start_ticks = xTaskGetTickCount();
while ((xTaskGetTickCount() - start_ticks) < timeout_ticks)
{
if (wifi_connect_get_status() == WIFI_CONNECT_STATUS_CONNECTED)
return true;
vTaskDelay(pdMS_TO_TICKS(200));
}
return false;
}
/* 函数: env_data_update_system_info
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void env_data_update_system_info(void)
{
if (s_env_data_lock == NULL)
return;
time_t now;
struct tm timeinfo;
time(&now);
localtime_r(&now, &timeinfo);
char time_buf[32];
strftime(time_buf, sizeof(time_buf), "%Y-%m-%d %H:%M:%S", &timeinfo);
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
strncpy(s_env_data.time_str, time_buf, sizeof(s_env_data.time_str));
xSemaphoreGive(s_env_data_lock);
set_var_local_time(s_env_data.time_str);
}
/* 函数: ui_task
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void ui_task(void *arg)
{
for (;;)
{
if (!s_ui_ready)
{
vTaskDelay(pdMS_TO_TICKS(20));
continue;
}
env_data_update_system_info();
lvgl_port_lock(0);
ui_tick();
lvgl_port_unlock();
vTaskDelay(pdMS_TO_TICKS(30));
}
}
/* 函数: ui_init_task
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void ui_init_task(void *arg)
{
(void)arg;
ESP_LOGI(TAG, "UI init start");
lvgl_port_lock(100 / portTICK_PERIOD_MS);
ui_init();
lvgl_port_unlock();
ESP_LOGI(TAG, "UI init done");
s_ui_ready = true;
reconfigure_twdt(TWDT_NORMAL_TIMEOUT_MS, (1U << portNUM_PROCESSORS) - 1U);
vTaskDelete(NULL);
}
/* 函数: status_task
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void status_task(void *arg)
{
(void)arg;
for (;;)
{
human_door_state_t io_state{};
if (human_door_read(&io_state) == ESP_OK)
{
set_var_hum_status(io_state.human_present ? "有人" : "无人");
set_var_door_status(io_state.door_closed ? "关闭" : "开启");
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.human_present = io_state.human_present;
s_env_data.door_closed = io_state.door_closed;
xSemaphoreGive(s_env_data_lock);
}
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
/* 函数: relay_status_text
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static const char *relay_status_text(bool on)
{
return on ? "" : "";
}
/* 函数: relay_status_task
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void relay_status_task(void *arg)
{
(void)arg;
for (;;)
{
bool relay_on = false;
if (relay_ctrl_get(RELAY_CTRL_ID_1, &relay_on) == ESP_OK)
{
set_var_fan_status(relay_status_text(relay_on));
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.fan_on = relay_on;
xSemaphoreGive(s_env_data_lock);
}
}
if (relay_ctrl_get(RELAY_CTRL_ID_2, &relay_on) == ESP_OK)
{
set_var_light_status(relay_status_text(relay_on));
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.light_on = relay_on;
xSemaphoreGive(s_env_data_lock);
}
}
if (relay_ctrl_get(RELAY_CTRL_ID_3, &relay_on) == ESP_OK)
{
set_var_cool_status(relay_status_text(relay_on));
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.cool_on = relay_on;
xSemaphoreGive(s_env_data_lock);
}
}
if (relay_ctrl_get(RELAY_CTRL_ID_4, &relay_on) == ESP_OK)
{
set_var_hot_status(relay_status_text(relay_on));
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.hot_on = relay_on;
xSemaphoreGive(s_env_data_lock);
}
}
vTaskDelay(pdMS_TO_TICKS(200));
}
}
/* 函数: sntp_task
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void sntp_task(void *arg)
{
(void)arg;
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
{
esp_err_t sntp_ret = sntp_timp_sync_time(10000);
if (sntp_ret != ESP_OK)
{
ESP_LOGW(TAG, "SNTP sync failed: %s", esp_err_to_name(sntp_ret));
}
}
vTaskDelete(NULL);
}
/* 函数: su03t_rx_callback
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx)
{
(void)user_ctx;
if (frame == NULL)
{
return;
}
char hex_buf[256];
size_t pos = 0;
for (size_t i = 0; i < frame->params_len && pos + 4 < sizeof(hex_buf); ++i)
{
int n = snprintf(&hex_buf[pos], sizeof(hex_buf) - pos, "%02X ", frame->params[i]);
if (n <= 0)
{
break;
}
pos += (size_t)n;
}
if (pos == 0)
{
snprintf(hex_buf, sizeof(hex_buf), "(no params)");
}
ESP_LOGI(TAG, "SU03T RX msgno=0x%02X len=%u params=%s",
frame->msgno,
(unsigned)frame->params_len,
hex_buf);
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.su03t_last_msgno = frame->msgno;
s_env_data.su03t_rx_count++;
xSemaphoreGive(s_env_data_lock);
}
}
/* 函数: hx711_task
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
static void hx711_task(void *arg)
{
(void)arg;
HX711 hx711(kClockPin, kDataPin, HX711::Mode::kChannelA128);
int64_t tare_sum = 0;
int tare_ok_count = 0;
// 上电空载自动去皮:当前重量作为 0g 基准
for (int i = 0; i < HX711_TARE_SAMPLES; ++i)
{
int32_t raw = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS));
if (raw != HX711::kUndefined)
{
tare_sum += raw;
tare_ok_count++;
}
vTaskDelay(pdMS_TO_TICKS(40));
}
int32_t tare_offset = 0;
if (tare_ok_count > 0)
{
tare_offset = (int32_t)(tare_sum / tare_ok_count);
ESP_LOGI(TAG, "HX711 tare done: raw0=%ld, samples=%d", (long)tare_offset, tare_ok_count);
}
else
{
ESP_LOGW(TAG, "HX711 tare failed, use 0 as offset");
}
float filtered_weight_g = 0.0f;
float display_weight_g = 0.0f;
bool display_initialized = false;
bool display_locked = false;
uint32_t stable_count = 0;
uint32_t err_cnt = 0;
for (;;)
{
int32_t value = hx711.Read(pdMS_TO_TICKS(HX711_READ_TIMEOUT_MS));
if (value != HX711::kUndefined)
{
float weight_g = ((float)(value - tare_offset)) / HX711_COUNTS_PER_GRAM;
if (fabsf(weight_g) < HX711_ZERO_DEADBAND_G)
{
weight_g = 0.0f;
}
if (weight_g < 0.0f)
{
weight_g = 0.0f;
}
// 一阶低通,减小抖动
filtered_weight_g = filtered_weight_g * (1.0f - HX711_FILTER_ALPHA) + weight_g * HX711_FILTER_ALPHA;
float rounded_weight_g = roundf(filtered_weight_g * 100.0f) / 100.0f;
if (!display_initialized)
{
display_weight_g = rounded_weight_g;
display_initialized = true;
}
float diff_from_display = fabsf(rounded_weight_g - display_weight_g);
// 稳定后锁定显示,重量明显变化时再解锁并继续更新
if (display_locked)
{
if (diff_from_display >= HX711_UNLOCK_DELTA_G)
{
display_locked = false;
stable_count = 0;
}
}
if (!display_locked)
{
if (diff_from_display <= HX711_STABLE_BAND_G)
{
if (stable_count < HX711_STABLE_SAMPLES)
{
stable_count++;
}
if (stable_count >= HX711_STABLE_SAMPLES)
{
display_locked = true;
}
}
else
{
stable_count = 0;
}
if (diff_from_display >= HX711_UPDATE_MIN_STEP_G)
{
display_weight_g = rounded_weight_g;
}
}
set_var_ice_weight(display_weight_g);
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.ice_weight = display_weight_g;
xSemaphoreGive(s_env_data_lock);
}
err_cnt = 0;
}
else
{
if ((++err_cnt % 20) == 0)
{
ESP_LOGW(TAG, "HX711 read timeout, check DOUT/SCK wiring and power");
}
}
vTaskDelay(pdMS_TO_TICKS(HX711_REFRESH_MS));
}
}
/* 函数: mqtt_publish_task
* 作用: 定时将传感器数据打包成JSON并发布到MQTT
*/
static void mqtt_publish_task(void *arg)
{
(void)arg;
for (;;)
{
if (agri_env_mqtt_is_connected())
{
env_data_t local_data;
if (s_env_data_lock)
{
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
local_data = s_env_data;
xSemaphoreGive(s_env_data_lock);
cJSON *root = cJSON_CreateObject();
if (root)
{
cJSON_AddStringToObject(root, "time", local_data.time_str);
cJSON_AddNumberToObject(root, "lux", local_data.lux);
cJSON_AddNumberToObject(root, "temp", local_data.temp);
cJSON_AddNumberToObject(root, "humidity", local_data.humidity);
cJSON_AddNumberToObject(root, "gas_percent", local_data.gas_percent);
cJSON_AddNumberToObject(root, "tvoc", local_data.tvoc);
cJSON_AddNumberToObject(root, "hcho", local_data.hcho);
cJSON_AddNumberToObject(root, "co2", local_data.co2);
cJSON_AddNumberToObject(root, "ice_weight", local_data.ice_weight);
cJSON_AddNumberToObject(root, "fire_percent", local_data.fire_percent);
cJSON_AddBoolToObject(root, "fire_danger", local_data.fire_danger);
cJSON_AddBoolToObject(root, "human_present", local_data.human_present);
cJSON_AddBoolToObject(root, "door_closed", local_data.door_closed);
cJSON_AddBoolToObject(root, "fan_on", local_data.fan_on);
cJSON_AddBoolToObject(root, "light_on", local_data.light_on);
cJSON_AddBoolToObject(root, "cool_on", local_data.cool_on);
cJSON_AddBoolToObject(root, "hot_on", local_data.hot_on);
cJSON_AddNumberToObject(root, "su03t_last_msgno", local_data.su03t_last_msgno);
cJSON_AddNumberToObject(root, "su03t_rx_count", local_data.su03t_rx_count);
// ======== 新增:系统模式与阈值 ========
cJSON_AddStringToObject(root, "mode", local_data.auto_mode ? "auto" : "manual");
cJSON_AddNumberToObject(root, "th_temp_h", local_data.th_temp_h);
cJSON_AddNumberToObject(root, "th_temp_l", local_data.th_temp_l);
cJSON_AddNumberToObject(root, "th_hum_h", local_data.th_hum_h);
cJSON_AddNumberToObject(root, "th_gas_h", local_data.th_gas_h);
// 补充系统及其他分析状态数据
cJSON_AddStringToObject(root, "ip_address", wifi_connect_get_ip());
cJSON_AddStringToObject(root, "food_status", local_data.co2 >= CO2_SPOILAGE_THRESHOLD_PPM ? "spoilage" : "good");
cJSON_AddNumberToObject(root, "uptime_s", esp_timer_get_time() / 1000000ULL);
cJSON_AddNumberToObject(root, "free_heap_kb", esp_get_free_heap_size() / 1024);
char *json_str = cJSON_PrintUnformatted(root);
if (json_str)
{
agri_env_mqtt_publish(CONFIG_AGRI_ENV_MQTT_PUBLISH_TOPIC, json_str, 0, 0);
free(json_str); // 注意 cJSON_PrintUnformatted 使用 malloc 分配内存
}
cJSON_Delete(root);
}
}
}
vTaskDelay(pdMS_TO_TICKS(1000)); // 每1秒发布一次
}
}
/* 函数: app_main
* 作用: 执行模块内与函数名对应的业务逻辑。
* 重点: 关注输入合法性、返回码与并发安全。
*/
extern "C" void app_main(void)
{
vTaskDelay(pdMS_TO_TICKS(100));
ESP_LOGI(TAG, "--- APP STARTING ---");
s_env_data_lock = xSemaphoreCreateMutex();
// 初始化默认配置与阈值
s_env_data.auto_mode = false; // 默认手动模式
s_env_data.th_temp_h = 35.0f; // 超过 35度 制冷
s_env_data.th_temp_l = 15.0f; // 低于 15度 制热
s_env_data.th_hum_h = 70.0f; // 湿度高于 70% 通风
s_env_data.th_gas_h = 20.0f; // 气体浓度高于 20% 通风
// 1. 初始化 Wi-Fi
ESP_ERROR_CHECK(wifi_connect_init());
// 2. 初始化显示屏和 LVGL
start_lvgl_demo();
// UI 初始化期间,临时放宽 TWDT仅监控 CPU0 空闲任务)
reconfigure_twdt(TWDT_UI_INIT_TIMEOUT_MS, 1U << 0);
// 3. 在 CPU1 上执行 UI 初始化,避免 app_main 长时间占用 CPU0
xTaskCreatePinnedToCore(ui_init_task, "ui_init_task", 8192, NULL, 8, NULL, 1);
set_var_food_status("良好");
set_var_fire_status("安全");
// 7. 创建 UI 刷新任务并固定在 CPU1与 ui_init_task 同核)
xTaskCreatePinnedToCore(ui_task, "ui_task", 8192, NULL, 5, NULL, 1);
if (wait_for_wifi_connected(pdMS_TO_TICKS(15000)))
{
set_var_system_ip(wifi_connect_get_ip());
// 注册 MQTT 数据接收回调
agri_env_set_mqtt_cmd_cb(app_mqtt_cmd_handler);
esp_err_t err = agri_env_mqtt_start();
if (err != ESP_OK)
{
ESP_LOGW(TAG, "MQTT 启动失败: %s", esp_err_to_name(err));
}
}
// 4. 独立 SNTP 对时任务
xTaskCreate(sntp_task, "sntp_task", 4096, NULL, 4, NULL);
// HX711 电子秤GPIO13(SCK) / GPIO14(DOUT)
xTaskCreate(hx711_task, "hx711_task", 4096, NULL, 6, NULL);
// 初始化继电器 (独立配置每个通道)
const relay_config_t relay_cfg[RELAY_CTRL_ID_MAX] = {
{.pin = GPIO_NUM_12, .active_high = true},
{.pin = GPIO_NUM_11, .active_high = true},
{.pin = GPIO_NUM_10, .active_high = true},
{.pin = GPIO_NUM_9, .active_high = true},
};
ESP_ERROR_CHECK(relay_ctrl_init(relay_cfg));
set_var_fan_status("");
set_var_light_status("");
set_var_cool_status("");
set_var_hot_status("");
s_env_data.fan_on = false;
s_env_data.light_on = false;
s_env_data.cool_on = false;
s_env_data.hot_on = false;
// 5. 初始化 I2C 总线并注册传感器 (共享总线)
ESP_ERROR_CHECK(bh1750_user_init());
i2c_master_bus_handle_t i2c_bus = bh1750_get_i2c_bus_handle();
// AHT30 挂载到同一条 I2C 总线上
aht30_handle_t aht30_dev = NULL;
ESP_ERROR_CHECK(aht30_create(i2c_bus, AHT30_I2C_ADDRESS, &aht30_dev));
// MQ-2 使用 ADC(GPIO8)
ESP_ERROR_CHECK(mq2_init());
// JW01 使用 UART0(GPIO43/44)
ESP_ERROR_CHECK(jw01_init());
// 火焰传感器使用 ADC(GPIO3)
ESP_ERROR_CHECK(fire_sensor_init());
// SU-03T 语音模块UART2: RX=IO41 TX=IO42
ESP_ERROR_CHECK(su03t_init());
ESP_ERROR_CHECK(su03t_start_receiver(su03t_rx_callback, NULL, 4096, 5));
// GPIO16: HC-SR312, GPIO17: Door switch(低电平=关门)
ESP_ERROR_CHECK(human_door_init());
set_var_hum_status("无人");
set_var_door_status("关闭");
// 6. 创建传感器读取任务
xTaskCreate([](void *arg)
{
aht30_handle_t aht30 = (aht30_handle_t)arg;
uint32_t log_cnt = 0;
for (;;) {
float lux = 0.0f, temp = 0.0f, hum = 0.0f, gas_percent = 0.0f, fire_percent = 0.0f;
jw01_data_t jw01{};
esp_err_t bh_ret = ESP_FAIL;
esp_err_t aht_ret = ESP_FAIL;
esp_err_t mq2_ret = ESP_FAIL;
esp_err_t jw_ret = ESP_FAIL;
esp_err_t fire_ret = ESP_FAIL;
// 读取 BH1750
bh_ret = bh1750_user_read(&lux);
if (bh_ret == ESP_OK) {
set_var_light_val(lux);
}
// 读取 AHT30
aht_ret = aht30_get_temperature_humidity_value(aht30, &temp, &hum);
if (aht_ret == ESP_OK) {
set_var_temp(temp);
set_var_humity_val(hum);
}
// 读取 MQ-2更新空气质量变量
mq2_ret = mq2_read_percent(&gas_percent);
if (mq2_ret == ESP_OK) {
set_var_air_quity(gas_percent);
}
// 读取火焰传感器,更新火焰状态
fire_ret = fire_sensor_read_percent(&fire_percent);
if (fire_ret == ESP_OK) {
set_var_fire_status(fire_sensor_is_danger(fire_percent, FIRE_DANGER_THRESHOLD_PERCENT) ? "危险" : "安全");
}
// 读取 JW01TVOC/HCHO/CO2
jw_ret = jw01_read(&jw01, 200);
if (jw_ret == ESP_OK) {
if (jw01.co2_valid) {
if (jw01.co2 >= CO2_SPOILAGE_THRESHOLD_PPM) {
set_var_food_status("变质");
} else {
set_var_food_status("良好");
}
}
}
// 每 5 次打印一次综合状态,避免日志刷屏
if ((log_cnt++ % 10) == 0) {
ESP_LOGI(TAG,
"SENS bh=%s lux=%.1f | aht=%s t=%.1f h=%.1f | mq2=%s gas=%.1f | fire=%s fp=%.1f | jw01=%s co2_valid=%d co2=%.1f",
esp_err_to_name(bh_ret), lux,
esp_err_to_name(aht_ret), temp, hum,
esp_err_to_name(mq2_ret), gas_percent,
esp_err_to_name(fire_ret), fire_percent,
esp_err_to_name(jw_ret), jw01.co2_valid ? 1 : 0, jw01.co2);
}
// 提取自动化逻辑所需参数
bool auto_mode = false; // 默认手动
float th_temp_h = 35.0f;
float th_temp_l = 15.0f;
float th_hum_h = 70.0f;
float th_gas_h = 20.0f;
// 数据存入共享结构体
if (s_env_data_lock) {
xSemaphoreTake(s_env_data_lock, portMAX_DELAY);
s_env_data.lux = lux;
s_env_data.temp = temp;
s_env_data.humidity = hum;
s_env_data.gas_percent = gas_percent;
s_env_data.fire_percent = fire_percent;
s_env_data.fire_danger = fire_sensor_is_danger(fire_percent, FIRE_DANGER_THRESHOLD_PERCENT);
if (jw01.tvoc_valid) s_env_data.tvoc = jw01.tvoc;
if (jw01.hcho_valid) s_env_data.hcho = jw01.hcho;
if (jw01.co2_valid) s_env_data.co2 = jw01.co2;
auto_mode = s_env_data.auto_mode;
th_temp_h = s_env_data.th_temp_h;
th_temp_l = s_env_data.th_temp_l;
th_hum_h = s_env_data.th_hum_h;
th_gas_h = s_env_data.th_gas_h;
xSemaphoreGive(s_env_data_lock);
}
// ======== 新增:自动联动逻辑 ========
if (auto_mode)
{
// 制冷控制(温度高于上限)
if (temp >= th_temp_h) relay_ctrl_set(RELAY_CTRL_ID_3, true);
else relay_ctrl_set(RELAY_CTRL_ID_3, false);
// 制热控制(温度低于下限)
if (temp <= th_temp_l) relay_ctrl_set(RELAY_CTRL_ID_4, true);
else relay_ctrl_set(RELAY_CTRL_ID_4, false);
// 风扇控制(湿度或有害气体超标)
if (hum >= th_hum_h || gas_percent >= th_gas_h) relay_ctrl_set(RELAY_CTRL_ID_1, true);
else relay_ctrl_set(RELAY_CTRL_ID_1, false);
}
vTaskDelay(pdMS_TO_TICKS(1000));
} }, "sensor_task", 4096 * 3, (void *)aht30_dev, 6, NULL);
// 独立任务:人体与门状态检测
xTaskCreate(status_task, "status_task", 4096, NULL, 6, NULL);
// 独立任务:继电器状态同步到 UI
xTaskCreate(relay_status_task, "relay_status_task", 4096, NULL, 5, NULL);
// 独立任务MQTT 定时发布传感器数据
xTaskCreate(mqtt_publish_task, "mqtt_publish_task", 4096 * 2, NULL, 5, NULL);
}