#include #include #include #include #include #include #include #include #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); // 发送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