diff --git a/main/main.cpp b/main/main.cpp index d5e121d..d3fd4c1 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -61,6 +61,7 @@ static const gpio_num_t kDataPin = GPIO_NUM_14; #define TWDT_NORMAL_TIMEOUT_MS 5000 #define TWDT_UI_INIT_TIMEOUT_MS 120000 #define DOOR_OPEN_ALARM_SECONDS 60 +#define SU03T_AUTO_ALERT_INTERVAL_MS (180000U) typedef struct { @@ -419,6 +420,77 @@ static void su03t_reply_double_value(uint8_t reply_msgno, double value, const ch ESP_LOGI(TAG, "SU03T TX %s msgno=0x%02X value=%.3f", label, reply_msgno, value); } +static void su03t_reply_code(uint8_t reply_msgno, const char *label) +{ + esp_err_t ret = su03t_send_frame(reply_msgno, NULL, 0); + if (ret != ESP_OK) + { + ESP_LOGW(TAG, "SU03T TX %s failed: %s", label, esp_err_to_name(ret)); + return; + } + + ESP_LOGI(TAG, "SU03T TX %s msgno=0x%02X", label, reply_msgno); +} + +static void su03t_auto_alert_task(void *arg) +{ + (void)arg; + int64_t last_temp_ms = 0; + int64_t last_humi_ms = 0; + int64_t last_fire_ms = 0; + int64_t last_door_ms = 0; + + for (;;) + { + bool fire_danger = false; + bool door_closed = true; + float humidity = 0.0f; + float temp = 0.0f; + float th_hum_h = 70.0f; + float th_temp_h = 35.0f; + + if (s_env_data_lock) + { + xSemaphoreTake(s_env_data_lock, portMAX_DELAY); + fire_danger = s_env_data.fire_danger; + door_closed = s_env_data.door_closed; + humidity = s_env_data.humidity; + temp = s_env_data.temp; + th_hum_h = s_env_data.th_hum_h; + th_temp_h = s_env_data.th_temp_h; + xSemaphoreGive(s_env_data_lock); + } + + const int64_t now_ms = esp_timer_get_time() / 1000; + + if (temp >= th_temp_h && (now_ms - last_temp_ms) >= SU03T_AUTO_ALERT_INTERVAL_MS) + { + su03t_reply_double_value(0x10, (double)temp, "auto_temp_high"); + last_temp_ms = now_ms; + } + + if (humidity >= th_hum_h && (now_ms - last_humi_ms) >= SU03T_AUTO_ALERT_INTERVAL_MS) + { + su03t_reply_double_value(0x11, (double)humidity, "auto_humi_high"); + last_humi_ms = now_ms; + } + + if (fire_danger && (now_ms - last_fire_ms) >= SU03T_AUTO_ALERT_INTERVAL_MS) + { + su03t_reply_code(0x12, "auto_fire_alarm"); + last_fire_ms = now_ms; + } + + if (!door_closed && (now_ms - last_door_ms) >= SU03T_AUTO_ALERT_INTERVAL_MS) + { + su03t_reply_code(0x13, "auto_door_open"); + last_door_ms = now_ms; + } + + vTaskDelay(pdMS_TO_TICKS(1000)); + } +} + /* 函数: su03t_rx_callback * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 @@ -442,11 +514,31 @@ static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx) double reply_value = 0.0; uint8_t reply_msgno = 0; const char *reply_label = NULL; + bool reply_code_only = false; bool need_reply = false; + bool human_present = false; + bool door_closed = true; + bool fan_on = false; + bool light_on = false; + bool cool_on = false; + bool hot_on = false; + bool auto_mode = false; + float co2 = 0.0f; + if (s_env_data_lock) { xSemaphoreTake(s_env_data_lock, portMAX_DELAY); + + human_present = s_env_data.human_present; + door_closed = s_env_data.door_closed; + fan_on = s_env_data.fan_on; + light_on = s_env_data.light_on; + cool_on = s_env_data.cool_on; + hot_on = s_env_data.hot_on; + auto_mode = s_env_data.auto_mode; + co2 = s_env_data.co2; + switch (cmd->id) { case SU03T_VOICE_CMD_ASK_TEMP: @@ -485,6 +577,54 @@ static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx) reply_label = "weight"; need_reply = true; break; + case SU03T_VOICE_CMD_ASK_HUM: + reply_msgno = human_present ? 0x1A : 0x1B; + reply_label = human_present ? "human_present" : "human_absent"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_DOOR: + reply_msgno = door_closed ? 0x0D : 0x0C; + reply_label = door_closed ? "door_closed" : "door_open"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_FAN: + reply_msgno = fan_on ? 0x14 : 0x15; + reply_label = fan_on ? "fan_on" : "fan_off"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_LIGHT: + reply_msgno = light_on ? 0x20 : 0x21; + reply_label = light_on ? "light_on" : "light_off"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_COOL: + reply_msgno = cool_on ? 0x16 : 0x17; + reply_label = cool_on ? "cool_on" : "cool_off"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_HOT: + reply_msgno = hot_on ? 0x18 : 0x19; + reply_label = hot_on ? "hot_on" : "hot_off"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_CONTROL: + reply_msgno = auto_mode ? 0x1C : 0x1D; + reply_label = auto_mode ? "mode_auto" : "mode_manual"; + need_reply = true; + reply_code_only = true; + break; + case SU03T_VOICE_CMD_ASK_RICE: + reply_msgno = (co2 >= CO2_SPOILAGE_THRESHOLD_PPM) ? 0x1F : 0x1E; + reply_label = (co2 >= CO2_SPOILAGE_THRESHOLD_PPM) ? "rice_spoilage" : "rice_good"; + need_reply = true; + reply_code_only = true; + break; default: break; } @@ -493,7 +633,14 @@ static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx) if (need_reply) { - su03t_reply_double_value(reply_msgno, reply_value, reply_label); + if (reply_code_only) + { + su03t_reply_code(reply_msgno, reply_label); + } + else + { + su03t_reply_double_value(reply_msgno, reply_value, reply_label); + } } su03t_voice_handle_frame(frame); @@ -922,6 +1069,9 @@ extern "C" void app_main(void) // 独立任务:继电器状态同步到 UI xTaskCreate(relay_status_task, "relay_status_task", 4096, NULL, 5, NULL); + // 独立任务:SU03T 自动语音告警(默认 3 分钟节流) + xTaskCreate(su03t_auto_alert_task, "su03t_auto_alert", 4096, NULL, 5, NULL); + // 独立任务:MQTT 定时发布传感器数据 xTaskCreate(mqtt_publish_task, "mqtt_publish_task", 4096 * 2, NULL, 5, NULL); }