mirror of
https://git.beihong.wang/wangbeihong/iot-bedroom-environment-controller.git
synced 2026-04-23 11:23:03 +08:00
911 lines
32 KiB
C++
911 lines
32 KiB
C++
#include <stdio.h>
|
||
#include <stdlib.h>
|
||
#include <string.h>
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <math.h>
|
||
#include <time.h>
|
||
#include <inttypes.h>
|
||
|
||
#include "freertos/FreeRTOS.h"
|
||
#include "freertos/task.h"
|
||
#include "freertos/semphr.h"
|
||
|
||
#include "esp_system.h"
|
||
#include "esp_event.h"
|
||
#include "esp_log.h"
|
||
#include "esp_err.h"
|
||
#include "nvs_flash.h"
|
||
#include "esp_netif.h"
|
||
#include "esp_sntp.h"
|
||
#include "esp_timer.h"
|
||
|
||
#include "mqtt_client.h"
|
||
#include "cJSON.h"
|
||
#include "esp_mac.h"
|
||
#include "driver/gpio.h"
|
||
#include "driver/uart.h"
|
||
#include "driver/ledc.h"
|
||
|
||
#include "common.h"
|
||
#include "sensors.h"
|
||
#include "mqtt_manager.h"
|
||
#include "wifi_manager.h"
|
||
#include "peripherals.h"
|
||
|
||
#include "lvgl_st7789_use.h"
|
||
#include "iot_servo.h"
|
||
#include "time_alarm.h"
|
||
#include "wifi-connect.h"
|
||
#include "serial_mcu.h"
|
||
#include "app_state.h"
|
||
|
||
#include "ui.h" // 使用EEZStudio提供的ui组件,便于后续扩展
|
||
#include "esp_lvgl_port.h"
|
||
#include "vars.h" // 定义全局变量接口
|
||
|
||
// 添加 extern "C" 包裹 C 头文件声明
|
||
#ifdef __cplusplus
|
||
extern "C"
|
||
{
|
||
#endif
|
||
|
||
// C 函数声明
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
/* Forward declarations */
|
||
static void mqtt_app_start(void);
|
||
static void cooling_mode_task(void *pvParameters);
|
||
|
||
#ifdef __cplusplus
|
||
extern "C"
|
||
{
|
||
#endif
|
||
void time_period_check_task(void *pvParameters);
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
static const char *TAG = "main";
|
||
/* 其他子模块在各自文件中定义 TAG,避免在 main 中重复定义未使用的 TAG */
|
||
|
||
/* 共享应用状态由 main/app_state.c 提供定义,使用 app_state.h 中的 extern 访问 */
|
||
|
||
/* 校准常量已迁移到 peripherals 模块 */
|
||
|
||
/* 简单遥测上报封装(暂为适配层) */
|
||
static void update_telemetry_and_report(void)
|
||
{
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
#define I2C_MASTER_SCL_IO 5 // GPIO number for I2C master clock
|
||
#define I2C_MASTER_SDA_IO 4 // GPIO number for I2C master data
|
||
#define I2C_MASTER_NUM I2C_NUM_0 // I2C port number for master dev
|
||
#define I2C_MASTER_FREQ_HZ 100000 // I2C master clock frequency
|
||
|
||
// SERVO_GPIO defined in peripherals.h
|
||
|
||
// ========================= MQTT配置 =========================
|
||
#define MQTT_BROKER_URL "mqtt://beihong.wang:1883" // MQTT代理URL,从menuconfig配置获取
|
||
#define MQTT_USERNAME "esp_mqtt_client" // MQTT用户名
|
||
#define MQTT_CLIENT_ID "esp_mqtt_client" // MQTT客户端ID
|
||
#define MQTT_PASSWORD "664hd78gas97" // MQTT密码
|
||
|
||
// MQTT主题配置
|
||
#define MQTT_PUBLISH_TOPIC_QOS0 "topic/sensor/esp32_iothome_001" // QoS0发布的主题
|
||
#define MQTT_NOTIFY_TOPIC "topic/control/esp32_iothome_001" // 上层通知主题
|
||
#define MQTT_UNSUBSCRIBE_TOPIC "topic/control/esp32_iothome_001" // 取消订阅的主题
|
||
|
||
// mqtt_publish_task moved to mqtt_manager.c
|
||
|
||
void mqtt_publish_feedback(void)
|
||
{
|
||
mqtt_manager_publish_feedback();
|
||
}
|
||
|
||
// mqtt_event_handler moved to mqtt_manager.c
|
||
|
||
// 优化后的代码
|
||
static void safe_mqtt_start_task(void *pvParameters)
|
||
{
|
||
(void)pvParameters;
|
||
|
||
ESP_LOGI(TAG, "安全MQTT启动任务开始,等待WiFi连接...");
|
||
|
||
int retry_count = 0;
|
||
const int max_retries = 60; // 60秒超时
|
||
bool wifi_connected = false;
|
||
|
||
while (retry_count < max_retries)
|
||
{
|
||
wifi_connect_status_t wifi_status = wifi_connect_get_status();
|
||
|
||
if (wifi_status == WIFI_CONNECT_STATUS_CONNECTED)
|
||
{
|
||
wifi_connected = true;
|
||
ESP_LOGI(TAG, "WiFi连接成功 (IP获取可能需要更多时间),准备启动MQTT");
|
||
break;
|
||
}
|
||
|
||
// 增加对 FAILED 的处理,避免死等
|
||
if (wifi_status == WIFI_CONNECT_STATUS_FAILED)
|
||
{
|
||
ESP_LOGW(TAG, "WiFi连接明确失败,等待重连...");
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||
retry_count++;
|
||
}
|
||
|
||
if (!wifi_connected)
|
||
{
|
||
ESP_LOGW(TAG, "WiFi连接超时 (%d秒),但仍尝试启动MQTT", max_retries);
|
||
}
|
||
|
||
// 等待 SNTP 同步
|
||
// 注意:如果 WiFi 没连上,SNTP 也会超时,这里依赖 time_alarm 内部的超时机制
|
||
ESP_LOGI(TAG, "等待 SNTP 时间同步...");
|
||
time_alarm_wait_for_sntp_sync();
|
||
|
||
// 启动 MQTT
|
||
ESP_LOGI(TAG, "启动MQTT管理器");
|
||
esp_err_t err = mqtt_manager_start();
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGE(TAG, "MQTT启动失败: %s", esp_err_to_name(err));
|
||
// 可选:这里可以添加重启逻辑,或者进入配网模式
|
||
}
|
||
|
||
vTaskDelete(NULL);
|
||
}
|
||
|
||
static void mqtt_app_start(void)
|
||
{
|
||
ESP_LOGI(TAG, "mqtt_app_start: 创建安全MQTT启动任务");
|
||
|
||
// 【关键修改】将栈大小从 3072 改为 2048 (8KB) 甚至 1536 (6KB)
|
||
// 这个任务只是等待,不需要大栈空间
|
||
BaseType_t task_ok = xTaskCreate(safe_mqtt_start_task,
|
||
"safe_mqtt_start",
|
||
2048, // 修改这里:从 3072 降为 2048
|
||
NULL,
|
||
5,
|
||
NULL);
|
||
|
||
if (task_ok != pdPASS)
|
||
{
|
||
ESP_LOGE(TAG, "创建安全启动任务失败(内存不足),直接启动MQTT");
|
||
// 如果连任务都创建不了,说明内存极度紧张,直接启动可能会再次失败
|
||
esp_err_t err = mqtt_manager_start();
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGE(TAG, "直接启动MQTT也失败: %s", esp_err_to_name(err));
|
||
}
|
||
}
|
||
}
|
||
|
||
// ========================= HTTP服务器相关函数 =========================
|
||
|
||
// I2C句柄已在前面声明
|
||
extern i2c_master_bus_handle_t bus_handle;
|
||
extern i2c_master_dev_handle_t dev_handle;
|
||
|
||
// 舵机初始化函数
|
||
// 舵机与外设控制由 peripherals 模块提供
|
||
|
||
static void rx_task(void *arg)
|
||
{
|
||
static const char *RX_TASK_TAG = "RX_TASK";
|
||
esp_log_level_set(RX_TASK_TAG, ESP_LOG_INFO);
|
||
uint8_t *data = (uint8_t *)malloc(1024 + 1);
|
||
static uint8_t frame_buffer[256]; // 帧缓冲区
|
||
static int frame_index = 0; // 帧索引
|
||
|
||
while (1)
|
||
{
|
||
const int rxBytes = uart_read_bytes(UART_NUM_1, data, 1024, 1000 / portTICK_PERIOD_MS);
|
||
if (rxBytes > 0)
|
||
{
|
||
// 处理接收到的数据
|
||
for (int i = 0; i < rxBytes; i++)
|
||
{
|
||
// 查找帧头 AA
|
||
if (frame_index == 0 && data[i] == 0xAA)
|
||
{
|
||
frame_buffer[frame_index++] = data[i];
|
||
}
|
||
// 检查数据部分是否在01到06范围内
|
||
else if (frame_index == 1 && data[i] >= 0x01 && data[i] <= 0x06)
|
||
{
|
||
frame_buffer[frame_index++] = data[i];
|
||
}
|
||
// 检查帧尾 55
|
||
else if (frame_index == 2 && data[i] == 0x55)
|
||
{
|
||
frame_buffer[frame_index++] = data[i];
|
||
|
||
// 接收到完整帧 AA [01-06] 55
|
||
ESP_LOGI(RX_TASK_TAG, "Valid frame received: AA 0x%02X 55", frame_buffer[1]);
|
||
|
||
// 解析按键状态
|
||
uint8_t data_value = frame_buffer[1];
|
||
|
||
// 根据数据值解析按键状态
|
||
switch (data_value)
|
||
{
|
||
case 0x01:
|
||
ESP_LOGI(RX_TASK_TAG, "Key 1 pressed - LED toggle");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
light_source_control_flag = !light_source_control_flag;
|
||
led_brightness_value = light_source_control_flag ? 100 : 0;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(RX_TASK_TAG, "LED light: %s (brightness=%d)",
|
||
light_source_control_flag ? "ON" : "OFF", led_brightness_value);
|
||
mqtt_manager_publish_feedback();
|
||
break;
|
||
case 0x02:
|
||
ESP_LOGI(RX_TASK_TAG, "Key 2 pressed - Fan toggle");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
fan_control_flag = !fan_control_flag;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(RX_TASK_TAG, "Fan: %s", fan_control_flag ? "ON" : "OFF");
|
||
mqtt_manager_publish_feedback();
|
||
break;
|
||
case 0x03:
|
||
ESP_LOGI(RX_TASK_TAG, "Key 3 pressed - Curtain toggle");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
servo_control_flag = !servo_control_flag;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(RX_TASK_TAG, "Curtain: %s", servo_control_flag ? "OPEN" : "CLOSE");
|
||
mqtt_manager_publish_feedback();
|
||
break;
|
||
case 0x04:
|
||
ESP_LOGI(RX_TASK_TAG, "Key 4 pressed - Buzzer toggle");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = !buzzer_control_flag;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(RX_TASK_TAG, "Buzzer: %s", buzzer_control_flag ? "ON" : "OFF");
|
||
mqtt_manager_publish_feedback();
|
||
break;
|
||
case 0x05:
|
||
ESP_LOGI(RX_TASK_TAG, "Key 5 pressed - Backlight toggle");
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
led_backlight_on = !led_backlight_on;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(RX_TASK_TAG, "LCD Backlight: %s", led_backlight_on ? "ON" : "OFF");
|
||
mqtt_manager_publish_feedback();
|
||
break;
|
||
case 0x06:
|
||
ESP_LOGI(RX_TASK_TAG, "Key 6 pressed");
|
||
// ui_toggle_page(); // 翻页
|
||
break;
|
||
default:
|
||
ESP_LOGI(RX_TASK_TAG, "Unknown key value: 0x%02X", data_value);
|
||
break;
|
||
}
|
||
|
||
frame_index = 0;
|
||
}
|
||
else
|
||
{
|
||
frame_index = 0;
|
||
if (data[i] == 0xAA)
|
||
{
|
||
frame_buffer[frame_index++] = data[i];
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
free(data);
|
||
}
|
||
|
||
// void initialize_nvs()
|
||
// {
|
||
// esp_err_t ret = nvs_flash_init(); // 初始化NVS, 并检查是否需要擦除NVS
|
||
// if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND)
|
||
// {
|
||
// ESP_ERROR_CHECK(nvs_flash_erase());
|
||
// ret = nvs_flash_init();
|
||
// }
|
||
// ESP_ERROR_CHECK(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));
|
||
}
|
||
}
|
||
|
||
/* 外设控制逻辑已迁移到 peripherals 模块(main/peripherals.c),
|
||
在此文件中不再实现 `peripheral_control_task` 与 `init_gpio_output`。
|
||
main.c 通过包含 peripherals.h 并在 app_main 中调用 `peripherals_init()`
|
||
与 `xTaskCreate(peripheral_control_task, ...)` 来使用该任务。 */
|
||
|
||
/* 闹钟逻辑已迁移到 time_alarm 模块 (main/time_alarm.c) */
|
||
|
||
// 添加 extern "C" 包裹 app_main 函数
|
||
#ifdef __cplusplus
|
||
extern "C"
|
||
{
|
||
#endif
|
||
|
||
void app_main(void)
|
||
{
|
||
// esp_log_level_set("*", ESP_LOG_INFO);
|
||
// esp_log_level_set("mqtt_client", ESP_LOG_VERBOSE);
|
||
// esp_log_level_set("mqtt_example", ESP_LOG_VERBOSE);
|
||
// esp_log_level_set("transport_base", ESP_LOG_VERBOSE);
|
||
// esp_log_level_set("esp-tls", ESP_LOG_VERBOSE);
|
||
// esp_log_level_set("transport", ESP_LOG_VERBOSE);
|
||
// esp_log_level_set("outbox", ESP_LOG_VERBOSE);
|
||
// esp_log_level_set("dhcps", ESP_LOG_DEBUG);
|
||
// esp_log_level_set("esp_netif", ESP_LOG_DEBUG);
|
||
// esp_log_level_set("esp_netif_lwip", ESP_LOG_DEBUG);
|
||
// esp_log_level_set("wifi", ESP_LOG_DEBUG);
|
||
esp_log_level_set("mqtt_manager", ESP_LOG_DEBUG);
|
||
|
||
// 设置时区为北京时间 (CST-8)
|
||
setenv("TZ", "CST-8", 1);
|
||
tzset();
|
||
|
||
// 初始化 Wi-Fi 配网组件,支持长按按键进入配网
|
||
ESP_ERROR_CHECK(wifi_connect_init());
|
||
printf("设备启动完成:进入配网模式,手机连接 ESP32-* 后访问 http://192.168.4.1\n");
|
||
|
||
// 初始化模块骨架(stub),保持与现有流程兼容
|
||
ESP_ERROR_CHECK(wifi_manager_init());
|
||
// mqtt_manager_init 为骨架占位,实际 MQTT 启动仍由原有 mqtt_app_start
|
||
ESP_ERROR_CHECK(mqtt_manager_init());
|
||
|
||
// 启动 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);
|
||
|
||
// 初始化I2C总线
|
||
ESP_ERROR_CHECK(i2c_master_init());
|
||
|
||
// 创建互斥锁
|
||
xSensorDataMutex = xSemaphoreCreateMutex();
|
||
if (xSensorDataMutex == NULL)
|
||
{
|
||
ESP_LOGE(TAG, "创建传感器数据互斥锁失败");
|
||
}
|
||
|
||
// 添加MQTT消息互斥锁
|
||
xMqttMessageMutex = xSemaphoreCreateMutex();
|
||
if (xMqttMessageMutex == NULL)
|
||
{
|
||
ESP_LOGE(TAG, "创建MQTT消息互斥锁失败");
|
||
}
|
||
|
||
// 创建时间段配置互斥锁
|
||
xTimePeriodMutex = xSemaphoreCreateMutex();
|
||
if (xTimePeriodMutex == NULL)
|
||
{
|
||
ESP_LOGE(TAG, "创建时间段配置互斥锁失败");
|
||
}
|
||
|
||
// 创建控制标志互斥锁
|
||
xControlFlagMutex = xSemaphoreCreateMutex();
|
||
if (xControlFlagMutex == NULL)
|
||
{
|
||
ESP_LOGE(TAG, "创建控制标志互斥锁失败");
|
||
}
|
||
|
||
// 初始化外设与模块(stub),sensor 需在 I2C 与互斥锁初始化后进行
|
||
ESP_ERROR_CHECK(peripherals_init());
|
||
ESP_ERROR_CHECK(sensors_init());
|
||
ESP_ERROR_CHECK(time_alarm_init());
|
||
|
||
// 初始化舵机
|
||
servo_init();
|
||
|
||
// GPIO输出初始化(风扇控制)
|
||
init_gpio_output();
|
||
|
||
// MCU间的串口通信初始化
|
||
serial_mcu_init();
|
||
|
||
mqtt_app_start(); // 启动 MQTT 客户端
|
||
ESP_ERROR_CHECK(time_alarm_start());
|
||
// 创建降温模式任务
|
||
xTaskCreate(cooling_mode_task, "cooling_task", 3072, NULL, 5, NULL);
|
||
// 创建时间段检查任务
|
||
xTaskCreate(time_period_check_task, "time_period_task", 3072, NULL, 5, NULL);
|
||
// 创建外设控制任务
|
||
xTaskCreate(peripheral_control_task, "periph_ctrl_task", 3072, NULL, 5, NULL);
|
||
|
||
// 传感器任务由 sensors_init() 启动
|
||
// 创建接收任务
|
||
xTaskCreate(rx_task, "uart_rx_task", 3072, NULL, configMAX_PRIORITIES - 1, NULL);
|
||
|
||
while (1)
|
||
{
|
||
|
||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||
}
|
||
}
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|
||
|
||
/**
|
||
* @brief 降温模式任务
|
||
* 监测温度,当温度超过阈值时自动开启风扇
|
||
* 当温度超过35°C时触发高温提醒
|
||
*/
|
||
static void cooling_mode_task(void *pvParameters)
|
||
{
|
||
ESP_LOGI(TAG, "Cooling mode task started");
|
||
|
||
// 从NVS加载配置
|
||
cooling_mode_load_from_nvs();
|
||
|
||
// 高温阈值
|
||
|
||
while (1)
|
||
{
|
||
if (g_cooling_mode_enabled)
|
||
{
|
||
float current_temp = 0;
|
||
bool temp_valid = false;
|
||
|
||
// 获取当前温度
|
||
if (xSensorDataMutex != NULL && xSemaphoreTake(xSensorDataMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
current_temp = g_sensor_data.temperature;
|
||
temp_valid = g_sensor_data.ahtxx_valid;
|
||
xSemaphoreGive(xSensorDataMutex);
|
||
}
|
||
|
||
if (temp_valid)
|
||
{
|
||
// 降温模式:温度超过阈值,开启风扇
|
||
if (current_temp > g_temperature_threshold)
|
||
{
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
if (!fan_control_flag)
|
||
{
|
||
fan_control_flag = true;
|
||
ESP_LOGI(TAG, "Temperature %.1f°C > %.1f°C, cooling mode: Fan ON",
|
||
current_temp, g_temperature_threshold);
|
||
update_telemetry_and_report();
|
||
}
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
}
|
||
// 温度恢复到阈值以下,关闭风扇
|
||
else if (current_temp < (g_temperature_threshold - 1.0f)) // 添加1°C的滞后,防止频繁切换
|
||
{
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
if (fan_control_flag)
|
||
{
|
||
fan_control_flag = false;
|
||
ESP_LOGI(TAG, "Temperature %.1f°C < %.1f°C, cooling mode: Fan OFF",
|
||
current_temp, g_temperature_threshold - 1.0f);
|
||
update_telemetry_and_report();
|
||
}
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
}
|
||
|
||
// 高温提醒:温度超过阈值
|
||
if (current_temp > g_temperature_threshold)
|
||
{
|
||
if (!g_high_temp_alerted)
|
||
{
|
||
g_high_temp_alerted = true;
|
||
|
||
// 蜂鸣器发出高温提示音
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = true;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
|
||
ESP_LOGW(TAG, "High temperature alert: %.1f°C (>%.1f°C)", current_temp, g_temperature_threshold);
|
||
|
||
// 延时3秒后自动关闭蜂鸣器
|
||
vTaskDelay(pdMS_TO_TICKS(3000));
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "蜂鸣器已自动关闭(3秒)");
|
||
|
||
// 发送MQTT提醒消息
|
||
if (g_mqtt_client != NULL)
|
||
{
|
||
cJSON *root = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(root, "type", "device_message");
|
||
cJSON_AddStringToObject(root, "device_id", g_device_message.device_id);
|
||
cJSON_AddStringToObject(root, "device_type", g_device_message.device_type);
|
||
cJSON_AddStringToObject(root, "message_type", "high_temp_alert");
|
||
|
||
cJSON *data_obj = cJSON_CreateObject();
|
||
cJSON_AddStringToObject(data_obj, "alert", "温度过高请注意通风");
|
||
cJSON_AddNumberToObject(data_obj, "temperature", roundf(current_temp * 10) / 10);
|
||
cJSON_AddItemToObject(root, "data", data_obj);
|
||
|
||
// 尝试使用预分配缓冲区避免堆分配
|
||
static char temp_json_buf[512];
|
||
bool printed_temp = cJSON_PrintPreallocated(root, temp_json_buf, sizeof(temp_json_buf), 0);
|
||
if (printed_temp)
|
||
{
|
||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, temp_json_buf, 0, 0, 0);
|
||
ESP_LOGI(TAG, "High temp alert sent: %s", temp_json_buf);
|
||
}
|
||
else
|
||
{
|
||
char *json_message = cJSON_Print(root);
|
||
if (json_message)
|
||
{
|
||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message, 0, 0, 0);
|
||
ESP_LOGI(TAG, "High temp alert sent: %s", json_message);
|
||
free(json_message);
|
||
}
|
||
}
|
||
cJSON_Delete(root);
|
||
}
|
||
update_telemetry_and_report();
|
||
}
|
||
}
|
||
else
|
||
{
|
||
// 温度恢复正常,重置高温提醒标志
|
||
if (g_high_temp_alerted)
|
||
{
|
||
g_high_temp_alerted = false;
|
||
// 关闭高温提醒蜂鸣器
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
buzzer_control_flag = false;
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
ESP_LOGI(TAG, "Temperature normalized: %.1f°C, reset high temp alert", current_temp);
|
||
update_telemetry_and_report();
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
// 每5秒检查一次
|
||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||
}
|
||
}
|
||
|
||
|
||
|
||
// Remaining MQ135 implementation removed
|
||
|
||
/* ----------------- 时间段与降温模式的最小存根实现 ----------------- */
|
||
#ifdef __cplusplus
|
||
extern "C"
|
||
{
|
||
#endif
|
||
|
||
void cooling_mode_save_to_nvs(void)
|
||
{
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open("cooling_config", NVS_READWRITE, &nvs_handle);
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGE(TAG, "Failed to open NVS for cooling config: %s", esp_err_to_name(err));
|
||
return;
|
||
}
|
||
|
||
err = nvs_set_u8(nvs_handle, "cooling_enabled", g_cooling_mode_enabled ? 1 : 0);
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGE(TAG, "Failed to save cooling enabled: %s", esp_err_to_name(err));
|
||
}
|
||
|
||
err = nvs_set_u32(nvs_handle, "temp_th", (uint32_t)(g_temperature_threshold * 10));
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGE(TAG, "Failed to save temperature threshold: %s", esp_err_to_name(err));
|
||
}
|
||
|
||
nvs_close(nvs_handle);
|
||
ESP_LOGI(TAG, "Cooling mode config saved to NVS");
|
||
}
|
||
|
||
void cooling_mode_load_from_nvs(void)
|
||
{
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open("cooling_config", NVS_READONLY, &nvs_handle);
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGI(TAG, "No cooling config found in NVS, using defaults");
|
||
g_cooling_mode_enabled = false;
|
||
g_temperature_threshold = 39.0f;
|
||
return;
|
||
}
|
||
|
||
uint8_t cooling_enabled = 0;
|
||
err = nvs_get_u8(nvs_handle, "cooling_enabled", &cooling_enabled);
|
||
if (err == ESP_OK)
|
||
{
|
||
g_cooling_mode_enabled = (cooling_enabled == 1);
|
||
}
|
||
else
|
||
{
|
||
g_cooling_mode_enabled = false;
|
||
}
|
||
|
||
uint32_t temp_threshold = 390; // 39.0°C * 10
|
||
err = nvs_get_u32(nvs_handle, "temp_th", &temp_threshold);
|
||
if (err == ESP_OK)
|
||
{
|
||
g_temperature_threshold = temp_threshold / 10.0f;
|
||
}
|
||
else
|
||
{
|
||
g_temperature_threshold = 39.0f;
|
||
}
|
||
|
||
nvs_close(nvs_handle);
|
||
ESP_LOGI(TAG, "Cooling mode config loaded from NVS: enabled=%d, threshold=%.1f°C",
|
||
g_cooling_mode_enabled, g_temperature_threshold);
|
||
}
|
||
|
||
void time_period_set(time_period_type_t period_type, uint8_t start_hour, uint8_t start_minute,
|
||
uint8_t end_hour, uint8_t end_minute)
|
||
{
|
||
ESP_LOGI(TAG, "设置时间段: type=%d, start=%02d:%02d, end=%02d:%02d",
|
||
period_type, start_hour, start_minute, end_hour, end_minute);
|
||
|
||
if (xTimePeriodMutex != NULL && xSemaphoreTake(xTimePeriodMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
if (period_type == TIME_PERIOD_DAY)
|
||
{
|
||
g_day_period.start_hour = start_hour;
|
||
g_day_period.start_minute = start_minute;
|
||
g_day_period.end_hour = end_hour;
|
||
g_day_period.end_minute = end_minute;
|
||
}
|
||
else if (period_type == TIME_PERIOD_NIGHT)
|
||
{
|
||
g_night_period.start_hour = start_hour;
|
||
g_night_period.start_minute = start_minute;
|
||
g_night_period.end_hour = end_hour;
|
||
g_night_period.end_minute = end_minute;
|
||
}
|
||
xSemaphoreGive(xTimePeriodMutex);
|
||
}
|
||
|
||
time_period_save_to_nvs();
|
||
}
|
||
|
||
void time_period_save_to_nvs(void)
|
||
{
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open("time_period", NVS_READWRITE, &nvs_handle);
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGE(TAG, "Failed to open NVS for time period: %s", esp_err_to_name(err));
|
||
return;
|
||
}
|
||
|
||
// 保存昼间时段
|
||
err = nvs_set_u8(nvs_handle, "day_start_hour", g_day_period.start_hour);
|
||
err = nvs_set_u8(nvs_handle, "day_start_min", g_day_period.start_minute);
|
||
err = nvs_set_u8(nvs_handle, "day_end_hour", g_day_period.end_hour);
|
||
err = nvs_set_u8(nvs_handle, "day_end_min", g_day_period.end_minute);
|
||
|
||
// 保存夜间时段
|
||
err = nvs_set_u8(nvs_handle, "night_start_hour", g_night_period.start_hour);
|
||
err = nvs_set_u8(nvs_handle, "night_start_min", g_night_period.start_minute);
|
||
err = nvs_set_u8(nvs_handle, "night_end_hour", g_night_period.end_hour);
|
||
err = nvs_set_u8(nvs_handle, "night_end_min", g_night_period.end_minute);
|
||
|
||
// 保存是否启用时间段控制
|
||
err = nvs_set_u8(nvs_handle, "use_time_period", g_use_time_period ? 1 : 0);
|
||
|
||
nvs_close(nvs_handle);
|
||
ESP_LOGI(TAG, "Time period config saved to NVS");
|
||
}
|
||
|
||
void time_period_load_from_nvs(void)
|
||
{
|
||
nvs_handle_t nvs_handle;
|
||
esp_err_t err = nvs_open("time_period", NVS_READONLY, &nvs_handle);
|
||
if (err != ESP_OK)
|
||
{
|
||
ESP_LOGI(TAG, "No time period config found in NVS, using defaults");
|
||
g_day_period.start_hour = 8;
|
||
g_day_period.start_minute = 0;
|
||
g_day_period.end_hour = 20;
|
||
g_day_period.end_minute = 0;
|
||
|
||
g_night_period.start_hour = 20;
|
||
g_night_period.start_minute = 0;
|
||
g_night_period.end_hour = 8;
|
||
g_night_period.end_minute = 0;
|
||
|
||
g_use_time_period = false;
|
||
return;
|
||
}
|
||
|
||
// 加载昼间时段
|
||
uint8_t value;
|
||
err = nvs_get_u8(nvs_handle, "day_start_hour", &value);
|
||
if (err == ESP_OK)
|
||
g_day_period.start_hour = value;
|
||
|
||
err = nvs_get_u8(nvs_handle, "day_start_min", &value);
|
||
if (err == ESP_OK)
|
||
g_day_period.start_minute = value;
|
||
|
||
err = nvs_get_u8(nvs_handle, "day_end_hour", &value);
|
||
if (err == ESP_OK)
|
||
g_day_period.end_hour = value;
|
||
|
||
err = nvs_get_u8(nvs_handle, "day_end_min", &value);
|
||
if (err == ESP_OK)
|
||
g_day_period.end_minute = value;
|
||
|
||
// 加载夜间时段
|
||
err = nvs_get_u8(nvs_handle, "night_start_hour", &value);
|
||
if (err == ESP_OK)
|
||
g_night_period.start_hour = value;
|
||
|
||
err = nvs_get_u8(nvs_handle, "night_start_min", &value);
|
||
if (err == ESP_OK)
|
||
g_night_period.start_minute = value;
|
||
|
||
err = nvs_get_u8(nvs_handle, "night_end_hour", &value);
|
||
if (err == ESP_OK)
|
||
g_night_period.end_hour = value;
|
||
|
||
err = nvs_get_u8(nvs_handle, "night_end_min", &value);
|
||
if (err == ESP_OK)
|
||
g_night_period.end_minute = value;
|
||
|
||
// 加载是否启用时间段控制
|
||
uint8_t use_time_period = 0;
|
||
err = nvs_get_u8(nvs_handle, "use_time_period", &use_time_period);
|
||
if (err == ESP_OK)
|
||
{
|
||
g_use_time_period = (use_time_period == 1);
|
||
}
|
||
|
||
nvs_close(nvs_handle);
|
||
ESP_LOGI(TAG, "Time period config loaded from NVS");
|
||
ESP_LOGI(TAG, "Day period: %02d:%02d - %02d:%02d",
|
||
g_day_period.start_hour, g_day_period.start_minute,
|
||
g_day_period.end_hour, g_day_period.end_minute);
|
||
ESP_LOGI(TAG, "Night period: %02d:%02d - %02d:%02d",
|
||
g_night_period.start_hour, g_night_period.start_minute,
|
||
g_night_period.end_hour, g_night_period.end_minute);
|
||
ESP_LOGI(TAG, "Use time period: %d", g_use_time_period);
|
||
}
|
||
|
||
static bool is_in_time_period(time_period_config_t *period)
|
||
{
|
||
time_t now = time(NULL);
|
||
if (now == (time_t)-1)
|
||
{
|
||
return false;
|
||
}
|
||
|
||
struct tm tm_now;
|
||
localtime_r(&now, &tm_now);
|
||
|
||
int current_minutes = tm_now.tm_hour * 60 + tm_now.tm_min;
|
||
int start_minutes = period->start_hour * 60 + period->start_minute;
|
||
int end_minutes = period->end_hour * 60 + period->end_minute;
|
||
|
||
// 处理跨越午夜的情况(如夜间时段 20:00-8:00)
|
||
if (end_minutes < start_minutes)
|
||
{
|
||
// 时间段跨越午夜
|
||
return (current_minutes >= start_minutes || current_minutes < end_minutes);
|
||
}
|
||
else
|
||
{
|
||
// 时间段在同一天内
|
||
return (current_minutes >= start_minutes && current_minutes < end_minutes);
|
||
}
|
||
}
|
||
|
||
void time_period_check_task(void *pvParameters)
|
||
{
|
||
(void)pvParameters;
|
||
ESP_LOGI(TAG, "时间段检查任务启动");
|
||
|
||
// 从NVS加载时间段配置
|
||
time_period_load_from_nvs();
|
||
|
||
while (1)
|
||
{
|
||
if (g_use_time_period)
|
||
{
|
||
bool in_day_period = is_in_time_period(&g_day_period);
|
||
bool in_night_period = is_in_time_period(&g_night_period);
|
||
|
||
ESP_LOGV(TAG, "时间段状态: 昼间=%d, 夜间=%d", in_day_period, in_night_period);
|
||
|
||
// 时间段控制逻辑
|
||
if (xControlFlagMutex != NULL && xSemaphoreTake(xControlFlagMutex, portMAX_DELAY) == pdTRUE)
|
||
{
|
||
// 夜间时段:关闭灯光,降低亮度
|
||
if (in_night_period)
|
||
{
|
||
if (light_source_control_flag)
|
||
{
|
||
light_source_control_flag = false;
|
||
ESP_LOGI(TAG, "夜间时段:关闭灯光");
|
||
}
|
||
if (led_brightness_value > 50)
|
||
{
|
||
led_brightness_value = 50;
|
||
ESP_LOGI(TAG, "夜间时段:降低亮度到50%");
|
||
}
|
||
}
|
||
// 昼间时段:可以开启灯光,亮度恢复
|
||
else if (in_day_period)
|
||
{
|
||
// 昼间时段不强制开启灯光,但允许用户手动开启
|
||
// 亮度可以恢复到较高值
|
||
if (led_brightness_value < 80)
|
||
{
|
||
led_brightness_value = 80;
|
||
ESP_LOGI(TAG, "昼间时段:亮度恢复到80%");
|
||
}
|
||
}
|
||
xSemaphoreGive(xControlFlagMutex);
|
||
}
|
||
|
||
// 更新MQTT状态
|
||
update_telemetry_and_report();
|
||
}
|
||
|
||
vTaskDelay(pdMS_TO_TICKS(10000)); // 每10秒检查一次
|
||
}
|
||
}
|
||
|
||
#ifdef __cplusplus
|
||
}
|
||
#endif
|