Files
iot-bedroom-environment-con…/main/main.cpp
2026-03-31 21:52:29 +08:00

914 lines
32 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.
#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, "创建控制标志互斥锁失败");
}
// 初始化外设与模块stubsensor 需在 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);
// 发送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));
}
}
/**
* @brief 自动通风控制模式任务
* 监测空气质量当空气质量大于50时自动开启风扇并发送提醒
*/
/* ventilation_mode_task 已移除,保留 README 文档说明,不再在代码中保留未使用的静态函数 */
// MQ135 task removed; provide a short stub to avoid undefined references
void mq135_task(void *pvParameters)
{
ESP_LOGI("mq135_task", "mq135 task removed");
vTaskDelete(NULL);
}
// 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, "temperature_threshold", (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 = 28.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 = 280; // 28.0°C * 10
err = nvs_get_u32(nvs_handle, "temperature_threshold", &temp_threshold);
if (err == ESP_OK)
{
g_temperature_threshold = temp_threshold / 10.0f;
}
else
{
g_temperature_threshold = 28.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