From 3c71558a5f2b4697ff64cb6a560287f56d2e1cab Mon Sep 17 00:00:00 2001 From: Xiaoxia Date: Sat, 19 Jul 2025 22:45:22 +0800 Subject: [PATCH] =?UTF-8?q?v1.8.0:=20Audio=20=E4=BB=A3=E7=A0=81=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E4=B8=8E=E4=BD=8E=E5=8A=9F=E8=80=97=E4=BC=98=E5=8C=96?= =?UTF-8?q?=20(#943)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Reconstruct Audio Code * Remove old IoT implementation * Add MQTT-UDP documentation * OTA升级失败时,可以继续使用 --- CMakeLists.txt | 2 +- README.md | 3 +- README_en.md | 2 +- README_ja.md | 2 +- docs/mcp-protocol.md | 2 +- docs/mqtt-udp.md | 393 +++++++++++ docs/websocket.md | 97 ++- main/CMakeLists.txt | 42 +- main/Kconfig.projbuild | 11 - main/application.cc | 661 ++++-------------- main/application.h | 70 +- main/audio/README.md | 88 +++ main/{audio_codecs => audio}/audio_codec.cc | 0 main/{audio_codecs => audio}/audio_codec.h | 0 .../audio_processor.h | 0 main/audio/audio_service.cc | 544 ++++++++++++++ main/audio/audio_service.h | 157 +++++ .../codecs}/box_audio_codec.cc | 0 .../codecs}/box_audio_codec.h | 0 .../codecs}/dummy_audio_codec.cc | 0 .../codecs}/dummy_audio_codec.h | 0 .../codecs}/es8311_audio_codec.cc | 0 .../codecs}/es8311_audio_codec.h | 0 .../codecs}/es8374_audio_codec.cc | 0 .../codecs}/es8374_audio_codec.h | 0 .../codecs}/es8388_audio_codec.cc | 0 .../codecs}/es8388_audio_codec.h | 0 .../codecs}/no_audio_codec.cc | 69 +- .../codecs}/no_audio_codec.h | 5 - .../processors}/afe_audio_processor.cc | 0 .../processors}/afe_audio_processor.h | 0 .../processors}/audio_debugger.cc | 0 .../processors}/audio_debugger.h | 0 .../processors}/no_audio_processor.cc | 0 .../processors}/no_audio_processor.h | 0 main/{audio_processing => audio}/wake_word.h | 5 +- .../wake_words}/afe_wake_word.cc | 10 +- .../wake_words}/afe_wake_word.h | 5 +- .../wake_words}/custom_wake_word.cc | 10 +- .../wake_words}/custom_wake_word.h | 5 +- .../wake_words}/esp_wake_word.cc | 10 +- .../wake_words}/esp_wake_word.h | 5 +- main/audio_processing/no_wake_word.cc | 45 -- main/audio_processing/no_wake_word.h | 32 - main/background_task.cc | 70 -- main/background_task.h | 30 - main/boards/README.md | 15 +- .../atk-dnesp32s3-box/atk_dnesp32s3_box.cc | 76 +- .../atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc | 12 +- main/boards/atk-dnesp32s3/atk_dnesp32s3.cc | 11 +- .../atk-dnesp32s3m-4g/atk_dnesp32s3m.cc | 11 +- .../atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc | 11 +- .../atommatrix_echo_base.cc | 10 +- .../atoms3-echo-base/atoms3_echo_base.cc | 11 +- .../atoms3r_cam_m12_echo_base.cc | 10 +- .../atoms3r-echo-base/atoms3r_echo_base.cc | 11 +- .../esp32_bread_board_lcd.cc | 13 +- .../bread-compact-esp32/esp32_bread_board.cc | 13 +- .../compact_ml307_board.cc | 13 +- .../compact_wifi_board_lcd.cc | 14 +- .../compact_wifi_board_s3cam.cc | 15 +- .../bread-compact-wifi/compact_wifi_board.cc | 13 +- main/boards/common/adc_battery_monitor.cc | 81 +++ main/boards/common/adc_battery_monitor.h | 30 + main/boards/common/afsk_demod.cc | 2 +- main/boards/common/button.cc | 4 +- main/boards/common/button.h | 8 +- main/boards/common/ml307_board.cc | 6 - main/boards/common/sleep_timer.cc | 114 +++ main/boards/common/sleep_timer.h | 32 + main/boards/df-k10/config.json | 3 +- main/boards/df-k10/df_k10_board.cc | 6 - main/boards/df-s3-ai-cam/config.json | 3 +- main/boards/df-s3-ai-cam/df_s3_ai_cam.cc | 15 +- main/boards/doit-s3-aibox/doit_s3_aibox.cc | 10 +- main/boards/du-chatx/du-chatx-wifi.cc | 12 +- main/boards/echoear/EchoEar.cc | 3 +- main/boards/echoear/config.json | 2 +- main/boards/electron-bot/electron_bot.cc | 2 +- main/boards/esp-box-3/esp_box3_board.cc | 11 +- .../boards/esp-box-lite/esp_box_lite_board.cc | 9 - main/boards/esp-box/esp_box_board.cc | 11 +- main/boards/esp-hi/config.json | 1 - .../esp-s3-lcd-ev-board.cc | 11 +- .../boards/esp-sparkbot/esp_sparkbot_board.cc | 2 +- main/boards/esp-spot-s3/esp_spot_s3_board.cc | 11 +- main/boards/esp32-cgc-144/board_control.cc | 60 -- .../esp32-cgc-144/esp32_cgc_144_board.cc | 15 +- main/boards/esp32-cgc/esp32_cgc_board.cc | 14 +- .../esp32-s3-touch-amoled-1.8.cc | 2 +- .../esp32-s3-touch-lcd-1.46.cc | 11 +- .../esp32-s3-touch-lcd-1.85.cc | 12 +- .../esp32-s3-touch-lcd-1.85c.cc | 11 +- .../esp32-s3-touch-lcd-3.5.cc | 2 +- .../esp32s3_korvo2_v3_board.cc | 10 +- .../genjutech-s3-1.54tft.cc | 14 +- main/boards/jiuchuan-s3/jiuchuan_dev_board.cc | 20 +- main/boards/kevin-box-1/kevin_box_board.cc | 10 +- main/boards/kevin-box-2/kevin_box_board.cc | 11 +- main/boards/kevin-c3/kevin_c3_board.cc | 22 +- .../kevin-sp-v3-dev/kevin-sp-v3_board.cc | 11 +- .../kevin-sp-v4-dev/kevin-sp-v4_board.cc | 11 +- .../kevin_yuying_313lcd.cc | 11 +- .../labplus-ledong-v2/labplus_ledong_v2.cc | 11 +- main/boards/labplus-mpython-v3/mpython_pro.cc | 11 +- .../lichuang-c3-dev/lichuang_c3_dev_board.cc | 11 +- .../boards/lichuang-dev/lichuang_dev_board.cc | 8 +- .../lilygo-t-cameraplus-s3.cc | 14 +- .../tcamerapluss3_audio_codec.h | 2 +- .../lilygo-t-circle-s3/lilygo-t-circle-s3.cc | 6 - .../tcircles3_audio_codec.h | 2 +- .../lilygo-t-display-s3-pro-mvsrlora.cc | 6 - .../tdisplays3promvsrlora_audio_codec.h | 2 +- .../boards/m5stack-core-s3/m5stack_core_s3.cc | 10 - main/boards/m5stack-tab5/m5stack_tab5.cc | 9 - .../magiclick-2p4/magiclick_2p4_board.cc | 11 +- .../magiclick-2p5/magiclick_2p5_board.cc | 11 +- .../magiclick-c3-v2/magiclick_c3_v2_board.cc | 25 +- .../boards/magiclick-c3/magiclick_c3_board.cc | 25 +- main/boards/minsi-k08-dual/minsi_k08_dual.cc | 15 +- main/boards/mixgo-nova/mixgo-nova.cc | 10 +- .../movecall_cuican_esp32s3.cc | 11 +- .../movecall_moji_esp32s3.cc | 11 +- main/boards/otto-robot/otto_robot.cc | 3 +- .../sensecap-watcher/sensecap_watcher.cc | 10 - .../sp-esp32-s3-1.28-box.cc | 11 +- .../sp-esp32-s3-1.54-muma.cc | 12 +- .../surfer-c3-1.14tft/surfer-c3-1.14tft.cc | 26 +- main/boards/taiji-pi-s3/taiji_pi_s3.cc | 10 +- main/boards/tudouzi/kevin_box_board.cc | 11 +- .../esp32-c6-lcd-1.69.cc | 14 +- .../esp32-c6-touch-amoled-1.43.cc | 12 +- .../boards/waveshare-p4-nano/esp32-p4-nano.cc | 11 +- .../esp32-p4-wifi6-touch-lcd-4b.cc | 11 +- .../esp32-p4-wifi6-touch-lcd-xc.cc | 11 +- .../esp32-s3-touch-amoled-1.75.cc | 2 +- .../waveshare-s3-touch-lcd-3.5b.cc | 17 +- .../xingzhi-cube-0.85tft-ml307.cc | 13 +- .../xingzhi-cube-0.85tft-wifi.cc | 13 +- .../xingzhi-cube-0.96oled-ml307.cc | 10 +- .../xingzhi-cube-0.96oled-wifi.cc | 10 +- .../xingzhi-cube-1.54tft-ml307.cc | 13 +- .../xingzhi-cube-1.54tft-wifi.cc | 13 +- main/boards/xmini-c3-4g/README.md | 4 + main/boards/xmini-c3-4g/power_manager.h | 200 ------ main/boards/xmini-c3-4g/xmini_c3_4g_board.cc | 76 +- main/boards/xmini-c3-v3/README.md | 4 + main/boards/xmini-c3-v3/power_manager.h | 196 ------ main/boards/xmini-c3-v3/xmini_c3_board.cc | 52 +- main/boards/xmini-c3/xmini_c3_board.cc | 6 +- .../zhengchen-1.54tft-ml307.cc | 13 +- .../zhengchen-1.54tft-wifi.cc | 13 +- main/display/display.cc | 32 +- main/display/display.h | 3 + main/idf_component.yml | 2 +- main/iot/README.md | 211 ------ main/iot/thing.cc | 92 --- main/iot/thing.h | 300 -------- main/iot/thing_manager.cc | 63 -- main/iot/thing_manager.h | 42 -- main/iot/things/battery.cc | 35 - main/iot/things/lamp.cc | 58 -- main/iot/things/screen.cc | 54 -- main/iot/things/speaker.cc | 33 - main/ota.cc | 31 +- main/ota.h | 4 +- main/protocols/mqtt_protocol.cc | 28 +- main/protocols/mqtt_protocol.h | 2 +- main/protocols/protocol.cc | 52 +- main/protocols/protocol.h | 8 +- main/protocols/websocket_protocol.cc | 32 +- main/protocols/websocket_protocol.h | 2 +- main/system_info.cc | 2 +- 173 files changed, 2099 insertions(+), 3265 deletions(-) create mode 100644 docs/mqtt-udp.md create mode 100644 main/audio/README.md rename main/{audio_codecs => audio}/audio_codec.cc (100%) rename main/{audio_codecs => audio}/audio_codec.h (100%) rename main/{audio_processing => audio}/audio_processor.h (100%) create mode 100644 main/audio/audio_service.cc create mode 100644 main/audio/audio_service.h rename main/{audio_codecs => audio/codecs}/box_audio_codec.cc (100%) rename main/{audio_codecs => audio/codecs}/box_audio_codec.h (100%) rename main/{audio_codecs => audio/codecs}/dummy_audio_codec.cc (100%) rename main/{audio_codecs => audio/codecs}/dummy_audio_codec.h (100%) rename main/{audio_codecs => audio/codecs}/es8311_audio_codec.cc (100%) rename main/{audio_codecs => audio/codecs}/es8311_audio_codec.h (100%) rename main/{audio_codecs => audio/codecs}/es8374_audio_codec.cc (100%) rename main/{audio_codecs => audio/codecs}/es8374_audio_codec.h (100%) rename main/{audio_codecs => audio/codecs}/es8388_audio_codec.cc (100%) rename main/{audio_codecs => audio/codecs}/es8388_audio_codec.h (100%) rename main/{audio_codecs => audio/codecs}/no_audio_codec.cc (82%) rename main/{audio_codecs => audio/codecs}/no_audio_codec.h (87%) rename main/{audio_processing => audio/processors}/afe_audio_processor.cc (100%) rename main/{audio_processing => audio/processors}/afe_audio_processor.h (100%) rename main/{audio_processing => audio/processors}/audio_debugger.cc (100%) rename main/{audio_processing => audio/processors}/audio_debugger.h (100%) rename main/{audio_processing => audio/processors}/no_audio_processor.cc (100%) rename main/{audio_processing => audio/processors}/no_audio_processor.h (100%) rename main/{audio_processing => audio}/wake_word.h (84%) rename main/{audio_processing => audio/wake_words}/afe_wake_word.cc (96%) rename main/{audio_processing => audio/wake_words}/afe_wake_word.h (95%) rename main/{audio_processing => audio/wake_words}/custom_wake_word.cc (97%) rename main/{audio_processing => audio/wake_words}/custom_wake_word.h (95%) rename main/{audio_processing => audio/wake_words}/esp_wake_word.cc (91%) rename main/{audio_processing => audio/wake_words}/esp_wake_word.h (93%) delete mode 100644 main/audio_processing/no_wake_word.cc delete mode 100644 main/audio_processing/no_wake_word.h delete mode 100644 main/background_task.cc delete mode 100644 main/background_task.h create mode 100644 main/boards/common/adc_battery_monitor.cc create mode 100644 main/boards/common/adc_battery_monitor.h create mode 100644 main/boards/common/sleep_timer.cc create mode 100644 main/boards/common/sleep_timer.h delete mode 100644 main/boards/esp32-cgc-144/board_control.cc create mode 100644 main/boards/xmini-c3-4g/README.md delete mode 100644 main/boards/xmini-c3-4g/power_manager.h create mode 100644 main/boards/xmini-c3-v3/README.md delete mode 100644 main/boards/xmini-c3-v3/power_manager.h delete mode 100644 main/iot/README.md delete mode 100644 main/iot/thing.cc delete mode 100644 main/iot/thing.h delete mode 100644 main/iot/thing_manager.cc delete mode 100644 main/iot/thing_manager.h delete mode 100644 main/iot/things/battery.cc delete mode 100644 main/iot/things/lamp.cc delete mode 100644 main/iot/things/screen.cc delete mode 100644 main/iot/things/speaker.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index e8f5f384..f7a69783 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "1.7.7") +set(PROJECT_VER "1.8.0") # Add this line to disable the specific warning add_compile_options(-Wno-missing-field-initializers) diff --git a/README.md b/README.md index 18a3dcae..574a7f37 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ 我们希望通过这个项目,能够帮助大家了解 AI 硬件开发,将当下飞速发展的大语言模型应用到实际的硬件设备中。 -如果你有任何想法或建议,请随时提出 Issues 或加入 QQ 群:575180511 +如果你有任何想法或建议,请随时提出 Issues 或加入 QQ 群:1011329060 ### 基于 MCP 控制万物 @@ -125,6 +125,7 @@ - [自定义开发板指南](main/boards/README.md) - 学习如何为小智 AI 创建自定义开发板 - [MCP 协议物联网控制用法说明](docs/mcp-usage.md) - 了解如何通过 MCP 协议控制物联网设备 - [MCP 协议交互流程](docs/mcp-protocol.md) - 设备端 MCP 协议的实现方式 +- [MQTT + UDP 混合通信协议文档](docs/mqtt-udp.md) - [一份详细的 WebSocket 通信协议文档](docs/websocket.md) ## 大模型配置 diff --git a/README_en.md b/README_en.md index aa4a8b38..f6a6a490 100644 --- a/README_en.md +++ b/README_en.md @@ -14,7 +14,7 @@ This is an open-source ESP32 project, released under the MIT license, allowing a We hope this project helps everyone understand AI hardware development and apply rapidly evolving large language models to real hardware devices. -If you have any ideas or suggestions, please feel free to raise Issues or join the QQ group: 575180511 +If you have any ideas or suggestions, please feel free to raise Issues or join the QQ group: 1011329060 ### Control Everything with MCP diff --git a/README_ja.md b/README_ja.md index de4c89cf..b5a2b650 100644 --- a/README_ja.md +++ b/README_ja.md @@ -14,7 +14,7 @@ このプロジェクトを通じて、AIハードウェア開発を理解し、急速に進化する大規模言語モデルを実際のハードウェアデバイスに応用できるようになることを目指しています。 -ご意見やご提案があれば、いつでもIssueを提出するか、QQグループ:575180511 にご参加ください。 +ご意見やご提案があれば、いつでもIssueを提出するか、QQグループ:1011329060 にご参加ください。 ### MCPであらゆるものを制御 diff --git a/docs/mcp-protocol.md b/docs/mcp-protocol.md index 7cef3572..0c8ec907 100644 --- a/docs/mcp-protocol.md +++ b/docs/mcp-protocol.md @@ -42,7 +42,7 @@ MCP 的交互主要围绕客户端(后台 API)发现和调用设备上的“ - **时机:** 设备启动并成功连接到后台 API 后。 - **发送方:** 设备。 - - **消息:** 设备发送基础协议的 "hello" 消息给后台 API,消息中包含设备支持的能力列表,例如通过 `CONFIG_IOT_PROTOCOL_MCP` 配置来表明支持 MCP 协议 (`"mcp": true`)。 + - **消息:** 设备发送基础协议的 "hello" 消息给后台 API,消息中包含设备支持的能力列表,例如通过支持 MCP 协议 (`"mcp": true`)。 - **示例 (非 MCP 负载,而是基础协议消息):** ```json { diff --git a/docs/mqtt-udp.md b/docs/mqtt-udp.md new file mode 100644 index 00000000..478e4669 --- /dev/null +++ b/docs/mqtt-udp.md @@ -0,0 +1,393 @@ +# MQTT + UDP 混合通信协议文档 + +基于代码实现整理的 MQTT + UDP 混合通信协议文档,概述设备端与服务器之间如何通过 MQTT 进行控制消息传输,通过 UDP 进行音频数据传输的交互方式。 + +--- + +## 1. 协议概览 + +本协议采用混合传输方式: +- **MQTT**:用于控制消息、状态同步、JSON 数据交换 +- **UDP**:用于实时音频数据传输,支持加密 + +### 1.1 协议特点 + +- **双通道设计**:控制与数据分离,确保实时性 +- **加密传输**:UDP 音频数据使用 AES-CTR 加密 +- **序列号保护**:防止数据包重放和乱序 +- **自动重连**:MQTT 连接断开时自动重连 + +--- + +## 2. 总体流程概览 + +```mermaid +sequenceDiagram + participant Device as ESP32 设备 + participant MQTT as MQTT 服务器 + participant UDP as UDP 服务器 + + Note over Device, UDP: 1. 建立 MQTT 连接 + Device->>MQTT: MQTT Connect + MQTT->>Device: Connected + + Note over Device, UDP: 2. 请求音频通道 + Device->>MQTT: Hello Message (type: "hello", transport: "udp") + MQTT->>Device: Hello Response (UDP 连接信息 + 加密密钥) + + Note over Device, UDP: 3. 建立 UDP 连接 + Device->>UDP: UDP Connect + UDP->>Device: Connected + + Note over Device, UDP: 4. 音频数据传输 + loop 音频流传输 + Device->>UDP: 加密音频数据 (Opus) + UDP->>Device: 加密音频数据 (Opus) + end + + Note over Device, UDP: 5. 控制消息交换 + par 控制消息 + Device->>MQTT: Listen/TTS/MCP 消息 + MQTT->>Device: STT/TTS/MCP 响应 + end + + Note over Device, UDP: 6. 关闭连接 + Device->>MQTT: Goodbye Message + Device->>UDP: Disconnect +``` + +--- + +## 3. MQTT 控制通道 + +### 3.1 连接建立 + +设备通过 MQTT 连接到服务器,连接参数包括: +- **Endpoint**:MQTT 服务器地址和端口 +- **Client ID**:设备唯一标识符 +- **Username/Password**:认证凭据 +- **Keep Alive**:心跳间隔(默认240秒) + +### 3.2 Hello 消息交换 + +#### 3.2.1 设备端发送 Hello + +```json +{ + "type": "hello", + "version": 3, + "transport": "udp", + "features": { + "mcp": true + }, + "audio_params": { + "format": "opus", + "sample_rate": 16000, + "channels": 1, + "frame_duration": 60 + } +} +``` + +#### 3.2.2 服务器响应 Hello + +```json +{ + "type": "hello", + "transport": "udp", + "session_id": "xxx", + "audio_params": { + "format": "opus", + "sample_rate": 24000, + "channels": 1, + "frame_duration": 60 + }, + "udp": { + "server": "192.168.1.100", + "port": 8888, + "key": "0123456789ABCDEF0123456789ABCDEF", + "nonce": "0123456789ABCDEF0123456789ABCDEF" + } +} +``` + +**字段说明:** +- `udp.server`:UDP 服务器地址 +- `udp.port`:UDP 服务器端口 +- `udp.key`:AES 加密密钥(十六进制字符串) +- `udp.nonce`:AES 加密随机数(十六进制字符串) + +### 3.3 JSON 消息类型 + +#### 3.3.1 设备端→服务器 + +1. **Listen 消息** + ```json + { + "session_id": "xxx", + "type": "listen", + "state": "start", + "mode": "manual" + } + ``` + +2. **Abort 消息** + ```json + { + "session_id": "xxx", + "type": "abort", + "reason": "wake_word_detected" + } + ``` + +3. **MCP 消息** + ```json + { + "session_id": "xxx", + "type": "mcp", + "payload": { + "jsonrpc": "2.0", + "id": 1, + "result": {...} + } + } + ``` + +4. **Goodbye 消息** + ```json + { + "session_id": "xxx", + "type": "goodbye" + } + ``` + +#### 3.3.2 服务器→设备端 + +支持的消息类型与 WebSocket 协议一致,包括: +- **STT**:语音识别结果 +- **TTS**:语音合成控制 +- **LLM**:情感表达控制 +- **MCP**:物联网控制 +- **System**:系统控制 +- **Custom**:自定义消息(可选) + +--- + +## 4. UDP 音频通道 + +### 4.1 连接建立 + +设备收到 MQTT Hello 响应后,使用其中的 UDP 连接信息建立音频通道: +1. 解析 UDP 服务器地址和端口 +2. 解析加密密钥和随机数 +3. 初始化 AES-CTR 加密上下文 +4. 建立 UDP 连接 + +### 4.2 音频数据格式 + +#### 4.2.1 加密音频包结构 + +``` +|type 1byte|flags 1byte|payload_len 2bytes|ssrc 4bytes|timestamp 4bytes|sequence 4bytes| +|payload payload_len bytes| +``` + +**字段说明:** +- `type`:数据包类型,固定为 0x01 +- `flags`:标志位,当前未使用 +- `payload_len`:负载长度(网络字节序) +- `ssrc`:同步源标识符 +- `timestamp`:时间戳(网络字节序) +- `sequence`:序列号(网络字节序) +- `payload`:加密的 Opus 音频数据 + +#### 4.2.2 加密算法 + +使用 **AES-CTR** 模式加密: +- **密钥**:128位,由服务器提供 +- **随机数**:128位,由服务器提供 +- **计数器**:包含时间戳和序列号信息 + +### 4.3 序列号管理 + +- **发送端**:`local_sequence_` 单调递增 +- **接收端**:`remote_sequence_` 验证连续性 +- **防重放**:拒绝序列号小于期望值的数据包 +- **容错处理**:允许轻微的序列号跳跃,记录警告 + +### 4.4 错误处理 + +1. **解密失败**:记录错误,丢弃数据包 +2. **序列号异常**:记录警告,但仍处理数据包 +3. **数据包格式错误**:记录错误,丢弃数据包 + +--- + +## 5. 状态管理 + +### 5.1 连接状态 + +```mermaid +stateDiagram + direction TB + [*] --> Disconnected + Disconnected --> MqttConnecting: StartMqttClient() + MqttConnecting --> MqttConnected: MQTT Connected + MqttConnecting --> Disconnected: Connect Failed + MqttConnected --> RequestingChannel: OpenAudioChannel() + RequestingChannel --> ChannelOpened: Hello Exchange Success + RequestingChannel --> MqttConnected: Hello Timeout/Failed + ChannelOpened --> UdpConnected: UDP Connect Success + UdpConnected --> AudioStreaming: Start Audio Transfer + AudioStreaming --> UdpConnected: Stop Audio Transfer + UdpConnected --> ChannelOpened: UDP Disconnect + ChannelOpened --> MqttConnected: CloseAudioChannel() + MqttConnected --> Disconnected: MQTT Disconnect +``` + +### 5.2 状态检查 + +设备通过以下条件判断音频通道是否可用: +```cpp +bool IsAudioChannelOpened() const { + return udp_ != nullptr && !error_occurred_ && !IsTimeout(); +} +``` + +--- + +## 6. 配置参数 + +### 6.1 MQTT 配置 + +从设置中读取的配置项: +- `endpoint`:MQTT 服务器地址 +- `client_id`:客户端标识符 +- `username`:用户名 +- `password`:密码 +- `keepalive`:心跳间隔(默认240秒) +- `publish_topic`:发布主题 + +### 6.2 音频参数 + +- **格式**:Opus +- **采样率**:16000 Hz(设备端)/ 24000 Hz(服务器端) +- **声道数**:1(单声道) +- **帧时长**:60ms + +--- + +## 7. 错误处理与重连 + +### 7.1 MQTT 重连机制 + +- 连接失败时自动重试 +- 支持错误上报控制 +- 断线时触发清理流程 + +### 7.2 UDP 连接管理 + +- 连接失败时不自动重试 +- 依赖 MQTT 通道重新协商 +- 支持连接状态查询 + +### 7.3 超时处理 + +基类 `Protocol` 提供超时检测: +- 默认超时时间:120 秒 +- 基于最后接收时间计算 +- 超时时自动标记为不可用 + +--- + +## 8. 安全考虑 + +### 8.1 传输加密 + +- **MQTT**:支持 TLS/SSL 加密(端口8883) +- **UDP**:使用 AES-CTR 加密音频数据 + +### 8.2 认证机制 + +- **MQTT**:用户名/密码认证 +- **UDP**:通过 MQTT 通道分发密钥 + +### 8.3 防重放攻击 + +- 序列号单调递增 +- 拒绝过期数据包 +- 时间戳验证 + +--- + +## 9. 性能优化 + +### 9.1 并发控制 + +使用互斥锁保护 UDP 连接: +```cpp +std::lock_guard lock(channel_mutex_); +``` + +### 9.2 内存管理 + +- 动态创建/销毁网络对象 +- 智能指针管理音频数据包 +- 及时释放加密上下文 + +### 9.3 网络优化 + +- UDP 连接复用 +- 数据包大小优化 +- 序列号连续性检查 + +--- + +## 10. 与 WebSocket 协议的比较 + +| 特性 | MQTT + UDP | WebSocket | +|------|------------|-----------| +| 控制通道 | MQTT | WebSocket | +| 音频通道 | UDP (加密) | WebSocket (二进制) | +| 实时性 | 高 (UDP) | 中等 | +| 可靠性 | 中等 | 高 | +| 复杂度 | 高 | 低 | +| 加密 | AES-CTR | TLS | +| 防火墙友好度 | 低 | 高 | + +--- + +## 11. 部署建议 + +### 11.1 网络环境 + +- 确保 UDP 端口可达 +- 配置防火墙规则 +- 考虑 NAT 穿透 + +### 11.2 服务器配置 + +- MQTT Broker 配置 +- UDP 服务器部署 +- 密钥管理系统 + +### 11.3 监控指标 + +- 连接成功率 +- 音频传输延迟 +- 数据包丢失率 +- 解密失败率 + +--- + +## 12. 总结 + +MQTT + UDP 混合协议通过以下设计实现高效的音视频通信: + +- **分离式架构**:控制与数据通道分离,各司其职 +- **加密保护**:AES-CTR 确保音频数据安全传输 +- **序列化管理**:防止重放攻击和数据乱序 +- **自动恢复**:支持连接断开后的自动重连 +- **性能优化**:UDP 传输保证音频数据的实时性 + +该协议适用于对实时性要求较高的语音交互场景,但需要在网络复杂度和传输性能之间做出权衡。 \ No newline at end of file diff --git a/docs/websocket.md b/docs/websocket.md index 8e621281..f7671141 100644 --- a/docs/websocket.md +++ b/docs/websocket.md @@ -84,7 +84,7 @@ 在建立 WebSocket 连接时,代码示例中设置了以下请求头: - `Authorization`: 用于存放访问令牌,形如 `"Bearer "` -- `Protocol-Version`: 固定示例中为 `"1"`,与 hello 消息体内的 `version` 字段保持一致 +- `Protocol-Version`: 协议版本号,与 hello 消息体内的 `version` 字段保持一致 - `Device-Id`: 设备物理网卡 MAC 地址 - `Client-Id`: 软件生成的 UUID(擦除 NVS 或重新烧录完整固件会重置) @@ -92,11 +92,44 @@ --- -## 3. JSON 消息结构 +## 3. 二进制协议版本 + +设备支持多种二进制协议版本,通过配置中的 `version` 字段指定: + +### 3.1 版本1(默认) +直接发送 Opus 音频数据,无额外元数据。Websocket 协议会区分 text 与 binary。 + +### 3.2 版本2 +使用 `BinaryProtocol2` 结构: +```c +struct BinaryProtocol2 { + uint16_t version; // 协议版本 + uint16_t type; // 消息类型 (0: OPUS, 1: JSON) + uint32_t reserved; // 保留字段 + uint32_t timestamp; // 时间戳(毫秒,用于服务器端AEC) + uint32_t payload_size; // 负载大小(字节) + uint8_t payload[]; // 负载数据 +} __attribute__((packed)); +``` + +### 3.3 版本3 +使用 `BinaryProtocol3` 结构: +```c +struct BinaryProtocol3 { + uint8_t type; // 消息类型 + uint8_t reserved; // 保留字段 + uint16_t payload_size; // 负载大小 + uint8_t payload[]; // 负载数据 +} __attribute__((packed)); +``` + +--- + +## 4. JSON 消息结构 WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及其对应业务逻辑。若消息里包含未列出的字段,可能为可选或特定实现细节。 -### 3.1 设备端→服务器 +### 4.1 设备端→服务器 1. **Hello** - 连接成功后,由设备端发送,告知服务器基本参数。 @@ -183,7 +216,7 @@ WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及 --- -### 3.2 服务器→设备端 +### 4.2 服务器→设备端 1. **Hello** - 服务器端返回的握手确认消息。 @@ -227,17 +260,43 @@ WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及 } ``` -6. **音频数据:二进制帧** +6. **System** + - 系统控制命令,常用于远程升级更新。 + - 例: + ```json + { + "session_id": "xxx", + "type": "system", + "command": "reboot" + } + ``` + - 支持的命令: + - `"reboot"`:重启设备 + +7. **Custom**(可选) + - 自定义消息,当 `CONFIG_RECEIVE_CUSTOM_MESSAGE` 启用时支持。 + - 例: + ```json + { + "session_id": "xxx", + "type": "custom", + "payload": { + "message": "自定义内容" + } + } + ``` + +8. **音频数据:二进制帧** - 当服务器发送音频二进制帧(Opus 编码)时,设备端解码并播放。 - 若设备端正在处于 "listening" (录音)状态,收到的音频帧会被忽略或清空以防冲突。 --- -## 4. 音频编解码 +## 5. 音频编解码 1. **设备端发送录音数据** - 音频输入经过可能的回声消除、降噪或音量增益后,通过 Opus 编码打包为二进制帧发送给服务器。 - - 如果设备端每次编码生成的二进制帧大小为 N 字节,则会通过 WebSocket 的 **binary** 消息发送这块数据。 + - 根据协议版本,可能直接发送 Opus 数据(版本1)或使用带元数据的二进制协议(版本2/3)。 2. **设备端播放收到的音频** - 收到服务器的二进制帧时,同样认定是 Opus 数据。 @@ -246,7 +305,7 @@ WebSocket 文本帧以 JSON 方式传输,以下为常见的 `"type"` 字段及 --- -## 5. 常见状态流转 +## 6. 常见状态流转 以下为常见设备端关键状态流转,与 WebSocket 消息对应: @@ -307,7 +366,7 @@ stateDiagram --- -## 6. 错误处理 +## 7. 错误处理 1. **连接失败** - 如果 `Connect(url)` 返回失败或在等待服务器 "hello" 消息时超时,触发 `on_network_error_()` 回调。设备会提示"无法连接到服务"或类似错误信息。 @@ -319,7 +378,7 @@ stateDiagram --- -## 7. 其它注意事项 +## 8. 其它注意事项 1. **鉴权** - 设备通过设置 `Authorization: Bearer ` 提供鉴权,服务器端需验证是否有效。 @@ -331,17 +390,23 @@ stateDiagram 3. **音频负载** - 代码里默认使用 Opus 格式,并设置 `sample_rate = 16000`,单声道。帧时长由 `OPUS_FRAME_DURATION_MS` 控制,一般为 60ms。可根据带宽或性能做适当调整。为了获得更好的音乐播放效果,服务器下行音频可能使用 24000 采样率。 -4. **物联网控制推荐 MCP 协议** +4. **协议版本配置** + - 通过设置中的 `version` 字段配置二进制协议版本(1、2 或 3) + - 版本1:直接发送 Opus 数据 + - 版本2:使用带时间戳的二进制协议,适用于服务器端 AEC + - 版本3:使用简化的二进制协议 + +5. **物联网控制推荐 MCP 协议** - 设备与服务器之间的物联网能力发现、状态同步、控制指令等,建议全部通过 MCP 协议(type: "mcp")实现。原有的 type: "iot" 方案已废弃。 - MCP 协议可在 WebSocket、MQTT 等多种底层协议上传输,具备更好的扩展性和标准化能力。 - 详细用法请参考 [MCP 协议文档](./mcp-protocol.md) 及 [MCP 物联网控制用法](./mcp-usage.md)。 -5. **错误或异常 JSON** +6. **错误或异常 JSON** - 当 JSON 中缺少必要字段,例如 `{"type": ...}`,设备端会记录错误日志(`ESP_LOGE(TAG, "Missing message type, data: %s", data);`),不会执行任何业务。 --- -## 8. 消息示例 +## 9. 消息示例 下面给出一个典型的双向消息示例(流程简化示意): @@ -418,13 +483,13 @@ stateDiagram --- -## 9. 总结 +## 10. 总结 本协议通过在 WebSocket 上层传输 JSON 文本与二进制音频帧,完成功能包括音频流上传、TTS 音频播放、语音识别与状态管理、MCP 指令下发等。其核心特征: - **握手阶段**:发送 `"type":"hello"`,等待服务器返回。 -- **音频通道**:采用 Opus 编码的二进制帧双向传输语音流。 -- **JSON 消息**:使用 `"type"` 为核心字段标识不同业务逻辑,包括 TTS、STT、MCP、WakeWord 等。 +- **音频通道**:采用 Opus 编码的二进制帧双向传输语音流,支持多种协议版本。 +- **JSON 消息**:使用 `"type"` 为核心字段标识不同业务逻辑,包括 TTS、STT、MCP、WakeWord、System、Custom 等。 - **扩展性**:可根据实际需求在 JSON 消息中添加字段,或在 headers 里进行额外鉴权。 服务器与设备端需提前约定各类消息的字段含义、时序逻辑以及错误处理规则,方能保证通信顺畅。上述信息可作为基础文档,便于后续对接、开发或扩展。 diff --git a/main/CMakeLists.txt b/main/CMakeLists.txt index 25b98b0b..88c1435c 100644 --- a/main/CMakeLists.txt +++ b/main/CMakeLists.txt @@ -1,11 +1,12 @@ -set(SOURCES "audio_codecs/audio_codec.cc" - "audio_codecs/no_audio_codec.cc" - "audio_codecs/box_audio_codec.cc" - "audio_codecs/es8311_audio_codec.cc" - "audio_codecs/es8374_audio_codec.cc" - "audio_codecs/es8388_audio_codec.cc" - "audio_codecs/dummy_audio_codec.cc" - "audio_processing/audio_debugger.cc" +set(SOURCES "audio/audio_codec.cc" + "audio/audio_service.cc" + "audio/codecs/no_audio_codec.cc" + "audio/codecs/box_audio_codec.cc" + "audio/codecs/es8311_audio_codec.cc" + "audio/codecs/es8374_audio_codec.cc" + "audio/codecs/es8388_audio_codec.cc" + "audio/codecs/dummy_audio_codec.cc" + "audio/processors/audio_debugger.cc" "led/single_led.cc" "led/circular_strip.cc" "led/gpio_led.cc" @@ -15,23 +16,16 @@ set(SOURCES "audio_codecs/audio_codec.cc" "protocols/protocol.cc" "protocols/mqtt_protocol.cc" "protocols/websocket_protocol.cc" - "iot/thing.cc" - "iot/thing_manager.cc" "mcp_server.cc" "system_info.cc" "application.cc" "ota.cc" "settings.cc" - "background_task.cc" "device_state_event.cc" "main.cc" ) -set(INCLUDE_DIRS "." "display" "audio_codecs" "protocols" "audio_processing") - -# 添加 IOT 相关文件 -file(GLOB IOT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/iot/things/*.cc) -list(APPEND SOURCES ${IOT_SOURCES}) +set(INCLUDE_DIRS "." "display" "audio" "protocols") # 添加板级公共文件 file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc) @@ -223,18 +217,16 @@ file(GLOB BOARD_SOURCES list(APPEND SOURCES ${BOARD_SOURCES}) if(CONFIG_USE_AUDIO_PROCESSOR) - list(APPEND SOURCES "audio_processing/afe_audio_processor.cc") + list(APPEND SOURCES "audio/processors/afe_audio_processor.cc") else() - list(APPEND SOURCES "audio_processing/no_audio_processor.cc") + list(APPEND SOURCES "audio/processors/no_audio_processor.cc") endif() if(CONFIG_USE_AFE_WAKE_WORD) - list(APPEND SOURCES "audio_processing/afe_wake_word.cc") + list(APPEND SOURCES "audio/wake_words/afe_wake_word.cc") elseif(CONFIG_USE_ESP_WAKE_WORD) - list(APPEND SOURCES "audio_processing/esp_wake_word.cc") + list(APPEND SOURCES "audio/wake_words/esp_wake_word.cc") elseif(CONFIG_USE_CUSTOM_WAKE_WORD) - list(APPEND SOURCES "audio_processing/custom_wake_word.cc") -else() - list(APPEND SOURCES "audio_processing/no_wake_word.cc") + list(APPEND SOURCES "audio/wake_words/custom_wake_word.cc") endif() # 根据Kconfig选择语言目录 @@ -256,8 +248,8 @@ file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.p3) # 如果目标芯片是 ESP32,则排除特定文件 if(CONFIG_IDF_TARGET_ESP32) - list(REMOVE_ITEM SOURCES "audio_codecs/box_audio_codec.cc" - "audio_codecs/es8388_audio_codec.cc" + list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc" + "audio/codecs/es8388_audio_codec.cc" "led/gpio_led.cc" ) endif() diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index b9940b68..297515f9 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -472,17 +472,6 @@ config RECEIVE_CUSTOM_MESSAGE help 启用接收自定义消息功能,允许设备接收来自服务器的自定义消息(最好通过 MQTT 协议) -choice IOT_PROTOCOL - prompt "IoT Protocol" - default IOT_PROTOCOL_MCP - help - IoT 协议,用于获取设备状态与发送控制指令 - config IOT_PROTOCOL_MCP - bool "MCP 2024-11-05" - config IOT_PROTOCOL_XIAOZHI - bool "Xiaozhi IoT 1.0 (Deprecated)" -endchoice - choice I2S_TYPE_TAIJIPI_S3 depends on BOARD_TYPE_ESP32S3_Taiji_Pi prompt "taiji-pi-S3 I2S Type" diff --git a/main/application.cc b/main/application.cc index 04ebc05a..a0738d9e 100644 --- a/main/application.cc +++ b/main/application.cc @@ -6,26 +6,8 @@ #include "mqtt_protocol.h" #include "websocket_protocol.h" #include "font_awesome_symbols.h" -#include "iot/thing_manager.h" #include "assets/lang_config.h" #include "mcp_server.h" -#include "audio_debugger.h" - -#if CONFIG_USE_AUDIO_PROCESSOR -#include "afe_audio_processor.h" -#else -#include "no_audio_processor.h" -#endif - -#if CONFIG_USE_AFE_WAKE_WORD -#include "afe_wake_word.h" -#elif CONFIG_USE_ESP_WAKE_WORD -#include "esp_wake_word.h" -#elif CONFIG_USE_CUSTOM_WAKE_WORD -#include "custom_wake_word.h" -#else -#include "no_wake_word.h" -#endif #include #include @@ -53,7 +35,6 @@ static const char* const STATE_STRINGS[] = { Application::Application() { event_group_ = xEventGroupCreate(); - background_task_ = new BackgroundTask(4096 * 7); #if CONFIG_USE_DEVICE_AEC aec_mode_ = kAecOnDeviceSide; @@ -63,22 +44,6 @@ Application::Application() { aec_mode_ = kAecOff; #endif -#if CONFIG_USE_AUDIO_PROCESSOR - audio_processor_ = std::make_unique(); -#else - audio_processor_ = std::make_unique(); -#endif - -#if CONFIG_USE_AFE_WAKE_WORD - wake_word_ = std::make_unique(); -#elif CONFIG_USE_ESP_WAKE_WORD - wake_word_ = std::make_unique(); -#elif CONFIG_USE_CUSTOM_WAKE_WORD - wake_word_ = std::make_unique(); -#else - wake_word_ = std::make_unique(); -#endif - esp_timer_create_args_t clock_timer_args = { .callback = [](void* arg) { Application* app = (Application*)arg; @@ -97,9 +62,6 @@ Application::~Application() { esp_timer_stop(clock_timer_handle_); esp_timer_delete(clock_timer_handle_); } - if (background_task_ != nullptr) { - delete background_task_; - } vEventGroupDelete(event_group_); } @@ -108,9 +70,10 @@ void Application::CheckNewVersion(Ota& ota) { int retry_count = 0; int retry_delay = 10; // 初始重试延迟为10秒 + auto& board = Board::GetInstance(); while (true) { SetDeviceState(kDeviceStateActivating); - auto display = Board::GetInstance().GetDisplay(); + auto display = board.GetDisplay(); display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION); if (!ota.CheckVersion()) { @@ -148,40 +111,38 @@ void Application::CheckNewVersion(Ota& ota) { std::string message = std::string(Lang::Strings::NEW_VERSION) + ota.GetFirmwareVersion(); display->SetChatMessage("system", message.c_str()); - auto& board = Board::GetInstance(); board.SetPowerSaveMode(false); - wake_word_->StopDetection(); - // 预先关闭音频输出,避免升级过程有音频操作 - auto codec = board.GetAudioCodec(); - codec->EnableInput(false); - codec->EnableOutput(false); - { - std::lock_guard lock(mutex_); - audio_decode_queue_.clear(); - } - background_task_->WaitForCompletion(); - delete background_task_; - background_task_ = nullptr; + audio_service_.Stop(); vTaskDelay(pdMS_TO_TICKS(1000)); - ota.StartUpgrade([display](int progress, size_t speed) { + bool upgrade_success = ota.StartUpgrade([display](int progress, size_t speed) { char buffer[64]; snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024); display->SetChatMessage("system", buffer); }); - // If upgrade success, the device will reboot and never reach here - display->SetStatus(Lang::Strings::UPGRADE_FAILED); - ESP_LOGI(TAG, "Firmware upgrade failed..."); - vTaskDelay(pdMS_TO_TICKS(3000)); - Reboot(); - return; + if (!upgrade_success) { + // Upgrade failed, restart audio service and continue running + ESP_LOGE(TAG, "Firmware upgrade failed, restarting audio service and continuing operation..."); + audio_service_.Start(); // Restart audio service + board.SetPowerSaveMode(true); // Restore power save mode + Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "sad", Lang::Sounds::P3_EXCLAMATION); + vTaskDelay(pdMS_TO_TICKS(3000)); + // Continue to normal operation (don't break, just fall through) + } else { + // Upgrade success, reboot immediately + ESP_LOGI(TAG, "Firmware upgrade successful, rebooting..."); + display->SetChatMessage("system", "Upgrade successful, rebooting..."); + vTaskDelay(pdMS_TO_TICKS(1000)); // Brief pause to show message + Reboot(); + return; // This line will never be reached after reboot + } } // No new version, mark the current version as valid ota.MarkCurrentVersionValid(); if (!ota.HasActivationCode() && !ota.HasActivationChallenge()) { - xEventGroupSetBits(event_group_, CHECK_NEW_VERSION_DONE_EVENT); + xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); // Exit the loop if done checking new version break; } @@ -197,7 +158,7 @@ void Application::CheckNewVersion(Ota& ota) { ESP_LOGI(TAG, "Activating... %d/%d", i + 1, 10); esp_err_t err = ota.Activate(); if (err == ESP_OK) { - xEventGroupSetBits(event_group_, CHECK_NEW_VERSION_DONE_EVENT); + xEventGroupSetBits(event_group_, MAIN_EVENT_CHECK_NEW_VERSION_DONE); break; } else if (err == ESP_ERR_TIMEOUT) { vTaskDelay(pdMS_TO_TICKS(3000)); @@ -236,7 +197,7 @@ void Application::ShowActivationCode(const std::string& code, const std::string& auto it = std::find_if(digit_sounds.begin(), digit_sounds.end(), [digit](const digit_sound& ds) { return ds.digit == digit; }); if (it != digit_sounds.end()) { - PlaySound(it->sound); + audio_service_.PlaySound(it->sound); } } } @@ -248,8 +209,7 @@ void Application::Alert(const char* status, const char* message, const char* emo display->SetEmotion(emotion); display->SetChatMessage("system", message); if (!sound.empty()) { - ResetDecoder(); - PlaySound(sound); + audio_service_.PlaySound(sound); } } @@ -262,59 +222,17 @@ void Application::DismissAlert() { } } -void Application::PlaySound(const std::string_view& sound) { - // Wait for the previous sound to finish - { - std::unique_lock lock(mutex_); - audio_decode_cv_.wait(lock, [this]() { - return audio_decode_queue_.empty(); - }); - } - background_task_->WaitForCompletion(); - - const char* data = sound.data(); - size_t size = sound.size(); - for (const char* p = data; p < data + size; ) { - auto p3 = (BinaryProtocol3*)p; - p += sizeof(BinaryProtocol3); - - auto payload_size = ntohs(p3->payload_size); - AudioStreamPacket packet; - packet.sample_rate = 16000; - packet.frame_duration = 60; - packet.payload.resize(payload_size); - memcpy(packet.payload.data(), p3->payload, payload_size); - p += payload_size; - - std::lock_guard lock(mutex_); - audio_decode_queue_.emplace_back(std::move(packet)); - } -} - -void Application::EnterAudioTestingMode() { - ESP_LOGI(TAG, "Entering audio testing mode"); - ResetDecoder(); - SetDeviceState(kDeviceStateAudioTesting); -} - -void Application::ExitAudioTestingMode() { - ESP_LOGI(TAG, "Exiting audio testing mode"); - SetDeviceState(kDeviceStateWifiConfiguring); - // Copy audio_testing_queue_ to audio_decode_queue_ - std::lock_guard lock(mutex_); - audio_decode_queue_ = std::move(audio_testing_queue_); - audio_decode_cv_.notify_all(); -} - void Application::ToggleChatState() { if (device_state_ == kDeviceStateActivating) { SetDeviceState(kDeviceStateIdle); return; } else if (device_state_ == kDeviceStateWifiConfiguring) { - EnterAudioTestingMode(); + audio_service_.EnableAudioTesting(true); + SetDeviceState(kDeviceStateAudioTesting); return; } else if (device_state_ == kDeviceStateAudioTesting) { - ExitAudioTestingMode(); + audio_service_.EnableAudioTesting(false); + SetDeviceState(kDeviceStateWifiConfiguring); return; } @@ -350,7 +268,8 @@ void Application::StartListening() { SetDeviceState(kDeviceStateIdle); return; } else if (device_state_ == kDeviceStateWifiConfiguring) { - EnterAudioTestingMode(); + audio_service_.EnableAudioTesting(true); + SetDeviceState(kDeviceStateAudioTesting); return; } @@ -380,7 +299,8 @@ void Application::StartListening() { void Application::StopListening() { if (device_state_ == kDeviceStateAudioTesting) { - ExitAudioTestingMode(); + audio_service_.EnableAudioTesting(false); + SetDeviceState(kDeviceStateWifiConfiguring); return; } @@ -409,43 +329,22 @@ void Application::Start() { /* Setup the display */ auto display = board.GetDisplay(); - /* Setup the audio codec */ + /* Setup the audio service */ auto codec = board.GetAudioCodec(); - opus_decoder_ = std::make_unique(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS); - opus_encoder_ = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); - opus_encoder_->SetComplexity(0); - if (aec_mode_ != kAecOff) { - ESP_LOGI(TAG, "AEC mode: %d, setting opus encoder complexity to 0", aec_mode_); - opus_encoder_->SetComplexity(0); - } else { -#if CONFIG_USE_AUDIO_PROCESSOR - ESP_LOGI(TAG, "Audio processor detected, setting opus encoder complexity to 5"); - opus_encoder_->SetComplexity(5); -#else - ESP_LOGI(TAG, "Audio processor not detected, setting opus encoder complexity to 0"); - opus_encoder_->SetComplexity(0); -#endif - } + audio_service_.Initialize(codec); + audio_service_.Start(); - if (codec->input_sample_rate() != 16000) { - input_resampler_.Configure(codec->input_sample_rate(), 16000); - reference_resampler_.Configure(codec->input_sample_rate(), 16000); - } - codec->Start(); - -#if CONFIG_USE_AUDIO_PROCESSOR - xTaskCreatePinnedToCore([](void* arg) { - Application* app = (Application*)arg; - app->AudioLoop(); - vTaskDelete(NULL); - }, "audio_loop", 4096 * 2, this, 8, &audio_loop_task_handle_, 1); -#else - xTaskCreate([](void* arg) { - Application* app = (Application*)arg; - app->AudioLoop(); - vTaskDelete(NULL); - }, "audio_loop", 4096 * 2, this, 8, &audio_loop_task_handle_); -#endif + AudioServiceCallbacks callbacks; + callbacks.on_send_queue_available = [this]() { + xEventGroupSetBits(event_group_, MAIN_EVENT_SEND_AUDIO); + }; + callbacks.on_wake_word_detected = [this](const std::string& wake_word) { + xEventGroupSetBits(event_group_, MAIN_EVENT_WAKE_WORD_DETECTED); + }; + callbacks.on_vad_change = [this](bool speaking) { + xEventGroupSetBits(event_group_, MAIN_EVENT_VAD_CHANGE); + }; + audio_service_.SetCallbacks(callbacks); /* Start the clock timer to update the status bar */ esp_timer_start_periodic(clock_timer_handle_, 1000000); @@ -464,9 +363,7 @@ void Application::Start() { display->SetStatus(Lang::Strings::LOADING_PROTOCOL); // Add MCP common tools before initializing the protocol -#if CONFIG_IOT_PROTOCOL_MCP McpServer::GetInstance().AddCommonTools(); -#endif if (ota.HasMqttConfig()) { protocol_ = std::make_unique(); @@ -478,13 +375,12 @@ void Application::Start() { } protocol_->OnNetworkError([this](const std::string& message) { - SetDeviceState(kDeviceStateIdle); - Alert(Lang::Strings::ERROR, message.c_str(), "sad", Lang::Sounds::P3_EXCLAMATION); + last_error_message_ = message; + xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR); }); - protocol_->OnIncomingAudio([this](AudioStreamPacket&& packet) { - std::lock_guard lock(mutex_); - if (device_state_ == kDeviceStateSpeaking && audio_decode_queue_.size() < MAX_AUDIO_PACKETS_IN_QUEUE) { - audio_decode_queue_.emplace_back(std::move(packet)); + protocol_->OnIncomingAudio([this](std::unique_ptr packet) { + if (device_state_ == kDeviceStateSpeaking) { + audio_service_.PushPacketToDecodeQueue(std::move(packet)); } }); protocol_->OnAudioChannelOpened([this, codec, &board]() { @@ -493,15 +389,6 @@ void Application::Start() { ESP_LOGW(TAG, "Server sample rate %d does not match device output sample rate %d, resampling may cause distortion", protocol_->server_sample_rate(), codec->output_sample_rate()); } - -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - protocol_->SendIotDescriptors(thing_manager.GetDescriptorsJson()); - std::string states; - if (thing_manager.GetStatesJson(states, false)) { - protocol_->SendIotStates(states); - } -#endif }); protocol_->OnAudioChannelClosed([this, &board]() { board.SetPowerSaveMode(true); @@ -525,7 +412,6 @@ void Application::Start() { }); } else if (strcmp(state->valuestring, "stop") == 0) { Schedule([this]() { - background_task_->WaitForCompletion(); if (device_state_ == kDeviceStateSpeaking) { if (listening_mode_ == kListeningModeManualStop) { SetDeviceState(kDeviceStateIdle); @@ -558,36 +444,11 @@ void Application::Start() { display->SetEmotion(emotion_str.c_str()); }); } -#if CONFIG_RECEIVE_CUSTOM_MESSAGE - } else if (strcmp(type->valuestring, "custom") == 0) { - auto payload = cJSON_GetObjectItem(root, "payload"); - ESP_LOGI(TAG, "Received custom message: %s", cJSON_PrintUnformatted(root)); - if (cJSON_IsObject(payload)) { - Schedule([this, display, payload_str = std::string(cJSON_PrintUnformatted(payload))]() { - display->SetChatMessage("system", payload_str.c_str()); - }); - } else { - ESP_LOGW(TAG, "Invalid custom message format: missing payload"); - } -#endif -#if CONFIG_IOT_PROTOCOL_MCP } else if (strcmp(type->valuestring, "mcp") == 0) { auto payload = cJSON_GetObjectItem(root, "payload"); if (cJSON_IsObject(payload)) { McpServer::GetInstance().ParseMessage(payload); } -#endif -#if CONFIG_IOT_PROTOCOL_XIAOZHI - } else if (strcmp(type->valuestring, "iot") == 0) { - auto commands = cJSON_GetObjectItem(root, "commands"); - if (cJSON_IsArray(commands)) { - auto& thing_manager = iot::ThingManager::GetInstance(); - for (int i = 0; i < cJSON_GetArraySize(commands); ++i) { - auto command = cJSON_GetArrayItem(commands, i); - thing_manager.Invoke(command); - } - } -#endif } else if (strcmp(type->valuestring, "system") == 0) { auto command = cJSON_GetObjectItem(root, "command"); if (cJSON_IsString(command)) { @@ -610,112 +471,24 @@ void Application::Start() { } else { ESP_LOGW(TAG, "Alert command requires status, message and emotion"); } +#if CONFIG_RECEIVE_CUSTOM_MESSAGE + } else if (strcmp(type->valuestring, "custom") == 0) { + auto payload = cJSON_GetObjectItem(root, "payload"); + ESP_LOGI(TAG, "Received custom message: %s", cJSON_PrintUnformatted(root)); + if (cJSON_IsObject(payload)) { + Schedule([this, display, payload_str = std::string(cJSON_PrintUnformatted(payload))]() { + display->SetChatMessage("system", payload_str.c_str()); + }); + } else { + ESP_LOGW(TAG, "Invalid custom message format: missing payload"); + } +#endif } else { ESP_LOGW(TAG, "Unknown message type: %s", type->valuestring); } }); bool protocol_started = protocol_->Start(); - audio_debugger_ = std::make_unique(); - audio_processor_->Initialize(codec); - audio_processor_->OnOutput([this](std::vector&& data) { - { - std::lock_guard lock(mutex_); - if (audio_send_queue_.size() >= MAX_AUDIO_PACKETS_IN_QUEUE) { - ESP_LOGW(TAG, "Too many audio packets in queue, drop the newest packet"); - return; - } - } - background_task_->Schedule([this, data = std::move(data)]() mutable { - opus_encoder_->Encode(std::move(data), [this](std::vector&& opus) { - AudioStreamPacket packet; - packet.payload = std::move(opus); -#ifdef CONFIG_USE_SERVER_AEC - { - std::lock_guard lock(timestamp_mutex_); - if (!timestamp_queue_.empty()) { - packet.timestamp = timestamp_queue_.front(); - timestamp_queue_.pop_front(); - } else { - packet.timestamp = 0; - } - - if (timestamp_queue_.size() > 3) { // 限制队列长度3 - timestamp_queue_.pop_front(); // 该包发送前先出队保持队列长度 - return; - } - } -#endif - std::lock_guard lock(mutex_); - if (audio_send_queue_.size() >= MAX_AUDIO_PACKETS_IN_QUEUE) { - ESP_LOGW(TAG, "Too many audio packets in queue, drop the oldest packet"); - audio_send_queue_.pop_front(); - } - audio_send_queue_.emplace_back(std::move(packet)); - xEventGroupSetBits(event_group_, SEND_AUDIO_EVENT); - }); - }); - }); - audio_processor_->OnVadStateChange([this](bool speaking) { - if (device_state_ == kDeviceStateListening) { - Schedule([this, speaking]() { - if (speaking) { - voice_detected_ = true; - } else { - voice_detected_ = false; - } - auto led = Board::GetInstance().GetLed(); - led->OnStateChanged(); - }); - } - }); - - wake_word_->Initialize(codec); - wake_word_->OnWakeWordDetected([this](const std::string& wake_word) { - Schedule([this, &wake_word]() { - if (!protocol_) { - return; - } - - if (device_state_ == kDeviceStateIdle) { - wake_word_->EncodeWakeWordData(); - - if (!protocol_->IsAudioChannelOpened()) { - SetDeviceState(kDeviceStateConnecting); - if (!protocol_->OpenAudioChannel()) { - wake_word_->StartDetection(); - return; - } - } - - ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); -#if CONFIG_USE_AFE_WAKE_WORD || CONFIG_USE_CUSTOM_WAKE_WORD - AudioStreamPacket packet; - // Encode and send the wake word data to the server - while (wake_word_->GetWakeWordOpus(packet.payload)) { - protocol_->SendAudio(packet); - } - // Set the chat state to wake word detected - protocol_->SendWakeWordDetected(wake_word); -#else - // Play the pop up sound to indicate the wake word is detected - // And wait 60ms to make sure the queue has been processed by audio task - ResetDecoder(); - PlaySound(Lang::Sounds::P3_POPUP); - vTaskDelay(pdMS_TO_TICKS(60)); -#endif - SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); - } else if (device_state_ == kDeviceStateSpeaking) { - AbortSpeaking(kAbortReasonWakeWordDetected); - } else if (device_state_ == kDeviceStateActivating) { - SetDeviceState(kDeviceStateIdle); - } - }); - }); - wake_word_->StartDetection(); - - // Wait for the new version check to finish - xEventGroupWaitBits(event_group_, CHECK_NEW_VERSION_DONE_EVENT, pdTRUE, pdFALSE, portMAX_DELAY); SetDeviceState(kDeviceStateIdle); has_server_time_ = ota.HasServerTime(); @@ -724,8 +497,7 @@ void Application::Start() { display->ShowNotification(message.c_str()); display->SetChatMessage("system", ""); // Play the success sound to indicate the device is ready - ResetDecoder(); - PlaySound(Lang::Sounds::P3_SUCCESS); + audio_service_.PlaySound(Lang::Sounds::P3_SUCCESS); } // Print heap stats @@ -746,19 +518,6 @@ void Application::OnClockTimer() { // SystemInfo::PrintTaskCpuUsage(pdMS_TO_TICKS(1000)); // SystemInfo::PrintTaskList(); SystemInfo::PrintHeapStats(); - - // If we have synchronized server time, set the status to clock "HH:MM" if the device is idle - if (has_server_time_) { - if (device_state_ == kDeviceStateIdle) { - Schedule([this]() { - // Set status to clock "HH:MM" - time_t now = time(NULL); - char time_str[64]; - strftime(time_str, sizeof(time_str), "%H:%M ", localtime(&now)); - Board::GetInstance().GetDisplay()->SetStatus(time_str); - }); - } - } } } @@ -768,7 +527,7 @@ void Application::Schedule(std::function callback) { std::lock_guard lock(mutex_); main_tasks_.push_back(std::move(callback)); } - xEventGroupSetBits(event_group_, SCHEDULE_EVENT); + xEventGroupSetBits(event_group_, MAIN_EVENT_SCHEDULE); } // The Main Event Loop controls the chat state and websocket connection @@ -779,20 +538,36 @@ void Application::MainEventLoop() { vTaskPrioritySet(NULL, 3); while (true) { - auto bits = xEventGroupWaitBits(event_group_, SCHEDULE_EVENT | SEND_AUDIO_EVENT, pdTRUE, pdFALSE, portMAX_DELAY); + auto bits = xEventGroupWaitBits(event_group_, MAIN_EVENT_SCHEDULE | + MAIN_EVENT_SEND_AUDIO | + MAIN_EVENT_WAKE_WORD_DETECTED | + MAIN_EVENT_VAD_CHANGE | + MAIN_EVENT_ERROR, pdTRUE, pdFALSE, portMAX_DELAY); + if (bits & MAIN_EVENT_ERROR) { + SetDeviceState(kDeviceStateIdle); + Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "sad", Lang::Sounds::P3_EXCLAMATION); + } - if (bits & SEND_AUDIO_EVENT) { - std::unique_lock lock(mutex_); - auto packets = std::move(audio_send_queue_); - lock.unlock(); - for (auto& packet : packets) { - if (!protocol_->SendAudio(packet)) { + if (bits & MAIN_EVENT_SEND_AUDIO) { + while (auto packet = audio_service_.PopPacketFromSendQueue()) { + if (!protocol_->SendAudio(std::move(packet))) { break; } } } - if (bits & SCHEDULE_EVENT) { + if (bits & MAIN_EVENT_WAKE_WORD_DETECTED) { + OnWakeWordDetected(); + } + + if (bits & MAIN_EVENT_VAD_CHANGE) { + if (device_state_ == kDeviceStateListening) { + auto led = Board::GetInstance().GetLed(); + led->OnStateChanged(); + } + } + + if (bits & MAIN_EVENT_SCHEDULE) { std::unique_lock lock(mutex_); auto tasks = std::move(main_tasks_); lock.unlock(); @@ -803,170 +578,43 @@ void Application::MainEventLoop() { } } -// The Audio Loop is used to input and output audio data -void Application::AudioLoop() { - auto codec = Board::GetInstance().GetAudioCodec(); - while (true) { - OnAudioInput(); - if (codec->output_enabled()) { - OnAudioOutput(); - } - } -} - -void Application::OnAudioOutput() { - if (busy_decoding_audio_) { +void Application::OnWakeWordDetected() { + if (!protocol_) { return; } - auto now = std::chrono::steady_clock::now(); - auto codec = Board::GetInstance().GetAudioCodec(); - const int max_silence_seconds = 10; + if (device_state_ == kDeviceStateIdle) { + audio_service_.EncodeWakeWord(); - std::unique_lock lock(mutex_); - if (audio_decode_queue_.empty()) { - // Disable the output if there is no audio data for a long time - if (device_state_ == kDeviceStateIdle) { - auto duration = std::chrono::duration_cast(now - last_output_time_).count(); - if (duration > max_silence_seconds) { - codec->EnableOutput(false); + if (!protocol_->IsAudioChannelOpened()) { + SetDeviceState(kDeviceStateConnecting); + if (!protocol_->OpenAudioChannel()) { + audio_service_.EnableWakeWordDetection(true); + return; } } - return; - } - auto packet = std::move(audio_decode_queue_.front()); - audio_decode_queue_.pop_front(); - lock.unlock(); - audio_decode_cv_.notify_all(); - - // Synchronize the sample rate and frame duration - SetDecodeSampleRate(packet.sample_rate, packet.frame_duration); - - busy_decoding_audio_ = true; - if (!background_task_->Schedule([this, codec, packet = std::move(packet)]() mutable { - busy_decoding_audio_ = false; - if (aborted_) { - return; + auto wake_word = audio_service_.GetLastWakeWord(); + ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str()); +#if CONFIG_USE_AFE_WAKE_WORD || CONFIG_USE_CUSTOM_WAKE_WORD + // Encode and send the wake word data to the server + while (auto packet = audio_service_.PopWakeWordPacket()) { + protocol_->SendAudio(std::move(packet)); } - - std::vector pcm; - if (!opus_decoder_->Decode(std::move(packet.payload), pcm)) { - return; - } - // Resample if the sample rate is different - if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { - int target_size = output_resampler_.GetOutputSamples(pcm.size()); - std::vector resampled(target_size); - output_resampler_.Process(pcm.data(), pcm.size(), resampled.data()); - pcm = std::move(resampled); - } - codec->OutputData(pcm); -#ifdef CONFIG_USE_SERVER_AEC - std::lock_guard lock(timestamp_mutex_); - timestamp_queue_.push_back(packet.timestamp); + // Set the chat state to wake word detected + protocol_->SendWakeWordDetected(wake_word); +#else + // Play the pop up sound to indicate the wake word is detected + audio_service_.PlaySound(Lang::Sounds::P3_POPUP); #endif - last_output_time_ = std::chrono::steady_clock::now(); - })) { - busy_decoding_audio_ = false; + SetListeningMode(aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime); + } else if (device_state_ == kDeviceStateSpeaking) { + AbortSpeaking(kAbortReasonWakeWordDetected); + } else if (device_state_ == kDeviceStateActivating) { + SetDeviceState(kDeviceStateIdle); } } -void Application::OnAudioInput() { - if (device_state_ == kDeviceStateAudioTesting) { - if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) { - ExitAudioTestingMode(); - return; - } - std::vector data; - int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000; - if (ReadAudio(data, 16000, samples)) { - background_task_->Schedule([this, data = std::move(data)]() mutable { - opus_encoder_->Encode(std::move(data), [this](std::vector&& opus) { - AudioStreamPacket packet; - packet.payload = std::move(opus); - packet.frame_duration = OPUS_FRAME_DURATION_MS; - packet.sample_rate = 16000; - std::lock_guard lock(mutex_); - audio_testing_queue_.push_back(std::move(packet)); - }); - }); - return; - } - } - - if (wake_word_->IsDetectionRunning()) { - std::vector data; - int samples = wake_word_->GetFeedSize(); - if (samples > 0) { - if (ReadAudio(data, 16000, samples)) { - wake_word_->Feed(data); - return; - } - } - } - - if (audio_processor_->IsRunning()) { - std::vector data; - int samples = audio_processor_->GetFeedSize(); - if (samples > 0) { - if (ReadAudio(data, 16000, samples)) { - audio_processor_->Feed(data); - return; - } - } - } - - vTaskDelay(pdMS_TO_TICKS(OPUS_FRAME_DURATION_MS / 2)); -} - -bool Application::ReadAudio(std::vector& data, int sample_rate, int samples) { - auto codec = Board::GetInstance().GetAudioCodec(); - if (!codec->input_enabled()) { - return false; - } - - if (codec->input_sample_rate() != sample_rate) { - data.resize(samples * codec->input_sample_rate() / sample_rate); - if (!codec->InputData(data)) { - return false; - } - if (codec->input_channels() == 2) { - auto mic_channel = std::vector(data.size() / 2); - auto reference_channel = std::vector(data.size() / 2); - for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) { - mic_channel[i] = data[j]; - reference_channel[i] = data[j + 1]; - } - auto resampled_mic = std::vector(input_resampler_.GetOutputSamples(mic_channel.size())); - auto resampled_reference = std::vector(reference_resampler_.GetOutputSamples(reference_channel.size())); - input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data()); - reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data()); - data.resize(resampled_mic.size() + resampled_reference.size()); - for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) { - data[j] = resampled_mic[i]; - data[j + 1] = resampled_reference[i]; - } - } else { - auto resampled = std::vector(input_resampler_.GetOutputSamples(data.size())); - input_resampler_.Process(data.data(), data.size(), resampled.data()); - data = std::move(resampled); - } - } else { - data.resize(samples); - if (!codec->InputData(data)) { - return false; - } - } - - // 音频调试:发送原始音频数据 - if (audio_debugger_) { - audio_debugger_->Feed(data); - } - - return true; -} - void Application::AbortSpeaking(AbortReason reason) { ESP_LOGI(TAG, "Abort speaking"); aborted_ = true; @@ -987,8 +635,6 @@ void Application::SetDeviceState(DeviceState state) { auto previous_state = device_state_; device_state_ = state; ESP_LOGI(TAG, "STATE: %s", STATE_STRINGS[device_state_]); - // The state is changed, wait for all background tasks to finish - background_task_->WaitForCompletion(); // Send the state change event DeviceStateEventManager::GetInstance().PostStateChangeEvent(previous_state, state); @@ -1002,51 +648,39 @@ void Application::SetDeviceState(DeviceState state) { case kDeviceStateIdle: display->SetStatus(Lang::Strings::STANDBY); display->SetEmotion("neutral"); - audio_processor_->Stop(); - wake_word_->StartDetection(); + audio_service_.EnableVoiceProcessing(false); + audio_service_.EnableWakeWordDetection(true); break; case kDeviceStateConnecting: display->SetStatus(Lang::Strings::CONNECTING); display->SetEmotion("neutral"); display->SetChatMessage("system", ""); - timestamp_queue_.clear(); break; case kDeviceStateListening: display->SetStatus(Lang::Strings::LISTENING); display->SetEmotion("neutral"); - // Update the IoT states before sending the start listening command -#if CONFIG_IOT_PROTOCOL_XIAOZHI - UpdateIotStates(); -#endif // Make sure the audio processor is running - if (!audio_processor_->IsRunning()) { + if (!audio_service_.IsAudioProcessorRunning()) { // Send the start listening command protocol_->SendStartListening(listening_mode_); - if (previous_state == kDeviceStateSpeaking) { - audio_decode_queue_.clear(); - audio_decode_cv_.notify_all(); - // FIXME: Wait for the speaker to empty the buffer - vTaskDelay(pdMS_TO_TICKS(120)); - } - opus_encoder_->ResetState(); - audio_processor_->Start(); - wake_word_->StopDetection(); + audio_service_.EnableVoiceProcessing(true); + audio_service_.EnableWakeWordDetection(false); } break; case kDeviceStateSpeaking: display->SetStatus(Lang::Strings::SPEAKING); if (listening_mode_ != kListeningModeRealtime) { - audio_processor_->Stop(); + audio_service_.EnableVoiceProcessing(false); // Only AFE wake word can be detected in speaking mode #if CONFIG_USE_AFE_WAKE_WORD - wake_word_->StartDetection(); + audio_service_.EnableWakeWordDetection(true); #else - wake_word_->StopDetection(); + audio_service_.EnableWakeWordDetection(false); #endif } - ResetDecoder(); + audio_service_.ResetDecoder(); break; default: // Do nothing @@ -1054,41 +688,6 @@ void Application::SetDeviceState(DeviceState state) { } } -void Application::ResetDecoder() { - std::lock_guard lock(mutex_); - opus_decoder_->ResetState(); - audio_decode_queue_.clear(); - audio_decode_cv_.notify_all(); - last_output_time_ = std::chrono::steady_clock::now(); - auto codec = Board::GetInstance().GetAudioCodec(); - codec->EnableOutput(true); -} - -void Application::SetDecodeSampleRate(int sample_rate, int frame_duration) { - if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) { - return; - } - - opus_decoder_.reset(); - opus_decoder_ = std::make_unique(sample_rate, 1, frame_duration); - - auto codec = Board::GetInstance().GetAudioCodec(); - if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { - ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate()); - output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate()); - } -} - -void Application::UpdateIotStates() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - std::string states; - if (thing_manager.GetStatesJson(states, true)) { - protocol_->SendIotStates(states); - } -#endif -} - void Application::Reboot() { ESP_LOGI(TAG, "Rebooting..."); esp_restart(); @@ -1124,6 +723,10 @@ bool Application::CanEnterSleepMode() { return false; } + if (!audio_service_.IsIdle()) { + return false; + } + // Now it is safe to enter sleep mode return true; } @@ -1143,15 +746,15 @@ void Application::SetAecMode(AecMode mode) { auto display = board.GetDisplay(); switch (aec_mode_) { case kAecOff: - audio_processor_->EnableDeviceAec(false); + audio_service_.EnableDeviceAec(false); display->ShowNotification(Lang::Strings::RTC_MODE_OFF); break; case kAecOnServerSide: - audio_processor_->EnableDeviceAec(false); + audio_service_.EnableDeviceAec(false); display->ShowNotification(Lang::Strings::RTC_MODE_ON); break; case kAecOnDeviceSide: - audio_processor_->EnableDeviceAec(true); + audio_service_.EnableDeviceAec(true); display->ShowNotification(Lang::Strings::RTC_MODE_ON); break; } @@ -1162,3 +765,7 @@ void Application::SetAecMode(AecMode mode) { } }); } + +void Application::PlaySound(const std::string_view& sound) { + audio_service_.PlaySound(sound); +} \ No newline at end of file diff --git a/main/application.h b/main/application.h index 221a5c75..83b2ff3c 100644 --- a/main/application.h +++ b/main/application.h @@ -8,26 +8,21 @@ #include #include -#include +#include #include -#include #include -#include -#include -#include - #include "protocol.h" #include "ota.h" -#include "background_task.h" -#include "audio_processor.h" -#include "wake_word.h" -#include "audio_debugger.h" +#include "audio_service.h" #include "device_state_event.h" -#define SCHEDULE_EVENT (1 << 0) -#define SEND_AUDIO_EVENT (1 << 1) -#define CHECK_NEW_VERSION_DONE_EVENT (1 << 2) +#define MAIN_EVENT_SCHEDULE (1 << 0) +#define MAIN_EVENT_SEND_AUDIO (1 << 1) +#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2) +#define MAIN_EVENT_VAD_CHANGE (1 << 3) +#define MAIN_EVENT_ERROR (1 << 4) +#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5) enum AecMode { kAecOff, @@ -35,10 +30,6 @@ enum AecMode { kAecOnServerSide, }; -#define OPUS_FRAME_DURATION_MS 60 -#define MAX_AUDIO_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) -#define AUDIO_TESTING_MAX_DURATION_MS 10000 - class Application { public: static Application& GetInstance() { @@ -51,7 +42,7 @@ public: void Start(); DeviceState GetDeviceState() const { return device_state_; } - bool IsVoiceDetected() const { return voice_detected_; } + bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); } void Schedule(std::function callback); void SetDeviceState(DeviceState state); void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = ""); @@ -60,72 +51,41 @@ public: void ToggleChatState(); void StartListening(); void StopListening(); - void UpdateIotStates(); void Reboot(); void WakeWordInvoke(const std::string& wake_word); - void PlaySound(const std::string_view& sound); bool CanEnterSleepMode(); void SendMcpMessage(const std::string& payload); void SetAecMode(AecMode mode); - bool ReadAudio(std::vector& data, int sample_rate, int samples); AecMode GetAecMode() const { return aec_mode_; } - BackgroundTask* GetBackgroundTask() const { return background_task_; } + void PlaySound(const std::string_view& sound); + AudioService& GetAudioService() { return audio_service_; } private: Application(); ~Application(); - std::unique_ptr wake_word_; - std::unique_ptr audio_processor_; - std::unique_ptr audio_debugger_; std::mutex mutex_; - std::list> main_tasks_; + std::deque> main_tasks_; std::unique_ptr protocol_; EventGroupHandle_t event_group_ = nullptr; esp_timer_handle_t clock_timer_handle_ = nullptr; volatile DeviceState device_state_ = kDeviceStateUnknown; ListeningMode listening_mode_ = kListeningModeAutoStop; AecMode aec_mode_ = kAecOff; + std::string last_error_message_; + AudioService audio_service_; bool has_server_time_ = false; bool aborted_ = false; - bool voice_detected_ = false; - bool busy_decoding_audio_ = false; int clock_ticks_ = 0; TaskHandle_t check_new_version_task_handle_ = nullptr; - // Audio encode / decode - TaskHandle_t audio_loop_task_handle_ = nullptr; - BackgroundTask* background_task_ = nullptr; - std::chrono::steady_clock::time_point last_output_time_; - std::list audio_send_queue_; - std::list audio_decode_queue_; - std::condition_variable audio_decode_cv_; - std::list audio_testing_queue_; - - // 新增:用于维护音频包的timestamp队列 - std::list timestamp_queue_; - std::mutex timestamp_mutex_; - - std::unique_ptr opus_encoder_; - std::unique_ptr opus_decoder_; - - OpusResampler input_resampler_; - OpusResampler reference_resampler_; - OpusResampler output_resampler_; - void MainEventLoop(); - void OnAudioInput(); - void OnAudioOutput(); - void ResetDecoder(); - void SetDecodeSampleRate(int sample_rate, int frame_duration); + void OnWakeWordDetected(); void CheckNewVersion(Ota& ota); void ShowActivationCode(const std::string& code, const std::string& message); void OnClockTimer(); void SetListeningMode(ListeningMode mode); - void AudioLoop(); - void EnterAudioTestingMode(); - void ExitAudioTestingMode(); }; #endif // _APPLICATION_H_ diff --git a/main/audio/README.md b/main/audio/README.md new file mode 100644 index 00000000..6159f9ad --- /dev/null +++ b/main/audio/README.md @@ -0,0 +1,88 @@ +# Audio Service Architecture + +The audio service is a core component responsible for managing all audio-related functionalities, including capturing audio from the microphone, processing it, encoding/decoding, and playing back audio through the speaker. It is designed to be modular and efficient, running its main operations in dedicated FreeRTOS tasks to ensure real-time performance. + +## Key Components + +- **`AudioService`**: The central orchestrator. It initializes and manages all other audio components, tasks, and data queues. +- **`AudioCodec`**: A hardware abstraction layer (HAL) for the physical audio codec chip. It handles the raw I2S communication for audio input and output. +- **`AudioProcessor`**: Performs real-time audio processing on the microphone input stream. This typically includes Acoustic Echo Cancellation (AEC), noise suppression, and Voice Activity Detection (VAD). `AfeAudioProcessor` is the default implementation, utilizing the ESP-ADF Audio Front-End. +- **`WakeWord`**: Detects keywords (e.g., "你好,小智", "Hi, ESP") from the audio stream. It runs independently from the main audio processor until a wake word is detected. +- **`OpusEncoderWrapper` / `OpusDecoderWrapper`**: Manages the encoding of PCM audio to the Opus format and decoding Opus packets back to PCM. Opus is used for its high compression and low latency, making it ideal for voice streaming. +- **`OpusResampler`**: A utility to convert audio streams between different sample rates (e.g., resampling from the codec's native sample rate to the required 16kHz for processing). + +## Threading Model + +The service operates on three primary tasks to handle the different stages of the audio pipeline concurrently: + +1. **`AudioInputTask`**: Solely responsible for reading raw PCM data from the `AudioCodec`. It then feeds this data to either the `WakeWord` engine or the `AudioProcessor` based on the current state. +2. **`AudioOutputTask`**: Responsible for playing audio. It retrieves decoded PCM data from the `audio_playback_queue_` and sends it to the `AudioCodec` to be played on the speaker. +3. **`OpusCodecTask`**: A worker task that handles both encoding and decoding. It fetches raw audio from `audio_encode_queue_`, encodes it into Opus packets, and places them in the `audio_send_queue_`. Concurrently, it fetches Opus packets from `audio_decode_queue_`, decodes them into PCM, and places the result in the `audio_playback_queue_`. + +## Data Flow + +There are two primary data flows: audio input (uplink) and audio output (downlink). + +### 1. Audio Input (Uplink) Flow + +This flow captures audio from the microphone, processes it, encodes it, and prepares it for sending to a server. + +```mermaid +graph TD + subgraph Device + Mic[("Microphone")] -->|I2S| Codec(AudioCodec) + + subgraph AudioInputTask + Codec -->|Raw PCM| Read(ReadAudioData) + Read -->|16kHz PCM| Processor(AudioProcessor) + end + + subgraph OpusCodecTask + Processor -->|Clean PCM| EncodeQueue(audio_encode_queue_) + EncodeQueue --> Encoder(OpusEncoder) + Encoder -->|Opus Packet| SendQueue(audio_send_queue_) + end + + SendQueue --> |"PopPacketFromSendQueue()"| App(Application Layer) + end + + App -->|Network| Server((Cloud Server)) +``` + +- The `AudioInputTask` continuously reads raw PCM data from the `AudioCodec`. +- This data is fed into an `AudioProcessor` for cleaning (AEC, VAD). +- The processed PCM data is pushed into the `audio_encode_queue_`. +- The `OpusCodecTask` picks up the PCM data, encodes it into Opus format, and pushes the resulting packet to the `audio_send_queue_`. +- The application can then retrieve these Opus packets and send them over the network. + +### 2. Audio Output (Downlink) Flow + +This flow receives encoded audio data, decodes it, and plays it on the speaker. + +```mermaid +graph TD + Server((Cloud Server)) -->|Network| App(Application Layer) + + subgraph Device + App -->|"PushPacketToDecodeQueue()"| DecodeQueue(audio_decode_queue_) + + subgraph OpusCodecTask + DecodeQueue -->|Opus Packet| Decoder(OpusDecoder) + Decoder -->|PCM| PlaybackQueue(audio_playback_queue_) + end + + subgraph AudioOutputTask + PlaybackQueue -->|PCM| Codec(AudioCodec) + end + + Codec -->|I2S| Speaker[("Speaker")] + end +``` + +- The application receives Opus packets from the network and pushes them into the `audio_decode_queue_`. +- The `OpusCodecTask` retrieves these packets, decodes them back into PCM data, and pushes the data to the `audio_playback_queue_`. +- The `AudioOutputTask` takes the PCM data from the queue and sends it to the `AudioCodec` for playback. + +## Power Management + +To conserve energy, the audio codec's input (ADC) and output (DAC) channels are automatically disabled after a period of inactivity (`AUDIO_POWER_TIMEOUT_MS`). A timer (`audio_power_timer_`) periodically checks for activity and manages the power state. The channels are automatically re-enabled when new audio needs to be captured or played. \ No newline at end of file diff --git a/main/audio_codecs/audio_codec.cc b/main/audio/audio_codec.cc similarity index 100% rename from main/audio_codecs/audio_codec.cc rename to main/audio/audio_codec.cc diff --git a/main/audio_codecs/audio_codec.h b/main/audio/audio_codec.h similarity index 100% rename from main/audio_codecs/audio_codec.h rename to main/audio/audio_codec.h diff --git a/main/audio_processing/audio_processor.h b/main/audio/audio_processor.h similarity index 100% rename from main/audio_processing/audio_processor.h rename to main/audio/audio_processor.h diff --git a/main/audio/audio_service.cc b/main/audio/audio_service.cc new file mode 100644 index 00000000..e43cef32 --- /dev/null +++ b/main/audio/audio_service.cc @@ -0,0 +1,544 @@ +#include "audio_service.h" +#include + +#if CONFIG_USE_AUDIO_PROCESSOR +#include "processors/afe_audio_processor.h" +#else +#include "processors/no_audio_processor.h" +#endif + +#if CONFIG_USE_AFE_WAKE_WORD +#include "wake_words/afe_wake_word.h" +#elif CONFIG_USE_ESP_WAKE_WORD +#include "wake_words/esp_wake_word.h" +#elif CONFIG_USE_CUSTOM_WAKE_WORD +#include "wake_words/custom_wake_word.h" +#endif + +#define TAG "AudioService" + + +AudioService::AudioService() { + event_group_ = xEventGroupCreate(); +} + +AudioService::~AudioService() { + if (event_group_ != nullptr) { + vEventGroupDelete(event_group_); + } +} + + +void AudioService::Initialize(AudioCodec* codec) { + codec_ = codec; + codec_->Start(); + + /* Setup the audio codec */ + opus_decoder_ = std::make_unique(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS); + opus_encoder_ = std::make_unique(16000, 1, OPUS_FRAME_DURATION_MS); + opus_encoder_->SetComplexity(0); + + if (codec->input_sample_rate() != 16000) { + input_resampler_.Configure(codec->input_sample_rate(), 16000); + reference_resampler_.Configure(codec->input_sample_rate(), 16000); + } + + audio_debugger_ = std::make_unique(); +#if CONFIG_USE_AUDIO_PROCESSOR + audio_processor_ = std::make_unique(); +#else + audio_processor_ = std::make_unique(); +#endif + +#if CONFIG_USE_AFE_WAKE_WORD + wake_word_ = std::make_unique(); +#elif CONFIG_USE_ESP_WAKE_WORD + wake_word_ = std::make_unique(); +#elif CONFIG_USE_CUSTOM_WAKE_WORD + wake_word_ = std::make_unique(); +#else + wake_word_ = nullptr; +#endif + + audio_processor_->OnOutput([this](std::vector&& data) { + PushTaskToEncodeQueue(kAudioTaskTypeEncodeToSendQueue, std::move(data)); + }); + + audio_processor_->OnVadStateChange([this](bool speaking) { + voice_detected_ = speaking; + if (callbacks_.on_vad_change) { + callbacks_.on_vad_change(speaking); + } + }); + + if (wake_word_) { + wake_word_->OnWakeWordDetected([this](const std::string& wake_word) { + if (callbacks_.on_wake_word_detected) { + callbacks_.on_wake_word_detected(wake_word); + } + }); + } + + esp_timer_create_args_t audio_power_timer_args = { + .callback = [](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->CheckAndUpdateAudioPowerState(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "audio_power_timer", + .skip_unhandled_events = true, + }; + esp_timer_create(&audio_power_timer_args, &audio_power_timer_); +} + +void AudioService::Start() { + service_stopped_ = false; + xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING); + + esp_timer_start_periodic(audio_power_timer_, 1000000); + + /* Start the audio input task */ +#if CONFIG_USE_AUDIO_PROCESSOR + xTaskCreatePinnedToCore([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioInputTask(); + vTaskDelete(NULL); + }, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 1); +#else + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioInputTask(); + vTaskDelete(NULL); + }, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_); +#endif + + /* Start the audio output task */ + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->AudioOutputTask(); + vTaskDelete(NULL); + }, "audio_output", 2048, this, 3, &audio_output_task_handle_); + + /* Start the opus codec task */ + xTaskCreate([](void* arg) { + AudioService* audio_service = (AudioService*)arg; + audio_service->OpusCodecTask(); + vTaskDelete(NULL); + }, "opus_codec", 4096 * 7, this, 2, &opus_codec_task_handle_); +} + +void AudioService::Stop() { + esp_timer_stop(audio_power_timer_); + service_stopped_ = true; + xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | + AS_EVENT_WAKE_WORD_RUNNING | + AS_EVENT_AUDIO_PROCESSOR_RUNNING); + + std::lock_guard lock(audio_queue_mutex_); + audio_encode_queue_.clear(); + audio_decode_queue_.clear(); + audio_playback_queue_.clear(); + audio_testing_queue_.clear(); + audio_queue_cv_.notify_all(); +} + +bool AudioService::ReadAudioData(std::vector& data, int sample_rate, int samples) { + if (!codec_->input_enabled()) { + codec_->EnableInput(true); + esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); + } + + if (codec_->input_sample_rate() != sample_rate) { + data.resize(samples * codec_->input_sample_rate() / sample_rate); + if (!codec_->InputData(data)) { + return false; + } + if (codec_->input_channels() == 2) { + auto mic_channel = std::vector(data.size() / 2); + auto reference_channel = std::vector(data.size() / 2); + for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) { + mic_channel[i] = data[j]; + reference_channel[i] = data[j + 1]; + } + auto resampled_mic = std::vector(input_resampler_.GetOutputSamples(mic_channel.size())); + auto resampled_reference = std::vector(reference_resampler_.GetOutputSamples(reference_channel.size())); + input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data()); + reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data()); + data.resize(resampled_mic.size() + resampled_reference.size()); + for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) { + data[j] = resampled_mic[i]; + data[j + 1] = resampled_reference[i]; + } + } else { + auto resampled = std::vector(input_resampler_.GetOutputSamples(data.size())); + input_resampler_.Process(data.data(), data.size(), resampled.data()); + data = std::move(resampled); + } + } else { + data.resize(samples); + if (!codec_->InputData(data)) { + return false; + } + } + + /* Update the last input time */ + last_input_time_ = std::chrono::steady_clock::now(); + debug_statistics_.input_count++; + + // 音频调试:发送原始音频数据 + if (audio_debugger_) { + audio_debugger_->Feed(data); + } + + return true; +} + +void AudioService::AudioInputTask() { + while (true) { + EventBits_t bits = xEventGroupWaitBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING | + AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING, + pdFALSE, pdFALSE, portMAX_DELAY); + + if (service_stopped_) { + break; + } + if (audio_input_need_warmup_) { + audio_input_need_warmup_ = false; + vTaskDelay(pdMS_TO_TICKS(120)); + continue; + } + + /* Used for audio testing in NetworkConfiguring mode by clicking the BOOT button */ + if (bits & AS_EVENT_AUDIO_TESTING_RUNNING) { + if (audio_testing_queue_.size() >= AUDIO_TESTING_MAX_DURATION_MS / OPUS_FRAME_DURATION_MS) { + ESP_LOGW(TAG, "Audio testing queue is full, stopping audio testing"); + EnableAudioTesting(false); + continue; + } + std::vector data; + int samples = OPUS_FRAME_DURATION_MS * 16000 / 1000; + if (ReadAudioData(data, 16000, samples)) { + // If input channels is 2, we need to fetch the left channel data + if (codec_->input_channels() == 2) { + auto mono_data = std::vector(data.size() / 2); + for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) { + mono_data[i] = data[j]; + } + data = std::move(mono_data); + } + PushTaskToEncodeQueue(kAudioTaskTypeEncodeToTestingQueue, std::move(data)); + continue; + } + } + + /* Feed the wake word */ + if (bits & AS_EVENT_WAKE_WORD_RUNNING) { + std::vector data; + int samples = wake_word_->GetFeedSize(); + if (samples > 0) { + if (ReadAudioData(data, 16000, samples)) { + wake_word_->Feed(data); + continue; + } + } + } + + /* Feed the audio processor */ + if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) { + std::vector data; + int samples = audio_processor_->GetFeedSize(); + if (samples > 0) { + if (ReadAudioData(data, 16000, samples)) { + audio_processor_->Feed(data); + continue; + } + } + } + + ESP_LOGE(TAG, "Should not be here, bits: %lx", bits); + break; + } + + ESP_LOGW(TAG, "Audio input task stopped"); +} + +void AudioService::AudioOutputTask() { + while (true) { + std::unique_lock lock(audio_queue_mutex_); + audio_queue_cv_.wait(lock, [this]() { return !audio_playback_queue_.empty() || service_stopped_; }); + if (service_stopped_) { + break; + } + + auto task = std::move(audio_playback_queue_.front()); + audio_playback_queue_.pop_front(); + audio_queue_cv_.notify_all(); + lock.unlock(); + + if (!codec_->output_enabled()) { + codec_->EnableOutput(true); + esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000); + } + codec_->OutputData(task->pcm); + + /* Update the last output time */ + last_output_time_ = std::chrono::steady_clock::now(); + debug_statistics_.playback_count++; + } + + ESP_LOGW(TAG, "Audio output task stopped"); +} + +void AudioService::OpusCodecTask() { + while (true) { + std::unique_lock lock(audio_queue_mutex_); + audio_queue_cv_.wait(lock, [this]() { + return service_stopped_ || + (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) || + (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE); + }); + if (service_stopped_) { + break; + } + + /* Decode the audio from decode queue */ + if (!audio_decode_queue_.empty() && audio_playback_queue_.size() < MAX_PLAYBACK_TASKS_IN_QUEUE) { + auto packet = std::move(audio_decode_queue_.front()); + audio_decode_queue_.pop_front(); + audio_queue_cv_.notify_all(); + lock.unlock(); + + auto task = std::make_unique(); + task->type = kAudioTaskTypeDecodeToPlaybackQueue; + + SetDecodeSampleRate(packet->sample_rate, packet->frame_duration); + if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) { + // Resample if the sample rate is different + if (opus_decoder_->sample_rate() != codec_->output_sample_rate()) { + int target_size = output_resampler_.GetOutputSamples(task->pcm.size()); + std::vector resampled(target_size); + output_resampler_.Process(task->pcm.data(), task->pcm.size(), resampled.data()); + task->pcm = std::move(resampled); + } + + lock.lock(); + audio_playback_queue_.push_back(std::move(task)); + audio_queue_cv_.notify_all(); + } else { + ESP_LOGE(TAG, "Failed to decode audio"); + lock.lock(); + } + debug_statistics_.decode_count++; + } + + /* Encode the audio to send queue */ + if (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) { + auto task = std::move(audio_encode_queue_.front()); + audio_encode_queue_.pop_front(); + audio_queue_cv_.notify_all(); + lock.unlock(); + opus_encoder_->Encode(std::move(task->pcm), [this, &task](std::vector&& opus) { + auto packet = std::make_unique(); + packet->payload = std::move(opus); + packet->frame_duration = OPUS_FRAME_DURATION_MS; + packet->sample_rate = 16000; + + if (task->type == kAudioTaskTypeEncodeToSendQueue) { + { + std::lock_guard lock(audio_queue_mutex_); + audio_send_queue_.push_back(std::move(packet)); + } + if (callbacks_.on_send_queue_available) { + callbacks_.on_send_queue_available(); + } + } else if (task->type == kAudioTaskTypeEncodeToTestingQueue) { + std::lock_guard lock(audio_queue_mutex_); + audio_testing_queue_.push_back(std::move(packet)); + } + }); + debug_statistics_.encode_count++; + lock.lock(); + } + } + + ESP_LOGW(TAG, "Opus codec task stopped"); +} + +void AudioService::SetDecodeSampleRate(int sample_rate, int frame_duration) { + if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) { + return; + } + + opus_decoder_.reset(); + opus_decoder_ = std::make_unique(sample_rate, 1, frame_duration); + + auto codec = Board::GetInstance().GetAudioCodec(); + if (opus_decoder_->sample_rate() != codec->output_sample_rate()) { + ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate()); + output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate()); + } +} + +void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector&& pcm) { + auto task = std::make_unique(); + task->type = type; + task->pcm = std::move(pcm); + + /* Push the task to the encode queue */ + std::unique_lock lock(audio_queue_mutex_); + audio_queue_cv_.wait(lock, [this]() { return audio_encode_queue_.size() < MAX_ENCODE_TASKS_IN_QUEUE; }); + audio_encode_queue_.push_back(std::move(task)); + audio_queue_cv_.notify_all(); +} + +bool AudioService::PushPacketToDecodeQueue(std::unique_ptr packet, bool wait) { + std::unique_lock lock(audio_queue_mutex_); + if (audio_decode_queue_.size() >= MAX_DECODE_PACKETS_IN_QUEUE) { + if (wait) { + audio_queue_cv_.wait(lock, [this]() { return audio_decode_queue_.size() < MAX_DECODE_PACKETS_IN_QUEUE; }); + } else { + return false; + } + } + audio_decode_queue_.push_back(std::move(packet)); + audio_queue_cv_.notify_all(); + return true; +} + +std::unique_ptr AudioService::PopPacketFromSendQueue() { + std::lock_guard lock(audio_queue_mutex_); + if (audio_send_queue_.empty()) { + return nullptr; + } + auto packet = std::move(audio_send_queue_.front()); + audio_send_queue_.pop_front(); + audio_queue_cv_.notify_all(); + return packet; +} + +void AudioService::EncodeWakeWord() { + if (wake_word_) { + wake_word_->EncodeWakeWordData(); + } +} + +const std::string& AudioService::GetLastWakeWord() const { + return wake_word_->GetLastDetectedWakeWord(); +} + +std::unique_ptr AudioService::PopWakeWordPacket() { + auto packet = std::make_unique(); + if (wake_word_->GetWakeWordOpus(packet->payload)) { + return packet; + } + return nullptr; +} + +void AudioService::EnableWakeWordDetection(bool enable) { + if (!wake_word_) { + return; + } + + ESP_LOGD(TAG, "%s wake word detection", enable ? "Enabling" : "Disabling"); + if (enable) { + if (!wake_word_initialized_) { + wake_word_->Initialize(codec_); + wake_word_initialized_ = true; + } + wake_word_->Start(); + xEventGroupSetBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING); + } else { + wake_word_->Stop(); + xEventGroupClearBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING); + } +} + +void AudioService::EnableVoiceProcessing(bool enable) { + ESP_LOGD(TAG, "%s voice processing", enable ? "Enabling" : "Disabling"); + if (enable) { + if (!audio_processor_initialized_) { + audio_processor_->Initialize(codec_); + audio_processor_initialized_ = true; + } + + /* We should make sure no audio is playing */ + ResetDecoder(); + audio_input_need_warmup_ = true; + audio_processor_->Start(); + xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING); + } else { + audio_processor_->Stop(); + xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING); + } +} + +void AudioService::EnableAudioTesting(bool enable) { + ESP_LOGI(TAG, "%s audio testing", enable ? "Enabling" : "Disabling"); + if (enable) { + xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING); + } else { + xEventGroupClearBits(event_group_, AS_EVENT_AUDIO_TESTING_RUNNING); + /* Copy audio_testing_queue_ to audio_decode_queue_ */ + std::lock_guard lock(audio_queue_mutex_); + audio_decode_queue_ = std::move(audio_testing_queue_); + audio_queue_cv_.notify_all(); + } +} + +void AudioService::EnableDeviceAec(bool enable) { + ESP_LOGI(TAG, "%s device AEC", enable ? "Enabling" : "Disabling"); + audio_processor_->EnableDeviceAec(enable); +} + +void AudioService::SetCallbacks(AudioServiceCallbacks& callbacks) { + callbacks_ = callbacks; +} + +void AudioService::PlaySound(const std::string_view& sound) { + const char* data = sound.data(); + size_t size = sound.size(); + for (const char* p = data; p < data + size; ) { + auto p3 = (BinaryProtocol3*)p; + p += sizeof(BinaryProtocol3); + + auto payload_size = ntohs(p3->payload_size); + auto packet = std::make_unique(); + packet->sample_rate = 16000; + packet->frame_duration = 60; + packet->payload.resize(payload_size); + memcpy(packet->payload.data(), p3->payload, payload_size); + p += payload_size; + + PushPacketToDecodeQueue(std::move(packet), true); + } +} + +bool AudioService::IsIdle() { + std::lock_guard lock(audio_queue_mutex_); + return audio_encode_queue_.empty() && audio_decode_queue_.empty() && audio_playback_queue_.empty() && audio_testing_queue_.empty(); +} + +void AudioService::ResetDecoder() { + std::lock_guard lock(audio_queue_mutex_); + opus_decoder_->ResetState(); + audio_decode_queue_.clear(); + audio_playback_queue_.clear(); + audio_testing_queue_.clear(); + audio_queue_cv_.notify_all(); +} + +void AudioService::CheckAndUpdateAudioPowerState() { + auto now = std::chrono::steady_clock::now(); + auto input_elapsed = std::chrono::duration_cast(now - last_input_time_).count(); + auto output_elapsed = std::chrono::duration_cast(now - last_output_time_).count(); + if (input_elapsed > AUDIO_POWER_TIMEOUT_MS && codec_->input_enabled()) { + codec_->EnableInput(false); + } + if (output_elapsed > AUDIO_POWER_TIMEOUT_MS && codec_->output_enabled()) { + codec_->EnableOutput(false); + } + if (!codec_->input_enabled() && !codec_->output_enabled()) { + esp_timer_stop(audio_power_timer_); + } +} \ No newline at end of file diff --git a/main/audio/audio_service.h b/main/audio/audio_service.h new file mode 100644 index 00000000..95cabacf --- /dev/null +++ b/main/audio/audio_service.h @@ -0,0 +1,157 @@ +#ifndef AUDIO_SERVICE_H +#define AUDIO_SERVICE_H + +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "audio_codec.h" +#include "audio_processor.h" +#include "processors/audio_debugger.h" +#include "wake_word.h" +#include "protocol.h" + + +/* + * There are two types of audio data flow: + * 1. (MIC) -> [Processors] -> {Encode Queue} -> [Opus Encoder] -> {Send Queue} -> (Server) + * 2. (Server) -> {Decode Queue} -> [Opus Decoder] -> {Playback Queue} -> (Speaker) + * + * We use one task for MIC / Speaker / Processors, and one task for Opus Encoder / Opus Decoder. + * + * Decode Queue and Send Queue are the main queues, because Opus packets are quite smaller than PCM packets. + * + */ + +#define OPUS_FRAME_DURATION_MS 60 +#define MAX_ENCODE_TASKS_IN_QUEUE 2 +#define MAX_PLAYBACK_TASKS_IN_QUEUE 2 +#define MAX_DECODE_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) +#define MAX_SEND_PACKETS_IN_QUEUE (2400 / OPUS_FRAME_DURATION_MS) +#define AUDIO_TESTING_MAX_DURATION_MS 10000 + +#define AUDIO_POWER_TIMEOUT_MS 15000 +#define AUDIO_POWER_CHECK_INTERVAL_MS 1000 + + +#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0) +#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1) +#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2) +#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3) + +struct AudioServiceCallbacks { + std::function on_send_queue_available; + std::function on_wake_word_detected; + std::function on_vad_change; + std::function on_audio_testing_queue_full; +}; + + +enum AudioTaskType { + kAudioTaskTypeEncodeToSendQueue, + kAudioTaskTypeEncodeToTestingQueue, + kAudioTaskTypeDecodeToPlaybackQueue, +}; + +struct AudioTask { + AudioTaskType type; + std::vector pcm; +}; + +struct DebugStatistics { + uint32_t input_count = 0; + uint32_t decode_count = 0; + uint32_t encode_count = 0; + uint32_t playback_count = 0; +}; + +class AudioService { +public: + AudioService(); + ~AudioService(); + + void Initialize(AudioCodec* codec); + void Start(); + void Stop(); + void EncodeWakeWord(); + std::unique_ptr PopWakeWordPacket(); + const std::string& GetLastWakeWord() const; + bool IsVoiceDetected() const { return voice_detected_; } + bool IsIdle(); + bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; } + bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; } + + void EnableWakeWordDetection(bool enable); + void EnableVoiceProcessing(bool enable); + void EnableAudioTesting(bool enable); + void EnableDeviceAec(bool enable); + + void SetCallbacks(AudioServiceCallbacks& callbacks); + + bool PushPacketToDecodeQueue(std::unique_ptr packet, bool wait = false); + std::unique_ptr PopPacketFromSendQueue(); + void PlaySound(const std::string_view& sound); + bool ReadAudioData(std::vector& data, int sample_rate, int samples); + void ResetDecoder(); + +private: + AudioCodec* codec_ = nullptr; + AudioServiceCallbacks callbacks_; + std::unique_ptr audio_processor_; + std::unique_ptr wake_word_; + std::unique_ptr audio_debugger_; + std::unique_ptr opus_encoder_; + std::unique_ptr opus_decoder_; + OpusResampler input_resampler_; + OpusResampler reference_resampler_; + OpusResampler output_resampler_; + DebugStatistics debug_statistics_; + + EventGroupHandle_t event_group_; + + // Audio encode / decode + TaskHandle_t audio_input_task_handle_ = nullptr; + TaskHandle_t audio_output_task_handle_ = nullptr; + TaskHandle_t opus_codec_task_handle_ = nullptr; + std::mutex audio_queue_mutex_; + std::condition_variable audio_queue_cv_; + std::deque> audio_decode_queue_; + std::deque> audio_send_queue_; + std::deque> audio_testing_queue_; + std::deque> audio_encode_queue_; + std::deque> audio_playback_queue_; + + // For server AEC + std::deque timestamp_queue_; + std::mutex timestamp_mutex_; + + bool wake_word_initialized_ = false; + bool audio_processor_initialized_ = false; + bool voice_detected_ = false; + bool service_stopped_ = true; + bool audio_input_need_warmup_ = false; + + esp_timer_handle_t audio_power_timer_ = nullptr; + std::chrono::steady_clock::time_point last_input_time_; + std::chrono::steady_clock::time_point last_output_time_; + + void AudioInputTask(); + void AudioOutputTask(); + void OpusCodecTask(); + void PushTaskToEncodeQueue(AudioTaskType type, std::vector&& pcm); + void SetDecodeSampleRate(int sample_rate, int frame_duration); + void CheckAndUpdateAudioPowerState(); +}; + +#endif \ No newline at end of file diff --git a/main/audio_codecs/box_audio_codec.cc b/main/audio/codecs/box_audio_codec.cc similarity index 100% rename from main/audio_codecs/box_audio_codec.cc rename to main/audio/codecs/box_audio_codec.cc diff --git a/main/audio_codecs/box_audio_codec.h b/main/audio/codecs/box_audio_codec.h similarity index 100% rename from main/audio_codecs/box_audio_codec.h rename to main/audio/codecs/box_audio_codec.h diff --git a/main/audio_codecs/dummy_audio_codec.cc b/main/audio/codecs/dummy_audio_codec.cc similarity index 100% rename from main/audio_codecs/dummy_audio_codec.cc rename to main/audio/codecs/dummy_audio_codec.cc diff --git a/main/audio_codecs/dummy_audio_codec.h b/main/audio/codecs/dummy_audio_codec.h similarity index 100% rename from main/audio_codecs/dummy_audio_codec.h rename to main/audio/codecs/dummy_audio_codec.h diff --git a/main/audio_codecs/es8311_audio_codec.cc b/main/audio/codecs/es8311_audio_codec.cc similarity index 100% rename from main/audio_codecs/es8311_audio_codec.cc rename to main/audio/codecs/es8311_audio_codec.cc diff --git a/main/audio_codecs/es8311_audio_codec.h b/main/audio/codecs/es8311_audio_codec.h similarity index 100% rename from main/audio_codecs/es8311_audio_codec.h rename to main/audio/codecs/es8311_audio_codec.h diff --git a/main/audio_codecs/es8374_audio_codec.cc b/main/audio/codecs/es8374_audio_codec.cc similarity index 100% rename from main/audio_codecs/es8374_audio_codec.cc rename to main/audio/codecs/es8374_audio_codec.cc diff --git a/main/audio_codecs/es8374_audio_codec.h b/main/audio/codecs/es8374_audio_codec.h similarity index 100% rename from main/audio_codecs/es8374_audio_codec.h rename to main/audio/codecs/es8374_audio_codec.h diff --git a/main/audio_codecs/es8388_audio_codec.cc b/main/audio/codecs/es8388_audio_codec.cc similarity index 100% rename from main/audio_codecs/es8388_audio_codec.cc rename to main/audio/codecs/es8388_audio_codec.cc diff --git a/main/audio_codecs/es8388_audio_codec.h b/main/audio/codecs/es8388_audio_codec.h similarity index 100% rename from main/audio_codecs/es8388_audio_codec.h rename to main/audio/codecs/es8388_audio_codec.h diff --git a/main/audio_codecs/no_audio_codec.cc b/main/audio/codecs/no_audio_codec.cc similarity index 82% rename from main/audio_codecs/no_audio_codec.cc rename to main/audio/codecs/no_audio_codec.cc index 2a7d3539..eea3309a 100644 --- a/main/audio_codecs/no_audio_codec.cc +++ b/main/audio/codecs/no_audio_codec.cc @@ -74,63 +74,6 @@ NoAudioCodecDuplex::NoAudioCodecDuplex(int input_sample_rate, int output_sample_ ESP_LOGI(TAG, "Duplex channels created"); } -ATK_NoAudioCodecDuplex::ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { - duplex_ = true; - input_sample_rate_ = input_sample_rate; - output_sample_rate_ = output_sample_rate; - - i2s_chan_config_t chan_cfg = { - .id = I2S_NUM_0, - .role = I2S_ROLE_MASTER, - .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, - .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, - .auto_clear_after_cb = true, - .auto_clear_before_cb = false, - .intr_priority = 0, - }; - ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); - - i2s_std_config_t std_cfg = { - .clk_cfg = { - .sample_rate_hz = (uint32_t)output_sample_rate_, - .clk_src = I2S_CLK_SRC_DEFAULT, - .mclk_multiple = I2S_MCLK_MULTIPLE_256, - #ifdef I2S_HW_VERSION_2 - .ext_clk_freq_hz = 0, - #endif - }, - .slot_cfg = { - .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, - .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, - .slot_mode = I2S_SLOT_MODE_STEREO, - .slot_mask = I2S_STD_SLOT_BOTH, - .ws_width = I2S_DATA_BIT_WIDTH_16BIT, - .ws_pol = false, - .bit_shift = true, - #ifdef I2S_HW_VERSION_2 - .left_align = true, - .big_endian = false, - .bit_order_lsb = false - #endif - }, - .gpio_cfg = { - .mclk = I2S_GPIO_UNUSED, - .bclk = bclk, - .ws = ws, - .dout = dout, - .din = din, - .invert_flags = { - .mclk_inv = false, - .bclk_inv = false, - .ws_inv = false - } - } - }; - ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); - ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); - ESP_LOGI(TAG, "Duplex channels created"); -} - NoAudioCodecSimplex::NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din) { duplex_ = false; @@ -377,18 +320,12 @@ int NoAudioCodec::Read(int16_t* dest, int samples) { int NoAudioCodecSimplexPdm::Read(int16_t* dest, int samples) { size_t bytes_read; - // PDM 解调后的数据位宽为 16 位 - std::vector bit16_buffer(samples); - if (i2s_channel_read(rx_handle_, bit16_buffer.data(), samples * sizeof(int16_t), &bytes_read, portMAX_DELAY) != ESP_OK) { + // PDM 解调后的数据位宽为 16 位,直接读取到目标缓冲区 + if (i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY) != ESP_OK) { ESP_LOGE(TAG, "Read Failed!"); return 0; } // 计算实际读取的样本数 - samples = bytes_read / sizeof(int16_t); - - // 将 16 位数据直接复制到目标缓冲区 - memcpy(dest, bit16_buffer.data(), samples * sizeof(int16_t)); - - return samples; + return bytes_read / sizeof(int16_t); } diff --git a/main/audio_codecs/no_audio_codec.h b/main/audio/codecs/no_audio_codec.h similarity index 87% rename from main/audio_codecs/no_audio_codec.h rename to main/audio/codecs/no_audio_codec.h index 51014f3f..cea633be 100644 --- a/main/audio_codecs/no_audio_codec.h +++ b/main/audio/codecs/no_audio_codec.h @@ -20,11 +20,6 @@ public: NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); }; -class ATK_NoAudioCodecDuplex : public NoAudioCodec { -public: - ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); -}; - class NoAudioCodecSimplex : public NoAudioCodec { public: NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din); diff --git a/main/audio_processing/afe_audio_processor.cc b/main/audio/processors/afe_audio_processor.cc similarity index 100% rename from main/audio_processing/afe_audio_processor.cc rename to main/audio/processors/afe_audio_processor.cc diff --git a/main/audio_processing/afe_audio_processor.h b/main/audio/processors/afe_audio_processor.h similarity index 100% rename from main/audio_processing/afe_audio_processor.h rename to main/audio/processors/afe_audio_processor.h diff --git a/main/audio_processing/audio_debugger.cc b/main/audio/processors/audio_debugger.cc similarity index 100% rename from main/audio_processing/audio_debugger.cc rename to main/audio/processors/audio_debugger.cc diff --git a/main/audio_processing/audio_debugger.h b/main/audio/processors/audio_debugger.h similarity index 100% rename from main/audio_processing/audio_debugger.h rename to main/audio/processors/audio_debugger.h diff --git a/main/audio_processing/no_audio_processor.cc b/main/audio/processors/no_audio_processor.cc similarity index 100% rename from main/audio_processing/no_audio_processor.cc rename to main/audio/processors/no_audio_processor.cc diff --git a/main/audio_processing/no_audio_processor.h b/main/audio/processors/no_audio_processor.h similarity index 100% rename from main/audio_processing/no_audio_processor.h rename to main/audio/processors/no_audio_processor.h diff --git a/main/audio_processing/wake_word.h b/main/audio/wake_word.h similarity index 84% rename from main/audio_processing/wake_word.h rename to main/audio/wake_word.h index 395f96cd..9725d2fc 100644 --- a/main/audio_processing/wake_word.h +++ b/main/audio/wake_word.h @@ -14,9 +14,8 @@ public: virtual void Initialize(AudioCodec* codec) = 0; virtual void Feed(const std::vector& data) = 0; virtual void OnWakeWordDetected(std::function callback) = 0; - virtual void StartDetection() = 0; - virtual void StopDetection() = 0; - virtual bool IsDetectionRunning() = 0; + virtual void Start() = 0; + virtual void Stop() = 0; virtual size_t GetFeedSize() = 0; virtual void EncodeWakeWordData() = 0; virtual bool GetWakeWordOpus(std::vector& opus) = 0; diff --git a/main/audio_processing/afe_wake_word.cc b/main/audio/wake_words/afe_wake_word.cc similarity index 96% rename from main/audio_processing/afe_wake_word.cc rename to main/audio/wake_words/afe_wake_word.cc index 44214f17..37145b93 100644 --- a/main/audio_processing/afe_wake_word.cc +++ b/main/audio/wake_words/afe_wake_word.cc @@ -81,21 +81,17 @@ void AfeWakeWord::OnWakeWordDetected(std::functionreset_buffer(afe_data_); } } -bool AfeWakeWord::IsDetectionRunning() { - return xEventGroupGetBits(event_group_) & DETECTION_RUNNING_EVENT; -} - void AfeWakeWord::Feed(const std::vector& data) { if (afe_data_ == nullptr) { return; @@ -128,7 +124,7 @@ void AfeWakeWord::AudioDetectionTask() { StoreWakeWordData(res->data, res->data_size / sizeof(int16_t)); if (res->wakeup_state == WAKENET_DETECTED) { - StopDetection(); + Stop(); last_detected_wake_word_ = wake_words_[res->wakenet_model_index - 1]; if (wake_word_detected_callback_) { diff --git a/main/audio_processing/afe_wake_word.h b/main/audio/wake_words/afe_wake_word.h similarity index 95% rename from main/audio_processing/afe_wake_word.h rename to main/audio/wake_words/afe_wake_word.h index 795a20b7..a3d3128c 100644 --- a/main/audio_processing/afe_wake_word.h +++ b/main/audio/wake_words/afe_wake_word.h @@ -26,9 +26,8 @@ public: void Initialize(AudioCodec* codec); void Feed(const std::vector& data); void OnWakeWordDetected(std::function callback); - void StartDetection(); - void StopDetection(); - bool IsDetectionRunning(); + void Start(); + void Stop(); size_t GetFeedSize(); void EncodeWakeWordData(); bool GetWakeWordOpus(std::vector& opus); diff --git a/main/audio_processing/custom_wake_word.cc b/main/audio/wake_words/custom_wake_word.cc similarity index 97% rename from main/audio_processing/custom_wake_word.cc rename to main/audio/wake_words/custom_wake_word.cc index 37dc5b7f..c13c94e7 100644 --- a/main/audio_processing/custom_wake_word.cc +++ b/main/audio/wake_words/custom_wake_word.cc @@ -76,21 +76,17 @@ void CustomWakeWord::OnWakeWordDetected(std::functionreset_buffer(afe_data_); } } -bool CustomWakeWord::IsDetectionRunning() { - return xEventGroupGetBits(event_group_) & DETECTION_RUNNING_EVENT; -} - void CustomWakeWord::Feed(const std::vector& data) { if (afe_data_ == nullptr) { return; @@ -158,7 +154,7 @@ void CustomWakeWord::AudioDetectionTask() { ESP_LOGI(TAG, "Custom wake word '%s' detected successfully!", CONFIG_CUSTOM_WAKE_WORD); // 停止检测 - StopDetection(); + Stop(); last_detected_wake_word_ = CONFIG_CUSTOM_WAKE_WORD_DISPLAY; // 调用回调 diff --git a/main/audio_processing/custom_wake_word.h b/main/audio/wake_words/custom_wake_word.h similarity index 95% rename from main/audio_processing/custom_wake_word.h rename to main/audio/wake_words/custom_wake_word.h index 0c873829..66fb0b55 100644 --- a/main/audio_processing/custom_wake_word.h +++ b/main/audio/wake_words/custom_wake_word.h @@ -31,9 +31,8 @@ public: void Initialize(AudioCodec* codec); void Feed(const std::vector& data); void OnWakeWordDetected(std::function callback); - void StartDetection(); - void StopDetection(); - bool IsDetectionRunning(); + void Start(); + void Stop(); size_t GetFeedSize(); void EncodeWakeWordData(); bool GetWakeWordOpus(std::vector& opus); diff --git a/main/audio_processing/esp_wake_word.cc b/main/audio/wake_words/esp_wake_word.cc similarity index 91% rename from main/audio_processing/esp_wake_word.cc rename to main/audio/wake_words/esp_wake_word.cc index c5d4ed32..dbb94cc2 100644 --- a/main/audio_processing/esp_wake_word.cc +++ b/main/audio/wake_words/esp_wake_word.cc @@ -50,22 +50,18 @@ void EspWakeWord::OnWakeWordDetected(std::function& data) { int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data()); if (res > 0) { - StopDetection(); + Stop(); last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res); if (wake_word_detected_callback_) { diff --git a/main/audio_processing/esp_wake_word.h b/main/audio/wake_words/esp_wake_word.h similarity index 93% rename from main/audio_processing/esp_wake_word.h rename to main/audio/wake_words/esp_wake_word.h index 189243c8..8361f541 100644 --- a/main/audio_processing/esp_wake_word.h +++ b/main/audio/wake_words/esp_wake_word.h @@ -27,9 +27,8 @@ public: void Initialize(AudioCodec* codec); void Feed(const std::vector& data); void OnWakeWordDetected(std::function callback); - void StartDetection(); - void StopDetection(); - bool IsDetectionRunning(); + void Start(); + void Stop(); size_t GetFeedSize(); void EncodeWakeWordData(); bool GetWakeWordOpus(std::vector& opus); diff --git a/main/audio_processing/no_wake_word.cc b/main/audio_processing/no_wake_word.cc deleted file mode 100644 index 5c5b5c95..00000000 --- a/main/audio_processing/no_wake_word.cc +++ /dev/null @@ -1,45 +0,0 @@ -#include "no_wake_word.h" -#include - -#define TAG "NoWakeWord" - -void NoWakeWord::Initialize(AudioCodec* codec) { - codec_ = codec; -} - -void NoWakeWord::Feed(const std::vector& data) { - // Do nothing - no wake word processing -} - -void NoWakeWord::OnWakeWordDetected(std::function callback) { - // Do nothing - no wake word processing -} - -void NoWakeWord::StartDetection() { - // Do nothing - no wake word processing -} - -void NoWakeWord::StopDetection() { - // Do nothing - no wake word processing -} - -bool NoWakeWord::IsDetectionRunning() { - return false; // No wake word processing -} - -size_t NoWakeWord::GetFeedSize() { - return 0; // No specific feed size requirement -} - -void NoWakeWord::EncodeWakeWordData() { - // Do nothing - no encoding needed -} - -bool NoWakeWord::GetWakeWordOpus(std::vector& opus) { - opus.clear(); - return false; // No opus data available -} - -const std::string& NoWakeWord::GetLastDetectedWakeWord() const { - return last_detected_wake_word_; -} \ No newline at end of file diff --git a/main/audio_processing/no_wake_word.h b/main/audio_processing/no_wake_word.h deleted file mode 100644 index ba075100..00000000 --- a/main/audio_processing/no_wake_word.h +++ /dev/null @@ -1,32 +0,0 @@ -#ifndef NO_WAKE_WORD_H -#define NO_WAKE_WORD_H - -#include -#include -#include - -#include "wake_word.h" -#include "audio_codec.h" - -class NoWakeWord : public WakeWord { -public: - NoWakeWord() = default; - ~NoWakeWord() = default; - - void Initialize(AudioCodec* codec) override; - void Feed(const std::vector& data) override; - void OnWakeWordDetected(std::function callback) override; - void StartDetection() override; - void StopDetection() override; - bool IsDetectionRunning() override; - size_t GetFeedSize() override; - void EncodeWakeWordData() override; - bool GetWakeWordOpus(std::vector& opus) override; - const std::string& GetLastDetectedWakeWord() const override; - -private: - AudioCodec* codec_ = nullptr; - std::string last_detected_wake_word_; -}; - -#endif \ No newline at end of file diff --git a/main/background_task.cc b/main/background_task.cc deleted file mode 100644 index 48461164..00000000 --- a/main/background_task.cc +++ /dev/null @@ -1,70 +0,0 @@ -#include "background_task.h" - -#include -#include - -#define TAG "BackgroundTask" - -BackgroundTask::BackgroundTask(uint32_t stack_size) { - xTaskCreate([](void* arg) { - BackgroundTask* task = (BackgroundTask*)arg; - task->BackgroundTaskLoop(); - }, "background_task", stack_size, this, 2, &background_task_handle_); -} - -BackgroundTask::~BackgroundTask() { - if (background_task_handle_ != nullptr) { - vTaskDelete(background_task_handle_); - } -} - -bool BackgroundTask::Schedule(std::function callback) { - std::lock_guard lock(mutex_); - if (waiting_for_completion_ > 0) { - return false; - } - if (active_tasks_ >= 30) { - int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); - if (free_sram < 10000) { - ESP_LOGW(TAG, "active_tasks_ == %d, free_sram == %u", active_tasks_, free_sram); - return false; - } - } - active_tasks_++; - background_tasks_.emplace_back([this, cb = std::move(callback)]() { - cb(); - { - std::lock_guard lock(mutex_); - active_tasks_--; - if (background_tasks_.empty() && active_tasks_ == 0) { - condition_variable_.notify_all(); - } - } - }); - condition_variable_.notify_all(); - return true; -} - -void BackgroundTask::WaitForCompletion() { - std::unique_lock lock(mutex_); - waiting_for_completion_++; - condition_variable_.wait(lock, [this]() { - return background_tasks_.empty() && active_tasks_ == 0; - }); - waiting_for_completion_--; -} - -void BackgroundTask::BackgroundTaskLoop() { - ESP_LOGI(TAG, "background_task started"); - while (true) { - std::unique_lock lock(mutex_); - condition_variable_.wait(lock, [this]() { return !background_tasks_.empty(); }); - - std::list> tasks = std::move(background_tasks_); - lock.unlock(); - - for (auto& task : tasks) { - task(); - } - } -} diff --git a/main/background_task.h b/main/background_task.h deleted file mode 100644 index 659b5e33..00000000 --- a/main/background_task.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef BACKGROUND_TASK_H -#define BACKGROUND_TASK_H - -#include -#include -#include -#include -#include -#include - -class BackgroundTask { -public: - BackgroundTask(uint32_t stack_size = 4096 * 2); - ~BackgroundTask(); - - bool Schedule(std::function callback); - void WaitForCompletion(); - -private: - std::mutex mutex_; - std::list> background_tasks_; - std::condition_variable condition_variable_; - TaskHandle_t background_task_handle_ = nullptr; - int active_tasks_ = 0; - int waiting_for_completion_ = 0; - - void BackgroundTaskLoop(); -}; - -#endif diff --git a/main/boards/README.md b/main/boards/README.md index 8c4ae756..bb14eab7 100644 --- a/main/boards/README.md +++ b/main/boards/README.md @@ -118,12 +118,12 @@ mkdir main/boards/my-custom-board ```cpp #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" +#include "mcp_server.h" #include #include @@ -220,12 +220,9 @@ private: }); } - // IoT设备初始化 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - // 可以添加更多IoT设备 + // MCP Tools 初始化 + void InitializeTools() { + // 参考 MCP 文档 } public: @@ -235,7 +232,7 @@ public: InitializeSpi(); InitializeDisplay(); InitializeButtons(); - InitializeIot(); + InitializeTools(); GetBacklight()->SetBrightness(100); } diff --git a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc index b875c3a7..9f561e6f 100644 --- a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc +++ b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc @@ -1,13 +1,11 @@ #include "wifi_board.h" -#include "audio_codec.h" -#include "es8311_audio_codec.h" -#include "no_audio_codec.h" +#include "codecs/es8311_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "led/single_led.h" -#include "iot/thing_manager.h" #include "i2c_device.h" #include @@ -25,6 +23,68 @@ LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); + +class ATK_NoAudioCodecDuplex : public NoAudioCodec { +public: + ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) { + duplex_ = true; + input_sample_rate_ = input_sample_rate; + output_sample_rate_ = output_sample_rate; + + i2s_chan_config_t chan_cfg = { + .id = I2S_NUM_0, + .role = I2S_ROLE_MASTER, + .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM, + .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM, + .auto_clear_after_cb = true, + .auto_clear_before_cb = false, + .intr_priority = 0, + }; + ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_)); + + i2s_std_config_t std_cfg = { + .clk_cfg = { + .sample_rate_hz = (uint32_t)output_sample_rate_, + .clk_src = I2S_CLK_SRC_DEFAULT, + .mclk_multiple = I2S_MCLK_MULTIPLE_256, + #ifdef I2S_HW_VERSION_2 + .ext_clk_freq_hz = 0, + #endif + }, + .slot_cfg = { + .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT, + .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO, + .slot_mode = I2S_SLOT_MODE_STEREO, + .slot_mask = I2S_STD_SLOT_BOTH, + .ws_width = I2S_DATA_BIT_WIDTH_16BIT, + .ws_pol = false, + .bit_shift = true, + #ifdef I2S_HW_VERSION_2 + .left_align = true, + .big_endian = false, + .bit_order_lsb = false + #endif + }, + .gpio_cfg = { + .mclk = I2S_GPIO_UNUSED, + .bclk = bclk, + .ws = ws, + .dout = dout, + .din = din, + .invert_flags = { + .mclk_inv = false, + .bclk_inv = false, + .ws_inv = false + } + } + }; + ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg)); + ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg)); + ESP_LOGI(TAG, "Duplex channels created"); + } +}; + + class XL9555_IN : public I2cDevice { public: XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { @@ -205,13 +265,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); @@ -219,7 +272,6 @@ public: xl9555_in_->SetOutputState(5, 1); xl9555_in_->SetOutputState(7, 1); InitializeButtons(); - InitializeIot(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc b/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc index 5b2f9c99..db9154cc 100644 --- a/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc +++ b/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "power_manager.h" @@ -336,14 +335,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: atk_dnesp32s3_box0() : right_button_(R_BUTTON_GPIO, false), @@ -356,7 +347,6 @@ public: InitializeSpi(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc index 010ccb0b..73470e11 100644 --- a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc +++ b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "es8388_audio_codec.h" +#include "codecs/es8388_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "esp32_camera.h" @@ -144,13 +143,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - // 初始化摄像头:ov2640; // 根据正点原子官方示例参数 void InitializeCamera() { @@ -214,7 +206,6 @@ public: InitializeSpi(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); InitializeCamera(); } diff --git a/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc b/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc index 2d646e82..1a60ca96 100644 --- a/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc +++ b/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc @@ -1,12 +1,11 @@ #include "ml307_board.h" -#include "es8388_audio_codec.h" +#include "codecs/es8388_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "driver/gpio.h" #include "assets/lang_config.h" @@ -176,13 +175,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), @@ -193,7 +185,6 @@ public: InitializeSpi(); InitializeSt7735Display(); InitializeButtons(); - InitializeIot(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc b/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc index 51c65273..92b5945e 100644 --- a/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc +++ b/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "es8388_audio_codec.h" +#include "codecs/es8388_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "driver/gpio.h" #include "assets/lang_config.h" @@ -186,13 +185,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: atk_dnesp32s3m_wifi() : boot_button_(BOOT_BUTTON_GPIO), @@ -203,7 +195,6 @@ public: InitializeSpi(); InitializeSt7735Display(); InitializeButtons(); - InitializeIot(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc b/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc index edb33649..0b5f5b60 100644 --- a/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc +++ b/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -101,19 +100,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - } - public: AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); I2cDetect(); InitializePi4ioe(); InitializeButtons(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/atoms3-echo-base/atoms3_echo_base.cc b/main/boards/atoms3-echo-base/atoms3_echo_base.cc index 3795b021..8709dc63 100644 --- a/main/boards/atoms3-echo-base/atoms3_echo_base.cc +++ b/main/boards/atoms3-echo-base/atoms3_echo_base.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "assets/lang_config.h" #include @@ -197,13 +196,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); @@ -212,7 +204,6 @@ public: InitializeSpi(); InitializeGc9107Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc b/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc index e26a9470..f7acca00 100644 --- a/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc +++ b/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "assets/lang_config.h" #include @@ -157,12 +156,6 @@ private: camera_->SetHMirror(false); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - } - virtual Camera* GetCamera() override { return camera_; } @@ -174,7 +167,6 @@ public: I2cDetect(); CheckEchoBaseConnection(); InitializePi4ioe(); - InitializeIot(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc index ff2b79a2..847978c6 100644 --- a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc +++ b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "assets/lang_config.h" #include @@ -273,13 +272,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: AtomS3rEchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); @@ -290,7 +282,6 @@ public: InitializeSpi(); InitializeGc9107Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc b/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc index ae472daa..bd212491 100644 --- a/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc +++ b/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -173,22 +172,12 @@ private: } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { - thing_manager.AddThing(iot::CreateThing("Screen")); - } - } - public: CompactWifiBoardLCD() : boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) { InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/bread-compact-esp32/esp32_bread_board.cc b/main/boards/bread-compact-esp32/esp32_bread_board.cc index 45e8a102..e86f714f 100644 --- a/main/boards/bread-compact-esp32/esp32_bread_board.cc +++ b/main/boards/bread-compact-esp32/esp32_bread_board.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "display/oled_display.h" @@ -134,14 +133,8 @@ private: } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } public: @@ -150,7 +143,7 @@ public: InitializeDisplayI2c(); InitializeSsd1306Display(); InitializeButtons(); - InitializeIot(); + InitializeTools(); } virtual AudioCodec* GetAudioCodec() override diff --git a/main/boards/bread-compact-ml307/compact_ml307_board.cc b/main/boards/bread-compact-ml307/compact_ml307_board.cc index e020cc2b..e91a1d0e 100644 --- a/main/boards/bread-compact-ml307/compact_ml307_board.cc +++ b/main/boards/bread-compact-ml307/compact_ml307_board.cc @@ -1,5 +1,5 @@ #include "dual_network_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/oled_display.h" #include "system_reset.h" #include "application.h" @@ -7,7 +7,6 @@ #include "config.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -155,14 +154,8 @@ private: } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } public: @@ -175,7 +168,7 @@ public: InitializeDisplayI2c(); InitializeSsd1306Display(); InitializeButtons(); - InitializeIot(); + InitializeTools(); } virtual Led* GetLed() override { diff --git a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc index a5c8b89a..da6112c2 100644 --- a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc +++ b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" @@ -7,7 +7,6 @@ #include "config.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -150,15 +149,8 @@ private: } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } public: @@ -167,7 +159,7 @@ public: InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); + InitializeTools(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc b/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc index a8435e5f..45115408 100644 --- a/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc +++ b/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" @@ -7,7 +7,6 @@ #include "config.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "esp32_camera.h" @@ -179,24 +178,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); -#elif CONFIG_IOT_PROTOCOL_MCP - -#endif - } - public: CompactWifiBoardS3Cam() : boot_button_(BOOT_BUTTON_GPIO) { InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); InitializeCamera(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index 3608a754..25536af2 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/oled_display.h" #include "system_reset.h" #include "application.h" @@ -7,7 +7,6 @@ #include "config.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -153,14 +152,8 @@ private: } // 物联网初始化,逐步迁移到 MCP 协议 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } public: @@ -172,7 +165,7 @@ public: InitializeDisplayI2c(); InitializeSsd1306Display(); InitializeButtons(); - InitializeIot(); + InitializeTools(); } virtual Led* GetLed() override { diff --git a/main/boards/common/adc_battery_monitor.cc b/main/boards/common/adc_battery_monitor.cc new file mode 100644 index 00000000..9edf11cb --- /dev/null +++ b/main/boards/common/adc_battery_monitor.cc @@ -0,0 +1,81 @@ +#include "adc_battery_monitor.h" + +AdcBatteryMonitor::AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin) + : charging_pin_(charging_pin) { + + // Initialize charging pin + gpio_config_t gpio_cfg = { + .pin_bit_mask = 1ULL << charging_pin, + .mode = GPIO_MODE_INPUT, + .pull_up_en = GPIO_PULLUP_DISABLE, + .pull_down_en = GPIO_PULLDOWN_DISABLE, + .intr_type = GPIO_INTR_DISABLE, + }; + ESP_ERROR_CHECK(gpio_config(&gpio_cfg)); + + // Initialize ADC battery estimation + adc_battery_estimation_t adc_cfg = { + .internal = { + .adc_unit = adc_unit, + .adc_bitwidth = ADC_BITWIDTH_12, + .adc_atten = ADC_ATTEN_DB_12, + }, + .adc_channel = adc_channel, + .upper_resistor = upper_resistor, + .lower_resistor = lower_resistor + }; + adc_cfg.charging_detect_cb = [](void *user_data) -> bool { + AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data; + return gpio_get_level(self->charging_pin_) == 1; + }; + adc_cfg.charging_detect_user_data = this; + adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg); + + // Initialize timer + esp_timer_create_args_t timer_cfg = { + .callback = [](void *arg) { + AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg; + self->CheckBatteryStatus(); + }, + .arg = this, + .name = "adc_battery_monitor", + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_)); + ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); +} + +AdcBatteryMonitor::~AdcBatteryMonitor() { + if (adc_battery_estimation_handle_) { + ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_)); + } +} + +bool AdcBatteryMonitor::IsCharging() { + bool is_charging = false; + ESP_ERROR_CHECK(adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging)); + return is_charging; +} + +bool AdcBatteryMonitor::IsDischarging() { + return !IsCharging(); +} + +uint8_t AdcBatteryMonitor::GetBatteryLevel() { + float capacity = 0; + ESP_ERROR_CHECK(adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity)); + return capacity; +} + +void AdcBatteryMonitor::OnChargingStatusChanged(std::function callback) { + on_charging_status_changed_ = callback; +} + +void AdcBatteryMonitor::CheckBatteryStatus() { + bool new_charging_status = IsCharging(); + if (new_charging_status != is_charging_) { + is_charging_ = new_charging_status; + if (on_charging_status_changed_) { + on_charging_status_changed_(is_charging_); + } + } +} \ No newline at end of file diff --git a/main/boards/common/adc_battery_monitor.h b/main/boards/common/adc_battery_monitor.h new file mode 100644 index 00000000..6a123a7d --- /dev/null +++ b/main/boards/common/adc_battery_monitor.h @@ -0,0 +1,30 @@ +#ifndef ADC_BATTERY_MONITOR_H +#define ADC_BATTERY_MONITOR_H + +#include +#include +#include +#include + +class AdcBatteryMonitor { +public: + AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin = GPIO_NUM_NC); + ~AdcBatteryMonitor(); + + bool IsCharging(); + bool IsDischarging(); + uint8_t GetBatteryLevel(); + + void OnChargingStatusChanged(std::function callback); + +private: + gpio_num_t charging_pin_; + adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr; + esp_timer_handle_t timer_handle_ = nullptr; + bool is_charging_ = false; + std::function on_charging_status_changed_; + + void CheckBatteryStatus(); +}; + +#endif // ADC_BATTERY_MONITOR_H diff --git a/main/boards/common/afsk_demod.cc b/main/boards/common/afsk_demod.cc index ac52919e..ac2df808 100644 --- a/main/boards/common/afsk_demod.cc +++ b/main/boards/common/afsk_demod.cc @@ -29,7 +29,7 @@ namespace audio_wifi_config continue; } - if (!app->ReadAudio(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data + if (!app->GetAudioService().ReadAudioData(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data // 读取音频失败,短暂延迟后重试 ESP_LOGI(kLogTag, "Failed to read audio data, retrying."); vTaskDelay(pdMS_TO_TICKS(10)); diff --git a/main/boards/common/button.cc b/main/boards/common/button.cc index 84400a60..9570a5fb 100644 --- a/main/boards/common/button.cc +++ b/main/boards/common/button.cc @@ -18,7 +18,7 @@ AdcButton::AdcButton(const button_adc_config_t& adc_config) : Button(nullptr) { Button::Button(button_handle_t button_handle) : button_handle_(button_handle) { } -Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time) : gpio_num_(gpio_num) { +Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time, bool enable_power_save) : gpio_num_(gpio_num) { if (gpio_num == GPIO_NUM_NC) { return; } @@ -29,7 +29,7 @@ Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, button_gpio_config_t gpio_config = { .gpio_num = gpio_num, .active_level = static_cast(active_high ? 1 : 0), - .enable_power_save = false, + .enable_power_save = enable_power_save, .disable_pull = false }; ESP_ERROR_CHECK(iot_button_new_gpio_device(&button_config, &gpio_config, &button_handle_)); diff --git a/main/boards/common/button.h b/main/boards/common/button.h index 8396d89c..ceecbe51 100644 --- a/main/boards/common/button.h +++ b/main/boards/common/button.h @@ -11,7 +11,7 @@ class Button { public: Button(button_handle_t button_handle); - Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0); + Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0, bool enable_power_save = false); ~Button(); void OnPressDown(std::function callback); @@ -40,4 +40,10 @@ public: }; #endif +class PowerSaveButton : public Button { +public: + PowerSaveButton(gpio_num_t gpio_num) : Button(gpio_num, false, 0, 0, true) { + } +}; + #endif // BUTTON_H_ diff --git a/main/boards/common/ml307_board.cc b/main/boards/common/ml307_board.cc index 5cabd5d4..14503934 100644 --- a/main/boards/common/ml307_board.cc +++ b/main/boards/common/ml307_board.cc @@ -66,12 +66,6 @@ void Ml307Board::StartNetwork() { ESP_LOGI(TAG, "ML307 Revision: %s", module_revision.c_str()); ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str()); ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str()); - - // Close all previous connections - modem_->ResetConnections(); - - // Enable sleep mode - modem_->SetSleepMode(true, 30); } NetworkInterface* Ml307Board::GetNetwork() { diff --git a/main/boards/common/sleep_timer.cc b/main/boards/common/sleep_timer.cc new file mode 100644 index 00000000..f8c23b8e --- /dev/null +++ b/main/boards/common/sleep_timer.cc @@ -0,0 +1,114 @@ +#include "sleep_timer.h" +#include "application.h" +#include "board.h" +#include "display.h" + +#include +#include +#include + +#define TAG "SleepTimer" + + +SleepTimer::SleepTimer(int seconds_to_light_sleep, int seconds_to_deep_sleep) + : seconds_to_light_sleep_(seconds_to_light_sleep), seconds_to_deep_sleep_(seconds_to_deep_sleep) { + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto self = static_cast(arg); + self->CheckTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "sleep_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sleep_timer_)); +} + +SleepTimer::~SleepTimer() { + esp_timer_stop(sleep_timer_); + esp_timer_delete(sleep_timer_); +} + +void SleepTimer::SetEnabled(bool enabled) { + if (enabled && !enabled_) { + ticks_ = 0; + enabled_ = enabled; + ESP_ERROR_CHECK(esp_timer_start_periodic(sleep_timer_, 1000000)); + ESP_LOGI(TAG, "Sleep timer enabled"); + } else if (!enabled && enabled_) { + ESP_ERROR_CHECK(esp_timer_stop(sleep_timer_)); + enabled_ = enabled; + WakeUp(); + ESP_LOGI(TAG, "Sleep timer disabled"); + } +} + +void SleepTimer::OnEnterLightSleepMode(std::function callback) { + on_enter_light_sleep_mode_ = callback; +} + +void SleepTimer::OnExitLightSleepMode(std::function callback) { + on_exit_light_sleep_mode_ = callback; +} + +void SleepTimer::OnEnterDeepSleepMode(std::function callback) { + on_enter_deep_sleep_mode_ = callback; +} + +void SleepTimer::CheckTimer() { + auto& app = Application::GetInstance(); + if (!app.CanEnterSleepMode()) { + ticks_ = 0; + return; + } + + ticks_++; + if (seconds_to_light_sleep_ != -1 && ticks_ >= seconds_to_light_sleep_) { + if (!in_light_sleep_mode_) { + in_light_sleep_mode_ = true; + if (on_enter_light_sleep_mode_) { + on_enter_light_sleep_mode_(); + } + + app.Schedule([this, &app]() { + while (in_light_sleep_mode_) { + auto& board = Board::GetInstance(); + board.GetDisplay()->UpdateStatusBar(true); + lv_refr_now(nullptr); + lvgl_port_stop(); + + // 配置timer唤醒源(30秒后自动唤醒) + esp_sleep_enable_timer_wakeup(30 * 1000000); + + // 进入light sleep模式 + esp_light_sleep_start(); + lvgl_port_resume(); + + auto wakeup_reason = esp_sleep_get_wakeup_cause(); + if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) { + break; + } + } + WakeUp(); + }); + } + } + if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) { + if (on_enter_deep_sleep_mode_) { + on_enter_deep_sleep_mode_(); + } + + esp_deep_sleep_start(); + } +} + +void SleepTimer::WakeUp() { + ticks_ = 0; + if (in_light_sleep_mode_) { + in_light_sleep_mode_ = false; + if (on_exit_light_sleep_mode_) { + on_exit_light_sleep_mode_(); + } + } +} diff --git a/main/boards/common/sleep_timer.h b/main/boards/common/sleep_timer.h new file mode 100644 index 00000000..159e220a --- /dev/null +++ b/main/boards/common/sleep_timer.h @@ -0,0 +1,32 @@ +#pragma once + +#include + +#include +#include + +class SleepTimer { +public: + SleepTimer(int seconds_to_light_sleep = 20, int seconds_to_deep_sleep = -1); + ~SleepTimer(); + + void SetEnabled(bool enabled); + void OnEnterLightSleepMode(std::function callback); + void OnExitLightSleepMode(std::function callback); + void OnEnterDeepSleepMode(std::function callback); + void WakeUp(); + +private: + void CheckTimer(); + + esp_timer_handle_t sleep_timer_ = nullptr; + bool enabled_ = false; + int ticks_ = 0; + int seconds_to_light_sleep_; + int seconds_to_deep_sleep_; + bool in_light_sleep_mode_ = false; + + std::function on_enter_light_sleep_mode_; + std::function on_exit_light_sleep_mode_; + std::function on_enter_deep_sleep_mode_; +}; diff --git a/main/boards/df-k10/config.json b/main/boards/df-k10/config.json index 0ead8f10..55137d40 100644 --- a/main/boards/df-k10/config.json +++ b/main/boards/df-k10/config.json @@ -4,8 +4,7 @@ { "name": "df-k10", "sdkconfig_append": [ - "CONFIG_SPIRAM_MODE_OCT=y", - "CONFIG_IOT_PROTOCOL_MCP=y" + "CONFIG_SPIRAM_MODE_OCT=y" ] } ] diff --git a/main/boards/df-k10/df_k10_board.cc b/main/boards/df-k10/df_k10_board.cc index 2d7e6000..5412adf5 100644 --- a/main/boards/df-k10/df_k10_board.cc +++ b/main/boards/df-k10/df_k10_board.cc @@ -7,7 +7,6 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "esp32_camera.h" #include "led/circular_strip.h" @@ -258,11 +257,6 @@ public: InitializeButtons(); InitializeIot(); InitializeCamera(); - -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); -#endif } virtual Led* GetLed() override { diff --git a/main/boards/df-s3-ai-cam/config.json b/main/boards/df-s3-ai-cam/config.json index 4d797a43..ff50ca68 100644 --- a/main/boards/df-s3-ai-cam/config.json +++ b/main/boards/df-s3-ai-cam/config.json @@ -6,8 +6,7 @@ "sdkconfig_append": [ "CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=10", "CONFIG_ESP_PHY_MAX_TX_POWER=10", - "CONFIG_SPIRAM_MODE_OCT=y", - "CONFIG_IOT_PROTOCOL_MCP=y" + "CONFIG_SPIRAM_MODE_OCT=y" ] } ] diff --git a/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc b/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc index c966860a..855f055f 100644 --- a/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc +++ b/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "esp32_camera.h" #include "led/gpio_led.h" @@ -30,12 +29,6 @@ class DfrobotEsp32S3AiCam : public WifiBoard { }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - } - void InitializeCamera() { camera_config_t config = {}; config.ledc_channel = LEDC_CHANNEL_2; // LEDC通道选择 用于生成XCLK时钟 但是S3不用 @@ -73,13 +66,7 @@ class DfrobotEsp32S3AiCam : public WifiBoard { DfrobotEsp32S3AiCam() : boot_button_(BOOT_BUTTON_GPIO) { InitializeButtons(); - InitializeIot(); InitializeCamera(); - -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); -#endif } virtual Led* GetLed() override { diff --git a/main/boards/doit-s3-aibox/doit_s3_aibox.cc b/main/boards/doit-s3-aibox/doit_s3_aibox.cc index 1a75785e..04da3bac 100644 --- a/main/boards/doit-s3-aibox/doit_s3_aibox.cc +++ b/main/boards/doit-s3-aibox/doit_s3_aibox.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/gpio_led.h" #include #include @@ -100,12 +99,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - } - void InitializeGpio(gpio_num_t gpio_num_) { gpio_config_t config = { @@ -128,7 +121,6 @@ public: // 上拉io48 置高电平 InitializeGpio(GPIO_NUM_48); InitializeButtons(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/du-chatx/du-chatx-wifi.cc b/main/boards/du-chatx/du-chatx-wifi.cc index 58ccdf50..baa7a1db 100644 --- a/main/boards/du-chatx/du-chatx-wifi.cc +++ b/main/boards/du-chatx/du-chatx-wifi.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "power_manager.h" #include "power_save_timer.h" @@ -124,20 +123,11 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: DuChatX() : boot_button_(BOOT_BUTTON_GPIO) { InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); InitializePowerSaveTimer(); InitializePowerManager(); diff --git a/main/boards/echoear/EchoEar.cc b/main/boards/echoear/EchoEar.cc index 84f0887b..784ef0e8 100644 --- a/main/boards/echoear/EchoEar.cc +++ b/main/boards/echoear/EchoEar.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "backlight.h" #include diff --git a/main/boards/echoear/config.json b/main/boards/echoear/config.json index 25069cee..5b1a9172 100644 --- a/main/boards/echoear/config.json +++ b/main/boards/echoear/config.json @@ -2,7 +2,7 @@ "target": "esp32s3", "builds": [ { - "name": "EchoEar", + "name": "echoear", "sdkconfig_append": [] } ] diff --git a/main/boards/electron-bot/electron_bot.cc b/main/boards/electron-bot/electron_bot.cc index 7a498f13..70fd2a93 100644 --- a/main/boards/electron-bot/electron_bot.cc +++ b/main/boards/electron-bot/electron_bot.cc @@ -8,7 +8,7 @@ #include #include "application.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "button.h" #include "config.h" #include "display/lcd_display.h" diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index 1bf77a8e..c243b69d 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/lcd_display.h" #include "esp_lcd_ili9341.h" #include "font_awesome_symbols.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include #include @@ -143,20 +142,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeIli9341Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp-box-lite/esp_box_lite_board.cc b/main/boards/esp-box-lite/esp_box_lite_board.cc index 9cafd2d2..02063243 100644 --- a/main/boards/esp-box-lite/esp_box_lite_board.cc +++ b/main/boards/esp-box-lite/esp_box_lite_board.cc @@ -6,7 +6,6 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "assets/lang_config.h" #include @@ -205,20 +204,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: EspBoxBoardLite() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeIli9341Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp-box/esp_box_board.cc b/main/boards/esp-box/esp_box_board.cc index c2e14b94..46e09640 100644 --- a/main/boards/esp-box/esp_box_board.cc +++ b/main/boards/esp-box/esp_box_board.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/lcd_display.h" #include "esp_lcd_ili9341.h" #include "font_awesome_symbols.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include #include @@ -143,20 +142,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeIli9341Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp-hi/config.json b/main/boards/esp-hi/config.json index 79c5c82e..e58704b0 100644 --- a/main/boards/esp-hi/config.json +++ b/main/boards/esp-hi/config.json @@ -28,7 +28,6 @@ "CONFIG_MMAP_FILE_NAME_LENGTH=25", "CONFIG_ESP_CONSOLE_NONE=y", "CONFIG_USE_ESP_WAKE_WORD=y", - "CONFIG_IOT_PROTOCOL_MCP=y", "CONFIG_COMPILER_OPTIMIZATION_SIZE=y" ] } diff --git a/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc b/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc index 208b6059..18164397 100644 --- a/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc +++ b/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" @@ -7,7 +7,6 @@ #include "pin_config.h" #include "config.h" -#include "iot/thing_manager.h" #include #include @@ -17,7 +16,6 @@ #include #include -#include "audio_codecs/box_audio_codec.h" #include "esp_io_expander_tca9554.h" #define TAG "ESP_S3_LCD_EV_Board" @@ -175,17 +173,10 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - } - public: ESP_S3_LCD_EV_Board() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); InitializeButtons(); - InitializeIot(); InitializeRGB_GC9503V_Display(); } diff --git a/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/main/boards/esp-sparkbot/esp_sparkbot_board.cc index 3fc54961..76bd913e 100644 --- a/main/boards/esp-sparkbot/esp_sparkbot_board.cc +++ b/main/boards/esp-sparkbot/esp_sparkbot_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "font_awesome_symbols.h" #include "application.h" diff --git a/main/boards/esp-spot-s3/esp_spot_s3_board.cc b/main/boards/esp-spot-s3/esp_spot_s3_board.cc index af8f5fa1..0ff61397 100644 --- a/main/boards/esp-spot-s3/esp_spot_s3_board.cc +++ b/main/boards/esp-spot-s3/esp_spot_s3_board.cc @@ -1,9 +1,8 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "sdkconfig.h" #include @@ -162,13 +161,6 @@ private: gpio_config(&io_conf_2); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - - void BlinkGreenFor5s() { auto* led = static_cast(GetLed()); if (!led) { @@ -201,7 +193,6 @@ public: InitializeADC(); InitializeI2c(); InitializeButtons(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/esp32-cgc-144/board_control.cc b/main/boards/esp32-cgc-144/board_control.cc deleted file mode 100644 index 66f22d79..00000000 --- a/main/boards/esp32-cgc-144/board_control.cc +++ /dev/null @@ -1,60 +0,0 @@ -#include -#include -#include -#include - -#include "board.h" -#include "boards/common/wifi_board.h" -#include "boards/esp32-cgc-144/config.h" -#include "iot/thing.h" - -#include "audio_codec.h" - -#define TAG "BoardControl" - -namespace iot { - -class BoardControl : public Thing { -public: - BoardControl() : Thing("BoardControl", "当前 AI 机器人管理和控制") { - // 修改音量调节 - properties_.AddNumberProperty("volume", "当前音量值", [this]() -> int { - auto codec = Board::GetInstance().GetAudioCodec(); - return codec->output_volume(); - }); - - // 定义设备可以被远程执行的指令 - #if defined(ESP32_CGC_144_lite) - methods_.AddMethod("SetVolume", "设置音量", ParameterList({ - Parameter("volume", "0到100之间的整数", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - auto codec = Board::GetInstance().GetAudioCodec(); - // 获取传入的音量值 - int volume = parameters["volume"].number(); - // 限制音量值 - if (volume > 66) { - volume = 66; - } - codec->SetOutputVolume(static_cast(volume)); - }); - #else - methods_.AddMethod("SetVolume", "设置音量", ParameterList({ - Parameter("volume", "0到100之间的整数", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - auto codec = Board::GetInstance().GetAudioCodec(); - // 获取传入的音量值 - int volume = parameters["volume"].number(); - // 限制音量值 - if (volume > 100) { - volume = 100; - } - codec->SetOutputVolume(static_cast(volume)); - }); - #endif - - } -}; - -} // namespace iot - -DECLARE_THING(BoardControl); diff --git a/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc b/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc index 8d3f7bd6..84a743e1 100644 --- a/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc +++ b/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" @@ -8,7 +8,6 @@ #include "power_save_timer.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -157,16 +156,8 @@ void InitializePowerManager() { } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("BoardControl")); - thing_manager.AddThing(iot::CreateThing("Battery")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } public: @@ -177,7 +168,7 @@ public: InitializeSpi(); InitializeSt7735Display(); InitializeButtons(); - InitializeIot(); + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32-cgc/esp32_cgc_board.cc b/main/boards/esp32-cgc/esp32_cgc_board.cc index b0190219..12f7e6ae 100644 --- a/main/boards/esp32-cgc/esp32_cgc_board.cc +++ b/main/boards/esp32-cgc/esp32_cgc_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" @@ -7,7 +7,6 @@ #include "config.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -153,15 +152,8 @@ private: } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } public: @@ -170,7 +162,7 @@ public: InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc index 73a4c23c..83b240b6 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc @@ -3,7 +3,7 @@ #include "esp_lcd_sh8601.h" #include "font_awesome_symbols.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "led/single_led.h" diff --git a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc index 562ebe29..f5082e65 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc +++ b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include #include "i2c_device.h" @@ -215,13 +214,6 @@ private: }, this); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: CustomBoard() { InitializeI2c(); @@ -229,7 +221,6 @@ public: InitializeSpi(); InitializeSpd2010Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc index 55d486b4..fd252473 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include #include "i2c_device.h" @@ -425,14 +424,6 @@ private: }, this); } - - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: CustomBoard() { InitializeI2c(); @@ -440,7 +431,6 @@ public: InitializeSpi(); Initializest77916Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc index 31099817..2e23b9d1 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include #include "i2c_device.h" @@ -374,13 +373,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: CustomBoard() : boot_button_(BOOT_BUTTON_GPIO) { @@ -389,7 +381,6 @@ public: InitializeSpi(); Initializest77916Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc index 41e9b139..98a09235 100644 --- a/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc +++ b/main/boards/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" diff --git a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc index e386f319..d9b852c6 100644 --- a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc +++ b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -269,12 +268,6 @@ private: camera_ = new Esp32Camera(config); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - - } public: Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) { @@ -290,7 +283,6 @@ public: #else InitializeSt7789Display(); #endif - InitializeIot(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc index 85b24500..cc156035 100644 --- a/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc +++ b/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -219,14 +218,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: GenJuTech_s3_1_54TFT() : boot_button_(BOOT_BUTTON_GPIO), @@ -238,8 +229,7 @@ public: InitializeCodecI2c(); InitializeSpi(); InitializeButtons(); - InitializeSt7789Display(); - InitializeIot(); + InitializeSt7789Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc index a68ad090..6e053d3c 100644 --- a/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc +++ b/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -232,25 +231,18 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } public: JiuchuanDevBoard() : - boot_button_(BOOT_BUTTON_GPIO), - pwr_button_(PWR_BUTTON_GPIO,true), - wifi_button(WIFI_BUTTON_GPIO), - cmd_button(CMD_BUTTON_GPIO) { + boot_button_(BOOT_BUTTON_GPIO), + pwr_button_(PWR_BUTTON_GPIO,true), + wifi_button(WIFI_BUTTON_GPIO), + cmd_button(CMD_BUTTON_GPIO) { + InitializeI2c(); InitializePowerManager(); InitializePowerSaveTimer(); InitializeButtons(); InitializeGC9301isplay(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/kevin-box-1/kevin_box_board.cc b/main/boards/kevin-box-1/kevin_box_board.cc index c76299d6..c55d3f7b 100644 --- a/main/boards/kevin-box-1/kevin_box_board.cc +++ b/main/boards/kevin-box-1/kevin_box_board.cc @@ -1,10 +1,9 @@ #include "ml307_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/oled_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" @@ -176,12 +175,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - } - public: KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), @@ -194,7 +187,6 @@ public: Enable4GModule(); InitializeButtons(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index 428265fd..014e23e3 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -1,10 +1,9 @@ #include "dual_network_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/oled_display.h" #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" #include "config.h" #include "power_save_timer.h" #include "axp2101.h" @@ -229,13 +228,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: KevinBoxBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), @@ -250,7 +242,6 @@ public: InitializeButtons(); InitializePowerSaveTimer(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/kevin-c3/kevin_c3_board.cc b/main/boards/kevin-c3/kevin_c3_board.cc index 3e99650e..df7e82c2 100644 --- a/main/boards/kevin-c3/kevin_c3_board.cc +++ b/main/boards/kevin-c3/kevin_c3_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "config.h" @@ -34,6 +34,14 @@ private: }, }; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } } void InitializeButtons() { @@ -52,19 +60,19 @@ private: } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { + void InitializeTools() { led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8); new LedStripControl(led_strip_); } public: - KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) { - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - + KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); InitializeButtons(); - InitializeIot(); + InitializeTools(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); } virtual Led* GetLed() override { diff --git a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc index 13ddddaa..1f50ba7b 100644 --- a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc +++ b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc @@ -1,13 +1,12 @@ #include "wifi_board.h" #include "ml307_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -126,13 +125,6 @@ private: camera_ = new Esp32Camera(config); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Lamp")); - } public: KEVIN_SP_V3Board() : boot_button_(BOOT_BUTTON_GPIO) { @@ -141,7 +133,6 @@ public: InitializeButtons(); InitializeSt7789Display(); InitializeCamera(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc index 303a1c90..fb962d7e 100644 --- a/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc +++ b/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -139,13 +138,6 @@ private: camera_ = new Esp32Camera(config); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Lamp")); - } public: KEVIN_SP_V4Board() : boot_button_(BOOT_BUTTON_GPIO) { @@ -155,7 +147,6 @@ public: InitializeButtons(); InitializeSt7789Display(); InitializeCamera(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc b/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc index 24eadca0..7162de22 100644 --- a/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc +++ b/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "pin_config.h" #include "config.h" -#include "iot/thing_manager.h" #include #include @@ -143,18 +142,10 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: Yuying_313lcd() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); InitializeButtons(); - InitializeIot(); InitializeRGB_GC9503V_Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc b/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc index c5b34422..d242d08d 100644 --- a/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc +++ b/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "es8388_audio_codec.h" +#include "codecs/es8388_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -131,20 +130,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: labplus_ledong_v2() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeJd9853Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->SetBrightness(100); } diff --git a/main/boards/labplus-mpython-v3/mpython_pro.cc b/main/boards/labplus-mpython-v3/mpython_pro.cc index fffcb0ca..293a986f 100644 --- a/main/boards/labplus-mpython-v3/mpython_pro.cc +++ b/main/boards/labplus-mpython-v3/mpython_pro.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "es8388_audio_codec.h" +#include "codecs/es8388_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -111,20 +110,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: mpython_v3() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->SetBrightness(100); } diff --git a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc index 0725a237..0eb7759e 100644 --- a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc +++ b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -100,20 +99,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: LichuangC3DevBoard() : boot_button_(BOOT_BUTTON_GPIO) { InitializeI2c(); InitializeSpi(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->SetBrightness(100); } diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index 658db9ee..9b682d6b 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "esp32_camera.h" #include @@ -249,11 +248,6 @@ public: InitializeButtons(); InitializeCamera(); -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); -#endif GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc index b2c1ddcf..a51047f4 100644 --- a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc +++ b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc @@ -6,7 +6,6 @@ #include "config.h" #include "power_save_timer.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "sy6970.h" #include "pin_config.h" #include "esp32_camera.h" @@ -277,6 +276,10 @@ private: camera_->SetHMirror(1); } + void InitializeTools() { + static IrFilterController irFilter(AP1511B_GPIO); + } + public: LilygoTCameraPlusS3Board() : boot_button_(BOOT_BUTTON_GPIO), key1_button_(KEY1_BUTTON_GPIO) { InitializePowerSaveTimer(); @@ -288,14 +291,7 @@ public: InitializeSt7789Display(); InitializeButtons(); InitializeCamera(); -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); -#elif CONFIG_IOT_PROTOCOL_MCP - static IrFilterController irFilter(AP1511B_GPIO); -#endif + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h b/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h index 8c3948b6..cdb9aaa5 100644 --- a/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h +++ b/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h @@ -1,7 +1,7 @@ #ifndef _TCIRCLES3_AUDIO_CODEC_H #define _TCIRCLES3_AUDIO_CODEC_H -#include "audio_codecs/audio_codec.h" +#include "audio_codec.h" #include #include diff --git a/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc b/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc index c88bbaef..8a117cf4 100644 --- a/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc +++ b/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc @@ -6,7 +6,6 @@ #include "config.h" #include "power_save_timer.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -231,11 +230,6 @@ public: InitSpi(); InitGc9d01nDisplay(); InitializeButtons(); -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); -#endif GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h b/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h index 3c050dc2..f68f818d 100644 --- a/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h +++ b/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h @@ -1,7 +1,7 @@ #ifndef _TCIRCLES3_AUDIO_CODEC_H #define _TCIRCLES3_AUDIO_CODEC_H -#include "audio_codecs/audio_codec.h" +#include "audio_codec.h" #include #include diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc b/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc index 9ea8bff3..9f764ce4 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc @@ -6,7 +6,6 @@ #include "config.h" #include "power_save_timer.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -256,11 +255,6 @@ public: InitSpi(); InitSt7796Display(); InitializeButtons(); -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); -#endif GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h b/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h index ac5b42ab..6612b96c 100644 --- a/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h +++ b/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h @@ -1,7 +1,7 @@ #ifndef _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H #define _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H -#include "audio_codecs/audio_codec.h" +#include "audio_codec.h" #include #include diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc index fba76a22..2b09d23f 100644 --- a/main/boards/m5stack-core-s3/m5stack_core_s3.cc +++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc @@ -5,7 +5,6 @@ #include "config.h" #include "power_save_timer.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "axp2101.h" #include @@ -342,14 +341,6 @@ private: camera_ = new Esp32Camera(config); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: M5StackCoreS3Board() { InitializePowerSaveTimer(); @@ -360,7 +351,6 @@ public: InitializeSpi(); InitializeIli9342Display(); InitializeCamera(); - InitializeIot(); InitializeFt6336TouchPad(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/m5stack-tab5/m5stack_tab5.cc b/main/boards/m5stack-tab5/m5stack_tab5.cc index 6aaec494..d9c244b1 100644 --- a/main/boards/m5stack-tab5/m5stack_tab5.cc +++ b/main/boards/m5stack-tab5/m5stack_tab5.cc @@ -7,7 +7,6 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include #include "esp_lcd_mipi_dsi.h" @@ -278,13 +277,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: M5StackTab5Board() : boot_button_(BOOT_BUTTON_GPIO) { @@ -294,7 +286,6 @@ public: InitializeGt911TouchPad(); InitializeIli9881cDisplay(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/magiclick-2p4/magiclick_2p4_board.cc b/main/boards/magiclick-2p4/magiclick_2p4_board.cc index 8e0251fc..d4028c88 100644 --- a/main/boards/magiclick-2p4/magiclick_2p4_board.cc +++ b/main/boards/magiclick-2p4/magiclick_2p4_board.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" #include "display/lcd_display.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "led/circular_strip.h" -#include "iot/thing_manager.h" #include "config.h" #include "font_awesome_symbols.h" #include "assets/lang_config.h" @@ -225,13 +224,6 @@ private: DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: magiclick_2p4() : main_button_(MAIN_BUTTON_GPIO), @@ -244,7 +236,6 @@ public: InitializeButtons(); InitializeSpi(); InitializeNv3023Display(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/magiclick-2p5/magiclick_2p5_board.cc b/main/boards/magiclick-2p5/magiclick_2p5_board.cc index 1379431e..d9eadcba 100644 --- a/main/boards/magiclick-2p5/magiclick_2p5_board.cc +++ b/main/boards/magiclick-2p5/magiclick_2p5_board.cc @@ -1,10 +1,9 @@ #include "dual_network_board.h" #include "display/lcd_display.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "led/circular_strip.h" -#include "iot/thing_manager.h" #include "config.h" #include "assets/lang_config.h" #include "font_awesome_symbols.h" @@ -281,13 +280,6 @@ private: DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: magiclick_2p5() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), main_button_(MAIN_BUTTON_GPIO), @@ -301,7 +293,6 @@ public: InitializeButtons(); InitializeSpi(); InitializeGc9107Display(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc index 8620b11a..f04c8060 100644 --- a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc +++ b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" #include "display/lcd_display.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" #include "config.h" #include "power_save_timer.h" #include "font_awesome_symbols.h" @@ -139,6 +138,14 @@ private: }, }; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } } void InitializeButtons() { @@ -207,25 +214,17 @@ private: DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: magiclick_c3_v2() : boot_button_(BOOT_BUTTON_GPIO) { - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - InitializeCodecI2c(); InitializeButtons(); InitializePowerSaveTimer(); InitializeSpi(); InitializeGc9107Display(); - InitializeIot(); GetBacklight()->RestoreBrightness(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); } virtual Led* GetLed() override { diff --git a/main/boards/magiclick-c3/magiclick_c3_board.cc b/main/boards/magiclick-c3/magiclick_c3_board.cc index 71a21f07..90662d42 100644 --- a/main/boards/magiclick-c3/magiclick_c3_board.cc +++ b/main/boards/magiclick-c3/magiclick_c3_board.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" #include "display/lcd_display.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" #include "config.h" #include "power_save_timer.h" #include "font_awesome_symbols.h" @@ -103,6 +102,14 @@ private: }, }; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } } void InitializeButtons() { @@ -165,25 +172,17 @@ private: DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: magiclick_c3() : boot_button_(BOOT_BUTTON_GPIO) { - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - InitializeCodecI2c(); InitializeButtons(); InitializePowerSaveTimer(); InitializeSpi(); InitializeNv3023Display(); - InitializeIot(); GetBacklight()->RestoreBrightness(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); } virtual Led* GetLed() override { diff --git a/main/boards/minsi-k08-dual/minsi_k08_dual.cc b/main/boards/minsi-k08-dual/minsi_k08_dual.cc index 44b3f0e2..6bd9a3fc 100644 --- a/main/boards/minsi-k08-dual/minsi_k08_dual.cc +++ b/main/boards/minsi-k08-dual/minsi_k08_dual.cc @@ -1,6 +1,6 @@ #include "dual_network_board.h" //#include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" @@ -9,7 +9,6 @@ #include "power_save_timer.h" #include "mcp_server.h" #include "lamp_controller.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "power_manager.h" @@ -196,16 +195,8 @@ private: } // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Lamp")); - thing_manager.AddThing(iot::CreateThing("Battery")); -#elif CONFIG_IOT_PROTOCOL_MCP + void InitializeTools() { static LampController lamp(LAMP_GPIO); -#endif } @@ -219,7 +210,7 @@ public: InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); + InitializeTools(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/mixgo-nova/mixgo-nova.cc b/main/boards/mixgo-nova/mixgo-nova.cc index 86c7f9db..bc841f5c 100644 --- a/main/boards/mixgo-nova/mixgo-nova.cc +++ b/main/boards/mixgo-nova/mixgo-nova.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8374_audio_codec.h" +#include "codecs/es8374_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include "led/circular_strip.h" #include "assets/lang_config.h" @@ -135,12 +134,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: MIXGO_NOVA() : boot_button_(BOOT_BUTTON_GPIO), @@ -150,7 +143,6 @@ public: InitializeSpi(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) { GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc b/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc index 81078129..ed33f926 100644 --- a/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc +++ b/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -98,20 +97,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: MovecallCuicanESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); InitializeSpi(); InitializeGc9a01Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc index 301b928e..dca60715 100644 --- a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc +++ b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include @@ -124,20 +123,12 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: MovecallMojiESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); InitializeSpi(); InitializeGc9a01Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/otto-robot/otto_robot.cc b/main/boards/otto-robot/otto_robot.cc index 62629da0..eb3ab883 100644 --- a/main/boards/otto-robot/otto_robot.cc +++ b/main/boards/otto-robot/otto_robot.cc @@ -7,11 +7,10 @@ #include #include "application.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "button.h" #include "config.h" #include "display/lcd_display.h" -#include "iot/thing_manager.h" #include "lamp_controller.h" #include "led/single_led.h" #include "mcp_server.h" diff --git a/main/boards/sensecap-watcher/sensecap_watcher.cc b/main/boards/sensecap-watcher/sensecap_watcher.cc index 48ff57c1..eac4e794 100644 --- a/main/boards/sensecap-watcher/sensecap_watcher.cc +++ b/main/boards/sensecap-watcher/sensecap_watcher.cc @@ -8,7 +8,6 @@ #include "knob.h" #include "config.h" #include "led/single_led.h" -#include "iot/thing_manager.h" #include "power_save_timer.h" #include "sscma_camera.h" @@ -367,14 +366,6 @@ private: } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - uint16_t BatterygetVoltage(void) { static bool initialized = false; static adc_oneshot_unit_handle_t adc_handle; @@ -550,7 +541,6 @@ public: Initializespd2010Display(); GetBacklight()->RestoreBrightness(); // 对于不带摄像头的版本,InitializeCamera需要3s, 所以先恢复背光亮度 InitializeCamera(); - InitializeIot(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc b/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc index 4b37cc88..37c58d67 100644 --- a/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc +++ b/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include @@ -271,13 +270,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: Spotpear_ESP32_S3_1_28_BOX() : boot_button_(BOOT_BUTTON_GPIO) { @@ -292,7 +284,6 @@ public: InitializeSpi(); InitializeGc9a01Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc b/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc index cac7ead1..34edf9b8 100644 --- a/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc +++ b/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include @@ -275,14 +274,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: Spotpear_esp32_s3_lcd_1_54() :boot_button_(BOOT_BUTTON_GPIO){ @@ -298,7 +289,6 @@ public: InitializePowerManager(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc b/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc index e4a5cccd..b94ad9ac 100644 --- a/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc +++ b/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "button.h" #include "config.h" #include "i2c_device.h" -#include "iot/thing_manager.h" #include #include @@ -50,6 +49,14 @@ private: }, }; ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); + + // Print I2C bus info + if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) { + while (true) { + ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware"); + vTaskDelay(1000 / portTICK_PERIOD_MS); + } + } } void InitializePowerManager() { @@ -149,19 +156,8 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: SurferC3114TFT() : boot_button_(BOOT_BUTTON_GPIO) { - // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 - esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); - InitializePowerManager(); InitializePowerSaveTimer(); @@ -169,9 +165,11 @@ public: InitializeSpi(); InitializeSt7789Display(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); + + // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 + esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/taiji-pi-s3/taiji_pi_s3.cc b/main/boards/taiji-pi-s3/taiji_pi_s3.cc index 06fe4067..ef3667aa 100644 --- a/main/boards/taiji-pi-s3/taiji_pi_s3.cc +++ b/main/boards/taiji-pi-s3/taiji_pi_s3.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "application.h" #include "i2c_device.h" #include "config.h" -#include "iot/thing_manager.h" #include #include @@ -604,12 +603,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } void InitializeMute() { gpio_reset_pin(AUDIO_MUTE_PIN); /* Set the GPIO as a push/pull output */ @@ -623,7 +616,6 @@ public: InitializeCst816sTouchPad(); InitializeSpi(); Initializest77916Display(); - InitializeIot(); InitializeMute(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/tudouzi/kevin_box_board.cc b/main/boards/tudouzi/kevin_box_board.cc index c75d69ce..84fc455a 100644 --- a/main/boards/tudouzi/kevin_box_board.cc +++ b/main/boards/tudouzi/kevin_box_board.cc @@ -1,10 +1,9 @@ #include "ml307_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "display/oled_display.h" #include "application.h" #include "button.h" #include "led/single_led.h" -#include "iot/thing_manager.h" #include "config.h" #include "power_save_timer.h" #include "axp2101.h" @@ -224,13 +223,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), @@ -245,7 +237,6 @@ public: InitializeButtons(); InitializePowerSaveTimer(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc b/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc index 9f934f6b..66867f30 100644 --- a/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc +++ b/main/boards/waveshare-c6-lcd-1.69/esp32-c6-lcd-1.69.cc @@ -1,12 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" - #include #include "i2c_device.h" @@ -150,15 +148,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - // thing_manager.AddThing(iot::CreateThing("Battery")); - // thing_manager.AddThing(iot::CreateThing("BoardControl")); - } - public: CustomBoard() : boot_button_(BOOT_BUTTON_GPIO) { @@ -166,7 +155,6 @@ public: InitializeSpi(); InitializeLcdDisplay(); InitializeButtons(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc b/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc index f4708482..6cba856e 100644 --- a/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc +++ b/main/boards/waveshare-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc @@ -2,8 +2,7 @@ #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include #include @@ -194,13 +193,6 @@ private: }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - void InitializeTouch() { i2c_device_config_t dev_cfg = { @@ -273,8 +265,6 @@ public: InitializeLcdDisplay(); InitializeButtons(); InitializeTools(); - //InitializeTouch(); - //InitializeIot(); } virtual AudioCodec* GetAudioCodec() override { diff --git a/main/boards/waveshare-p4-nano/esp32-p4-nano.cc b/main/boards/waveshare-p4-nano/esp32-p4-nano.cc index daa69a50..2cb8cb43 100644 --- a/main/boards/waveshare-p4-nano/esp32-p4-nano.cc +++ b/main/boards/waveshare-p4-nano/esp32-p4-nano.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "application.h" #include "display/lcd_display.h" // #include "display/no_display.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_mipi_dsi.h" @@ -210,18 +209,10 @@ private: app.ToggleChatState(); }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: WaveshareEsp32p4nano() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); - InitializeIot(); InitializeLCD(); InitializeTouch(); InitializeButtons(); diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc index 3bf11cd1..76864a2e 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-4b/esp32-p4-wifi6-touch-lcd-4b.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "application.h" #include "display/lcd_display.h" // #include "display/no_display.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_mipi_dsi.h" @@ -163,18 +162,10 @@ private: app.ToggleChatState(); }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: WaveshareEsp32p44b() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); - InitializeIot(); InitializeLCD(); InitializeTouch(); InitializeButtons(); diff --git a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc index 5012bdb7..25f6dbf6 100644 --- a/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc +++ b/main/boards/waveshare-p4-wifi6-touch-lcd-xc/esp32-p4-wifi6-touch-lcd-xc.cc @@ -1,10 +1,9 @@ #include "wifi_board.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "application.h" #include "display/lcd_display.h" // #include "display/no_display.h" #include "button.h" -#include "iot/thing_manager.h" #include "esp_lcd_panel_ops.h" #include "esp_lcd_mipi_dsi.h" @@ -165,18 +164,10 @@ private: app.ToggleChatState(); }); } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto &thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - } - public: WaveshareEsp32p4xc() : boot_button_(BOOT_BUTTON_GPIO) { InitializeCodecI2c(); - InitializeIot(); InitializeLCD(); InitializeTouch(); InitializeButtons(); diff --git a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc index 86b803fe..15b81363 100644 --- a/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc +++ b/main/boards/waveshare-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc @@ -3,7 +3,7 @@ #include "esp_lcd_sh8601.h" #include "font_awesome_symbols.h" -#include "audio_codecs/box_audio_codec.h" +#include "codecs/box_audio_codec.h" #include "application.h" #include "button.h" #include "led/single_led.h" diff --git a/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc b/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc index 348accab..5504f824 100644 --- a/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc +++ b/main/boards/waveshare-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc @@ -1,12 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" - #include #include "i2c_device.h" @@ -332,18 +330,6 @@ private: } - // 物联网初始化,添加对 AI 可见设备 - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - - thing_manager.AddThing(iot::CreateThing("BoardControl")); -#if PMIC_ENABLE - thing_manager.AddThing(iot::CreateThing("Battery")); -#endif - } - public: CustomBoard() : boot_button_(BOOT_BUTTON_GPIO) { @@ -362,7 +348,6 @@ public: #endif InitializeButtons(); InitializeCamera(); - InitializeIot(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc b/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc index c59384b5..5b2323c4 100644 --- a/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc +++ b/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc @@ -1,12 +1,11 @@ #include "ml307_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "../xingzhi-cube-1.54tft-wifi/power_manager.h" @@ -186,13 +185,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - void Initializegpio21_45() { rtc_gpio_init(GPIO_NUM_21); rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); @@ -219,8 +211,7 @@ public: InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); - InitializeNv3023Display(); - InitializeIot(); + InitializeNv3023Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc b/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc index 0380e2b4..c1f14c1a 100644 --- a/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc +++ b/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "../xingzhi-cube-1.54tft-wifi/power_manager.h" @@ -190,13 +189,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - void Initializegpio21_45() { rtc_gpio_init(GPIO_NUM_21); rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); @@ -223,8 +215,7 @@ public: InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); - InitializeNv3023Display(); - InitializeIot(); + InitializeNv3023Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc index 394b7792..536ff514 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc @@ -1,12 +1,11 @@ #include "dual_network_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/oled_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "../xingzhi-cube-1.54tft-wifi/power_manager.h" @@ -195,12 +194,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: XINGZHI_CUBE_0_96OLED_ML307() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), @@ -211,7 +204,6 @@ public: InitializeDisplayI2c(); InitializeSsd1306Display(); InitializeButtons(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc index e636acd7..256e5f6a 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc @@ -1,11 +1,10 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/oled_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "power_save_timer.h" @@ -186,12 +185,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: XINGZHI_CUBE_0_96OLED_WIFI() : boot_button_(BOOT_BUTTON_GPIO), @@ -202,7 +195,6 @@ public: InitializeDisplayI2c(); InitializeSsd1306Display(); InitializeButtons(); - InitializeIot(); } virtual Led* GetLed() override { diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc index 05a7e90d..90c58110 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc @@ -1,12 +1,11 @@ #include "dual_network_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "../xingzhi-cube-1.54tft-wifi/power_manager.h" @@ -177,13 +176,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: XINGZHI_CUBE_1_54TFT_ML307() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), @@ -194,8 +186,7 @@ public: InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); - InitializeSt7789Display(); - InitializeIot(); + InitializeSt7789Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc index 902e1839..4ef60e87 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "power_manager.h" @@ -167,13 +166,6 @@ private: }); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: XINGZHI_CUBE_1_54TFT_WIFI() : boot_button_(BOOT_BUTTON_GPIO), @@ -183,8 +175,7 @@ public: InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); - InitializeSt7789Display(); - InitializeIot(); + InitializeSt7789Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/xmini-c3-4g/README.md b/main/boards/xmini-c3-4g/README.md new file mode 100644 index 00000000..9697085b --- /dev/null +++ b/main/boards/xmini-c3-4g/README.md @@ -0,0 +1,4 @@ +# 开源地址 + +https://oshwhub.com/tenclass01/xmini_c3_4g + diff --git a/main/boards/xmini-c3-4g/power_manager.h b/main/boards/xmini-c3-4g/power_manager.h deleted file mode 100644 index 91b4ad45..00000000 --- a/main/boards/xmini-c3-4g/power_manager.h +++ /dev/null @@ -1,200 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector voltages_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 1; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - adc_cali_handle_t adc_cali_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (voltages_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value, voltage; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value)); - if (adc_value == 0) { - return; - } - - ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_value, &voltage)); - - // 将 ADC 值添加到队列中 - voltages_.push_back(voltage); - if (voltages_.size() > kBatteryAdcDataCount) { - voltages_.erase(voltages_.begin()); - } - uint32_t average_voltage = 0; - for (auto value : voltages_) { - average_voltage += value; - } - average_voltage /= voltages_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1600, 0}, - {1700, 20}, - {1800, 40}, - {1900, 60}, - {2000, 80}, - {2100, 100} - }; - - // 低于最低值时 - if (average_voltage < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_voltage >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_voltage >= levels[i].adc && average_voltage < levels[i+1].adc) { - float ratio = static_cast(average_voltage - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (voltages_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_4, &chan_config)); - - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = ADC_UNIT_1, - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - // 如果电量已经满了,则不再显示充电中 - if (battery_level_ == 100) { - return false; - } - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; diff --git a/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc b/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc index c8fc92a2..9c269bfd 100644 --- a/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc +++ b/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc @@ -1,5 +1,5 @@ #include "ml307_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/oled_display.h" #include "application.h" #include "button.h" @@ -7,9 +7,9 @@ #include "mcp_server.h" #include "settings.h" #include "config.h" -#include "power_save_timer.h" +#include "sleep_timer.h" #include "font_awesome_symbols.h" -#include "power_manager.h" +#include "adc_battery_monitor.h" #include #include @@ -32,44 +32,42 @@ private: Display* display_ = nullptr; Button boot_button_; bool press_to_talk_enabled_ = false; - PowerSaveTimer* power_save_timer_ = nullptr; - PowerManager* power_manager_ = nullptr; + SleepTimer* sleep_timer_ = nullptr; + AdcBatteryMonitor* adc_battery_monitor_ = nullptr; - void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_12); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { + void InitializeBatteryMonitor() { + adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_4, 100000, 100000, GPIO_NUM_12); + adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) { if (is_charging) { - power_save_timer_->SetEnabled(false); + sleep_timer_->SetEnabled(false); } else { - power_save_timer_->SetEnabled(true); + sleep_timer_->SetEnabled(true); } }); } void InitializePowerSaveTimer() { #if CONFIG_USE_ESP_WAKE_WORD - power_save_timer_ = new PowerSaveTimer(160, 600); + sleep_timer_ = new SleepTimer(600); #else - power_save_timer_ = new PowerSaveTimer(160, 60); + sleep_timer_ = new SleepTimer(30); #endif - power_save_timer_->OnEnterSleepMode([this]() { + sleep_timer_->OnEnterLightSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); - auto display = GetDisplay(); - display->SetChatMessage("system", ""); - display->SetEmotion("sleepy"); - - auto codec = GetAudioCodec(); - codec->EnableInput(false); + // Show the standby screen + GetDisplay()->ShowStandbyScreen(true); + // Enable sleep mode, and sleep in 1 second after DTR is set to high + modem_->SetSleepMode(true, 1); + // Set the DTR pin to high to make the modem enter sleep mode + modem_->GetAtUart()->SetDtrPin(true); }); - power_save_timer_->OnExitSleepMode([this]() { - auto codec = GetAudioCodec(); - codec->EnableInput(true); - - auto display = GetDisplay(); - display->SetChatMessage("system", ""); - display->SetEmotion("neutral"); + sleep_timer_->OnExitLightSleepMode([this]() { + // Set the DTR pin to low to make the modem wake up + modem_->GetAtUart()->SetDtrPin(false); + // Hide the standby screen + GetDisplay()->ShowStandbyScreen(false); }); - power_save_timer_->SetEnabled(true); + sleep_timer_->SetEnabled(true); } void InitializeCodecI2c() { @@ -152,9 +150,6 @@ private: } }); boot_button_.OnPressDown([this]() { - if (power_save_timer_) { - power_save_timer_->WakeUp(); - } if (press_to_talk_enabled_) { Application::GetInstance().StartListening(); } @@ -170,9 +165,6 @@ private: Settings settings("vendor"); press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0; -#if CONFIG_IOT_PROTOCOL_XIAOZHI -#error "XiaoZhi 协议不支持" -#elif CONFIG_IOT_PROTOCOL_MCP auto& mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.set_press_to_talk", "Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n" @@ -191,14 +183,13 @@ private: } throw std::runtime_error("Invalid mode: " + mode); }); -#endif } public: XminiC3Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, ML307_DTR_PIN), - boot_button_(BOOT_BUTTON_GPIO) { + boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) { - InitializePowerManager(); + InitializeBatteryMonitor(); InitializePowerSaveTimer(); InitializeCodecI2c(); InitializeSsd1306Display(); @@ -223,14 +214,9 @@ public: } virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); + charging = adc_battery_monitor_->IsCharging(); + discharging = adc_battery_monitor_->IsDischarging(); + level = adc_battery_monitor_->GetBatteryLevel(); return true; } @@ -248,7 +234,7 @@ public: virtual void SetPowerSaveMode(bool enabled) override { if (!enabled) { - power_save_timer_->WakeUp(); + sleep_timer_->WakeUp(); } Ml307Board::SetPowerSaveMode(enabled); } diff --git a/main/boards/xmini-c3-v3/README.md b/main/boards/xmini-c3-v3/README.md new file mode 100644 index 00000000..87dfd43c --- /dev/null +++ b/main/boards/xmini-c3-v3/README.md @@ -0,0 +1,4 @@ +# 开源地址 + +https://oshwhub.com/tenclass01/xmini_c3 + diff --git a/main/boards/xmini-c3-v3/power_manager.h b/main/boards/xmini-c3-v3/power_manager.h deleted file mode 100644 index 8f95da6d..00000000 --- a/main/boards/xmini-c3-v3/power_manager.h +++ /dev/null @@ -1,196 +0,0 @@ -#pragma once - -#include -#include - -#include -#include -#include -#include -#include - - -class PowerManager { -private: - esp_timer_handle_t timer_handle_; - std::function on_charging_status_changed_; - std::function on_low_battery_status_changed_; - - gpio_num_t charging_pin_ = GPIO_NUM_NC; - std::vector voltages_; - uint32_t battery_level_ = 0; - bool is_charging_ = false; - bool is_low_battery_ = false; - int ticks_ = 0; - const int kBatteryAdcInterval = 1; - const int kBatteryAdcDataCount = 3; - const int kLowBatteryLevel = 20; - - adc_oneshot_unit_handle_t adc_handle_; - adc_cali_handle_t adc_cali_handle_; - - void CheckBatteryStatus() { - // Get charging status - bool new_charging_status = gpio_get_level(charging_pin_) == 1; - if (new_charging_status != is_charging_) { - is_charging_ = new_charging_status; - if (on_charging_status_changed_) { - on_charging_status_changed_(is_charging_); - } - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据不足,则读取电池电量数据 - if (voltages_.size() < kBatteryAdcDataCount) { - ReadBatteryAdcData(); - return; - } - - // 如果电池电量数据充足,则每 kBatteryAdcInterval 个 tick 读取一次电池电量数据 - ticks_++; - if (ticks_ % kBatteryAdcInterval == 0) { - ReadBatteryAdcData(); - } - } - - void ReadBatteryAdcData() { - int adc_value, voltage; - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_3, &adc_value)); - if (adc_value == 0) { - return; - } - - ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_value, &voltage)); - - // 将 ADC 值添加到队列中 - voltages_.push_back(voltage); - if (voltages_.size() > kBatteryAdcDataCount) { - voltages_.erase(voltages_.begin()); - } - uint32_t average_voltage = 0; - for (auto value : voltages_) { - average_voltage += value; - } - average_voltage /= voltages_.size(); - - // 定义电池电量区间 - const struct { - uint16_t adc; - uint8_t level; - } levels[] = { - {1600, 0}, - {1700, 20}, - {1800, 40}, - {1900, 60}, - {2000, 80}, - {2100, 100} - }; - - // 低于最低值时 - if (average_voltage < levels[0].adc) { - battery_level_ = 0; - } - // 高于最高值时 - else if (average_voltage >= levels[5].adc) { - battery_level_ = 100; - } else { - // 线性插值计算中间值 - for (int i = 0; i < 5; i++) { - if (average_voltage >= levels[i].adc && average_voltage < levels[i+1].adc) { - float ratio = static_cast(average_voltage - levels[i].adc) / (levels[i+1].adc - levels[i].adc); - battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); - break; - } - } - } - - // Check low battery status - if (voltages_.size() >= kBatteryAdcDataCount) { - bool new_low_battery_status = battery_level_ <= kLowBatteryLevel; - if (new_low_battery_status != is_low_battery_) { - is_low_battery_ = new_low_battery_status; - if (on_low_battery_status_changed_) { - on_low_battery_status_changed_(is_low_battery_); - } - } - } - } - -public: - PowerManager(gpio_num_t pin) : charging_pin_(pin) { - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - // 创建电池电量检查定时器 - esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - PowerManager* self = static_cast(arg); - self->CheckBatteryStatus(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_check_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); - - // 初始化 ADC - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_1, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_)); - - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_3, &chan_config)); - - adc_cali_curve_fitting_config_t cali_config = { - .unit_id = ADC_UNIT_1, - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - ESP_ERROR_CHECK(adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_)); - } - - ~PowerManager() { - if (timer_handle_) { - esp_timer_stop(timer_handle_); - esp_timer_delete(timer_handle_); - } - if (adc_handle_) { - adc_oneshot_del_unit(adc_handle_); - } - } - - bool IsCharging() { - return is_charging_; - } - - bool IsDischarging() { - // 没有区分充电和放电,所以直接返回相反状态 - return !is_charging_; - } - - uint8_t GetBatteryLevel() { - return battery_level_; - } - - void OnLowBatteryStatusChanged(std::function callback) { - on_low_battery_status_changed_ = callback; - } - - void OnChargingStatusChanged(std::function callback) { - on_charging_status_changed_ = callback; - } -}; diff --git a/main/boards/xmini-c3-v3/xmini_c3_board.cc b/main/boards/xmini-c3-v3/xmini_c3_board.cc index ad0eab52..5042e4b3 100644 --- a/main/boards/xmini-c3-v3/xmini_c3_board.cc +++ b/main/boards/xmini-c3-v3/xmini_c3_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/oled_display.h" #include "application.h" #include "button.h" @@ -7,9 +7,9 @@ #include "mcp_server.h" #include "settings.h" #include "config.h" -#include "power_save_timer.h" +#include "sleep_timer.h" #include "font_awesome_symbols.h" -#include "power_manager.h" +#include "adc_battery_monitor.h" #include #include @@ -23,6 +23,7 @@ LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); + class XminiC3Board : public WifiBoard { private: i2c_master_bus_handle_t codec_i2c_bus_; @@ -31,27 +32,27 @@ private: Display* display_ = nullptr; Button boot_button_; bool press_to_talk_enabled_ = false; - PowerSaveTimer* power_save_timer_ = nullptr; - PowerManager* power_manager_ = nullptr; + SleepTimer* sleep_timer_ = nullptr; + AdcBatteryMonitor* adc_battery_monitor_ = nullptr; void InitializePowerManager() { - power_manager_ = new PowerManager(GPIO_NUM_12); - power_manager_->OnChargingStatusChanged([this](bool is_charging) { + adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 100000, 100000, GPIO_NUM_12); + adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) { if (is_charging) { - power_save_timer_->SetEnabled(false); + sleep_timer_->SetEnabled(false); } else { - power_save_timer_->SetEnabled(true); + sleep_timer_->SetEnabled(true); } }); } void InitializePowerSaveTimer() { #if CONFIG_USE_ESP_WAKE_WORD - power_save_timer_ = new PowerSaveTimer(160, 600); + sleep_timer_ = new SleepTimer(600); #else - power_save_timer_ = new PowerSaveTimer(160, 60); + sleep_timer_ = new SleepTimer(30); #endif - power_save_timer_->OnEnterSleepMode([this]() { + sleep_timer_->OnEnterLightSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); auto display = GetDisplay(); display->SetChatMessage("system", ""); @@ -60,7 +61,7 @@ private: auto codec = GetAudioCodec(); codec->EnableInput(false); }); - power_save_timer_->OnExitSleepMode([this]() { + sleep_timer_->OnExitLightSleepMode([this]() { auto codec = GetAudioCodec(); codec->EnableInput(true); @@ -68,7 +69,7 @@ private: display->SetChatMessage("system", ""); display->SetEmotion("neutral"); }); - power_save_timer_->SetEnabled(true); + sleep_timer_->SetEnabled(true); } void InitializeCodecI2c() { @@ -155,8 +156,8 @@ private: } }); boot_button_.OnPressDown([this]() { - if (power_save_timer_) { - power_save_timer_->WakeUp(); + if (sleep_timer_) { + sleep_timer_->WakeUp(); } if (press_to_talk_enabled_) { Application::GetInstance().StartListening(); @@ -173,9 +174,6 @@ private: Settings settings("vendor"); press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0; -#if CONFIG_IOT_PROTOCOL_XIAOZHI -#error "XiaoZhi 协议不支持" -#elif CONFIG_IOT_PROTOCOL_MCP auto& mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.set_press_to_talk", "Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n" @@ -194,11 +192,10 @@ private: } throw std::runtime_error("Invalid mode: " + mode); }); -#endif } public: - XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO) { + XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) { InitializePowerManager(); InitializePowerSaveTimer(); InitializeCodecI2c(); @@ -224,14 +221,9 @@ public: } virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { - static bool last_discharging = false; - charging = power_manager_->IsCharging(); - discharging = power_manager_->IsDischarging(); - if (discharging != last_discharging) { - power_save_timer_->SetEnabled(discharging); - last_discharging = discharging; - } - level = power_manager_->GetBatteryLevel(); + charging = adc_battery_monitor_->IsCharging(); + discharging = adc_battery_monitor_->IsDischarging(); + level = adc_battery_monitor_->GetBatteryLevel(); return true; } @@ -249,7 +241,7 @@ public: virtual void SetPowerSaveMode(bool enabled) override { if (!enabled) { - power_save_timer_->WakeUp(); + sleep_timer_->WakeUp(); } WifiBoard::SetPowerSaveMode(enabled); } diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index 96933514..b67ea218 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -1,5 +1,5 @@ #include "wifi_board.h" -#include "audio_codecs/es8311_audio_codec.h" +#include "codecs/es8311_audio_codec.h" #include "display/oled_display.h" #include "application.h" #include "button.h" @@ -160,9 +160,6 @@ private: Settings settings("vendor"); press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0; -#if CONFIG_IOT_PROTOCOL_XIAOZHI -#error "XiaoZhi 协议不支持" -#elif CONFIG_IOT_PROTOCOL_MCP auto& mcp_server = McpServer::GetInstance(); mcp_server.AddTool("self.set_press_to_talk", "Switch between press to talk mode (长按说话) and click to talk mode (单击说话).\n" @@ -181,7 +178,6 @@ private: } throw std::runtime_error("Invalid mode: " + mode); }); -#endif } public: diff --git a/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc b/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc index e73fd548..d0738102 100644 --- a/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc +++ b/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc @@ -1,12 +1,11 @@ #include "dual_network_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "../zhengchen-1.54tft-wifi/zhengchen_lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "../zhengchen-1.54tft-wifi/power_manager.h" @@ -167,13 +166,6 @@ private: display_->SetupHighTempWarningPopup(); } - void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); - } - public: ZHENGCHEN_1_54TFT_ML307() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), @@ -184,8 +176,7 @@ public: InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); - InitializeSt7789Display(); - InitializeIot(); + InitializeSt7789Display(); GetBacklight()->RestoreBrightness(); } diff --git a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc index b7e18bc6..71f1748b 100644 --- a/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc +++ b/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc @@ -1,12 +1,11 @@ #include "wifi_board.h" -#include "audio_codecs/no_audio_codec.h" +#include "codecs/no_audio_codec.h" #include "zhengchen_lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" -#include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "power_manager.h" @@ -179,13 +178,7 @@ private: display_->SetupHighTempWarningPopup(); } - void InitializeIot() { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Screen")); - thing_manager.AddThing(iot::CreateThing("Battery")); -#endif + void InitializeTools() { } public: @@ -198,7 +191,7 @@ ZHENGCHEN_1_54TFT_WIFI() : InitializeSpi(); InitializeButtons(); InitializeSt7789Display(); - InitializeIot(); + InitializeTools(); GetBacklight()->RestoreBrightness(); } diff --git a/main/display/display.cc b/main/display/display.cc index 3f34ede0..5fa96732 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -69,6 +69,8 @@ void Display::SetStatus(const char* status) { lv_label_set_text(status_label_, status); lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN); lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); + + last_status_update_time_ = std::chrono::system_clock::now(); } void Display::ShowNotification(const std::string ¬ification, int duration_ms) { @@ -89,9 +91,11 @@ void Display::ShowNotification(const char* notification, int duration_ms) { } void Display::UpdateStatusBar(bool update_all) { + auto& app = Application::GetInstance(); auto& board = Board::GetInstance(); auto codec = board.GetAudioCodec(); + // Update mute icon { DisplayLockGuard lock(this); if (mute_label_ == nullptr) { @@ -108,6 +112,23 @@ void Display::UpdateStatusBar(bool update_all) { } } + // Update time + if (app.GetDeviceState() == kDeviceStateIdle) { + if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) { + // Set status to clock "HH:MM" + time_t now = time(NULL); + struct tm* tm = localtime(&now); + // Check if the we have already set the time + if (tm->tm_year >= 2025 - 1900) { + char time_str[16]; + strftime(time_str, sizeof(time_str), "%H:%M ", tm); + SetStatus(time_str); + } else { + ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year); + } + } + } + esp_pm_lock_acquire(pm_lock_); // 更新电池图标 int battery_level; @@ -137,7 +158,6 @@ void Display::UpdateStatusBar(bool update_all) { if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) { if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // 如果低电量提示框隐藏,则显示 lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - auto& app = Application::GetInstance(); app.PlaySound(Lang::Sounds::P3_LOW_BATTERY); } } else { @@ -248,3 +268,13 @@ void Display::SetTheme(const std::string& theme_name) { Settings settings("display", true); settings.SetString("theme", theme_name); } + +void Display::ShowStandbyScreen(bool show) { + if (show) { + SetChatMessage("system", ""); + SetEmotion("sleepy"); + } else { + SetChatMessage("system", ""); + SetEmotion("neutral"); + } +} \ No newline at end of file diff --git a/main/display/display.h b/main/display/display.h index 8d89f4bd..8952f533 100644 --- a/main/display/display.h +++ b/main/display/display.h @@ -7,6 +7,7 @@ #include #include +#include struct DisplayFonts { const lv_font_t* text_font = nullptr; @@ -29,6 +30,7 @@ public: virtual void SetTheme(const std::string& theme_name); virtual std::string GetTheme() { return current_theme_name_; } virtual void UpdateStatusBar(bool update_all = false); + virtual void ShowStandbyScreen(bool show); inline int width() const { return width_; } inline int height() const { return height_; } @@ -55,6 +57,7 @@ protected: bool muted_ = false; std::string current_theme_name_; + std::chrono::system_clock::time_point last_status_update_time_; esp_timer_handle_t notification_timer_ = nullptr; friend class DisplayLockGuard; diff --git a/main/idf_component.yml b/main/idf_component.yml index 7d5d3e32..19df4921 100644 --- a/main/idf_component.yml +++ b/main/idf_component.yml @@ -15,7 +15,7 @@ dependencies: 78/esp_lcd_nv3023: ~1.0.0 78/esp-wifi-connect: ~2.4.3 78/esp-opus-encoder: ~2.3.3 - 78/esp-ml307: ~3.0.0 + 78/esp-ml307: ~3.0.2 78/xiaozhi-fonts: ~1.3.2 espressif/led_strip: ^2.5.5 espressif/esp_codec_dev: ~1.3.2 diff --git a/main/iot/README.md b/main/iot/README.md deleted file mode 100644 index 7ce70da4..00000000 --- a/main/iot/README.md +++ /dev/null @@ -1,211 +0,0 @@ -# 物联网控制模块 - -> ⚠️ **注意:本模块已不推荐使用。请使用"MCP协议"来实现物联网控制,获得更好的兼容性与功能支持。** - -本模块实现了小智AI语音聊天机器人的物联网控制功能,使用户可以通过语音指令控制接入到ESP32开发板的各种物联网设备。 - -## 工作原理 - -整个物联网控制模块的工作流程如下: - -1. **设备注册**:在开发板初始化阶段(如在`compact_wifi_board.cc`中),各种物联网设备通过`ThingManager`注册到系统中 -2. **设备描述**:系统将设备描述信息(包括名称、属性、方法等)通过通信协议(如MQTT或WebSocket)发送给AI服务器 -3. **用户交互**:用户通过语音与小智AI对话,表达控制物联网设备的意图 -4. **命令执行**:AI服务器解析用户意图,生成控制命令,通过协议发送回ESP32,由`ThingManager`分发给对应的设备执行 -5. **状态更新**:设备执行命令后,状态变化会通过`ThingManager`收集并发送回AI服务器,保持状态同步 - -## 核心组件 - -### ThingManager - -`ThingManager`是物联网控制模块的核心管理类,采用单例模式实现: - -- `AddThing`:注册物联网设备 -- `GetDescriptorsJson`:获取所有设备的描述信息,用于向AI服务器报告设备能力 -- `GetStatesJson`:获取所有设备的当前状态,可以选择只返回变化的部分 -- `Invoke`:根据AI服务器下发的命令,调用对应设备的方法 - -### Thing - -`Thing`是所有物联网设备的基类,提供了以下核心功能: - -- 属性管理:通过`PropertyList`定义设备的可查询状态 -- 方法管理:通过`MethodList`定义设备可执行的操作 -- JSON序列化:将设备描述和状态转换为JSON格式,便于网络传输 -- 命令执行:解析和执行来自AI服务器的指令 - -## 设备设计示例 - -### 灯(Lamp) - -灯是一个简单的物联网设备示例,通过GPIO控制LED的开关状态: - -```cpp -class Lamp : public Thing { -private: - gpio_num_t gpio_num_ = GPIO_NUM_18; // GPIO引脚 - bool power_ = false; // 灯的开关状态 - -public: - Lamp() : Thing("Lamp", "一个测试用的灯") { - // 初始化GPIO - InitializeGpio(); - - // 定义属性:power(表示灯的开关状态) - properties_.AddBooleanProperty("power", "灯是否打开", [this]() -> bool { - return power_; - }); - - // 定义方法:TurnOn(打开灯) - methods_.AddMethod("TurnOn", "打开灯", ParameterList(), [this](const ParameterList& parameters) { - power_ = true; - gpio_set_level(gpio_num_, 1); - }); - - // 定义方法:TurnOff(关闭灯) - methods_.AddMethod("TurnOff", "关闭灯", ParameterList(), [this](const ParameterList& parameters) { - power_ = false; - gpio_set_level(gpio_num_, 0); - }); - } -}; -``` - -用户可以通过语音指令如"小智,请打开灯"来控制灯的开关。 - -### 扬声器(Speaker) - -扬声器控制实现了音量调节功能: - -```cpp -class Speaker : public Thing { -public: - Speaker() : Thing("Speaker", "扬声器") { - // 定义属性:volume(当前音量值) - properties_.AddNumberProperty("volume", "当前音量值", [this]() -> int { - auto codec = Board::GetInstance().GetAudioCodec(); - return codec->output_volume(); - }); - - // 定义方法:SetVolume(设置音量) - methods_.AddMethod("SetVolume", "设置音量", ParameterList({ - Parameter("volume", "0到100之间的整数", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - auto codec = Board::GetInstance().GetAudioCodec(); - codec->SetOutputVolume(static_cast(parameters["volume"].number())); - }); - } -}; -``` - -用户可以通过语音指令如"小智,把音量调到50"来控制扬声器的音量。 - -## 设计自定义物联网设备 - -要设计一个新的物联网设备,需要以下步骤: - -1. **创建设备类**:继承`Thing`基类 -2. **定义属性**:使用`properties_`添加设备的可查询状态 -3. **定义方法**:使用`methods_`添加设备可执行的操作 -4. **实现硬件控制**:在方法回调中实现对硬件的控制 -5. **注册设备**:注册设备有两种方式(见下文),并在板级初始化中添加设备实例 - -### 两种设备注册方式 - -1. **使用DECLARE_THING宏**:适合通用设备类型 - ```cpp - // 在设备实现文件末尾添加 - DECLARE_THING(MyDevice); - - // 然后在板级初始化中 - thing_manager.AddThing(iot::CreateThing("MyDevice")); - ``` - -2. **直接创建设备实例**:适合特定于板级的设备 - ```cpp - // 在板级初始化中 - auto my_device = new iot::MyDevice(); - thing_manager.AddThing(my_device); - ``` - -### 设备实现位置建议 - -您可以根据设备的通用性选择不同的实现位置: - -1. **通用设备**:放在`main/iot/things/`目录下,使用`DECLARE_THING`注册 -2. **板级特定设备**:直接在板级目录(如`main/boards/your_board/`)中实现,使用直接创建实例的方式注册 - -这种灵活性允许您为不同的板设计特定的设备实现,同时保持通用设备的可重用性。 - -### 属性类型 - -物联网设备支持以下属性类型: - -- **布尔值**(`kValueTypeBoolean`):开关状态等 -- **数值**(`kValueTypeNumber`):温度、音量等 -- **字符串**(`kValueTypeString`):设备名称、状态描述等 - -### 方法参数 - -设备方法可以定义参数,支持以下参数类型: - -- **布尔值**:开关等 -- **数值**:温度、音量等 -- **字符串**:命令、模式等 - -## 使用示例 - -在板级初始化代码(如`compact_wifi_board.cc`)中注册物联网设备: - -```cpp -void InitializeIot() { - auto& thing_manager = iot::ThingManager::GetInstance(); - thing_manager.AddThing(iot::CreateThing("Speaker")); - thing_manager.AddThing(iot::CreateThing("Lamp")); -} -``` - -之后,用户可以通过语音指令控制这些设备,例如: - -- "小智,打开灯" -- "我要睡觉了,帮我关灯" -- "音量有点太小了" -- "把音量设置为80%" - -AI服务器会将这些语音指令解析为对应的设备控制命令,通过协议发送给ESP32执行。 - -## 注意事项 - -### 大模型控制的随机性 - -由于语音控制由大型语言模型(LLM)处理,控制过程可能存在一定的随机性和不确定性。为了增强安全性和可靠性,请考虑以下建议: - -1. **关键操作添加警示信息**:对于潜在危险或不可逆的操作,在方法描述中添加警示信息 - ```cpp - methods_.AddMethod("PowerOff", "关闭系统电源[警告:此操作将导致设备完全关闭,请慎重使用]", - ParameterList(), [this](const ParameterList& parameters) { - // 关闭电源的实现 - }); - ``` - -2. **二次确认机制**:重要操作应在描述中明确要求二次确认 - ```cpp - methods_.AddMethod("ResetToFactory", "恢复出厂设置[必须要用户二次确认]", - ParameterList(), [this](const ParameterList& parameters) { - // 恢复出厂设置的实现 - }); - ``` - -### 通信协议限制 - -当前IoT协议(1.0版本)存在以下限制: - -1. **单向控制流**:大模型只能下发指令,无法立即获取指令执行结果 -2. **状态更新延迟**:设备状态变更需要等到下一轮对话时,通过读取property属性值才能获知 -3. **异步反馈**:如果需要操作结果反馈,必须通过设备属性的方式间接实现 - -### 最佳实践 - -1. **使用有意义的属性名称**:属性名称应清晰表达其含义,便于大模型理解和使用 - -2. **不产生歧义的方法描述**:为每个方法提供明确的自然语言描述,帮助大模型更准确地理解和调用 \ No newline at end of file diff --git a/main/iot/thing.cc b/main/iot/thing.cc deleted file mode 100644 index 77f4680e..00000000 --- a/main/iot/thing.cc +++ /dev/null @@ -1,92 +0,0 @@ -#include "thing.h" -#include "application.h" - -#include - -#define TAG "Thing" - - -namespace iot { - -#if CONFIG_IOT_PROTOCOL_XIAOZHI -static std::map>* thing_creators = nullptr; -#endif - -void RegisterThing(const std::string& type, std::function creator) { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - if (thing_creators == nullptr) { - thing_creators = new std::map>(); - } - (*thing_creators)[type] = creator; -#endif -} - -Thing* CreateThing(const std::string& type) { -#if CONFIG_IOT_PROTOCOL_XIAOZHI - auto creator = thing_creators->find(type); - if (creator == thing_creators->end()) { - ESP_LOGE(TAG, "Thing type not found: %s", type.c_str()); - return nullptr; - } - return creator->second(); -#else - return nullptr; -#endif -} - -std::string Thing::GetDescriptorJson() { - std::string json_str = "{"; - json_str += "\"name\":\"" + name_ + "\","; - json_str += "\"description\":\"" + description_ + "\","; - json_str += "\"properties\":" + properties_.GetDescriptorJson() + ","; - json_str += "\"methods\":" + methods_.GetDescriptorJson(); - json_str += "}"; - return json_str; -} - -std::string Thing::GetStateJson() { - std::string json_str = "{"; - json_str += "\"name\":\"" + name_ + "\","; - json_str += "\"state\":" + properties_.GetStateJson(); - json_str += "}"; - return json_str; -} - -void Thing::Invoke(const cJSON* command) { - auto method_name = cJSON_GetObjectItem(command, "method"); - auto input_params = cJSON_GetObjectItem(command, "parameters"); - - try { - auto& method = methods_[method_name->valuestring]; - for (auto& param : method.parameters()) { - auto input_param = cJSON_GetObjectItem(input_params, param.name().c_str()); - if (param.required() && input_param == nullptr) { - throw std::runtime_error("Parameter " + param.name() + " is required"); - } - if (param.type() == kValueTypeNumber) { - if (cJSON_IsNumber(input_param)) { - param.set_number(input_param->valueint); - } - } else if (param.type() == kValueTypeString) { - if (cJSON_IsString(input_param) || cJSON_IsObject(input_param) || cJSON_IsArray(input_param)) { - std::string value_str = input_param->valuestring; - param.set_string(value_str); - } - } else if (param.type() == kValueTypeBoolean) { - if (cJSON_IsBool(input_param)) { - param.set_boolean(input_param->valueint == 1); - } - } - } - - Application::GetInstance().Schedule([&method]() { - method.Invoke(); - }); - } catch (const std::runtime_error& e) { - ESP_LOGE(TAG, "Method not found: %s", method_name->valuestring); - return; - } -} - - -} // namespace iot diff --git a/main/iot/thing.h b/main/iot/thing.h deleted file mode 100644 index 21c8d691..00000000 --- a/main/iot/thing.h +++ /dev/null @@ -1,300 +0,0 @@ -#ifndef THING_H -#define THING_H - -#include -#include -#include -#include -#include -#include - -namespace iot { - -enum ValueType { - kValueTypeBoolean, - kValueTypeNumber, - kValueTypeString -}; - -class Property { -private: - std::string name_; - std::string description_; - ValueType type_; - std::function boolean_getter_; - std::function number_getter_; - std::function string_getter_; - -public: - Property(const std::string& name, const std::string& description, std::function getter) : - name_(name), description_(description), type_(kValueTypeBoolean), boolean_getter_(getter) {} - Property(const std::string& name, const std::string& description, std::function getter) : - name_(name), description_(description), type_(kValueTypeNumber), number_getter_(getter) {} - Property(const std::string& name, const std::string& description, std::function getter) : - name_(name), description_(description), type_(kValueTypeString), string_getter_(getter) {} - - const std::string& name() const { return name_; } - const std::string& description() const { return description_; } - ValueType type() const { return type_; } - - bool boolean() const { return boolean_getter_(); } - int number() const { return number_getter_(); } - std::string string() const { return string_getter_(); } - - std::string GetDescriptorJson() { - std::string json_str = "{"; - json_str += "\"description\":\"" + description_ + "\","; - if (type_ == kValueTypeBoolean) { - json_str += "\"type\":\"boolean\""; - } else if (type_ == kValueTypeNumber) { - json_str += "\"type\":\"number\""; - } else if (type_ == kValueTypeString) { - json_str += "\"type\":\"string\""; - } - json_str += "}"; - return json_str; - } - - std::string GetStateJson() { - if (type_ == kValueTypeBoolean) { - return boolean_getter_() ? "true" : "false"; - } else if (type_ == kValueTypeNumber) { - return std::to_string(number_getter_()); - } else if (type_ == kValueTypeString) { - return "\"" + string_getter_() + "\""; - } - return "null"; - } -}; - -class PropertyList { -private: - std::vector properties_; - -public: - PropertyList() = default; - PropertyList(const std::vector& properties) : properties_(properties) {} - - void AddBooleanProperty(const std::string& name, const std::string& description, std::function getter) { - properties_.push_back(Property(name, description, getter)); - } - void AddNumberProperty(const std::string& name, const std::string& description, std::function getter) { - properties_.push_back(Property(name, description, getter)); - } - void AddStringProperty(const std::string& name, const std::string& description, std::function getter) { - properties_.push_back(Property(name, description, getter)); - } - - const Property& operator[](const std::string& name) const { - for (auto& property : properties_) { - if (property.name() == name) { - return property; - } - } - throw std::runtime_error("Property not found: " + name); - } - - std::string GetDescriptorJson() { - std::string json_str = "{"; - for (auto& property : properties_) { - json_str += "\"" + property.name() + "\":" + property.GetDescriptorJson() + ","; - } - if (json_str.back() == ',') { - json_str.pop_back(); - } - json_str += "}"; - return json_str; - } - - std::string GetStateJson() { - std::string json_str = "{"; - for (auto& property : properties_) { - json_str += "\"" + property.name() + "\":" + property.GetStateJson() + ","; - } - if (json_str.back() == ',') { - json_str.pop_back(); - } - json_str += "}"; - return json_str; - } -}; - -class Parameter { -private: - std::string name_; - std::string description_; - ValueType type_; - bool required_; - bool boolean_; - int number_; - std::string string_; - -public: - Parameter(const std::string& name, const std::string& description, ValueType type, bool required = true) : - name_(name), description_(description), type_(type), required_(required) {} - - const std::string& name() const { return name_; } - const std::string& description() const { return description_; } - ValueType type() const { return type_; } - bool required() const { return required_; } - - bool boolean() const { return boolean_; } - int number() const { return number_; } - const std::string& string() const { return string_; } - - void set_boolean(bool value) { boolean_ = value; } - void set_number(int value) { number_ = value; } - void set_string(const std::string& value) { string_ = value; } - - std::string GetDescriptorJson() { - std::string json_str = "{"; - json_str += "\"description\":\"" + description_ + "\","; - if (type_ == kValueTypeBoolean) { - json_str += "\"type\":\"boolean\""; - } else if (type_ == kValueTypeNumber) { - json_str += "\"type\":\"number\""; - } else if (type_ == kValueTypeString) { - json_str += "\"type\":\"string\""; - } - json_str += "}"; - return json_str; - } -}; - -class ParameterList { -private: - std::vector parameters_; - -public: - ParameterList() = default; - ParameterList(const std::vector& parameters) : parameters_(parameters) {} - void AddParameter(const Parameter& parameter) { - parameters_.push_back(parameter); - } - - const Parameter& operator[](const std::string& name) const { - for (auto& parameter : parameters_) { - if (parameter.name() == name) { - return parameter; - } - } - throw std::runtime_error("Parameter not found: " + name); - } - - // iterator - auto begin() { return parameters_.begin(); } - auto end() { return parameters_.end(); } - - std::string GetDescriptorJson() { - std::string json_str = "{"; - for (auto& parameter : parameters_) { - json_str += "\"" + parameter.name() + "\":" + parameter.GetDescriptorJson() + ","; - } - if (json_str.back() == ',') { - json_str.pop_back(); - } - json_str += "}"; - return json_str; - } -}; - -class Method { -private: - std::string name_; - std::string description_; - ParameterList parameters_; - std::function callback_; - -public: - Method(const std::string& name, const std::string& description, const ParameterList& parameters, std::function callback) : - name_(name), description_(description), parameters_(parameters), callback_(callback) {} - - const std::string& name() const { return name_; } - const std::string& description() const { return description_; } - ParameterList& parameters() { return parameters_; } - - std::string GetDescriptorJson() { - std::string json_str = "{"; - json_str += "\"description\":\"" + description_ + "\","; - json_str += "\"parameters\":" + parameters_.GetDescriptorJson(); - json_str += "}"; - return json_str; - } - - void Invoke() { - callback_(parameters_); - } -}; - -class MethodList { -private: - std::vector methods_; - -public: - MethodList() = default; - MethodList(const std::vector& methods) : methods_(methods) {} - - void AddMethod(const std::string& name, const std::string& description, const ParameterList& parameters, std::function callback) { - methods_.push_back(Method(name, description, parameters, callback)); - } - - Method& operator[](const std::string& name) { - for (auto& method : methods_) { - if (method.name() == name) { - return method; - } - } - throw std::runtime_error("Method not found: " + name); - } - - std::string GetDescriptorJson() { - std::string json_str = "{"; - for (auto& method : methods_) { - json_str += "\"" + method.name() + "\":" + method.GetDescriptorJson() + ","; - } - if (json_str.back() == ',') { - json_str.pop_back(); - } - json_str += "}"; - return json_str; - } -}; - -class Thing { -public: - Thing(const std::string& name, const std::string& description) : - name_(name), description_(description) {} - virtual ~Thing() = default; - - virtual std::string GetDescriptorJson(); - virtual std::string GetStateJson(); - virtual void Invoke(const cJSON* command); - - const std::string& name() const { return name_; } - const std::string& description() const { return description_; } - -protected: - PropertyList properties_; - MethodList methods_; - -private: - std::string name_; - std::string description_; -}; - - -void RegisterThing(const std::string& type, std::function creator); -Thing* CreateThing(const std::string& type); - -#define DECLARE_THING(TypeName) \ - static iot::Thing* Create##TypeName() { \ - return new iot::TypeName(); \ - } \ - static bool Register##TypeNameHelper = []() { \ - RegisterThing(#TypeName, Create##TypeName); \ - return true; \ - }(); - -} // namespace iot - -#endif // THING_H diff --git a/main/iot/thing_manager.cc b/main/iot/thing_manager.cc deleted file mode 100644 index 92438697..00000000 --- a/main/iot/thing_manager.cc +++ /dev/null @@ -1,63 +0,0 @@ -#include "thing_manager.h" - -#include - -#define TAG "ThingManager" - -namespace iot { - -void ThingManager::AddThing(Thing* thing) { - things_.push_back(thing); -} - -std::string ThingManager::GetDescriptorsJson() { - std::string json_str = "["; - for (auto& thing : things_) { - json_str += thing->GetDescriptorJson() + ","; - } - if (json_str.back() == ',') { - json_str.pop_back(); - } - json_str += "]"; - return json_str; -} - -bool ThingManager::GetStatesJson(std::string& json, bool delta) { - if (!delta) { - last_states_.clear(); - } - bool changed = false; - json = "["; - // 枚举thing,获取每个thing的state,如果发生变化,则更新,保存到last_states_ - // 如果delta为true,则只返回变化的部分 - for (auto& thing : things_) { - std::string state = thing->GetStateJson(); - if (delta) { - // 如果delta为true,则只返回变化的部分 - auto it = last_states_.find(thing->name()); - if (it != last_states_.end() && it->second == state) { - continue; - } - changed = true; - last_states_[thing->name()] = state; - } - json += state + ","; - } - if (json.back() == ',') { - json.pop_back(); - } - json += "]"; - return changed; -} - -void ThingManager::Invoke(const cJSON* command) { - auto name = cJSON_GetObjectItem(command, "name"); - for (auto& thing : things_) { - if (thing->name() == name->valuestring) { - thing->Invoke(command); - return; - } - } -} - -} // namespace iot diff --git a/main/iot/thing_manager.h b/main/iot/thing_manager.h deleted file mode 100644 index d51c910b..00000000 --- a/main/iot/thing_manager.h +++ /dev/null @@ -1,42 +0,0 @@ -#ifndef THING_MANAGER_H -#define THING_MANAGER_H - - -#include "thing.h" - -#include - -#include -#include -#include -#include - -namespace iot { - -class ThingManager { -public: - static ThingManager& GetInstance() { - static ThingManager instance; - return instance; - } - ThingManager(const ThingManager&) = delete; - ThingManager& operator=(const ThingManager&) = delete; - - void AddThing(Thing* thing); - - std::string GetDescriptorsJson(); - bool GetStatesJson(std::string& json, bool delta = false); - void Invoke(const cJSON* command); - -private: - ThingManager() = default; - ~ThingManager() = default; - - std::vector things_; - std::map last_states_; -}; - - -} // namespace iot - -#endif // THING_MANAGER_H diff --git a/main/iot/things/battery.cc b/main/iot/things/battery.cc deleted file mode 100644 index 4bded669..00000000 --- a/main/iot/things/battery.cc +++ /dev/null @@ -1,35 +0,0 @@ -#include "iot/thing.h" -#include "board.h" - -#include - -#define TAG "Battery" - -namespace iot { - -// 这里仅定义 Battery 的属性和方法,不包含具体的实现 -class Battery : public Thing { -private: - int level_ = 0; - bool charging_ = false; - bool discharging_ = false; - -public: - Battery() : Thing("Battery", "The battery of the device") { - // 定义设备的属性 - properties_.AddNumberProperty("level", "Current battery level", [this]() -> int { - auto& board = Board::GetInstance(); - if (board.GetBatteryLevel(level_, charging_, discharging_)) { - return level_; - } - return 0; - }); - properties_.AddBooleanProperty("charging", "Whether the battery is charging", [this]() -> int { - return charging_; - }); - } -}; - -} // namespace iot - -DECLARE_THING(Battery); \ No newline at end of file diff --git a/main/iot/things/lamp.cc b/main/iot/things/lamp.cc deleted file mode 100644 index e50f5425..00000000 --- a/main/iot/things/lamp.cc +++ /dev/null @@ -1,58 +0,0 @@ -#include "iot/thing.h" -#include "board.h" -#include "audio_codec.h" - -#include -#include - -#define TAG "Lamp" - -namespace iot { - -// 这里仅定义 Lamp 的属性和方法,不包含具体的实现 -class Lamp : public Thing { -private: -#ifdef CONFIG_IDF_TARGET_ESP32 - gpio_num_t gpio_num_ = GPIO_NUM_12; -#else - gpio_num_t gpio_num_ = GPIO_NUM_18; -#endif - bool power_ = false; - - void InitializeGpio() { - gpio_config_t config = { - .pin_bit_mask = (1ULL << gpio_num_), - .mode = GPIO_MODE_OUTPUT, - .pull_up_en = GPIO_PULLUP_DISABLE, - .pull_down_en = GPIO_PULLDOWN_DISABLE, - .intr_type = GPIO_INTR_DISABLE, - }; - ESP_ERROR_CHECK(gpio_config(&config)); - gpio_set_level(gpio_num_, 0); - } - -public: - Lamp() : Thing("Lamp", "A test lamp"), power_(false) { - InitializeGpio(); - - // 定义设备的属性 - properties_.AddBooleanProperty("power", "Whether the lamp is on", [this]() -> bool { - return power_; - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("turn_on", "Turn on the lamp", ParameterList(), [this](const ParameterList& parameters) { - power_ = true; - gpio_set_level(gpio_num_, 1); - }); - - methods_.AddMethod("turn_off", "Turn off the lamp", ParameterList(), [this](const ParameterList& parameters) { - power_ = false; - gpio_set_level(gpio_num_, 0); - }); - } -}; - -} // namespace iot - -DECLARE_THING(Lamp); diff --git a/main/iot/things/screen.cc b/main/iot/things/screen.cc deleted file mode 100644 index c266221a..00000000 --- a/main/iot/things/screen.cc +++ /dev/null @@ -1,54 +0,0 @@ -#include "iot/thing.h" -#include "board.h" -#include "display/lcd_display.h" -#include "settings.h" - -#include -#include - -#define TAG "Screen" - -namespace iot { - -// 这里仅定义 Screen 的属性和方法,不包含具体的实现 -class Screen : public Thing { -public: - Screen() : Thing("Screen", "A screen that can set theme and brightness") { - // 定义设备的属性 - properties_.AddStringProperty("theme", "Current theme", [this]() -> std::string { - auto theme = Board::GetInstance().GetDisplay()->GetTheme(); - return theme; - }); - - properties_.AddNumberProperty("brightness", "Current brightness percentage", [this]() -> int { - // 这里可以添加获取当前亮度的逻辑 - auto backlight = Board::GetInstance().GetBacklight(); - return backlight ? backlight->brightness() : 100; - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("set_theme", "Set the screen theme", ParameterList({ - Parameter("theme_name", "Valid string values are 'light' and 'dark'", kValueTypeString, true) - }), [this](const ParameterList& parameters) { - std::string theme_name = static_cast(parameters["theme_name"].string()); - auto display = Board::GetInstance().GetDisplay(); - if (display) { - display->SetTheme(theme_name); - } - }); - - methods_.AddMethod("set_brightness", "Set the brightness", ParameterList({ - Parameter("brightness", "An integer between 0 and 100", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - uint8_t brightness = static_cast(parameters["brightness"].number()); - auto backlight = Board::GetInstance().GetBacklight(); - if (backlight) { - backlight->SetBrightness(brightness, true); - } - }); - } -}; - -} // namespace iot - -DECLARE_THING(Screen); \ No newline at end of file diff --git a/main/iot/things/speaker.cc b/main/iot/things/speaker.cc deleted file mode 100644 index cf15beb1..00000000 --- a/main/iot/things/speaker.cc +++ /dev/null @@ -1,33 +0,0 @@ -#include "iot/thing.h" -#include "board.h" -#include "audio_codec.h" - -#include - -#define TAG "Speaker" - -namespace iot { - -// 这里仅定义 Speaker 的属性和方法,不包含具体的实现 -class Speaker : public Thing { -public: - Speaker() : Thing("AudioSpeaker", "The audio speaker of the device") { - // 定义设备的属性 - properties_.AddNumberProperty("volume", "Current audio volume value", [this]() -> int { - auto codec = Board::GetInstance().GetAudioCodec(); - return codec->output_volume(); - }); - - // 定义设备可以被远程执行的指令 - methods_.AddMethod("set_volume", "Set the audio volume", ParameterList({ - Parameter("volume", "An integer between 0 and 100", kValueTypeNumber, true) - }), [this](const ParameterList& parameters) { - auto codec = Board::GetInstance().GetAudioCodec(); - codec->SetOutputVolume(static_cast(parameters["volume"].number())); - }); - } -}; - -} // namespace iot - -DECLARE_THING(Speaker); diff --git a/main/ota.cc b/main/ota.cc index 435d94b7..a3e5be1b 100644 --- a/main/ota.cc +++ b/main/ota.cc @@ -260,13 +260,13 @@ void Ota::MarkCurrentVersionValid() { } } -void Ota::Upgrade(const std::string& firmware_url) { +bool Ota::Upgrade(const std::string& firmware_url) { ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str()); esp_ota_handle_t update_handle = 0; auto update_partition = esp_ota_get_next_update_partition(NULL); if (update_partition == NULL) { ESP_LOGE(TAG, "Failed to get update partition"); - return; + return false; } ESP_LOGI(TAG, "Writing to partition %s at offset 0x%lx", update_partition->label, update_partition->address); @@ -277,18 +277,18 @@ void Ota::Upgrade(const std::string& firmware_url) { auto http = std::unique_ptr(network->CreateHttp(0)); if (!http->Open("GET", firmware_url)) { ESP_LOGE(TAG, "Failed to open HTTP connection"); - return; + return false; } if (http->GetStatusCode() != 200) { ESP_LOGE(TAG, "Failed to get firmware, status code: %d", http->GetStatusCode()); - return; + return false; } size_t content_length = http->GetBodyLength(); if (content_length == 0) { ESP_LOGE(TAG, "Failed to get content length"); - return; + return false; } char buffer[512]; @@ -298,7 +298,7 @@ void Ota::Upgrade(const std::string& firmware_url) { int ret = http->Read(buffer, sizeof(buffer)); if (ret < 0) { ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret)); - return; + return false; } // Calculate speed and progress every second @@ -328,13 +328,13 @@ void Ota::Upgrade(const std::string& firmware_url) { auto current_version = esp_app_get_description()->version; if (memcmp(new_app_info.version, current_version, sizeof(new_app_info.version)) == 0) { ESP_LOGE(TAG, "Firmware version is the same, skipping upgrade"); - return; + return false; } if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) { esp_ota_abort(update_handle); ESP_LOGE(TAG, "Failed to begin OTA"); - return; + return false; } image_header_checked = true; @@ -345,7 +345,7 @@ void Ota::Upgrade(const std::string& firmware_url) { if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err)); esp_ota_abort(update_handle); - return; + return false; } } http->Close(); @@ -357,23 +357,22 @@ void Ota::Upgrade(const std::string& firmware_url) { } else { ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err)); } - return; + return false; } err = esp_ota_set_boot_partition(update_partition); if (err != ESP_OK) { ESP_LOGE(TAG, "Failed to set boot partition: %s", esp_err_to_name(err)); - return; + return false; } - ESP_LOGI(TAG, "Firmware upgrade successful, rebooting in 3 seconds..."); - vTaskDelay(pdMS_TO_TICKS(3000)); - esp_restart(); + ESP_LOGI(TAG, "Firmware upgrade successful"); + return true; } -void Ota::StartUpgrade(std::function callback) { +bool Ota::StartUpgrade(std::function callback) { upgrade_callback_ = callback; - Upgrade(firmware_url_); + return Upgrade(firmware_url_); } std::vector Ota::ParseVersion(const std::string& version) { diff --git a/main/ota.h b/main/ota.h index a7bee9a3..f4f739ea 100644 --- a/main/ota.h +++ b/main/ota.h @@ -20,7 +20,7 @@ public: bool HasWebsocketConfig() { return has_websocket_config_; } bool HasActivationCode() { return has_activation_code_; } bool HasServerTime() { return has_server_time_; } - void StartUpgrade(std::function callback); + bool StartUpgrade(std::function callback); void MarkCurrentVersionValid(); const std::string& GetFirmwareVersion() const { return firmware_version_; } @@ -46,7 +46,7 @@ private: std::string serial_number_; int activation_timeout_ms_ = 30000; - void Upgrade(const std::string& firmware_url); + bool Upgrade(const std::string& firmware_url); std::function upgrade_callback_; std::vector ParseVersion(const std::string& version); bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion); diff --git a/main/protocols/mqtt_protocol.cc b/main/protocols/mqtt_protocol.cc index af08b722..e8847ff3 100644 --- a/main/protocols/mqtt_protocol.cc +++ b/main/protocols/mqtt_protocol.cc @@ -40,7 +40,7 @@ bool MqttProtocol::StartMqttClient(bool report_error) { auto client_id = settings.GetString("client_id"); auto username = settings.GetString("username"); auto password = settings.GetString("password"); - int keepalive_interval = settings.GetInt("keepalive", 120); + int keepalive_interval = settings.GetInt("keepalive", 240); publish_topic_ = settings.GetString("publish_topic"); if (endpoint.empty()) { @@ -121,25 +121,25 @@ bool MqttProtocol::SendText(const std::string& text) { return true; } -bool MqttProtocol::SendAudio(const AudioStreamPacket& packet) { +bool MqttProtocol::SendAudio(std::unique_ptr packet) { std::lock_guard lock(channel_mutex_); if (udp_ == nullptr) { return false; } std::string nonce(aes_nonce_); - *(uint16_t*)&nonce[2] = htons(packet.payload.size()); - *(uint32_t*)&nonce[8] = htonl(packet.timestamp); + *(uint16_t*)&nonce[2] = htons(packet->payload.size()); + *(uint32_t*)&nonce[8] = htonl(packet->timestamp); *(uint32_t*)&nonce[12] = htonl(++local_sequence_); std::string encrypted; - encrypted.resize(aes_nonce_.size() + packet.payload.size()); + encrypted.resize(aes_nonce_.size() + packet->payload.size()); memcpy(encrypted.data(), nonce.data(), nonce.size()); size_t nc_off = 0; uint8_t stream_block[16] = {0}; - if (mbedtls_aes_crypt_ctr(&aes_ctx_, packet.payload.size(), &nc_off, (uint8_t*)nonce.c_str(), stream_block, - (uint8_t*)packet.payload.data(), (uint8_t*)&encrypted[nonce.size()]) != 0) { + if (mbedtls_aes_crypt_ctr(&aes_ctx_, packet->payload.size(), &nc_off, (uint8_t*)nonce.c_str(), stream_block, + (uint8_t*)packet->payload.data(), (uint8_t*)&encrypted[nonce.size()]) != 0) { ESP_LOGE(TAG, "Failed to encrypt audio data"); return false; } @@ -228,12 +228,12 @@ bool MqttProtocol::OpenAudioChannel() { uint8_t stream_block[16] = {0}; auto nonce = (uint8_t*)data.data(); auto encrypted = (uint8_t*)data.data() + aes_nonce_.size(); - AudioStreamPacket packet; - packet.sample_rate = server_sample_rate_; - packet.frame_duration = server_frame_duration_; - packet.timestamp = timestamp; - packet.payload.resize(decrypted_size); - int ret = mbedtls_aes_crypt_ctr(&aes_ctx_, decrypted_size, &nc_off, nonce, stream_block, encrypted, (uint8_t*)packet.payload.data()); + auto packet = std::make_unique(); + packet->sample_rate = server_sample_rate_; + packet->frame_duration = server_frame_duration_; + packet->timestamp = timestamp; + packet->payload.resize(decrypted_size); + int ret = mbedtls_aes_crypt_ctr(&aes_ctx_, decrypted_size, &nc_off, nonce, stream_block, encrypted, (uint8_t*)packet->payload.data()); if (ret != 0) { ESP_LOGE(TAG, "Failed to decrypt audio data, ret: %d", ret); return; @@ -263,9 +263,7 @@ std::string MqttProtocol::GetHelloMessage() { #if CONFIG_USE_SERVER_AEC cJSON_AddBoolToObject(features, "aec", true); #endif -#if CONFIG_IOT_PROTOCOL_MCP cJSON_AddBoolToObject(features, "mcp", true); -#endif cJSON_AddItemToObject(root, "features", features); cJSON* audio_params = cJSON_CreateObject(); cJSON_AddStringToObject(audio_params, "format", "opus"); diff --git a/main/protocols/mqtt_protocol.h b/main/protocols/mqtt_protocol.h index d85bf684..00fbce64 100644 --- a/main/protocols/mqtt_protocol.h +++ b/main/protocols/mqtt_protocol.h @@ -26,7 +26,7 @@ public: ~MqttProtocol(); bool Start() override; - bool SendAudio(const AudioStreamPacket& packet) override; + bool SendAudio(std::unique_ptr packet) override; bool OpenAudioChannel() override; void CloseAudioChannel() override; bool IsAudioChannelOpened() const override; diff --git a/main/protocols/protocol.cc b/main/protocols/protocol.cc index 1d915be8..88e75283 100644 --- a/main/protocols/protocol.cc +++ b/main/protocols/protocol.cc @@ -8,7 +8,7 @@ void Protocol::OnIncomingJson(std::function callback) { on_incoming_json_ = callback; } -void Protocol::OnIncomingAudio(std::function callback) { +void Protocol::OnIncomingAudio(std::function packet)> callback) { on_incoming_audio_ = callback; } @@ -65,56 +65,6 @@ void Protocol::SendStopListening() { SendText(message); } -void Protocol::SendIotDescriptors(const std::string& descriptors) { - cJSON* root = cJSON_Parse(descriptors.c_str()); - if (root == nullptr) { - ESP_LOGE(TAG, "Failed to parse IoT descriptors: %s", descriptors.c_str()); - return; - } - - if (!cJSON_IsArray(root)) { - ESP_LOGE(TAG, "IoT descriptors should be an array"); - cJSON_Delete(root); - return; - } - - int arraySize = cJSON_GetArraySize(root); - for (int i = 0; i < arraySize; ++i) { - cJSON* descriptor = cJSON_GetArrayItem(root, i); - if (descriptor == nullptr) { - ESP_LOGE(TAG, "Failed to get IoT descriptor at index %d", i); - continue; - } - - cJSON* messageRoot = cJSON_CreateObject(); - cJSON_AddStringToObject(messageRoot, "session_id", session_id_.c_str()); - cJSON_AddStringToObject(messageRoot, "type", "iot"); - cJSON_AddBoolToObject(messageRoot, "update", true); - - cJSON* descriptorArray = cJSON_CreateArray(); - cJSON_AddItemToArray(descriptorArray, cJSON_Duplicate(descriptor, 1)); - cJSON_AddItemToObject(messageRoot, "descriptors", descriptorArray); - - char* message = cJSON_PrintUnformatted(messageRoot); - if (message == nullptr) { - ESP_LOGE(TAG, "Failed to print JSON message for IoT descriptor at index %d", i); - cJSON_Delete(messageRoot); - continue; - } - - SendText(std::string(message)); - cJSON_free(message); - cJSON_Delete(messageRoot); - } - - cJSON_Delete(root); -} - -void Protocol::SendIotStates(const std::string& states) { - std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"iot\",\"update\":true,\"states\":" + states + "}"; - SendText(message); -} - void Protocol::SendMcpMessage(const std::string& payload) { std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"mcp\",\"payload\":" + payload + "}"; SendText(message); diff --git a/main/protocols/protocol.h b/main/protocols/protocol.h index 31f1ac43..6c4d6ab3 100644 --- a/main/protocols/protocol.h +++ b/main/protocols/protocol.h @@ -55,7 +55,7 @@ public: return session_id_; } - void OnIncomingAudio(std::function callback); + void OnIncomingAudio(std::function packet)> callback); void OnIncomingJson(std::function callback); void OnAudioChannelOpened(std::function callback); void OnAudioChannelClosed(std::function callback); @@ -65,18 +65,16 @@ public: virtual bool OpenAudioChannel() = 0; virtual void CloseAudioChannel() = 0; virtual bool IsAudioChannelOpened() const = 0; - virtual bool SendAudio(const AudioStreamPacket& packet) = 0; + virtual bool SendAudio(std::unique_ptr packet) = 0; virtual void SendWakeWordDetected(const std::string& wake_word); virtual void SendStartListening(ListeningMode mode); virtual void SendStopListening(); virtual void SendAbortSpeaking(AbortReason reason); - virtual void SendIotDescriptors(const std::string& descriptors); - virtual void SendIotStates(const std::string& states); virtual void SendMcpMessage(const std::string& message); protected: std::function on_incoming_json_; - std::function on_incoming_audio_; + std::function packet)> on_incoming_audio_; std::function on_audio_channel_opened_; std::function on_audio_channel_closed_; std::function on_network_error_; diff --git a/main/protocols/websocket_protocol.cc b/main/protocols/websocket_protocol.cc index 088b173a..a388667e 100644 --- a/main/protocols/websocket_protocol.cc +++ b/main/protocols/websocket_protocol.cc @@ -28,35 +28,35 @@ bool WebsocketProtocol::Start() { return true; } -bool WebsocketProtocol::SendAudio(const AudioStreamPacket& packet) { +bool WebsocketProtocol::SendAudio(std::unique_ptr packet) { if (websocket_ == nullptr || !websocket_->IsConnected()) { return false; } if (version_ == 2) { std::string serialized; - serialized.resize(sizeof(BinaryProtocol2) + packet.payload.size()); + serialized.resize(sizeof(BinaryProtocol2) + packet->payload.size()); auto bp2 = (BinaryProtocol2*)serialized.data(); bp2->version = htons(version_); bp2->type = 0; bp2->reserved = 0; - bp2->timestamp = htonl(packet.timestamp); - bp2->payload_size = htonl(packet.payload.size()); - memcpy(bp2->payload, packet.payload.data(), packet.payload.size()); + bp2->timestamp = htonl(packet->timestamp); + bp2->payload_size = htonl(packet->payload.size()); + memcpy(bp2->payload, packet->payload.data(), packet->payload.size()); return websocket_->Send(serialized.data(), serialized.size(), true); } else if (version_ == 3) { std::string serialized; - serialized.resize(sizeof(BinaryProtocol3) + packet.payload.size()); + serialized.resize(sizeof(BinaryProtocol3) + packet->payload.size()); auto bp3 = (BinaryProtocol3*)serialized.data(); bp3->type = 0; bp3->reserved = 0; - bp3->payload_size = htons(packet.payload.size()); - memcpy(bp3->payload, packet.payload.data(), packet.payload.size()); + bp3->payload_size = htons(packet->payload.size()); + memcpy(bp3->payload, packet->payload.data(), packet->payload.size()); return websocket_->Send(serialized.data(), serialized.size(), true); } else { - return websocket_->Send(packet.payload.data(), packet.payload.size(), true); + return websocket_->Send(packet->payload.data(), packet->payload.size(), true); } } @@ -124,30 +124,30 @@ bool WebsocketProtocol::OpenAudioChannel() { bp2->timestamp = ntohl(bp2->timestamp); bp2->payload_size = ntohl(bp2->payload_size); auto payload = (uint8_t*)bp2->payload; - on_incoming_audio_(AudioStreamPacket{ + on_incoming_audio_(std::make_unique(AudioStreamPacket{ .sample_rate = server_sample_rate_, .frame_duration = server_frame_duration_, .timestamp = bp2->timestamp, .payload = std::vector(payload, payload + bp2->payload_size) - }); + })); } else if (version_ == 3) { BinaryProtocol3* bp3 = (BinaryProtocol3*)data; bp3->type = bp3->type; bp3->payload_size = ntohs(bp3->payload_size); auto payload = (uint8_t*)bp3->payload; - on_incoming_audio_(AudioStreamPacket{ + on_incoming_audio_(std::make_unique(AudioStreamPacket{ .sample_rate = server_sample_rate_, .frame_duration = server_frame_duration_, .timestamp = 0, .payload = std::vector(payload, payload + bp3->payload_size) - }); + })); } else { - on_incoming_audio_(AudioStreamPacket{ + on_incoming_audio_(std::make_unique(AudioStreamPacket{ .sample_rate = server_sample_rate_, .frame_duration = server_frame_duration_, .timestamp = 0, .payload = std::vector((uint8_t*)data, (uint8_t*)data + len) - }); + })); } } } else { @@ -214,9 +214,7 @@ std::string WebsocketProtocol::GetHelloMessage() { #if CONFIG_USE_SERVER_AEC cJSON_AddBoolToObject(features, "aec", true); #endif -#if CONFIG_IOT_PROTOCOL_MCP cJSON_AddBoolToObject(features, "mcp", true); -#endif cJSON_AddItemToObject(root, "features", features); cJSON_AddStringToObject(root, "transport", "websocket"); cJSON* audio_params = cJSON_CreateObject(); diff --git a/main/protocols/websocket_protocol.h b/main/protocols/websocket_protocol.h index 3b79ef88..a62c58f4 100644 --- a/main/protocols/websocket_protocol.h +++ b/main/protocols/websocket_protocol.h @@ -16,7 +16,7 @@ public: ~WebsocketProtocol(); bool Start() override; - bool SendAudio(const AudioStreamPacket& packet) override; + bool SendAudio(std::unique_ptr packet) override; bool OpenAudioChannel() override; void CloseAudioChannel() override; bool IsAudioChannelOpened() const override; diff --git a/main/system_info.cc b/main/system_info.cc index 769362da..22ce809a 100644 --- a/main/system_info.cc +++ b/main/system_info.cc @@ -133,7 +133,7 @@ exit: //Common return path } void SystemInfo::PrintTaskList() { - char buffer[500]; + char buffer[1000]; vTaskList(buffer); ESP_LOGI(TAG, "Task list: \n%s", buffer); }