mirror of
https://git.beihong.wang/wangbeihong/iot-bedroom-environment-controller.git
synced 2026-04-23 16:53:05 +08:00
能用,自动没测,不想改了,崩溃了
This commit is contained in:
@@ -17,3 +17,8 @@ device_message_t g_device_message;
|
||||
float g_temperature_threshold = 28.0f;
|
||||
bool g_cooling_mode_enabled = false;
|
||||
bool g_high_temp_alerted = false;
|
||||
|
||||
// 时间段配置(昼间/夜间)
|
||||
time_period_config_t g_day_period = {.start_hour = 8, .start_minute = 0, .end_hour = 20, .end_minute = 0};
|
||||
time_period_config_t g_night_period = {.start_hour = 20, .start_minute = 0, .end_hour = 8, .end_minute = 0};
|
||||
bool g_use_time_period = false; // 是否启用时间段控制
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
|
||||
// 时间段配置结构体
|
||||
typedef struct {
|
||||
uint8_t start_hour;
|
||||
uint8_t start_minute;
|
||||
uint8_t end_hour;
|
||||
uint8_t end_minute;
|
||||
} time_period_config_t;
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
@@ -28,6 +36,11 @@ extern float g_temperature_threshold;
|
||||
extern bool g_cooling_mode_enabled;
|
||||
extern bool g_high_temp_alerted;
|
||||
|
||||
// 时间段配置
|
||||
extern time_period_config_t g_day_period;
|
||||
extern time_period_config_t g_night_period;
|
||||
extern bool g_use_time_period;
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -57,6 +57,8 @@ void time_period_set(time_period_type_t period_type, uint8_t start_hour, uint8_t
|
||||
uint8_t end_hour, uint8_t end_minute);
|
||||
void cooling_mode_save_to_nvs(void);
|
||||
void cooling_mode_load_from_nvs(void);
|
||||
void time_period_save_to_nvs(void);
|
||||
void time_period_load_from_nvs(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
430
main/main.cpp
430
main/main.cpp
@@ -58,9 +58,17 @@ extern "C"
|
||||
|
||||
/* Forward declarations */
|
||||
static void mqtt_app_start(void);
|
||||
static void time_period_check_task(void *pvParameters);
|
||||
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 */
|
||||
|
||||
@@ -101,14 +109,81 @@ void mqtt_publish_feedback(void)
|
||||
|
||||
// mqtt_event_handler moved to mqtt_manager.c
|
||||
|
||||
// mqtt_app_start now delegates to mqtt_manager_start
|
||||
static void mqtt_app_start(void)
|
||||
// 优化后的代码
|
||||
static void safe_mqtt_start_task(void *pvParameters)
|
||||
{
|
||||
ESP_LOGI(TAG, "mqtt_app_start: delegating to mqtt_manager_start");
|
||||
(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_manager_start failed: %s", esp_err_to_name(err));
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -320,17 +395,6 @@ extern "C"
|
||||
|
||||
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);
|
||||
// 连接WIFI
|
||||
// ESP_ERROR_CHECK(example_connect());
|
||||
|
||||
// // Print out Access Point Information
|
||||
// wifi_ap_record_t ap_info;
|
||||
// ESP_ERROR_CHECK(esp_wifi_sta_get_ap_info(&ap_info));
|
||||
// ESP_LOGI(TAG, "--- Access Point Information ---");
|
||||
// ESP_LOG_BUFFER_HEX("MAC Address", ap_info.bssid, sizeof(ap_info.bssid));
|
||||
// ESP_LOG_BUFFER_CHAR("SSID", ap_info.ssid, sizeof(ap_info.ssid));
|
||||
// ESP_LOGI(TAG, "Primary Channel: %d", ap_info.primary);
|
||||
// ESP_LOGI(TAG, "RSSI: %d", ap_info.rssi);
|
||||
|
||||
// 初始化I2C总线
|
||||
ESP_ERROR_CHECK(i2c_master_init());
|
||||
@@ -377,26 +441,24 @@ extern "C"
|
||||
// MCU间的串口通信初始化
|
||||
serial_mcu_init();
|
||||
|
||||
|
||||
|
||||
mqtt_app_start(); // 启动 MQTT 客户端
|
||||
ESP_ERROR_CHECK(time_alarm_start());
|
||||
// 创建时间段检查任务
|
||||
xTaskCreate(time_period_check_task, "time_period_task", 4096, NULL, 5, NULL);
|
||||
// 创建降温模式任务
|
||||
xTaskCreate(cooling_mode_task, "cooling_mode_task", 4096, NULL, 5, NULL);
|
||||
// 自动通风与 MQ135 相关任务已移除
|
||||
// xTaskCreate(ventilation_mode_task, "ventilation_mode_task", 4096, NULL, 5, NULL);
|
||||
// xTaskCreate(mq135_task, "mq135_task", 4096, NULL, 5, NULL);
|
||||
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, "peripheral_control_task", 4096, NULL, 5, NULL);
|
||||
xTaskCreate(peripheral_control_task, "periph_ctrl_task", 3072, NULL, 5, NULL);
|
||||
|
||||
// 传感器任务由 sensors_init() 启动
|
||||
// 创建接收任务
|
||||
xTaskCreate(rx_task, "uart_rx_task", 4096, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
xTaskCreate(rx_task, "uart_rx_task", 3072, NULL, configMAX_PRIORITIES - 1, NULL);
|
||||
|
||||
while (1)
|
||||
{
|
||||
// 定期打印传感器数据
|
||||
print_sensor_data();
|
||||
|
||||
vTaskDelay(5000 / portTICK_PERIOD_MS);
|
||||
}
|
||||
}
|
||||
@@ -418,7 +480,6 @@ static void cooling_mode_task(void *pvParameters)
|
||||
cooling_mode_load_from_nvs();
|
||||
|
||||
// 高温阈值
|
||||
const float HIGH_TEMP_THRESHOLD = 48.0f;
|
||||
|
||||
while (1)
|
||||
{
|
||||
@@ -468,8 +529,8 @@ static void cooling_mode_task(void *pvParameters)
|
||||
}
|
||||
}
|
||||
|
||||
// 高温提醒:温度超过35°C
|
||||
if (current_temp > HIGH_TEMP_THRESHOLD)
|
||||
// 高温提醒:温度超过阈值
|
||||
if (current_temp > g_temperature_threshold)
|
||||
{
|
||||
if (!g_high_temp_alerted)
|
||||
{
|
||||
@@ -482,7 +543,7 @@ static void cooling_mode_task(void *pvParameters)
|
||||
xSemaphoreGive(xControlFlagMutex);
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "High temperature alert: %.1f°C (>40°C)", current_temp);
|
||||
ESP_LOGW(TAG, "High temperature alert: %.1f°C (>%.1f°C)", current_temp, g_temperature_threshold);
|
||||
|
||||
// 发送MQTT提醒消息
|
||||
if (g_mqtt_client != NULL)
|
||||
@@ -498,12 +559,23 @@ static void cooling_mode_task(void *pvParameters)
|
||||
cJSON_AddNumberToObject(data_obj, "temperature", roundf(current_temp * 10) / 10);
|
||||
cJSON_AddItemToObject(root, "data", data_obj);
|
||||
|
||||
char *json_message = cJSON_Print(root);
|
||||
if (json_message)
|
||||
// 尝试使用预分配缓冲区避免堆分配
|
||||
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, json_message, 0, 0, 0);
|
||||
ESP_LOGI(TAG, "High temp alert sent: %s", json_message);
|
||||
free(json_message);
|
||||
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);
|
||||
}
|
||||
@@ -557,37 +629,285 @@ extern "C"
|
||||
|
||||
void cooling_mode_save_to_nvs(void)
|
||||
{
|
||||
// TODO: 实现 NVS 存储,目前为最小存根
|
||||
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)
|
||||
{
|
||||
// TODO: 从 NVS 加载设置。目前使用默认值
|
||||
g_cooling_mode_enabled = false;
|
||||
g_temperature_threshold = 28.0f;
|
||||
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)
|
||||
{
|
||||
// TODO: 保存时间段设置或通知其他模块。当前为占位实现。
|
||||
(void)period_type;
|
||||
(void)start_hour;
|
||||
(void)start_minute;
|
||||
(void)end_hour;
|
||||
(void)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
|
||||
|
||||
static void time_period_check_task(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
while (1)
|
||||
{
|
||||
// 占位:以后在此检查时间段并执行对应操作
|
||||
vTaskDelay(pdMS_TO_TICKS(10000));
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
|
||||
#include "mqtt_manager.h"
|
||||
#include "esp_log.h"
|
||||
#include "mqtt_client.h"
|
||||
@@ -17,6 +16,7 @@
|
||||
#include "common.h"
|
||||
#include "time_alarm.h"
|
||||
#include "app_state.h"
|
||||
#include "vars.h"
|
||||
|
||||
static const char *TAG = "mqtt_manager";
|
||||
|
||||
@@ -30,8 +30,19 @@ static const char *TAG = "mqtt_manager";
|
||||
#define MQTT_NOTIFY_TOPIC "topic/control/esp32_iothome_001"
|
||||
#define MQTT_UNSUBSCRIBE_TOPIC "topic/control/esp32_iothome_001"
|
||||
|
||||
#define MQTT_MAX_RETRY_COUNT 5
|
||||
#define MQTT_RETRY_DELAY_MS 5000
|
||||
|
||||
esp_mqtt_client_handle_t g_mqtt_client = NULL;
|
||||
|
||||
// 全局静态 JSON 缓冲区(4KB),用于所有 JSON 打印,防止堆碎片化
|
||||
static char GLOBAL_MQTT_JSON_BUFFER[4096];
|
||||
static bool g_mqtt_connected = false;
|
||||
static int g_mqtt_retry_count = 0;
|
||||
|
||||
// 静态任务缓冲区:使用静态创建避免在堆上分配任务栈/TCB
|
||||
|
||||
|
||||
// app_state.h 提供共享状态的 extern 声明;保留跨模块函数声明
|
||||
extern sensor_data_t g_sensor_data;
|
||||
extern void alarm_set_time(uint8_t alarm_idx, uint8_t hour, uint8_t minute, uint8_t second);
|
||||
@@ -58,6 +69,7 @@ void mqtt_publish_task(void *pvParameters)
|
||||
g_device_message.state.standby_mode = false;
|
||||
g_device_message.state.error_code = 0;
|
||||
g_device_message.state.auto_mode = false; // 默认手动模式
|
||||
set_var_mode("Manual"); // 初始化屏幕模式显示
|
||||
|
||||
g_device_message.telemetry.temperature = 0;
|
||||
g_device_message.telemetry.humidity = 0;
|
||||
@@ -73,31 +85,37 @@ void mqtt_publish_task(void *pvParameters)
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (xSensorDataMutex != NULL && xSemaphoreTake(xSensorDataMutex, portMAX_DELAY) == pdTRUE)
|
||||
// 只有在MQTT连接时才更新传感器数据并生成JSON消息
|
||||
if (g_mqtt_connected)
|
||||
{
|
||||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
if (xSensorDataMutex != NULL && xSemaphoreTake(xSensorDataMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
if (g_sensor_data.ahtxx_valid)
|
||||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
g_device_message.telemetry.temperature = g_sensor_data.temperature;
|
||||
g_device_message.telemetry.humidity = g_sensor_data.humidity;
|
||||
if (g_sensor_data.ahtxx_valid)
|
||||
{
|
||||
g_device_message.telemetry.temperature = g_sensor_data.temperature;
|
||||
g_device_message.telemetry.humidity = g_sensor_data.humidity;
|
||||
}
|
||||
if (g_sensor_data.bh1750_valid)
|
||||
{
|
||||
g_device_message.telemetry.light_intensity = g_sensor_data.lux;
|
||||
}
|
||||
if (g_sensor_data.sgp30_valid)
|
||||
{
|
||||
g_device_message.telemetry.co2_ppm = g_sensor_data.co2_ppm;
|
||||
g_device_message.telemetry.tvoc_ppb = g_sensor_data.tvoc_ppb;
|
||||
}
|
||||
xSemaphoreGive(xMqttMessageMutex);
|
||||
}
|
||||
if (g_sensor_data.bh1750_valid)
|
||||
{
|
||||
g_device_message.telemetry.light_intensity = g_sensor_data.lux;
|
||||
}
|
||||
if (g_sensor_data.sgp30_valid)
|
||||
{
|
||||
g_device_message.telemetry.co2_ppm = g_sensor_data.co2_ppm;
|
||||
g_device_message.telemetry.tvoc_ppb = g_sensor_data.tvoc_ppb;
|
||||
}
|
||||
xSemaphoreGive(xMqttMessageMutex);
|
||||
xSemaphoreGive(xSensorDataMutex);
|
||||
}
|
||||
xSemaphoreGive(xSensorDataMutex);
|
||||
}
|
||||
|
||||
char *json_message = NULL;
|
||||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
bool json_heap_alloc = false;
|
||||
// 只有在MQTT连接时才生成JSON消息
|
||||
if (g_mqtt_connected && xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
time_t now;
|
||||
time(&now);
|
||||
@@ -145,22 +163,40 @@ void mqtt_publish_task(void *pvParameters)
|
||||
cJSON_AddNumberToObject(telemetry_obj, "tvoc_ppb", g_device_message.telemetry.tvoc_ppb);
|
||||
cJSON_AddItemToObject(data_obj, "telemetry", telemetry_obj);
|
||||
|
||||
json_message = cJSON_Print(root);
|
||||
// 优先将 JSON 打印到全局静态缓冲区,避免堆分配;若缓冲不足再回退到 cJSON_Print
|
||||
bool printed = cJSON_PrintPreallocated(root, GLOBAL_MQTT_JSON_BUFFER, sizeof(GLOBAL_MQTT_JSON_BUFFER), 0);
|
||||
if (printed)
|
||||
{
|
||||
json_message = GLOBAL_MQTT_JSON_BUFFER; // 直接使用全局内存
|
||||
json_heap_alloc = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
json_message = cJSON_Print(root);
|
||||
json_heap_alloc = (json_message != NULL);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
|
||||
xSemaphoreGive(xMqttMessageMutex);
|
||||
}
|
||||
|
||||
if (json_message != NULL)
|
||||
if (json_message != NULL)
|
||||
{
|
||||
ESP_LOGI(TAG, "准备发布MQTT消息:");
|
||||
ESP_LOGI(TAG, "Topic: %s", MQTT_PUBLISH_TOPIC_QOS0);
|
||||
if (g_mqtt_client != NULL)
|
||||
if (g_mqtt_client != NULL && g_mqtt_connected)
|
||||
{
|
||||
int msg_id = esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message, 0, 0, 0);
|
||||
ESP_LOGI(TAG, "MQTT消息已发布, msg_id=%d", msg_id);
|
||||
}
|
||||
free(json_message);
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "MQTT客户端未连接,跳过发布");
|
||||
}
|
||||
if (json_heap_alloc)
|
||||
{
|
||||
free(json_message);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t delay_ms = 3000;
|
||||
@@ -179,7 +215,7 @@ void mqtt_publish_task(void *pvParameters)
|
||||
|
||||
void mqtt_manager_publish_feedback(void)
|
||||
{
|
||||
if (g_mqtt_client == NULL)
|
||||
if (g_mqtt_client == NULL || !g_mqtt_connected)
|
||||
return;
|
||||
|
||||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
@@ -243,13 +279,28 @@ void mqtt_manager_publish_feedback(void)
|
||||
}
|
||||
cJSON_AddItemToObject(data_obj, "alarms", alarms_obj);
|
||||
|
||||
char *json_message = cJSON_Print(root);
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (json_message)
|
||||
// 优先写入静态缓冲,减少堆分配
|
||||
char *json_message_fb = NULL;
|
||||
bool json_fb_heap = false;
|
||||
bool printed_fb = cJSON_PrintPreallocated(root, GLOBAL_MQTT_JSON_BUFFER, sizeof(GLOBAL_MQTT_JSON_BUFFER), 0);
|
||||
if (printed_fb)
|
||||
{
|
||||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message, 0, 0, 0);
|
||||
free(json_message);
|
||||
json_message_fb = GLOBAL_MQTT_JSON_BUFFER;
|
||||
json_fb_heap = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
json_message_fb = cJSON_Print(root);
|
||||
json_fb_heap = (json_message_fb != NULL);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
if (json_message_fb)
|
||||
{
|
||||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message_fb, 0, 0, 0);
|
||||
if (json_fb_heap)
|
||||
{
|
||||
free(json_message_fb);
|
||||
}
|
||||
}
|
||||
|
||||
xSemaphoreGive(xMqttMessageMutex);
|
||||
@@ -259,7 +310,7 @@ void mqtt_manager_publish_feedback(void)
|
||||
// 发布闹钟状态消息
|
||||
void mqtt_manager_publish_alarm_status(int alarm_idx, const char *alarm_status, bool triggered)
|
||||
{
|
||||
if (g_mqtt_client == NULL)
|
||||
if (g_mqtt_client == NULL || !g_mqtt_connected)
|
||||
return;
|
||||
|
||||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
@@ -307,14 +358,29 @@ void mqtt_manager_publish_alarm_status(int alarm_idx, const char *alarm_status,
|
||||
cJSON_AddStringToObject(telemetry_obj, "buzzer_state", g_device_message.telemetry.buzzer_state);
|
||||
cJSON_AddItemToObject(data_obj, "telemetry", telemetry_obj);
|
||||
|
||||
char *json_message = cJSON_Print(root);
|
||||
// 优先写入静态缓冲,减少堆分配
|
||||
char *json_message_alarm = NULL;
|
||||
bool json_alarm_heap = false;
|
||||
bool printed_alarm = cJSON_PrintPreallocated(root, GLOBAL_MQTT_JSON_BUFFER, sizeof(GLOBAL_MQTT_JSON_BUFFER), 0);
|
||||
if (printed_alarm)
|
||||
{
|
||||
json_message_alarm = GLOBAL_MQTT_JSON_BUFFER;
|
||||
json_alarm_heap = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
json_message_alarm = cJSON_Print(root);
|
||||
json_alarm_heap = (json_message_alarm != NULL);
|
||||
}
|
||||
cJSON_Delete(root);
|
||||
|
||||
if (json_message)
|
||||
if (json_message_alarm)
|
||||
{
|
||||
ESP_LOGI(TAG, "发布闹钟状态: alarm%d %s (triggered=%d)", alarm_idx + 1, alarm_status, triggered);
|
||||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message, 0, 0, 0);
|
||||
free(json_message);
|
||||
esp_mqtt_client_publish(g_mqtt_client, MQTT_PUBLISH_TOPIC_QOS0, json_message_alarm, 0, 0, 0);
|
||||
if (json_alarm_heap)
|
||||
{
|
||||
free(json_message_alarm);
|
||||
}
|
||||
}
|
||||
|
||||
xSemaphoreGive(xMqttMessageMutex);
|
||||
@@ -332,12 +398,31 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
|
||||
{
|
||||
case MQTT_EVENT_CONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_CONNECTED");
|
||||
g_mqtt_connected = true;
|
||||
g_mqtt_retry_count = 0; // 重置重试计数器
|
||||
msg_id = esp_mqtt_client_subscribe(client, MQTT_NOTIFY_TOPIC, 2);
|
||||
ESP_LOGI(TAG, "sent subscribe successful, msg_id=%d", msg_id);
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_DISCONNECTED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DISCONNECTED");
|
||||
g_mqtt_connected = false;
|
||||
|
||||
// 如果不是手动停止,尝试重连
|
||||
if (g_mqtt_client != NULL && g_mqtt_retry_count < MQTT_MAX_RETRY_COUNT)
|
||||
{
|
||||
g_mqtt_retry_count++;
|
||||
ESP_LOGI(TAG, "MQTT断开连接,%d秒后尝试重连 (第%d次)",
|
||||
MQTT_RETRY_DELAY_MS/1000, g_mqtt_retry_count);
|
||||
|
||||
// 延迟后重连
|
||||
vTaskDelay(pdMS_TO_TICKS(MQTT_RETRY_DELAY_MS));
|
||||
esp_mqtt_client_reconnect(g_mqtt_client);
|
||||
}
|
||||
else if (g_mqtt_retry_count >= MQTT_MAX_RETRY_COUNT)
|
||||
{
|
||||
ESP_LOGW(TAG, "MQTT重连次数已达上限(%d次),停止重试", MQTT_MAX_RETRY_COUNT);
|
||||
}
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_SUBSCRIBED:
|
||||
@@ -485,15 +570,17 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
|
||||
if (auto_mode_obj != NULL && cJSON_IsBool(auto_mode_obj))
|
||||
{
|
||||
bool auto_mode = cJSON_IsTrue(auto_mode_obj);
|
||||
ESP_LOGI(TAG, "Setting auto_mode to: %s", auto_mode ? "true (自动模式)" : "false (手动模式)");
|
||||
ESP_LOGI(TAG, "Setting auto_mode to: %s", auto_mode ? "true (Auto)" : "false (Manual)");
|
||||
|
||||
if (xMqttMessageMutex != NULL && xSemaphoreTake(xMqttMessageMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
g_device_message.state.auto_mode = auto_mode;
|
||||
if (auto_mode) {
|
||||
strcpy(g_device_message.state.current_mode, "auto");
|
||||
set_var_mode("Auto");
|
||||
} else {
|
||||
strcpy(g_device_message.state.current_mode, "manual");
|
||||
set_var_mode("Manual");
|
||||
}
|
||||
xSemaphoreGive(xMqttMessageMutex);
|
||||
}
|
||||
@@ -937,6 +1024,16 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
|
||||
{
|
||||
mqtt_manager_publish_feedback();
|
||||
}
|
||||
|
||||
// 处理时间段启用控制
|
||||
cJSON *use_time_period_obj = cJSON_GetObjectItem(controls_obj, "use_time_period");
|
||||
if (use_time_period_obj != NULL && cJSON_IsBool(use_time_period_obj))
|
||||
{
|
||||
g_use_time_period = cJSON_IsTrue(use_time_period_obj) ? true : false;
|
||||
time_period_save_to_nvs();
|
||||
ESP_LOGI(TAG, "时间段控制 %s", g_use_time_period ? "启用" : "禁用");
|
||||
mqtt_manager_publish_feedback();
|
||||
}
|
||||
|
||||
cJSON *temp_threshold_obj = cJSON_GetObjectItem(controls_obj, "temperature_threshold");
|
||||
if (temp_threshold_obj != NULL && cJSON_IsNumber(temp_threshold_obj))
|
||||
@@ -969,15 +1066,52 @@ static void mqtt_event_handler(void *handler_args, esp_event_base_t base, int32_
|
||||
ESP_LOGI(TAG, "Last tls stack error number: 0x%x", event->error_handle->esp_tls_stack_err);
|
||||
ESP_LOGI(TAG, "Last captured errno : %d (%s)", event->error_handle->esp_transport_sock_errno,
|
||||
strerror(event->error_handle->esp_transport_sock_errno));
|
||||
|
||||
// 检查是否是DNS解析错误
|
||||
if (event->error_handle->esp_tls_last_esp_err == 0x8002 || // ESP_ERR_ESP_TLS_CANNOT_RESOLVE_HOSTNAME
|
||||
event->error_handle->esp_transport_sock_errno == 202 || // EAI_NONAME
|
||||
event->error_handle->esp_transport_sock_errno == 203) // EAI_SERVICE
|
||||
{
|
||||
ESP_LOGE(TAG, "DNS解析失败!请检查MQTT服务器地址: %s", MQTT_BROKER_URL);
|
||||
ESP_LOGE(TAG, "可能的解决方案:");
|
||||
ESP_LOGE(TAG, "1. 检查域名 %s 是否正确", "beihong.wang");
|
||||
ESP_LOGE(TAG, "2. 检查网络连接");
|
||||
ESP_LOGE(TAG, "3. 尝试使用IP地址代替域名");
|
||||
}
|
||||
}
|
||||
else if (event->error_handle->error_type == MQTT_ERROR_TYPE_CONNECTION_REFUSED)
|
||||
{
|
||||
ESP_LOGI(TAG, "Connection refused error: 0x%x", event->error_handle->connect_return_code);
|
||||
ESP_LOGI(TAG, "Possible reasons: wrong username/password, server refused connection");
|
||||
}
|
||||
else
|
||||
{
|
||||
ESP_LOGW(TAG, "Unknown error type: 0x%x", event->error_handle->error_type);
|
||||
}
|
||||
|
||||
// 记录错误后尝试重连
|
||||
if (g_mqtt_client != NULL && g_mqtt_retry_count < MQTT_MAX_RETRY_COUNT)
|
||||
{
|
||||
g_mqtt_retry_count++;
|
||||
ESP_LOGI(TAG, "MQTT error, will retry in %d seconds (attempt %d/%d)",
|
||||
MQTT_RETRY_DELAY_MS/1000, g_mqtt_retry_count, MQTT_MAX_RETRY_COUNT);
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(MQTT_RETRY_DELAY_MS));
|
||||
esp_mqtt_client_reconnect(g_mqtt_client);
|
||||
}
|
||||
break;
|
||||
|
||||
case MQTT_EVENT_ANY:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_ANY");
|
||||
break;
|
||||
case MQTT_EVENT_BEFORE_CONNECT:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_BEFORE_CONNECT");
|
||||
break;
|
||||
case MQTT_EVENT_DELETED:
|
||||
ESP_LOGI(TAG, "MQTT_EVENT_DELETED");
|
||||
break;
|
||||
case MQTT_USER_EVENT:
|
||||
ESP_LOGI(TAG, "MQTT_USER_EVENT");
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -1014,7 +1148,7 @@ esp_err_t mqtt_manager_start(void)
|
||||
|
||||
g_mqtt_client = client;
|
||||
|
||||
xTaskCreate(mqtt_publish_task, "mqtt_publish_task", 4096, NULL, 5, NULL);
|
||||
xTaskCreate(mqtt_publish_task, "mqtt_publish_task", 3046, NULL, 5, NULL);
|
||||
return ESP_OK;
|
||||
}
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
#include "bh1750.h"
|
||||
#include "sgp30/sgp30.h"
|
||||
#include "driver/i2c_master.h"
|
||||
|
||||
#include "vars.h"
|
||||
|
||||
static const char *TAG = "sensors";
|
||||
|
||||
@@ -112,10 +112,18 @@ void i2c0_ahtxx_task(void *pvParameters)
|
||||
g_sensor_data.ahtxx_valid = true;
|
||||
xSemaphoreGive(xSensorDataMutex);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "AHTxx: temperature=%.1f°C, humidity=%.1f%%", temperature, humidity);
|
||||
|
||||
// ESP_LOGI(TAG, "AHTxx: temperature=%.1f°C, humidity=%.1f%%", temperature, humidity);
|
||||
|
||||
// 更新屏幕显示变量
|
||||
char temp_str[16];
|
||||
char humidity_str[16];
|
||||
snprintf(temp_str, sizeof(temp_str), "%.1f", temperature);
|
||||
snprintf(humidity_str, sizeof(humidity_str), "%.1f", humidity);
|
||||
set_var_tempture(temp_str);
|
||||
set_var_humity(humidity_str);
|
||||
}
|
||||
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
@@ -141,20 +149,20 @@ void i2c0_bh1750_task(void *pvParameters)
|
||||
}
|
||||
}
|
||||
|
||||
// 设置测量模式为连续高分辨率模式
|
||||
ret = bh1750_set_measure_mode(bh1750_handle, BH1750_CONTINUE_1LX_RES);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "BH1750 set measure mode failed, err=0x%x", ret);
|
||||
}
|
||||
|
||||
// 上电
|
||||
// 先上电
|
||||
ret = bh1750_power_on(bh1750_handle);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "BH1750 power on failed, err=0x%x", ret);
|
||||
}
|
||||
|
||||
// 设置测量模式为连续高分辨率模式(必须在power_on之后)
|
||||
ret = bh1750_set_measure_mode(bh1750_handle, BH1750_CONTINUE_1LX_RES);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "BH1750 set measure mode failed, err=0x%x", ret);
|
||||
}
|
||||
|
||||
float lux;
|
||||
while (1)
|
||||
{
|
||||
@@ -176,10 +184,15 @@ void i2c0_bh1750_task(void *pvParameters)
|
||||
g_sensor_data.bh1750_valid = true;
|
||||
xSemaphoreGive(xSensorDataMutex);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "BH1750: illuminance=%.1f lux", lux);
|
||||
|
||||
// ESP_LOGI(TAG, "BH1750: illuminance=%.1f lux", lux);
|
||||
|
||||
// 更新屏幕显示变量
|
||||
char light_str[16];
|
||||
snprintf(light_str, sizeof(light_str), "%.1f", lux);
|
||||
set_var_light_value(light_str);
|
||||
}
|
||||
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
@@ -187,13 +200,13 @@ void i2c0_bh1750_task(void *pvParameters)
|
||||
void i2c0_sgp30_task(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
|
||||
|
||||
// SGP30 配置
|
||||
sgp30_config_t sgp30_config = {
|
||||
.i2c_master_port = I2C_MASTER_NUM,
|
||||
.i2c_address = 0x58, // SGP30 默认地址
|
||||
};
|
||||
|
||||
|
||||
// 初始化 SGP30
|
||||
esp_err_t ret = sgp30_init(&sgp30_config);
|
||||
if (ret != ESP_OK)
|
||||
@@ -209,16 +222,16 @@ void i2c0_sgp30_task(void *pvParameters)
|
||||
vTaskDelay(pdMS_TO_TICKS(5000));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ESP_LOGI(TAG, "SGP30 initialized successfully");
|
||||
|
||||
|
||||
uint16_t co2_ppm, tvoc_ppb;
|
||||
while (1)
|
||||
{
|
||||
ret = sgp30_read_measurements(&co2_ppm, &tvoc_ppb);
|
||||
if (ret != ESP_OK)
|
||||
{
|
||||
ESP_LOGE(TAG, "SGP30 read failed, err=0x%x", ret);
|
||||
|
||||
if (xSensorDataMutex != NULL && xSemaphoreTake(xSensorDataMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
g_sensor_data.sgp30_valid = false;
|
||||
@@ -234,10 +247,18 @@ void i2c0_sgp30_task(void *pvParameters)
|
||||
g_sensor_data.sgp30_valid = true;
|
||||
xSemaphoreGive(xSensorDataMutex);
|
||||
}
|
||||
|
||||
ESP_LOGI(TAG, "SGP30: CO2=%d ppm, TVOC=%d ppb", co2_ppm, tvoc_ppb);
|
||||
|
||||
// ESP_LOGI(TAG, "SGP30: CO2=%d ppm, TVOC=%d ppb", co2_ppm, tvoc_ppb);
|
||||
|
||||
// 更新屏幕显示变量
|
||||
char co2_str[16];
|
||||
char voc_str[16];
|
||||
snprintf(co2_str, sizeof(co2_str), "%d", co2_ppm);
|
||||
snprintf(voc_str, sizeof(voc_str), "%d", tvoc_ppb);
|
||||
set_var_co2_value(co2_str);
|
||||
set_var_voc_value(voc_str);
|
||||
}
|
||||
|
||||
|
||||
// SGP30 建议每秒读取一次
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
}
|
||||
@@ -255,15 +276,6 @@ void get_sensor_data(sensor_data_t *data)
|
||||
}
|
||||
}
|
||||
|
||||
void print_sensor_data(void)
|
||||
{
|
||||
if (xSensorDataMutex != NULL && xSemaphoreTake(xSensorDataMutex, portMAX_DELAY) == pdTRUE)
|
||||
{
|
||||
// 传感器日志已移除,保持占位
|
||||
xSemaphoreGive(xSensorDataMutex);
|
||||
}
|
||||
}
|
||||
|
||||
esp_err_t sensors_init(void)
|
||||
{
|
||||
esp_err_t err = i2c_master_init();
|
||||
@@ -273,17 +285,23 @@ esp_err_t sensors_init(void)
|
||||
return err;
|
||||
}
|
||||
|
||||
BaseType_t ok = xTaskCreate(i2c0_ahtxx_task, "i2c0_ahtxx_task", 4096, NULL, 5, NULL);
|
||||
ESP_LOGI(TAG, "创建传感器任务前堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
/* i2c0_ahtxx_task 需要较大栈,防止栈溢出(观察到任务崩溃),恢复为 4096 */
|
||||
BaseType_t ok = xTaskCreate(i2c0_ahtxx_task, "i2c0_ahtxx_task", 3048, NULL, 5, NULL);
|
||||
ESP_LOGI(TAG, "创建 i2c0_ahtxx_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
if (ok != pdPASS)
|
||||
{
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ok = xTaskCreate(i2c0_bh1750_task, "i2c0_bh1750_task", 4096, NULL, 5, NULL);
|
||||
// 降低光照/SGP30任务栈到 1536,释放堆空间(如遇溢出可再调大)
|
||||
ok = xTaskCreate(i2c0_bh1750_task, "i2c0_bh1750_task", 3072, NULL, 5, NULL);
|
||||
ESP_LOGI(TAG, "创建 i2c0_bh1750_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
if (ok != pdPASS)
|
||||
{
|
||||
return ESP_ERR_NO_MEM;
|
||||
}
|
||||
ok = xTaskCreate(i2c0_sgp30_task, "i2c0_sgp30_task", 4096, NULL, 5, NULL);
|
||||
ok = xTaskCreate(i2c0_sgp30_task, "i2c0_sgp30_task", 2048, NULL, 5, NULL);
|
||||
ESP_LOGI(TAG, "创建 i2c0_sgp30_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
if (ok != pdPASS)
|
||||
{
|
||||
return ESP_ERR_NO_MEM;
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
#include "time_alarm.h"
|
||||
#include "esp_log.h"
|
||||
#include "esp_timer.h"
|
||||
#include "nvs_flash.h"
|
||||
|
||||
#include "freertos/FreeRTOS.h"
|
||||
#include "freertos/task.h"
|
||||
@@ -10,9 +12,13 @@
|
||||
#include "mqtt_manager.h"
|
||||
#include "common.h"
|
||||
#include "app_state.h"
|
||||
#include "vars.h"
|
||||
#include "wifi-connect.h"
|
||||
|
||||
static const char *TAG = "time_alarm";
|
||||
|
||||
#define SNTP_TIME_VALID_UNIX_TS 1700000000
|
||||
|
||||
typedef struct {
|
||||
uint8_t hour;
|
||||
uint8_t minute;
|
||||
@@ -31,64 +37,74 @@ extern bool servo_control_flag;
|
||||
extern bool light_source_control_flag;
|
||||
extern uint8_t led_brightness_value;
|
||||
|
||||
// 来自 app_state.h 的外部符号
|
||||
|
||||
// 本地静态函数声明
|
||||
static void alarm_save_to_nvs(void);
|
||||
extern SemaphoreHandle_t xMqttMessageMutex;
|
||||
extern device_message_t g_device_message;
|
||||
|
||||
// mqtt_manager 提供的反馈接口
|
||||
extern void mqtt_manager_publish_feedback(void);
|
||||
|
||||
static void local_sntp_init(void)
|
||||
static void wait_for_time_sync(void)
|
||||
{
|
||||
int retry = 0;
|
||||
int max_retry = 30;
|
||||
|
||||
// 等待WiFi连接
|
||||
while (retry < max_retry)
|
||||
{
|
||||
if (wifi_connect_get_status() == WIFI_CONNECT_STATUS_CONNECTED)
|
||||
{
|
||||
ESP_LOGI(TAG, "WiFi已连接,开始初始化SNTP");
|
||||
break;
|
||||
}
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
retry++;
|
||||
}
|
||||
|
||||
if (retry >= max_retry)
|
||||
{
|
||||
ESP_LOGW(TAG, "WiFi连接超时,跳过SNTP时间同步");
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化SNTP
|
||||
esp_sntp_setoperatingmode(SNTP_OPMODE_POLL);
|
||||
esp_sntp_setservername(0, "cn.pool.ntp.org");
|
||||
esp_sntp_setservername(1, "ntp1.aliyun.com");
|
||||
esp_sntp_init();
|
||||
}
|
||||
|
||||
static void wait_for_time_sync(void)
|
||||
{
|
||||
time_t now = 0;
|
||||
struct tm timeinfo = {0};
|
||||
int retry = 0;
|
||||
const int max_retry = 30; // 增加重试次数到30次(30秒)
|
||||
|
||||
ESP_LOGI(TAG, "等待SNTP时间同步...");
|
||||
|
||||
while (1)
|
||||
// 等待时间同步(time()返回有效时间戳即表示同步完成)
|
||||
retry = 0;
|
||||
max_retry = 30;
|
||||
while (retry < max_retry)
|
||||
{
|
||||
time(&now);
|
||||
localtime_r(&now, &timeinfo);
|
||||
|
||||
// 检查时间是否有效(年份 > 2016)
|
||||
if (timeinfo.tm_year > (2016 - 1900))
|
||||
time_t now = time(NULL);
|
||||
if (now >= SNTP_TIME_VALID_UNIX_TS)
|
||||
{
|
||||
ESP_LOGI(TAG, "SNTP时间同步成功!当前时间: %04d-%02d-%02d %02d:%02d:%02d",
|
||||
struct tm timeinfo;
|
||||
localtime_r(&now, &timeinfo);
|
||||
ESP_LOGI(TAG, "SNTP时间同步成功: %04d-%02d-%02d %02d:%02d:%02d",
|
||||
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
|
||||
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
if (retry % 5 == 0) // 每5秒打印一次状态
|
||||
if (retry % 5 == 0)
|
||||
{
|
||||
ESP_LOGI(TAG, "等待SNTP同步... 已等待 %d 秒", retry);
|
||||
ESP_LOGI(TAG, "等待SNTP时间同步... %d秒", retry);
|
||||
}
|
||||
|
||||
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||
retry++;
|
||||
|
||||
if (retry >= max_retry)
|
||||
{
|
||||
ESP_LOGW(TAG, "SNTP时间同步失败,使用系统默认时间");
|
||||
// 设置一个默认时间,避免卡住
|
||||
now = 1704067200; // 2024-01-01 00:00:00
|
||||
localtime_r(&now, &timeinfo);
|
||||
ESP_LOGI(TAG, "使用默认时间: %04d-%02d-%02d %02d:%02d:%02d",
|
||||
timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday,
|
||||
timeinfo.tm_hour, timeinfo.tm_min, timeinfo.tm_sec);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGW(TAG, "SNTP时间同步超时");
|
||||
}
|
||||
|
||||
// 对外包装:等待 SNTP 同步完成或超时
|
||||
void time_alarm_wait_for_sntp_sync(void)
|
||||
{
|
||||
wait_for_time_sync();
|
||||
}
|
||||
|
||||
void alarm_set_time(uint8_t alarm_idx, uint8_t hour, uint8_t minute, uint8_t second)
|
||||
@@ -100,6 +116,9 @@ void alarm_set_time(uint8_t alarm_idx, uint8_t hour, uint8_t minute, uint8_t sec
|
||||
g_alarms[alarm_idx].second = second;
|
||||
g_alarms[alarm_idx].triggered = false;
|
||||
ESP_LOGI(TAG, "Alarm[%d] time set to %02d:%02d:%02d, enable=%d", alarm_idx, hour, minute, second, g_alarms[alarm_idx].enable ? 1 : 0);
|
||||
|
||||
// 保存到NVS
|
||||
alarm_save_to_nvs();
|
||||
}
|
||||
|
||||
void alarm_set_enable(uint8_t alarm_idx, bool enable)
|
||||
@@ -110,6 +129,9 @@ void alarm_set_enable(uint8_t alarm_idx, bool enable)
|
||||
g_alarms[alarm_idx].triggered = false;
|
||||
ESP_LOGI(TAG, "Alarm[%d] enable=%d, time=%02d:%02d:%02d", alarm_idx, enable ? 1 : 0,
|
||||
g_alarms[alarm_idx].hour, g_alarms[alarm_idx].minute, g_alarms[alarm_idx].second);
|
||||
|
||||
// 保存到NVS
|
||||
alarm_save_to_nvs();
|
||||
}
|
||||
|
||||
void alarm_disable_all(void)
|
||||
@@ -119,6 +141,9 @@ void alarm_disable_all(void)
|
||||
g_alarms[i].enable = false;
|
||||
g_alarms[i].triggered = false;
|
||||
}
|
||||
|
||||
// 保存到NVS
|
||||
alarm_save_to_nvs();
|
||||
}
|
||||
|
||||
// 获取闹钟状态信息
|
||||
@@ -193,8 +218,11 @@ static void alarm_trigger_action(int idx)
|
||||
|
||||
// 创建自动停止任务(延迟10秒后停止)
|
||||
TaskHandle_t stop_task_handle = NULL;
|
||||
// 增加栈大小到4096,因为cJSON操作需要较多栈空间
|
||||
BaseType_t result = xTaskCreate(alarm_auto_stop_task, "alarm_stop_task", 4096, (void *)idx, 3, &stop_task_handle);
|
||||
// 调整栈为2048以节省内存(保守测试)并记录堆情况
|
||||
ESP_LOGI(TAG, "创建 alarm_auto_stop_task 前堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
// 降低自动停止任务栈到 1536,释放堆空间(如遇溢出可再调大)
|
||||
BaseType_t result = xTaskCreate(alarm_auto_stop_task, "alarm_stop_task", 1536, (void *)idx, 3, &stop_task_handle);
|
||||
ESP_LOGI(TAG, "创建 alarm_auto_stop_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
|
||||
if (result == pdPASS) {
|
||||
ESP_LOGI(TAG, "Auto-stop task created for alarm %d", idx);
|
||||
@@ -207,7 +235,6 @@ static void alarm_task(void *pvParameters)
|
||||
{
|
||||
(void)pvParameters;
|
||||
ESP_LOGI(TAG, "alarm task started");
|
||||
local_sntp_init();
|
||||
wait_for_time_sync();
|
||||
|
||||
// 打印当前设置的闹钟状态
|
||||
@@ -231,6 +258,11 @@ static void alarm_task(void *pvParameters)
|
||||
ESP_LOGI(TAG, "当前系统时间: %02d:%02d:%02d", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||||
}
|
||||
|
||||
// 每秒更新屏幕时间显示
|
||||
char time_str[16];
|
||||
snprintf(time_str, sizeof(time_str), "%02d:%02d:%02d", tm_now.tm_hour, tm_now.tm_min, tm_now.tm_sec);
|
||||
set_var_time(time_str);
|
||||
|
||||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||||
{
|
||||
if (g_alarms[i].enable)
|
||||
@@ -265,6 +297,68 @@ static void alarm_task(void *pvParameters)
|
||||
}
|
||||
}
|
||||
|
||||
static void alarm_save_to_nvs(void)
|
||||
{
|
||||
nvs_handle_t nvs_handle;
|
||||
esp_err_t err = nvs_open("alarm_config", NVS_READWRITE, &nvs_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to open NVS for alarm config: %s", esp_err_to_name(err));
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ALARM_MAX_NUM; i++) {
|
||||
char key[16];
|
||||
snprintf(key, sizeof(key), "alarm%d_hour", i);
|
||||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].hour);
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_min", i);
|
||||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].minute);
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_sec", i);
|
||||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].second);
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_enable", i);
|
||||
err = nvs_set_u8(nvs_handle, key, g_alarms[i].enable ? 1 : 0);
|
||||
}
|
||||
|
||||
nvs_close(nvs_handle);
|
||||
ESP_LOGI(TAG, "Alarm config saved to NVS");
|
||||
}
|
||||
|
||||
static void alarm_load_from_nvs(void)
|
||||
{
|
||||
nvs_handle_t nvs_handle;
|
||||
esp_err_t err = nvs_open("alarm_config", NVS_READONLY, &nvs_handle);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGI(TAG, "No alarm config found in NVS, using defaults");
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < ALARM_MAX_NUM; i++) {
|
||||
char key[16];
|
||||
uint8_t value;
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_hour", i);
|
||||
err = nvs_get_u8(nvs_handle, key, &value);
|
||||
if (err == ESP_OK) g_alarms[i].hour = value;
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_min", i);
|
||||
err = nvs_get_u8(nvs_handle, key, &value);
|
||||
if (err == ESP_OK) g_alarms[i].minute = value;
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_sec", i);
|
||||
err = nvs_get_u8(nvs_handle, key, &value);
|
||||
if (err == ESP_OK) g_alarms[i].second = value;
|
||||
|
||||
snprintf(key, sizeof(key), "alarm%d_enable", i);
|
||||
err = nvs_get_u8(nvs_handle, key, &value);
|
||||
if (err == ESP_OK) g_alarms[i].enable = (value == 1);
|
||||
}
|
||||
|
||||
nvs_close(nvs_handle);
|
||||
ESP_LOGI(TAG, "Alarm config loaded from NVS");
|
||||
}
|
||||
|
||||
esp_err_t time_alarm_init(void)
|
||||
{
|
||||
for (int i = 0; i < ALARM_MAX_NUM; i++)
|
||||
@@ -275,6 +369,10 @@ esp_err_t time_alarm_init(void)
|
||||
g_alarms[i].enable = false;
|
||||
g_alarms[i].triggered = false;
|
||||
}
|
||||
|
||||
// 从NVS加载闹钟配置
|
||||
alarm_load_from_nvs();
|
||||
|
||||
ESP_LOGI(TAG, "time_alarm initialized");
|
||||
return ESP_OK;
|
||||
}
|
||||
@@ -283,7 +381,10 @@ esp_err_t time_alarm_start(void)
|
||||
{
|
||||
if (alarm_task_handle != NULL)
|
||||
return ESP_OK;
|
||||
BaseType_t ok = xTaskCreate(alarm_task, "alarm_clock", 4096, NULL, 5, &alarm_task_handle);
|
||||
ESP_LOGI(TAG, "创建 alarm_task 前堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
// 降低主闹钟任务栈到 2048,释放堆空间(如遇溢出可再调大)
|
||||
BaseType_t ok = xTaskCreate(alarm_task, "alarm_clock", 2048, NULL, 5, &alarm_task_handle);
|
||||
ESP_LOGI(TAG, "创建 alarm_task 后堆: %d", heap_caps_get_free_size(MALLOC_CAP_8BIT));
|
||||
if (ok != pdPASS)
|
||||
{
|
||||
ESP_LOGE(TAG, "Failed to create alarm task");
|
||||
|
||||
@@ -21,6 +21,9 @@ esp_err_t time_alarm_init(void);
|
||||
esp_err_t time_alarm_start(void);
|
||||
esp_err_t time_alarm_stop(void);
|
||||
|
||||
// 等待 SNTP 时间同步完成(会阻塞直到同步成功或超时)
|
||||
void time_alarm_wait_for_sntp_sync(void);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
Reference in New Issue
Block a user