From 5916428fe46c00781eebdf611567dbacca52e47c Mon Sep 17 00:00:00 2001 From: Wang Beihong Date: Thu, 23 Apr 2026 21:58:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20SU-03T=20=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E5=91=BD=E4=BB=A4=E8=A7=A3=E6=9E=90=E4=B8=8E=E5=88=86?= =?UTF-8?q?=E5=8F=91=E5=8A=9F=E8=83=BD=EF=BC=8C=E6=9B=B4=E6=96=B0=E4=B8=BB?= =?UTF-8?q?=E6=B5=81=E7=A8=8B=E4=BB=A5=E6=94=AF=E6=8C=81=E8=AF=AD=E9=9F=B3?= =?UTF-8?q?=E5=91=BD=E4=BB=A4=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Copilot --- components/su-03t/CMakeLists.txt | 4 +- components/su-03t/include/su-03t_voice.h | 56 ++++++++++++ components/su-03t/su-03t_voice.c | 108 +++++++++++++++++++++++ main/main.cpp | 102 +++++++++++++++++---- 4 files changed, 250 insertions(+), 20 deletions(-) create mode 100644 components/su-03t/include/su-03t_voice.h create mode 100644 components/su-03t/su-03t_voice.c diff --git a/components/su-03t/CMakeLists.txt b/components/su-03t/CMakeLists.txt index 03a0a63..1cc143b 100644 --- a/components/su-03t/CMakeLists.txt +++ b/components/su-03t/CMakeLists.txt @@ -1,3 +1,3 @@ -idf_component_register(SRCS "su-03t.c" +idf_component_register(SRCS "su-03t.c" "su-03t_voice.c" INCLUDE_DIRS "include" - REQUIRES driver) + REQUIRES driver log) diff --git a/components/su-03t/include/su-03t_voice.h b/components/su-03t/include/su-03t_voice.h new file mode 100644 index 0000000..de82396 --- /dev/null +++ b/components/su-03t/include/su-03t_voice.h @@ -0,0 +1,56 @@ +/* + * 文件: components/su-03t/include/su-03t_voice.h + * 角色: SU-03T 语音命令解析与分发 + * 说明: + * - 这里维护语音消息号到业务命令的映射。 + * - 主接收回调只负责拿到帧,这里负责做语义判断和后续分发。 + */ + +#pragma once + +#include +#include +#include "su-03t.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef enum { + SU03T_VOICE_CMD_ASK_TEMP = 0x00, + SU03T_VOICE_CMD_ASK_HUMI = 0x01, + SU03T_VOICE_CMD_ASK_LUX = 0x02, + SU03T_VOICE_CMD_ASK_MQ2 = 0x03, + SU03T_VOICE_CMD_ASK_CO2 = 0x04, + SU03T_VOICE_CMD_ASK_WEIGHT = 0x05, + SU03T_VOICE_CMD_ASK_HUM = 0x06, + SU03T_VOICE_CMD_ASK_DOOR = 0x07, + SU03T_VOICE_CMD_ASK_FAN = 0x08, + SU03T_VOICE_CMD_ASK_LIGHT = 0x09, + SU03T_VOICE_CMD_ASK_COOL = 0x0A, + SU03T_VOICE_CMD_ASK_HOT = 0x0B, + SU03T_VOICE_CMD_ASK_CONTROL = 0x0C, + SU03T_VOICE_CMD_ASK_RICE = 0x0D, + SU03T_VOICE_CMD_UNKNOWN = 0xFF, +} su03t_voice_cmd_id_t; + +typedef struct { + su03t_voice_cmd_id_t id; + uint8_t msgno; + const char *key; + const char *label; +} su03t_voice_command_t; + +const su03t_voice_command_t *su03t_voice_command_from_msgno(uint8_t msgno); +const char *su03t_voice_command_key(uint8_t msgno); +void su03t_voice_handle_frame(const su03t_frame_t *frame); + +/* 以 8 字节 little-endian double 作为参数区发送一帧。 */ +esp_err_t su03t_voice_send_double_reply(uint8_t msgno, double value); + +/* 从接收帧参数区解析 little-endian double(要求 params_len == 8)。 */ +bool su03t_voice_parse_double_payload(const su03t_frame_t *frame, double *out_value); + +#ifdef __cplusplus +} +#endif \ No newline at end of file diff --git a/components/su-03t/su-03t_voice.c b/components/su-03t/su-03t_voice.c new file mode 100644 index 0000000..a5493b2 --- /dev/null +++ b/components/su-03t/su-03t_voice.c @@ -0,0 +1,108 @@ +/* + * 文件: components/su-03t/su-03t_voice.c + * 角色: SU-03T 语音命令解析与分发 + */ + +#include +#include +#include "esp_log.h" +#include "su-03t_voice.h" + +static const char *TAG = "su03t_voice"; + +const su03t_voice_command_t *su03t_voice_command_from_msgno(uint8_t msgno) +{ + static const su03t_voice_command_t k_commands[] = { + {SU03T_VOICE_CMD_ASK_TEMP, 0x00, "ask_temp", "温度是多少"}, + {SU03T_VOICE_CMD_ASK_HUMI, 0x01, "ask_humi", "湿度是多少"}, + {SU03T_VOICE_CMD_ASK_LUX, 0x02, "ask_lux", "光照强度是多少"}, + {SU03T_VOICE_CMD_ASK_MQ2, 0x03, "ask_mq2", "烟雾浓度是多少"}, + {SU03T_VOICE_CMD_ASK_CO2, 0x04, "ask_co2", "二氧化碳浓度是多少"}, + {SU03T_VOICE_CMD_ASK_WEIGHT, 0x05, "ask_weight", "粮食重量是多少"}, + {SU03T_VOICE_CMD_ASK_HUM, 0x06, "ask_hum", "有人在吗"}, + {SU03T_VOICE_CMD_ASK_DOOR, 0x07, "ask_door", "门磁状态"}, + {SU03T_VOICE_CMD_ASK_FAN, 0x08, "ask_fan", "风扇继电器状态"}, + {SU03T_VOICE_CMD_ASK_LIGHT, 0x09, "ask_light", "照明继电器状态"}, + {SU03T_VOICE_CMD_ASK_COOL, 0x0A, "ask_cool", "制冷继电器状态"}, + {SU03T_VOICE_CMD_ASK_HOT, 0x0B, "ask_hot", "制热继电器状态"}, + {SU03T_VOICE_CMD_ASK_CONTROL, 0x0C, "ask_control", "当前控制模式"}, + {SU03T_VOICE_CMD_ASK_RICE, 0x0D, "ask_rice", "粮食状态"}, + }; + + for (size_t i = 0; i < sizeof(k_commands) / sizeof(k_commands[0]); ++i) + { + if (k_commands[i].msgno == msgno) + { + return &k_commands[i]; + } + } + + return NULL; +} + +const char *su03t_voice_command_key(uint8_t msgno) +{ + const su03t_voice_command_t *cmd = su03t_voice_command_from_msgno(msgno); + return cmd != NULL ? cmd->key : NULL; +} + +void su03t_voice_handle_frame(const su03t_frame_t *frame) +{ + if (frame == NULL) + { + return; + } + + const su03t_voice_command_t *cmd = su03t_voice_command_from_msgno(frame->msgno); + if (cmd == NULL) + { + ESP_LOGW(TAG, "unknown voice command: msgno=0x%02X", frame->msgno); + return; + } + + switch (cmd->id) + { + case SU03T_VOICE_CMD_ASK_TEMP: + case SU03T_VOICE_CMD_ASK_HUMI: + case SU03T_VOICE_CMD_ASK_LUX: + case SU03T_VOICE_CMD_ASK_MQ2: + case SU03T_VOICE_CMD_ASK_CO2: + case SU03T_VOICE_CMD_ASK_WEIGHT: + case SU03T_VOICE_CMD_ASK_HUM: + case SU03T_VOICE_CMD_ASK_DOOR: + case SU03T_VOICE_CMD_ASK_FAN: + case SU03T_VOICE_CMD_ASK_LIGHT: + case SU03T_VOICE_CMD_ASK_COOL: + case SU03T_VOICE_CMD_ASK_HOT: + case SU03T_VOICE_CMD_ASK_CONTROL: + case SU03T_VOICE_CMD_ASK_RICE: + ESP_LOGI(TAG, "voice command matched: %s (%s)", cmd->key, cmd->label); + break; + default: + ESP_LOGW(TAG, "voice command id not handled: msgno=0x%02X", frame->msgno); + break; + } +} + +esp_err_t su03t_voice_send_double_reply(uint8_t msgno, double value) +{ + uint8_t payload[sizeof(double)]; + memcpy(payload, &value, sizeof(payload)); + return su03t_send_frame(msgno, payload, sizeof(payload)); +} + +bool su03t_voice_parse_double_payload(const su03t_frame_t *frame, double *out_value) +{ + if (frame == NULL || out_value == NULL) + { + return false; + } + + if (frame->params_len != sizeof(double)) + { + return false; + } + + memcpy(out_value, frame->params, sizeof(double)); + return true; +} \ No newline at end of file diff --git a/main/main.cpp b/main/main.cpp index 7b866c6..d5e121d 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -38,6 +38,7 @@ #include "fire_sensor.h" #include "hx711.hpp" #include "su-03t.h" +#include "su-03t_voice.h" #include "agri_env.h" #include "cJSON.h" @@ -406,6 +407,18 @@ static void sntp_task(void *arg) vTaskDelete(NULL); } +static void su03t_reply_double_value(uint8_t reply_msgno, double value, const char *label) +{ + esp_err_t ret = su03t_voice_send_double_reply(reply_msgno, value); + 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 value=%.3f", label, reply_msgno, value); +} + /* 函数: su03t_rx_callback * 作用: 执行模块内与函数名对应的业务逻辑。 * 重点: 关注输入合法性、返回码与并发安全。 @@ -418,26 +431,79 @@ static void su03t_rx_callback(const su03t_frame_t *frame, void *user_ctx) return; } - char hex_buf[256]; - size_t pos = 0; - for (size_t i = 0; i < frame->params_len && pos + 4 < sizeof(hex_buf); ++i) + const su03t_voice_command_t *cmd = su03t_voice_command_from_msgno(frame->msgno); + if (cmd != NULL) { - int n = snprintf(&hex_buf[pos], sizeof(hex_buf) - pos, "%02X ", frame->params[i]); - if (n <= 0) - { - break; - } - pos += (size_t)n; - } - if (pos == 0) - { - snprintf(hex_buf, sizeof(hex_buf), "(no params)"); - } + ESP_LOGI(TAG, "SU03T RX msgno=0x%02X cmd=%s len=%u", + frame->msgno, + cmd->key, + (unsigned)frame->params_len); - ESP_LOGI(TAG, "SU03T RX msgno=0x%02X len=%u params=%s", - frame->msgno, - (unsigned)frame->params_len, - hex_buf); + double reply_value = 0.0; + uint8_t reply_msgno = 0; + const char *reply_label = NULL; + bool need_reply = false; + + if (s_env_data_lock) + { + xSemaphoreTake(s_env_data_lock, portMAX_DELAY); + switch (cmd->id) + { + case SU03T_VOICE_CMD_ASK_TEMP: + reply_msgno = 0x01; + reply_value = (double)s_env_data.temp; + reply_label = "temp"; + need_reply = true; + break; + case SU03T_VOICE_CMD_ASK_HUMI: + reply_msgno = 0x02; + reply_value = (double)s_env_data.humidity; + reply_label = "humidity"; + need_reply = true; + break; + case SU03T_VOICE_CMD_ASK_LUX: + reply_msgno = 0x03; + reply_value = (double)s_env_data.lux; + reply_label = "lux"; + need_reply = true; + break; + case SU03T_VOICE_CMD_ASK_MQ2: + reply_msgno = 0x04; + reply_value = (double)s_env_data.gas_percent; + reply_label = "air_quality"; + need_reply = true; + break; + case SU03T_VOICE_CMD_ASK_CO2: + reply_msgno = 0x05; + reply_value = (double)s_env_data.co2; + reply_label = "co2"; + need_reply = true; + break; + case SU03T_VOICE_CMD_ASK_WEIGHT: + reply_msgno = 0x06; + reply_value = (double)s_env_data.ice_weight; + reply_label = "weight"; + need_reply = true; + break; + default: + break; + } + xSemaphoreGive(s_env_data_lock); + } + + if (need_reply) + { + su03t_reply_double_value(reply_msgno, reply_value, reply_label); + } + + su03t_voice_handle_frame(frame); + } + else + { + ESP_LOGI(TAG, "SU03T RX msgno=0x%02X cmd=unknown len=%u", + frame->msgno, + (unsigned)frame->params_len); + } if (s_env_data_lock) {