54ac8bb4231715c1a90745e5b5c7ebf0369e4a3d..24ff11ba18235d2c33ef73921ad01b9c5e7d4f21
2026-08-24 wangrong
Merge branch 'master' of http://124.222.109.197:10000/r/xiaozhiAI
24ff11 对比 | 目录
2026-08-24 wangrong
initial commit
beef65 对比 | 目录
1,627个文件已添加
126552 ■■■■■ 已修改文件
smartlearning/Interface.md 1362 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/README.md 736 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/001.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/001.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/002.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/002.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/003.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter01/resources/003.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/001.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/001.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/002.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/002.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/003.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter02/resources/003.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/001.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/001.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/002.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/002.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/003.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/chapter03/resources/003.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/course.json 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course01/courseTask.json 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/001.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/001.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/002.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/002.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/003.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter01/resources/003.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/001.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/001.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/002.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/002.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/003.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter02/resources/003.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/001.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/001.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/002.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/002.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/003.jpg 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/chapter03/resources/003.mp3 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/course.json 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/course02/courseTask.json 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/courses/index.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/data/tasklist.json 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/interface.md 1363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/mqtt_interface.md 429 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/mqtt消息规则 v2 - 副本.pdf 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/mqtt消息规则.pdf 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/mqtt消息规则V1.1.pdf 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/mqtt消息规则_V1.0.pdf 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/mqtt消息规则_V1.1.xlsx 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/requires.txt 368 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/docs/smartlearing_prd_v1.01.txt 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/.env 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__init__.py 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__pycache__/__init__.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__pycache__/config.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__pycache__/config.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__pycache__/main.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/__pycache__/main.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/config.py 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/main.py 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/models/__init__.py 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/models/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/models/__pycache__/__init__.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/models/__pycache__/schemas.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/models/__pycache__/schemas.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/models/schemas.py 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__init__.py 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__pycache__/__init__.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__pycache__/client.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__pycache__/client.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/client.py 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/mqtt/init_dispatcher.py 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__init__.py 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/__init__.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/courses.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/courses.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/devices.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/devices.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/dialogue.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/dialogue.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/scene.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/__pycache__/scene.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/courses.py 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/devices.py 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/dialogue.py 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/routes/scene.py 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__init__.py 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/__init__.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/__init__.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/auto_init.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/auto_init.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/course_service.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/course_service.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/device_store.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/device_store.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/dialogue_service.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/dialogue_service.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/init_registry.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/init_registry.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/scene_engine.cpython-310.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/__pycache__/scene_engine.cpython-313.pyc 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/auto_init.py 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/course_service.py 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/device_store.py 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/dialogue_service.py 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/init_registry.py 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/app/services/scene_engine.py 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/requirements.txt 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/run.py 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/runtime/.gitignore 1 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/server/runtime/.gitkeep 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/.clang-format 126 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/.gitignore 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/CMakeLists.txt 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/LICENSE 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/README.md 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/README_ja.md 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/README_zh.md 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/blufi.md 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/blufi_zh.md 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/code_style.md 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/code_style_zh.md 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/custom-board.md 473 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/custom-board_zh.md 453 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mcp-based-graph.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mcp-protocol.md 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mcp-protocol_zh.md 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mcp-usage.md 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mcp-usage_zh.md 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mqtt-udp.md 409 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/mqtt-udp_zh.md 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/AtomMatrix-echo-base.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/ESP32-BreadBoard.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/atoms3r-echo-base.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/esp32s3-box3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/lichuang-s3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/m5stack-cores3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/magiclick-2p4.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/waveshare-esp32-s3-touch-amoled-1.8.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v0/wiring.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/atoms3r.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/electron-bot.png 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/esp-hi.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/esp-sparkbot.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/espbox3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/lichuang-s3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/lilygo-t-circle-s3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/m5cores3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/magiclick.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/movecall-cuican-esp32s3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/movecall-moji-esp32s3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/otto-robot.png 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/sensecap_watcher.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/waveshare.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/wiring2.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_0.96.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_1.54.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/v1/xmini-c3.jpg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/websocket.md 530 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/docs/websocket_zh.md 495 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/CMakeLists.txt 1200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/Kconfig.projbuild 962 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/application.cc 1116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/application.h 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets.cc 562 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets.h 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/common/exclamation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/common/low_battery.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/common/popup.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/common/success.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/common/vibration.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sr-RS/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/0.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/1.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/2.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/3.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/4.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/5.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/6.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/7.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/8.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/9.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/activation.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_pin.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_reg.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/language.json 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/upgrade.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/welcome.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/wificonfig.ogg 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/README.md 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/audio_codec.cc 67 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/audio_codec.h 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/audio_processor.h 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/audio_service.cc 734 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/audio_service.h 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.cc 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.cc 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.h 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.cc 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.cc 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.cc 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.cc 206 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.cc 385 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.cc 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.h 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.cc 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.h 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.cc 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.h 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.cc 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.h 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_word.h 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.cc 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.cc 303 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.h 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.cc 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README.md 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README_en.md 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/aipi-lite.cc 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config_en.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/power_manager.h 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc 299 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.h 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc 388 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.h 84 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/power_manager.h 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc 477 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.h 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/power_manager.h 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc 456 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.h 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc 471 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.h 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/power_manager.h 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.h 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc 220 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/config.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/config.h 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/README.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/atom_echos3r.cc 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.h 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/README.md 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.h 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/README.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/atoms3_echo_base.cc 232 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.h 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/README.md 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc 190 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.h 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/README.md 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc 312 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.h 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.h 278 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc 200 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/README.md 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/esp32_bread_board.cc 162 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/compact_ml307_board.cc 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.h 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/compact_nt26_board.cc 181 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.h 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/config.h 289 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/README.md 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc 214 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/config.h 308 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/compact_wifi_board.cc 188 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.h 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.cc 116 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.h 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.cc 375 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.h 177 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/axp2101.cc 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/axp2101.h 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/backlight.cc 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/backlight.h 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/blufi.cpp 899 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/blufi.h 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/board.cc 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/board.h 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/button.cc 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/button.h 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/camera.h 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.cc 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.h 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.cc 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/esp_video.cc 1041 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/esp_video.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.cc 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.h 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/knob.cc 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/knob.h 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/lamp_controller.h 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.cc 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.h 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.cc 269 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.cc 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.h 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.cc 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.h 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.cc 247 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.h 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.cc 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.h 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/sy6970.cc 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/sy6970.h 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/system_reset.cc 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/system_reset.h 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.cc 357 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.h 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/README.md 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/config.h 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/config.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/df_k10_board.cc 299 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.cc 227 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.cc 124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.h 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/README.md 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.h 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.json 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc 112 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/README.md 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.h 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/doit_s3_aibox.cc 137 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/du-chatx/du-chatx-wifi.cc 164 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/du-chatx/power_manager.h 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/README.md 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.h 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot.cc 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot_controller.cc 376 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.cc 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.h 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.cc 470 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.h 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.cc 149 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.h 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/otto_icon_font.c 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/electron-bot/power_manager.h 128 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/README.md 132 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/esp_box3_board.cc 175 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.cc 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.h 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/esp_box_lite_board.cc 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box/config.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-box/esp_box_board.cc 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/README.md 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.cc 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.json 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.cc 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.h 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-hi/esp_hi.cc 422 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/README.md 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.h 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/README.md 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h 146 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/pin_config.h 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/README.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc 240 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c 504 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/pin_config.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/README.md 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.json 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/esp-sensairshuttle.cc 315 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.h 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/esp_sparkbot_board.cc 302 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-spot/README.md 63 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.h 79 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-spot/esp_spot_board.cc 436 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/README.md 82 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.h 88 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/esp_vocat.cc 1017 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/touch.h 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/README.md 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.h 73 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager.h 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager_lite.h 185 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/README.md 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.h 271 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/esp32_cgc_board.cc 180 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.h 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/esp32s3_korvo2_v3_board.cc 449 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/power_manager.h 249 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.h 81 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc 454 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/power_manager.h 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/ReadMe.md 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc 229 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.h 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc 259 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/power_manager.h 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/hu-087/README.md 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/hu-087/config.h 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/hu-087/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/hu-087/hu_087_board.cc 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/README.md 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.h 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c 384 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/gpio_manager.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc 395 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_controller.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_manager.h 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/kevin_box_board.cc 270 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.h 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/kevin_c3_board.cc 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.cc 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.h 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/config.h 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc 157 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc 173 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.h 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c 478 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h 145 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/pin_config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/README.md 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc 163 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/README.md 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/mpython_pro.cc 143 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/README.md 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_controller.cc 399 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.cc 577 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.h 92 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_robot_pro.cc 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.cc 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.h 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/README.md 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.h 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear.cc 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_controller.cc 493 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.cc 763 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.h 105 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.cc 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.h 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/README.md 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.h 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/eda-tv-pro.cc 122 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/README.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc 129 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/lichuang_dev_board.cc 296 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/README.md 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.h 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.json 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc 350 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/pin_config.h 154 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc 169 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/README.md 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.h 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c 353 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h 99 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc 258 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/pin_config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/README.md 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/config.h 76 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.cc 404 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc 397 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.cc 377 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.h 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/t_display_p4_config.h 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc 283 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/README.md 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.h 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc 358 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.cc 418 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.h 155 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.cc 705 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.h 133 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/README.md 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.cc 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/m5stack_core_s3.cc 396 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/README.md 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.h 325 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.json 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.c 381 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.h 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/m5stack_tab5.cc 574 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/sdkconfig.tab5 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.cc 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.h 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/magiclick_2p4_board.cc 272 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.h 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/magiclick_2p5_board.cc 424 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/power_manager.h 195 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc 233 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/magiclick_c3_board.cc 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/README.md 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/minsi_k08_dual.cc 246 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/power_manager.h 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/README.md 72 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/mixgo-nova.cc 168 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/README.md 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/README.md 26 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README.md 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README_zh.md 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.h 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/README.md 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_audio_codec.h 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_board.cc 205 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.h 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/power_manager.h 150 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/README.md 207 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.h 176 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.cc 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.h 91 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_controller.cc 849 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.cc 134 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.h 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_icon_font.c 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.cc 964 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.h 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_robot.cc 417 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/power_manager.h 151 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.cc 192 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.h 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README.md 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README_en.md 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.h 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config_en.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.cc 216 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.h 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_watcher.cc 668 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.cc 743 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.h 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/README.md 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.h 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/power_manager.h 189 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc 491 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/README.md 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/power_manager.h 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc 325 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/README.md 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/power_manager.h 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc 210 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/README.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.json 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/taiji_pi_s3.cc 663 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/tudouzi/kevin_box_board.cc 255 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/README.md 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.h 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/esp32-c6-lcd-1.69.cc 262 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/power_manager.h 174 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/README.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/README.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.h 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc 292 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/README.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/esp32-c6-touch-amoled-1.8.cc 352 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/README.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.h 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc 322 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/README.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.h 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/esp32-c6-touch-amoled-2.16.cc 352 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/README.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.h 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/esp32-c6-touch-lcd-1.83.cc 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/README.md 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc 284 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/lcd_init_cmds.h 224 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/README.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/esp32-p4-wifi6-touch-lcd-3.5.cc 191 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/README.md 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.h 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.json 118 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/esp32-p4-wifi6-touch-lcd.cc 458 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/lcd_init_cmds.h 1124 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.h 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp32-s3-audio_board.cc 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.c 460 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.h 102 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/README.md 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.h 115 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.json 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/esp32-s3-cam-xxxx.cc 282 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/README.md 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.cc 59 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.h 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.h 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.json 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.cc 403 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.h 75 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc 172 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/README.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.h 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.cc 393 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.h 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/waveshare-s3-epaper-3.97.cc 178 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/README.md 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.h 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/esp32-s3-lcd-0.85.cc 311 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.cc 130 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.h 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/power_manager.h 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/README.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.h 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.cc 338 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.h 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/waveshare-s3-rlcd-4.2.cc 170 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/README.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc 223 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/README.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.h 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/esp32-s3-touch-amoled-1.43c.cc 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/README.md 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.h 57 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.json 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc 370 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.json 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc 352 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/README.md 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.h 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc 363 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/README.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.h 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/README.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.h 56 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/esp32-s3-touch-lcd-1.54.cc 355 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/power_manager.h 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/README.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.h 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.h 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc 447 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.h 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc 407 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.cc 147 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.h 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.h 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc 382 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.h 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.json 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.cc 286 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.h 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc 365 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/README.md 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.h 89 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/esp32_s3_touch_lcd_4.3c.cc 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c 3306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/README.md 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.h 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.json 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc 440 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/README.md 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.h 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/esp32-touch-lcd-3.5.cc 341 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/README.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.h 104 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.json 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc 306 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/README.md 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.h 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/customlcddisplay.h 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/power_manager.h 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/xingzhi-abs-2.0.cc 389 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc 239 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.h 37 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc 244 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.h 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc 234 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.h 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc 225 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.h 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc 212 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.h 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h 186 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/README.md 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.json 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc 213 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.h 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/power_manager.h 314 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/README.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.h 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.json 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc 203 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/README.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.h 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/xmini_c3_board.cc 201 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.h 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.json 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/xmini-c3/xmini_c3_board.cc 182 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/README.md 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.h 61 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.json 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.cc 278 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.h 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/yunliao_s3.cc 295 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/README.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc 211 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/README.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.h 39 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.json 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/power_manager.h 238 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc 228 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h 66 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/README.md 50 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.h 70 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/mcp_controller.cc 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/power_manager.h 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/zhengchen_cam_board_ml307.cc 340 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/README.md 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.h 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.json 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/mcp_controller.cc 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/power_manager.h 237 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/zhengchen_cam_board.cc 333 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/device_state.h 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/device_state_machine.cc 161 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/device_state_machine.h 83 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/display.cc 60 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/display.h 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/emote_display.cc 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/emote_display.h 42 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lcd_display.cc 1310 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lcd_display.h 85 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.cc 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.h 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/LICENSE.txt 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/README.md 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.c 821 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.h 68 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec_mve.h 140 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.cc 252 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.h 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.cpp 467 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.h 86 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.c 264 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.h 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.cc 274 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.cc 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.h 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.cc 64 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.h 53 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.cc 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.h 94 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/oled_display.cc 408 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/display/oled_display.h 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/idf_component.yml 136 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/circular_strip.cc 245 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/circular_strip.h 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/gpio_led.cc 263 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/gpio_led.h 49 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/led.h 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/single_led.cc 167 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/led/single_led.h 38 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/main.cc 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/mcp_server.cc 563 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/mcp_server.h 344 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/ota.cc 492 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/ota.h 58 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.cc 389 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.h 65 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/protocols/protocol.cc 90 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/protocols/protocol.h 98 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.cc 254 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.h 34 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/settings.cc 108 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/settings.h 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/system_info.cc 156 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/main/system_info.h 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/16m.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/16m_custom_wakeword.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/16m_echoear.csv 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/32m.csv 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/4m.csv 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/4m_esp-hi.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v1/8m.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v2/16m.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v2/16m_c3.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v2/32m.csv 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v2/4m.csv 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v2/8m.csv 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/partitions/v2/README.md 107 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/Image_Converter/LVGLImage.py 1426 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/Image_Converter/README.md 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/Image_Converter/lvgl_tools_gui.py 253 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/Image_Converter/requirements.txt 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/acoustic_check/demod.py 280 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/acoustic_check/graphic.py 444 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/acoustic_check/main.py 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/acoustic_check/readme.md 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/acoustic_check/requirements.txt 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/audio_debug_server.py 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/build_default_assets.py 934 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/download_github_runs.py 294 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/gen_lang.py 187 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/mp3_to_ogg.sh 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/ogg_converter/README.md 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/ogg_converter/xiaozhi_ogg_converter.py 230 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/README.md 95 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/batch_convert_gui.py 221 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_audio_to_p3.py 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_p3_to_audio.py 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/img/img.png 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/p3_gui_player.py 241 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/play_p3.py 71 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/p3_tools/requirements.txt 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/release.py 456 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/sonic_wifi_config.html 208 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/README.md 110 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build.py 400 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build_all.py 148 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/pack_model.py 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/spiffs_assets_gen.py 647 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/scripts/versions.py 250 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c3 15 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c5 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c6 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32p4 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32s3 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
smartlearning/Interface.md
New file
@@ -0,0 +1,1362 @@
# SmartLearning æŽ¥å£æ–‡æ¡£
> æœåŠ¡é»˜è®¤åœ°å€ï¼š`http://{host}:{port}`(默认 `http://0.0.0.0:9500`)
> äº¤äº’式 API æ–‡æ¡£ï¼šå¯åŠ¨æœåŠ¡åŽè®¿é—® `/docs`
> åè®®ç‰ˆæœ¬ï¼šHTTP API v1 / MQTT snlp/v1
---
## ç›®å½•
- [一、通用说明](#一通用说明)
- [二、HTTP æŽ¥å£](#二http-接口)
  - [2.1 ç³»ç»Ÿ](#21-系统)
  - [2.2 é™æ€èµ„源](#22-静态资源)
  - [2.3 åœºæ™¯è¯†åˆ«](#23-场景识别)
  - [2.4 å¯¹è¯æµ](#24-对话流)
  - [2.5 è¯¾ç¨‹ä¸Žä»»åŠ¡](#25-课程与任务)
  - [2.6 è®¾å¤‡åˆå§‹åŒ–](#26-设备初始化)
  - [2.7 è®¾å¤‡ç®¡ç†](#27-设备管理)
- [三、MQTT æŽ¥å£](#三mqtt-接口)
  - [3.1 Topic è§„则](#31-topic-规则)
  - [3.2 æ¶ˆæ¯é€šç”¨æ ¼å¼](#32-消息通用格式)
  - [3.3 ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt)](#33-终端上报rpt)
  - [3.4 äº‘端下发(cmd)](#34-云端下发cmd)
  - [3.5 æœåŠ¡ç«¯è‡ªåŠ¨ä¸‹å‘æµç¨‹](#35-服务端自动下发流程)
- [四、接口速查表](#四接口速查表)
---
## ä¸€ã€é€šç”¨è¯´æ˜Ž
### 1.1 Base URL
| çŽ¯å¢ƒ | åœ°å€ |
|------|------|
| æœ¬åœ°å¼€å‘ | `http://localhost:9500` |
| ç”Ÿäº§éƒ¨ç½² | ç”± `.env` ä¸­ `BASE_URL` é…ç½®ï¼Œç»ˆç«¯ä¸‹è½½èµ„源须使用该地址 |
### 1.2 è¯·æ±‚头
JSON æŽ¥å£ç»Ÿä¸€ä½¿ç”¨ï¼š
```
Content-Type: application/json
```
SSE å¯¹è¯æµæŽ¥å£è¿”回:
```
Content-Type: text/event-stream
```
### 1.3 é”™è¯¯å“åº”
FastAPI æ ‡å‡†é”™è¯¯æ ¼å¼ï¼š
```json
{
  "detail": "Course not found: course99"
}
```
常见 HTTP çŠ¶æ€ç ï¼š
| çŠ¶æ€ç  | è¯´æ˜Ž |
|--------|------|
| 200 | æˆåŠŸ |
| 400 | è¯·æ±‚参数错误 |
| 404 | èµ„源不存在 |
| 422 | è¯·æ±‚体校验失败 |
---
## äºŒã€HTTP æŽ¥å£
### 2.1 ç³»ç»Ÿ
#### GET /health
健康检查,查看服务与 MQTT è¿žæŽ¥çŠ¶æ€ã€‚
**请求示例:**
```bash
curl http://localhost:9500/health
```
**响应示例:**
```json
{
  "status": "ok",
  "mqtt_connected": true
}
```
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| status | string | æœåŠ¡çŠ¶æ€ï¼Œå›ºå®š `"ok"` |
| mqtt_connected | boolean | MQTT Broker æ˜¯å¦å·²è¿žæŽ¥ |
---
### 2.2 é™æ€èµ„源
#### GET /static/{path}
托管 `data/` ç›®å½•下的课程文件,供终端 HTTP ä¸‹è½½ã€‚
**URL è§„则:**
```
{BASE_URL}/static/{相对路径}
```
**示例:**
| èµ„源 | URL |
|------|-----|
| è¯¾ç¨‹ç´¢å¼• | `http://localhost:9500/static/courses/index.json` |
| è¯¾ç¨‹é…ç½® | `http://localhost:9500/static/courses/course01/course.json` |
| è¯¾ç¨‹ä»»åŠ¡åŒ… | `http://localhost:9500/static/courses/course01/courseTask.json` |
| éŸ³é¢‘ | `http://localhost:9500/static/courses/course01/chapter01/resources/001.mp3` |
| å°é¢ | `http://localhost:9500/static/courses/course01/chapter01/resources/001.png` |
**请求示例:**
```bash
curl -O http://localhost:9500/static/courses/course01/courseTask.json
```
**响应:** å¯¹åº”文件的二进制/JSON å†…容。
---
### 2.3 åœºæ™¯è¯†åˆ«
#### POST /api/v1/scene/analyze
根据设备上下文进行场景识别,返回问候策略与建议意图。
**请求体:**
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| timestamp | int | æ˜¯ | Unix æ—¶é—´æˆ³ |
| audio_blob | string | æ˜¯ | Base64 éŸ³é¢‘或占位符 |
| device_id | string | æ˜¯ | è®¾å¤‡ ID |
| context_hints | object | å¦ | åœºæ™¯æç¤ºï¼Œå¦‚ `{"scene":"dining","confidence":0.9}` |
**请求示例:**
```bash
curl -X POST http://localhost:9500/api/v1/scene/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "timestamp": 1718365200,
    "audio_blob": "",
    "device_id": "SN-88293021",
    "context_hints": {
      "scene": "dining",
      "confidence": 0.9
    }
  }'
```
**响应示例:**
```json
{
  "scene": "用餐",
  "confidence": 0.9,
  "greeting_strategy": "contextual",
  "suggested_intent": "What are you having for dinner?"
}
```
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| scene | string | åœºæ™¯åç§° |
| confidence | float | ç½®ä¿¡åº¦ 0~1 |
| greeting_strategy | string | `"contextual"` åœºæ™¯é—®å€™ / `"generic"` é€šç”¨é—®å€™ï¼ˆç½®ä¿¡åº¦ < 0.7) |
| suggested_intent | string | å»ºè®®çš„开场白或提问 |
**低置信度响应示例:**
```json
{
  "scene": "通勤",
  "confidence": 0.62,
  "greeting_strategy": "generic",
  "suggested_intent": "Hello! How can I help you practice English today?"
}
```
---
### 2.4 å¯¹è¯æµ
#### GET /api/v1/dialogue/stream
SSE(Server-Sent Events)流式返回教学对话内容。
**Query å‚数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | é»˜è®¤å€¼ | è¯´æ˜Ž |
|------|------|------|--------|------|
| dialogue_id | string | å¦ | è‡ªåŠ¨ç”Ÿæˆ UUID | å¯¹è¯ ID |
| user_text | string | å¦ | â€” | ç”¨æˆ·å›žå¤æ–‡æœ¬ï¼ˆASR ç»“果) |
| scene_prompt | string | å¦ | â€” | åœºæ™¯å¼€åœºç™½ï¼ˆæ¥è‡ª scene/analyze) |
| difficulty | string | å¦ | A2 | CEFR éš¾åº¦ç­‰çº§ |
**请求示例:**
```bash
# ä»…开场问候
curl -N "http://localhost:9500/api/v1/dialogue/stream?scene_prompt=What%20are%20you%20having%20for%20dinner?&difficulty=A2"
# å«ç”¨æˆ·å›žå¤çš„反馈
curl -N "http://localhost:9500/api/v1/dialogue/stream?user_text=Good%20morning.&difficulty=A2"
```
**响应格式(SSE):**
```
event: message
data: {"dialogue_id":"a1b2c3d4-...","text":"What are you having for dinner?","audio_url":null,"intent_type":"greeting","difficulty_level":"A2"}
event: message
data: {"dialogue_id":"a1b2c3d4-...","text":"Great job! Your sentence looks correct.","audio_url":null,"intent_type":"praise","difficulty_level":"A2"}
event: message
data: {"dialogue_id":"a1b2c3d4-...","text":"What would you do differently next time?","audio_url":null,"intent_type":"question","difficulty_level":"A2"}
event: done
data: {"dialogue_id":"a1b2c3d4-..."}
```
**message äº‹ä»¶ data å­—段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| dialogue_id | string | å¯¹è¯ ID |
| text | string | å¯¹è¯æ–‡æœ¬ |
| audio_url | string \| null | éŸ³é¢‘ URL(当前为 null) |
| intent_type | string | `greeting` / `praise` / `correction` / `question` |
| difficulty_level | string | éš¾åº¦ç­‰çº§ |
---
### 2.5 è¯¾ç¨‹ä¸Žä»»åŠ¡
#### GET /api/v1/courses
获取已发布课程索引(对应 `data/courses/index.json`)。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/courses
```
**响应示例:**
```json
{
  "courses": [
    {
      "course_id": "course01",
      "config": "courses/course01/course.json"
    },
    {
      "course_id": "course02",
      "config": "courses/course02/course.json"
    }
  ]
}
```
---
#### GET /api/v1/courses/{course_id}
获取单门课程详情(章节与视频列表)。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/courses/course01
```
**响应示例:**
```json
{
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "课程01",
  "chapters": [
    {
      "chapter_id": "chapter01",
      "chapter_name": "第一章",
      "videos": [
        {
          "id": "001",
          "title": "课程001",
          "path": "courses/course01/chapter01/resources/001.mp3"
        }
      ]
    }
  ]
}
```
---
#### GET /api/v1/courses/base-manifest
预览全部基础课程同步包(MQTT ä¸‹å‘前的完整 payload,`url` å·²è¡¥å…¨ï¼‰ã€‚
**请求示例:**
```bash
curl http://localhost:9500/api/v1/courses/base-manifest
```
**响应示例:**
```json
{
  "scope": "base",
  "course_count": 2,
  "courses": [
    {
      "version": "1.0",
      "course_id": "course01",
      "course_name": "小智学习课程01",
      "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
      "assets": [
        {
          "url": "http://localhost:9500/static/courses/course01/chapter01/resources/001.mp3",
          "md5": "...",
          "title": "第一章 è¯¾ç¨‹001",
          "target_path": "courses/course01/chapter01/resources/001.mp3",
          "cover_url": "http://localhost:9500/static/courses/course01/chapter01/resources/001.png",
          "cover_target_path": "courses/course01/chapter01/resources/001.png"
        }
      ]
    }
  ]
}
```
---
#### GET /api/v1/courses/{course_id}/manifest
获取单门课程的同步包(`url` / `cover_url` å·²è¡¥å…¨ä¸ºå®Œæ•´ HTTP åœ°å€ï¼‰ã€‚
**请求示例:**
```bash
curl http://localhost:9500/api/v1/courses/course01/manifest
```
**响应示例:**
```json
{
  "version": "1.0",
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
  "assets": [
    {
      "url": "http://localhost:9500/static/courses/course01/chapter01/resources/001.mp3",
      "md5": "...",
      "title": "第一章 è¯¾ç¨‹001",
      "target_path": "courses/course01/chapter01/resources/001.mp3",
      "cover_url": "http://localhost:9500/static/courses/course01/chapter01/resources/001.png",
      "cover_target_path": "courses/course01/chapter01/resources/001.png"
    }
  ]
}
```
---
#### GET /api/v1/courses/{course_id}/tasks
获取原始 `courseTask.json`(本地文件内容,`url` ä¸ºç›¸å¯¹è·¯å¾„)。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/courses/course01/tasks
```
**响应示例:**
```json
{
  "version": "1.0",
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
  "assets": [
    {
      "url": "courses/course01/chapter01/resources/001.mp3",
      "md5": "...",
      "title": "第一章 è¯¾ç¨‹001",
      "target_path": "courses/course01/chapter01/resources/001.mp3",
      "cover_url": "courses/course01/chapter01/resources/001.png",
      "cover_target_path": "courses/course01/chapter01/resources/001.png"
    }
  ]
}
```
---
#### GET /api/v1/tasklist
获取设备开机初始化任务清单(`data/tasklist.json`)。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/tasklist
```
**响应示例:**
```json
{
  "version": "1.0",
  "description": "终端设备初始化任务清单,开机后由服务端下发执行",
  "tasks": [
    {
      "task_id": "init_001",
      "name": "同步系统时钟",
      "action": "SET_CLOCK",
      "channel": "ctl",
      "ttl": 60,
      "payload": { "ts": 0 }
    },
    {
      "task_id": "init_002",
      "name": "设置默认音量",
      "action": "SET_VOLUME",
      "channel": "ctl",
      "ttl": 60,
      "payload": { "level": 50 }
    },
    {
      "task_id": "init_003",
      "name": "唤醒设备",
      "action": "SET_POWER",
      "channel": "ctl",
      "ttl": 60,
      "payload": { "state": "wake" }
    }
  ]
}
```
---
### 2.6 è®¾å¤‡åˆå§‹åŒ–
#### POST /api/v1/devices/init
手动触发设备完整初始化(基础 ctl æŒ‡ä»¤ + å…¨éƒ¨åŸºç¡€è¯¾ç¨‹ SYNC_COURSE_TASK)。
**请求体:**
| å­—段 | ç±»åž‹ | å¿…å¡« | é»˜è®¤å€¼ | è¯´æ˜Ž |
|------|------|------|--------|------|
| device_id | string | æ˜¯ | â€” | è®¾å¤‡ ID |
| force | boolean | å¦ | false | å¼ºåˆ¶é‡æ–°åˆå§‹åŒ–(忽略已初始化记录) |
| course_id | string | å¦ | â€” | æœ¬æŽ¥å£ä¸ä½¿ç”¨ |
**请求示例:**
```bash
curl -X POST http://localhost:9500/api/v1/devices/init \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "SN-88293021",
    "force": true
  }'
```
**响应示例(成功下发):**
```json
{
  "device_id": "SN-88293021",
  "commands": [
    {
      "header": {
        "version": "1.0",
        "device_id": "SN-88293021",
        "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
        "timestamp": 1718365200,
        "ttl": 60
      },
      "action": "SET_CLOCK",
      "payload": { "ts": 1718365200 }
    },
    {
      "header": {
        "version": "1.0",
        "device_id": "SN-88293021",
        "req_id": "e1c1de2a-3067-417d-b533-67c9a9827a8e",
        "timestamp": 1718365201,
        "ttl": 7200
      },
      "action": "SYNC_COURSE_TASK",
      "payload": {
        "version": "1.0",
        "course_id": "course01",
        "course_name": "小智学习课程01",
        "assets": []
      }
    }
  ],
  "status": "dispatched"
}
```
**响应示例(已初始化且未 force):**
```json
{
  "device_id": "SN-88293021",
  "commands": [],
  "status": "already_initialized"
}
```
| status å€¼ | è¯´æ˜Ž |
|-----------|------|
| dispatched | æŒ‡ä»¤å·²ä¸‹å‘ |
| already_initialized | è®¾å¤‡å·²åœ¨åˆå§‹åŒ–记录中,跳过 |
---
#### POST /api/v1/devices/course-init
手动触发单门课程资源同步(下发 1 æ¡ `SYNC_COURSE_TASK`)。
**请求体:**
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| device_id | string | æ˜¯ | è®¾å¤‡ ID |
| course_id | string | æ˜¯ | è¯¾ç¨‹ ID,如 `course01` |
**请求示例:**
```bash
curl -X POST http://localhost:9500/api/v1/devices/course-init \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "SN-88293021",
    "course_id": "course01"
  }'
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "course_id": "course01",
  "commands": [
    {
      "header": {
        "version": "1.0",
        "device_id": "SN-88293021",
        "req_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "timestamp": 1718365300,
        "ttl": 7200
      },
      "action": "SYNC_COURSE_TASK",
      "payload": {
        "version": "1.0",
        "course_id": "course01",
        "course_name": "小智学习课程01",
        "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
        "assets": [
          {
            "url": "http://localhost:9500/static/courses/course01/chapter01/resources/001.mp3",
            "md5": "...",
            "title": "第一章 è¯¾ç¨‹001",
            "target_path": "courses/course01/chapter01/resources/001.mp3",
            "cover_url": "http://localhost:9500/static/courses/course01/chapter01/resources/001.png",
            "cover_target_path": "courses/course01/chapter01/resources/001.png"
          }
        ]
      }
    }
  ],
  "status": "dispatched"
}
```
---
### 2.7 è®¾å¤‡ç®¡ç†
#### GET /api/v1/devices
获取当前在线设备列表(来自 MQTT `rpt/state` ä¸ŠæŠ¥ï¼‰ã€‚
**请求示例:**
```bash
curl http://localhost:9500/api/v1/devices
```
**响应示例:**
```json
{
  "devices": [
    {
      "device_id": "SN-88293021",
      "cpu": 15.0,
      "mem": 45.0,
      "battery": 80.0,
      "net": "wifi",
      "mode": "idle",
      "last_seen": 1718365205
    }
  ]
}
```
---
#### GET /api/v1/devices/initialized
获取已完成初始化的设备记录。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/devices/initialized
```
**响应示例:**
```json
{
  "devices": {
    "SN-88293021": {
      "initialized_at": 1718365200,
      "source": "auto",
      "mode": "idle",
      "courses_synced": true,
      "courses_sync_at": 1718365201,
      "courses_sync_count": 2
    }
  }
}
```
---
#### GET /api/v1/devices/{device_id}
获取单个设备最新状态。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/devices/SN-88293021
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "cpu": 15.0,
  "mem": 45.0,
  "battery": 80.0,
  "net": "wifi",
  "mode": "idle",
  "last_seen": 1718365205
}
```
**404 å“åº”:**
```json
{
  "detail": "Device not found"
}
```
---
#### GET /api/v1/devices/{device_id}/init-status
查询设备初始化状态。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/devices/SN-88293021/init-status
```
**响应示例(已初始化):**
```json
{
  "device_id": "SN-88293021",
  "initialized": true,
  "record": {
    "initialized_at": 1718365200,
    "source": "auto",
    "mode": "idle",
    "courses_synced": true,
    "courses_sync_at": 1718365201,
    "courses_sync_count": 2
  }
}
```
**响应示例(未初始化):**
```json
{
  "device_id": "SN-88293021",
  "initialized": false,
  "record": null
}
```
---
#### DELETE /api/v1/devices/initialized/{device_id}
清除设备初始化记录,下次上报 `rpt/state` æ—¶å°†é‡æ–°è§¦å‘自动初始化。
**请求示例:**
```bash
curl -X DELETE http://localhost:9500/api/v1/devices/initialized/SN-88293021
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "status": "removed"
}
```
---
#### GET /api/v1/devices/{device_id}/acks
获取设备指令回执历史。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/devices/SN-88293021/acks
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "acks": [
    {
      "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
      "status": "success",
      "error_code": 0
    }
  ]
}
```
---
#### GET /api/v1/devices/{device_id}/events
获取设备事件与进度上报历史。
**请求示例:**
```bash
curl http://localhost:9500/api/v1/devices/SN-88293021/events
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "events": [
    {
      "event": "asr_result",
      "text": "Good morning",
      "confidence": 0.95,
      "_type": "event"
    },
    {
      "task_id": "uuid",
      "status": "downloading",
      "progress": 45,
      "info": "Downloading assets...",
      "_type": "progress"
    }
  ]
}
```
---
#### POST /api/v1/devices/{device_id}/cmd
手动向设备下发实时控制指令(MQTT `cmd/ctl`)。
**请求体:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": { "level": 80 }
}
```
**请求示例:**
```bash
curl -X POST http://localhost:9500/api/v1/devices/SN-88293021/cmd \
  -H "Content-Type: application/json" \
  -d '{
    "header": {
      "device_id": "SN-88293021",
      "timestamp": 1718365200,
      "ttl": 60
    },
    "action": "SET_VOLUME",
    "payload": { "level": 80 }
  }'
```
**响应示例(返回实际下发的完整指令):**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": { "level": 80 }
}
```
---
#### POST /api/v1/devices/{device_id}/task
手动向设备下发异步任务指令(MQTT `cmd/task`)。
**请求示例:**
```bash
curl -X POST http://localhost:9500/api/v1/devices/SN-88293021/task \
  -H "Content-Type: application/json" \
  -d '{
    "header": {
      "device_id": "SN-88293021",
      "timestamp": 1718365200,
      "ttl": 7200
    },
    "action": "SYNC_COURSE_TASK",
    "payload": {
      "version": "1.0",
      "course_id": "course01",
      "course_name": "小智学习课程01",
      "assets": []
    }
  }'
```
**响应示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": 1718365200,
    "ttl": 7200
  },
  "action": "SYNC_COURSE_TASK",
  "payload": {
    "version": "1.0",
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "assets": []
  }
}
```
---
## ä¸‰ã€MQTT æŽ¥å£
### 3.1 Topic è§„则
```
snlp/v1/{device_id}/{direct}/{msg_type}
```
| æ–¹å‘ | Topic | QoS | è¯´æ˜Ž |
|------|-------|-----|------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/state` | 0 | å¿ƒè·³/状态上报 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/event` | 0 | äº‹ä»¶ä¸ŠæŠ¥ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/ack` | 1 | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/progress` | 0 | å¼‚步任务进度 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/log` | 0 | ç³»ç»Ÿæ—¥å¿— |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/ctl` | 1 | å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/task` | 1 | å¼‚步任务指令 |
**参数说明:**
| å‚æ•° | è¯´æ˜Ž |
|------|------|
| v1 | åè®®ç‰ˆæœ¬å· |
| device_id | è®¾å¤‡å”¯ä¸€ ID,不可修改 |
| direct | `rpt`(终端上报)/ `cmd`(云端下发) |
| msg_type | `state` / `event` / `ack` / `progress` / `log` / `ctl` / `task` |
---
### 3.2 æ¶ˆæ¯é€šç”¨æ ¼å¼
#### äº‘端下发(cmd/ctl、cmd/task)
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": 1718365200,
    "ttl": 3600
  },
  "action": "ACTION_TYPE",
  "payload": {}
}
```
| header å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|-------------|------|------|
| version | string | åè®®è¯­ä¹‰ç‰ˆæœ¬ï¼Œå›ºå®š `"1.0"` |
| device_id | string | ç›®æ ‡è®¾å¤‡ ID |
| req_id | string | æŒ‡ä»¤å”¯ä¸€ ID(UUID),终端 ack æ—¶é¡»å›žä¼  |
| timestamp | int | Unix æ—¶é—´æˆ³ |
| ttl | int | æŒ‡ä»¤æœ‰æ•ˆæœŸï¼ˆç§’) |
#### ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt/*)
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",
  "payload": {}
}
```
---
### 3.3 ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt)
#### rpt/state â€” å¿ƒè·³/状态
**Topic:** `snlp/v1/{device_id}/rpt/state`
**发布示例(终端 â†’ äº‘):**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",
  "payload": {
    "cpu": 15,
    "mem": 45,
    "battery": 80,
    "net": "wifi",
    "mode": "idle"
  }
}
```
**服务端行为:**
- æ›´æ–°è®¾å¤‡åœ¨çº¿çŠ¶æ€
- è‹¥è®¾å¤‡æœªåˆå§‹åŒ–且 MQTT å·²è¿žæŽ¥ â†’ è‡ªåŠ¨è§¦å‘å®Œæ•´åˆå§‹åŒ–æµç¨‹
**mode å¸¸è§å€¼ï¼š** `idle` / `dialogue` / `translate` / `study` / `sleep`
---
#### rpt/event â€” äº‹ä»¶ä¸ŠæŠ¥
**Topic:** `snlp/v1/{device_id}/rpt/event`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365210
  },
  "msg_type": "event",
  "payload": {
    "event": "asr_result",
    "text": "Good morning",
    "confidence": 0.95
  }
}
```
**常见 event ç±»åž‹ï¼š**
| event | è¯´æ˜Ž |
|-------|------|
| wake | å”¤é†’触发 |
| sleep | ä¼‘眠触发 |
| asr_result | ASR è¯†åˆ«ç»“æžœ |
| alarm | å¼‚常报警 |
---
#### rpt/ack â€” æŒ‡ä»¤å›žæ‰§
**Topic:** `snlp/v1/{device_id}/rpt/ack`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365215
  },
  "msg_type": "ack",
  "payload": {
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "success",
    "error_code": 0
  }
}
```
| payload å­—段 | è¯´æ˜Ž |
|--------------|------|
| req_id | å¯¹åº”下发指令的 req_id |
| status | `success` / `failed` |
| error_code | 0 è¡¨ç¤ºæˆåŠŸï¼Œéž 0 ä¸ºé”™è¯¯ç  |
---
#### rpt/progress â€” ä»»åŠ¡è¿›åº¦
**Topic:** `snlp/v1/{device_id}/rpt/progress`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365220
  },
  "msg_type": "progress",
  "payload": {
    "task_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "downloading",
    "progress": 45,
    "info": "Downloading courses/course01/chapter01/resources/001.mp3"
  }
}
```
---
#### rpt/log â€” ç³»ç»Ÿæ—¥å¿—
**Topic:** `snlp/v1/{device_id}/rpt/log`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365230
  },
  "msg_type": "log",
  "payload": {
    "level": "error",
    "module": "audio",
    "message": "Microphone init failed"
  }
}
```
---
### 3.4 äº‘端下发(cmd)
#### cmd/ctl â€” å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤
**Topic:** `snlp/v1/{device_id}/cmd/ctl`
**Action åˆ—表:**
| Action | payload ç¤ºä¾‹ | è¯´æ˜Ž |
|--------|-------------|------|
| SET_VOLUME | `{"level": 50}` | è®¾ç½®éŸ³é‡ 0–100 |
| SET_POWER | `{"state": "wake"}` | å”¤é†’ / `sleep` ä¼‘眠 |
| SET_CLOCK | `{"ts": 1718365200}` | åŒæ­¥ Unix æ—¶é—´æˆ³ |
| PLAY_MEDIA | `{"path": "courses/course01/chapter01/resources/001.mp3", "volume": 50, "loop": false}` | æ’­æ”¾æœ¬åœ°åª’体 |
| STOP_MEDIA | `{}` | åœæ­¢æ’­æ”¾ |
| SET_ALARM | `{"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}` | è®¾ç½®é—¹é’Ÿ |
| UNSET_ALARM | `{"id": "alarm_01"}` | åˆ é™¤é—¹é’Ÿ |
| REBOOT | `{"delay": 5}` | å»¶æ—¶é‡å¯ |
**SET_CLOCK ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_CLOCK",
  "payload": {
    "ts": 1718365200
  }
}
```
**SET_VOLUME ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "34dc4157-9e52-4a99-bdad-b3daf665df11",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": {
    "level": 50
  }
}
```
**SET_POWER ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_POWER",
  "payload": {
    "state": "wake"
  }
}
```
---
#### cmd/task â€” å¼‚步任务指令
**Topic:** `snlp/v1/{device_id}/cmd/task`
**当前实现使用的 Action:**
| Action | è¯´æ˜Ž |
|--------|------|
| SYNC_COURSE_TASK | ä¸‹å‘完整课程资源包(courseTask.json æ ¼å¼ï¼‰ï¼Œç»ˆç«¯æŒ‰ assets æ‰¹é‡ä¸‹è½½ |
**SYNC_COURSE_TASK ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "e1c1de2a-3067-417d-b533-67c9a9827a8e",
    "timestamp": 1718365201,
    "ttl": 7200
  },
  "action": "SYNC_COURSE_TASK",
  "payload": {
    "version": "1.0",
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
    "assets": [
      {
        "url": "http://your-server:9500/static/courses/course01/chapter01/resources/001.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹001",
        "target_path": "courses/course01/chapter01/resources/001.mp3",
        "cover_url": "http://your-server:9500/static/courses/course01/chapter01/resources/001.png",
        "cover_target_path": "courses/course01/chapter01/resources/001.png"
      },
      {
        "url": "http://your-server:9500/static/courses/course01/chapter01/resources/002.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹002",
        "target_path": "courses/course01/chapter01/resources/002.mp3",
        "cover_url": "http://your-server:9500/static/courses/course01/chapter01/resources/002.png",
        "cover_target_path": "courses/course01/chapter01/resources/002.png"
      }
    ]
  }
}
```
**assets å­—段说明:**
| å­—段 | è¯´æ˜Ž |
|------|------|
| url | èµ„源完整 HTTP ä¸‹è½½åœ°å€ï¼ˆæœåŠ¡ç«¯è‡ªåŠ¨è¡¥å…¨ï¼‰ |
| md5 | æ–‡ä»¶æ ¡éªŒå€¼ |
| title | èµ„源标题 |
| target_path | ç»ˆç«¯å­˜å‚¨ç›¸å¯¹è·¯å¾„(与服务端目录结构一致) |
| cover_url | å°é¢å®Œæ•´ HTTP ä¸‹è½½åœ°å€ |
| cover_target_path | å°é¢ç»ˆç«¯å­˜å‚¨ç›¸å¯¹è·¯å¾„ |
**终端处理逻辑:**
1. æ”¶åˆ° `SYNC_COURSE_TASK` åŽè§£æž `assets` åˆ—表
2. æŒ‰æ¯é¡¹çš„ `url` ä¸‹è½½æ–‡ä»¶ï¼Œä¿å­˜åˆ°å­˜å‚¨å¡ `target_path` è·¯å¾„
3. æŒ‰ `cover_url` ä¸‹è½½å°é¢ï¼Œä¿å­˜åˆ° `cover_target_path`
4. ä¸‹è½½è¿‡ç¨‹ä¸­é€šè¿‡ `rpt/progress` ä¸ŠæŠ¥è¿›åº¦
5. å®ŒæˆåŽé€šè¿‡ `rpt/ack` å›žä¼  `req_id` ä¸Žæ‰§è¡Œç»“æžœ
**PRD é¢„ç•™ Action(可通过 HTTP `/devices/{id}/task` æ‰‹åŠ¨ä¸‹å‘ï¼‰ï¼š**
| Action | payload ç¤ºä¾‹ | è¯´æ˜Ž |
|--------|-------------|------|
| DOWNLOAD | `{"task_id":"uuid","url":"...","md5":"...","target_path":"..."}` | å•文件下载 |
| UPDATE_MANIFEST | `{"course_id":"...","assets":[...]}` | æ›´æ–°èµ„源清单 |
| OTA_UPDATE | `{"url":"...","version":"1.0.2"}` | å›ºä»¶å‡çº§ |
| DELETE_FILE | `{"path":"..."}` | åˆ é™¤æ–‡ä»¶ |
---
### 3.5 æœåŠ¡ç«¯è‡ªåŠ¨ä¸‹å‘æµç¨‹
设备首次上报 `rpt/state` æ—¶ï¼ŒæœåŠ¡ç«¯è‡ªåŠ¨æ‰§è¡Œï¼š
```
1. cmd/ctl  SET_CLOCK   {"ts": <当前时间戳>}
2. cmd/ctl  SET_VOLUME  {"level": 50}
3. cmd/ctl  SET_POWER   {"state": "wake"}
4. cmd/task SYNC_COURSE_TASK  {course01 å®Œæ•´ payload}
5. cmd/task SYNC_COURSE_TASK  {course02 å®Œæ•´ payload}
   ... index.json ä¸­æ¯é—¨åŸºç¡€è¯¾ç¨‹å„ 1 æ¡
```
**手动 MQTT å‘布示例(mosquitto_pub):**
```bash
mosquitto_pub \
  -h your-mqtt-broker -p 2100 \
  -u your-username -P your-password \
  -t "snlp/v1/SN-88293021/cmd/task" \
  -q 1 \
  -m '{"header":{"version":"1.0","device_id":"SN-88293021","req_id":"manual-uuid","timestamp":1718365200,"ttl":7200},"action":"SYNC_COURSE_TASK","payload":{"version":"1.0","course_id":"course01","course_name":"小智学习课程01","assets":[]}}'
```
---
## å››ã€æŽ¥å£é€ŸæŸ¥è¡¨
### HTTP æŽ¥å£
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/health` | å¥åº·æ£€æŸ¥ |
| GET | `/static/{path}` | é™æ€èµ„源下载 |
| POST | `/api/v1/scene/analyze` | åœºæ™¯è¯†åˆ« |
| GET | `/api/v1/dialogue/stream` | SSE å¯¹è¯æµ |
| GET | `/api/v1/courses` | è¯¾ç¨‹ç´¢å¼• |
| GET | `/api/v1/courses/base-manifest` | å…¨éƒ¨åŸºç¡€è¯¾ç¨‹åŒæ­¥åŒ…预览 |
| GET | `/api/v1/courses/{course_id}` | è¯¾ç¨‹è¯¦æƒ… |
| GET | `/api/v1/courses/{course_id}/manifest` | å•课程同步包预览 |
| GET | `/api/v1/courses/{course_id}/tasks` | åŽŸå§‹ courseTask.json |
| GET | `/api/v1/tasklist` | è®¾å¤‡åˆå§‹åŒ–任务清单 |
| POST | `/api/v1/devices/init` | æ‰‹åŠ¨è®¾å¤‡åˆå§‹åŒ– |
| POST | `/api/v1/devices/course-init` | æ‰‹åŠ¨å•è¯¾ç¨‹åŒæ­¥ |
| GET | `/api/v1/devices` | åœ¨çº¿è®¾å¤‡åˆ—表 |
| GET | `/api/v1/devices/initialized` | å·²åˆå§‹åŒ–设备 |
| DELETE | `/api/v1/devices/initialized/{device_id}` | æ¸…除初始化记录 |
| GET | `/api/v1/devices/{device_id}` | è®¾å¤‡çŠ¶æ€ |
| GET | `/api/v1/devices/{device_id}/init-status` | åˆå§‹åŒ–状态 |
| GET | `/api/v1/devices/{device_id}/acks` | æŒ‡ä»¤å›žæ‰§åŽ†å² |
| GET | `/api/v1/devices/{device_id}/events` | äº‹ä»¶/进度历史 |
| POST | `/api/v1/devices/{device_id}/cmd` | æ‰‹åŠ¨ä¸‹å‘ ctl æŒ‡ä»¤ |
| POST | `/api/v1/devices/{device_id}/task` | æ‰‹åŠ¨ä¸‹å‘ task æŒ‡ä»¤ |
### MQTT æŽ¥å£
| æ–¹å‘ | Topic | å…³é”® Action / ç”¨é€” |
|------|-------|-------------------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/state` | å¿ƒè·³ï¼Œè§¦å‘自动初始化 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/event` | ASR ç»“果、唤醒/休眠事件 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/ack` | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/progress` | ä¸‹è½½/OTA è¿›åº¦ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/log` | è¿œç¨‹æŽ’障日志 |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{id}/cmd/ctl` | SET_CLOCK / SET_VOLUME / SET_POWER / PLAY_MEDIA ç­‰ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{id}/cmd/task` | SYNC_COURSE_TASK(课程资源包同步) |
---
*文档版本:1.0 | å¯¹åº”服务端版本:1.0.0*
smartlearning/README.md
New file
@@ -0,0 +1,736 @@
# SmartLearning æƒ…境感知智能英语学习系统
基于 PRD(`docs/smartlearing_prd_v1.01.txt`)实现的云端服务端,负责场景识别、智能对话、课程资源管理与终端设备 MQTT æŒ‡ä»¤ä¸‹å‘,支撑「场景感知 â†’ æ„å›¾åˆ†æž â†’ æ™ºèƒ½å¯¹è¯ â†’ å­¦ä¹ åé¦ˆã€é—­çŽ¯ã€‚
---
## ç›®å½•
- [系统架构](#系统架构)
- [项目结构](#项目结构)
- [核心流程](#核心流程)
- [MQTT åè®®](#mqtt-协议)
- [课程与任务清单](#课程与任务清单)
- [HTTP API](#http-api)
- [配置说明](#配置说明)
- [本地开发](#本地开发)
- [打包与部署](#打包与部署)
- [常见问题](#常见问题)
---
## ç³»ç»Ÿæž¶æž„
```
┌─────────────┐     MQTT (snlp/v1/...)      â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
│  å­¦ä¹ ç»ˆç«¯    â”‚ â—„──────────────────────────► â”‚  SmartLearning   â”‚
│  (设备)     â”‚     HTTP API / SSE           â”‚  Server (本仓库)  â”‚
└─────────────┘ â—„──────────────────────────► â””────────┬─────────┘
                                                       â”‚
                                              â”Œâ”€â”€â”€â”€â”€â”€â”€â”€â–¼â”€â”€â”€â”€â”€â”€â”€â”€â”€â”
                                              â”‚  data/ è¯¾ç¨‹èµ„源     â”‚
                                              â”‚  runtime/ è¿è¡ŒçŠ¶æ€  â”‚
                                              â””──────────────────┘
```
| ç»„ä»¶ | æŠ€æœ¯æ ˆ | èŒè´£ |
|------|--------|------|
| HTTP æœåŠ¡ | FastAPI + Uvicorn | REST API、SSE å¯¹è¯æµã€é™æ€èµ„源托管 |
| MQTT å®¢æˆ·ç«¯ | paho-mqtt | è®¢é˜…终端上报、下发控制/任务指令 |
| è¯¾ç¨‹æ•°æ® | JSON æ–‡ä»¶ | è¯¾ç¨‹ç´¢å¼•、章节视频清单、初始化任务 |
| è¿è¡ŒçŠ¶æ€ | JSON æŒä¹…化 | è®°å½•已完成初始化的设备 ID |
---
## é¡¹ç›®ç»“æž„
```
smartlearning/
├── docs/
│   â””── smartlearing_prd_v1.01.txt   # äº§å“éœ€æ±‚与 MQTT åè®®è§„范
├── data/                            # è¯¾ç¨‹ä¸Žä»»åŠ¡æ•°æ®ï¼ˆåŒæ—¶ä½œä¸ºé™æ€èµ„æºæ ¹ç›®å½•ï¼‰
│   â”œâ”€â”€ tasklist.json                # è®¾å¤‡å¼€æœºåˆå§‹åŒ–任务清单
│   â””── courses/
│       â”œâ”€â”€ index.json               # å·²å‘布课程索引(基础课程包)
│       â”œâ”€â”€ course01/
│       â”‚   â”œâ”€â”€ course.json          # è¯¾ç¨‹è§†é¢‘清单
│       â”‚   â”œâ”€â”€ courseTask.json      # å•课程初始化任务(可选扩展)
│       â”‚   â””── chapter01/resources/ # è§†é¢‘文件目录
│       â””── course02/
│           â””── ...
├── server/
│   â”œâ”€â”€ app/
│   â”‚   â”œâ”€â”€ main.py                  # åº”用入口
│   â”‚   â”œâ”€â”€ config.py                # é…ç½®ï¼ˆæ”¯æŒ .env)
│   â”‚   â”œâ”€â”€ routes/                  # API è·¯ç”±
│   â”‚   â”œâ”€â”€ services/                # ä¸šåŠ¡é€»è¾‘
│   â”‚   â””── mqtt/                    # MQTT ä¸ŽæŒ‡ä»¤ä¸‹å‘
│   â”œâ”€â”€ runtime/
│   â”‚   â””── initialized_devices.json # å·²åˆå§‹åŒ–设备记录(运行时生成)
│   â”œâ”€â”€ requirements.txt
│   â””── run.py                       # å¯åŠ¨è„šæœ¬
└── README.md
```
---
## æ ¸å¿ƒæµç¨‹
### 1. è®¾å¤‡é¦–次上线(自动初始化)
终端开机后通过 MQTT ä¸ŠæŠ¥å¿ƒè·³ `rpt/state`,服务端检测到该 `device_id` å°šæœªåˆå§‹åŒ–时,**自动**执行两阶段流程:
```
终端上报 rpt/state(首次)
        â”‚
        â–¼
┌───────────────────────────────────────┐
│ é˜¶æ®µ 1:设备基础初始化(cmd/ctl)      â”‚
│  Â· SET_CLOCK   åŒæ­¥ç³»ç»Ÿæ—¶é’Ÿ            â”‚
│  Â· SET_VOLUME  è®¾ç½®é»˜è®¤éŸ³é‡ï¼ˆ50)       â”‚
│  Â· SET_POWER   å”¤é†’设备                â”‚
└───────────────────────────────────────┘
        â”‚
        â–¼
┌───────────────────────────────────────┐
│ é˜¶æ®µ 2:基础课程资源同步(cmd/task)    â”‚
│  Â· UPDATE_MANIFEST  ä¸‹å‘完整资源清单   â”‚
│  Â· DOWNLOAD Ã— N     é€æ–‡ä»¶ä¸‹è½½èµ„源包   â”‚
│   ï¼ˆindex.json ä¸­æ‰€æœ‰è¯¾ç¨‹ï¼‰            â”‚
└───────────────────────────────────────┘
        â”‚
        â–¼
写入 server/runtime/initialized_devices.json
(同一设备不会重复触发,除非手动强制)
```
**自动触发条件(须同时满足):**
1. æ”¶åˆ° `snlp/v1/{device_id}/rpt/state`
2. `device_id` ä¸åœ¨æœ¬åœ°å·²åˆå§‹åŒ–记录中
3. MQTT å·²è¿žæŽ¥
4. `AUTO_INIT_ENABLED=true`(默认开启)
**不会自动触发的情况:**
- å·²åˆå§‹åŒ–设备再次上报心跳 â†’ è·³è¿‡
- MQTT æœªè¿žæŽ¥ â†’ è·³è¿‡ï¼ˆä¸‹æ¬¡å¿ƒè·³å¯é‡è¯•)
- å¯é€šè¿‡ `DELETE /api/v1/devices/initialized/{device_id}` æ¸…除记录后重新触发
### 2. åŸºç¡€è¯¾ç¨‹èµ„源同步说明
`data/courses/index.json` ä¸­åˆ—出的课程均视为**基础课程**,在设备初始化完成后自动同步到终端存储卡。
**终端存储路径规则:**
```
设备路径 = {DEVICE_DATA_ROOT} + æœåŠ¡ç«¯ data/ ä¸‹çš„相对路径
```
默认 `DEVICE_DATA_ROOT=/data`,示例:
| æœåŠ¡ç«¯æ–‡ä»¶ | ç»ˆç«¯ç›®æ ‡è·¯å¾„ |
|-----------|-------------|
| `data/courses/index.json` | `/data/courses/index.json` |
| `data/courses/course01/course.json` | `/data/courses/course01/course.json` |
| `data/courses/course01/chapter01/resources/001.mp3` | `/data/courses/course01/chapter01/resources/001.mp3` |
当前 `course01` + `course02` å…± **23 ä¸ªæ–‡ä»¶**(索引 + é…ç½® + ä»»åŠ¡æ¸…å• + 18 ä¸ªè§†é¢‘占位路径)。
### 3. å•课程初始化(手动,可选)
若需单独对某一门课程执行完整任务(含播放引导),可调用:
```http
POST /api/v1/devices/course-init
Content-Type: application/json
{
  "device_id": "SN-88293021",
  "course_id": "course01"
}
```
该接口读取 `courseTask.json`,下发 `UPDATE_MANIFEST`、`DOWNLOAD`、`PLAY_MEDIA` ç­‰æŒ‡ä»¤ï¼Œé€‚用于选课后增量同步或重新拉取。
### 4. åœºæ™¯æ„ŸçŸ¥ä¸Žå¯¹è¯
```
环境音/语音 â†’ POST /api/v1/scene/analyze â†’ åœºæ™¯è¯†åˆ«ï¼ˆç½®ä¿¡åº¦ < 0.7 èµ°é€šç”¨é—®å€™ï¼‰
                    â†“
用户回复 â†’ GET /api/v1/dialogue/stream (SSE) â†’ æµå¼è¿”回教学对话与反馈
```
### 5. æ‰‹åŠ¨å¼ºåˆ¶é‡æ–°åˆå§‹åŒ–
```http
POST /api/v1/devices/init
Content-Type: application/json
{
  "device_id": "SN-88293021",
  "force": true
}
```
`force: false` ä¸”设备已初始化时,返回 `status: "already_initialized"`,不会重复下发。
---
## MQTT åè®®
### Topic è§„则
```
snlp/v1/{device_id}/{direct}/{msg_type}
```
| æ–¹å‘ | Topic | è¯´æ˜Ž |
|------|-------|------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/state` | å¿ƒè·³/状态(cpu、电量、网络、模式) |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/event` | äº‹ä»¶ï¼ˆå”¤é†’、ASR ç»“果等) |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/ack` | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/progress` | å¼‚步任务进度(下载/OTA) |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/ctl` | å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/task` | å¼‚步任务指令(下载资源包等) |
### æŒ‡ä»¤ Action ä¸€è§ˆ
| Action | é€šé“ | è¯´æ˜Ž |
|--------|------|------|
| `SET_CLOCK` | ctl | åŒæ­¥æ—¶é—´æˆ³ |
| `SET_VOLUME` | ctl | è®¾ç½®éŸ³é‡ 0–100 |
| `SET_POWER` | ctl | ä¼‘眠/唤醒 |
| `PLAY_MEDIA` | ctl | æ’­æ”¾æœ¬åœ°åª’体 |
| `STOP_MEDIA` | ctl | åœæ­¢æ’­æ”¾ |
| `DOWNLOAD` | task | å¼‚步下载文件到 `target_path` |
| `UPDATE_MANIFEST` | task | æ›´æ–°è¯¾ç¨‹èµ„源清单 |
| `OTA_UPDATE` | task | å›ºä»¶å‡çº§ |
| `REBOOT` | ctl | å»¶æ—¶é‡å¯ |
### ä¸‹å‘指令 Payload æ ¼å¼
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "uuid",
    "timestamp": 1718365200,
    "ttl": 3600
  },
  "action": "DOWNLOAD",
  "payload": {
    "task_id": "uuid",
    "url": "http://your-server:9500/static/courses/course01/chapter01/resources/001.mp3",
    "md5": "",
    "target_path": "/data/courses/course01/chapter01/resources/001.mp3"
  }
}
```
终端执行后须回 `rpt/ack`;下载过程中通过 `rpt/progress` ä¸ŠæŠ¥è¿›åº¦ã€‚
---
## è¯¾ç¨‹ä¸Žä»»åŠ¡æ¸…å•
### index.json â€” åŸºç¡€è¯¾ç¨‹ç´¢å¼•
```json
{
  "courses": [
    { "course_id": "course01", "config": "courses/course01/course.json" },
    { "course_id": "course02", "config": "courses/course02/course.json" }
  ]
}
```
新增课程:在 `data/courses/` ä¸‹åˆ›å»ºç›®å½•å’Œ `course.json`,并登记到 `index.json`,设备重新初始化后自动同步。
### course.json â€” è¯¾ç¨‹è§†é¢‘清单
```json
{
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "chapters": [
    {
      "chapter_id": "chapter01",
      "chapter_name": "第一章",
      "audios": [
        {
          "id": "001",
          "title": "课程001",
          "path": "courses/course01/chapter01/resources/001.mp3"
        }
      ]
    }
  ]
}
```
`path` ä¸ºç›¸å¯¹äºŽ `data/` çš„路径,须与实际文件位置一致。
### tasklist.json â€” è®¾å¤‡å¼€æœºä»»åŠ¡
定义设备基础初始化步骤(时钟、音量、唤醒)。基础课程资源包由服务端在初始化完成后**自动**通过 `cmd/task` ä¸‹å‘,无需在此文件中配置。
### courseTask.json â€” å•课程任务(可选)
定义某一门课程的扩展初始化流程(如下载 + æ’­æ”¾å¼•导),通过 `course-init` API æ‰‹åŠ¨è§¦å‘ã€‚
---
## HTTP API
服务默认地址:`http://localhost:9500`
交互式文档:`http://localhost:9500/docs`
### å¥åº·æ£€æŸ¥
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/health` | æœåŠ¡çŠ¶æ€ä¸Ž MQTT è¿žæŽ¥çŠ¶æ€ |
### åœºæ™¯ä¸Žå¯¹è¯
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | `/api/v1/scene/analyze` | åœºæ™¯è¯†åˆ« |
| GET | `/api/v1/dialogue/stream` | SSE å¯¹è¯æµï¼ˆå‚数:`dialogue_id`、`user_text`、`scene_prompt`、`difficulty`) |
### è¯¾ç¨‹ä¸Žåˆå§‹åŒ–
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/api/v1/courses` | è¯¾ç¨‹ç´¢å¼• |
| GET | `/api/v1/courses/base-manifest` | é¢„览基础课程同步资源列表 |
| GET | `/api/v1/courses/{course_id}` | è¯¾ç¨‹è¯¦æƒ… |
| GET | `/api/v1/courses/{course_id}/manifest` | å•课程资源清单 |
| GET | `/api/v1/courses/{course_id}/tasks` | å•课程任务清单 |
| GET | `/api/v1/tasklist` | è®¾å¤‡åˆå§‹åŒ–任务清单 |
| POST | `/api/v1/devices/init` | æ‰‹åŠ¨è§¦å‘è®¾å¤‡åˆå§‹åŒ–ï¼ˆæ”¯æŒ `force`) |
| POST | `/api/v1/devices/course-init` | å•课程初始化 |
### è®¾å¤‡ç®¡ç†
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/api/v1/devices` | åœ¨çº¿è®¾å¤‡åˆ—表(来自 MQTT ä¸ŠæŠ¥ï¼‰ |
| GET | `/api/v1/devices/initialized` | å·²åˆå§‹åŒ–设备记录 |
| GET | `/api/v1/devices/{device_id}` | è®¾å¤‡çŠ¶æ€ |
| GET | `/api/v1/devices/{device_id}/init-status` | åˆå§‹åŒ–状态 |
| DELETE | `/api/v1/devices/initialized/{device_id}` | æ¸…除初始化记录 |
| GET | `/api/v1/devices/{device_id}/acks` | æŒ‡ä»¤å›žæ‰§åŽ†å² |
| GET | `/api/v1/devices/{device_id}/events` | äº‹ä»¶åŽ†å² |
| POST | `/api/v1/devices/{device_id}/cmd` | æ‰‹åŠ¨ä¸‹å‘ ctl æŒ‡ä»¤ |
| POST | `/api/v1/devices/{device_id}/task` | æ‰‹åŠ¨ä¸‹å‘ task æŒ‡ä»¤ |
### é™æ€èµ„源
```
GET /static/{path}
```
示例:`/static/courses/course01/chapter01/resources/001.mp3`
终端 `DOWNLOAD` æŒ‡ä»¤ä¸­çš„ `url` æŒ‡å‘此地址,请确保 `BASE_URL` é…ç½®ä¸ºç»ˆç«¯å¯è®¿é—®çš„公网/内网地址。
### è¯·æ±‚示例
**场景识别:**
```bash
curl -X POST http://localhost:9500/api/v1/scene/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "timestamp": 1718365200,
    "audio_blob": "",
    "device_id": "SN-88293021",
    "context_hints": { "scene": "dining", "confidence": 0.9 }
  }'
```
**对话流(SSE):**
```bash
curl -N "http://localhost:9500/api/v1/dialogue/stream?user_text=Good%20morning&difficulty=A2"
```
**预览基础课程同步清单:**
```bash
curl http://localhost:9500/api/v1/courses/base-manifest
```
---
## é…ç½®è¯´æ˜Ž
在 `server/` ç›®å½•下创建 `.env` æ–‡ä»¶ï¼ˆæŽ¨èï¼Œé¿å…å°†å¯†é’¥å†™å…¥ä»£ç ï¼‰ï¼š
```env
# HTTP æœåŠ¡
HOST=0.0.0.0
PORT=9500
BASE_URL=http://your-public-ip:9500
# è¯¾ç¨‹æ•°æ®ç›®å½•(默认指向项目根 data/)
# DATA_DIR=E:/smartlearning/data
# MQTT
MQTT_BROKER=your-mqtt-broker
MQTT_PORT=1883
MQTT_USERNAME=your-username
MQTT_PASSWORD=your-password
MQTT_CLIENT_ID=snlp-server
MQTT_ENABLED=true
# åˆå§‹åŒ–
AUTO_INIT_ENABLED=true
DEVICE_DATA_ROOT=/data
# åœºæ™¯è¯†åˆ«
SCENE_CONFIDENCE_THRESHOLD=0.7
```
| å˜é‡ | é»˜è®¤å€¼ | è¯´æ˜Ž |
|------|--------|------|
| `HOST` | `0.0.0.0` | ç›‘听地址 |
| `PORT` | `9500` | ç›‘听端口 |
| `BASE_URL` | `http://localhost:9500` | èµ„源下载 URL å‰ç¼€ï¼Œ**部署时必须改为终端可访问的地址** |
| `DATA_DIR` | `../data` | è¯¾ç¨‹æ•°æ®ç›®å½• |
| `MQTT_BROKER` | â€” | MQTT Broker åœ°å€ |
| `MQTT_PORT` | â€” | MQTT ç«¯å£ |
| `MQTT_ENABLED` | `true` | è®¾ä¸º `false` å¯ä»…运行 HTTP API |
| `AUTO_INIT_ENABLED` | `true` | æ˜¯å¦è‡ªåŠ¨è§¦å‘è®¾å¤‡åˆå§‹åŒ– |
| `DEVICE_DATA_ROOT` | `/data` | ç»ˆç«¯å­˜å‚¨å¡æ ¹è·¯å¾„ |
### å·²åˆå§‹åŒ–设备记录
路径:`server/runtime/initialized_devices.json`
```json
{
  "devices": {
    "SN-88293021": {
      "initialized_at": 1781701381,
      "source": "auto",
      "mode": "idle",
      "courses_synced": true,
      "courses_sync_at": 1781701381,
      "courses_download_count": 23
    }
  }
}
```
---
## æœ¬åœ°å¼€å‘
### çŽ¯å¢ƒè¦æ±‚
- Python 3.10+
- å¯è®¿é—®çš„ MQTT Broker(自动初始化依赖 MQTT)
### å®‰è£…与启动
```bash
cd server
pip install -r requirements.txt
# åˆ›å»º .env å¹¶å¡«å†™é…ç½®ï¼ˆè§ä¸Šæ–‡ï¼‰
# copy .env.example .env   # å¦‚有示例文件
python run.py
```
或使用 uvicorn ç›´æŽ¥å¯åŠ¨ï¼ˆæ”¯æŒçƒ­é‡è½½ï¼‰ï¼š
```bash
cd server
uvicorn app.main:app --host 0.0.0.0 --port 9500 --reload
```
### éªŒè¯
```bash
# å¥åº·æ£€æŸ¥
curl http://localhost:9500/health
# è¯¾ç¨‹ç´¢å¼•
curl http://localhost:9500/api/v1/courses
# åŸºç¡€è¯¾ç¨‹åŒæ­¥æ¸…单
curl http://localhost:9500/api/v1/courses/base-manifest
```
访问 `http://localhost:9500/docs` æŸ¥çœ‹å®Œæ•´ API æ–‡æ¡£ã€‚
---
## æ‰“包与部署
### æ–¹å¼ä¸€ï¼šç›´æŽ¥éƒ¨ç½²ï¼ˆæŽ¨èå°è§„模/内网)
**1. æ‰“包项目**
在开发机上将项目目录打包(排除缓存与虚拟环境):
```bash
# Linux / macOS
cd /path/to
tar czvf smartlearning-server.tar.gz \
  --exclude='server/__pycache__' \
  --exclude='server/.venv' \
  --exclude='server/runtime/*.json' \
  smartlearning/
# Windows PowerShell
Compress-Archive -Path E:\smartlearning -DestinationPath smartlearning-server.zip
```
上传到服务器后解压:
```bash
tar xzvf smartlearning-server.tar.gz
cd smartlearning/server
```
**2. åˆ›å»ºè™šæ‹ŸçŽ¯å¢ƒå¹¶å®‰è£…ä¾èµ–**
```bash
python3 -m venv .venv
source .venv/bin/activate        # Windows: .venv\Scripts\activate
pip install -r requirements.txt
```
**3. é…ç½®çŽ¯å¢ƒå˜é‡**
```bash
cp .env.example .env   # æˆ–手动创建
vim .env               # å¡«å†™ BASE_URL、MQTT ç­‰
```
> **重要:** `BASE_URL` å¿…须设置为终端能访问到的地址(公网 IP æˆ–域名),否则 `DOWNLOAD` æŒ‡ä»¤ä¸­çš„资源 URL æ— æ•ˆã€‚
**4. å‡†å¤‡è¯¾ç¨‹èµ„源**
将视频文件放入 `data/courses/` å¯¹åº”目录,确保与 `course.json` ä¸­ `path` ä¸€è‡´ã€‚
**5. ä½¿ç”¨ systemd å®ˆæŠ¤è¿›ç¨‹ï¼ˆLinux)**
创建 `/etc/systemd/system/smartlearning.service`:
```ini
[Unit]
Description=SmartLearning Server
After=network.target
[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/smartlearning/server
EnvironmentFile=/opt/smartlearning/server/.env
ExecStart=/opt/smartlearning/server/.venv/bin/python run.py
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
```
```bash
sudo systemctl daemon-reload
sudo systemctl enable smartlearning
sudo systemctl start smartlearning
sudo systemctl status smartlearning
```
**6. Nginx åå‘代理(可选)**
```nginx
server {
    listen 80;
    server_name your-domain.com;
    location / {
        proxy_pass http://127.0.0.1:9500;
        proxy_http_version 1.1;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_buffering off;          # SSE å¯¹è¯æµéœ€è¦å…³é—­ç¼“冲
    }
}
```
若使用 HTTPS,配置 TLS è¯ä¹¦å¹¶å°† `BASE_URL` æ”¹ä¸º `https://your-domain.com`。
---
### æ–¹å¼äºŒï¼šDocker éƒ¨ç½²
在项目根目录创建 `Dockerfile`(如尚未存在,可参考以下内容):
```dockerfile
FROM python:3.12-slim
WORKDIR /app
COPY server/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY server/ ./server/
COPY data/ ./data/
WORKDIR /app/server
ENV HOST=0.0.0.0
ENV PORT=9500
ENV DATA_DIR=/app/data
EXPOSE 9500
CMD ["python", "run.py"]
```
`docker-compose.yml` ç¤ºä¾‹ï¼š
```yaml
services:
  smartlearning:
    build: .
    ports:
      - "9500:9500"
    env_file:
      - server/.env
    volumes:
      - ./data:/app/data:ro
      - ./server/runtime:/app/server/runtime
    restart: unless-stopped
```
构建与启动:
```bash
docker compose build
docker compose up -d
docker compose logs -f
```
---
### æ–¹å¼ä¸‰ï¼šWindows æœåŠ¡éƒ¨ç½²
**使用 NSSM æ³¨å†Œä¸º Windows æœåŠ¡ï¼š**
```powershell
# å®‰è£… NSSM åŽæ‰§è¡Œ
nssm install SmartLearning "E:\smartlearning\server\.venv\Scripts\python.exe" "E:\smartlearning\server\run.py"
nssm set SmartLearning AppDirectory "E:\smartlearning\server"
nssm set SmartLearning AppEnvironmentExtra "BASE_URL=http://your-ip:9500"
nssm start SmartLearning
```
---
### ç”Ÿäº§çŽ¯å¢ƒæ£€æŸ¥æ¸…å•
| æ£€æŸ¥é¡¹ | è¯´æ˜Ž |
|--------|------|
| `BASE_URL` | ç»ˆç«¯å¯é€šè¿‡æ­¤åœ°å€ä¸‹è½½ `/static/` èµ„源 |
| MQTT è¿žé€š | `/health` è¿”回 `"mqtt_connected": true` |
| é˜²ç«å¢™ | æ”¾è¡Œ HTTP ç«¯å£ï¼ˆ9500 æˆ– Nginx 80/443) |
| è¯¾ç¨‹æ–‡ä»¶ | `data/courses/` ä¸‹è§†é¢‘文件已就位 |
| æŒä¹…化 | `server/runtime/` ç›®å½•需可写(初始化记录) |
| å¯†é’¥å®‰å…¨ | MQTT è´¦å·å¯†ç æ”¾åœ¨ `.env`,勿提交到版本库 |
| è¿›ç¨‹å®ˆæŠ¤ | ä½¿ç”¨ systemd / Docker `restart` ç­–ç•¥ |
---
### å‡çº§ä¸Žå›žæ»š
**升级步骤:**
```bash
# 1. å¤‡ä»½è¿è¡ŒçŠ¶æ€
cp server/runtime/initialized_devices.json /backup/
# 2. æ›´æ–°ä»£ç 
git pull   # æˆ–替换解压包
# 3. æ›´æ–°ä¾èµ–
cd server && pip install -r requirements.txt
# 4. é‡å¯æœåŠ¡
sudo systemctl restart smartlearning
# æˆ– docker compose restart
```
**新增课程后:** æ— éœ€é‡å¯æœåŠ¡ï¼›å·²åˆå§‹åŒ–è®¾å¤‡éœ€æ¸…é™¤è®°å½•æˆ– `force: true` é‡æ–°åˆå§‹åŒ–方可同步新课程。
```bash
curl -X DELETE http://localhost:9500/api/v1/devices/initialized/SN-88293021
# è®¾å¤‡ä¸‹æ¬¡ä¸ŠæŠ¥ state æ—¶å°†è‡ªåŠ¨é‡æ–°åˆå§‹åŒ–å¹¶åŒæ­¥å…¨éƒ¨åŸºç¡€è¯¾ç¨‹
```
---
## å¸¸è§é—®é¢˜
**Q: è®¾å¤‡ä¸ŠæŠ¥äº† state,但没有收到初始化指令?**
1. æ£€æŸ¥ `/health` ä¸­ `mqtt_connected` æ˜¯å¦ä¸º `true`
2. æ£€æŸ¥è¯¥è®¾å¤‡æ˜¯å¦å·²åœ¨ `initialized_devices.json` ä¸­
3. ç¡®è®¤ `AUTO_INIT_ENABLED=true`
4. ç”¨ `GET /api/v1/devices/initialized` æŸ¥çœ‹è®°å½•
**Q: ç»ˆç«¯ä¸‹è½½èµ„源失败?**
1. ç¡®è®¤ `BASE_URL` æ˜¯ç»ˆç«¯ç½‘络可达的地址,不是 `localhost`
2. ç¡®è®¤è§†é¢‘文件已上传到 `data/courses/` å¯¹åº”路径
3. ç”¨æµè§ˆå™¨æˆ– curl è®¿é—® `BASE_URL/static/courses/...` éªŒè¯
**Q: å¦‚何只同步基础课程,不重复做时钟/音量初始化?**
当前设计为一体化流程。若仅需补发资源,可手动调用:
```bash
# é€šè¿‡å†…部 API ä¸‹å‘(需自行扩展端点)或直接向 MQTT å‘布 DOWNLOAD æŒ‡ä»¤
curl -X POST http://localhost:9500/api/v1/devices/SN-88293021/task \
  -H "Content-Type: application/json" \
  -d '{"header":{"device_id":"SN-88293021","timestamp":0},"action":"DOWNLOAD","payload":{"url":"...","target_path":"..."}}'
```
或清除初始化记录后让设备重新走完整流程。
**Q: MQTT Broker æš‚时不可用,HTTP API è¿˜èƒ½ç”¨å—?**
可以。设置 `MQTT_ENABLED=false` æˆ– Broker è¿žæŽ¥å¤±è´¥æ—¶ï¼ŒHTTP API ä»æ­£å¸¸è¿è¡Œï¼Œä½†è‡ªåŠ¨åˆå§‹åŒ–ä¸ŽæŒ‡ä»¤ä¸‹å‘ä¸å¯ç”¨ã€‚
---
## ç›¸å…³æ–‡æ¡£
- äº§å“éœ€æ±‚与 MQTT è¯¦ç»†è§„范:`docs/smartlearing_prd_v1.01.txt`
- API äº¤äº’式文档:启动服务后访问 `/docs`
---
## ç‰ˆæœ¬
- æœåŠ¡ç«¯ç‰ˆæœ¬ï¼š1.0.0
- åè®®ç‰ˆæœ¬ï¼šsnlp/v1
http://localhost:9500/api/v1/courses/course01/manifest
http://localhost:9500/api/v1/courses/base-manifest
# ç¬¬ä¸€æ­¥ï¼šæ¸…除该设备的初始化记录
curl.exe -X DELETE http://124.222.109.197:2101/api/v1/devices/initialized/498e93f3-cdc9-4362-b32a-d2fc10e69c12
Invoke-WebRequest -Uri "http://124.222.109.197:2101/api/v1/devices/initialized/498e93f3-cdc9-4362-b32a-d2fc10e69c12" -Method Delete
# ç¬¬äºŒæ­¥ï¼šå¼ºåˆ¶é‡æ–°åˆå§‹åŒ–(含全部基础课程同步)
Invoke-RestMethod -Uri "http://124.222.109.197:2101/api/v1/devices/init" -Method Post -ContentType "application/json" -Body '{"device_id": "498e93f3-cdc9-4362-b32a-d2fc10e69c12", "force": true}'
smartlearning/data/courses/course01/chapter01/resources/.gitkeep
smartlearning/data/courses/course01/chapter01/resources/001.jpg
smartlearning/data/courses/course01/chapter01/resources/001.mp3
Binary files differ
smartlearning/data/courses/course01/chapter01/resources/002.jpg
smartlearning/data/courses/course01/chapter01/resources/002.mp3
Binary files differ
smartlearning/data/courses/course01/chapter01/resources/003.jpg
smartlearning/data/courses/course01/chapter01/resources/003.mp3
Binary files differ
smartlearning/data/courses/course01/chapter02/resources/.gitkeep
smartlearning/data/courses/course01/chapter02/resources/001.jpg
smartlearning/data/courses/course01/chapter02/resources/001.mp3
Binary files differ
smartlearning/data/courses/course01/chapter02/resources/002.jpg
smartlearning/data/courses/course01/chapter02/resources/002.mp3
Binary files differ
smartlearning/data/courses/course01/chapter02/resources/003.jpg
smartlearning/data/courses/course01/chapter02/resources/003.mp3
Binary files differ
smartlearning/data/courses/course01/chapter03/resources/.gitkeep
smartlearning/data/courses/course01/chapter03/resources/001.jpg
smartlearning/data/courses/course01/chapter03/resources/001.mp3
Binary files differ
smartlearning/data/courses/course01/chapter03/resources/002.jpg
smartlearning/data/courses/course01/chapter03/resources/002.mp3
Binary files differ
smartlearning/data/courses/course01/chapter03/resources/003.jpg
smartlearning/data/courses/course01/chapter03/resources/003.mp3
Binary files differ
smartlearning/data/courses/course01/course.json
New file
@@ -0,0 +1,70 @@
{
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "基础英语听力课程,包含日常对话与场景化练习",
  "chapters": [
    {
      "chapter_id": "chapter01",
      "chapter_name": "日常场景对话",
      "audios": [
        {
          "id": "001",
          "title": "餐厅点餐对话",
          "path": "courses/course01/chapter01/resources/001.mp3"
        },
        {
          "id": "002",
          "title": "酒店入住对话",
          "path": "courses/course01/chapter01/resources/002.mp3"
        },
        {
          "id": "003",
          "title": "机场问路对话",
          "path": "courses/course01/chapter01/resources/003.mp3"
        }
      ]
    },
    {
      "chapter_id": "chapter02",
      "chapter_name": "商务英语场景",
      "audios": [
        {
          "id": "001",
          "title": "会议发言",
          "path": "courses/course01/chapter02/resources/001.mp3"
        },
        {
          "id": "002",
          "title": "电话沟通",
          "path": "courses/course01/chapter02/resources/002.mp3"
        },
        {
          "id": "003",
          "title": "邮件写作",
          "path": "courses/course01/chapter02/resources/003.mp3"
        }
      ]
    },
    {
      "chapter_id": "chapter03",
      "chapter_name": "生活实用场景",
      "audios": [
        {
          "id": "001",
          "title": "购物对话",
          "path": "courses/course01/chapter03/resources/001.mp3"
        },
        {
          "id": "002",
          "title": "医疗问诊",
          "path": "courses/course01/chapter03/resources/002.mp3"
        },
        {
          "id": "003",
          "title": "公共交通",
          "path": "courses/course01/chapter03/resources/003.mp3"
        }
      ]
    }
  ]
}
smartlearning/data/courses/course01/courseTask.json
New file
@@ -0,0 +1,32 @@
{
  "course_id": "course01",
  "tasks": [
    {
      "task_id": "course01_chapter01_download",
      "action": "DOWNLOAD",
      "payload": {
        "url": "http://example.com/course01/chapter01/001.mp3",
        "md5": "abc123",
        "target_path": "/data/courses/course01/chapter01/resources/001.mp3"
      }
    },
    {
      "task_id": "course01_chapter01_download_002",
      "action": "DOWNLOAD",
      "payload": {
        "url": "http://example.com/course01/chapter01/002.mp3",
        "md5": "def456",
        "target_path": "/data/courses/course01/chapter01/resources/002.mp3"
      }
    },
    {
      "task_id": "course01_chapter01_download_003",
      "action": "DOWNLOAD",
      "payload": {
        "url": "http://example.com/course01/chapter01/003.mp3",
        "md5": "ghi789",
        "target_path": "/data/courses/course01/chapter01/resources/003.mp3"
      }
    }
  ]
}
smartlearning/data/courses/course02/chapter01/resources/.gitkeep
smartlearning/data/courses/course02/chapter01/resources/001.jpg
smartlearning/data/courses/course02/chapter01/resources/001.mp3
Binary files differ
smartlearning/data/courses/course02/chapter01/resources/002.jpg
smartlearning/data/courses/course02/chapter01/resources/002.mp3
Binary files differ
smartlearning/data/courses/course02/chapter01/resources/003.jpg
smartlearning/data/courses/course02/chapter01/resources/003.mp3
Binary files differ
smartlearning/data/courses/course02/chapter02/resources/.gitkeep
smartlearning/data/courses/course02/chapter02/resources/001.jpg
smartlearning/data/courses/course02/chapter02/resources/001.mp3
Binary files differ
smartlearning/data/courses/course02/chapter02/resources/002.jpg
smartlearning/data/courses/course02/chapter02/resources/002.mp3
Binary files differ
smartlearning/data/courses/course02/chapter02/resources/003.jpg
smartlearning/data/courses/course02/chapter02/resources/003.mp3
Binary files differ
smartlearning/data/courses/course02/chapter03/resources/.gitkeep
smartlearning/data/courses/course02/chapter03/resources/001.jpg
smartlearning/data/courses/course02/chapter03/resources/001.mp3
Binary files differ
smartlearning/data/courses/course02/chapter03/resources/002.jpg
smartlearning/data/courses/course02/chapter03/resources/002.mp3
Binary files differ
smartlearning/data/courses/course02/chapter03/resources/003.jpg
smartlearning/data/courses/course02/chapter03/resources/003.mp3
Binary files differ
smartlearning/data/courses/course02/course.json
New file
@@ -0,0 +1,70 @@
{
  "course_id": "course02",
  "course_name": "小智学习课程02",
  "description": "商务英语进阶课程,涵盖会议、电话沟通与邮件写作高级技巧",
  "chapters": [
    {
      "chapter_id": "chapter01",
      "chapter_name": "商务会议场景",
      "audios": [
        {
          "id": "001",
          "title": "会议主持技巧",
          "path": "courses/course02/chapter01/resources/001.mp3"
        },
        {
          "id": "002",
          "title": "团队协作讨论",
          "path": "courses/course02/chapter01/resources/002.mp3"
        },
        {
          "id": "003",
          "title": "项目汇报演讲",
          "path": "courses/course02/chapter01/resources/003.mp3"
        }
      ]
    },
    {
      "chapter_id": "chapter02",
      "chapter_name": "电话沟通进阶",
      "audios": [
        {
          "id": "001",
          "title": "国际客户接待",
          "path": "courses/course02/chapter02/resources/001.mp3"
        },
        {
          "id": "002",
          "title": "商务邀约技巧",
          "path": "courses/course02/chapter02/resources/002.mp3"
        },
        {
          "id": "003",
          "title": "跨文化沟通",
          "path": "courses/course02/chapter02/resources/003.mp3"
        }
      ]
    },
    {
      "chapter_id": "chapter03",
      "chapter_name": "邮件写作高级",
      "audios": [
        {
          "id": "001",
          "title": "商务邮件结构",
          "path": "courses/course02/chapter03/resources/001.mp3"
        },
        {
          "id": "002",
          "title": "正式邀请函写作",
          "path": "courses/course02/chapter03/resources/002.mp3"
        },
        {
          "id": "003",
          "title": "项目进度汇报",
          "path": "courses/course02/chapter03/resources/003.mp3"
        }
      ]
    }
  ]
}
smartlearning/data/courses/course02/courseTask.json
New file
@@ -0,0 +1,81 @@
{
    "version": "1.0",
    "course_id": "course02",
    "course_name": "小智学习课程02",
    "description": "课程02终端初始化任务:下载章节视频并更新本地资源清单",
    "assets": [
        {
            "url": "courses/course02/chapter01/resources/001.mp3",
            "md5": "...",
            "title": "第一章 è¯¾ç¨‹001",
            "target_path": "courses/course02/chapter01/resources/001.mp3",
            "cover_url": "courses/course02/chapter01/resources/001.jpg",
            "cover_target_path": "courses/course02/chapter01/resources/001.jpg"
        },
        {
            "url": "courses/course02/chapter01/resources/002.mp3",
            "md5": "...",
            "title": "第一章 è¯¾ç¨‹002",
            "target_path": "courses/course02/chapter01/resources/002.mp3",
            "cover_url": "courses/course02/chapter01/resources/002.jpg",
            "cover_target_path": "courses/course02/chapter01/resources/002.jpg"
        },
        {
            "url": "courses/course02/chapter01/resources/003.mp3",
            "md5": "...",
            "title": "第一章 è¯¾ç¨‹003",
            "target_path": "courses/course02/chapter01/resources/003.mp3",
            "cover_url": "courses/course02/chapter01/resources/003.jpg",
            "cover_target_path": "courses/course02/chapter01/resources/003.jpg"
        },
        {
            "url": "courses/course02/chapter02/resources/001.mp3",
            "md5": "...",
            "title": "第二章 è¯¾ç¨‹001",
            "target_path": "courses/course02/chapter02/resources/001.mp3",
            "cover_url": "courses/course02/chapter02/resources/001.jpg",
            "cover_target_path": "courses/course02/chapter02/resources/001.jpg"
        },
        {
            "url": "courses/course02/chapter02/resources/002.mp3",
            "md5": "...",
            "title": "第二章 è¯¾ç¨‹002",
            "target_path": "courses/course02/chapter02/resources/002.mp3",
            "cover_url": "courses/course02/chapter02/resources/002.jpg",
            "cover_target_path": "courses/course02/chapter02/resources/002.jpg"
        },
        {
            "url": "courses/course02/chapter02/resources/003.mp3",
            "md5": "...",
            "title": "第二章 è¯¾ç¨‹003",
            "target_path": "courses/course02/chapter02/resources/003.mp3",
            "cover_url": "courses/course02/chapter02/resources/003.jpg",
            "cover_target_path": "courses/course02/chapter02/resources/003.jpg"
        },
        {
            "url": "courses/course02/chapter03/resources/001.mp3",
            "md5": "...",
            "title": "第三章 è¯¾ç¨‹001",
            "target_path": "courses/course02/chapter03/resources/001.mp3",
            "cover_url": "courses/course02/chapter03/resources/001.jpg",
            "cover_target_path": "courses/course02/chapter03/resources/001.jpg"
        },
        {
            "url": "courses/course02/chapter03/resources/002.mp3",
            "md5": "...",
            "title": "第三章 è¯¾ç¨‹002",
            "target_path": "courses/course02/chapter03/resources/002.mp3",
            "cover_url": "courses/course02/chapter03/resources/002.jpg",
            "cover_target_path": "courses/course02/chapter03/resources/002.jpg"
        },
        {
            "url": "courses/course02/chapter03/resources/003.mp3",
            "md5": "...",
            "title": "第三章 è¯¾ç¨‹003",
            "target_path": "courses/course02/chapter03/resources/003.mp3",
            "cover_url": "courses/course02/chapter03/resources/003.jpg",
            "cover_target_path": "courses/course02/chapter03/resources/003.jpg"
        }
    ]
}
smartlearning/data/courses/index.json
New file
@@ -0,0 +1,12 @@
{
  "courses": [
    {
      "course_id": "course01",
      "config": "courses/course01/course.json"
    },
    {
      "course_id": "course02",
      "config": "courses/course02/course.json"
    }
  ]
}
smartlearning/data/tasklist.json
New file
@@ -0,0 +1,37 @@
{
  "tasks": [
    {
      "task_id": "init_course01",
      "action": "UPDATE_MANIFEST",
      "payload": {
        "course_id": "course01",
        "assets": [
          {
            "url": "http://example.com/course01/chapter01/001.mp3",
            "md5": "abc123",
            "target_path": "/data/courses/course01/chapter01/resources/001.mp3"
          },
          {
            "url": "http://example.com/course01/chapter01/002.mp3",
            "md5": "def456",
            "target_path": "/data/courses/course01/chapter01/resources/002.mp3"
          }
        ]
      }
    },
    {
      "task_id": "init_course02",
      "action": "UPDATE_MANIFEST",
      "payload": {
        "course_id": "course02",
        "assets": [
          {
            "url": "http://example.com/course02/chapter01/001.mp3",
            "md5": "ghi789",
            "target_path": "/data/courses/course02/chapter01/resources/001.mp3"
          }
        ]
      }
    }
  ]
}
smartlearning/docs/interface.md
New file
@@ -0,0 +1,1363 @@
# SmartLearning æŽ¥å£æ–‡æ¡£
> æœåŠ¡é»˜è®¤åœ°å€ï¼š`http://{host}:{port}`(默认 `http://0.0.0.0:2101`)
> äº¤äº’式 API æ–‡æ¡£ï¼šå¯åŠ¨æœåŠ¡åŽè®¿é—® `/docs`
> åè®®ç‰ˆæœ¬ï¼šHTTP API v1 / MQTT snlp/v1
---
## ç›®å½•
- [一、通用说明](#一通用说明)
- [二、HTTP æŽ¥å£](#二http-接口)
  - [2.1 ç³»ç»Ÿ](#21-系统)
  - [2.2 é™æ€èµ„源](#22-静态资源)
  - [2.3 åœºæ™¯è¯†åˆ«](#23-场景识别)
  - [2.4 å¯¹è¯æµ](#24-对话流)
  - [2.5 è¯¾ç¨‹ä¸Žä»»åŠ¡](#25-课程与任务)
  - [2.6 è®¾å¤‡åˆå§‹åŒ–](#26-设备初始化)
  - [2.7 è®¾å¤‡ç®¡ç†](#27-设备管理)
- [三、MQTT æŽ¥å£](#三mqtt-接口)
  - [3.1 Topic è§„则](#31-topic-规则)
  - [3.2 æ¶ˆæ¯é€šç”¨æ ¼å¼](#32-消息通用格式)
  - [3.3 ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt)](#33-终端上报rpt)
  - [3.4 äº‘端下发(cmd)](#34-云端下发cmd)
  - [3.5 æœåŠ¡ç«¯è‡ªåŠ¨ä¸‹å‘æµç¨‹](#35-服务端自动下发流程)
- [四、接口速查表](#四接口速查表)
---
## ä¸€ã€é€šç”¨è¯´æ˜Ž
### 1.1 Base URL
| çŽ¯å¢ƒ | åœ°å€ |
|------|------|
| æœ¬åœ°å¼€å‘ | `http://124.222.109.197:2101` |
| ç”Ÿäº§éƒ¨ç½² | ç”± `.env` ä¸­ `BASE_URL` é…ç½®ï¼Œç»ˆç«¯ä¸‹è½½èµ„源须使用该地址 |
### 1.2 è¯·æ±‚头
JSON æŽ¥å£ç»Ÿä¸€ä½¿ç”¨ï¼š
```
Content-Type: application/json
```
SSE å¯¹è¯æµæŽ¥å£è¿”回:
```
Content-Type: text/event-stream
```
### 1.3 é”™è¯¯å“åº”
FastAPI æ ‡å‡†é”™è¯¯æ ¼å¼ï¼š
```json
{
  "detail": "Course not found: course99"
}
```
常见 HTTP çŠ¶æ€ç ï¼š
| çŠ¶æ€ç  | è¯´æ˜Ž |
|--------|------|
| 200 | æˆåŠŸ |
| 400 | è¯·æ±‚参数错误 |
| 404 | èµ„源不存在 |
| 422 | è¯·æ±‚体校验失败 |
---
## äºŒã€HTTP æŽ¥å£
### 2.1 ç³»ç»Ÿ
#### GET /health
健康检查,查看服务与 MQTT è¿žæŽ¥çŠ¶æ€ã€‚
**请求示例:**
```bash
curl http://124.222.109.197:2101/health
```
**响应示例:**
```json
{
  "status": "ok",
  "mqtt_connected": true
}
```
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| status | string | æœåŠ¡çŠ¶æ€ï¼Œå›ºå®š `"ok"` |
| mqtt_connected | boolean | MQTT Broker æ˜¯å¦å·²è¿žæŽ¥ |
---
### 2.2 é™æ€èµ„源
#### GET /static/{path}
托管 `data/` ç›®å½•下的课程文件,供终端 HTTP ä¸‹è½½ã€‚
**URL è§„则:**
```
{BASE_URL}/static/{相对路径}
```
**示例:**
| èµ„源 | URL |
|------|-----|
| è¯¾ç¨‹ç´¢å¼• | `http://124.222.109.197:2101/static/courses/index.json` |
| è¯¾ç¨‹é…ç½® | `http://124.222.109.197:2101/static/courses/course01/course.json` |
| è¯¾ç¨‹ä»»åŠ¡åŒ… | `http://124.222.109.197:2101/static/courses/course01/courseTask.json` |
| éŸ³é¢‘ | `http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.mp3` |
| å°é¢ | `http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.png` |
**请求示例:**
```bash
curl -O http://124.222.109.197:2101/static/courses/course01/courseTask.json
```
**响应:** å¯¹åº”文件的二进制/JSON å†…容。
---
### 2.3 åœºæ™¯è¯†åˆ«
#### POST /api/v1/scene/analyze
根据设备上下文进行场景识别,返回问候策略与建议意图。
**请求体:**
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| timestamp | int | æ˜¯ | Unix æ—¶é—´æˆ³ |
| audio_blob | string | æ˜¯ | Base64 éŸ³é¢‘或占位符 |
| device_id | string | æ˜¯ | è®¾å¤‡ ID |
| context_hints | object | å¦ | åœºæ™¯æç¤ºï¼Œå¦‚ `{"scene":"dining","confidence":0.9}` |
**请求示例:**
```bash
curl -X POST http://124.222.109.197:2101/api/v1/scene/analyze \
  -H "Content-Type: application/json" \
  -d '{
    "timestamp": 1718365200,
    "audio_blob": "",
    "device_id": "SN-88293021",
    "context_hints": {
      "scene": "dining",
      "confidence": 0.9
    }
  }'
```
**响应示例:**
```json
{
  "scene": "用餐",
  "confidence": 0.9,
  "greeting_strategy": "contextual",
  "suggested_intent": "What are you having for dinner?"
}
```
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| scene | string | åœºæ™¯åç§° |
| confidence | float | ç½®ä¿¡åº¦ 0~1 |
| greeting_strategy | string | `"contextual"` åœºæ™¯é—®å€™ / `"generic"` é€šç”¨é—®å€™ï¼ˆç½®ä¿¡åº¦ < 0.7) |
| suggested_intent | string | å»ºè®®çš„开场白或提问 |
**低置信度响应示例:**
```json
{
  "scene": "通勤",
  "confidence": 0.62,
  "greeting_strategy": "generic",
  "suggested_intent": "Hello! How can I help you practice English today?"
}
```
---
### 2.4 å¯¹è¯æµ
#### GET /api/v1/dialogue/stream
SSE(Server-Sent Events)流式返回教学对话内容。
**Query å‚数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | é»˜è®¤å€¼ | è¯´æ˜Ž |
|------|------|------|--------|------|
| dialogue_id | string | å¦ | è‡ªåŠ¨ç”Ÿæˆ UUID | å¯¹è¯ ID |
| user_text | string | å¦ | â€” | ç”¨æˆ·å›žå¤æ–‡æœ¬ï¼ˆASR ç»“果) |
| scene_prompt | string | å¦ | â€” | åœºæ™¯å¼€åœºç™½ï¼ˆæ¥è‡ª scene/analyze) |
| difficulty | string | å¦ | A2 | CEFR éš¾åº¦ç­‰çº§ |
**请求示例:**
```bash
# ä»…开场问候
curl -N "http://124.222.109.197:2101/api/v1/dialogue/stream?scene_prompt=What%20are%20you%20having%20for%20dinner?&difficulty=A2"
# å«ç”¨æˆ·å›žå¤çš„反馈
curl -N "http://124.222.109.197:2101/api/v1/dialogue/stream?user_text=Good%20morning.&difficulty=A2"
```
**响应格式(SSE):**
```
event: message
data: {"dialogue_id":"a1b2c3d4-...","text":"What are you having for dinner?","audio_url":null,"intent_type":"greeting","difficulty_level":"A2"}
event: message
data: {"dialogue_id":"a1b2c3d4-...","text":"Great job! Your sentence looks correct.","audio_url":null,"intent_type":"praise","difficulty_level":"A2"}
event: message
data: {"dialogue_id":"a1b2c3d4-...","text":"What would you do differently next time?","audio_url":null,"intent_type":"question","difficulty_level":"A2"}
event: done
data: {"dialogue_id":"a1b2c3d4-..."}
```
**message äº‹ä»¶ data å­—段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| dialogue_id | string | å¯¹è¯ ID |
| text | string | å¯¹è¯æ–‡æœ¬ |
| audio_url | string \| null | éŸ³é¢‘ URL(当前为 null) |
| intent_type | string | `greeting` / `praise` / `correction` / `question` |
| difficulty_level | string | éš¾åº¦ç­‰çº§ |
---
### 2.5 è¯¾ç¨‹ä¸Žä»»åŠ¡
#### GET /api/v1/courses
获取已发布课程索引(对应 `data/courses/index.json`)。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/courses
```
**响应示例:**
```json
{
  "courses": [
    {
      "course_id": "course01",
      "config": "courses/course01/course.json"
    },
    {
      "course_id": "course02",
      "config": "courses/course02/course.json"
    }
  ]
}
```
---
#### GET /api/v1/courses/{course_id}
获取单门课程详情(章节与视频列表)。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/courses/course01
```
**响应示例:**
```json
{
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "课程01",
  "chapters": [
    {
      "chapter_id": "chapter01",
      "chapter_name": "第一章",
      "videos": [
        {
          "id": "001",
          "title": "课程001",
          "path": "courses/course01/chapter01/resources/001.mp3"
        }
      ]
    }
  ]
}
```
---
#### GET /api/v1/courses/base-manifest
预览全部基础课程同步包(MQTT ä¸‹å‘前的完整 payload,`url` å·²è¡¥å…¨ï¼‰ã€‚
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/courses/base-manifest
```
**响应示例:**
```json
{
  "scope": "base",
  "course_count": 2,
  "courses": [
    {
      "version": "1.0",
      "course_id": "course01",
      "course_name": "小智学习课程01",
      "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
      "assets": [
        {
          "url": "http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.mp3",
          "md5": "...",
          "title": "第一章 è¯¾ç¨‹001",
          "target_path": "courses/course01/chapter01/resources/001.mp3",
          "cover_url": "http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.png",
          "cover_target_path": "courses/course01/chapter01/resources/001.png"
        }
      ]
    }
  ]
}
```
---
#### GET /api/v1/courses/{course_id}/manifest
获取单门课程的同步包(`url` / `cover_url` å·²è¡¥å…¨ä¸ºå®Œæ•´ HTTP åœ°å€ï¼‰ã€‚
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/courses/course01/manifest
```
**响应示例:**
```json
{
  "version": "1.0",
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
  "assets": [
    {
      "url": "http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.mp3",
      "md5": "...",
      "title": "第一章 è¯¾ç¨‹001",
      "target_path": "courses/course01/chapter01/resources/001.mp3",
      "cover_url": "http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.png",
      "cover_target_path": "courses/course01/chapter01/resources/001.png"
    }
  ]
}
```
---
#### GET /api/v1/courses/{course_id}/tasks
获取原始 `courseTask.json`(本地文件内容,`url` ä¸ºç›¸å¯¹è·¯å¾„)。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/courses/course01/tasks
```
**响应示例:**
```json
{
  "version": "1.0",
  "course_id": "course01",
  "course_name": "小智学习课程01",
  "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
  "assets": [
    {
      "url": "courses/course01/chapter01/resources/001.mp3",
      "md5": "...",
      "title": "第一章 è¯¾ç¨‹001",
      "target_path": "courses/course01/chapter01/resources/001.mp3",
      "cover_url": "courses/course01/chapter01/resources/001.png",
      "cover_target_path": "courses/course01/chapter01/resources/001.png"
    }
  ]
}
```
---
#### GET /api/v1/tasklist
获取设备开机初始化任务清单(`data/tasklist.json`)。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/tasklist
```
**响应示例:**
```json
{
  "version": "1.0",
  "description": "终端设备初始化任务清单,开机后由服务端下发执行",
  "tasks": [
    {
      "task_id": "init_001",
      "name": "同步系统时钟",
      "action": "SET_CLOCK",
      "channel": "ctl",
      "ttl": 60,
      "payload": { "ts": 0 }
    },
    {
      "task_id": "init_002",
      "name": "设置默认音量",
      "action": "SET_VOLUME",
      "channel": "ctl",
      "ttl": 60,
      "payload": { "level": 50 }
    },
    {
      "task_id": "init_003",
      "name": "唤醒设备",
      "action": "SET_POWER",
      "channel": "ctl",
      "ttl": 60,
      "payload": { "state": "wake" }
    }
  ]
}
```
---
### 2.6 è®¾å¤‡åˆå§‹åŒ–
#### POST /api/v1/devices/init
手动触发设备完整初始化(基础 ctl æŒ‡ä»¤ + å…¨éƒ¨åŸºç¡€è¯¾ç¨‹ SYNC_COURSE_TASK)。
**请求体:**
| å­—段 | ç±»åž‹ | å¿…å¡« | é»˜è®¤å€¼ | è¯´æ˜Ž |
|------|------|------|--------|------|
| device_id | string | æ˜¯ | â€” | è®¾å¤‡ ID |
| force | boolean | å¦ | false | å¼ºåˆ¶é‡æ–°åˆå§‹åŒ–(忽略已初始化记录) |
| course_id | string | å¦ | â€” | æœ¬æŽ¥å£ä¸ä½¿ç”¨ |
**请求示例:**
```bash
curl -X POST http://124.222.109.197:2101/api/v1/devices/init \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "SN-88293021",
    "force": true
  }'
```
**响应示例(成功下发):**
```json
{
  "device_id": "SN-88293021",
  "commands": [
    {
      "header": {
        "version": "1.0",
        "device_id": "SN-88293021",
        "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
        "timestamp": 1718365200,
        "ttl": 60
      },
      "action": "SET_CLOCK",
      "payload": { "ts": 1718365200 }
    },
    {
      "header": {
        "version": "1.0",
        "device_id": "SN-88293021",
        "req_id": "e1c1de2a-3067-417d-b533-67c9a9827a8e",
        "timestamp": 1718365201,
        "ttl": 7200
      },
      "action": "SYNC_COURSE_TASK",
      "payload": {
        "version": "1.0",
        "course_id": "course01",
        "course_name": "小智学习课程01",
        "assets": []
      }
    }
  ],
  "status": "dispatched"
}
```
**响应示例(已初始化且未 force):**
```json
{
  "device_id": "SN-88293021",
  "commands": [],
  "status": "already_initialized"
}
```
| status å€¼ | è¯´æ˜Ž |
|-----------|------|
| dispatched | æŒ‡ä»¤å·²ä¸‹å‘ |
| already_initialized | è®¾å¤‡å·²åœ¨åˆå§‹åŒ–记录中,跳过 |
---
#### POST /api/v1/devices/course-init
手动触发单门课程资源同步(下发 1 æ¡ `SYNC_COURSE_TASK`)。
**请求体:**
| å­—段 | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| device_id | string | æ˜¯ | è®¾å¤‡ ID |
| course_id | string | æ˜¯ | è¯¾ç¨‹ ID,如 `course01` |
**请求示例:**
```bash
curl -X POST http://124.222.109.197:2101/api/v1/devices/course-init \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "SN-88293021",
    "course_id": "course01"
  }'
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "course_id": "course01",
  "commands": [
    {
      "header": {
        "version": "1.0",
        "device_id": "SN-88293021",
        "req_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
        "timestamp": 1718365300,
        "ttl": 7200
      },
      "action": "SYNC_COURSE_TASK",
      "payload": {
        "version": "1.0",
        "course_id": "course01",
        "course_name": "小智学习课程01",
        "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
        "assets": [
          {
            "url": "http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.mp3",
            "md5": "...",
            "title": "第一章 è¯¾ç¨‹001",
            "target_path": "courses/course01/chapter01/resources/001.mp3",
            "cover_url": "http://124.222.109.197:2101/static/courses/course01/chapter01/resources/001.png",
            "cover_target_path": "courses/course01/chapter01/resources/001.png"
          }
        ]
      }
    }
  ],
  "status": "dispatched"
}
```
---
### 2.7 è®¾å¤‡ç®¡ç†
#### GET /api/v1/devices
获取当前在线设备列表(来自 MQTT `rpt/state` ä¸ŠæŠ¥ï¼‰ã€‚
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/devices
```
**响应示例:**
```json
{
  "devices": [
    {
      "device_id": "SN-88293021",
      "cpu": 15.0,
      "mem": 45.0,
      "battery": 80.0,
      "net": "wifi",
      "mode": "idle",
      "last_seen": 1718365205
    }
  ]
}
```
---
#### GET /api/v1/devices/initialized
获取已完成初始化的设备记录。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/devices/initialized
```
**响应示例:**
```json
{
  "devices": {
    "SN-88293021": {
      "initialized_at": 1718365200,
      "source": "auto",
      "mode": "idle",
      "courses_synced": true,
      "courses_sync_at": 1718365201,
      "courses_sync_count": 2
    }
  }
}
```
---
#### GET /api/v1/devices/{device_id}
获取单个设备最新状态。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/devices/SN-88293021
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "cpu": 15.0,
  "mem": 45.0,
  "battery": 80.0,
  "net": "wifi",
  "mode": "idle",
  "last_seen": 1718365205
}
```
**404 å“åº”:**
```json
{
  "detail": "Device not found"
}
```
---
#### GET /api/v1/devices/{device_id}/init-status
查询设备初始化状态。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/devices/SN-88293021/init-status
```
**响应示例(已初始化):**
```json
{
  "device_id": "SN-88293021",
  "initialized": true,
  "record": {
    "initialized_at": 1718365200,
    "source": "auto",
    "mode": "idle",
    "courses_synced": true,
    "courses_sync_at": 1718365201,
    "courses_sync_count": 2
  }
}
```
**响应示例(未初始化):**
```json
{
  "device_id": "SN-88293021",
  "initialized": false,
  "record": null
}
```
---
#### DELETE /api/v1/devices/initialized/{device_id}
清除设备初始化记录,下次上报 `rpt/state` æ—¶å°†é‡æ–°è§¦å‘自动初始化。
**请求示例:**
```bash
curl -X DELETE http://124.222.109.197:2101/api/v1/devices/initialized/SN-88293021
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "status": "removed"
}
```
---
#### GET /api/v1/devices/{device_id}/acks
获取设备指令回执历史。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/devices/SN-88293021/acks
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "acks": [
    {
      "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
      "status": "success",
      "error_code": 0
    }
  ]
}
```
---
#### GET /api/v1/devices/{device_id}/events
获取设备事件与进度上报历史。
**请求示例:**
```bash
curl http://124.222.109.197:2101/api/v1/devices/SN-88293021/events
```
**响应示例:**
```json
{
  "device_id": "SN-88293021",
  "events": [
    {
      "event": "asr_result",
      "text": "Good morning",
      "confidence": 0.95,
      "_type": "event"
    },
    {
      "task_id": "uuid",
      "status": "downloading",
      "progress": 45,
      "info": "Downloading assets...",
      "_type": "progress"
    }
  ]
}
```
---
#### POST /api/v1/devices/{device_id}/cmd
手动向设备下发实时控制指令(MQTT `cmd/ctl`)。
**请求体:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": { "level": 80 }
}
```
**请求示例:**
```bash
curl -X POST http://124.222.109.197:2101/api/v1/devices/SN-88293021/cmd \
  -H "Content-Type: application/json" \
  -d '{
    "header": {
      "device_id": "SN-88293021",
      "timestamp": 1718365200,
      "ttl": 60
    },
    "action": "SET_VOLUME",
    "payload": { "level": 80 }
  }'
```
**响应示例(返回实际下发的完整指令):**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": { "level": 80 }
}
```
---
#### POST /api/v1/devices/{device_id}/task
手动向设备下发异步任务指令(MQTT `cmd/task`)。
**请求示例:**
```bash
curl -X POST http://124.222.109.197:2101/api/v1/devices/SN-88293021/task \
  -H "Content-Type: application/json" \
  -d '{
    "header": {
      "device_id": "SN-88293021",
      "timestamp": 1718365200,
      "ttl": 7200
    },
    "action": "SYNC_COURSE_TASK",
    "payload": {
      "version": "1.0",
      "course_id": "course01",
      "course_name": "小智学习课程01",
      "assets": []
    }
  }'
```
**响应示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": 1718365200,
    "ttl": 7200
  },
  "action": "SYNC_COURSE_TASK",
  "payload": {
    "version": "1.0",
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "assets": []
  }
}
```
---
## ä¸‰ã€MQTT æŽ¥å£
### 3.1 Topic è§„则
```
snlp/v1/{device_id}/{direct}/{msg_type}
```
| æ–¹å‘ | Topic | QoS | è¯´æ˜Ž |
|------|-------|-----|------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/state` | 0 | å¿ƒè·³/状态上报 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/event` | 0 | äº‹ä»¶ä¸ŠæŠ¥ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/ack` | 1 | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/progress` | 0 | å¼‚步任务进度 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/log` | 0 | ç³»ç»Ÿæ—¥å¿— |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/ctl` | 1 | å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/task` | 1 | å¼‚步任务指令 |
**参数说明:**
| å‚æ•° | è¯´æ˜Ž |
|------|------|
| v1 | åè®®ç‰ˆæœ¬å· |
| device_id | è®¾å¤‡å”¯ä¸€ ID,不可修改 |
| direct | `rpt`(终端上报)/ `cmd`(云端下发) |
| msg_type | `state` / `event` / `ack` / `progress` / `log` / `ctl` / `task` |
---
### 3.2 æ¶ˆæ¯é€šç”¨æ ¼å¼
#### äº‘端下发(cmd/ctl、cmd/task)
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": 1718365200,
    "ttl": 3600
  },
  "action": "ACTION_TYPE",
  "payload": {}
}
```
| header å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|-------------|------|------|
| version | string | åè®®è¯­ä¹‰ç‰ˆæœ¬ï¼Œå›ºå®š `"1.0"` |
| device_id | string | ç›®æ ‡è®¾å¤‡ ID |
| req_id | string | æŒ‡ä»¤å”¯ä¸€ ID(UUID),终端 ack æ—¶é¡»å›žä¼  |
| timestamp | int | Unix æ—¶é—´æˆ³ |
| ttl | int | æŒ‡ä»¤æœ‰æ•ˆæœŸï¼ˆç§’) |
#### ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt/*)
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",
  "payload": {}
}
```
---
### 3.3 ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt)
#### rpt/state â€” å¿ƒè·³/状态
**Topic:** `snlp/v1/{device_id}/rpt/state`
**发布示例(终端 â†’ äº‘):**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",
  "payload": {
    "cpu": 15,
    "mem": 45,
    "battery": 80,
    "net": "wifi",
    "mode": "idle"
  }
}
```
**服务端行为:**
- æ›´æ–°è®¾å¤‡åœ¨çº¿çŠ¶æ€
- è‹¥è®¾å¤‡æœªåˆå§‹åŒ–且 MQTT å·²è¿žæŽ¥ â†’ è‡ªåŠ¨è§¦å‘å®Œæ•´åˆå§‹åŒ–æµç¨‹
**mode å¸¸è§å€¼ï¼š** `idle` / `dialogue` / `translate` / `study` / `sleep`
---
#### rpt/event â€” äº‹ä»¶ä¸ŠæŠ¥
**Topic:** `snlp/v1/{device_id}/rpt/event`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365210
  },
  "msg_type": "event",
  "payload": {
    "event": "asr_result",
    "text": "Good morning",
    "confidence": 0.95
  }
}
```
**常见 event ç±»åž‹ï¼š**
| event | è¯´æ˜Ž |
|-------|------|
| wake | å”¤é†’触发 |
| sleep | ä¼‘眠触发 |
| asr_result | ASR è¯†åˆ«ç»“æžœ |
| alarm | å¼‚常报警 |
---
#### rpt/ack â€” æŒ‡ä»¤å›žæ‰§
**Topic:** `snlp/v1/{device_id}/rpt/ack`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365215
  },
  "msg_type": "ack",
  "payload": {
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "success",
    "error_code": 0
  }
}
```
| payload å­—段 | è¯´æ˜Ž |
|--------------|------|
| req_id | å¯¹åº”下发指令的 req_id |
| status | `success` / `failed` |
| error_code | 0 è¡¨ç¤ºæˆåŠŸï¼Œéž 0 ä¸ºé”™è¯¯ç  |
---
#### rpt/progress â€” ä»»åŠ¡è¿›åº¦
**Topic:** `snlp/v1/{device_id}/rpt/progress`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365220
  },
  "msg_type": "progress",
  "payload": {
    "task_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "downloading",
    "progress": 45,
    "info": "Downloading courses/course01/chapter01/resources/001.mp3"
  }
}
```
---
#### rpt/log â€” ç³»ç»Ÿæ—¥å¿—
**Topic:** `snlp/v1/{device_id}/rpt/log`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365230
  },
  "msg_type": "log",
  "payload": {
    "level": "error",
    "module": "audio",
    "message": "Microphone init failed"
  }
}
```
---
### 3.4 äº‘端下发(cmd)
#### cmd/ctl â€” å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤
**Topic:** `snlp/v1/{device_id}/cmd/ctl`
**Action åˆ—表:**
| Action | payload ç¤ºä¾‹ | è¯´æ˜Ž |
|--------|-------------|------|
| SET_VOLUME | `{"level": 50}` | è®¾ç½®éŸ³é‡ 0–100 |
| SET_POWER | `{"state": "wake"}` | å”¤é†’ / `sleep` ä¼‘眠 |
| SET_CLOCK | `{"ts": 1718365200}` | åŒæ­¥ Unix æ—¶é—´æˆ³ |
| PLAY_MEDIA | `{"path": "courses/course01/chapter01/resources/001.mp3", "volume": 50, "loop": false}` | æ’­æ”¾æœ¬åœ°åª’体 |
| STOP_MEDIA | `{}` | åœæ­¢æ’­æ”¾ |
| SET_ALARM | `{"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}` | è®¾ç½®é—¹é’Ÿ |
| UNSET_ALARM | `{"id": "alarm_01"}` | åˆ é™¤é—¹é’Ÿ |
| REBOOT | `{"delay": 5}` | å»¶æ—¶é‡å¯ |
**SET_CLOCK ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_CLOCK",
  "payload": {
    "ts": 1718365200
  }
}
```
**SET_VOLUME ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "34dc4157-9e52-4a99-bdad-b3daf665df11",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": {
    "level": 50
  }
}
```
**SET_POWER ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_POWER",
  "payload": {
    "state": "wake"
  }
}
```
---
#### cmd/task â€” å¼‚步任务指令
**Topic:** `snlp/v1/{device_id}/cmd/task`
**当前实现使用的 Action:**
| Action | è¯´æ˜Ž |
|--------|------|
| SYNC_COURSE_TASK | ä¸‹å‘完整课程资源包(courseTask.json æ ¼å¼ï¼‰ï¼Œç»ˆç«¯æŒ‰ assets æ‰¹é‡ä¸‹è½½ |
**SYNC_COURSE_TASK ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "e1c1de2a-3067-417d-b533-67c9a9827a8e",
    "timestamp": 1718365201,
    "ttl": 7200
  },
  "action": "SYNC_COURSE_TASK",
  "payload": {
    "version": "1.0",
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
    "assets": [
      {
        "url": "http://your-server:2101/static/courses/course01/chapter01/resources/001.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹001",
        "target_path": "courses/course01/chapter01/resources/001.mp3",
        "cover_url": "http://your-server:2101/static/courses/course01/chapter01/resources/001.png",
        "cover_target_path": "courses/course01/chapter01/resources/001.png"
      },
      {
        "url": "http://your-server:2101/static/courses/course01/chapter01/resources/002.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹002",
        "target_path": "courses/course01/chapter01/resources/002.mp3",
        "cover_url": "http://your-server:2101/static/courses/course01/chapter01/resources/002.png",
        "cover_target_path": "courses/course01/chapter01/resources/002.png"
      }
    ]
  }
}
```
**assets å­—段说明:**
| å­—段 | è¯´æ˜Ž |
|------|------|
| url | èµ„源完整 HTTP ä¸‹è½½åœ°å€ï¼ˆæœåŠ¡ç«¯è‡ªåŠ¨è¡¥å…¨ï¼‰ |
| md5 | æ–‡ä»¶æ ¡éªŒå€¼ |
| title | èµ„源标题 |
| target_path | ç»ˆç«¯å­˜å‚¨ç›¸å¯¹è·¯å¾„(与服务端目录结构一致) |
| cover_url | å°é¢å®Œæ•´ HTTP ä¸‹è½½åœ°å€ |
| cover_target_path | å°é¢ç»ˆç«¯å­˜å‚¨ç›¸å¯¹è·¯å¾„ |
**终端处理逻辑:**
1. æ”¶åˆ° `SYNC_COURSE_TASK` åŽè§£æž `assets` åˆ—表
2. æŒ‰æ¯é¡¹çš„ `url` ä¸‹è½½æ–‡ä»¶ï¼Œä¿å­˜åˆ°å­˜å‚¨å¡ `target_path` è·¯å¾„
3. æŒ‰ `cover_url` ä¸‹è½½å°é¢ï¼Œä¿å­˜åˆ° `cover_target_path`
4. ä¸‹è½½è¿‡ç¨‹ä¸­é€šè¿‡ `rpt/progress` ä¸ŠæŠ¥è¿›åº¦
5. å®ŒæˆåŽé€šè¿‡ `rpt/ack` å›žä¼  `req_id` ä¸Žæ‰§è¡Œç»“æžœ
**PRD é¢„ç•™ Action(可通过 HTTP `/devices/{id}/task` æ‰‹åŠ¨ä¸‹å‘ï¼‰ï¼š**
| Action | payload ç¤ºä¾‹ | è¯´æ˜Ž |
|--------|-------------|------|
| DOWNLOAD | `{"task_id":"uuid","url":"...","md5":"...","target_path":"..."}` | å•文件下载 |
| UPDATE_MANIFEST | `{"course_id":"...","assets":[...]}` | æ›´æ–°èµ„源清单 |
| OTA_UPDATE | `{"url":"...","version":"1.0.2"}` | å›ºä»¶å‡çº§ |
| DELETE_FILE | `{"path":"..."}` | åˆ é™¤æ–‡ä»¶ |
---
### 3.5 æœåŠ¡ç«¯è‡ªåŠ¨ä¸‹å‘æµç¨‹
设备首次上报 `rpt/state` æ—¶ï¼ŒæœåŠ¡ç«¯è‡ªåŠ¨æ‰§è¡Œï¼š
```
1. cmd/ctl  SET_CLOCK   {"ts": <当前时间戳>}
2. cmd/ctl  SET_VOLUME  {"level": 50}
3. cmd/ctl  SET_POWER   {"state": "wake"}
4. cmd/task SYNC_COURSE_TASK  {course01 å®Œæ•´ payload}
5. cmd/task SYNC_COURSE_TASK  {course02 å®Œæ•´ payload}
   ... index.json ä¸­æ¯é—¨åŸºç¡€è¯¾ç¨‹å„ 1 æ¡
```
**手动 MQTT å‘布示例(mosquitto_pub):**
```bash
mosquitto_pub \
  -h your-mqtt-broker -p 2100 \
  -u your-username -P your-password \
  -t "snlp/v1/SN-88293021/cmd/task" \
  -q 1 \
  -m '{"header":{"version":"1.0","device_id":"SN-88293021","req_id":"manual-uuid","timestamp":1718365200,"ttl":7200},"action":"SYNC_COURSE_TASK","payload":{"version":"1.0","course_id":"course01","course_name":"小智学习课程01","assets":[]}}'
```
---
## å››ã€æŽ¥å£é€ŸæŸ¥è¡¨
### HTTP æŽ¥å£
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | `/health` | å¥åº·æ£€æŸ¥ |
| GET | `/static/{path}` | é™æ€èµ„源下载 |
| POST | `/api/v1/scene/analyze` | åœºæ™¯è¯†åˆ« |
| GET | `/api/v1/dialogue/stream` | SSE å¯¹è¯æµ |
| GET | `/api/v1/courses` | è¯¾ç¨‹ç´¢å¼• |
| GET | `/api/v1/courses/base-manifest` | å…¨éƒ¨åŸºç¡€è¯¾ç¨‹åŒæ­¥åŒ…预览 |
| GET | `/api/v1/courses/{course_id}` | è¯¾ç¨‹è¯¦æƒ… |
| GET | `/api/v1/courses/{course_id}/manifest` | å•课程同步包预览 |
| GET | `/api/v1/courses/{course_id}/tasks` | åŽŸå§‹ courseTask.json |
| GET | `/api/v1/tasklist` | è®¾å¤‡åˆå§‹åŒ–任务清单 |
| POST | `/api/v1/devices/init` | æ‰‹åŠ¨è®¾å¤‡åˆå§‹åŒ– |
| POST | `/api/v1/devices/course-init` | æ‰‹åŠ¨å•è¯¾ç¨‹åŒæ­¥ |
| GET | `/api/v1/devices` | åœ¨çº¿è®¾å¤‡åˆ—表 |
| GET | `/api/v1/devices/initialized` | å·²åˆå§‹åŒ–设备 |
| DELETE | `/api/v1/devices/initialized/{device_id}` | æ¸…除初始化记录 |
| GET | `/api/v1/devices/{device_id}` | è®¾å¤‡çŠ¶æ€ |
| GET | `/api/v1/devices/{device_id}/init-status` | åˆå§‹åŒ–状态 |
| GET | `/api/v1/devices/{device_id}/acks` | æŒ‡ä»¤å›žæ‰§åŽ†å² |
| GET | `/api/v1/devices/{device_id}/events` | äº‹ä»¶/进度历史 |
| POST | `/api/v1/devices/{device_id}/cmd` | æ‰‹åŠ¨ä¸‹å‘ ctl æŒ‡ä»¤ |
| POST | `/api/v1/devices/{device_id}/task` | æ‰‹åŠ¨ä¸‹å‘ task æŒ‡ä»¤ |
### MQTT æŽ¥å£
| æ–¹å‘ | Topic | å…³é”® Action / ç”¨é€” |
|------|-------|-------------------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/state` | å¿ƒè·³ï¼Œè§¦å‘自动初始化 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/event` | ASR ç»“果、唤醒/休眠事件 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/ack` | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/progress` | ä¸‹è½½/OTA è¿›åº¦ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/log` | è¿œç¨‹æŽ’障日志 |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{id}/cmd/ctl` | SET_CLOCK / SET_VOLUME / SET_POWER / PLAY_MEDIA ç­‰ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{id}/cmd/task` | SYNC_COURSE_TASK(课程资源包同步) |
---
*文档版本:1.0 | å¯¹åº”服务端版本:1.0.0*
smartlearning/docs/mqtt_interface.md
New file
@@ -0,0 +1,429 @@
# æŽ¥å£æ–‡æ¡£
> äº¤äº’式 API æ–‡æ¡£ï¼šå¯åŠ¨æœåŠ¡åŽè®¿é—® `/docs`
> åè®®ç‰ˆæœ¬ï¼š/ MQTT snlp/v1
---
## ç›®å½•
- [一、MQTT æŽ¥å£](#一mqtt-接口)
  - [1.1 Topic è§„则](#11-topic-规则)
  - [1.2 æ¶ˆæ¯é€šç”¨æ ¼å¼](#12-消息通用格式)
  - [1.3 ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt)](#13-终端上报rpt)
  - [1.4 äº‘端下发(cmd)](#14-云端下发cmd)
  - [1.5 æœåŠ¡ç«¯è‡ªåŠ¨ä¸‹å‘æµç¨‹](#15-服务端自动下发流程)
- [二、接口速查表](#二接口速查表)
---
## ä¸€ã€MQTT æŽ¥å£
### 1.1 Topic è§„则
```
snlp/v1/{device_id}/{direct}/{msg_type}
```
| æ–¹å‘ | Topic | QoS | è¯´æ˜Ž |
|------|-------|-----|------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/state` | 0 | å¿ƒè·³/状态上报 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/event` | 0 | äº‹ä»¶ä¸ŠæŠ¥ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/ack` | 1 | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/progress` | 0 | å¼‚步任务进度 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{device_id}/rpt/log` | 0 | ç³»ç»Ÿæ—¥å¿— |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/ctl` | 1 | å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{device_id}/cmd/task` | 1 | å¼‚步任务指令 |
**参数说明:**
| å‚æ•° | è¯´æ˜Ž |
|------|------|
| v1 | åè®®ç‰ˆæœ¬å· |
| device_id | è®¾å¤‡å”¯ä¸€ ID,不可修改 |
| direct | `rpt`(终端上报)/ `cmd`(云端下发) |
| msg_type | `state` / `event` / `ack` / `progress` / `log` / `ctl` / `task` |
---
### 1.2 æ¶ˆæ¯é€šç”¨æ ¼å¼
#### äº‘端下发(cmd/ctl、cmd/task)
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "timestamp": 1718365200,
    "ttl": 3600
  },
  "action": "ACTION_TYPE",
  "payload": {}
}
```
| header å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|-------------|------|------|
| version | string | åè®®è¯­ä¹‰ç‰ˆæœ¬ï¼Œå›ºå®š `"1.0"` |
| device_id | string | ç›®æ ‡è®¾å¤‡ ID |
| req_id | string | æŒ‡ä»¤å”¯ä¸€ ID(UUID),终端 ack æ—¶é¡»å›žä¼  |
| timestamp | int | Unix æ—¶é—´æˆ³ |
| ttl | int | æŒ‡ä»¤æœ‰æ•ˆæœŸï¼ˆç§’) |
#### ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt/*)
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",
  "payload": {}
}
```
---
### 1.3 ç»ˆç«¯ä¸ŠæŠ¥ï¼ˆrpt)
#### rpt/state â€” å¿ƒè·³/状态
**Topic:** `snlp/v1/{device_id}/rpt/state`
**发布示例(终端 â†’ äº‘):**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",
  "payload": {
    "cpu": 15,
    "mem": 45,
    "battery": 80,
    "net": "wifi",
    "mode": "idle"
  }
}
```
**服务端行为:**
- æ›´æ–°è®¾å¤‡åœ¨çº¿çŠ¶æ€
- è‹¥è®¾å¤‡æœªåˆå§‹åŒ–且 MQTT å·²è¿žæŽ¥ â†’ è‡ªåŠ¨è§¦å‘å®Œæ•´åˆå§‹åŒ–æµç¨‹
**mode å¸¸è§å€¼ï¼š** `idle` / `dialogue` / `translate` / `study` / `sleep`
---
#### rpt/event â€” äº‹ä»¶ä¸ŠæŠ¥
**Topic:** `snlp/v1/{device_id}/rpt/event`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365210
  },
  "msg_type": "event",
  "payload": {
    "event": "asr_result",
    "text": "Good morning",
    "confidence": 0.95
  }
}
```
**常见 event ç±»åž‹ï¼š**
| event | è¯´æ˜Ž |
|-------|------|
| wake | å”¤é†’触发 |
| sleep | ä¼‘眠触发 |
| asr_result | ASR è¯†åˆ«ç»“æžœ |
| alarm | å¼‚常报警 |
---
#### rpt/ack â€” æŒ‡ä»¤å›žæ‰§
**Topic:** `snlp/v1/{device_id}/rpt/ack`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365215
  },
  "msg_type": "ack",
  "payload": {
    "req_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "success",
    "error_code": 0
  }
}
```
| payload å­—段 | è¯´æ˜Ž |
|--------------|------|
| req_id | å¯¹åº”下发指令的 req_id |
| status | `success` / `failed` |
| error_code | 0 è¡¨ç¤ºæˆåŠŸï¼Œéž 0 ä¸ºé”™è¯¯ç  |
---
#### rpt/progress â€” ä»»åŠ¡è¿›åº¦
**Topic:** `snlp/v1/{device_id}/rpt/progress`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365220
  },
  "msg_type": "progress",
  "payload": {
    "task_id": "550e8400-e29b-41d4-a716-446655440000",
    "status": "downloading",
    "progress": 45,
    "info": "Downloading courses/course01/chapter01/resources/001.mp3"
  }
}
```
---
#### rpt/log â€” ç³»ç»Ÿæ—¥å¿—
**Topic:** `snlp/v1/{device_id}/rpt/log`
**发布示例:**
```json
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365230
  },
  "msg_type": "log",
  "payload": {
    "level": "error",
    "module": "audio",
    "message": "Microphone init failed"
  }
}
```
---
### 1.4 äº‘端下发(cmd)
#### cmd/ctl â€” å®žæ—¶æŽ§åˆ¶æŒ‡ä»¤
**Topic:** `snlp/v1/{device_id}/cmd/ctl`
**Action åˆ—表:**
| Action | payload ç¤ºä¾‹ | è¯´æ˜Ž |
|--------|-------------|------|
| SET_VOLUME | `{"level": 50}` | è®¾ç½®éŸ³é‡ 0–100 |
| SET_POWER | `{"state": "wake"}` | å”¤é†’ / `sleep` ä¼‘眠 |
| SET_CLOCK | `{"ts": 1718365200}` | åŒæ­¥ Unix æ—¶é—´æˆ³ |
| PLAY_MEDIA | `{"path": "courses/course01/chapter01/resources/001.mp3", "volume": 50, "loop": false}` | æ’­æ”¾æœ¬åœ°åª’体 |
| STOP_MEDIA | `{}` | åœæ­¢æ’­æ”¾ |
| SET_ALARM | `{"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}` | è®¾ç½®é—¹é’Ÿ |
| UNSET_ALARM | `{"id": "alarm_01"}` | åˆ é™¤é—¹é’Ÿ |
| REBOOT | `{"delay": 5}` | å»¶æ—¶é‡å¯ |
**SET_CLOCK ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "8f932149-719c-4464-99be-66dfcfa14f29",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_CLOCK",
  "payload": {
    "ts": 1718365200
  }
}
```
**SET_VOLUME ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "34dc4157-9e52-4a99-bdad-b3daf665df11",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_VOLUME",
  "payload": {
    "level": 50
  }
}
```
**SET_POWER ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "timestamp": 1718365200,
    "ttl": 60
  },
  "action": "SET_POWER",
  "payload": {
    "state": "wake"
  }
}
```
---
#### cmd/task â€” å¼‚步任务指令
**Topic:** `snlp/v1/{device_id}/cmd/task`
**当前实现使用的 Action:**
| Action | è¯´æ˜Ž |
|--------|------|
| SYNC_COURSE_TASK | ä¸‹å‘完整课程资源包(courseTask.json æ ¼å¼ï¼‰ï¼Œç»ˆç«¯æŒ‰ assets æ‰¹é‡ä¸‹è½½ |
**SYNC_COURSE_TASK ä¸‹å‘示例:**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "e1c1de2a-3067-417d-b533-67c9a9827a8e",
    "timestamp": 1718365201,
    "ttl": 7200
  },
  "action": "SYNC_COURSE_TASK",
  "payload": {
    "version": "1.0",
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
    "assets": [
      {
        "url": "http://your-server:9500/static/courses/course01/chapter01/resources/001.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹001",
        "target_path": "courses/course01/chapter01/resources/001.mp3",
        "cover_url": "http://your-server:9500/static/courses/course01/chapter01/resources/001.png",
        "cover_target_path": "courses/course01/chapter01/resources/001.png"
      },
      {
        "url": "http://your-server:9500/static/courses/course01/chapter01/resources/002.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹002",
        "target_path": "courses/course01/chapter01/resources/002.mp3",
        "cover_url": "http://your-server:9500/static/courses/course01/chapter01/resources/002.png",
        "cover_target_path": "courses/course01/chapter01/resources/002.png"
      }
    ]
  }
}
```
**assets å­—段说明:**
| å­—段 | è¯´æ˜Ž |
|------|------|
| url | èµ„源完整 HTTP ä¸‹è½½åœ°å€ï¼ˆæœåŠ¡ç«¯è‡ªåŠ¨è¡¥å…¨ï¼‰ |
| md5 | æ–‡ä»¶æ ¡éªŒå€¼ |
| title | èµ„源标题 |
| target_path | ç»ˆç«¯å­˜å‚¨ç›¸å¯¹è·¯å¾„(与服务端目录结构一致) |
| cover_url | å°é¢å®Œæ•´ HTTP ä¸‹è½½åœ°å€ |
| cover_target_path | å°é¢ç»ˆç«¯å­˜å‚¨ç›¸å¯¹è·¯å¾„ |
**终端处理逻辑:**
1. æ”¶åˆ° `SYNC_COURSE_TASK` åŽè§£æž `assets` åˆ—表
2. æŒ‰æ¯é¡¹çš„ `url` ä¸‹è½½æ–‡ä»¶ï¼Œä¿å­˜åˆ°å­˜å‚¨å¡ `target_path` è·¯å¾„
1. æŒ‰ `cover_url` ä¸‹è½½å°é¢ï¼Œä¿å­˜åˆ° `cover_target_path`
4. ä¸‹è½½è¿‡ç¨‹ä¸­é€šè¿‡ `rpt/progress` ä¸ŠæŠ¥è¿›åº¦
5. å®ŒæˆåŽé€šè¿‡ `rpt/ack` å›žä¼  `req_id` ä¸Žæ‰§è¡Œç»“æžœ
**PRD é¢„ç•™ Action(可通过 HTTP `/devices/{id}/task` æ‰‹åŠ¨ä¸‹å‘ï¼‰ï¼š**
| Action | payload ç¤ºä¾‹ | è¯´æ˜Ž |
|--------|-------------|------|
| DOWNLOAD | `{"task_id":"uuid","url":"...","md5":"...","target_path":"..."}` | å•文件下载 |
| UPDATE_MANIFEST | `{"course_id":"...","assets":[...]}` | æ›´æ–°èµ„源清单 |
| OTA_UPDATE | `{"url":"...","version":"1.0.2"}` | å›ºä»¶å‡çº§ |
| DELETE_FILE | `{"path":"..."}` | åˆ é™¤æ–‡ä»¶ |
---
### 1.5 æœåŠ¡ç«¯è‡ªåŠ¨ä¸‹å‘æµç¨‹
设备首次上报 `rpt/state` æ—¶ï¼ŒæœåŠ¡ç«¯è‡ªåŠ¨æ‰§è¡Œï¼š
```
1. cmd/ctl  SET_CLOCK   {"ts": <当前时间戳>}
2. cmd/ctl  SET_VOLUME  {"level": 50}
1. cmd/ctl  SET_POWER   {"state": "wake"}
4. cmd/task SYNC_COURSE_TASK  {course01 å®Œæ•´ payload}
5. cmd/task SYNC_COURSE_TASK  {course02 å®Œæ•´ payload}
   ... index.json ä¸­æ¯é—¨åŸºç¡€è¯¾ç¨‹å„ 1 æ¡
```
**手动 MQTT å‘布示例(mosquitto_pub):**
```bash
mosquitto_pub \
  -h your-mqtt-broker -p 2100 \
  -u your-username -P your-password \
  -t "snlp/v1/SN-88293021/cmd/task" \
  -q 1 \
  -m '{"header":{"version":"1.0","device_id":"SN-88293021","req_id":"manual-uuid","timestamp":1718365200,"ttl":7200},"action":"SYNC_COURSE_TASK","payload":{"version":"1.0","course_id":"course01","course_name":"小智学习课程01","assets":[]}}'
```
---
## äºŒã€æŽ¥å£é€ŸæŸ¥è¡¨
### MQTT æŽ¥å£
| æ–¹å‘ | Topic | å…³é”® Action / ç”¨é€” |
|------|-------|-------------------|
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/state` | å¿ƒè·³ï¼Œè§¦å‘自动初始化 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/event` | ASR ç»“果、唤醒/休眠事件 |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/ack` | æŒ‡ä»¤æ‰§è¡Œå›žæ‰§ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/progress` | ä¸‹è½½/OTA è¿›åº¦ |
| ç»ˆç«¯ â†’ äº‘ | `snlp/v1/{id}/rpt/log` | è¿œç¨‹æŽ’障日志 |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{id}/cmd/ctl` | SET_CLOCK / SET_VOLUME / SET_POWER / PLAY_MEDIA ç­‰ |
| äº‘ â†’ ç»ˆç«¯ | `snlp/v1/{id}/cmd/task` | SYNC_COURSE_TASK(课程资源包同步) |
---
*文档版本:1.0 | å¯¹åº”服务端版本:1.0.0*
smartlearning/docs/mqttÏûÏ¢¹æÔò v2 - ¸±±¾.pdf
Binary files differ
smartlearning/docs/mqttÏûÏ¢¹æÔò.pdf
Binary files differ
smartlearning/docs/mqttÏûÏ¢¹æÔòV1.1.pdf
Binary files differ
smartlearning/docs/mqttÏûÏ¢¹æÔò_V1.0.pdf
Binary files differ
smartlearning/docs/mqttÏûÏ¢¹æÔò_V1.1.xlsx
Binary files differ
smartlearning/docs/requires.txt
New file
@@ -0,0 +1,368 @@
第一部分:产品需求规格说明书(PRD):情境感知智能英语学习系统
1. é¡¹ç›®æ¦‚è¿°
1.1 é¡¹ç›®èƒŒæ™¯
现有英语学习产品多为离散的“题库式”或“课本式”教学,缺乏语境与应用结合。本产品通过引入边缘计算与大模型技术,实现基于物理环境感知的沉浸式英语教学,解决“学而不用”的问题。
1.2 äº§å“ç›®æ ‡
- å®žçŽ°â€œåœºæ™¯æ„ŸçŸ¥ -> æ„å›¾åˆ†æž -> æ™ºèƒ½å¯¹è¯ -> å­¦ä¹ åé¦ˆâ€çš„闭环系统。
- æ‰“造具备 IoT åŸºç¡€æŽ§åˆ¶ã€å¤šåŠŸèƒ½é›†æˆä¸Žé«˜éšç§ä¿æŠ¤çš„å­¦ä¹ ç»ˆç«¯ã€‚
- æž„建云端 AI æ•™å­¦çŸ©é˜µï¼Œæ”¯æŒè‡ªé€‚应难度调整与个性化学习轨迹。
2. ç»ˆç«¯è®¾å¤‡è¯¦ç»†éœ€æ±‚
2.1 äº¤äº’状态机(FSM)
- å¾…机(Idle):实时进行 VAD æ£€æµ‹ï¼Œä½ŽåŠŸè€—è¿è¡Œã€‚
- å¯¹è¯æ¨¡å¼ï¼ˆDialogue):处理全双工语音流,支持打断(Barge-in)。
- ç¿»è¯‘模式(Translate):短时录音,云端或本地模型进行翻译并播放。
- æ•™å­¦æ¨¡å¼ï¼ˆStudy):加载预设课程内容,支持点读、跟读、评分。
- ä¼‘眠(Sleep):仅保留心跳检测与预设唤醒词监听,禁用麦克风阵列。
2.2 æ ¸å¿ƒåŠŸèƒ½æŠ€æœ¯æŒ‡æ ‡
- è¯­éŸ³é‡‡é›†ï¼šæ”¯æŒ 3 ç±³è¿œåœºæ‹¾éŸ³ï¼Œä¿¡å™ªæ¯” (SNR) > 15dB,支持声源定位 (DoA)。
- é™å™ª/回声消除 (AEC):支持 50dB ä»¥ä¸Šå›žå£°æ¶ˆé™¤ï¼Œç¡®ä¿ç»ˆç«¯æ’­æ”¾éŸ³é¢‘时仍可拾取用户语音。
- æœ¬åœ°ç¿»è¯‘:支持离线中英互译(首字延迟 < 300ms)。
- å“åº”速度:从采集到服务端下发响应的 RTT (Round Trip Time) éœ€æŽ§åˆ¶åœ¨ 1.5s ä»¥å†…。
2.3 IoT ä¸Žç³»ç»Ÿé›†æˆ
- ç”µæºç®¡ç†ï¼šæ”¯æŒ RTC å®šæ—¶ï¼Œæ”¯æŒå¼‚常断电自启动,支持电量低时自动进入省电模式。
- ç½‘络连接:自动切换 Wi-Fi/4G/5G,支持断网本地缓存(最大支持 100MB è¯­éŸ³ç‰‡æ®µç¼“存)。
- å®‰å…¨æ€§ï¼šç‰©ç†éº¦å…‹é£Žé™éŸ³å¼€å…³ï¼ˆç¡¬ä»¶çº§æ–­å¼€ï¼‰ï¼Œç«¯ä¾§æ•°æ®åŠ å¯†å­˜å‚¨ (AES-128)。
3. æœåŠ¡ç«¯å¹³å°è¯¦ç»†éœ€æ±‚
3.1 åœºæ™¯è¯†åˆ«å¼•擎 (AI Context Engine)
- åœºæ™¯è§£æžï¼šé€šè¿‡ LLM å¯¹çŽ¯å¢ƒéŸ³è¯­ä¹‰ç†è§£ï¼Œåˆ†ç±»å™¨å®šä¹‰ä¸ºï¼šå±…å®¶(卧室/厨房/书房)、校内、通勤、运动等。
- å¯¹è¯ç”Ÿæˆï¼šé›†æˆ LLM Agent,基于用户画像历史与当前场景,动态生成下一轮提问(支持多轮上下文管理)。
- ç½®ä¿¡åº¦æœºåˆ¶ï¼šè‹¥åœºæ™¯è¯†åˆ«ç½®ä¿¡åº¦ < 0.7,触发通用问候策略,避免干扰用户。
3.2 æ•™å­¦ç®¡æŽ§ç³»ç»Ÿ
- ç”¨æˆ·æ¨¡åž‹ï¼šè®°å½•用户的CEFR等级、词汇库、错误率,通过反馈循环动态调节难度。
- æ•°æ®å­˜å‚¨ï¼šç¬¦åˆ GDPR/PIPL éšç§åˆè§„,敏感语音数据定期脱敏处理,不留存未经许可的原始音频。
4. éžåŠŸèƒ½æ€§éœ€æ±‚ï¼ˆSLA)
- å¯ç”¨æ€§ï¼šç³»ç»Ÿå¯ç”¨æ€§ 99.9%。
- æ€§èƒ½ç“¶é¢ˆï¼šå¤§æ¨¡åž‹æŽ¨ç†æŽ¥å£æ”¯æŒè‡³å°‘ 1000 QPS å¹¶å‘。
- å®‰å…¨åˆè§„:通过加密通道(TLS 1.3)通信;设备绑定具备唯一的安全芯片校验(Secure Element)。
- å¯æ‰©å±•性:支持通过插件机制增加新语种或场景模板。
5. äº¤äº’协议与数据流(API规范)
- è¯·æ±‚接口:/api/v1/scene/analyze (POST)
  - Payload: {timestamp, audio_blob, device_id, context_hints}
- å“åº”接口:/api/v1/dialogue/stream (SSE)
  - Payload: {dialogue_id, text, audio_url, intent_type, difficulty_level}
- æŒ‡ä»¤åŒæ­¥ï¼šé€šè¿‡ MQTT Broker ä¸‹å‘控制指令(如:SetVolume, Sleep, UpdateFirmware)。
6. å…¸åž‹åœºæ™¯é€»è¾‘流程定义
- åœºæ™¯è¯†åˆ«ï¼šæ£€æµ‹åˆ°ç‰¹å®šçŽ¯å¢ƒéŸ³çº¹ -> åˆ¤å®šä¸ºâ€œç”¨é¤åœºæ™¯â€ -> å‘送指令至终端播放引导提示音。
- å›žå¤å¤„理:检测用户回复 -> ASR识别 -> åˆ†æžè¯­æ³•与正确性 -> è¿”回反馈(赞赏或纠错)-> è¿›å…¥ä¸‹ä¸€è½®æé—®ã€‚
第二部分:参照以上需求,初步一套mqtt消息的topic规则如下:
一、消息topic定义:  snlp/v1/{device_id}/{direct}/{msg_type}
表格如下:
方向    Topic è·¯å¾„    msg_type    è¯´æ˜Ž
rpt (终端->云)    snlp/v1/{device_id}/rpt/state    çŠ¶æ€ä¸ŠæŠ¥    åŒ…含心跳、电量、网络、当前工作模式
    snlp/v1/{device_id}/rpt/event    äº‹ä»¶ä¸ŠæŠ¥    å”¤é†’/休眠触发、ASR识别结果、异常报警
    snlp/v1/{device_id}/rpt/log    ç³»ç»Ÿæ—¥å¿—    ç”¨äºŽè¿œç¨‹æŽ’障的系统运行日志
    snlp/v1/{device_id}/rpt/ack    æŒ‡ä»¤å“åº”    å…³é”®ï¼š å¯¹åº” CMD çš„æ‰§è¡Œç»“æžœ (成功/失败/进度)
    snlp/v1/{device_id}/rpt/progress    è¿‡ç¨‹è¿›åº¦    é’ˆå¯¹â€œå¼‚步文件下载”和“OTA升级”
cmd (云->终端)    snlp/v1/{device_id}/cmd/ctl    å®žæ—¶æŒ‡ä»¤    å®žæ—¶æŒ‡ä»¤ï¼ˆéŸ³é‡ã€ä¼‘眠、NTP)
    snlp/v1/{device_id}/cmd/task    å¼‚步任务指令    å¼‚步任务指令(OTA、下载资源包)
v1 : è¡¨ç¤ºç‰ˆæœ¬å·1, ç”¨äºŽç‰ˆæœ¬è¿‡æ¸¡æ—¶çš„兼容性处理
device_id:   æ˜¯è®¾å¤‡çš„唯一id,只读,是不能被修改的  ï¼ˆä»¥åŽå†è€ƒè™‘增加注册机制,换取正式的 Access Token)
direct:  rpt (终端->云) ï¼Œ   cmd (云->终端)
msg_type: state, event,log,ack,progress, task, ctl
二、Payload è®¾è®¡è§„范,为了保持一致性,所有 Payload å‡é‡‡ç”¨ JSON æ ¼å¼ï¼Œå¹¶ç»Ÿä¸€åŒ…含 Header åŒºå—。
1. æœåŠ¡ç«¯ä¸‹å‘æŒ‡ä»¤ï¼š  snlp/v1/{device_id}/cmd/ctl
{
  "header": {
        "version": "1.0",           // åè®®è¯­ä¹‰ç‰ˆæœ¬
        "device_id": "...",         // å¿…须包含
        "req_id": "UUID-...",       // ä»… CMD ç±»æŒ‡ä»¤å¿…é¡»
        "timestamp": 1718365200,    // ç»Ÿä¸€ Unix æ—¶é—´æˆ³
        "ttl": 1001                 // å‘½ä»¤æœ‰æ•ˆæœŸï¼Œå•位为秒
  },
  "action": "ACTION_TYPE",        // å¦‚: PLAY_MEDIA, SYNC_CLOCK, SET_ALARM
  "payload": { ... }              // å…·ä½“操作参数
}
具体的payload示例:
播放音频 (play_media):   {"url": "...", "md5": "...", "volume": 50, "loop": false}
定时闹钟 (set_alarm):   {"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}
下载任务 (download_task):   {"url": "...", "md5": "...", "target_path": "/data/unit1/lesson1.mp3"}
任务进度反馈 (task_progress):   {  "task_id": "UUID-...",   "status": "downloading",   "progress": 45,   "info": "Downloading assets..."  }
2. ç»ˆç«¯ä¸ŠæŠ¥ï¼š ç»ˆç«¯ä¸ŠæŠ¥æ•°æ® (Topic: rpt/state, rpt/event, rpt/ack)
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",            // æˆ– event, ack
  "payload": { ... }
}
具体的payload示例:
心跳/健康 (state):    {"cpu": 15, "mem": 45, "battery": 80, "net": "wifi", "mode": "idle"}
指令响应 (ack - å¿…回):   {  "req_id": "UUID-xxxx-xxxx",   "status": "success",   "error_code": 0 }
事件上报 (event):   {"event": "asr_result", "text": "Good morning", "confidence": 0.95}
三、 action å­—段与具体执行动作的对应关系表
1. æŒ‡ä»¤æ˜ å°„å­—å…¸
指令分类    åŠŸèƒ½æè¿°    Action å­—段    å‚数定义 (Payload ç¤ºä¾‹)
基础控制    è®¾ç½®è®¾å¤‡éŸ³é‡ (0-100)    SET_VOLUME    {"level": 50}
    åˆ‡æ¢ç”µæºçŠ¶æ€    SET_POWER    {"state": "sleep/wake"}
    åŒæ­¥ NTP æ—¶é—´æˆ³    SET_CLOCK    {"ts": 1718365200}
媒体管理    è§¦å‘音频播放    PLAY_MEDIA    {"path": "/data/unit1/lesson1.mp3", "volume": 50, "loop": false}
    åœæ­¢å½“前播放    STOP_MEDIA    {}
文件管理    è§¦å‘异步文件下载    DOWNLOAD    {"url": "...", "md5": "...", "target_path": "/data/unit1/lesson1.mp3"}
    åˆ é™¤æŒ‡å®šè·¯å¾„文件    DELETE_FILE    {"path": "/data/..."}
学习配置    ä¸‹å‘/更新课程资源索引    UPDATE_MANIFEST    {"course_id": "...", "assets": [{"url": "...", "md5": "...", "target_path": "/data/unit1/lesson1.mp3"}]}
    ç®¡ç†å¼€æœºé—¹é’Ÿ    SET_ALARM    {"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}
    åˆ é™¤å¼€æœºé—¹é’Ÿ    UNSET_ALARM    {"id": "alarm_01"}
系统维护    è§¦å‘固件在线升级    OTA_UPDATE    {"url": "...", "version": "1.0.2"}
    å»¶æ—¶é‡å¯è®¾å¤‡    REBOOT    {"delay": 5}
2. è¯¦ç»†æ¶ˆæ¯æµé€»è¾‘表
设备开机/定时    å¿ƒè·³/状态    rpt/state    ä¸éœ€è¦
执行命令    æŒ‡ä»¤å“åº”    rpt/ack    å¿…é¡» (即时反馈)
场景切换/ASR    äº‹ä»¶ä¸ŠæŠ¥    rpt/event    ä¸éœ€è¦
服务端操作    æŽ§åˆ¶æŒ‡ä»¤    cmd/operate    è®¾å¤‡å¤„理后必须回 rpt/ack
四、关于任务列表与文件管理:
下载任务: ä¸‹å‘任务时,Payload åº”包含文件 URL å’Œ MD5。终端下载过程中通过 rpt/ack å®šæ—¶ä¸ŠæŠ¥ progress (0-100)。
任务列表: æœåŠ¡ç«¯å¯ä»¥ä¸‹å‘ä¸€ä¸ªâ€œè¯¾ç¨‹èµ„æºæ¸…å•â€çš„ JSON æ•°ç»„,终端收到后存入本地文件,实现资源的管理与对比。
五、 åè®®å®žæ–½å»ºè®®ï¼ˆChecklist)
1.  QoS è®¾ç½®ç­–略:
    cmd/ctl (实时控制): QoS 1 (至少送达一次)。
    rpt/state (心跳): QoS 0 (不保证送达,减轻并发压力)。
    rpt/ack (指令响应): QoS 1 (必须确保云端知晓任务完成情况)。
2.  å®‰å…¨æ€§è¡¥å……:
    åœ¨ MQTT è¿žæŽ¥é˜¶æ®µé‡‡ç”¨ TLS åŒå‘认证 (MQTTS)。
    Payload å†…部虽然有 timestamp,但在 Broker å±‚建议开启 MQTT Client ID å”¯ä¸€æ€§æ ¡éªŒï¼Œé˜²æ­¢å¤šè®¾å¤‡æŠ¢å  ID。
3.  æ‰©å±•性预留:
    åœ¨ cmd/operate çš„ Action å­—段设计中,预留 USER_DEFINED_xxx æŽ¥å£ï¼Œä»¥ä¾¿åŽç»­å¿«é€Ÿå¢žåŠ å¦‚â€œè¿œç¨‹è°ƒè¯•â€ã€â€œæ—¥å¿—æ‹‰å–â€ç­‰åŠŸèƒ½ï¼Œæ— éœ€ä¿®æ”¹åŽŸæœ‰è§£æžæž¶æž„ã€‚
第三部分:视频文件目录结构和组成
1. æ‰€æœ‰è¯¾ç¨‹çš„æ ¹ç›®å½•在:./data/courses, ./data/coursesindex.json文件内容是已确定发布的课程,结构如:
{
    "courses": [
        {
            "course_id": "course01",
            "config": "courses/course01/course.json"
        },
        {
            "course_id": "course02",
            "config": "courses/course02/course.json"
        }
    ]
}
2. æ¯ä¸ªè¯¾ç¨‹ç›®å½•下有一份course.json,该文件内容是当前目录所对应的课程的视频文件清单,结构如:
{
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "description": "课程01",
    "chapters": [
        {
            "chapter_id": "chapter01",
            "chapter_name": "第一章",
            "videos": [
                {
                    "id": "001",
                    "title": "课程001",
                    "path": "courses/course01/chapter01/resources/001.mp4"
                },
                {
                    "id": "002",
                    "title": "课程002",
                    "path": "courses/course01/chapter01/resources/002.mp4"
                },
                {
                    "id": "003",
                    "title": "课程003",
                    "path": "courses/course01/chapter01/resources/003.mp4"
                }
            ]
        }
    ]
}
#TODO:
根据以上需求描述和mqtt规范定义,请完成以下工作:
1. å®Œæˆserver端的基础功能开发;
2. å®šä¹‰ä¸€å¥—基础课程,存放在本地目录(后期可以人工根据这个基础的版本进行课程增设);
3. å®šä¹‰tasklist.json(制定一套基础的任务清单,用于下发到终端设备进行初始化),保证客户端可以自动初始化的功能;
4. å®šä¹‰courseTask.json(制定某一门课程任务清单,用于下发到终端设备进行课程初始化),使客户端和服务端可以有一套自动机制,
    å®¢æˆ·ç«¯å’ŒæœåŠ¡ç«¯å¯ä»¥è‡ªåŠ¨åŒ–çš„é…åˆå®Œæˆå¸¸è§„çš„åŠŸèƒ½å’Œæ­£å¸¸çš„å·¥ä½œæµç¨‹ï¼›
以下是不改代码情况下的**手动更新课程**操作说明,分三种方式。
---
## æ–¹å¼ä¸€ï¼šHTTP API è§¦å‘(推荐,最简单)
服务端会通过 MQTT è‡ªåŠ¨å‘ç»ˆç«¯ä¸‹å‘ `SYNC_COURSE_TASK`。
### 1. æ›´æ–°å•门课程
```bash
curl -X POST http://你的服务器:9500/api/v1/devices/course-init \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "SN-88293021",
    "course_id": "course01"
  }'
```
终端会收到 **1 æ¡** `cmd/task` æ¶ˆæ¯ï¼Œå†…容为 `course01` çš„完整 `courseTask.json`(含 `assets` åˆ—表)。
### 2. é‡æ–°åŒæ­¥å…¨éƒ¨åŸºç¡€è¯¾ç¨‹ï¼ˆindex.json ä¸­æ‰€æœ‰è¯¾ï¼‰
```bash
# ç¬¬ä¸€æ­¥ï¼šæ¸…除该设备的初始化记录
curl -X DELETE http://你的服务器:9500/api/v1/devices/initialized/SN-88293021
# ç¬¬äºŒæ­¥ï¼šå¼ºåˆ¶é‡æ–°åˆå§‹åŒ–(含全部基础课程同步)
curl -X POST http://你的服务器:9500/api/v1/devices/init \
  -H "Content-Type: application/json" \
  -d '{
    "device_id": "SN-88293021",
    "force": true
  }'
```
会对 `index.json` é‡Œæ¯é—¨è¯¾å„下发 **1 æ¡** `SYNC_COURSE_TASK`(当前为 course01、course02 å…± 2 æ¡ï¼‰ã€‚
---
## æ–¹å¼äºŒï¼šç›´æŽ¥å‘ MQTT æ¶ˆæ¯
用 MQTT å®¢æˆ·ç«¯ï¼ˆMQTTX、mosquitto_pub ç­‰ï¼‰è¿žæŽ¥ Broker,向终端发布消息。
**Topic:**
```
snlp/v1/{device_id}/cmd/task
```
**示例(course01):**
```json
{
  "header": {
    "version": "1.0",
    "device_id": "SN-88293021",
    "req_id": "手动填写-uuid",
    "timestamp": 1718365200,
    "ttl": 7200
  },
  "action": "SYNC_COURSE_TASK",
  "payload": {
    "version": "1.0",
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "description": "课程01终端初始化任务:下载章节视频并更新本地资源清单",
    "assets": [
      {
        "url": "http://你的服务器:9500/static/courses/course01/chapter01/resources/001.mp3",
        "md5": "...",
        "title": "第一章 è¯¾ç¨‹001",
        "target_path": "courses/course01/chapter01/resources/001.mp3",
        "cover_url": "http://你的服务器:9500/static/courses/course01/chapter01/resources/001.png",
        "cover_target_path": "courses/course01/chapter01/resources/001.png"
      }
    ]
  }
}
```
**注意:**
- `url` / `cover_url` å¿…须是终端能访问的**完整 HTTP åœ°å€**(不能用 `localhost`)
- `target_path` / `cover_target_path` ä¿æŒç›¸å¯¹è·¯å¾„,终端按此路径存到存储卡
- å¯å…ˆè°ƒ API æ‹¿åˆ°å®Œæ•´ payload,再原样发到 MQTT:
```bash
# èŽ·å–å·²è¡¥å…¨ URL çš„ courseTask å†…容
curl http://你的服务器:9500/api/v1/courses/course01/manifest
# èŽ·å–å…¨éƒ¨åŸºç¡€è¯¾ç¨‹
curl http://你的服务器:9500/api/v1/courses/base-manifest
```
把返回的 JSON ä½œä¸º MQTT æ¶ˆæ¯çš„ `payload` å­—段即可。
**mosquitto_pub ç¤ºä¾‹ï¼š**
```bash
mosquitto_pub -h ä½ çš„MQTT地址 -p 2100 \
  -u ç”¨æˆ·å -P å¯†ç  \
  -t "snlp/v1/SN-88293021/cmd/task" \
  -m '{"header":{...},"action":"SYNC_COURSE_TASK","payload":{...}}'
```
---
## æ–¹å¼ä¸‰ï¼šç»ˆç«¯ç›´æŽ¥é€šè¿‡ URL æ‹‰å–(不经过 MQTT è§¦å‘)
若终端支持自行拉取,可直接访问服务端静态资源:
| èµ„源 | URL |
|------|-----|
| è¯¾ç¨‹ä»»åŠ¡åŒ… | `http://服务器:9500/static/courses/course01/courseTask.json` |
| è¯¾ç¨‹é…ç½® | `http://服务器:9500/static/courses/course01/course.json` |
| è¯¾ç¨‹ç´¢å¼• | `http://服务器:9500/static/courses/index.json` |
| éŸ³é¢‘文件 | `http://服务器:9500/static/courses/course01/chapter01/resources/001.mp3` |
| å°é¢å›¾ | `http://服务器:9500/static/courses/course01/chapter01/resources/001.png` |
**手动更新流程:**
1. åœ¨æœåŠ¡ç«¯æ›´æ–° `data/courses/` ä¸‹çš„ `courseTask.json` å’Œèµ„源文件
2. ç»ˆç«¯ä¸‹è½½æœ€æ–°çš„ `courseTask.json`
3. æŒ‰ `assets` é‡Œæ¯ä¸€é¡¹çš„ `url` ä¸‹è½½æ–‡ä»¶ï¼Œä¿å­˜åˆ° `target_path` å¯¹åº”路径
本地 `courseTask.json` é‡Œæ˜¯ç›¸å¯¹è·¯å¾„,终端需自行拼成:
```
下载地址 = http://服务器:9500/static/ + assets[].url
保存路径 = å­˜å‚¨å¡æ ¹ç›®å½• + assets[].target_path
```
---
## æ–¹å¼å¯¹æ¯”
| æ–¹å¼ | é€‚用场景 | æ˜¯å¦éœ€è¦ MQTT |
|------|----------|---------------|
| `POST /devices/course-init` | æ›´æ–°å•门课 | æ˜¯ï¼ˆæœåŠ¡ç«¯ä»£å‘ï¼‰ |
| `POST /devices/init?force=true` | å…¨é‡é‡åŒæ­¥ | æ˜¯ |
| ç›´æŽ¥å‘ MQTT `SYNC_COURSE_TASK` | è°ƒè¯•、第三方系统对接 | æ˜¯ |
| ç»ˆç«¯ç›´æŽ¥ HTTP ä¸‹è½½ | ç»ˆç«¯è‡ªå¸¦æ‹‰å–逻辑 | å¦ |
---
## æ›´æ–°æœåŠ¡ç«¯è¯¾ç¨‹èµ„æºåŽçš„å»ºè®®æ­¥éª¤
1. ä¿®æ”¹ `data/courses/course01/courseTask.json`(及音频、封面文件)
2. ç¡®è®¤æµè§ˆå™¨èƒ½è®¿é—®ï¼š
   `http://服务器:9500/static/courses/course01/courseTask.json`
3. é€‰æ‰‹åŠ¨è§¦å‘æ–¹å¼ï¼š
   - åªæ›´æ–°ä¸€é—¨è¯¾ â†’ `course-init`
   - æ›´æ–°å…¨éƒ¨åŸºç¡€è¯¾ â†’ `force: true` é‡æ–° init,或逐门课调 `course-init`
4. ç»ˆç«¯æ‰§è¡Œå®ŒåŽé€šè¿‡ `rpt/ack` / `rpt/progress` ä¸ŠæŠ¥ç»“果(若终端已实现)
smartlearning/docs/smartlearing_prd_v1.01.txt
New file
@@ -0,0 +1,201 @@
第一部分:产品需求规格说明书(PRD):情境感知智能英语学习系统
1. é¡¹ç›®æ¦‚è¿°
1.1 é¡¹ç›®èƒŒæ™¯
现有英语学习产品多为离散的“题库式”或“课本式”教学,缺乏语境与应用结合。本产品通过引入边缘计算与大模型技术,实现基于物理环境感知的沉浸式英语教学,解决“学而不用”的问题。
1.2 äº§å“ç›®æ ‡
- å®žçŽ°â€œåœºæ™¯æ„ŸçŸ¥ -> æ„å›¾åˆ†æž -> æ™ºèƒ½å¯¹è¯ -> å­¦ä¹ åé¦ˆâ€çš„闭环系统。
- æ‰“造具备 IoT åŸºç¡€æŽ§åˆ¶ã€å¤šåŠŸèƒ½é›†æˆä¸Žé«˜éšç§ä¿æŠ¤çš„å­¦ä¹ ç»ˆç«¯ã€‚
- æž„建云端 AI æ•™å­¦çŸ©é˜µï¼Œæ”¯æŒè‡ªé€‚应难度调整与个性化学习轨迹。
2. ç»ˆç«¯è®¾å¤‡è¯¦ç»†éœ€æ±‚
2.1 äº¤äº’状态机(FSM)
- å¾…机(Idle):实时进行 VAD æ£€æµ‹ï¼Œä½ŽåŠŸè€—è¿è¡Œã€‚
- å¯¹è¯æ¨¡å¼ï¼ˆDialogue):处理全双工语音流,支持打断(Barge-in)。
- ç¿»è¯‘模式(Translate):短时录音,云端或本地模型进行翻译并播放。
- æ•™å­¦æ¨¡å¼ï¼ˆStudy):加载预设课程内容,支持点读、跟读、评分。
- ä¼‘眠(Sleep):仅保留心跳检测与预设唤醒词监听,禁用麦克风阵列。
2.2 æ ¸å¿ƒåŠŸèƒ½æŠ€æœ¯æŒ‡æ ‡
- è¯­éŸ³é‡‡é›†ï¼šæ”¯æŒ 3 ç±³è¿œåœºæ‹¾éŸ³ï¼Œä¿¡å™ªæ¯” (SNR) > 15dB,支持声源定位 (DoA)。
- é™å™ª/回声消除 (AEC):支持 50dB ä»¥ä¸Šå›žå£°æ¶ˆé™¤ï¼Œç¡®ä¿ç»ˆç«¯æ’­æ”¾éŸ³é¢‘时仍可拾取用户语音。
- æœ¬åœ°ç¿»è¯‘:支持离线中英互译(首字延迟 < 300ms)。
- å“åº”速度:从采集到服务端下发响应的 RTT (Round Trip Time) éœ€æŽ§åˆ¶åœ¨ 1.5s ä»¥å†…。
2.3 IoT ä¸Žç³»ç»Ÿé›†æˆ
- ç”µæºç®¡ç†ï¼šæ”¯æŒ RTC å®šæ—¶ï¼Œæ”¯æŒå¼‚常断电自启动,支持电量低时自动进入省电模式。
- ç½‘络连接:自动切换 Wi-Fi/4G/5G,支持断网本地缓存(最大支持 100MB è¯­éŸ³ç‰‡æ®µç¼“存)。
- å®‰å…¨æ€§ï¼šç‰©ç†éº¦å…‹é£Žé™éŸ³å¼€å…³ï¼ˆç¡¬ä»¶çº§æ–­å¼€ï¼‰ï¼Œç«¯ä¾§æ•°æ®åŠ å¯†å­˜å‚¨ (AES-128)。
3. æœåŠ¡ç«¯å¹³å°è¯¦ç»†éœ€æ±‚
3.1 åœºæ™¯è¯†åˆ«å¼•擎 (AI Context Engine)
- åœºæ™¯è§£æžï¼šé€šè¿‡ LLM å¯¹çŽ¯å¢ƒéŸ³è¯­ä¹‰ç†è§£ï¼Œåˆ†ç±»å™¨å®šä¹‰ä¸ºï¼šå±…å®¶(卧室/厨房/书房)、校内、通勤、运动等。
- å¯¹è¯ç”Ÿæˆï¼šé›†æˆ LLM Agent,基于用户画像历史与当前场景,动态生成下一轮提问(支持多轮上下文管理)。
- ç½®ä¿¡åº¦æœºåˆ¶ï¼šè‹¥åœºæ™¯è¯†åˆ«ç½®ä¿¡åº¦ < 0.7,触发通用问候策略,避免干扰用户。
3.2 æ•™å­¦ç®¡æŽ§ç³»ç»Ÿ
- ç”¨æˆ·æ¨¡åž‹ï¼šè®°å½•用户的CEFR等级、词汇库、错误率,通过反馈循环动态调节难度。
- æ•°æ®å­˜å‚¨ï¼šç¬¦åˆ GDPR/PIPL éšç§åˆè§„,敏感语音数据定期脱敏处理,不留存未经许可的原始音频。
4. éžåŠŸèƒ½æ€§éœ€æ±‚ï¼ˆSLA)
- å¯ç”¨æ€§ï¼šç³»ç»Ÿå¯ç”¨æ€§ 99.9%。
- æ€§èƒ½ç“¶é¢ˆï¼šå¤§æ¨¡åž‹æŽ¨ç†æŽ¥å£æ”¯æŒè‡³å°‘ 1000 QPS å¹¶å‘。
- å®‰å…¨åˆè§„:通过加密通道(TLS 1.3)通信;设备绑定具备唯一的安全芯片校验(Secure Element)。
- å¯æ‰©å±•性:支持通过插件机制增加新语种或场景模板。
5. äº¤äº’协议与数据流(API规范)
- è¯·æ±‚接口:/api/v1/scene/analyze (POST)
  - Payload: {timestamp, audio_blob, device_id, context_hints}
- å“åº”接口:/api/v1/dialogue/stream (SSE)
  - Payload: {dialogue_id, text, audio_url, intent_type, difficulty_level}
- æŒ‡ä»¤åŒæ­¥ï¼šé€šè¿‡ MQTT Broker ä¸‹å‘控制指令(如:SetVolume, Sleep, UpdateFirmware)。
6. å…¸åž‹åœºæ™¯é€»è¾‘流程定义
- åœºæ™¯è¯†åˆ«ï¼šæ£€æµ‹åˆ°ç‰¹å®šçŽ¯å¢ƒéŸ³çº¹ -> åˆ¤å®šä¸ºâ€œç”¨é¤åœºæ™¯â€ -> å‘送指令至终端播放引导提示音。
- å›žå¤å¤„理:检测用户回复 -> ASR识别 -> åˆ†æžè¯­æ³•与正确性 -> è¿”回反馈(赞赏或纠错)-> è¿›å…¥ä¸‹ä¸€è½®æé—®ã€‚
第二部分:参照以上需求,初步一套mqtt消息的topic规则如下:
一、消息topic定义:  snlp/v1/{device_id}/{direct}/{msg_type}
表格如下:
方向    Topic è·¯å¾„    msg_type    è¯´æ˜Ž
rpt (终端->云)    snlp/v1/{device_id}/rpt/state    çŠ¶æ€ä¸ŠæŠ¥    åŒ…含心跳、电量、网络、当前工作模式
    snlp/v1/{device_id}/rpt/event    äº‹ä»¶ä¸ŠæŠ¥    å”¤é†’/休眠触发、ASR识别结果、异常报警
    snlp/v1/{device_id}/rpt/log    ç³»ç»Ÿæ—¥å¿—    ç”¨äºŽè¿œç¨‹æŽ’障的系统运行日志
    snlp/v1/{device_id}/rpt/ack    æŒ‡ä»¤å“åº”    å…³é”®ï¼š å¯¹åº” CMD çš„æ‰§è¡Œç»“æžœ (成功/失败/进度)
    snlp/v1/{device_id}/rpt/progress    è¿‡ç¨‹è¿›åº¦    é’ˆå¯¹â€œå¼‚步文件下载”和“OTA升级”
cmd (云->终端)    snlp/v1/{device_id}/cmd/ctl    å®žæ—¶æŒ‡ä»¤    å®žæ—¶æŒ‡ä»¤ï¼ˆéŸ³é‡ã€ä¼‘眠、NTP)
    snlp/v1/{device_id}/cmd/task    å¼‚步任务指令    å¼‚步任务指令(OTA、下载资源包)
v1 : è¡¨ç¤ºç‰ˆæœ¬å·1, ç”¨äºŽç‰ˆæœ¬è¿‡æ¸¡æ—¶çš„兼容性处理
device_id:   æ˜¯è®¾å¤‡çš„唯一id,只读,是不能被修改的  ï¼ˆä»¥åŽå†è€ƒè™‘增加注册机制,换取正式的 Access Token)
direct:  rpt (终端->云) ï¼Œ   cmd (云->终端)
msg_type: state, event,log,ack,progress, task, ctl
二、Payload è®¾è®¡è§„范,为了保持一致性,所有 Payload å‡é‡‡ç”¨ JSON æ ¼å¼ï¼Œå¹¶ç»Ÿä¸€åŒ…含 Header åŒºå—。
1. æœåŠ¡ç«¯ä¸‹å‘æŒ‡ä»¤ï¼š  snlp/v1/{device_id}/cmd/ctl
{
  "header": {
        "version": "1.0",           // åè®®è¯­ä¹‰ç‰ˆæœ¬
        "device_id": "...",         // å¿…须包含
        "req_id": "UUID-...",       // ä»… CMD ç±»æŒ‡ä»¤å¿…é¡»
        "timestamp": 1718365200,    // ç»Ÿä¸€ Unix æ—¶é—´æˆ³
        "ttl": 1001                 // å‘½ä»¤æœ‰æ•ˆæœŸï¼Œå•位为秒
  },
  "action": "ACTION_TYPE",        // å¦‚: PLAY_MEDIA, SYNC_CLOCK, SET_ALARM
  "payload": { ... }              // å…·ä½“操作参数
}
具体的payload示例:
播放音频 (play_media):   {"url": "...", "md5": "...", "volume": 50, "loop": false}
定时闹钟 (set_alarm):   {"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}
下载任务 (download_task):   {"url": "...", "md5": "...", "target_path": "/data/unit1/lesson1.mp3"}
任务进度反馈 (task_progress):   {  "task_id": "UUID-...",   "status": "downloading",   "progress": 45,   "info": "Downloading assets..."  }
2. ç»ˆç«¯ä¸ŠæŠ¥ï¼š ç»ˆç«¯ä¸ŠæŠ¥æ•°æ® (Topic: rpt/state, rpt/event, rpt/ack)
{
  "header": {
    "device_id": "SN-88293021",
    "timestamp": 1718365205
  },
  "msg_type": "state",            // æˆ– event, ack
  "payload": { ... }
}
具体的payload示例:
心跳/健康 (state):    {"cpu": 15, "mem": 45, "battery": 80, "net": "wifi", "mode": "idle"}
指令响应 (ack - å¿…回):   {  "req_id": "UUID-xxxx-xxxx",   "status": "success",   "error_code": 0 }
事件上报 (event):   {"event": "asr_result", "text": "Good morning", "confidence": 0.95}
三、 action å­—段与具体执行动作的对应关系表
1. æŒ‡ä»¤æ˜ å°„å­—å…¸
指令分类    åŠŸèƒ½æè¿°    Action å­—段    å‚数定义 (Payload ç¤ºä¾‹)
基础控制    è®¾ç½®è®¾å¤‡éŸ³é‡ (0-100)    SET_VOLUME    {"level": 50}
    åˆ‡æ¢ç”µæºçŠ¶æ€    SET_POWER    {"state": "sleep/wake"}
    åŒæ­¥ NTP æ—¶é—´æˆ³    SET_CLOCK    {"ts": 1718365200}
媒体管理    è§¦å‘音频播放    PLAY_MEDIA    {"path": "/data/unit1/lesson1.mp3", "volume": 50, "loop": false}
    åœæ­¢å½“前播放    STOP_MEDIA    {}
文件管理    è§¦å‘异步文件下载    DOWNLOAD    {"url": "...", "md5": "...", "target_path": "/data/unit1/lesson1.mp3"}
    åˆ é™¤æŒ‡å®šè·¯å¾„文件    DELETE_FILE    {"path": "/data/..."}
学习配置    ä¸‹å‘/更新课程资源索引    UPDATE_MANIFEST    {"course_id": "...", "assets": [{"url": "...", "md5": "...", "target_path": "/data/unit1/lesson1.mp3"}]}
    ç®¡ç†å¼€æœºé—¹é’Ÿ    SET_ALARM    {"id": "alarm_01", "time": "07:00", "repeat": [1,2,3,4,5], "action": "add"}
    åˆ é™¤å¼€æœºé—¹é’Ÿ    UNSET_ALARM    {"id": "alarm_01"}
系统维护    è§¦å‘固件在线升级    OTA_UPDATE    {"url": "...", "version": "1.0.2"}
    å»¶æ—¶é‡å¯è®¾å¤‡    REBOOT    {"delay": 5}
2. è¯¦ç»†æ¶ˆæ¯æµé€»è¾‘表
设备开机/定时    å¿ƒè·³/状态    rpt/state    ä¸éœ€è¦
执行命令    æŒ‡ä»¤å“åº”    rpt/ack    å¿…é¡» (即时反馈)
场景切换/ASR    äº‹ä»¶ä¸ŠæŠ¥    rpt/event    ä¸éœ€è¦
服务端操作    æŽ§åˆ¶æŒ‡ä»¤    cmd/operate    è®¾å¤‡å¤„理后必须回 rpt/ack
四、关于任务列表与文件管理:
下载任务: ä¸‹å‘任务时,Payload åº”包含文件 URL å’Œ MD5。终端下载过程中通过 rpt/ack å®šæ—¶ä¸ŠæŠ¥ progress (0-100)。
任务列表: æœåŠ¡ç«¯å¯ä»¥ä¸‹å‘ä¸€ä¸ªâ€œè¯¾ç¨‹èµ„æºæ¸…å•â€çš„ JSON æ•°ç»„,终端收到后存入本地文件,实现资源的管理与对比。
五、 åè®®å®žæ–½å»ºè®®ï¼ˆChecklist)
1.  QoS è®¾ç½®ç­–略:
    cmd/ctl (实时控制): QoS 1 (至少送达一次)。
    rpt/state (心跳): QoS 0 (不保证送达,减轻并发压力)。
    rpt/ack (指令响应): QoS 1 (必须确保云端知晓任务完成情况)。
2.  å®‰å…¨æ€§è¡¥å……:
    åœ¨ MQTT è¿žæŽ¥é˜¶æ®µé‡‡ç”¨ TLS åŒå‘认证 (MQTTS)。
    Payload å†…部虽然有 timestamp,但在 Broker å±‚建议开启 MQTT Client ID å”¯ä¸€æ€§æ ¡éªŒï¼Œé˜²æ­¢å¤šè®¾å¤‡æŠ¢å  ID。
3.  æ‰©å±•性预留:
    åœ¨ cmd/operate çš„ Action å­—段设计中,预留 USER_DEFINED_xxx æŽ¥å£ï¼Œä»¥ä¾¿åŽç»­å¿«é€Ÿå¢žåŠ å¦‚â€œè¿œç¨‹è°ƒè¯•â€ã€â€œæ—¥å¿—æ‹‰å–â€ç­‰åŠŸèƒ½ï¼Œæ— éœ€ä¿®æ”¹åŽŸæœ‰è§£æžæž¶æž„ã€‚
第三部分:视频文件目录结构和组成
1. æ‰€æœ‰è¯¾ç¨‹çš„æ ¹ç›®å½•在:./data/courses, ./data/courses/index.json文件内容是已确定发布的课程,结构如:
{
    "courses": [
        {
            "course_id": "course01",
            "config": "courses/course01/course.json"
        },
        {
            "course_id": "course02",
            "config": "courses/course02/course.json"
        }
    ]
}
2. æ¯ä¸ªè¯¾ç¨‹ç›®å½•下有一份course.json,如:courses/course02/course.json文件内容是当前目录所对应的课程的视频文件清单,结构如:
{
    "course_id": "course01",
    "course_name": "小智学习课程01",
    "description": "课程01",
    "chapters": [
        {
            "chapter_id": "chapter01",
            "chapter_name": "第一章",
            "audios": [
                {
                    "id": "001",
                    "title": "课程001",
                    "path": "courses/course01/chapter01/resources/001.mp3"
                },
                {
                    "id": "002",
                    "title": "课程002",
                    "path": "courses/course01/chapter01/resources/002.mp3"
                },
                {
                    "id": "003",
                    "title": "课程003",
                    "path": "courses/course01/chapter01/resources/003.mp3"
                }
            ]
        }
    ]
}
#TODO:
根据以上需求描述和mqtt规范定义,请完成以下工作:
1. å®Œæˆserver端的基础功能开发;
2. å®šä¹‰ä¸€å¥—基础课程,存放在本地目录(后期可以人工根据这个基础的版本进行课程增设);
3. å®šä¹‰tasklist.json(制定一套基础的任务清单,用于下发到终端设备进行初始化),保证客户端可以自动初始化的功能;
4. å®šä¹‰courseTask.json(制定某一门课程任务清单,用于下发到终端设备进行课程初始化),使客户端和服务端可以有一套自动机制,
    å®¢æˆ·ç«¯å’ŒæœåŠ¡ç«¯å¯ä»¥è‡ªåŠ¨åŒ–çš„é…åˆå®Œæˆå¸¸è§„çš„åŠŸèƒ½å’Œæ­£å¸¸çš„å·¥ä½œæµç¨‹ï¼›
smartlearning/server/.env
New file
@@ -0,0 +1,22 @@
# HTTP æœåŠ¡
HOST=0.0.0.0
PORT=2101
BASE_URL=http://124.222.109.197:2101
# è¯¾ç¨‹æ•°æ®ç›®å½•(默认指向项目根 data/,一般无需修改)
# DATA_DIR=
# MQTT
MQTT_BROKER=127.0.0.1
MQTT_PORT=2100
MQTT_USERNAME="AI_XIAOZHI"
MQTT_PASSWORD="V9qL7!mX2@rT8$kN5^wP1&c"
MQTT_CLIENT_ID=snlp-server
MQTT_ENABLED=true
# åˆå§‹åŒ–
AUTO_INIT_ENABLED=true
DEVICE_DATA_ROOT=/data
# åœºæ™¯è¯†åˆ«ç½®ä¿¡åº¦é˜ˆå€¼
SCENE_CONFIDENCE_THRESHOLD=0.7
smartlearning/server/app/__init__.py
smartlearning/server/app/__pycache__/__init__.cpython-310.pyc
Binary files differ
smartlearning/server/app/__pycache__/__init__.cpython-313.pyc
Binary files differ
smartlearning/server/app/__pycache__/config.cpython-310.pyc
Binary files differ
smartlearning/server/app/__pycache__/config.cpython-313.pyc
Binary files differ
smartlearning/server/app/__pycache__/main.cpython-310.pyc
Binary files differ
smartlearning/server/app/__pycache__/main.cpython-313.pyc
Binary files differ
smartlearning/server/app/config.py
New file
@@ -0,0 +1,34 @@
from pathlib import Path
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
    model_config = SettingsConfigDict(env_file=".env", env_file_encoding="utf-8", extra="ignore")
    host: str = "0.0.0.0"
    port: int = 9500
    data_dir: Path = Path(__file__).resolve().parents[2] / "data"
    mqtt_broker: str = "124.222.109.197"
    mqtt_port: int = 2100
    mqtt_username: str = "AI_XIAOZHI"
    mqtt_password: str = "V9qL7!mX2@rT8$kN5^wP1&c"
    mqtt_client_id: str = "snlp-server"
    mqtt_enabled: bool = True
    scene_confidence_threshold: float = 0.7
    base_url: str = "http://localhost:9500"
    runtime_dir: Path = Path(__file__).resolve().parents[1] / "runtime"
    initialized_devices_file: Path | None = None
    auto_init_enabled: bool = True
    device_data_root: str = "/data"
    @property
    def resolved_initialized_devices_file(self) -> Path:
        if self.initialized_devices_file:
            return self.initialized_devices_file
        return self.runtime_dir / "initialized_devices.json"
settings = Settings()
smartlearning/server/app/main.py
New file
@@ -0,0 +1,76 @@
import logging
from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from app.config import settings
from app.mqtt.client import MqttService
from app.mqtt.init_dispatcher import InitTaskDispatcher
from app.routes import courses, devices, dialogue, scene
from app.services.auto_init import AutoInitService
from app.services.course_service import CourseService
from app.services.device_store import DeviceStore
from app.services.init_registry import InitRegistry
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(name)s: %(message)s")
logger = logging.getLogger(__name__)
@asynccontextmanager
async def lifespan(app: FastAPI):
    device_store = DeviceStore()
    init_registry = InitRegistry()
    course_service = CourseService()
    mqtt_service = MqttService(device_store)
    init_dispatcher = InitTaskDispatcher(mqtt_service, course_service)
    auto_init = AutoInitService(init_registry, init_dispatcher, mqtt_service)
    mqtt_service.auto_init = auto_init
    app.state.device_store = device_store
    app.state.init_registry = init_registry
    app.state.mqtt = mqtt_service
    app.state.init_dispatcher = init_dispatcher
    app.state.auto_init = auto_init
    mqtt_service.start()
    logger.info("SmartLearning server started")
    yield
    mqtt_service.stop()
    logger.info("SmartLearning server stopped")
app = FastAPI(
    title="SmartLearning Server",
    description="情境感知智能英语学习系统 - æœåŠ¡ç«¯",
    version="1.0.0",
    lifespan=lifespan,
)
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)
app.include_router(scene.router)
app.include_router(dialogue.router)
app.include_router(courses.router)
app.include_router(devices.router)
data_path = Path(settings.data_dir)
if data_path.exists():
    app.mount("/static", StaticFiles(directory=str(data_path)), name="static")
@app.get("/health")
async def health():
    mqtt = app.state.mqtt if hasattr(app.state, "mqtt") else None
    return {
        "status": "ok",
        "mqtt_connected": mqtt.connected if mqtt else False,
    }
smartlearning/server/app/models/__init__.py
smartlearning/server/app/models/__pycache__/__init__.cpython-310.pyc
Binary files differ
smartlearning/server/app/models/__pycache__/__init__.cpython-313.pyc
Binary files differ
smartlearning/server/app/models/__pycache__/schemas.cpython-310.pyc
Binary files differ
smartlearning/server/app/models/__pycache__/schemas.cpython-313.pyc
Binary files differ
smartlearning/server/app/models/schemas.py
New file
@@ -0,0 +1,61 @@
from typing import Any, Literal
from pydantic import BaseModel, Field
class SceneAnalyzeRequest(BaseModel):
    timestamp: int
    audio_blob: str = Field(description="Base64 encoded audio or placeholder")
    device_id: str
    context_hints: dict[str, Any] = Field(default_factory=dict)
class SceneAnalyzeResponse(BaseModel):
    scene: str
    confidence: float
    greeting_strategy: Literal["contextual", "generic"]
    suggested_intent: str
class DialogueStreamPayload(BaseModel):
    dialogue_id: str
    text: str
    audio_url: str | None = None
    intent_type: str
    difficulty_level: str
class MqttHeader(BaseModel):
    version: str | None = None
    device_id: str
    req_id: str | None = None
    timestamp: int
    ttl: int | None = None
class MqttCommand(BaseModel):
    header: MqttHeader
    action: str
    payload: dict[str, Any] = Field(default_factory=dict)
class MqttReport(BaseModel):
    header: MqttHeader
    msg_type: Literal["state", "event", "log", "ack", "progress"]
    payload: dict[str, Any] = Field(default_factory=dict)
class DeviceState(BaseModel):
    device_id: str
    cpu: float | None = None
    mem: float | None = None
    battery: float | None = None
    net: str | None = None
    mode: str | None = None
    last_seen: int
class InitTaskRequest(BaseModel):
    device_id: str
    course_id: str | None = None
    force: bool = False
smartlearning/server/app/mqtt/__init__.py
smartlearning/server/app/mqtt/__pycache__/__init__.cpython-310.pyc
Binary files differ
smartlearning/server/app/mqtt/__pycache__/__init__.cpython-313.pyc
Binary files differ
smartlearning/server/app/mqtt/__pycache__/client.cpython-310.pyc
Binary files differ
smartlearning/server/app/mqtt/__pycache__/client.cpython-313.pyc
Binary files differ
smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-310.pyc
Binary files differ
smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-313.pyc
Binary files differ
smartlearning/server/app/mqtt/client.py
New file
@@ -0,0 +1,125 @@
import json
import logging
import time
import uuid
from typing import Any
import paho.mqtt.client as mqtt
from app.config import settings
from app.services.device_store import DeviceStore
logger = logging.getLogger(__name__)
TOPIC_RPT_STATE = "snlp/v1/+/rpt/state"
TOPIC_RPT_EVENT = "snlp/v1/+/rpt/event"
TOPIC_RPT_ACK = "snlp/v1/+/rpt/ack"
TOPIC_RPT_PROGRESS = "snlp/v1/+/rpt/progress"
TOPIC_RPT_LOG = "snlp/v1/+/rpt/log"
class MqttService:
    def __init__(self, device_store: DeviceStore, auto_init: object | None = None) -> None:
        self.device_store = device_store
        self.auto_init = auto_init
        self._client: mqtt.Client | None = None
        self._connected = False
    def start(self) -> None:
        if not settings.mqtt_enabled:
            logger.info("MQTT disabled by configuration")
            return
        self._client = mqtt.Client(
            callback_api_version=mqtt.CallbackAPIVersion.VERSION2,
            client_id=settings.mqtt_client_id,
        )
        if settings.mqtt_username:
            self._client.username_pw_set(settings.mqtt_username, settings.mqtt_password)
        self._client.on_connect = self._on_connect
        self._client.on_message = self._on_message
        self._client.on_disconnect = self._on_disconnect
        try:
            self._client.connect(settings.mqtt_broker, settings.mqtt_port, keepalive=60)
            self._client.loop_start()
            logger.info("MQTT client connecting to %s:%s", settings.mqtt_broker, settings.mqtt_port)
        except Exception:
            logger.exception("Failed to connect MQTT broker; running in API-only mode")
    def stop(self) -> None:
        if self._client:
            self._client.loop_stop()
            self._client.disconnect()
    def _on_connect(self, client: mqtt.Client, userdata: Any, flags: Any, reason_code: Any, properties: Any = None) -> None:
        if reason_code == 0 or str(reason_code) == "Success":
            self._connected = True
            for topic in (TOPIC_RPT_STATE, TOPIC_RPT_EVENT, TOPIC_RPT_ACK, TOPIC_RPT_PROGRESS, TOPIC_RPT_LOG):
                client.subscribe(topic, qos=1 if "ack" in topic else 0)
            logger.info("MQTT connected and subscribed")
        else:
            logger.error("MQTT connect failed: %s", reason_code)
    def _on_disconnect(self, client: mqtt.Client, userdata: Any, flags: Any, reason_code: Any, properties: Any = None) -> None:
        self._connected = False
        logger.warning("MQTT disconnected: %s", reason_code)
    def _on_message(self, client: mqtt.Client, userdata: Any, msg: mqtt.MQTTMessage) -> None:
        try:
            payload = json.loads(msg.payload.decode("utf-8"))
            parts = msg.topic.split("/")
            device_id = parts[2] if len(parts) > 2 else payload.get("header", {}).get("device_id", "unknown")
            msg_type = parts[-1] if len(parts) > 4 else payload.get("msg_type", "unknown")
            header = payload.get("header", {})
            timestamp = int(header.get("timestamp", time.time()))
            body = payload.get("payload", {})
            if msg_type == "state":
                self.device_store.update_state(device_id, body, timestamp)
                if self.auto_init:
                    self.auto_init.on_device_state(device_id, body, timestamp)
            elif msg_type == "ack":
                self.device_store.record_ack(device_id, body)
            elif msg_type in ("event", "progress"):
                self.device_store.record_event(device_id, {**body, "_type": msg_type})
            elif msg_type == "log":
                logger.info("Device %s log: %s", device_id, body)
        except Exception:
            logger.exception("Failed to handle MQTT message on %s", msg.topic)
    def publish_ctl(self, device_id: str, action: str, payload: dict[str, Any], ttl: int = 60) -> dict[str, Any]:
        command = self._build_command(device_id, action, payload, ttl)
        topic = f"snlp/v1/{device_id}/cmd/ctl"
        self._publish(topic, command, qos=1)
        return command
    def publish_task(self, device_id: str, action: str, payload: dict[str, Any], ttl: int = 3600) -> dict[str, Any]:
        command = self._build_command(device_id, action, payload, ttl)
        topic = f"snlp/v1/{device_id}/cmd/task"
        self._publish(topic, command, qos=1)
        return command
    def _build_command(self, device_id: str, action: str, payload: dict[str, Any], ttl: int) -> dict[str, Any]:
        return {
            "header": {
                "version": "1.0",
                "device_id": device_id,
                "req_id": str(uuid.uuid4()),
                "timestamp": int(time.time()),
                "ttl": ttl,
            },
            "action": action,
            "payload": payload,
        }
    def _publish(self, topic: str, payload: dict[str, Any], qos: int = 1) -> None:
        if not self._client or not self._connected:
            logger.warning("MQTT not connected; command queued locally only: %s", topic)
            return
        self._client.publish(topic, json.dumps(payload, ensure_ascii=False), qos=qos)
    @property
    def connected(self) -> bool:
        return self._connected
smartlearning/server/app/mqtt/init_dispatcher.py
New file
@@ -0,0 +1,48 @@
from typing import Any
import time
from app.config import settings
from app.mqtt.client import MqttService
from app.services.course_service import CourseService
SYNC_COURSE_TASK_ACTION = "SYNC_COURSE_TASK"
class InitTaskDispatcher:
    def __init__(self, mqtt: MqttService, course_service: CourseService) -> None:
        self.mqtt = mqtt
        self.course_service = course_service
    def dispatch_device_init(self, device_id: str) -> list[dict[str, Any]]:
        tasklist = self.course_service.get_tasklist()
        commands: list[dict[str, Any]] = []
        for task in tasklist.get("tasks", []):
            action = task["action"]
            payload = dict(task.get("payload", {}))
            channel = task.get("channel", "ctl")
            if action == "SET_CLOCK" and payload.get("ts") == 0:
                payload["ts"] = int(time.time())
            cmd = self._send(device_id, action, payload, channel, task.get("ttl", 60))
            commands.append(cmd)
        return commands
    def dispatch_base_courses_sync(self, device_id: str) -> list[dict[str, Any]]:
        commands: list[dict[str, Any]] = []
        for payload in self.course_service.build_base_course_sync_payloads(settings.base_url):
            cmd = self.mqtt.publish_task(device_id, SYNC_COURSE_TASK_ACTION, payload, ttl=7200)
            commands.append(cmd)
        return commands
    def dispatch_course_init(self, device_id: str, course_id: str) -> list[dict[str, Any]]:
        payload = self.course_service.build_course_task_sync_payload(course_id, settings.base_url)
        cmd = self.mqtt.publish_task(device_id, SYNC_COURSE_TASK_ACTION, payload, ttl=7200)
        return [cmd]
    def _send(self, device_id: str, action: str, payload: dict[str, Any], channel: str, ttl: int) -> dict[str, Any]:
        if channel == "task":
            return self.mqtt.publish_task(device_id, action, payload, ttl)
        return self.mqtt.publish_ctl(device_id, action, payload, ttl)
smartlearning/server/app/routes/__init__.py
smartlearning/server/app/routes/__pycache__/__init__.cpython-310.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/__init__.cpython-313.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/courses.cpython-310.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/courses.cpython-313.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/devices.cpython-310.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/devices.cpython-313.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/dialogue.cpython-310.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/dialogue.cpython-313.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/scene.cpython-310.pyc
Binary files differ
smartlearning/server/app/routes/__pycache__/scene.cpython-313.pyc
Binary files differ
smartlearning/server/app/routes/courses.py
New file
@@ -0,0 +1,79 @@
from fastapi import APIRouter, HTTPException, Request
from app.config import settings
from app.models.schemas import InitTaskRequest
from app.mqtt.init_dispatcher import InitTaskDispatcher
from app.services.course_service import CourseService
router = APIRouter(prefix="/api/v1", tags=["courses"])
course_service = CourseService()
def _get_dispatcher(request: Request) -> InitTaskDispatcher:
    return request.app.state.init_dispatcher
@router.get("/courses")
async def list_courses():
    return course_service.get_course_index()
@router.get("/courses/base-manifest")
async def get_base_courses_manifest():
    payloads = course_service.build_base_course_sync_payloads(settings.base_url)
    return {
        "scope": "base",
        "course_count": len(payloads),
        "courses": payloads,
    }
@router.get("/courses/{course_id}")
async def get_course(course_id: str):
    try:
        return course_service.get_course(course_id)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.get("/tasklist")
async def get_tasklist():
    return course_service.get_tasklist()
@router.get("/courses/{course_id}/tasks")
async def get_course_tasks(course_id: str):
    try:
        return course_service.get_course_task(course_id)
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.get("/courses/{course_id}/manifest")
async def get_course_manifest(course_id: str):
    try:
        payload = course_service.build_course_task_sync_payload(course_id, settings.base_url)
        return payload
    except FileNotFoundError as exc:
        raise HTTPException(status_code=404, detail=str(exc)) from exc
@router.post("/devices/init")
async def init_device(request: Request, body: InitTaskRequest):
    auto_init = request.app.state.auto_init
    commands, status = auto_init.dispatch_manual_init(body.device_id, force=body.force)
    return {"device_id": body.device_id, "commands": commands, "status": status}
@router.post("/devices/course-init")
async def init_course_on_device(request: Request, body: InitTaskRequest):
    if not body.course_id:
        raise HTTPException(status_code=400, detail="course_id is required")
    dispatcher = _get_dispatcher(request)
    commands = dispatcher.dispatch_course_init(body.device_id, body.course_id)
    return {
        "device_id": body.device_id,
        "course_id": body.course_id,
        "commands": commands,
        "status": "dispatched",
    }
smartlearning/server/app/routes/devices.py
New file
@@ -0,0 +1,75 @@
from fastapi import APIRouter, HTTPException, Request
from app.models.schemas import MqttCommand
router = APIRouter(prefix="/api/v1/devices", tags=["devices"])
def _get_mqtt(request: Request):
    return request.app.state.mqtt
@router.get("/initialized")
async def list_initialized_devices(request: Request):
    registry = request.app.state.init_registry
    return {"devices": registry.list_initialized()}
@router.delete("/initialized/{device_id}")
async def reset_initialized_device(request: Request, device_id: str):
    registry = request.app.state.init_registry
    if not registry.remove(device_id):
        raise HTTPException(status_code=404, detail="Device not in initialized registry")
    return {"device_id": device_id, "status": "removed"}
@router.get("/{device_id}/init-status")
async def get_device_init_status(request: Request, device_id: str):
    registry = request.app.state.init_registry
    record = registry.get_record(device_id)
    return {
        "device_id": device_id,
        "initialized": record is not None,
        "record": record,
    }
@router.get("")
async def list_devices(request: Request):
    store = request.app.state.device_store
    return {"devices": [d.model_dump() for d in store.list_devices()]}
@router.get("/{device_id}")
async def get_device(request: Request, device_id: str):
    store = request.app.state.device_store
    device = store.get_device(device_id)
    if not device:
        raise HTTPException(status_code=404, detail="Device not found")
    return device
@router.get("/{device_id}/acks")
async def get_device_acks(request: Request, device_id: str):
    store = request.app.state.device_store
    return {"device_id": device_id, "acks": store.get_acks(device_id)}
@router.get("/{device_id}/events")
async def get_device_events(request: Request, device_id: str):
    store = request.app.state.device_store
    return {"device_id": device_id, "events": store.get_events(device_id)}
@router.post("/{device_id}/cmd")
async def send_command(request: Request, device_id: str, command: MqttCommand):
    mqtt = _get_mqtt(request)
    result = mqtt.publish_ctl(device_id, command.action, command.payload, command.header.ttl or 60)
    return result
@router.post("/{device_id}/task")
async def send_task(request: Request, device_id: str, command: MqttCommand):
    mqtt = _get_mqtt(request)
    result = mqtt.publish_task(device_id, command.action, command.payload, command.header.ttl or 3600)
    return result
smartlearning/server/app/routes/dialogue.py
New file
@@ -0,0 +1,31 @@
import json
from fastapi import APIRouter, Query
from sse_starlette.sse import EventSourceResponse
from app.services.dialogue_service import DialogueService
router = APIRouter(prefix="/api/v1/dialogue", tags=["dialogue"])
dialogue_service = DialogueService()
@router.get("/stream")
async def stream_dialogue(
    dialogue_id: str | None = Query(default=None),
    user_text: str | None = Query(default=None),
    scene_prompt: str | None = Query(default=None),
    difficulty: str = Query(default="A2"),
) -> EventSourceResponse:
    resolved_id = dialogue_id or dialogue_service.create_dialogue_id()
    async def event_generator():
        async for chunk in dialogue_service.stream_dialogue(
            dialogue_id=resolved_id,
            user_text=user_text,
            scene_prompt=scene_prompt,
            difficulty=difficulty,
        ):
            yield {"event": "message", "data": json.dumps(chunk, ensure_ascii=False)}
        yield {"event": "done", "data": json.dumps({"dialogue_id": resolved_id})}
    return EventSourceResponse(event_generator())
smartlearning/server/app/routes/scene.py
New file
@@ -0,0 +1,22 @@
from fastapi import APIRouter, HTTPException, Query
from app.models.schemas import SceneAnalyzeRequest, SceneAnalyzeResponse
from app.services.scene_engine import SceneEngine
router = APIRouter(prefix="/api/v1/scene", tags=["scene"])
scene_engine = SceneEngine()
@router.post("/analyze", response_model=SceneAnalyzeResponse)
async def analyze_scene(request: SceneAnalyzeRequest) -> SceneAnalyzeResponse:
    result = scene_engine.analyze(
        device_id=request.device_id,
        timestamp=request.timestamp,
        context_hints=request.context_hints,
    )
    return SceneAnalyzeResponse(
        scene=result["scene"],
        confidence=result["confidence"],
        greeting_strategy=result["greeting_strategy"],
        suggested_intent=result["suggested_intent"],
    )
smartlearning/server/app/services/__init__.py
smartlearning/server/app/services/__pycache__/__init__.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/__init__.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/auto_init.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/auto_init.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/course_service.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/course_service.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/device_store.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/device_store.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/dialogue_service.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/dialogue_service.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/init_registry.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/init_registry.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/scene_engine.cpython-310.pyc
Binary files differ
smartlearning/server/app/services/__pycache__/scene_engine.cpython-313.pyc
Binary files differ
smartlearning/server/app/services/auto_init.py
New file
@@ -0,0 +1,83 @@
import logging
import time
from typing import Any
from app.config import settings
from app.mqtt.init_dispatcher import InitTaskDispatcher
from app.mqtt.client import MqttService
from app.services.init_registry import InitRegistry
logger = logging.getLogger(__name__)
class AutoInitService:
    def __init__(
        self,
        registry: InitRegistry,
        dispatcher: InitTaskDispatcher,
        mqtt: MqttService,
    ) -> None:
        self.registry = registry
        self.dispatcher = dispatcher
        self.mqtt = mqtt
    def _run_full_init(self, device_id: str) -> list[dict[str, Any]]:
        commands = self.dispatcher.dispatch_device_init(device_id)
        logger.info("Device init dispatched for %s (%d commands)", device_id, len(commands))
        course_commands = self.dispatcher.dispatch_base_courses_sync(device_id)
        commands.extend(course_commands)
        logger.info(
            "Base courses sync dispatched for %s (%d courseTask messages)",
            device_id,
            len(course_commands),
        )
        self.registry.update_record(
            device_id,
            courses_synced=True,
            courses_sync_at=int(time.time()),
            courses_sync_count=len(course_commands),
        )
        return commands
    def on_device_state(self, device_id: str, payload: dict[str, Any], timestamp: int) -> list[dict[str, Any]] | None:
        if not settings.auto_init_enabled:
            return None
        if self.registry.is_initialized(device_id):
            return None
        if not self.mqtt.connected:
            logger.warning("Skip auto-init for %s: MQTT not connected", device_id)
            return None
        mode = payload.get("mode")
        if not self.registry.try_claim(device_id, source="auto", mode=mode, timestamp=timestamp):
            return None
        try:
            commands = self._run_full_init(device_id)
            logger.info("Auto-init completed for device %s (total %d commands)", device_id, len(commands))
            return commands
        except Exception:
            self.registry.remove(device_id)
            logger.exception("Auto-init failed for device %s, registry entry rolled back", device_id)
            raise
    def dispatch_manual_init(self, device_id: str, force: bool = False) -> tuple[list[dict[str, Any]], str]:
        if self.registry.is_initialized(device_id) and not force:
            return [], "already_initialized"
        if force:
            self.registry.remove(device_id)
        if not self.registry.try_claim(device_id, source="manual"):
            return [], "already_initialized"
        try:
            commands = self._run_full_init(device_id)
            return commands, "dispatched"
        except Exception:
            self.registry.remove(device_id)
            raise
smartlearning/server/app/services/course_service.py
New file
@@ -0,0 +1,72 @@
import json
from copy import deepcopy
from pathlib import Path
from typing import Any
from app.config import settings
class CourseService:
    def __init__(self, data_dir: Path | None = None) -> None:
        self.data_dir = data_dir or settings.data_dir
    def _read_json(self, path: Path) -> dict[str, Any]:
        with path.open(encoding="utf-8") as f:
            return json.load(f)
    def static_url(self, server_rel_path: str, base_url: str) -> str:
        rel = server_rel_path.replace("\\", "/").lstrip("/")
        return f"{base_url.rstrip('/')}/static/{rel}"
    def _resolve_media_url(self, path: str, base_url: str) -> str:
        if path.startswith("http://") or path.startswith("https://"):
            return path
        return self.static_url(path, base_url)
    def _resolve_asset_urls(self, asset: dict[str, Any], base_url: str) -> dict[str, Any]:
        resolved = dict(asset)
        if resolved.get("url"):
            resolved["url"] = self._resolve_media_url(resolved["url"], base_url)
        if resolved.get("cover_url"):
            resolved["cover_url"] = self._resolve_media_url(resolved["cover_url"], base_url)
        return resolved
    def get_course_index(self) -> dict[str, Any]:
        return self._read_json(self.data_dir / "courses" / "index.json")
    def get_course(self, course_id: str) -> dict[str, Any]:
        index = self.get_course_index()
        for item in index.get("courses", []):
            if item["course_id"] == course_id:
                config_path = self.data_dir / item["config"]
                return self._read_json(config_path)
        raise FileNotFoundError(f"Course not found: {course_id}")
    def get_tasklist(self) -> dict[str, Any]:
        return self._read_json(self.data_dir / "tasklist.json")
    def get_course_task(self, course_id: str) -> dict[str, Any]:
        path = self.data_dir / "courses" / course_id / "courseTask.json"
        if not path.exists():
            raise FileNotFoundError(f"Course task not found: {course_id}")
        return self._read_json(path)
    def build_course_task_sync_payload(self, course_id: str, base_url: str) -> dict[str, Any]:
        task = deepcopy(self.get_course_task(course_id))
        assets = task.get("assets", [])
        task["assets"] = [self._resolve_asset_urls(asset, base_url) for asset in assets]
        return task
    def build_base_course_sync_payloads(self, base_url: str) -> list[dict[str, Any]]:
        index = self.get_course_index()
        payloads: list[dict[str, Any]] = []
        for item in index.get("courses", []):
            course_id = item["course_id"]
            task_path = self.data_dir / "courses" / course_id / "courseTask.json"
            if task_path.exists():
                payloads.append(self.build_course_task_sync_payload(course_id, base_url))
        return payloads
    def build_manifest_assets(self, course_id: str, base_url: str) -> list[dict[str, Any]]:
        task = self.get_course_task(course_id)
        return [self._resolve_asset_urls(asset, base_url) for asset in task.get("assets", [])]
smartlearning/server/app/services/device_store.py
New file
@@ -0,0 +1,45 @@
from threading import Lock
from app.models.schemas import DeviceState
class DeviceStore:
    def __init__(self) -> None:
        self._devices: dict[str, DeviceState] = {}
        self._acks: dict[str, list[dict]] = {}
        self._events: dict[str, list[dict]] = {}
        self._lock = Lock()
    def update_state(self, device_id: str, payload: dict, timestamp: int) -> DeviceState:
        with self._lock:
            state = DeviceState(
                device_id=device_id,
                cpu=payload.get("cpu"),
                mem=payload.get("mem"),
                battery=payload.get("battery"),
                net=payload.get("net"),
                mode=payload.get("mode"),
                last_seen=timestamp,
            )
            self._devices[device_id] = state
            return state
    def record_ack(self, device_id: str, payload: dict) -> None:
        with self._lock:
            self._acks.setdefault(device_id, []).append(payload)
    def record_event(self, device_id: str, payload: dict) -> None:
        with self._lock:
            self._events.setdefault(device_id, []).append(payload)
    def get_device(self, device_id: str) -> DeviceState | None:
        return self._devices.get(device_id)
    def list_devices(self) -> list[DeviceState]:
        return list(self._devices.values())
    def get_acks(self, device_id: str) -> list[dict]:
        return list(self._acks.get(device_id, []))
    def get_events(self, device_id: str) -> list[dict]:
        return list(self._events.get(device_id, []))
smartlearning/server/app/services/dialogue_service.py
New file
@@ -0,0 +1,81 @@
import uuid
from typing import AsyncIterator
from app.services.scene_engine import GENERIC_GREETING
class DialogueService:
    def create_dialogue_id(self) -> str:
        return str(uuid.uuid4())
    async def stream_dialogue(
        self,
        dialogue_id: str,
        user_text: str | None = None,
        scene_prompt: str | None = None,
        difficulty: str = "A2",
    ) -> AsyncIterator[dict]:
        opening = scene_prompt or GENERIC_GREETING
        chunks = [
            {
                "dialogue_id": dialogue_id,
                "text": opening,
                "audio_url": None,
                "intent_type": "greeting",
                "difficulty_level": difficulty,
            }
        ]
        if user_text:
            feedback = self._evaluate_response(user_text)
            chunks.append(
                {
                    "dialogue_id": dialogue_id,
                    "text": feedback["text"],
                    "audio_url": None,
                    "intent_type": feedback["intent_type"],
                    "difficulty_level": difficulty,
                }
            )
            if feedback["follow_up"]:
                chunks.append(
                    {
                        "dialogue_id": dialogue_id,
                        "text": feedback["follow_up"],
                        "audio_url": None,
                        "intent_type": "question",
                        "difficulty_level": difficulty,
                    }
                )
        for chunk in chunks:
            yield chunk
    def _evaluate_response(self, text: str) -> dict:
        normalized = text.strip()
        if not normalized:
            return {
                "text": "I didn't catch that. Could you try again?",
                "intent_type": "correction",
                "follow_up": "Please repeat your answer slowly.",
            }
        if normalized[0].islower():
            return {
                "text": f'Good try! Remember to capitalize: "{normalized[0].upper()}{normalized[1:]}"',
                "intent_type": "correction",
                "follow_up": "Can you say the full sentence again?",
            }
        if not normalized.endswith((".", "!", "?")):
            return {
                "text": "Nice answer! Don't forget punctuation at the end.",
                "intent_type": "praise",
                "follow_up": "Tell me one more detail about it.",
            }
        return {
            "text": "Great job! Your sentence looks correct.",
            "intent_type": "praise",
            "follow_up": "What would you do differently next time?",
        }
smartlearning/server/app/services/init_registry.py
New file
@@ -0,0 +1,92 @@
import json
import logging
import time
from pathlib import Path
from threading import Lock
from typing import Any, Literal
from app.config import settings
logger = logging.getLogger(__name__)
InitSource = Literal["auto", "manual"]
class InitRegistry:
    def __init__(self, storage_path: Path | None = None) -> None:
        self.storage_path = storage_path or settings.resolved_initialized_devices_file
        self._lock = Lock()
        self._data = self._load()
    def _load(self) -> dict[str, Any]:
        if self.storage_path.exists():
            with self.storage_path.open(encoding="utf-8") as f:
                return json.load(f)
        return {"devices": {}}
    def _save(self) -> None:
        self.storage_path.parent.mkdir(parents=True, exist_ok=True)
        with self.storage_path.open("w", encoding="utf-8") as f:
            json.dump(self._data, f, ensure_ascii=False, indent=2)
    def is_initialized(self, device_id: str) -> bool:
        with self._lock:
            return device_id in self._data["devices"]
    def try_claim(
        self,
        device_id: str,
        source: InitSource,
        mode: str | None = None,
        timestamp: int | None = None,
    ) -> bool:
        with self._lock:
            if device_id in self._data["devices"]:
                return False
            self._data["devices"][device_id] = {
                "initialized_at": timestamp or int(time.time()),
                "source": source,
                "mode": mode,
            }
            self._save()
            return True
    def mark_initialized(
        self,
        device_id: str,
        source: InitSource,
        mode: str | None = None,
        timestamp: int | None = None,
    ) -> None:
        with self._lock:
            self._data["devices"][device_id] = {
                "initialized_at": timestamp or int(time.time()),
                "source": source,
                "mode": mode,
            }
            self._save()
    def remove(self, device_id: str) -> bool:
        with self._lock:
            if device_id not in self._data["devices"]:
                return False
            del self._data["devices"][device_id]
            self._save()
            return True
    def list_initialized(self) -> dict[str, Any]:
        with self._lock:
            return dict(self._data["devices"])
    def get_record(self, device_id: str) -> dict[str, Any] | None:
        with self._lock:
            record = self._data["devices"].get(device_id)
            return dict(record) if record else None
    def update_record(self, device_id: str, **fields: Any) -> bool:
        with self._lock:
            if device_id not in self._data["devices"]:
                return False
            self._data["devices"][device_id].update(fields)
            self._save()
            return True
smartlearning/server/app/services/scene_engine.py
New file
@@ -0,0 +1,57 @@
import hashlib
import random
from typing import Any
from app.config import settings
SCENE_CATALOG = [
    ("home_bedroom", "居家-卧室", "Let's talk about your morning routine."),
    ("home_kitchen", "居家-厨房", "What would you like to cook today?"),
    ("home_study", "居家-书房", "Shall we review your vocabulary?"),
    ("school", "校内", "How was your class today?"),
    ("commute", "通勤", "What do you see on your way?"),
    ("exercise", "运动", "How do you stay active?"),
    ("dining", "用餐", "What are you having for dinner?"),
]
GENERIC_GREETING = "Hello! How can I help you practice English today?"
class SceneEngine:
    def __init__(self, threshold: float | None = None) -> None:
        self.threshold = threshold or settings.scene_confidence_threshold
    def _derive_seed(self, device_id: str, timestamp: int, hints: dict[str, Any]) -> int:
        raw = f"{device_id}:{timestamp}:{hints.get('mode', '')}"
        return int(hashlib.md5(raw.encode()).hexdigest()[:8], 16)
    def analyze(
        self,
        device_id: str,
        timestamp: int,
        context_hints: dict[str, Any] | None = None,
    ) -> dict[str, Any]:
        hints = context_hints or {}
        rng = random.Random(self._derive_seed(device_id, timestamp, hints))
        if hints.get("scene"):
            scene_key = hints["scene"]
            match = next((s for s in SCENE_CATALOG if s[0] == scene_key), None)
            if match:
                confidence = float(hints.get("confidence", 0.85))
                return self._build_result(match, confidence)
        idx = rng.randint(0, len(SCENE_CATALOG) - 1)
        confidence = round(rng.uniform(0.55, 0.95), 2)
        return self._build_result(SCENE_CATALOG[idx], confidence)
    def _build_result(self, scene: tuple[str, str, str], confidence: float) -> dict[str, Any]:
        scene_key, scene_label, prompt = scene
        use_generic = confidence < self.threshold
        return {
            "scene": scene_label,
            "scene_key": scene_key,
            "confidence": confidence,
            "greeting_strategy": "generic" if use_generic else "contextual",
            "suggested_intent": GENERIC_GREETING if use_generic else prompt,
        }
smartlearning/server/requirements.txt
New file
@@ -0,0 +1,7 @@
fastapi>=0.115.0
uvicorn[standard]>=0.32.0
paho-mqtt>=2.1.0
pydantic>=2.9.0
pydantic-settings>=2.6.0
python-multipart>=0.0.12
sse-starlette>=2.1.0
smartlearning/server/run.py
New file
@@ -0,0 +1,11 @@
import uvicorn
from app.config import settings
def main() -> None:
    uvicorn.run("app.main:app", host=settings.host, port=settings.port, reload=False)
if __name__ == "__main__":
    main()
smartlearning/server/runtime/.gitignore
New file
@@ -0,0 +1 @@
*.json
smartlearning/server/runtime/.gitkeep
xiaozhi-esp32-2.2.6/.clang-format
New file
@@ -0,0 +1,126 @@
---
Language: Cpp
BasedOnStyle: Google
AccessModifierOffset: -4
AlignAfterOpenBracket: Align
AlignConsecutiveAssignments: false
AlignConsecutiveDeclarations: false
AlignEscapedNewlines: Left
AlignOperands: true
AlignTrailingComments: true
AllowAllParametersOfDeclarationOnNextLine: true
AllowShortBlocksOnASingleLine: false
AllowShortCaseLabelsOnASingleLine: false
AllowShortFunctionsOnASingleLine: All
AllowShortIfStatementsOnASingleLine: false
AllowShortLoopsOnASingleLine: false
AlwaysBreakAfterReturnType: ExceptShortType
AlwaysBreakBeforeMultilineStrings: true
AlwaysBreakTemplateDeclarations: Yes
BinPackArguments: true
BinPackParameters: true
BraceWrapping:
  AfterClass: false
  AfterControlStatement: false
  AfterEnum: false
  AfterFunction: false
  AfterNamespace: false
  AfterObjCDeclaration: false
  AfterStruct: false
  AfterUnion: false
  AfterExternBlock: false
  BeforeCatch: false
  BeforeElse: false
  IndentBraces: false
  SplitEmptyFunction: false
  SplitEmptyRecord: false
  SplitEmptyNamespace: false
BreakBeforeBinaryOperators: None
BreakBeforeBraces: Attach
BreakBeforeInheritanceComma: false
BreakInheritanceList: BeforeColon
BreakBeforeTernaryOperators: true
BreakConstructorInitializersBeforeComma: false
BreakConstructorInitializers: BeforeColon
BreakAfterJavaFieldAnnotations: false
BreakStringLiterals: true
ColumnLimit: 100
CommentPragmas: '^ IWYU pragma:'
CompactNamespaces: false
ConstructorInitializerAllOnOneLineOrOnePerLine: true
ConstructorInitializerIndentWidth: 4
ContinuationIndentWidth: 4
Cpp11BracedListStyle: true
DerivePointerAlignment: false
DisableFormat: false
ExperimentalAutoDetectBinPacking: false
FixNamespaceComments: true
ForEachMacros:
  - cJSON_ArrayForEach
IncludeBlocks: Preserve
IncludeCategories:
  - Regex: '^<esp_.*\.h>'
    Priority: 1
  - Regex: '^<driver/.*\.h>'
    Priority: 1
  - Regex: '^<.*\.h>'
    Priority: 2
  - Regex: '^<.*'
    Priority: 3
  - Regex: '.*'
    Priority: 4
IncludeIsMainRegex: '([-_](test|unittest))?$'
IndentCaseLabels: true
IndentPPDirectives: None
IndentWidth: 4
IndentWrappedFunctionNames: false
KeepEmptyLinesAtTheStartOfBlocks: false
MacroBlockBegin: ''
MacroBlockEnd: ''
MaxEmptyLinesToKeep: 1
NamespaceIndentation: None
PenaltyBreakAssignment: 2
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyBreakTemplateDeclaration: 10
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
PointerAlignment: Left
RawStringFormats:
  - Language: Cpp
    Delimiters:
      - cc
      - CC
      - cpp
      - Cpp
      - CPP
      - 'c++'
      - 'C++'
    CanonicalDelimiter: ''
    BasedOnStyle: google
ReflowComments: true
SortIncludes: true
SortUsingDeclarations: true
SpaceAfterCStyleCast: false
SpaceAfterTemplateKeyword: true
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements
SpaceBeforeRangeBasedForLoopColon: true
SpaceInEmptyParentheses: false
SpacesBeforeTrailingComments: 2
SpacesInAngles: false
SpacesInContainerLiterals: true
SpacesInCStyleCastParentheses: false
SpacesInParentheses: false
SpacesInSquareBrackets: false
Standard: Latest
StatementMacros:
  - Q_UNUSED
  - QT_REQUIRE_VERSION
TabWidth: 4
UseTab: Never
xiaozhi-esp32-2.2.6/.gitignore
New file
@@ -0,0 +1,20 @@
tmp/
components/
managed_components/
build/
dist/
.vscode/
.devcontainer/
sdkconfig.old
sdkconfig
dependencies.lock
.env
releases/
main/assets/lang_config.h
main/mmap_generate_emoji.h
.DS_Store
.cache
*.pyc
*.bin
mmap_generate_*.h
.clangd
xiaozhi-esp32-2.2.6/CMakeLists.txt
New file
@@ -0,0 +1,13 @@
# The following lines of boilerplate have to be in your project's
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
idf_build_set_property(MINIMAL_BUILD ON)
set(PROJECT_VER "2.2.6")
project(xiaozhi)
xiaozhi-esp32-2.2.6/LICENSE
New file
@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2025 Shenzhen Xinzhi Future Technology Co., Ltd.
Copyright (c) 2025 Project Contributors
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
xiaozhi-esp32-2.2.6/README.md
New file
@@ -0,0 +1,173 @@
# An MCP-based Chatbot
(English | [中文](README_zh.md) | [日本語](README_ja.md))
## Introduction
👉 [Human: Give AI a camera vs AI: Instantly finds out the owner hasn't washed hair for three days【bilibili】](https://www.bilibili.com/video/BV1bpjgzKEhd/)
👉 [Handcraft your AI girlfriend, beginner's guide【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/)
As a voice interaction entry, the XiaoZhi AI chatbot leverages the AI capabilities of large models like Qwen / DeepSeek, and achieves multi-terminal control via the MCP protocol.
<img src="docs/mcp-based-graph.jpg" alt="Control everything via MCP" width="320">
## Version Notes
The current v2 version is incompatible with the v1 partition table, so it is not possible to upgrade from v1 to v2 via OTA. For partition table details, see [partitions/v2/README.md](partitions/v2/README.md).
All hardware running v1 can be upgraded to v2 by manually flashing the firmware.
The stable version of v1 is 1.9.2. You can switch to v1 by running `git checkout v1`. The v1 branch will be maintained until February 2026.
### Features Implemented
- Wi-Fi / ML307 Cat.1 4G
- Offline voice wake-up [ESP-SR](https://github.com/espressif/esp-sr)
- Supports two communication protocols ([Websocket](docs/websocket.md) or MQTT+UDP)
- Uses OPUS audio codec
- Voice interaction based on streaming ASR + LLM + TTS architecture
- Speaker recognition, identifies the current speaker [3D Speaker](https://github.com/modelscope/3D-Speaker)
- OLED / LCD display, supports emoji display
- Battery display and power management
- Multi-language support (Chinese, English, Japanese)
- Supports ESP32-C3, ESP32-S3, ESP32-P4 chip platforms
- Device-side MCP for device control (Speaker, LED, Servo, GPIO, etc.)
- Cloud-side MCP to extend large model capabilities (smart home control, PC desktop operation, knowledge search, email, etc.)
- Customizable wake words, fonts, emojis, and chat backgrounds with online web-based editing ([Custom Assets Generator](https://github.com/78/xiaozhi-assets-generator))
## Hardware
### Breadboard DIY Practice
See the Feishu document tutorial:
👉 ["XiaoZhi AI Chatbot Encyclopedia"](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink)
Breadboard demo:
![Breadboard Demo](docs/v1/wiring2.jpg)
### Supports 70+ Open Source Hardware (Partial List)
- <a href="https://oshwhub.com/li-chuang-kai-fa-ban/li-chuang-shi-zhan-pai-esp32-s3-kai-fa-ban" target="_blank" title="LiChuang ESP32-S3 Development Board">LiChuang ESP32-S3 Development Board</a>
- <a href="https://github.com/espressif/esp-box" target="_blank" title="Espressif ESP32-S3-BOX3">Espressif ESP32-S3-BOX3</a>
- <a href="https://docs.m5stack.com/zh_CN/core/CoreS3" target="_blank" title="M5Stack CoreS3">M5Stack CoreS3</a>
- <a href="https://docs.m5stack.com/en/atom/Atomic%20Echo%20Base" target="_blank" title="AtomS3R + Echo Base">M5Stack AtomS3R + Echo Base</a>
- <a href="https://gf.bilibili.com/item/detail/1108782064" target="_blank" title="Magic Button 2.4">Magic Button 2.4</a>
- <a href="https://www.waveshare.net/shop/ESP32-S3-Touch-AMOLED-1.8.htm" target="_blank" title="Waveshare ESP32-S3-Touch-AMOLED-1.8">Waveshare ESP32-S3-Touch-AMOLED-1.8</a>
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="XiaGe Mini C3">XiaGe Mini C3</a>
- <a href="https://oshwhub.com/movecall/cuican-ai-pendant-lights-up-y" target="_blank" title="Movecall CuiCan ESP32S3">CuiCan AI Pendant</a>
- <a href="https://github.com/WMnologo/xingzhi-ai" target="_blank" title="WMnologo-Xingzhi-1.54">WMnologo-Xingzhi-1.54TFT</a>
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
- <a href="https://www.bilibili.com/video/BV1BHJtz6E2S/" target="_blank" title="ESP-HI Low Cost Robot Dog">ESP-HI Low Cost Robot Dog</a>
<div style="display: flex; justify-content: space-between;">
  <a href="docs/v1/lichuang-s3.jpg" target="_blank" title="LiChuang ESP32-S3 Development Board">
    <img src="docs/v1/lichuang-s3.jpg" width="240" />
  </a>
  <a href="docs/v1/espbox3.jpg" target="_blank" title="Espressif ESP32-S3-BOX3">
    <img src="docs/v1/espbox3.jpg" width="240" />
  </a>
  <a href="docs/v1/m5cores3.jpg" target="_blank" title="M5Stack CoreS3">
    <img src="docs/v1/m5cores3.jpg" width="240" />
  </a>
  <a href="docs/v1/atoms3r.jpg" target="_blank" title="AtomS3R + Echo Base">
    <img src="docs/v1/atoms3r.jpg" width="240" />
  </a>
  <a href="docs/v1/magiclick.jpg" target="_blank" title="Magic Button 2.4">
    <img src="docs/v1/magiclick.jpg" width="240" />
  </a>
  <a href="docs/v1/waveshare.jpg" target="_blank" title="Waveshare ESP32-S3-Touch-AMOLED-1.8">
    <img src="docs/v1/waveshare.jpg" width="240" />
  </a>
  <a href="docs/v1/lilygo-t-circle-s3.jpg" target="_blank" title="LILYGO T-Circle-S3">
    <img src="docs/v1/lilygo-t-circle-s3.jpg" width="240" />
  </a>
  <a href="docs/v1/xmini-c3.jpg" target="_blank" title="XiaGe Mini C3">
    <img src="docs/v1/xmini-c3.jpg" width="240" />
  </a>
  <a href="docs/v1/movecall-cuican-esp32s3.jpg" target="_blank" title="CuiCan">
    <img src="docs/v1/movecall-cuican-esp32s3.jpg" width="240" />
  </a>
  <a href="docs/v1/wmnologo_xingzhi_1.54.jpg" target="_blank" title="WMnologo-Xingzhi-1.54">
    <img src="docs/v1/wmnologo_xingzhi_1.54.jpg" width="240" />
  </a>
  <a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
    <img src="docs/v1/sensecap_watcher.jpg" width="240" />
  </a>
  <a href="docs/v1/esp-hi.jpg" target="_blank" title="ESP-HI Low Cost Robot Dog">
    <img src="docs/v1/esp-hi.jpg" width="240" />
  </a>
</div>
## Software
### Firmware Flashing
For beginners, it is recommended to use the firmware that can be flashed without setting up a development environment.
The firmware connects to the official [xiaozhi.me](https://xiaozhi.me) server by default. Personal users can register an account to use the Qwen real-time model for free.
👉 [Beginner's Firmware Flashing Guide](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS)
### Development Environment
- Cursor or VSCode
- Install ESP-IDF plugin, select SDK version 5.4 or above
- Linux is better than Windows for faster compilation and fewer driver issues
- This project uses Google C++ code style, please ensure compliance when submitting code
### Developer Documentation
- [Custom Board Guide](docs/custom-board.md) - Learn how to create custom boards for XiaoZhi AI
- [MCP Protocol IoT Control Usage](docs/mcp-usage.md) - Learn how to control IoT devices via MCP protocol
- [MCP Protocol Interaction Flow](docs/mcp-protocol.md) - Device-side MCP protocol implementation
- [MQTT + UDP Hybrid Communication Protocol Document](docs/mqtt-udp.md)
- [A detailed WebSocket communication protocol document](docs/websocket.md)
## Large Model Configuration
If you already have a XiaoZhi AI chatbot device and have connected to the official server, you can log in to the [xiaozhi.me](https://xiaozhi.me) console for configuration.
👉 [Backend Operation Video Tutorial (Old Interface)](https://www.bilibili.com/video/BV1jUCUY2EKM/)
## Related Open Source Projects
For server deployment on personal computers, refer to the following open-source projects:
- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python server
- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java server
- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang server
- [hackers365/xiaozhi-esp32-server-golang](https://github.com/hackers365/xiaozhi-esp32-server-golang) Golang server
Other client projects using the XiaoZhi communication protocol:
- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Python client
- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Android client
- [100askTeam/xiaozhi-linux](http://github.com/100askTeam/xiaozhi-linux) Linux client by 100ask
- [78/xiaozhi-sf32](https://github.com/78/xiaozhi-sf32) Bluetooth chip firmware by Sichuan
- [QuecPython/solution-xiaozhiAI](https://github.com/QuecPython/solution-xiaozhiAI) QuecPython firmware by Quectel
Custom Assets Tools:
- [78/xiaozhi-assets-generator](https://github.com/78/xiaozhi-assets-generator) Custom Assets Generator (Wake words, fonts, emojis, backgrounds)
## About the Project
This is an open-source ESP32 project, released under the MIT license, allowing anyone to use it for free, including for commercial purposes.
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 our [Discord](https://discord.gg/C759fGMBcZ) or QQ group: 994694848
## Star History
<a href="https://star-history.com/#78/xiaozhi-esp32&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date" />
 </picture>
</a>
xiaozhi-esp32-2.2.6/README_ja.md
New file
@@ -0,0 +1,169 @@
# MCP ãƒ™ãƒ¼ã‚¹ã®ãƒãƒ£ãƒƒãƒˆãƒœãƒƒãƒˆ
(日本語 | [中文](README_zh.md) | [English](README.md))
## ã¯ã˜ã‚ã«
👉 [人間:AIにカメラを装着 vs AI:その場で飼い主が3日間髪を洗っていないことを発見【bilibili】](https://www.bilibili.com/video/BV1bpjgzKEhd/)
👉 [手作りでAIガールフレンドを作る、初心者入門チュートリアル【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/)
シャオジーAIチャットボットは音声インタラクションの入口として、Qwen / DeepSeekなどの大規模モデルのAI能力を活用し、MCPプロトコルを通じてマルチエンド制御を実現します。
<img src="docs/mcp-based-graph.jpg" alt="MCPであらゆるものを制御" width="320">
## ãƒãƒ¼ã‚¸ãƒ§ãƒ³ãƒŽãƒ¼ãƒˆ
現在のv2バージョンはv1パーティションテーブルと互換性がないため、v1からv2へOTAでアップグレードすることはできません。パーティションテーブルの詳細については、[partitions/v2/README.md](partitions/v2/README.md)をご参照ください。
v1を実行しているすべてのハードウェアは、ファームウェアを手動で書き込むことでv2にアップグレードできます。
v1の安定版は1.9.2です。`git checkout v1`でv1に切り替えることができます。v1ブランチは2026å¹´2月まで継続的にメンテナンスされます。
### å®Ÿè£…済み機能
- Wi-Fi / ML307 Cat.1 4G
- ã‚ªãƒ•ライン音声ウェイクアップ [ESP-SR](https://github.com/espressif/esp-sr)
- 2種類の通信プロトコルに対応([Websocket](docs/websocket.md) ã¾ãŸã¯ MQTT+UDP)
- OPUSオーディオコーデックを採用
- ã‚¹ãƒˆãƒªãƒ¼ãƒŸãƒ³ã‚°ASR + LLM + TTSアーキテクチャに基づく音声インタラクション
- è©±è€…認識、現在話している人を識別 [3D Speaker](https://github.com/modelscope/3D-Speaker)
- OLED / LCDディスプレイ、表情表示対応
- ãƒãƒƒãƒ†ãƒªãƒ¼è¡¨ç¤ºã¨é›»æºç®¡ç†
- å¤šè¨€èªžå¯¾å¿œï¼ˆä¸­å›½èªžã€è‹±èªžã€æ—¥æœ¬èªžï¼‰
- ESP32-C3、ESP32-S3、ESP32-P4チッププラットフォーム対応
- ãƒ‡ãƒã‚¤ã‚¹å´MCPによるデバイス制御(音量・明るさ調整、アクション制御など)
- ã‚¯ãƒ©ã‚¦ãƒ‰å´MCPで大規模モデル能力を拡張(スマートホーム制御、PCデスクトップ操作、知識検索、メール送受信など)
- ã‚«ã‚¹ã‚¿ãƒžã‚¤ã‚ºå¯èƒ½ãªã‚¦ã‚§ã‚¤ã‚¯ãƒ¯ãƒ¼ãƒ‰ã€ãƒ•ォント、絵文字、チャット背景、オンラインWeb編集に対応 ([カスタムアセットジェネレーター](https://github.com/78/xiaozhi-assets-generator))
## ãƒãƒ¼ãƒ‰ã‚¦ã‚§ã‚¢
### ãƒ–レッドボード手作り実践
Feishuドキュメントチュートリアルをご覧ください:
👉 [「シャオジーAIチャットボット百科事典」](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink)
ブレッドボードのデモ:
![ブレッドボードデモ](docs/v1/wiring2.jpg)
### 70種類以上のオープンソースハードウェアに対応(一部のみ表示)
- <a href="https://oshwhub.com/li-chuang-kai-fa-ban/li-chuang-shi-zhan-pai-esp32-s3-kai-fa-ban" target="_blank" title="立創・実戦派 ESP32-S3 é–‹ç™ºãƒœãƒ¼ãƒ‰">立創・実戦派 ESP32-S3 é–‹ç™ºãƒœãƒ¼ãƒ‰</a>
- <a href="https://github.com/espressif/esp-box" target="_blank" title="楽鑫 ESP32-S3-BOX3">楽鑫 ESP32-S3-BOX3</a>
- <a href="https://docs.m5stack.com/zh_CN/core/CoreS3" target="_blank" title="M5Stack CoreS3">M5Stack CoreS3</a>
- <a href="https://docs.m5stack.com/en/atom/Atomic%20Echo%20Base" target="_blank" title="AtomS3R + Echo Base">M5Stack AtomS3R + Echo Base</a>
- <a href="https://gf.bilibili.com/item/detail/1108782064" target="_blank" title="マジックボタン2.4">マジックボタン2.4</a>
- <a href="https://www.waveshare.net/shop/ESP32-S3-Touch-AMOLED-1.8.htm" target="_blank" title="微雪電子 ESP32-S3-Touch-AMOLED-1.8">微雪電子 ESP32-S3-Touch-AMOLED-1.8</a>
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="エビ兄さん Mini C3">エビ兄さん Mini C3</a>
- <a href="https://oshwhub.com/movecall/cuican-ai-pendant-lights-up-y" target="_blank" title="Movecall CuiCan ESP32S3">CuiCan AIペンダント</a>
- <a href="https://github.com/WMnologo/xingzhi-ai" target="_blank" title="無名科技Nologo-星智-1.54">無名科技Nologo-星智-1.54TFT</a>
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
- <a href="https://www.bilibili.com/video/BV1BHJtz6E2S/" target="_blank" title="ESP-HI è¶…低コストロボット犬">ESP-HI è¶…低コストロボット犬</a>
<div style="display: flex; justify-content: space-between;">
  <a href="docs/v1/lichuang-s3.jpg" target="_blank" title="立創・実戦派 ESP32-S3 é–‹ç™ºãƒœãƒ¼ãƒ‰">
    <img src="docs/v1/lichuang-s3.jpg" width="240" />
  </a>
  <a href="docs/v1/espbox3.jpg" target="_blank" title="楽鑫 ESP32-S3-BOX3">
    <img src="docs/v1/espbox3.jpg" width="240" />
  </a>
  <a href="docs/v1/m5cores3.jpg" target="_blank" title="M5Stack CoreS3">
    <img src="docs/v1/m5cores3.jpg" width="240" />
  </a>
  <a href="docs/v1/atoms3r.jpg" target="_blank" title="AtomS3R + Echo Base">
    <img src="docs/v1/atoms3r.jpg" width="240" />
  </a>
  <a href="docs/v1/magiclick.jpg" target="_blank" title="マジックボタン2.4">
    <img src="docs/v1/magiclick.jpg" width="240" />
  </a>
  <a href="docs/v1/waveshare.jpg" target="_blank" title="微雪電子 ESP32-S3-Touch-AMOLED-1.8">
    <img src="docs/v1/waveshare.jpg" width="240" />
  </a>
  <a href="docs/v1/lilygo-t-circle-s3.jpg" target="_blank" title="LILYGO T-Circle-S3">
    <img src="docs/v1/lilygo-t-circle-s3.jpg" width="240" />
  </a>
  <a href="docs/v1/xmini-c3.jpg" target="_blank" title="エビ兄さん Mini C3">
    <img src="docs/v1/xmini-c3.jpg" width="240" />
  </a>
  <a href="docs/v1/movecall-cuican-esp32s3.jpg" target="_blank" title="CuiCan">
    <img src="docs/v1/movecall-cuican-esp32s3.jpg" width="240" />
  </a>
  <a href="docs/v1/wmnologo_xingzhi_1.54.jpg" target="_blank" title="無名科技Nologo-星智-1.54">
    <img src="docs/v1/wmnologo_xingzhi_1.54.jpg" width="240" />
  </a>
  <a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
    <img src="docs/v1/sensecap_watcher.jpg" width="240" />
  </a>
  <a href="docs/v1/esp-hi.jpg" target="_blank" title="ESP-HI è¶…低コストロボット犬">
    <img src="docs/v1/esp-hi.jpg" width="240" />
  </a>
</div>
## ã‚½ãƒ•トウェア
### ãƒ•ァームウェア書き込み
初心者の方は、まず開発環境を構築せずに書き込み可能なファームウェアを使用することをおすすめします。
ファームウェアはデフォルトで公式 [xiaozhi.me](https://xiaozhi.me) ã‚µãƒ¼ãƒãƒ¼ã«æŽ¥ç¶šã—ます。個人ユーザーはアカウント登録でQwenリアルタイムモデルを無料で利用できます。
👉 [初心者向けファームウェア書き込みガイド](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS)
### é–‹ç™ºç’°å¢ƒ
- Cursor ã¾ãŸã¯ VSCode
- ESP-IDFプラグインをインストールし、SDKバージョン5.4以上を選択
- LinuxはWindowsよりも優れており、コンパイルが速く、ドライバの問題も少ない
- æœ¬ãƒ—ロジェクトはGoogle C++コードスタイルを採用、コード提出時は準拠を確認してください
### é–‹ç™ºè€…ドキュメント
- [カスタム開発ボードガイド](docs/custom-board.md) - ã‚·ãƒ£ã‚ªã‚¸ãƒ¼AI用のカスタム開発ボード作成方法
- [MCPプロトコルIoT制御使用法](docs/mcp-usage.md) - MCPプロトコルでIoTデバイスを制御する方法
- [MCPプロトコルインタラクションフロー](docs/mcp-protocol.md) - ãƒ‡ãƒã‚¤ã‚¹å´MCPプロトコルの実装方法
- [MQTT + UDP ãƒã‚¤ãƒ–リッド通信プロトコルドキュメント](docs/mqtt-udp.md)
- [詳細なWebSocket通信プロトコルドキュメント](docs/websocket.md)
## å¤§è¦æ¨¡ãƒ¢ãƒ‡ãƒ«è¨­å®š
すでにシャオジーAIチャットボットデバイスをお持ちで、公式サーバーに接続済みの場合は、[xiaozhi.me](https://xiaozhi.me) ã‚³ãƒ³ã‚½ãƒ¼ãƒ«ã§è¨­å®šã§ãã¾ã™ã€‚
👉 [バックエンド操作ビデオチュートリアル(旧インターフェース)](https://www.bilibili.com/video/BV1jUCUY2EKM/)
## é–¢é€£ã‚ªãƒ¼ãƒ—ンソースプロジェクト
個人PCでサーバーをデプロイする場合は、以下のオープンソースプロジェクトを参照してください:
- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Pythonサーバー
- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Javaサーバー
- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golangサーバー
- [hackers365/xiaozhi-esp32-server-golang](https://github.com/hackers365/xiaozhi-esp32-server-golang) Golangサーバー
シャオジー通信プロトコルを利用した他のクライアントプロジェクト:
- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Pythonクライアント
- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Androidクライアント
- [100askTeam/xiaozhi-linux](http://github.com/100askTeam/xiaozhi-linux) ç™¾å•ç§‘技提供のLinuxクライアント
- [78/xiaozhi-sf32](https://github.com/78/xiaozhi-sf32) æ€æ¾ˆç§‘技のBluetoothチップファームウェア
- [QuecPython/solution-xiaozhiAI](https://github.com/QuecPython/solution-xiaozhiAI) ç§»é æä¾›ã®QuecPythonファームウェア
## ãƒ—ロジェクトについて
これはエビ兄さんがオープンソースで公開しているESP32プロジェクトで、MITライセンスのもと、誰でも無料で、商用利用も可能です。
このプロジェクトを通じて、AIハードウェア開発を理解し、急速に進化する大規模言語モデルを実際のハードウェアデバイスに応用できるようになることを目指しています。
ご意見やご提案があれば、いつでもIssueを提出するか、[Discord](https://discord.gg/C759fGMBcZ) ã¾ãŸã¯ QQグループ:1011329060 ã«ã”参加ください。
## ã‚¹ã‚¿ãƒ¼å±¥æ­´
<a href="https://star-history.com/#78/xiaozhi-esp32&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date" />
 </picture>
</a>
xiaozhi-esp32-2.2.6/README_zh.md
New file
@@ -0,0 +1,169 @@
# An MCP-based Chatbot
(中文 | [English](README.md) | [日本語](README_ja.md))
## ä»‹ç»
👉 [人类:给 AI è£…摄像头 vs AI:当场发现主人三天没洗头【bilibili】](https://www.bilibili.com/video/BV1bpjgzKEhd/)
👉 [手工打造你的 AI å¥³å‹ï¼Œæ–°æ‰‹å…¥é—¨æ•™ç¨‹ã€bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/)
小智 AI èŠå¤©æœºå™¨äººä½œä¸ºä¸€ä¸ªè¯­éŸ³äº¤äº’入口,利用 Qwen / DeepSeek ç­‰å¤§æ¨¡åž‹çš„ AI èƒ½åŠ›ï¼Œé€šè¿‡ MCP åè®®å®žçŽ°å¤šç«¯æŽ§åˆ¶ã€‚
<img src="docs/mcp-based-graph.jpg" alt="通过MCP控制万物" width="320">
### ç‰ˆæœ¬è¯´æ˜Ž
当前 v2 ç‰ˆæœ¬ä¸Ž v1 ç‰ˆæœ¬åˆ†åŒºè¡¨ä¸å…¼å®¹ï¼Œæ‰€ä»¥æ— æ³•从 v1 ç‰ˆæœ¬é€šè¿‡ OTA å‡çº§åˆ° v2 ç‰ˆæœ¬ã€‚分区表说明参见 [partitions/v2/README.md](partitions/v2/README.md)。
使用 v1 ç‰ˆæœ¬çš„æ‰€æœ‰ç¡¬ä»¶ï¼Œå¯ä»¥é€šè¿‡æ‰‹åŠ¨çƒ§å½•å›ºä»¶æ¥å‡çº§åˆ° v2 ç‰ˆæœ¬ã€‚
v1 çš„稳定版本为 1.9.2,可以通过 `git checkout v1` æ¥åˆ‡æ¢åˆ° v1 ç‰ˆæœ¬ï¼Œè¯¥åˆ†æ”¯ä¼šæŒç»­ç»´æŠ¤åˆ° 2026 å¹´ 2 æœˆã€‚
### å·²å®žçŽ°åŠŸèƒ½
- Wi-Fi / ML307 Cat.1 4G
- ç¦»çº¿è¯­éŸ³å”¤é†’ [ESP-SR](https://github.com/espressif/esp-sr)
- æ”¯æŒä¸¤ç§é€šä¿¡åè®®ï¼ˆ[Websocket](docs/websocket_zh.md) æˆ– MQTT+UDP)
- é‡‡ç”¨ OPUS éŸ³é¢‘编解码
- åŸºäºŽæµå¼ ASR + LLM + TTS æž¶æž„的语音交互
- å£°çº¹è¯†åˆ«ï¼Œè¯†åˆ«å½“前说话人的身份 [3D Speaker](https://github.com/modelscope/3D-Speaker)
- OLED / LCD æ˜¾ç¤ºå±ï¼Œæ”¯æŒè¡¨æƒ…显示
- ç”µé‡æ˜¾ç¤ºä¸Žç”µæºç®¡ç†
- æ”¯æŒå¤šè¯­è¨€ï¼ˆä¸­æ–‡ã€è‹±æ–‡ã€æ—¥æ–‡ï¼‰
- æ”¯æŒ ESP32-C3、ESP32-S3、ESP32-P4 èŠ¯ç‰‡å¹³å°
- é€šè¿‡è®¾å¤‡ç«¯ MCP å®žçŽ°è®¾å¤‡æŽ§åˆ¶ï¼ˆéŸ³é‡ã€ç¯å…‰ã€ç”µæœºã€GPIO ç­‰ï¼‰
- é€šè¿‡äº‘端 MCP æ‰©å±•大模型能力(智能家居控制、PC桌面操作、知识搜索、邮件收发等)
- è‡ªå®šä¹‰å”¤é†’词、字体、表情与聊天背景,支持网页端在线修改 ([自定义Assets生成器](https://github.com/78/xiaozhi-assets-generator))
## ç¡¬ä»¶
### é¢åŒ…板手工制作实践
详见飞书文档教程:
👉 [《小智 AI èŠå¤©æœºå™¨äººç™¾ç§‘全书》](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink)
面包板效果图如下:
![面包板效果图](docs/v1/wiring2.jpg)
### æ”¯æŒ 70 å¤šä¸ªå¼€æºç¡¬ä»¶ï¼ˆä»…展示部分)
- <a href="https://oshwhub.com/li-chuang-kai-fa-ban/li-chuang-shi-zhan-pai-esp32-s3-kai-fa-ban" target="_blank" title="立创·实战派 ESP32-S3 å¼€å‘板">立创·实战派 ESP32-S3 å¼€å‘板</a>
- <a href="https://github.com/espressif/esp-box" target="_blank" title="乐鑫 ESP32-S3-BOX3">乐鑫 ESP32-S3-BOX3</a>
- <a href="https://docs.m5stack.com/zh_CN/core/CoreS3" target="_blank" title="M5Stack CoreS3">M5Stack CoreS3</a>
- <a href="https://docs.m5stack.com/en/atom/Atomic%20Echo%20Base" target="_blank" title="AtomS3R + Echo Base">M5Stack AtomS3R + Echo Base</a>
- <a href="https://gf.bilibili.com/item/detail/1108782064" target="_blank" title="神奇按钮 2.4">神奇按钮 2.4</a>
- <a href="https://www.waveshare.net/shop/ESP32-S3-Touch-AMOLED-1.8.htm" target="_blank" title="微雪电子 ESP32-S3-Touch-AMOLED-1.8">微雪电子 ESP32-S3-Touch-AMOLED-1.8</a>
- <a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
- <a href="https://oshwhub.com/tenclass01/xmini_c3" target="_blank" title="虾哥 Mini C3">虾哥 Mini C3</a>
- <a href="https://oshwhub.com/movecall/cuican-ai-pendant-lights-up-y" target="_blank" title="Movecall CuiCan ESP32S3">璀璨·AI åŠå </a>
- <a href="https://github.com/WMnologo/xingzhi-ai" target="_blank" title="无名科技Nologo-星智-1.54">无名科技 Nologo-星智-1.54TFT</a>
- <a href="https://www.seeedstudio.com/SenseCAP-Watcher-W1-A-p-5979.html" target="_blank" title="SenseCAP Watcher">SenseCAP Watcher</a>
- <a href="https://www.bilibili.com/video/BV1BHJtz6E2S/" target="_blank" title="ESP-HI è¶…低成本机器狗">ESP-HI è¶…低成本机器狗</a>
<div style="display: flex; justify-content: space-between;">
  <a href="docs/v1/lichuang-s3.jpg" target="_blank" title="立创·实战派 ESP32-S3 å¼€å‘板">
    <img src="docs/v1/lichuang-s3.jpg" width="240" />
  </a>
  <a href="docs/v1/espbox3.jpg" target="_blank" title="乐鑫 ESP32-S3-BOX3">
    <img src="docs/v1/espbox3.jpg" width="240" />
  </a>
  <a href="docs/v1/m5cores3.jpg" target="_blank" title="M5Stack CoreS3">
    <img src="docs/v1/m5cores3.jpg" width="240" />
  </a>
  <a href="docs/v1/atoms3r.jpg" target="_blank" title="AtomS3R + Echo Base">
    <img src="docs/v1/atoms3r.jpg" width="240" />
  </a>
  <a href="docs/v1/magiclick.jpg" target="_blank" title="神奇按钮 2.4">
    <img src="docs/v1/magiclick.jpg" width="240" />
  </a>
  <a href="docs/v1/waveshare.jpg" target="_blank" title="微雪电子 ESP32-S3-Touch-AMOLED-1.8">
    <img src="docs/v1/waveshare.jpg" width="240" />
  </a>
  <a href="docs/v1/lilygo-t-circle-s3.jpg" target="_blank" title="LILYGO T-Circle-S3">
    <img src="docs/v1/lilygo-t-circle-s3.jpg" width="240" />
  </a>
  <a href="docs/v1/xmini-c3.jpg" target="_blank" title="虾哥 Mini C3">
    <img src="docs/v1/xmini-c3.jpg" width="240" />
  </a>
  <a href="docs/v1/movecall-cuican-esp32s3.jpg" target="_blank" title="CuiCan">
    <img src="docs/v1/movecall-cuican-esp32s3.jpg" width="240" />
  </a>
  <a href="docs/v1/wmnologo_xingzhi_1.54.jpg" target="_blank" title="无名科技Nologo-星智-1.54">
    <img src="docs/v1/wmnologo_xingzhi_1.54.jpg" width="240" />
  </a>
  <a href="docs/v1/sensecap_watcher.jpg" target="_blank" title="SenseCAP Watcher">
    <img src="docs/v1/sensecap_watcher.jpg" width="240" />
  </a>
  <a href="docs/v1/esp-hi.jpg" target="_blank" title="ESP-HI è¶…低成本机器狗">
    <img src="docs/v1/esp-hi.jpg" width="240" />
  </a>
</div>
## è½¯ä»¶
### å›ºä»¶çƒ§å½•
新手第一次操作建议先不要搭建开发环境,直接使用免开发环境烧录的固件。
固件默认接入 [xiaozhi.me](https://xiaozhi.me) å®˜æ–¹æœåŠ¡å™¨ï¼Œä¸ªäººç”¨æˆ·æ³¨å†Œè´¦å·å¯ä»¥å…è´¹ä½¿ç”¨ Qwen å®žæ—¶æ¨¡åž‹ã€‚
👉 [新手烧录固件教程](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS)
### å¼€å‘环境
- Cursor æˆ– VSCode
- å®‰è£… ESP-IDF æ’件,选择 SDK ç‰ˆæœ¬ 5.4 æˆ–以上
- Linux æ¯” Windows æ›´å¥½ï¼Œç¼–译速度快,也免去驱动问题的困扰
- æœ¬é¡¹ç›®ä½¿ç”¨ Google C++ ä»£ç é£Žæ ¼ï¼Œæäº¤ä»£ç æ—¶è¯·ç¡®ä¿ç¬¦åˆè§„范
### å¼€å‘者文档
- [自定义开发板指南](docs/custom-board_zh.md) - å­¦ä¹ å¦‚何为小智 AI åˆ›å»ºè‡ªå®šä¹‰å¼€å‘板
- [MCP åè®®ç‰©è”网控制用法说明](docs/mcp-usage_zh.md) - äº†è§£å¦‚何通过 MCP åè®®æŽ§åˆ¶ç‰©è”网设备
- [MCP åè®®äº¤äº’流程](docs/mcp-protocol_zh.md) - è®¾å¤‡ç«¯ MCP åè®®çš„实现方式
- [MQTT + UDP æ··åˆé€šä¿¡åè®®æ–‡æ¡£](docs/mqtt-udp_zh.md)
- [一份详细的 WebSocket é€šä¿¡åè®®æ–‡æ¡£](docs/websocket_zh.md)
## å¤§æ¨¡åž‹é…ç½®
如果你已经拥有一个小智 AI èŠå¤©æœºå™¨äººè®¾å¤‡ï¼Œå¹¶ä¸”已接入官方服务器,可以登录 [xiaozhi.me](https://xiaozhi.me) æŽ§åˆ¶å°è¿›è¡Œé…ç½®ã€‚
👉 [后台操作视频教程(旧版界面)](https://www.bilibili.com/video/BV1jUCUY2EKM/)
## ç›¸å…³å¼€æºé¡¹ç›®
在个人电脑上部署服务器,可以参考以下第三方开源的项目:
- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python æœåС噍
- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java æœåС噍
- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang æœåС噍
- [hackers365/xiaozhi-esp32-server-golang](https://github.com/hackers365/xiaozhi-esp32-server-golang) Golang æœåС噍
使用小智通信协议的第三方客户端项目:
- [huangjunsen0406/py-xiaozhi](https://github.com/huangjunsen0406/py-xiaozhi) Python å®¢æˆ·ç«¯
- [TOM88812/xiaozhi-android-client](https://github.com/TOM88812/xiaozhi-android-client) Android å®¢æˆ·ç«¯
- [100askTeam/xiaozhi-linux](http://github.com/100askTeam/xiaozhi-linux) ç™¾é—®ç§‘技提供的 Linux å®¢æˆ·ç«¯
- [78/xiaozhi-sf32](https://github.com/78/xiaozhi-sf32) æ€æ¾ˆç§‘技的蓝牙芯片固件
- [QuecPython/solution-xiaozhiAI](https://github.com/QuecPython/solution-xiaozhiAI) ç§»è¿œæä¾›çš„ QuecPython å›ºä»¶
## å…³äºŽé¡¹ç›®
这是一个由虾哥开源的 ESP32 é¡¹ç›®ï¼Œä»¥ MIT è®¸å¯è¯å‘布,允许任何人免费使用,修改或用于商业用途。
我们希望通过这个项目,能够帮助大家了解 AI ç¡¬ä»¶å¼€å‘,将当下飞速发展的大语言模型应用到实际的硬件设备中。
如果你有任何想法或建议,请随时提出 Issues æˆ–加入 [Discord](https://discord.gg/C759fGMBcZ) æˆ– QQ ç¾¤ï¼š1011329060
## Star History
<a href="https://star-history.com/#78/xiaozhi-esp32&Date">
 <picture>
   <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date&theme=dark" />
   <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date" />
   <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=78/xiaozhi-esp32&type=Date" />
 </picture>
</a>
xiaozhi-esp32-2.2.6/docs/blufi.md
New file
@@ -0,0 +1,34 @@
# BluFi Provisioning (with `esp-wifi-connect`)
This document explains how to enable and use BluFi (BLE-based WiFi provisioning) in the XiaoZhi firmware, together with the in-tree `esp-wifi-connect` component that handles WiFi connection and credential storage. See the official [Espressif BluFi documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/ble/blufi.html) for the protocol details.
## Prerequisites
- A chip and firmware configuration that support BLE.
- In `idf.py menuconfig`, enable `WiFi Configuration Method -> Esp Blufi` (`CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING=y`). If you want to use BluFi, disable the Hotspot option in the same menu; otherwise hotspot provisioning wins by default.
- Keep the default NVS and event-loop initialization provided by the project's `app_main`.
- Exactly one of `CONFIG_BT_BLUEDROID_ENABLED` / `CONFIG_BT_NIMBLE_ENABLED` must be selected; they are mutually exclusive.
## Workflow
1. A phone (using the official EspBlufi app or another BluFi client) connects to the device over BLE and sends the target WiFi SSID / password. The phone can also request the list of WiFi networks scanned by the device through the BluFi protocol.
2. In `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP`, the device stores the credentials into `SsidManager` (persisted in NVS by the `esp-wifi-connect` component).
3. The device then launches `WifiStation` to scan and connect; progress is reported back over BluFi.
4. If provisioning succeeds, the device connects to the new WiFi automatically. If it fails, an error status is sent back.
## Steps
1. **Configure**: turn on `Esp Blufi` in menuconfig, then build and flash the firmware.
2. **Trigger provisioning**: at first boot with no stored WiFi credentials the device enters provisioning automatically.
3. **Phone side**: open the EspBlufi app (or another BluFi client), scan and connect to the device, optionally enable encryption, then enter the WiFi SSID / password and send them.
4. **Observe the result**:
   - Success: BluFi reports success and the device connects to WiFi.
   - Failure: BluFi reports failure; retry or check the router.
## Notes
- BluFi cannot be used at the same time as hotspot provisioning. If hotspot provisioning is already enabled, the device will use it. Keep only one provisioning method in menuconfig.
- When running repeated tests, clear or overwrite the stored SSID (`wifi` NVS namespace) to avoid stale credentials interfering with the next run.
- If you write your own BluFi client, follow the official protocol frame format linked above.
- The EspBlufi app download links are listed in the official documentation.
- Because the BluFi API changed in IDF 5.5.2, firmware built with 5.5.2 advertises the Bluetooth name as `"Xiaozhi-Blufi"`, while 5.5.1 uses `"BLUFI_DEVICE"`.
xiaozhi-esp32-2.2.6/docs/blufi_zh.md
New file
@@ -0,0 +1,37 @@
# BluFi é…ç½‘(集成 esp-wifi-connect)
本文档说明如何在小智固件中启用和使用 BluFi(BLE Wi‑Fi é…ç½‘),并结合项目内置的 `esp-wifi-connect` ç»„件完成 Wi‑Fi è¿žæŽ¥ä¸Žå­˜å‚¨ã€‚官方
BluFi
协议说明请参考 [Espressif æ–‡æ¡£](https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-guides/ble/blufi.html)。
## å‰ç½®æ¡ä»¶
- éœ€è¦æ”¯æŒ BLE çš„芯片与固件配置。
- åœ¨ `idf.py menuconfig` ä¸­å¯ç”¨ `WiFi Configuration Method -> Esp Blufi`(`CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING=y`
  ï¼‰ã€‚如果想使用 BluFi,必须关闭同一菜单下的 Hotspot é€‰é¡¹ï¼Œå¦åˆ™é»˜è®¤ä½¿ç”¨ Hotspot é…ç½‘模式。
- ä¿æŒé»˜è®¤çš„ NVS ä¸Žäº‹ä»¶å¾ªçŽ¯åˆå§‹åŒ–ï¼ˆé¡¹ç›®çš„ `app_main` å·²å¤„理)。
- CONFIG_BT_BLUEDROID_ENABLED、CONFIG_BT_NIMBLE_ENABLED这两个宏应二选一,不能同时启用。
## å·¥ä½œæµç¨‹
1) æ‰‹æœºç«¯é€šè¿‡ BluFi(如官方 EspBlufi App æˆ–自研客户端)连接设备,发送 Wi‑Fi SSID/密码,手机端可以通过blufi协议获取设备端扫描到的WiFi列表。
2) è®¾å¤‡ä¾§åœ¨ `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP` ä¸­å°†å‡­æ®å†™å…¥ `SsidManager`(存储到 NVS,属于 `esp-wifi-connect` ç»„件)。
3) éšåŽå¯åЍ `WifiStation` æ‰«æå¹¶è¿žæŽ¥ï¼›çŠ¶æ€é€šè¿‡ BluFi è¿”回。
4) é…ç½‘成功后设备会自动连接新 Wi‑Fi;失败则返回失败状态。
## ä½¿ç”¨æ­¥éª¤
1. é…ç½®ï¼šåœ¨ menuconfig å¼€å¯ `Esp Blufi`。编译并烧录固件。
2. è§¦å‘配网:设备首次启动且没有已保存的 Wi‑Fi æ—¶ä¼šè‡ªåŠ¨è¿›å…¥é…ç½‘ã€‚
3. æ‰‹æœºç«¯æ“ä½œï¼šæ‰“å¼€ EspBlufi App(或其他 BluFi å®¢æˆ·ç«¯ï¼‰ï¼Œæœç´¢å¹¶è¿žæŽ¥è®¾å¤‡ï¼Œå¯ä»¥é€‰æ‹©æ˜¯å¦åŠ å¯†ï¼ŒæŒ‰æç¤ºè¾“å…¥ Wi‑Fi SSID/密码并发送。
4. è§‚察结果:
    - æˆåŠŸï¼šBluFi æŠ¥å‘Šè¿žæŽ¥æˆåŠŸï¼Œè®¾å¤‡è‡ªåŠ¨è¿žæŽ¥ Wi‑Fi。
    - å¤±è´¥ï¼šBluFi è¿”回失败状态,可重新发送或检查路由器。
## æ³¨æ„äº‹é¡¹
- BluFi é…ç½‘不支持与热点配网同时开启。如果热点配网已经启动,则默认使用热点配网。请在 menuconfig ä¸­åªä¿ç•™ä¸€ç§é…ç½‘方式。
- è‹¥å¤šæ¬¡æµ‹è¯•,建议清除或覆盖存储的 SSID(`wifi` å‘½åç©ºé—´ï¼‰ï¼Œé¿å…æ—§é…ç½®å¹²æ‰°ã€‚
- å¦‚果使用自定义 BluFi å®¢æˆ·ç«¯ï¼Œéœ€éµå¾ªå®˜æ–¹åè®®å¸§æ ¼å¼ï¼Œå‚考上文官方文档链接。
- å®˜æ–¹æ–‡æ¡£ä¸­å·²æä¾›EspBlufi APP下载地址
- ç”±äºŽIDF5.5.2的blufi接口发生变化,5.5.2版本编译后蓝牙名称为"Xiaozhi-Blufi",5.5.1版本中蓝牙名称为"BLUFI_DEVICE"
xiaozhi-esp32-2.2.6/docs/code_style.md
New file
@@ -0,0 +1,90 @@
# Code Style Guide
## Formatting Tool
This project uses `clang-format` to keep the code style consistent. The `.clang-format` file in the project root is based on the Google C++ style guide with a few project-specific tweaks.
### Installing clang-format
Make sure `clang-format` is available before you use it:
- **Windows**:
  ```powershell
  winget install LLVM
  # or with Chocolatey
  choco install llvm
  ```
- **Linux**:
  ```bash
  sudo apt install clang-format              # Ubuntu/Debian
  sudo dnf install clang-tools-extra         # Fedora
  ```
- **macOS**:
  ```bash
  brew install clang-format
  ```
### Usage
1. **Format a single file**:
   ```bash
   clang-format -i path/to/your/file.cpp
   ```
2. **Format the entire project**:
   ```bash
   # Run from the project root
   find main -iname '*.h' -o -iname '*.cc' | xargs clang-format -i
   ```
3. **Check formatting without modifying files (useful in CI / pre-commit)**:
   ```bash
   clang-format --dry-run -Werror path/to/your/file.cpp
   ```
### IDE Integration
- **Visual Studio Code**:
  1. Install the C/C++ extension.
  2. Set `C_Cpp.formatting` to `clangFormat` in settings.
  3. Optionally enable `editor.formatOnSave`.
- **CLion**:
  1. Open `Editor > Code Style > C/C++` in the settings.
  2. Set `Formatter` to `clang-format`.
  3. Choose "use the .clang-format file in the project".
### Main Rules
- Indent with 4 spaces.
- Line width capped at 100 characters.
- Attach-style braces (`{` on the same line as the control statement).
- Pointers and references bind to the type (left alignment).
- Includes are sorted automatically.
- Access specifiers are indented by -4 spaces.
### Notes
1. Make sure the code has been formatted before committing.
2. Do not fix up alignment by hand after running clang-format.
3. To exclude a block from formatting, wrap it with:
   ```cpp
   // clang-format off
   your code
   // clang-format on
   ```
### FAQ
1. **Formatting fails**:
   - Check whether `clang-format` is too old.
   - Make sure the file is UTF-8 encoded.
   - Validate the syntax of your `.clang-format` file.
2. **Output differs from what you expected**:
   - Verify that the `.clang-format` in the project root is actually picked up.
   - Make sure no other `.clang-format` higher in the tree is winning.
Questions and suggestions are welcome - please open an issue or a pull request.
xiaozhi-esp32-2.2.6/docs/code_style_zh.md
New file
@@ -0,0 +1,91 @@
# ä»£ç é£Žæ ¼æŒ‡å—
## ä»£ç æ ¼å¼åŒ–工具
本项目使用 clang-format å·¥å…·æ¥ç»Ÿä¸€ä»£ç é£Žæ ¼ã€‚我们已经在项目根目录下提供了 `.clang-format` é…ç½®æ–‡ä»¶ï¼Œè¯¥é…ç½®åŸºäºŽ Google C++ é£Žæ ¼æŒ‡å—,并做了一些自定义调整。
### å®‰è£… clang-format
在使用之前,请确保你已经安装了 clang-format å·¥å…·ï¼š
- **Windows**:
  ```powershell
  winget install LLVM
  # æˆ–者使用 Chocolatey
  choco install llvm
  ```
- **Linux**:
  ```bash
  sudo apt install clang-format  # Ubuntu/Debian
  sudo dnf install clang-tools-extra  # Fedora
  ```
- **macOS**:
  ```bash
  brew install clang-format
  ```
### ä½¿ç”¨æ–¹æ³•
1. **格式化单个文件**:
   ```bash
   clang-format -i path/to/your/file.cpp
   ```
2. **格式化整个项目**:
   ```bash
   # åœ¨é¡¹ç›®æ ¹ç›®å½•下执行
   find main -iname *.h -o -iname *.cc | xargs clang-format -i
   ```
3. **在提交代码前检查格式**:
   ```bash
   # æ£€æŸ¥æ–‡ä»¶æ ¼å¼æ˜¯å¦ç¬¦åˆè§„范(不修改文件)
   clang-format --dry-run -Werror path/to/your/file.cpp
   ```
### IDE é›†æˆ
- **Visual Studio Code**:
  1. å®‰è£… C/C++ æ‰©å±•
  2. åœ¨è®¾ç½®ä¸­å¯ç”¨ `C_Cpp.formatting` ä¸º `clang-format`
  3. å¯ä»¥è®¾ç½®ä¿å­˜æ—¶è‡ªåŠ¨æ ¼å¼åŒ–ï¼š`editor.formatOnSave: true`
- **CLion**:
  1. åœ¨è®¾ç½®ä¸­é€‰æ‹© `Editor > Code Style > C/C++`
  2. å°† `Formatter` è®¾ç½®ä¸º `clang-format`
  3. é€‰æ‹©ä½¿ç”¨é¡¹ç›®ä¸­çš„ `.clang-format` é…ç½®æ–‡ä»¶
### ä¸»è¦æ ¼å¼è§„则
- ç¼©è¿›ä½¿ç”¨ 4 ä¸ªç©ºæ ¼
- è¡Œå®½é™åˆ¶ä¸º 100 å­—符
- å¤§æ‹¬å·é‡‡ç”¨ Attach é£Žæ ¼ï¼ˆä¸ŽæŽ§åˆ¶è¯­å¥åœ¨åŒä¸€è¡Œï¼‰
- æŒ‡é’ˆå’Œå¼•用符号靠左对齐
- è‡ªåŠ¨æŽ’åºå¤´æ–‡ä»¶åŒ…å«
- ç±»è®¿é—®ä¿®é¥°ç¬¦ç¼©è¿›ä¸º -4 ç©ºæ ¼
### æ³¨æ„äº‹é¡¹
1. æäº¤ä»£ç å‰è¯·ç¡®ä¿ä»£ç å·²ç»è¿‡æ ¼å¼åŒ–
2. ä¸è¦æ‰‹åŠ¨è°ƒæ•´å·²æ ¼å¼åŒ–çš„ä»£ç å¯¹é½
3. å¦‚果某段代码不希望被格式化,可以使用以下注释包围:
   ```cpp
   // clang-format off
   // ä½ çš„代码
   // clang-format on
   ```
### å¸¸è§é—®é¢˜
1. **格式化失败**:
   - æ£€æŸ¥ clang-format ç‰ˆæœ¬æ˜¯å¦è¿‡ä½Ž
   - ç¡®è®¤æ–‡ä»¶ç¼–码为 UTF-8
   - éªŒè¯ .clang-format æ–‡ä»¶è¯­æ³•是否正确
2. **与期望格式不符**:
   - æ£€æŸ¥æ˜¯å¦ä½¿ç”¨äº†é¡¹ç›®æ ¹ç›®å½•下的 .clang-format é…ç½®
   - ç¡®è®¤æ²¡æœ‰å…¶ä»–位置的 .clang-format æ–‡ä»¶è¢«ä¼˜å…ˆä½¿ç”¨
如有任何问题或建议,欢迎提出 issue æˆ– pull request。
xiaozhi-esp32-2.2.6/docs/custom-board.md
New file
@@ -0,0 +1,473 @@
# Custom Board Guide
This guide describes how to add a new board to the XiaoZhi AI voice assistant project. XiaoZhi AI supports 70+ ESP32-series boards; each one lives in its own directory under `main/boards/`.
## Important
> **Warning**: for a custom board whose IO configuration differs from an existing board, never overwrite the original board's configuration. Always create a new board type - or use the `builds` array in `config.json` to produce a distinct firmware name with different `sdkconfig` macros. Use `python scripts/release.py [board-directory]` to build and package the firmware.
>
> Overwriting an existing board's configuration is dangerous because OTA updates may replace your custom firmware with the stock firmware for the original board. Every board must have a unique identity and its own firmware update channel.
## Directory Layout
A board directory typically contains:
- `xxx_board.cc` - board-level initialization and glue code.
- `config.h` - pin assignments and board-level settings.
- `config.json` - build configuration consumed by `scripts/release.py`.
- `README.md` - board-specific notes.
Boards can live directly under `main/boards/` or be grouped by manufacturer under `main/boards/<manufacturer>/<board>/` (see [Manufacturer Sub-directories](#manufacturer-sub-directories) below).
## Steps
### 1. Create the Board Directory
Create a new directory under `main/boards/` using the `[vendor]-[model]` naming style (e.g. `m5stack-tab5`):
```bash
mkdir main/boards/my-custom-board
```
### 2. Create the Configuration Files
#### config.h
Define all hardware settings in `config.h`:
- Audio sample rates and I2S pin mapping.
- Audio codec I2C address and pins.
- Button and LED pins.
- Display parameters and pins.
Example (from `lichuang-c3-dev`):
```c
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
// Audio
#define AUDIO_INPUT_SAMPLE_RATE  24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10
#define AUDIO_I2S_GPIO_WS   GPIO_NUM_12
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN  GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
#define AUDIO_CODEC_PA_PIN       GPIO_NUM_13
#define AUDIO_CODEC_I2C_SDA_PIN  GPIO_NUM_0
#define AUDIO_CODEC_I2C_SCL_PIN  GPIO_NUM_1
#define AUDIO_CODEC_ES8311_ADDR  ES8311_CODEC_DEFAULT_ADDR
// Buttons
#define BOOT_BUTTON_GPIO        GPIO_NUM_9
// Display
#define DISPLAY_SPI_SCK_PIN     GPIO_NUM_3
#define DISPLAY_SPI_MOSI_PIN    GPIO_NUM_5
#define DISPLAY_DC_PIN          GPIO_NUM_6
#define DISPLAY_SPI_CS_PIN      GPIO_NUM_4
#define DISPLAY_WIDTH   320
#define DISPLAY_HEIGHT  240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X  0
#define DISPLAY_OFFSET_Y  0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_
```
#### config.json
`config.json` drives `scripts/release.py`:
```json
{
    "target": "esp32s3",
    "builds": [
        {
            "name": "my-custom-board",
            "sdkconfig_append": [
                "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
                "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
            ]
        }
    ]
}
```
**Fields**:
- `target`: target chip, must match the real hardware (`esp32`, `esp32s3`, `esp32c3`, `esp32c6`, `esp32p4`, ...).
- `name`: firmware package name; typically matches the directory name.
- `sdkconfig_append`: extra sdkconfig lines merged into the defaults.
**Common `sdkconfig_append` entries**:
```json
// Flash size
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y"
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y"
"CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y"
// Partition table
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\""
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\""
// Language
"CONFIG_LANGUAGE_EN_US=y"
"CONFIG_LANGUAGE_ZH_CN=y"
// Wake word configuration
"CONFIG_USE_DEVICE_AEC=y"          // enable on-device AEC
"CONFIG_WAKE_WORD_DISABLED=y"      // disable wake word detection
```
### 3. Implement the Board Class
Create `my_custom_board.cc` containing the board-level implementation.
A basic board class has:
1. **Class declaration**: derive from `WifiBoard` or `Ml307Board`.
2. **Initialization helpers**: I2C, display, buttons, IoT/MCP tools, etc.
3. **Virtual overrides**: `GetAudioCodec()`, `GetDisplay()`, `GetBacklight()`, ...
4. **Board registration**: `DECLARE_BOARD(ClassName)`.
```cpp
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#define TAG "MyCustomBoard"
class MyCustomBoard : public WifiBoard {
private:
    i2c_master_bus_handle_t codec_i2c_bus_;
    Button boot_button_;
    LcdDisplay* display_;
    void InitializeI2c() {
        i2c_master_bus_config_t i2c_bus_cfg = {
            .i2c_port = I2C_NUM_0,
            .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
            .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
            .clk_source = I2C_CLK_SRC_DEFAULT,
            .glitch_ignore_cnt = 7,
            .intr_priority = 0,
            .trans_queue_depth = 0,
            .flags = {
                .enable_internal_pullup = 1,
            },
        };
        ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
    }
    void InitializeSpi() {
        spi_bus_config_t buscfg = {};
        buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
        buscfg.miso_io_num = GPIO_NUM_NC;
        buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
        buscfg.quadwp_io_num = GPIO_NUM_NC;
        buscfg.quadhd_io_num = GPIO_NUM_NC;
        buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
        ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
    }
    void InitializeButtons() {
        boot_button_.OnClick([this]() {
            auto& app = Application::GetInstance();
            if (app.GetDeviceState() == kDeviceStateStarting) {
                EnterWifiConfigMode();
                return;
            }
            app.ToggleChatState();
        });
    }
    void InitializeDisplay() {
        esp_lcd_panel_io_handle_t panel_io = nullptr;
        esp_lcd_panel_handle_t panel = nullptr;
        esp_lcd_panel_io_spi_config_t io_config = {};
        io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
        io_config.dc_gpio_num = DISPLAY_DC_PIN;
        io_config.spi_mode = 2;
        io_config.pclk_hz = 80 * 1000 * 1000;
        io_config.trans_queue_depth = 10;
        io_config.lcd_cmd_bits = 8;
        io_config.lcd_param_bits = 8;
        ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
        esp_lcd_panel_dev_config_t panel_config = {};
        panel_config.reset_gpio_num = GPIO_NUM_NC;
        panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
        panel_config.bits_per_pixel = 16;
        ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
        esp_lcd_panel_reset(panel);
        esp_lcd_panel_init(panel);
        esp_lcd_panel_invert_color(panel, true);
        esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
        esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
        display_ = new SpiLcdDisplay(panel_io, panel,
                                    DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                    DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
                                    DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
    }
    void InitializeTools() {
        // Register MCP tools here; see docs/mcp-usage.md.
    }
public:
    MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
        InitializeI2c();
        InitializeSpi();
        InitializeDisplay();
        InitializeButtons();
        InitializeTools();
        GetBacklight()->SetBrightness(100);
    }
    virtual AudioCodec* GetAudioCodec() override {
        static Es8311AudioCodec audio_codec(
            codec_i2c_bus_,
            I2C_NUM_0,
            AUDIO_INPUT_SAMPLE_RATE,
            AUDIO_OUTPUT_SAMPLE_RATE,
            AUDIO_I2S_GPIO_MCLK,
            AUDIO_I2S_GPIO_BCLK,
            AUDIO_I2S_GPIO_WS,
            AUDIO_I2S_GPIO_DOUT,
            AUDIO_I2S_GPIO_DIN,
            AUDIO_CODEC_PA_PIN,
            AUDIO_CODEC_ES8311_ADDR);
        return &audio_codec;
    }
    virtual Display* GetDisplay() override {
        return display_;
    }
    virtual Backlight* GetBacklight() override {
        static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
        return &backlight;
    }
};
DECLARE_BOARD(MyCustomBoard);
```
### 4. Hook Up the Build System
#### Add a Kconfig entry
In `main/Kconfig.projbuild`, add an entry to the `choice BOARD_TYPE` block:
```kconfig
choice BOARD_TYPE
    prompt "Board Type"
    default BOARD_TYPE_BREAD_COMPACT_WIFI
    help
        Board type.
    # ... other entries ...
    config BOARD_TYPE_MY_CUSTOM_BOARD
        bool "My Custom Board"
        depends on IDF_TARGET_ESP32S3  # pick the matching target
endchoice
```
Notes:
- The identifier must be uppercase and underscore-separated.
- `depends on` restricts the entry to the correct target (`IDF_TARGET_ESP32S3`, `IDF_TARGET_ESP32C3`, ...).
- The label can be localized.
#### Add a branch in CMakeLists.txt
Open `main/CMakeLists.txt` and extend the board-type chain:
```cmake
elseif(CONFIG_BOARD_TYPE_MY_CUSTOM_BOARD)
    set(BOARD_TYPE "my-custom-board")                # must match the directory name
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)     # pick a font for the display
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)         // optional, for emoji display
```
**Font and emoji guidance**:
Pick a font size that matches the display resolution:
- Small (128x64 OLED): `font_puhui_basic_14_1` / `font_awesome_14_1`
- Small-medium (240x240): `font_puhui_basic_16_4` / `font_awesome_16_4`
- Medium (240x320): `font_puhui_basic_20_4` / `font_awesome_20_4`
- Large (480x320+): `font_puhui_basic_30_4` / `font_awesome_30_4`
Emoji collections:
- `twemoji_32` - 32x32 pixels (small screens).
- `twemoji_64` - 64x64 pixels (large screens).
### 5. Build and Flash
#### Option A - use `idf.py` manually
1. Set the target chip (first time, or when switching targets):
   ```bash
   idf.py set-target esp32s3     # ESP32-S3
   idf.py set-target esp32c3     # ESP32-C3
   idf.py set-target esp32       # ESP32
   ```
2. Clean stale configuration:
   ```bash
   idf.py fullclean
   ```
3. Select the board via menuconfig:
   ```bash
   idf.py menuconfig
   ```
   Navigate to `Xiaozhi Assistant -> Board Type` and choose your board.
4. Build and flash:
   ```bash
   idf.py build
   idf.py flash monitor
   ```
#### Option B - use `release.py` (recommended)
If the board directory contains a `config.json`, you can build and package automatically:
```bash
python scripts/release.py my-custom-board
```
The script:
- Reads `target` from `config.json` and calls `idf.py set-target`.
- Appends the entries listed in `sdkconfig_append`.
- Builds and packages the firmware.
### 6. Write the README
In `README.md`, describe the board, hardware requirements, build instructions, and any special notes.
## Manufacturer Sub-directories
Boards can be grouped by manufacturer under `main/boards/<manufacturer>/<board>/`. This is the recommended layout when a single vendor ships several variants - for example `main/boards/waveshare/esp32-p4-nano/` or `main/boards/lceda-course-examples/eda-tv-pro/`.
To enable the layout, set the `MANUFACTURER` variable in `main/CMakeLists.txt` for your board:
```cmake
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-nano")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
```
When `MANUFACTURER` is set, the build system globs source files from `main/boards/${MANUFACTURER}/${BOARD_TYPE}/`. When it is empty, it falls back to the flat `main/boards/${BOARD_TYPE}/` layout.
Rules of thumb:
- Use the manufacturer layout when you have two or more boards from the same vendor that share drivers, assets, or documentation.
- Use the flat layout for one-off boards and community examples.
- Directory names use lowercase with dashes (e.g. `waveshare`, `lceda-course-examples`).
## Common Board Components
Several reusable components live in `main/boards/common/`. You can include them directly from your board class:
### Display drivers
Supported LCD families include:
- ST7789 (SPI)
- ILI9341 (SPI)
- SH8601 (QSPI)
- and many more.
### Audio codecs
- `Es8311AudioCodec` (most common)
- `Es8374AudioCodec`
- `Es8388AudioCodec`
- `Es8389AudioCodec`
- `BoxAudioCodec` (ES7210 mic array + codec combo used on ESP-Box boards)
- `NoAudioCodec` (direct I2S without external codec)
- `DummyAudioCodec` (placeholder for boards without audio)
### Power management
- `Axp2101` power management IC helpers.
- `Sy6970` battery charger helpers.
- `AdcBatteryMonitor` - simple ADC-based battery voltage monitor.
- `PowerSaveTimer` / `SleepTimer` - helpers for light-sleep scheduling.
### Networking
- `WifiBoard` - WiFi-only base class.
- `Ml307Board` / `Nt26Board` - 4G modem base classes.
- `DualNetworkBoard` - switchable WiFi / 4G base class.
- `RndisBoard` - RNDIS-over-USB networking (ESP32-S3 / ESP32-P4).
- `EspVideo` helpers for ESP-Video on ESP32-S3 / ESP32-P4.
### Input helpers
- `Button` - standard push buttons (click, long-press, multi-click).
- `Knob` - rotary encoder wrapper.
- `PressToTalkMcpTool` - push-to-talk tool that registers itself through MCP.
- `AfskDemod` - AFSK demodulator used by some acoustic provisioning flows.
- `SystemReset` - helper that performs a safe factory reset when a button is held at boot.
### MCP integration
Any board can register custom tools - speaker control, screen brightness, battery readout, light control, etc. See [MCP IoT control usage](./mcp-usage.md).
## Board Class Hierarchy
- `Board` - base class
  - `WifiBoard` - WiFi-connected board
  - `Ml307Board` / `Nt26Board` - 4G modem boards
  - `DualNetworkBoard` - WiFi + 4G switchable board
  - `RndisBoard` - RNDIS-over-USB board
## Tips
1. **Start from a similar board** - copying and tweaking an existing board is usually faster than starting from scratch.
2. **Bring up incrementally** - get the display up first, then audio, then the full stack.
3. **Double check pin assignments** - every pin defined in `config.h` must match your schematic.
4. **Check hardware compatibility** - especially codec / PMIC / touch controller combinations.
## Troubleshooting
1. **Display looks wrong** - verify SPI configuration, mirroring, and color inversion.
2. **No audio** - check I2S wiring, PA enable pin, and codec I2C address.
3. **Cannot connect to WiFi** - re-check WiFi credentials and provisioning method.
4. **Cannot reach the server** - verify the WebSocket / MQTT endpoint configuration.
## References
- ESP-IDF documentation: https://docs.espressif.com/projects/esp-idf/
- LVGL documentation: https://docs.lvgl.io/
- ESP-SR documentation: https://github.com/espressif/esp-sr
xiaozhi-esp32-2.2.6/docs/custom-board_zh.md
New file
@@ -0,0 +1,453 @@
# è‡ªå®šä¹‰å¼€å‘板指南
本指南介绍如何为小智AI语音聊天机器人项目定制一个新的开发板初始化程序。小智AI支持70多种ESP32系列开发板,每个开发板的初始化代码都放在对应的目录下。
## é‡è¦æç¤º
> **警告**: å¯¹äºŽè‡ªå®šä¹‰å¼€å‘板,当IO配置与原有开发板不同时,切勿直接覆盖原有开发板的配置编译固件。必须创建新的开发板类型,或者通过config.json文件中的builds配置不同的name和sdkconfig宏定义来区分。使用 `python scripts/release.py [开发板目录名字]` æ¥ç¼–译打包固件。
>
> å¦‚果直接覆盖原有配置,将来OTA升级时,您的自定义固件可能会被原有开发板的标准固件覆盖,导致您的设备无法正常工作。每个开发板有唯一的标识和对应的固件升级通道,保持开发板标识的唯一性非常重要。
## ç›®å½•结构
每个开发板的目录结构通常包含以下文件:
- `xxx_board.cc` - ä¸»è¦çš„æ¿çº§åˆå§‹åŒ–代码,实现了板子相关的初始化和功能
- `config.h` - æ¿çº§é…ç½®æ–‡ä»¶ï¼Œå®šä¹‰äº†ç¡¬ä»¶ç®¡è„šæ˜ å°„å’Œå…¶ä»–é…ç½®é¡¹
- `config.json` - ç¼–译配置,指定目标芯片和特殊的编译选项
- `README.md` - å¼€å‘板相关的说明文档
## å®šåˆ¶å¼€å‘板步骤
### 1. åˆ›å»ºæ–°çš„开发板目录
首先在`boards/`目录下创建一个新的目录,命名方式应使用 `[品牌名]-[开发板类型]` çš„形式,例如 `m5stack-tab5`:
```bash
mkdir main/boards/my-custom-board
```
### 2. åˆ›å»ºé…ç½®æ–‡ä»¶
#### config.h
在`config.h`中定义所有的硬件配置,包括:
- éŸ³é¢‘采样率和I2S引脚配置
- éŸ³é¢‘编解码芯片地址和I2C引脚配置
- æŒ‰é’®å’ŒLED引脚配置
- æ˜¾ç¤ºå±å‚数和引脚配置
参考示例(来自lichuang-c3-dev):
```c
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
// éŸ³é¢‘配置
#define AUDIO_INPUT_SAMPLE_RATE  24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10
#define AUDIO_I2S_GPIO_WS   GPIO_NUM_12
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
#define AUDIO_I2S_GPIO_DIN  GPIO_NUM_7
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
#define AUDIO_CODEC_PA_PIN       GPIO_NUM_13
#define AUDIO_CODEC_I2C_SDA_PIN  GPIO_NUM_0
#define AUDIO_CODEC_I2C_SCL_PIN  GPIO_NUM_1
#define AUDIO_CODEC_ES8311_ADDR  ES8311_CODEC_DEFAULT_ADDR
// æŒ‰é’®é…ç½®
#define BOOT_BUTTON_GPIO        GPIO_NUM_9
// æ˜¾ç¤ºå±é…ç½®
#define DISPLAY_SPI_SCK_PIN     GPIO_NUM_3
#define DISPLAY_SPI_MOSI_PIN    GPIO_NUM_5
#define DISPLAY_DC_PIN          GPIO_NUM_6
#define DISPLAY_SPI_CS_PIN      GPIO_NUM_4
#define DISPLAY_WIDTH   320
#define DISPLAY_HEIGHT  240
#define DISPLAY_MIRROR_X true
#define DISPLAY_MIRROR_Y false
#define DISPLAY_SWAP_XY true
#define DISPLAY_OFFSET_X  0
#define DISPLAY_OFFSET_Y  0
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
#endif // _BOARD_CONFIG_H_
```
#### config.json
在`config.json`中定义编译配置,这个文件用于 `scripts/release.py` è„šæœ¬è‡ªåŠ¨åŒ–ç¼–è¯‘ï¼š
```json
{
    "target": "esp32s3",  // ç›®æ ‡èŠ¯ç‰‡åž‹å·: esp32, esp32s3, esp32c3, esp32c6, esp32p4等
    "builds": [
        {
            "name": "my-custom-board",  // å¼€å‘板名称,用于生成固件包
            "sdkconfig_append": [
                // ç‰¹åˆ« Flash å¤§å°é…ç½®
                "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
                // ç‰¹åˆ«åˆ†åŒºè¡¨é…ç½®
                "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
            ]
        }
    ]
}
```
**配置项说明:**
- `target`: ç›®æ ‡èŠ¯ç‰‡åž‹å·ï¼Œå¿…é¡»ä¸Žç¡¬ä»¶åŒ¹é…
- `name`: ç¼–译输出的固件包名称,建议与目录名一致
- `sdkconfig_append`: é¢å¤–çš„ sdkconfig é…ç½®é¡¹æ•°ç»„,会追加到默认配置中
**常用的 sdkconfig_append é…ç½®ï¼š**
```json
// Flash å¤§å°
"CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y"   // 4MB Flash
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y"   // 8MB Flash
"CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y"  // 16MB Flash
// åˆ†åŒºè¡¨
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\""  // 4MB åˆ†åŒºè¡¨
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""  // 8MB åˆ†åŒºè¡¨
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"" // 16MB åˆ†åŒºè¡¨
// è¯­è¨€é…ç½®
"CONFIG_LANGUAGE_EN_US=y"  // è‹±è¯­
"CONFIG_LANGUAGE_ZH_CN=y"  // ç®€ä½“中文
// å”¤é†’词配置
"CONFIG_USE_DEVICE_AEC=y"          // å¯ç”¨è®¾å¤‡ç«¯ AEC
"CONFIG_WAKE_WORD_DISABLED=y"      // ç¦ç”¨å”¤é†’词
```
### 3. ç¼–写板级初始化代码
创建一个`my_custom_board.cc`文件,实现开发板的所有初始化逻辑。
一个基本的开发板类定义包含以下几个部分:
1. **类定义**:继承自`WifiBoard`或`Ml307Board`
2. **初始化函数**:包括I2C、显示屏、按钮、IoT等组件的初始化
3. **虚函数重写**:如`GetAudioCodec()`、`GetDisplay()`、`GetBacklight()`等
4. **注册开发板**:使用`DECLARE_BOARD`宏注册开发板
```cpp
#include "wifi_board.h"
#include "codecs/es8311_audio_codec.h"
#include "display/lcd_display.h"
#include "application.h"
#include "button.h"
#include "config.h"
#include "mcp_server.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/spi_common.h>
#define TAG "MyCustomBoard"
class MyCustomBoard : public WifiBoard {
private:
    i2c_master_bus_handle_t codec_i2c_bus_;
    Button boot_button_;
    LcdDisplay* display_;
    // I2C初始化
    void InitializeI2c() {
        i2c_master_bus_config_t i2c_bus_cfg = {
            .i2c_port = I2C_NUM_0,
            .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
            .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
            .clk_source = I2C_CLK_SRC_DEFAULT,
            .glitch_ignore_cnt = 7,
            .intr_priority = 0,
            .trans_queue_depth = 0,
            .flags = {
                .enable_internal_pullup = 1,
            },
        };
        ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
    }
    // SPI初始化(用于显示屏)
    void InitializeSpi() {
        spi_bus_config_t buscfg = {};
        buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
        buscfg.miso_io_num = GPIO_NUM_NC;
        buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
        buscfg.quadwp_io_num = GPIO_NUM_NC;
        buscfg.quadhd_io_num = GPIO_NUM_NC;
        buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
        ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
    }
    // æŒ‰é’®åˆå§‹åŒ–
    void InitializeButtons() {
        boot_button_.OnClick([this]() {
            auto& app = Application::GetInstance();
            if (app.GetDeviceState() == kDeviceStateStarting) {
                EnterWifiConfigMode();
                return;
            }
            app.ToggleChatState();
        });
    }
    // æ˜¾ç¤ºå±åˆå§‹åŒ–(以ST7789为例)
    void InitializeDisplay() {
        esp_lcd_panel_io_handle_t panel_io = nullptr;
        esp_lcd_panel_handle_t panel = nullptr;
        esp_lcd_panel_io_spi_config_t io_config = {};
        io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
        io_config.dc_gpio_num = DISPLAY_DC_PIN;
        io_config.spi_mode = 2;
        io_config.pclk_hz = 80 * 1000 * 1000;
        io_config.trans_queue_depth = 10;
        io_config.lcd_cmd_bits = 8;
        io_config.lcd_param_bits = 8;
        ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
        esp_lcd_panel_dev_config_t panel_config = {};
        panel_config.reset_gpio_num = GPIO_NUM_NC;
        panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
        panel_config.bits_per_pixel = 16;
        ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
        esp_lcd_panel_reset(panel);
        esp_lcd_panel_init(panel);
        esp_lcd_panel_invert_color(panel, true);
        esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
        esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
        // åˆ›å»ºæ˜¾ç¤ºå±å¯¹è±¡
        display_ = new SpiLcdDisplay(panel_io, panel,
                                    DISPLAY_WIDTH, DISPLAY_HEIGHT,
                                    DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
                                    DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
    }
    // MCP Tools åˆå§‹åŒ–
    void InitializeTools() {
        // å‚考 MCP æ–‡æ¡£
    }
public:
    // æž„造函数
    MyCustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
        InitializeI2c();
        InitializeSpi();
        InitializeDisplay();
        InitializeButtons();
        InitializeTools();
        GetBacklight()->SetBrightness(100);
    }
    // èŽ·å–éŸ³é¢‘ç¼–è§£ç å™¨
    virtual AudioCodec* GetAudioCodec() override {
        static Es8311AudioCodec audio_codec(
            codec_i2c_bus_,
            I2C_NUM_0,
            AUDIO_INPUT_SAMPLE_RATE,
            AUDIO_OUTPUT_SAMPLE_RATE,
            AUDIO_I2S_GPIO_MCLK,
            AUDIO_I2S_GPIO_BCLK,
            AUDIO_I2S_GPIO_WS,
            AUDIO_I2S_GPIO_DOUT,
            AUDIO_I2S_GPIO_DIN,
            AUDIO_CODEC_PA_PIN,
            AUDIO_CODEC_ES8311_ADDR);
        return &audio_codec;
    }
    // èŽ·å–æ˜¾ç¤ºå±
    virtual Display* GetDisplay() override {
        return display_;
    }
    // èŽ·å–èƒŒå…‰æŽ§åˆ¶
    virtual Backlight* GetBacklight() override {
        static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
        return &backlight;
    }
};
// æ³¨å†Œå¼€å‘板
DECLARE_BOARD(MyCustomBoard);
```
### 4. æ·»åŠ æž„å»ºç³»ç»Ÿé…ç½®
#### åœ¨ Kconfig.projbuild ä¸­æ·»åŠ å¼€å‘æ¿é€‰é¡¹
打开 `main/Kconfig.projbuild` æ–‡ä»¶ï¼Œåœ¨ `choice BOARD_TYPE` éƒ¨åˆ†æ·»åŠ æ–°çš„å¼€å‘æ¿é…ç½®é¡¹ï¼š
```kconfig
choice BOARD_TYPE
    prompt "Board Type"
    default BOARD_TYPE_BREAD_COMPACT_WIFI
    help
        Board type. å¼€å‘板类型
    # ... å…¶ä»–开发板选项 ...
    config BOARD_TYPE_MY_CUSTOM_BOARD
        bool "My Custom Board (我的自定义开发板)"
        depends on IDF_TARGET_ESP32S3  # æ ¹æ®ä½ çš„目标芯片修改
endchoice
```
**注意事项:**
- `BOARD_TYPE_MY_CUSTOM_BOARD` æ˜¯é…ç½®é¡¹åç§°ï¼Œéœ€è¦å…¨å¤§å†™ï¼Œä½¿ç”¨ä¸‹åˆ’线分隔
- `depends on` æŒ‡å®šäº†ç›®æ ‡èŠ¯ç‰‡ç±»åž‹ï¼ˆå¦‚ `IDF_TARGET_ESP32S3`、`IDF_TARGET_ESP32C3` ç­‰ï¼‰
- æè¿°æ–‡å­—可以使用中英文
#### åœ¨ CMakeLists.txt ä¸­æ·»åŠ å¼€å‘æ¿é…ç½®
打开 `main/CMakeLists.txt` æ–‡ä»¶ï¼Œåœ¨å¼€å‘板类型判断部分添加新的配置:
```cmake
# åœ¨ elseif é“¾ä¸­æ·»åŠ ä½ çš„å¼€å‘æ¿é…ç½®
elseif(CONFIG_BOARD_TYPE_MY_CUSTOM_BOARD)
    set(BOARD_TYPE "my-custom-board")  # ä¸Žç›®å½•名一致
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)  # æ ¹æ®å±å¹•大小选择合适的字体
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)  # å¯é€‰ï¼Œå¦‚果需要表情显示
endif()
```
**字体和表情配置说明:**
根据屏幕分辨率选择合适的字体大小:
- å°å±å¹•(128x64 OLED):`font_puhui_basic_14_1` / `font_awesome_14_1`
- ä¸­å°å±å¹•(240x240):`font_puhui_basic_16_4` / `font_awesome_16_4`
- ä¸­ç­‰å±å¹•(240x320):`font_puhui_basic_20_4` / `font_awesome_20_4`
- å¤§å±å¹•(480x320+):`font_puhui_basic_30_4` / `font_awesome_30_4`
表情集合选项:
- `twemoji_32` - 32x32 åƒç´ è¡¨æƒ…(小屏幕)
- `twemoji_64` - 64x64 åƒç´ è¡¨æƒ…(大屏幕)
### 5. é…ç½®å’Œç¼–译
#### æ–¹æ³•一:使用 idf.py æ‰‹åŠ¨é…ç½®
1. **设置目标芯片**(首次配置或更换芯片时):
   ```bash
   # å¯¹äºŽ ESP32-S3
   idf.py set-target esp32s3
   # å¯¹äºŽ ESP32-C3
   idf.py set-target esp32c3
   # å¯¹äºŽ ESP32
   idf.py set-target esp32
   ```
2. **清理旧配置**:
   ```bash
   idf.py fullclean
   ```
3. **进入配置菜单**:
   ```bash
   idf.py menuconfig
   ```
   åœ¨èœå•中导航到:`Xiaozhi Assistant` -> `Board Type`,选择你的自定义开发板。
4. **编译和烧录**:
   ```bash
   idf.py build
   idf.py flash monitor
   ```
#### æ–¹æ³•二:使用 release.py è„šæœ¬ï¼ˆæŽ¨èï¼‰
如果你的开发板目录下有 `config.json` æ–‡ä»¶ï¼Œå¯ä»¥ä½¿ç”¨æ­¤è„šæœ¬è‡ªåŠ¨å®Œæˆé…ç½®å’Œç¼–è¯‘ï¼š
```bash
python scripts/release.py my-custom-board
```
此脚本会自动:
- è¯»å– `config.json` ä¸­çš„ `target` é…ç½®å¹¶è®¾ç½®ç›®æ ‡èŠ¯ç‰‡
- åº”用 `sdkconfig_append` ä¸­çš„编译选项
- å®Œæˆç¼–译并打包固件
### 6. åˆ›å»ºREADME.md
在README.md中说明开发板的特性、硬件要求、编译和烧录步骤:
## å¸¸è§å¼€å‘板组件
### 1. æ˜¾ç¤ºå±
项目支持多种显示屏驱动,包括:
- ST7789 (SPI)
- ILI9341 (SPI)
- SH8601 (QSPI)
- ç­‰...
### 2. éŸ³é¢‘编解码器
支持的编解码器包括:
- ES8311 (常用)
- ES7210 (麦克风阵列)
- AW88298 (功放)
- ç­‰...
### 3. ç”µæºç®¡ç†
一些开发板使用电源管理芯片:
- AXP2101
- å…¶ä»–可用的PMIC
### 4. MCP设备控制
可以添加各种MCP工具,让AI能够使用:
- Speaker (扬声器控制)
- Screen (屏幕亮度调节)
- Battery (电池电量读取)
- Light (灯光控制)
- ç­‰...
## å¼€å‘板类继承关系
- `Board` - åŸºç¡€æ¿çº§ç±»
  - `WifiBoard` - Wi-Fi连接的开发板
  - `Ml307Board` - ä½¿ç”¨4G模块的开发板
  - `DualNetworkBoard` - æ”¯æŒWi-Fi与4G网络切换的开发板
## å¼€å‘技巧
1. **参考相似的开发板**:如果您的新开发板与现有开发板有相似之处,可以参考现有实现
2. **分步调试**:先实现基础功能(如显示),再添加更复杂的功能(如音频)
3. **管脚映射**:确保在config.h中正确配置所有管脚映射
4. **检查硬件兼容性**:确认所有芯片和驱动程序的兼容性
## å¯èƒ½é‡åˆ°çš„问题
1. **显示屏不正常**:检查SPI配置、镜像设置和颜色反转设置
2. **音频无输出**:检查I2S配置、PA使能引脚和编解码器地址
3. **无法连接网络**:检查Wi-Fi凭据和网络配置
4. **无法与服务器通信**:检查MQTT或WebSocket配置
## å‚考资料
- ESP-IDF æ–‡æ¡£: https://docs.espressif.com/projects/esp-idf/
- LVGL æ–‡æ¡£: https://docs.lvgl.io/
- ESP-SR æ–‡æ¡£: https://github.com/espressif/esp-sr
xiaozhi-esp32-2.2.6/docs/mcp-based-graph.jpg
xiaozhi-esp32-2.2.6/docs/mcp-protocol.md
New file
@@ -0,0 +1,270 @@
# MCP (Model Context Protocol) Interaction Flow
NOTICE: This document was AI-assisted; when implementing a backend, always cross-check the details against the code.
In this project, MCP is used between the backend API (MCP client) and the ESP32 device (MCP server) to let the backend discover and invoke the device's capabilities (tools).
## Message Format
From `main/protocols/protocol.cc` and `main/mcp_server.cc`, MCP messages are wrapped inside the underlying transport (WebSocket or MQTT). The inner payload follows the [JSON-RPC 2.0](https://www.jsonrpc.org/specification) specification.
Overall message layout:
```json
{
  "session_id": "...",   // session id
  "type": "mcp",         // fixed value "mcp"
  "payload": {           // JSON-RPC 2.0 payload
    "jsonrpc": "2.0",
    "method": "...",     // method name ("initialize", "tools/list", "tools/call", ...)
    "params": { ... },   // arguments (for requests)
    "id": ...,           // request id (for requests and responses)
    "result": { ... },   // success result (response)
    "error": { ... }     // error (response)
  }
}
```
The `payload` follows standard JSON-RPC 2.0:
- `jsonrpc`: always `"2.0"`.
- `method`: the method name (requests).
- `params`: structured parameters, usually an object (requests).
- `id`: request identifier; echoed back in responses.
- `result`: success value (responses).
- `error`: error information (responses).
## Interaction Flow
MCP interactions are driven by the client (backend) discovering and invoking tools on the device.
1. **Connection and capability announcement**
   - **When**: after the device boots and connects to the backend.
   - **Direction**: device -> backend.
   - **Message**: the device sends the transport hello, advertising supported capabilities. MCP support is signaled via `"mcp": true` in the `features` map.
   - **Example (transport hello, not an MCP payload):**
     ```json
     {
       "type": "hello",
       "version": 1,
       "features": {
         "mcp": true
       },
       "transport": "websocket",
       "audio_params": { ... },
       "session_id": "..."
     }
     ```
2. **Initialize the MCP session**
   - **When**: after the backend sees that the device supports MCP. Usually the first MCP request.
   - **Direction**: backend -> device.
   - **Method**: `initialize`
   - **Message (MCP payload):**
     ```json
     {
       "jsonrpc": "2.0",
       "method": "initialize",
       "params": {
         "capabilities": {
           // optional client capabilities
           "vision": {
             "url": "...",   // camera image upload endpoint (must be an http URL, not a websocket URL)
             "token": "..."  // token for the upload URL
           }
           // ... other client capabilities
         }
       },
       "id": 1
     }
     ```
   - **Device response:**
     ```json
     {
       "jsonrpc": "2.0",
       "id": 1,
       "result": {
         "protocolVersion": "2024-11-05",
         "capabilities": {
           "tools": {}
         },
         "serverInfo": {
           "name": "...",    // device name (BOARD_NAME)
           "version": "..."  // firmware version
         }
       }
     }
     ```
3. **Discover the tools**
   - **When**: whenever the backend needs the list of callable tools and their signatures.
   - **Direction**: backend -> device.
   - **Method**: `tools/list`
   - **Request parameters**:
     - `cursor` (string, optional): pagination cursor. Empty on the first request.
     - `withUserTools` (boolean, optional, default `false`): if `true`, the device also includes "user-only" tools (see "User-only tools" below) in the listing. This is typically used by a companion app that lets the user trigger privileged actions directly.
   - **Message (MCP payload):**
     ```json
     {
       "jsonrpc": "2.0",
       "method": "tools/list",
       "params": {
         "cursor": "",
         "withUserTools": false
       },
       "id": 2
     }
     ```
   - **Device response:**
     ```json
     {
       "jsonrpc": "2.0",
       "id": 2,
       "result": {
         "tools": [
           {
             "name": "self.get_device_status",
             "description": "...",
             "inputSchema": { ... }
           },
           {
             "name": "self.audio_speaker.set_volume",
             "description": "...",
             "inputSchema": { ... }
           }
           // ... more tools
         ],
         "nextCursor": "..."
       }
     }
     ```
   - **Pagination**: when `nextCursor` is non-empty, the backend must send another `tools/list` request with that cursor to fetch the next page.
4. **Call a tool**
   - **When**: the backend wants to execute a specific device function.
   - **Direction**: backend -> device.
   - **Method**: `tools/call`
   - **Message (MCP payload):**
     ```json
     {
       "jsonrpc": "2.0",
       "method": "tools/call",
       "params": {
         "name": "self.audio_speaker.set_volume",
         "arguments": {
           "volume": 50
         }
       },
       "id": 3
     }
     ```
   - **Successful response:**
     ```json
     {
       "jsonrpc": "2.0",
       "id": 3,
       "result": {
         "content": [
           { "type": "text", "text": "true" }
         ],
         "isError": false
       }
     }
     ```
   - **Error response:**
     ```json
     {
       "jsonrpc": "2.0",
       "id": 3,
       "error": {
         "code": -32601,
         "message": "Unknown tool: self.non_existent_tool"
       }
     }
     ```
5. **Device-initiated notifications**
   - **When**: the device wants to inform the backend of internal events (e.g. state transitions). `Application::SendMcpMessage` is the outbound entry point.
   - **Direction**: device -> backend.
   - **Method**: conventionally `notifications/...` or any custom method.
   - **Message (MCP payload)**: JSON-RPC notifications have no `id`.
     ```json
     {
       "jsonrpc": "2.0",
       "method": "notifications/state_changed",
       "params": {
         "newState": "idle",
         "oldState": "connecting"
       }
     }
     ```
   - **Backend handling**: process the notification without replying.
## User-only Tools
The MCP server on the device maintains two kinds of tools:
- **Regular tools** - registered via `McpServer::AddTool`. Exposed to the backend (and hence the AI model) by default.
- **User-only tools** - registered via `McpServer::AddUserOnlyTool`. These are hidden from standard `tools/list` results, because they are privileged or user-facing actions that should not be invoked autonomously by the AI. Examples include system reboot, firmware upgrade, and screen snapshot upload.
The backend opts in to user-only tools by sending `tools/list` with `params.withUserTools = true`. Typical usage: a companion app screen that exposes these actions to the end user.
See [MCP IoT control usage](./mcp-usage.md) for how to register either kind of tool on the device side.
## Sequence Diagram
A simplified diagram of the main MCP message flow:
```mermaid
sequenceDiagram
    participant Device as ESP32 Device
    participant BackendAPI as Backend API (Client)
    Note over Device, BackendAPI: Establish WebSocket / MQTT
    Device->>BackendAPI: Hello (features.mcp = true)
    BackendAPI->>Device: MCP Initialize request
    Note over BackendAPI: method: initialize
    Note over BackendAPI: params: { capabilities: ... }
    Device->>BackendAPI: MCP Initialize response
    Note over Device: result: { protocolVersion, serverInfo, ... }
    BackendAPI->>Device: MCP tools/list request
    Note over BackendAPI: params: { cursor: "", withUserTools: false }
    Device->>BackendAPI: MCP tools/list response
    Note over Device: result: { tools: [...], nextCursor: ... }
    loop Optional pagination
        BackendAPI->>Device: MCP tools/list request
        Note over BackendAPI: params: { cursor: "..." }
        Device->>BackendAPI: MCP tools/list response
        Note over Device: result: { tools: [...], nextCursor: "" }
    end
    BackendAPI->>Device: MCP tools/call request
    Note over BackendAPI: params: { name, arguments }
    alt Call succeeds
        Device->>BackendAPI: MCP tools/call success response
        Note over Device: result: { content, isError: false }
    else Call fails
        Device->>BackendAPI: MCP tools/call error response
        Note over Device: error: { code, message }
    end
    opt Device notification
        Device->>BackendAPI: MCP notification
        Note over Device: method: notifications/...
    end
```
This document summarizes the MCP interaction flow in this project. For exact parameter shapes, behavior, and available tools, refer to `McpServer::AddCommonTools` / `AddUserOnlyTools` in `main/mcp_server.cc` and the per-board `InitializeTools` implementations.
xiaozhi-esp32-2.2.6/docs/mcp-protocol_zh.md
New file
@@ -0,0 +1,269 @@
# MCP (Model Context Protocol) äº¤äº’流程
NOTICE: AI è¾…助生成, åœ¨å®žçŽ°åŽå°æœåŠ¡æ—¶, è¯·å‚照代码确认细节!!
本项目中的 MCP åè®®ç”¨äºŽåŽå° API(MCP å®¢æˆ·ç«¯ï¼‰ä¸Ž ESP32 è®¾å¤‡ï¼ˆMCP æœåŠ¡å™¨ï¼‰ä¹‹é—´çš„é€šä¿¡ï¼Œä»¥ä¾¿åŽå°èƒ½å¤Ÿå‘çŽ°å’Œè°ƒç”¨è®¾å¤‡æä¾›çš„åŠŸèƒ½ï¼ˆå·¥å…·ï¼‰ã€‚
## åè®®æ ¼å¼
根据代码 (`main/protocols/protocol.cc`, `main/mcp_server.cc`),MCP æ¶ˆæ¯æ˜¯å°è£…在基础通信协议(如 WebSocket æˆ– MQTT)的消息体中的。其内部结构遵循 [JSON-RPC 2.0](https://www.jsonrpc.org/specification) è§„范。
整体消息结构示例:
```json
{
  "session_id": "...", // ä¼šè¯ ID
  "type": "mcp",       // æ¶ˆæ¯ç±»åž‹ï¼Œå›ºå®šä¸º "mcp"
  "payload": {         // JSON-RPC 2.0 è´Ÿè½½
    "jsonrpc": "2.0",
    "method": "...",   // æ–¹æ³•名 (如 "initialize", "tools/list", "tools/call")
    "params": { ... }, // æ–¹æ³•参数 (对于 request)
    "id": ...,         // è¯·æ±‚ ID (对于 request å’Œ response)
    "result": { ... }, // æ–¹æ³•执行结果 (对于 success response)
    "error": { ... }   // é”™è¯¯ä¿¡æ¯ (对于 error response)
  }
}
```
其中,`payload` éƒ¨åˆ†æ˜¯æ ‡å‡†çš„ JSON-RPC 2.0 æ¶ˆæ¯ï¼š
- `jsonrpc`: å›ºå®šçš„字符串 "2.0"。
- `method`: è¦è°ƒç”¨çš„æ–¹æ³•名称 (对于 Request)。
- `params`: æ–¹æ³•的参数,一个结构化值,通常为对象 (对于 Request)。
- `id`: è¯·æ±‚的标识符,客户端发送请求时提供,服务器响应时原样返回。用于匹配请求和响应。
- `result`: æ–¹æ³•成功执行时的结果 (对于 Success Response)。
- `error`: æ–¹æ³•执行失败时的错误信息 (对于 Error Response)。
## äº¤äº’流程及发送时机
MCP çš„交互主要围绕客户端(后台 API)发现和调用设备上的“工具”(Tool)进行。
1.  **连接建立与能力通告**
    - **时机:** è®¾å¤‡å¯åŠ¨å¹¶æˆåŠŸè¿žæŽ¥åˆ°åŽå° API åŽã€‚
    - **发送方:** è®¾å¤‡ã€‚
    - **消息:** è®¾å¤‡å‘送基础协议的 "hello" æ¶ˆæ¯ç»™åŽå° API,消息中包含设备支持的能力列表,例如通过支持 MCP åè®® (`"mcp": true`)。
    - **示例 (非 MCP è´Ÿè½½ï¼Œè€Œæ˜¯åŸºç¡€åè®®æ¶ˆæ¯):**
      ```json
      {
        "type": "hello",
        "version": ...,
        "features": {
          "mcp": true,
          ...
        },
        "transport": "websocket", // æˆ– "mqtt"
        "audio_params": { ... },
        "session_id": "..." // è®¾å¤‡æ”¶åˆ°æœåС噍hello后可能设置
      }
      ```
2.  **初始化 MCP ä¼šè¯**
    - **时机:** åŽå° API æ”¶åˆ°è®¾å¤‡ "hello" æ¶ˆæ¯ï¼Œç¡®è®¤è®¾å¤‡æ”¯æŒ MCP åŽï¼Œé€šå¸¸ä½œä¸º MCP ä¼šè¯çš„第一个请求发送。
    - **发送方:** åŽå° API (客户端)。
    - **方法:** `initialize`
    - **消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "method": "initialize",
        "params": {
          "capabilities": {
            // å®¢æˆ·ç«¯èƒ½åŠ›ï¼Œå¯é€‰
            // æ‘„像头视觉相关
            "vision": {
              "url": "...", //摄像头: å›¾ç‰‡å¤„理地址(必须是http地址, ä¸æ˜¯websocket地址)
              "token": "..." // url token
            }
            // ... å…¶ä»–客户端能力
          }
        },
        "id": 1 // è¯·æ±‚ ID
      }
      ```
    - **设备响应时机:** è®¾å¤‡æ”¶åˆ° `initialize` è¯·æ±‚并处理后。
    - **设备响应消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "id": 1, // åŒ¹é…è¯·æ±‚ ID
        "result": {
          "protocolVersion": "2024-11-05",
          "capabilities": {
            "tools": {} // è¿™é‡Œçš„ tools ä¼¼ä¹Žä¸åˆ—出详细信息,需要 tools/list
          },
          "serverInfo": {
            "name": "...", // è®¾å¤‡åç§° (BOARD_NAME)
            "version": "..." // è®¾å¤‡å›ºä»¶ç‰ˆæœ¬
          }
        }
      }
      ```
3.  **发现设备工具列表**
    - **时机:** åŽå° API éœ€è¦èŽ·å–è®¾å¤‡å½“å‰æ”¯æŒçš„å…·ä½“åŠŸèƒ½ï¼ˆå·¥å…·ï¼‰åˆ—è¡¨åŠå…¶è°ƒç”¨æ–¹å¼æ—¶ã€‚
    - **发送方:** åŽå° API (客户端)。
    - **方法:** `tools/list`
    - **消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "method": "tools/list",
        "params": {
          "cursor": "" // ç”¨äºŽåˆ†é¡µï¼Œé¦–次请求为空字符串
        },
        "id": 2 // è¯·æ±‚ ID
      }
      ```
    - **设备响应时机:** è®¾å¤‡æ”¶åˆ° `tools/list` è¯·æ±‚并生成工具列表后。
    - **设备响应消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "id": 2, // åŒ¹é…è¯·æ±‚ ID
        "result": {
          "tools": [ // å·¥å…·å¯¹è±¡åˆ—表
            {
              "name": "self.get_device_status",
              "description": "...",
              "inputSchema": { ... } // å‚æ•° schema
            },
            {
              "name": "self.audio_speaker.set_volume",
              "description": "...",
              "inputSchema": { ... } // å‚æ•° schema
            }
            // ... æ›´å¤šå·¥å…·
          ],
          "nextCursor": "..." // å¦‚果列表很大需要分页,这里会包含下一个请求的 cursor å€¼
        }
      }
      ```
    - **分页处理:** å¦‚æžœ `nextCursor` å­—段非空,客户端需要再次发送 `tools/list` è¯·æ±‚,并在 `params` ä¸­å¸¦ä¸Šè¿™ä¸ª `cursor` å€¼ä»¥èŽ·å–ä¸‹ä¸€é¡µå·¥å…·ã€‚
4.  **调用设备工具**
    - **时机:** åŽå° API éœ€è¦æ‰§è¡Œè®¾å¤‡ä¸Šçš„æŸä¸ªå…·ä½“功能时。
    - **发送方:** åŽå° API (客户端)。
    - **方法:** `tools/call`
    - **消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "method": "tools/call",
        "params": {
          "name": "self.audio_speaker.set_volume", // è¦è°ƒç”¨çš„工具名称
          "arguments": {
            // å·¥å…·å‚数,对象格式
            "volume": 50 // å‚数名及其值
          }
        },
        "id": 3 // è¯·æ±‚ ID
      }
      ```
    - **设备响应时机:** è®¾å¤‡æ”¶åˆ° `tools/call` è¯·æ±‚,执行相应的工具函数后。
    - **设备成功响应消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "id": 3, // åŒ¹é…è¯·æ±‚ ID
        "result": {
          "content": [
            // å·¥å…·æ‰§è¡Œç»“果内容
            { "type": "text", "text": "true" } // ç¤ºä¾‹ï¼šset_volume è¿”回 bool
          ],
          "isError": false // è¡¨ç¤ºæˆåŠŸ
        }
      }
      ```
    - **设备失败响应消息 (MCP payload):**
      ```json
      {
        "jsonrpc": "2.0",
        "id": 3, // åŒ¹é…è¯·æ±‚ ID
        "error": {
          "code": -32601, // JSON-RPC é”™è¯¯ç ï¼Œä¾‹å¦‚ Method not found (-32601)
          "message": "Unknown tool: self.non_existent_tool" // é”™è¯¯æè¿°
        }
      }
      ```
5.  **设备主动发送消息 (Notifications)**
    - **时机:** è®¾å¤‡å†…部发生需要通知后台 API çš„事件时(例如,状态变化,虽然代码示例中没有明确的工具发送此类消息,但 `Application::SendMcpMessage` çš„存在暗示了设备可能主动发送 MCP æ¶ˆæ¯ï¼‰ã€‚
    - **发送方:** è®¾å¤‡ (服务器)。
    - **方法:** å¯èƒ½æ˜¯ä»¥ `notifications/` å¼€å¤´çš„æ–¹æ³•名,或者其他自定义方法。
    - **消息 (MCP payload):** éµå¾ª JSON-RPC Notification æ ¼å¼ï¼Œæ²¡æœ‰ `id` å­—段。
      ```json
      {
        "jsonrpc": "2.0",
        "method": "notifications/state_changed", // ç¤ºä¾‹æ–¹æ³•名
        "params": {
          "newState": "idle",
          "oldState": "connecting"
        }
        // æ²¡æœ‰ id å­—段
      }
      ```
    - **后台 API å¤„理:** æŽ¥æ”¶åˆ° Notification åŽï¼ŒåŽå° API è¿›è¡Œç›¸åº”的处理,但不回复。
## äº¤äº’图
下面是一个简化的交互序列图,展示了主要的 MCP æ¶ˆæ¯æµç¨‹ï¼š
```mermaid
sequenceDiagram
    participant Device as ESP32 Device
    participant BackendAPI as åŽå° API (Client)
    Note over Device, BackendAPI: å»ºç«‹ WebSocket / MQTT è¿žæŽ¥
    Device->>BackendAPI: Hello Message (包含 "mcp": true)
    BackendAPI->>Device: MCP Initialize Request
    Note over BackendAPI: method: initialize
    Note over BackendAPI: params: { capabilities: ... }
    Device->>BackendAPI: MCP Initialize Response
    Note over Device: result: { protocolVersion: ..., serverInfo: ... }
    BackendAPI->>Device: MCP Get Tools List Request
    Note over BackendAPI: method: tools/list
    Note over BackendAPI: params: { cursor: "" }
    Device->>BackendAPI: MCP Get Tools List Response
    Note over Device: result: { tools: [...], nextCursor: ... }
    loop Optional Pagination
        BackendAPI->>Device: MCP Get Tools List Request
        Note over BackendAPI: method: tools/list
        Note over BackendAPI: params: { cursor: "..." }
        Device->>BackendAPI: MCP Get Tools List Response
        Note over Device: result: { tools: [...], nextCursor: "" }
    end
    BackendAPI->>Device: MCP Call Tool Request
    Note over BackendAPI: method: tools/call
    Note over BackendAPI: params: { name: "...", arguments: { ... } }
    alt Tool Call Successful
        Device->>BackendAPI: MCP Tool Call Success Response
        Note over Device: result: { content: [...], isError: false }
    else Tool Call Failed
        Device->>BackendAPI: MCP Tool Call Error Response
        Note over Device: error: { code: ..., message: ... }
    end
    opt Device Notification
        Device->>BackendAPI: MCP Notification
        Note over Device: method: notifications/...
        Note over Device: params: { ... }
    end
```
这份文档概述了该项目中 MCP åè®®çš„主要交互流程。具体的参数细节和工具功能需要参考 `main/mcp_server.cc` ä¸­ `McpServer::AddCommonTools` ä»¥åŠå„个工具的实现。
xiaozhi-esp32-2.2.6/docs/mcp-usage.md
New file
@@ -0,0 +1,185 @@
# MCP IoT Control Usage
> This document describes how to implement IoT control for ESP32 devices using the MCP protocol. For the detailed wire protocol, see [`mcp-protocol.md`](./mcp-protocol.md).
## Introduction
MCP (Model Context Protocol) is the recommended protocol for IoT control in this project. It uses JSON-RPC 2.0 to let the backend discover and invoke "tools" registered by the device, giving you a flexible way to expose device functionality.
## Typical Flow
1. The device boots and connects to the backend over WebSocket or MQTT.
2. The backend sends an `initialize` call to start the MCP session.
3. The backend issues `tools/list` to discover available tools and their input schemas.
4. The backend calls individual tools with `tools/call` to control the device.
See [`mcp-protocol.md`](./mcp-protocol.md) for the exact message format.
## Registering Tools on the Device
Tools are registered through the `McpServer` singleton. There are two registration APIs:
- `McpServer::AddTool` - regular tool, visible in the default `tools/list` response and callable by the AI model.
- `McpServer::AddUserOnlyTool` - hidden tool, only returned when the backend lists tools with `withUserTools=true`. Use this for privileged or user-initiated actions (reboot, firmware upgrade, snapshots, etc.) that must not be invoked autonomously by the model.
Both APIs share the same signature:
```cpp
void AddTool(
    const std::string& name,           // unique tool name, e.g. self.dog.forward
    const std::string& description,    // short description for the model
    const PropertyList& properties,    // input parameters (may be empty); supported types: bool, int, string
    std::function<ReturnValue(const PropertyList&)> callback // implementation
);
void AddUserOnlyTool(
    const std::string& name,
    const std::string& description,
    const PropertyList& properties,
    std::function<ReturnValue(const PropertyList&)> callback
);
```
- `name` - unique identifier. A `module.action` naming style works well.
- `description` - natural-language description; used by the AI to decide when to call the tool.
- `properties` - input parameters. Supported property types are boolean, integer, and string, with optional min/max and default values.
- `callback` - implementation. Return values may be `bool`, `int`, or `std::string`.
## Example (ESP-Hi)
```cpp
void InitializeTools() {
    auto& mcp_server = McpServer::GetInstance();
    // Example 1: no arguments - move the robot forward
    mcp_server.AddTool("self.dog.forward",
        "Move the robot forward",
        PropertyList(),
        [this](const PropertyList&) -> ReturnValue {
            servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
            return true;
        });
    // Example 2: with arguments - set RGB light color
    mcp_server.AddTool("self.light.set_rgb",
        "Set the RGB color of the light",
        PropertyList({
            Property("r", kPropertyTypeInteger, 0, 255),
            Property("g", kPropertyTypeInteger, 0, 255),
            Property("b", kPropertyTypeInteger, 0, 255)
        }),
        [this](const PropertyList& properties) -> ReturnValue {
            int r = properties["r"].value<int>();
            int g = properties["g"].value<int>();
            int b = properties["b"].value<int>();
            led_on_ = true;
            SetLedColor(r, g, b);
            return true;
        });
}
```
## Example - Registering a User-only Tool
```cpp
mcp_server.AddUserOnlyTool("self.display.clear_cache",
    "Clear locally cached images. User-only action.",
    PropertyList(),
    [](const PropertyList&) -> ReturnValue {
        ClearLocalCache();
        return true;
    });
```
A tool registered this way will not appear in a regular `tools/list` response. The backend must set `params.withUserTools = true` to see it.
## Built-in Tools
`McpServer::AddCommonTools` and `McpServer::AddUserOnlyTools` register a number of tools automatically:
### Default (AI-callable) tools - from `AddCommonTools`
| Tool | Description |
|------|-------------|
| `self.get_device_status` | Returns the current volume, screen, battery, network, etc. |
| `self.audio_speaker.set_volume` | Set speaker volume (`volume`: 0-100). |
| `self.screen.set_brightness` | Set screen brightness when a backlight is available (`brightness`: 0-100). |
| `self.screen.set_theme` | Switch UI theme (`theme`: `"light"` or `"dark"`), when LVGL is enabled. |
| `self.camera.take_photo` | Take a picture with the on-board camera (when the board has one) and answer the given `question` about it. |
Board-specific tools are appended after these by each board's `InitializeTools()`.
### User-only tools - from `AddUserOnlyTools`
These tools are hidden by default. The backend must pass `withUserTools=true` to `tools/list` to see them. They are intended for companion apps / end users rather than the AI model.
| Tool | Description |
|------|-------------|
| `self.get_system_info` | Return a JSON blob describing the system. |
| `self.reboot` | Reboot the device after a short delay. |
| `self.upgrade_firmware` | Download firmware from `url` and install it, then reboot. |
| `self.screen.get_info` | Return the current screen width, height, and whether it is monochrome (LVGL boards only). |
| `self.screen.snapshot` | Snapshot the screen as JPEG and upload it to `url` (LVGL boards, when `CONFIG_LV_USE_SNAPSHOT=y`). |
| `self.screen.preview_image` | Download and display an image from `url` on the screen. |
| `self.assets.set_download_url` | Set the download URL for the assets partition. |
## JSON-RPC Examples
### 1. Get the tools list
```json
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": { "cursor": "", "withUserTools": false },
  "id": 1
}
```
### 2. Move the chassis forward
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.chassis.go_forward",
    "arguments": {}
  },
  "id": 2
}
```
### 3. Switch the light mode
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.chassis.switch_light_mode",
    "arguments": { "light_mode": 3 }
  },
  "id": 3
}
```
### 4. Reboot the device (user-only)
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.reboot",
    "arguments": {}
  },
  "id": 4
}
```
## Notes
- Tool names, parameters, and return values must match what the device registers via `AddTool` / `AddUserOnlyTool`.
- Prefer MCP for any new IoT control.
- For the wire protocol and advanced topics, see [`mcp-protocol.md`](./mcp-protocol.md).
xiaozhi-esp32-2.2.6/docs/mcp-usage_zh.md
New file
@@ -0,0 +1,115 @@
# MCP åè®®ç‰©è”网控制用法说明
> æœ¬æ–‡æ¡£ä»‹ç»å¦‚何基于 MCP åè®®å®žçް ESP32 è®¾å¤‡çš„物联网控制。详细协议流程请参考 [`mcp-protocol_zh.md`](./mcp-protocol_zh.md)。
## ç®€ä»‹
MCP(Model Context Protocol)是新一代推荐用于物联网控制的协议,通过标准 JSON-RPC 2.0 æ ¼å¼åœ¨åŽå°ä¸Žè®¾å¤‡é—´å‘现和调用"工具"(Tool),实现灵活的设备控制。
## å…¸åž‹ä½¿ç”¨æµç¨‹
1. è®¾å¤‡å¯åŠ¨åŽé€šè¿‡åŸºç¡€åè®®ï¼ˆå¦‚ WebSocket/MQTT)与后台建立连接。
2. åŽå°é€šè¿‡ MCP åè®®çš„ `initialize` æ–¹æ³•初始化会话。
3. åŽå°é€šè¿‡ `tools/list` èŽ·å–è®¾å¤‡æ”¯æŒçš„æ‰€æœ‰å·¥å…·ï¼ˆåŠŸèƒ½ï¼‰åŠå‚æ•°è¯´æ˜Žã€‚
4. åŽå°é€šè¿‡ `tools/call` è°ƒç”¨å…·ä½“工具,实现对设备的控制。
详细协议格式与交互请见 [`mcp-protocol_zh.md`](./mcp-protocol_zh.md)。
## è®¾å¤‡ç«¯å·¥å…·æ³¨å†Œæ–¹æ³•说明
设备通过 `McpServer::AddTool` æ–¹æ³•注册可被后台调用的"工具"。其常用函数签名如下:
```cpp
void AddTool(
    const std::string& name,           // å·¥å…·åç§°ï¼Œå»ºè®®å”¯ä¸€ä¸”有层次感,如 self.dog.forward
    const std::string& description,    // å·¥å…·æè¿°ï¼Œç®€æ˜Žè¯´æ˜ŽåŠŸèƒ½ï¼Œä¾¿äºŽå¤§æ¨¡åž‹ç†è§£
    const PropertyList& properties,    // è¾“入参数列表(可为空),支持类型:布尔、整数、字符串
    std::function<ReturnValue(const PropertyList&)> callback // å·¥å…·è¢«è°ƒç”¨æ—¶çš„回调实现
);
```
- name:工具唯一标识,建议用"模块.功能"命名风格。
- description:自然语言描述,便于 AI/用户理解。
- properties:参数列表,支持类型有布尔、整数、字符串,可指定范围和默认值。
- callback:收到调用请求时的实际执行逻辑,返回值可为 bool/int/string。
## å…¸åž‹æ³¨å†Œç¤ºä¾‹ï¼ˆä»¥ ESP-Hi ä¸ºä¾‹ï¼‰
```cpp
void InitializeTools() {
    auto& mcp_server = McpServer::GetInstance();
    // ä¾‹1:无参数,控制机器人前进
    mcp_server.AddTool("self.dog.forward", "机器人向前移动", PropertyList(), [this](const PropertyList&) -> ReturnValue {
        servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
        return true;
    });
    // ä¾‹2:带参数,设置灯光 RGB é¢œè‰²
    mcp_server.AddTool("self.light.set_rgb", "设置RGB颜色", PropertyList({
        Property("r", kPropertyTypeInteger, 0, 255),
        Property("g", kPropertyTypeInteger, 0, 255),
        Property("b", kPropertyTypeInteger, 0, 255)
    }), [this](const PropertyList& properties) -> ReturnValue {
        int r = properties["r"].value<int>();
        int g = properties["g"].value<int>();
        int b = properties["b"].value<int>();
        led_on_ = true;
        SetLedColor(r, g, b);
        return true;
    });
}
```
## å¸¸è§å·¥å…·è°ƒç”¨ JSON-RPC ç¤ºä¾‹
### 1. èŽ·å–å·¥å…·åˆ—è¡¨
```json
{
  "jsonrpc": "2.0",
  "method": "tools/list",
  "params": { "cursor": "" },
  "id": 1
}
```
### 2. æŽ§åˆ¶åº•盘前进
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.chassis.go_forward",
    "arguments": {}
  },
  "id": 2
}
```
### 3. åˆ‡æ¢ç¯å…‰æ¨¡å¼
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.chassis.switch_light_mode",
    "arguments": { "light_mode": 3 }
  },
  "id": 3
}
```
### 4. æ‘„像头翻转
```json
{
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "name": "self.camera.set_camera_flipped",
    "arguments": {}
  },
  "id": 4
}
```
## å¤‡æ³¨
- å·¥å…·åç§°ã€å‚数及返回值请以设备端 `AddTool` æ³¨å†Œä¸ºå‡†ã€‚
- æŽ¨èæ‰€æœ‰æ–°é¡¹ç›®ç»Ÿä¸€é‡‡ç”¨ MCP åè®®è¿›è¡Œç‰©è”网控制。
- è¯¦ç»†åè®®ä¸Žè¿›é˜¶ç”¨æ³•请查阅 [`mcp-protocol_zh.md`](./mcp-protocol_zh.md)。
xiaozhi-esp32-2.2.6/docs/mqtt-udp.md
New file
@@ -0,0 +1,409 @@
# MQTT + UDP Hybrid Communication Protocol
This document describes the MQTT + UDP hybrid protocol used between the device and the server, based on the current implementation: MQTT carries control messages, UDP carries real-time audio.
---
## 1. Overview
The protocol uses two channels:
- **MQTT** - control messages, state synchronization, JSON payloads.
- **UDP** - real-time audio, encrypted.
### 1.1 Key characteristics
- **Dual channel design** - control is separated from data so audio has low latency.
- **Encrypted transport** - UDP audio is encrypted with AES-CTR.
- **Sequence numbers** - guard against replay and reordering.
- **Automatic reconnect** - MQTT reconnects on disconnect.
---
## 2. End-to-end Flow
```mermaid
sequenceDiagram
    participant Device as ESP32 device
    participant MQTT as MQTT broker
    participant UDP as UDP server
    Note over Device, UDP: 1. Establish MQTT connection
    Device->>MQTT: MQTT Connect
    MQTT->>Device: Connected
    Note over Device, UDP: 2. Request audio channel
    Device->>MQTT: Hello message (type: "hello", transport: "udp")
    MQTT->>Device: Hello response (UDP endpoint + encryption keys)
    Note over Device, UDP: 3. Establish UDP connection
    Device->>UDP: UDP Connect
    UDP->>Device: Connected
    Note over Device, UDP: 4. Audio streaming
    loop Audio stream
        Device->>UDP: Encrypted audio (Opus)
        UDP->>Device: Encrypted audio (Opus)
    end
    Note over Device, UDP: 5. Control messages
    par Control
        Device->>MQTT: Listen / TTS / MCP messages
        MQTT->>Device: STT / TTS / MCP / Alert responses
    end
    Note over Device, UDP: 6. Teardown
    Device->>MQTT: Goodbye
    Device->>UDP: Disconnect
```
---
## 3. MQTT Control Channel
### 3.1 Connection
The device connects to the broker using:
- **Endpoint** - broker host and port.
- **Client ID** - device identifier.
- **Username / Password** - credentials.
- **Keep Alive** - heartbeat interval (default 240 s).
### 3.2 Hello exchange
#### 3.2.1 Device -> Server
```json
{
  "type": "hello",
  "version": 3,
  "transport": "udp",
  "features": {
    "mcp": true,
    "aec": true
  },
  "audio_params": {
    "format": "opus",
    "sample_rate": 16000,
    "channels": 1,
    "frame_duration": 60
  }
}
```
`features.mcp` is always set; `features.aec` is set when `CONFIG_USE_SERVER_AEC` is enabled.
#### 3.2.2 Server -> Device
```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"
  }
}
```
Field reference:
- `udp.server` - UDP server address.
- `udp.port` - UDP server port.
- `udp.key` - AES key, hex-encoded.
- `udp.nonce` - AES nonce, hex-encoded.
### 3.3 JSON message types
#### 3.3.1 Device -> Server
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 Server -> Device
Semantics match the WebSocket protocol. Supported types:
- **STT** - speech recognition result.
- **TTS** - TTS lifecycle (`start`, `stop`, `sentence_start`).
- **LLM** - emotion update for the UI.
- **MCP** - IoT control.
- **System** - system control, e.g. `"command": "reboot"`.
- **Alert** - show an alert on the UI; fields: `status`, `message`, `emotion`.
- **Goodbye** - server-initiated shutdown of the audio session. The device responds by closing the UDP channel without sending its own goodbye.
- **Custom** (optional, enabled via `CONFIG_RECEIVE_CUSTOM_MESSAGE`).
Example alert:
```json
{
  "session_id": "xxx",
  "type": "alert",
  "status": "Warning",
  "message": "Battery low",
  "emotion": "sad"
}
```
---
## 4. UDP Audio Channel
### 4.1 Establishing the channel
After the device receives the MQTT hello response, it:
1. Parses the UDP host and port.
2. Parses the AES key and nonce.
3. Initializes the AES-CTR context.
4. Opens the UDP socket.
### 4.2 Audio packet format
#### 4.2.1 Encrypted audio packet
```
|type 1B|flags 1B|payload_len 2B|ssrc 4B|timestamp 4B|sequence 4B|
|payload payload_len bytes|
```
Field reference:
- `type`: packet type, always `0x01`.
- `flags`: flags, currently unused.
- `payload_len`: payload length (network byte order).
- `ssrc`: synchronization source identifier.
- `timestamp`: timestamp (network byte order).
- `sequence`: sequence number (network byte order).
- `payload`: encrypted Opus audio data.
#### 4.2.2 Encryption
Uses **AES-CTR** with:
- **Key**: 128-bit, provided by the server.
- **Nonce**: 128-bit, provided by the server.
- **Counter**: built from the timestamp and sequence number.
### 4.3 Sequence number management
- **Sender**: `local_sequence_` is incremented monotonically.
- **Receiver**: `remote_sequence_` validates continuity.
- **Anti-replay**: packets with sequence numbers below the expected value are dropped.
- **Tolerance**: small gaps are logged as warnings but still accepted.
### 4.4 Error handling
1. **Decryption failure** - log an error and drop the packet.
2. **Sequence gap** - log a warning, continue processing the packet.
3. **Malformed packet** - log an error and drop.
---
## 5. State Management
### 5.1 Connection state
```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
    AudioStreaming --> UdpConnected: Stop audio
    UdpConnected --> ChannelOpened: UDP disconnect
    ChannelOpened --> MqttConnected: CloseAudioChannel()
    MqttConnected --> Disconnected: MQTT disconnect
```
### 5.2 State check
The device determines whether the audio channel is available with:
```cpp
bool IsAudioChannelOpened() const {
    return udp_ != nullptr && !error_occurred_ && !IsTimeout();
}
```
---
## 6. Configuration Parameters
### 6.1 MQTT settings
Read from storage:
- `endpoint` - broker address.
- `client_id` - client identifier.
- `username` - user name.
- `password` - password.
- `keepalive` - keep-alive interval (default 240 s).
- `publish_topic` - publish topic.
### 6.2 Audio parameters
- **Format**: Opus
- **Sample rate**: 16 kHz device / 24 kHz server
- **Channels**: 1 (mono)
- **Frame duration**: 60 ms
---
## 7. Error Handling and Reconnection
### 7.1 MQTT reconnect
- Automatic retry on connect failure.
- Optional error reporting.
- Clean-up runs on disconnect.
### 7.2 UDP connection
- No automatic retry; depends on re-negotiation via MQTT.
- Status can be queried at any time.
### 7.3 Timeouts
The base `Protocol` class provides timeout detection:
- Default timeout: 120 s.
- Based on the time since the last incoming packet.
- After a timeout the channel is marked unavailable.
---
## 8. Security
### 8.1 Transport encryption
- **MQTT**: supports TLS/SSL (port 8883).
- **UDP**: AES-CTR on audio payloads.
### 8.2 Authentication
- **MQTT**: user name / password.
- **UDP**: keys are distributed via the MQTT channel.
### 8.3 Anti-replay
- Monotonically increasing sequence numbers.
- Stale packets are dropped.
- Timestamps are validated.
---
## 9. Performance Notes
### 9.1 Concurrency
A mutex protects the UDP connection:
```cpp
std::lock_guard<std::mutex> lock(channel_mutex_);
```
### 9.2 Memory management
- Network objects are created and destroyed dynamically.
- Audio packets are managed with smart pointers.
- Encryption contexts are released promptly.
### 9.3 Network optimizations
- UDP connection reuse.
- Reasonable packet sizes.
- Sequence continuity checks.
---
## 10. Comparison with WebSocket
| Feature | MQTT + UDP | WebSocket |
|---------|------------|-----------|
| Control channel | MQTT | WebSocket |
| Audio channel | UDP (encrypted) | WebSocket (binary) |
| Latency | Low (UDP) | Medium |
| Reliability | Medium | High |
| Complexity | High | Low |
| Encryption | AES-CTR | TLS |
| Firewall friendliness | Low | High |
---
## 11. Deployment Notes
### 11.1 Network
- Ensure UDP ports are reachable.
- Configure firewall rules accordingly.
- Plan for NAT traversal if needed.
### 11.2 Server infrastructure
- MQTT broker configuration.
- UDP server deployment.
- Key management.
### 11.3 Monitoring
- Connection success rate.
- Audio transmission latency.
- Packet loss.
- Decryption failures.
---
## 12. Summary
The MQTT + UDP hybrid protocol achieves efficient audio communication through:
- **Split architecture** - separate control and data channels with clear responsibilities.
- **Encryption** - AES-CTR protects audio payloads.
- **Sequence management** - prevents replay and reordering.
- **Automatic recovery** - MQTT reconnects on failure.
- **Performance** - UDP keeps audio latency low.
The protocol is a good fit for low-latency voice interaction, at the cost of higher network complexity than pure WebSocket.
xiaozhi-esp32-2.2.6/docs/mqtt-udp_zh.md
New file
@@ -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<std::mutex> 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 ä¼ è¾“保证音频数据的实时性
该协议适用于对实时性要求较高的语音交互场景,但需要在网络复杂度和传输性能之间做出权衡。
xiaozhi-esp32-2.2.6/docs/v0/AtomMatrix-echo-base.jpg
xiaozhi-esp32-2.2.6/docs/v0/ESP32-BreadBoard.jpg
xiaozhi-esp32-2.2.6/docs/v0/atoms3r-echo-base.jpg
xiaozhi-esp32-2.2.6/docs/v0/esp32s3-box3.jpg
xiaozhi-esp32-2.2.6/docs/v0/lichuang-s3.jpg
xiaozhi-esp32-2.2.6/docs/v0/m5stack-cores3.jpg
xiaozhi-esp32-2.2.6/docs/v0/magiclick-2p4.jpg
xiaozhi-esp32-2.2.6/docs/v0/waveshare-esp32-s3-touch-amoled-1.8.jpg
xiaozhi-esp32-2.2.6/docs/v0/wiring.jpg
xiaozhi-esp32-2.2.6/docs/v1/atoms3r.jpg
xiaozhi-esp32-2.2.6/docs/v1/electron-bot.png
xiaozhi-esp32-2.2.6/docs/v1/esp-hi.jpg
xiaozhi-esp32-2.2.6/docs/v1/esp-sparkbot.jpg
xiaozhi-esp32-2.2.6/docs/v1/espbox3.jpg
xiaozhi-esp32-2.2.6/docs/v1/lichuang-s3.jpg
xiaozhi-esp32-2.2.6/docs/v1/lilygo-t-circle-s3.jpg
xiaozhi-esp32-2.2.6/docs/v1/m5cores3.jpg
xiaozhi-esp32-2.2.6/docs/v1/magiclick.jpg
xiaozhi-esp32-2.2.6/docs/v1/movecall-cuican-esp32s3.jpg
xiaozhi-esp32-2.2.6/docs/v1/movecall-moji-esp32s3.jpg
xiaozhi-esp32-2.2.6/docs/v1/otto-robot.png
xiaozhi-esp32-2.2.6/docs/v1/sensecap_watcher.jpg
xiaozhi-esp32-2.2.6/docs/v1/waveshare.jpg
xiaozhi-esp32-2.2.6/docs/v1/wiring2.jpg
xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_0.96.jpg
xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_1.54.jpg
xiaozhi-esp32-2.2.6/docs/v1/xmini-c3.jpg
xiaozhi-esp32-2.2.6/docs/websocket.md
New file
@@ -0,0 +1,530 @@
# WebSocket Communication Protocol
This document describes the WebSocket communication protocol between the device and the server, based on the current code. When implementing a server, please cross-check with the actual implementation.
---
## 1. Overall Flow
1. **Device initialization**
   - The device boots and initializes `Application`:
     - Initializes the audio codec, display, LEDs, etc.
     - Connects to the network.
     - Creates a WebSocket protocol instance (`WebsocketProtocol`) that implements the `Protocol` interface.
   - Enters the main loop and waits for events (audio input, audio output, scheduled tasks, etc.).
2. **Opening the WebSocket connection**
   - When the device needs to start a voice session (wake-up, button press, etc.), it calls `OpenAudioChannel()`:
     - Reads the WebSocket URL from settings.
     - Sets the request headers (`Authorization`, `Protocol-Version`, `Device-Id`, `Client-Id`).
     - Calls `Connect()` to establish the WebSocket connection.
3. **Device sends a "hello" message**
   - Once connected, the device sends a JSON message. Example:
   ```json
   {
     "type": "hello",
     "version": 1,
     "features": {
       "mcp": true,
       "aec": true
     },
     "transport": "websocket",
     "audio_params": {
       "format": "opus",
       "sample_rate": 16000,
       "channels": 1,
       "frame_duration": 60
     }
   }
   ```
   - `features` is optional and generated from compile-time configuration. For example, `"mcp": true` means the device supports MCP, and `"aec": true` is emitted when `CONFIG_USE_SERVER_AEC` is enabled.
   - `frame_duration` matches `OPUS_FRAME_DURATION_MS` (typically 60 ms).
4. **Server replies with "hello"**
   - The device waits for a JSON message whose `"type"` is `"hello"` and whose `"transport"` is `"websocket"`.
   - The server may include a `session_id`; the device will store it.
   - Example:
   ```json
   {
     "type": "hello",
     "transport": "websocket",
     "session_id": "xxx",
     "audio_params": {
       "format": "opus",
       "sample_rate": 24000,
       "channels": 1,
       "frame_duration": 60
     }
   }
   ```
   - If `transport` matches, the device marks the audio channel as opened.
   - If no valid hello arrives within the timeout (default 10 seconds), the connection is considered failed and the network error callback is fired.
5. **Subsequent exchanges**
   - Two kinds of data are sent in either direction:
     1. **Binary audio data** (Opus encoded)
     2. **Text JSON messages** (chat state, TTS/STT events, MCP messages, etc.)
   - In the code, the receive callback splits traffic as follows:
     - `OnData(...)`:
       - If `binary` is `true`, the payload is treated as an Opus frame and decoded.
       - If `binary` is `false`, the payload is parsed as JSON and dispatched by `type`.
   - When the server or network drops, `OnDisconnected()` fires:
     - The device invokes `on_audio_channel_closed_()` and eventually returns to the idle state.
6. **Closing the WebSocket connection**
   - When the device wants to end the session, it calls `CloseAudioChannel()` to tear down the socket and returns to idle.
   - The same callback chain runs if the server closes the socket first.
---
## 2. Common Request Headers
When establishing the WebSocket connection, the device sets the following headers:
- `Authorization`: access token, usually formatted as `"Bearer <token>"`.
- `Protocol-Version`: the protocol version number, matching the `version` field in the hello message.
- `Device-Id`: the physical MAC address of the device.
- `Client-Id`: a software-generated UUID (reset when NVS is erased or the full firmware is re-flashed).
These headers are sent with the WebSocket handshake; the server can use them for authentication or bookkeeping.
---
## 3. Binary Protocol Versions
The device supports several binary protocol versions, selected by the `version` field in settings:
### 3.1 Version 1 (default)
Raw Opus frames with no extra metadata. The WebSocket layer already distinguishes text and binary frames.
### 3.2 Version 2
Uses the `BinaryProtocol2` structure:
```c
struct BinaryProtocol2 {
    uint16_t version;        // protocol version
    uint16_t type;           // message type (0: OPUS, 1: JSON)
    uint32_t reserved;       // reserved
    uint32_t timestamp;      // timestamp in milliseconds (useful for server-side AEC)
    uint32_t payload_size;   // payload size in bytes
    uint8_t payload[];       // payload
} __attribute__((packed));
```
### 3.3 Version 3
Uses the `BinaryProtocol3` structure:
```c
struct BinaryProtocol3 {
    uint8_t type;            // message type
    uint8_t reserved;        // reserved
    uint16_t payload_size;   // payload size
    uint8_t payload[];       // payload
} __attribute__((packed));
```
---
## 4. JSON Message Structure
WebSocket text frames carry JSON. The most common `"type"` values and their semantics are listed below. Fields that are not listed may be implementation-specific or optional.
### 4.1 Device -> Server
1. **Hello**
   - Sent once the connection is established; announces the device parameters.
   - Example:
     ```json
     {
       "type": "hello",
       "version": 1,
       "features": {
         "mcp": true,
         "aec": true
       },
       "transport": "websocket",
       "audio_params": {
         "format": "opus",
         "sample_rate": 16000,
         "channels": 1,
         "frame_duration": 60
       }
     }
     ```
2. **Listen**
   - Tells the server that the device is starting or stopping microphone capture.
   - Common fields:
     - `"session_id"`: session identifier.
     - `"type": "listen"`
     - `"state"`: `"start"`, `"stop"`, or `"detect"` (wake word detected).
     - `"mode"`: `"auto"`, `"manual"`, or `"realtime"`.
   - Example (start listening):
     ```json
     {
       "session_id": "xxx",
       "type": "listen",
       "state": "start",
       "mode": "manual"
     }
     ```
3. **Abort**
   - Aborts the current TTS playback or the voice channel.
   - Example:
     ```json
     {
       "session_id": "xxx",
       "type": "abort",
       "reason": "wake_word_detected"
     }
     ```
   - `reason` may be `"wake_word_detected"` or other implementation-defined values.
4. **Wake Word Detected**
   - Sent by the device when the local wake word detector fires.
   - Opus audio containing the wake word may be streamed before this message to let the server run voice-print verification.
   - Example:
     ```json
     {
       "session_id": "xxx",
       "type": "listen",
       "state": "detect",
       "text": "Hi XiaoZhi"
     }
     ```
5. **MCP**
   - The recommended channel for IoT control. Device capability discovery and tool invocation all flow through `type: "mcp"` messages whose `payload` is JSON-RPC 2.0 (see [MCP protocol document](./mcp-protocol.md)).
   - Device-to-server response example:
     ```json
     {
       "session_id": "xxx",
       "type": "mcp",
       "payload": {
         "jsonrpc": "2.0",
         "id": 1,
         "result": {
           "content": [
             { "type": "text", "text": "true" }
           ],
           "isError": false
         }
       }
     }
     ```
---
### 4.2 Server -> Device
1. **Hello**
   - The handshake acknowledgement.
   - Must include `"type": "hello"` and `"transport": "websocket"`.
   - May include `audio_params`, meaning the audio parameters the server expects / the canonical set agreed with the device.
   - May include a `session_id` which the device records.
   - Once received, the device sets the "audio channel open" event.
2. **STT**
   - `{"session_id": "xxx", "type": "stt", "text": "..."}`
   - The speech-to-text result for the user utterance. Typically shown on the display before moving to the response.
3. **LLM**
   - `{"session_id": "xxx", "type": "llm", "emotion": "happy", "text": "😀"}`
   - Tells the device to update the emotion / facial expression on the UI.
4. **TTS**
   - `{"session_id": "xxx", "type": "tts", "state": "start"}`: the server is about to stream TTS audio. The device transitions to the speaking state.
   - `{"session_id": "xxx", "type": "tts", "state": "stop"}`: the TTS segment is finished.
   - `{"session_id": "xxx", "type": "tts", "state": "sentence_start", "text": "..."}`: show the current sentence on the UI (for example, subtitle display).
5. **MCP**
   - The server sends IoT-related commands or receives tool-call results. The `payload` structure follows JSON-RPC 2.0.
   - Server-to-device `tools/call` example:
     ```json
     {
       "session_id": "xxx",
       "type": "mcp",
       "payload": {
         "jsonrpc": "2.0",
         "method": "tools/call",
         "params": {
           "name": "self.light.set_rgb",
           "arguments": { "r": 255, "g": 0, "b": 0 }
         },
         "id": 1
       }
     }
     ```
6. **System**
   - System-level control, often used for remote upgrades / management.
   - Example:
     ```json
     {
       "session_id": "xxx",
       "type": "system",
       "command": "reboot"
     }
     ```
   - Supported commands:
     - `"reboot"`: reboot the device.
7. **Alert**
   - Instructs the device to show an alert and play a vibration sound. Handled in `Application::OnIncomingJson`.
   - Example:
     ```json
     {
       "session_id": "xxx",
       "type": "alert",
       "status": "Warning",
       "message": "Battery low",
       "emotion": "sad"
     }
     ```
   - Fields:
     - `status`: short title displayed on screen.
     - `message`: detailed message.
     - `emotion`: emotion shown while alerting (e.g. `"sad"`, `"neutral"`).
8. **Custom** (optional)
   - Available when `CONFIG_RECEIVE_CUSTOM_MESSAGE` is enabled.
   - Example:
     ```json
     {
       "session_id": "xxx",
       "type": "custom",
       "payload": {
         "message": "anything you want"
       }
     }
     ```
9. **Binary audio frames**
   - When the server pushes Opus-encoded audio as binary frames, the device decodes and plays them.
   - Frames received while the device is in the `listening` state are dropped to avoid conflicts with the microphone stream.
---
## 5. Audio Codec
1. **Device uploads microphone audio**
   - After optional AEC / NR / AGC processing, the audio is Opus-encoded and sent as binary frames.
   - Depending on the protocol version, the frames may be raw Opus (v1) or wrapped in the metadata structures (v2/v3).
2. **Device plays server audio**
   - Incoming binary frames are also treated as Opus.
   - The device decodes and sends them to the audio output.
   - If the sample rate differs from the device's output, it is resampled after decoding.
---
## 6. Device States
### 6.1 Main states
The device state machine is defined in [`main/device_state.h`](../main/device_state.h) and includes:
- `kDeviceStateUnknown`
- `kDeviceStateStarting`
- `kDeviceStateWifiConfiguring`
- `kDeviceStateIdle`
- `kDeviceStateConnecting`
- `kDeviceStateListening`
- `kDeviceStateSpeaking`
- `kDeviceStateUpgrading`
- `kDeviceStateActivating`
- `kDeviceStateAudioTesting`    (factory / bring-up audio testing)
- `kDeviceStateFatalError`      (non-recoverable error requiring user action)
### 6.2 Typical transitions
1. **Idle -> Connecting**
   - Triggered by wake word or button press. The device calls `OpenAudioChannel()`, sets up the WebSocket, and sends `"type":"hello"`.
2. **Connecting -> Listening**
   - Once connected, `SendStartListening(...)` is called and microphone streaming begins.
3. **Listening -> Speaking**
   - Server sends `{"type":"tts","state":"start"}`; the device stops sending mic audio and plays incoming TTS.
4. **Speaking -> Idle**
   - Server sends `{"type":"tts","state":"stop"}`. When auto-continue is enabled the device transitions back to Listening; otherwise it returns to Idle.
5. **Listening / Speaking -> Idle** (abort)
   - `SendAbortSpeaking(...)` or `CloseAudioChannel()` interrupts the session and closes the WebSocket.
### 6.3 Auto-mode state diagram
```mermaid
stateDiagram
  direction TB
  [*] --> kDeviceStateUnknown
  kDeviceStateUnknown --> kDeviceStateStarting: Initialize
  kDeviceStateStarting --> kDeviceStateWifiConfiguring: Configure WiFi
  kDeviceStateStarting --> kDeviceStateActivating: Activate device
  kDeviceStateActivating --> kDeviceStateUpgrading: New firmware detected
  kDeviceStateActivating --> kDeviceStateIdle: Activation complete
  kDeviceStateIdle --> kDeviceStateConnecting: Start connecting
  kDeviceStateConnecting --> kDeviceStateIdle: Connection failed
  kDeviceStateConnecting --> kDeviceStateListening: Connection succeeded
  kDeviceStateListening --> kDeviceStateSpeaking: TTS start
  kDeviceStateSpeaking --> kDeviceStateListening: TTS stop
  kDeviceStateListening --> kDeviceStateIdle: Manual abort
  kDeviceStateSpeaking --> kDeviceStateIdle: Auto stop
  kDeviceStateStarting --> kDeviceStateAudioTesting: Factory audio test
  kDeviceStateStarting --> kDeviceStateFatalError: Fatal error
```
### 6.4 Manual-mode state diagram
```mermaid
stateDiagram
  direction TB
  [*] --> kDeviceStateUnknown
  kDeviceStateUnknown --> kDeviceStateStarting: Initialize
  kDeviceStateStarting --> kDeviceStateWifiConfiguring: Configure WiFi
  kDeviceStateStarting --> kDeviceStateActivating: Activate device
  kDeviceStateActivating --> kDeviceStateUpgrading: New firmware detected
  kDeviceStateActivating --> kDeviceStateIdle: Activation complete
  kDeviceStateIdle --> kDeviceStateConnecting: Start connecting
  kDeviceStateConnecting --> kDeviceStateIdle: Connection failed
  kDeviceStateConnecting --> kDeviceStateListening: Connection succeeded
  kDeviceStateIdle --> kDeviceStateListening: Start listening
  kDeviceStateListening --> kDeviceStateIdle: Stop listening
  kDeviceStateIdle --> kDeviceStateSpeaking: Start speaking
  kDeviceStateSpeaking --> kDeviceStateIdle: Stop speaking
```
---
## 7. Error Handling
1. **Connection failure**
   - If `Connect(url)` fails or the server hello is not received before the timeout, `on_network_error_()` is invoked and the device shows a "cannot connect" alert.
2. **Server disconnect**
   - If the WebSocket drops unexpectedly, `OnDisconnected()` is called:
     - `on_audio_channel_closed_()` runs.
     - The device returns to Idle (or retries, depending on policy).
---
## 8. Other Notes
1. **Authentication**
   - The device supplies `Authorization: Bearer <token>`; the server must validate it.
   - If the token is missing or invalid the server may reject the handshake or terminate the session later.
2. **Session scope**
   - Many messages carry a `session_id`, useful when the server serves multiple concurrent interactions.
3. **Audio payload**
   - Default audio format is Opus at 16 kHz, mono. The frame duration is controlled by `OPUS_FRAME_DURATION_MS` (typically 60 ms). The server may use 24 kHz on the downlink for better music playback.
4. **Binary protocol version selection**
   - Configured through the `version` setting:
     - v1: raw Opus
     - v2: metadata + timestamp (useful for server-side AEC)
     - v3: lightweight header
   - The value is echoed back in the `Protocol-Version` header and the hello message.
5. **IoT control via MCP**
   - All IoT capability discovery and control flows through MCP (`type: "mcp"`). The legacy `type: "iot"` protocol is deprecated.
   - MCP works over both WebSocket and MQTT, giving better standardization and extensibility.
   - See [MCP protocol document](./mcp-protocol.md) and [MCP IoT control usage](./mcp-usage.md) for details.
6. **Malformed JSON**
   - When a required field such as `type` is missing, the device logs `ESP_LOGE(TAG, "Missing message type, data: %s", data);` and ignores the message.
---
## 9. Example Message Flow
A simplified two-way exchange:
1. **Device -> Server** (handshake)
   ```json
   {
     "type": "hello",
     "version": 1,
     "features": {
       "mcp": true,
       "aec": true
     },
     "transport": "websocket",
     "audio_params": {
       "format": "opus",
       "sample_rate": 16000,
       "channels": 1,
       "frame_duration": 60
     }
   }
   ```
2. **Server -> Device** (handshake ack)
   ```json
   {
     "type": "hello",
     "transport": "websocket",
     "session_id": "xxx",
     "audio_params": {
       "format": "opus",
       "sample_rate": 16000
     }
   }
   ```
3. **Device -> Server** (start listening)
   ```json
   {
     "session_id": "xxx",
     "type": "listen",
     "state": "start",
     "mode": "auto"
   }
   ```
   The device begins streaming binary Opus frames.
4. **Server -> Device** (ASR result)
   ```json
   {
     "session_id": "xxx",
     "type": "stt",
     "text": "what the user said"
   }
   ```
5. **Server -> Device** (TTS start)
   ```json
   {
     "session_id": "xxx",
     "type": "tts",
     "state": "start"
   }
   ```
   The server follows up with binary Opus frames for the device to play.
6. **Server -> Device** (TTS stop)
   ```json
   {
     "session_id": "xxx",
     "type": "tts",
     "state": "stop"
   }
   ```
   The device stops playback and, if no further instructions arrive, returns to idle.
---
## 10. Summary
This protocol carries JSON text and binary Opus frames over a WebSocket connection to implement audio streaming, TTS playback, speech recognition, device state management, MCP dispatch, and more. Key traits:
- **Handshake**: send `"type":"hello"` and wait for the server reply.
- **Audio channel**: bidirectional Opus streaming, with three binary framing variants.
- **JSON messages**: dispatched by `"type"` (TTS, STT, MCP, WakeWord, System, Alert, Custom, ...).
- **Extensibility**: extra fields in JSON, additional headers for authentication.
Server and device must agree on the meaning, timing, and error handling of each message type so the session runs smoothly. The text above provides the baseline for integration, debugging, and extension.
xiaozhi-esp32-2.2.6/docs/websocket_zh.md
New file
@@ -0,0 +1,495 @@
以下是一份基于代码实现整理的 WebSocket é€šä¿¡åè®®æ–‡æ¡£ï¼Œæ¦‚述设备端与服务器之间如何通过 WebSocket è¿›è¡Œäº¤äº’。
该文档仅基于所提供的代码推断,实际部署时可能需要结合服务器端实现进行进一步确认或补充。
---
## 1. æ€»ä½“流程概览
1. **设备端初始化**
   - è®¾å¤‡ä¸Šç”µã€åˆå§‹åŒ– `Application`:
     - åˆå§‹åŒ–音频编解码器、显示屏、LED ç­‰
     - è¿žæŽ¥ç½‘络
     - åˆ›å»ºå¹¶åˆå§‹åŒ–实现 `Protocol` æŽ¥å£çš„ WebSocket åè®®å®žä¾‹ï¼ˆ`WebsocketProtocol`)
   - è¿›å…¥ä¸»å¾ªçŽ¯ç­‰å¾…äº‹ä»¶ï¼ˆéŸ³é¢‘è¾“å…¥ã€éŸ³é¢‘è¾“å‡ºã€è°ƒåº¦ä»»åŠ¡ç­‰ï¼‰ã€‚
2. **建立 WebSocket è¿žæŽ¥**
   - å½“设备需要开始语音会话时(例如用户唤醒、手动按键触发等),调用 `OpenAudioChannel()`:
     - æ ¹æ®é…ç½®èŽ·å– WebSocket URL
     - è®¾ç½®è‹¥å¹²è¯·æ±‚头(`Authorization`, `Protocol-Version`, `Device-Id`, `Client-Id`)
     - è°ƒç”¨ `Connect()` ä¸ŽæœåŠ¡å™¨å»ºç«‹ WebSocket è¿žæŽ¥
3. **设备端发送 "hello" æ¶ˆæ¯**
   - è¿žæŽ¥æˆåŠŸåŽï¼Œè®¾å¤‡ä¼šå‘é€ä¸€æ¡ JSON æ¶ˆæ¯ï¼Œç¤ºä¾‹ç»“构如下:
   ```json
   {
     "type": "hello",
     "version": 1,
     "features": {
       "mcp": true
     },
     "transport": "websocket",
     "audio_params": {
       "format": "opus",
       "sample_rate": 16000,
       "channels": 1,
       "frame_duration": 60
     }
   }
   ```
   - å…¶ä¸­ `features` å­—段为可选,内容根据设备编译配置自动生成。例如:`"mcp": true` è¡¨ç¤ºæ”¯æŒ MCP åè®®ã€‚
   - `frame_duration` çš„值对应 `OPUS_FRAME_DURATION_MS`(例如 60ms)。
4. **服务器回复 "hello"**
   - è®¾å¤‡ç­‰å¾…服务器返回一条包含 `"type": "hello"` çš„ JSON æ¶ˆæ¯ï¼Œå¹¶æ£€æŸ¥ `"transport": "websocket"` æ˜¯å¦åŒ¹é…ã€‚
   - æœåŠ¡å™¨å¯é€‰ä¸‹å‘ `session_id` å­—段,设备端收到后会自动记录。
   - ç¤ºä¾‹ï¼š
   ```json
   {
     "type": "hello",
     "transport": "websocket",
     "session_id": "xxx",
     "audio_params": {
       "format": "opus",
       "sample_rate": 24000,
       "channels": 1,
       "frame_duration": 60
     }
   }
   ```
   - å¦‚果匹配,则认为服务器已就绪,标记音频通道打开成功。
   - å¦‚果在超时时间(默认 10 ç§’)内未收到正确回复,认为连接失败并触发网络错误回调。
5. **后续消息交互**
   - è®¾å¤‡ç«¯å’ŒæœåŠ¡å™¨ç«¯ä¹‹é—´å¯å‘é€ä¸¤ç§ä¸»è¦ç±»åž‹çš„æ•°æ®ï¼š
     1. **二进制音频数据**(Opus ç¼–码)
     2. **文本 JSON æ¶ˆæ¯**(用于传输聊天状态、TTS/STT äº‹ä»¶ã€MCP åè®®æ¶ˆæ¯ç­‰ï¼‰
   - åœ¨ä»£ç é‡Œï¼ŒæŽ¥æ”¶å›žè°ƒä¸»è¦åˆ†ä¸ºï¼š
     - `OnData(...)`:
       - å½“ `binary` ä¸º `true` æ—¶ï¼Œè®¤ä¸ºæ˜¯éŸ³é¢‘帧;设备会将其当作 Opus æ•°æ®è¿›è¡Œè§£ç ã€‚
       - å½“ `binary` ä¸º `false` æ—¶ï¼Œè®¤ä¸ºæ˜¯ JSON æ–‡æœ¬ï¼Œéœ€è¦åœ¨è®¾å¤‡ç«¯ç”¨ cJSON è¿›è¡Œè§£æžå¹¶åšç›¸åº”业务逻辑处理(如聊天、TTS、MCP åè®®æ¶ˆæ¯ç­‰ï¼‰ã€‚
   - å½“服务器或网络出现断连,回调 `OnDisconnected()` è¢«è§¦å‘:
     - è®¾å¤‡ä¼šè°ƒç”¨ `on_audio_channel_closed_()`,并最终回到空闲状态。
6. **关闭 WebSocket è¿žæŽ¥**
   - è®¾å¤‡åœ¨éœ€è¦ç»“束语音会话时,会调用 `CloseAudioChannel()` ä¸»åŠ¨æ–­å¼€è¿žæŽ¥ï¼Œå¹¶å›žåˆ°ç©ºé—²çŠ¶æ€ã€‚
   - æˆ–者如果服务器端主动断开,也会引发同样的回调流程。
---
## 2. é€šç”¨è¯·æ±‚头
在建立 WebSocket è¿žæŽ¥æ—¶ï¼Œä»£ç ç¤ºä¾‹ä¸­è®¾ç½®äº†ä»¥ä¸‹è¯·æ±‚头:
- `Authorization`: ç”¨äºŽå­˜æ”¾è®¿é—®ä»¤ç‰Œï¼Œå½¢å¦‚ `"Bearer <token>"`
- `Protocol-Version`: åè®®ç‰ˆæœ¬å·ï¼Œä¸Ž hello æ¶ˆæ¯ä½“内的 `version` å­—段保持一致
- `Device-Id`: è®¾å¤‡ç‰©ç†ç½‘卡 MAC åœ°å€
- `Client-Id`: è½¯ä»¶ç”Ÿæˆçš„ UUID(擦除 NVS æˆ–重新烧录完整固件会重置)
这些头会随着 WebSocket æ¡æ‰‹ä¸€èµ·å‘送到服务器,服务器可根据需求进行校验、认证等。
---
## 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"` å­—段及其对应业务逻辑。若消息里包含未列出的字段,可能为可选或特定实现细节。
### 4.1 è®¾å¤‡ç«¯â†’服务器
1. **Hello**
   - è¿žæŽ¥æˆåŠŸåŽï¼Œç”±è®¾å¤‡ç«¯å‘é€ï¼Œå‘ŠçŸ¥æœåŠ¡å™¨åŸºæœ¬å‚æ•°ã€‚
   - ä¾‹ï¼š
     ```json
     {
       "type": "hello",
       "version": 1,
       "features": {
         "mcp": true
       },
       "transport": "websocket",
       "audio_params": {
         "format": "opus",
         "sample_rate": 16000,
         "channels": 1,
         "frame_duration": 60
       }
     }
     ```
2. **Listen**
   - è¡¨ç¤ºè®¾å¤‡ç«¯å¼€å§‹æˆ–停止录音监听。
   - å¸¸è§å­—段:
     - `"session_id"`:会话标识
     - `"type": "listen"`
     - `"state"`:`"start"`, `"stop"`, `"detect"`(唤醒检测已触发)
     - `"mode"`:`"auto"`, `"manual"` æˆ– `"realtime"`,表示识别模式。
   - ä¾‹ï¼šå¼€å§‹ç›‘听
     ```json
     {
       "session_id": "xxx",
       "type": "listen",
       "state": "start",
       "mode": "manual"
     }
     ```
3. **Abort**
   - ç»ˆæ­¢å½“前说话(TTS æ’­æ”¾ï¼‰æˆ–语音通道。
   - ä¾‹ï¼š
     ```json
     {
       "session_id": "xxx",
       "type": "abort",
       "reason": "wake_word_detected"
     }
     ```
   - `reason` å€¼å¯ä¸º `"wake_word_detected"` æˆ–其他。
4. **Wake Word Detected**
   - ç”¨äºŽè®¾å¤‡ç«¯å‘服务器告知检测到唤醒词。
   - åœ¨å‘送该消息之前,可提前发送唤醒词的 Opus éŸ³é¢‘数据,用于服务器进行声纹检测。
   - ä¾‹ï¼š
     ```json
     {
       "session_id": "xxx",
       "type": "listen",
       "state": "detect",
       "text": "你好小明"
     }
     ```
5. **MCP**
   - æŽ¨èç”¨äºŽç‰©è”网控制的新一代协议。所有设备能力发现、工具调用等均通过 type: "mcp" çš„æ¶ˆæ¯è¿›è¡Œï¼Œpayload å†…部为标准 JSON-RPC 2.0(详见 [MCP åè®®æ–‡æ¡£](./mcp-protocol_zh.md))。
   - **设备端到服务器发送 result çš„例子:**
     ```json
     {
       "session_id": "xxx",
       "type": "mcp",
       "payload": {
         "jsonrpc": "2.0",
         "id": 1,
         "result": {
           "content": [
             { "type": "text", "text": "true" }
           ],
           "isError": false
         }
       }
     }
     ```
---
### 4.2 æœåŠ¡å™¨â†’è®¾å¤‡ç«¯
1. **Hello**
   - æœåŠ¡å™¨ç«¯è¿”å›žçš„æ¡æ‰‹ç¡®è®¤æ¶ˆæ¯ã€‚
   - å¿…须包含 `"type": "hello"` å’Œ `"transport": "websocket"`。
   - å¯èƒ½ä¼šå¸¦æœ‰ `audio_params`,表示服务器期望的音频参数,或与设备端对齐的配置。
   - æœåŠ¡å™¨å¯é€‰ä¸‹å‘ `session_id` å­—段,设备端收到后会自动记录。
   - æˆåŠŸæŽ¥æ”¶åŽè®¾å¤‡ç«¯ä¼šè®¾ç½®äº‹ä»¶æ ‡å¿—ï¼Œè¡¨ç¤º WebSocket é€šé“就绪。
2. **STT**
   - `{"session_id": "xxx", "type": "stt", "text": "..."}`
   - è¡¨ç¤ºæœåŠ¡å™¨ç«¯è¯†åˆ«åˆ°äº†ç”¨æˆ·è¯­éŸ³ã€‚ï¼ˆä¾‹å¦‚è¯­éŸ³è½¬æ–‡æœ¬ç»“æžœï¼‰
   - è®¾å¤‡å¯èƒ½å°†æ­¤æ–‡æœ¬æ˜¾ç¤ºåˆ°å±å¹•上,后续再进入回答等流程。
3. **LLM**
   - `{"session_id": "xxx", "type": "llm", "emotion": "happy", "text": "😀"}`
   - æœåŠ¡å™¨æŒ‡ç¤ºè®¾å¤‡è°ƒæ•´è¡¨æƒ…åŠ¨ç”» / UI è¡¨è¾¾ã€‚
4. **TTS**
   - `{"session_id": "xxx", "type": "tts", "state": "start"}`:服务器准备下发 TTS éŸ³é¢‘,设备端进入 "speaking" æ’­æ”¾çŠ¶æ€ã€‚
   - `{"session_id": "xxx", "type": "tts", "state": "stop"}`:表示本次 TTS ç»“束。
   - `{"session_id": "xxx", "type": "tts", "state": "sentence_start", "text": "..."}`
     - è®©è®¾å¤‡åœ¨ç•Œé¢ä¸Šæ˜¾ç¤ºå½“前要播放或朗读的文本片段(例如用于显示给用户)。
5. **MCP**
   - æœåŠ¡å™¨é€šè¿‡ type: "mcp" çš„æ¶ˆæ¯ä¸‹å‘物联网相关的控制指令或返回调用结果,payload ç»“构同上。
   - **服务器到设备端发送 tools/call çš„例子:**
     ```json
     {
       "session_id": "xxx",
       "type": "mcp",
       "payload": {
         "jsonrpc": "2.0",
         "method": "tools/call",
         "params": {
           "name": "self.light.set_rgb",
           "arguments": { "r": 255, "g": 0, "b": 0 }
         },
         "id": 1
       }
     }
     ```
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" ï¼ˆå½•音)状态,收到的音频帧会被忽略或清空以防冲突。
---
## 5. éŸ³é¢‘编解码
1. **设备端发送录音数据**
   - éŸ³é¢‘输入经过可能的回声消除、降噪或音量增益后,通过 Opus ç¼–码打包为二进制帧发送给服务器。
   - æ ¹æ®åè®®ç‰ˆæœ¬ï¼Œå¯èƒ½ç›´æŽ¥å‘送 Opus æ•°æ®ï¼ˆç‰ˆæœ¬1)或使用带元数据的二进制协议(版本2/3)。
2. **设备端播放收到的音频**
   - æ”¶åˆ°æœåŠ¡å™¨çš„äºŒè¿›åˆ¶å¸§æ—¶ï¼ŒåŒæ ·è®¤å®šæ˜¯ Opus æ•°æ®ã€‚
   - è®¾å¤‡ç«¯ä¼šè¿›è¡Œè§£ç ï¼Œç„¶åŽäº¤ç”±éŸ³é¢‘输出接口播放。
   - å¦‚果服务器的音频采样率与设备不一致,会在解码后再进行重采样。
---
## 6. å¸¸è§çŠ¶æ€æµè½¬
以下为常见设备端关键状态流转,与 WebSocket æ¶ˆæ¯å¯¹åº”:
1. **Idle** â†’ **Connecting**
   - ç”¨æˆ·è§¦å‘或唤醒后,设备调用 `OpenAudioChannel()` â†’ å»ºç«‹ WebSocket è¿žæŽ¥ â†’ å‘送 `"type":"hello"`。
2. **Connecting** â†’ **Listening**
   - æˆåŠŸå»ºç«‹è¿žæŽ¥åŽï¼Œè‹¥ç»§ç»­æ‰§è¡Œ `SendStartListening(...)`,则进入录音状态。此时设备会持续编码麦克风数据并发送到服务器。
3. **Listening** â†’ **Speaking**
   - æ”¶åˆ°æœåС噍 TTS Start æ¶ˆæ¯ (`{"type":"tts","state":"start"}`) â†’ åœæ­¢å½•音并播放接收到的音频。
4. **Speaking** â†’ **Idle**
   - æœåС噍 TTS Stop (`{"type":"tts","state":"stop"}`) â†’ éŸ³é¢‘播放结束。若未继续进入自动监听,则返回 Idle;如果配置了自动循环,则再度进入 Listening。
5. **Listening** / **Speaking** â†’ **Idle**(遇到异常或主动中断)
   - è°ƒç”¨ `SendAbortSpeaking(...)` æˆ– `CloseAudioChannel()` â†’ ä¸­æ–­ä¼šè¯ â†’ å…³é—­ WebSocket â†’ çŠ¶æ€å›žåˆ° Idle。
### è‡ªåŠ¨æ¨¡å¼çŠ¶æ€æµè½¬å›¾
```mermaid
stateDiagram
  direction TB
  [*] --> kDeviceStateUnknown
  kDeviceStateUnknown --> kDeviceStateStarting:初始化
  kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi
  kDeviceStateStarting --> kDeviceStateActivating:激活设备
  kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本
  kDeviceStateActivating --> kDeviceStateIdle:激活完成
  kDeviceStateIdle --> kDeviceStateConnecting:开始连接
  kDeviceStateConnecting --> kDeviceStateIdle:连接失败
  kDeviceStateConnecting --> kDeviceStateListening:连接成功
  kDeviceStateListening --> kDeviceStateSpeaking:开始说话
  kDeviceStateSpeaking --> kDeviceStateListening:结束说话
  kDeviceStateListening --> kDeviceStateIdle:手动终止
  kDeviceStateSpeaking --> kDeviceStateIdle:自动终止
```
### æ‰‹åŠ¨æ¨¡å¼çŠ¶æ€æµè½¬å›¾
```mermaid
stateDiagram
  direction TB
  [*] --> kDeviceStateUnknown
  kDeviceStateUnknown --> kDeviceStateStarting:初始化
  kDeviceStateStarting --> kDeviceStateWifiConfiguring:配置WiFi
  kDeviceStateStarting --> kDeviceStateActivating:激活设备
  kDeviceStateActivating --> kDeviceStateUpgrading:检测到新版本
  kDeviceStateActivating --> kDeviceStateIdle:激活完成
  kDeviceStateIdle --> kDeviceStateConnecting:开始连接
  kDeviceStateConnecting --> kDeviceStateIdle:连接失败
  kDeviceStateConnecting --> kDeviceStateListening:连接成功
  kDeviceStateIdle --> kDeviceStateListening:开始监听
  kDeviceStateListening --> kDeviceStateIdle:停止监听
  kDeviceStateIdle --> kDeviceStateSpeaking:开始说话
  kDeviceStateSpeaking --> kDeviceStateIdle:结束说话
```
---
## 7. é”™è¯¯å¤„理
1. **连接失败**
   - å¦‚æžœ `Connect(url)` è¿”回失败或在等待服务器 "hello" æ¶ˆæ¯æ—¶è¶…时,触发 `on_network_error_()` å›žè°ƒã€‚设备会提示"无法连接到服务"或类似错误信息。
2. **服务器断开**
   - å¦‚æžœ WebSocket å¼‚常断开,回调 `OnDisconnected()`:
     - è®¾å¤‡å›žè°ƒ `on_audio_channel_closed_()`
     - åˆ‡æ¢åˆ° Idle æˆ–其他重试逻辑。
---
## 8. å…¶å®ƒæ³¨æ„äº‹é¡¹
1. **鉴权**
   - è®¾å¤‡é€šè¿‡è®¾ç½® `Authorization: Bearer <token>` æä¾›é‰´æƒï¼ŒæœåŠ¡å™¨ç«¯éœ€éªŒè¯æ˜¯å¦æœ‰æ•ˆã€‚
   - å¦‚果令牌过期或无效,服务器可拒绝握手或在后续断开。
2. **会话控制**
   - ä»£ç ä¸­éƒ¨åˆ†æ¶ˆæ¯åŒ…含 `session_id`,用于区分独立的对话或操作。服务端可根据需要对不同会话做分离处理。
3. **音频负载**
   - ä»£ç é‡Œé»˜è®¤ä½¿ç”¨ Opus æ ¼å¼ï¼Œå¹¶è®¾ç½® `sample_rate = 16000`,单声道。帧时长由 `OPUS_FRAME_DURATION_MS` æŽ§åˆ¶ï¼Œä¸€èˆ¬ä¸º 60ms。可根据带宽或性能做适当调整。为了获得更好的音乐播放效果,服务器下行音频可能使用 24000 é‡‡æ ·çŽ‡ã€‚
4. **协议版本配置**
   - é€šè¿‡è®¾ç½®ä¸­çš„ `version` å­—段配置二进制协议版本(1、2 æˆ– 3)
   - ç‰ˆæœ¬1:直接发送 Opus æ•°æ®
   - ç‰ˆæœ¬2:使用带时间戳的二进制协议,适用于服务器端 AEC
   - ç‰ˆæœ¬3:使用简化的二进制协议
5. **物联网控制推荐 MCP åè®®**
   - è®¾å¤‡ä¸ŽæœåŠ¡å™¨ä¹‹é—´çš„ç‰©è”ç½‘èƒ½åŠ›å‘çŽ°ã€çŠ¶æ€åŒæ­¥ã€æŽ§åˆ¶æŒ‡ä»¤ç­‰ï¼Œå»ºè®®å…¨éƒ¨é€šè¿‡ MCP åè®®ï¼ˆtype: "mcp")实现。原有的 type: "iot" æ–¹æ¡ˆå·²åºŸå¼ƒã€‚
   - MCP åè®®å¯åœ¨ WebSocket、MQTT ç­‰å¤šç§åº•层协议上传输,具备更好的扩展性和标准化能力。
   - è¯¦ç»†ç”¨æ³•请参考 [MCP åè®®æ–‡æ¡£](./mcp-protocol_zh.md) åŠ [MCP ç‰©è”网控制用法](./mcp-usage_zh.md)。
6. **错误或异常 JSON**
   - å½“ JSON ä¸­ç¼ºå°‘必要字段,例如 `{"type": ...}`,设备端会记录错误日志(`ESP_LOGE(TAG, "Missing message type, data: %s", data);`),不会执行任何业务。
---
## 9. æ¶ˆæ¯ç¤ºä¾‹
下面给出一个典型的双向消息示例(流程简化示意):
1. **设备端 â†’ æœåС噍**(握手)
   ```json
   {
     "type": "hello",
     "version": 1,
     "features": {
       "mcp": true
     },
     "transport": "websocket",
     "audio_params": {
       "format": "opus",
       "sample_rate": 16000,
       "channels": 1,
       "frame_duration": 60
     }
   }
   ```
2. **服务器 â†’ è®¾å¤‡ç«¯**(握手应答)
   ```json
   {
     "type": "hello",
     "transport": "websocket",
     "session_id": "xxx",
     "audio_params": {
       "format": "opus",
       "sample_rate": 16000
     }
   }
   ```
3. **设备端 â†’ æœåС噍**(开始监听)
   ```json
   {
     "session_id": "xxx",
     "type": "listen",
     "state": "start",
     "mode": "auto"
   }
   ```
   åŒæ—¶è®¾å¤‡ç«¯å¼€å§‹å‘送二进制帧(Opus æ•°æ®ï¼‰ã€‚
4. **服务器 â†’ è®¾å¤‡ç«¯**(ASR ç»“果)
   ```json
   {
     "session_id": "xxx",
     "type": "stt",
     "text": "用户说的话"
   }
   ```
5. **服务器 â†’ è®¾å¤‡ç«¯**(TTS开始)
   ```json
   {
     "session_id": "xxx",
     "type": "tts",
     "state": "start"
   }
   ```
   æŽ¥ç€æœåŠ¡å™¨å‘é€äºŒè¿›åˆ¶éŸ³é¢‘å¸§ç»™è®¾å¤‡ç«¯æ’­æ”¾ã€‚
6. **服务器 â†’ è®¾å¤‡ç«¯**(TTS结束)
   ```json
   {
     "session_id": "xxx",
     "type": "tts",
     "state": "stop"
   }
   ```
   è®¾å¤‡ç«¯åœæ­¢æ’­æ”¾éŸ³é¢‘,若无更多指令,则回到空闲状态。
---
## 10. æ€»ç»“
本协议通过在 WebSocket ä¸Šå±‚传输 JSON æ–‡æœ¬ä¸ŽäºŒè¿›åˆ¶éŸ³é¢‘帧,完成功能包括音频流上传、TTS éŸ³é¢‘播放、语音识别与状态管理、MCP æŒ‡ä»¤ä¸‹å‘等。其核心特征:
- **握手阶段**:发送 `"type":"hello"`,等待服务器返回。
- **音频通道**:采用 Opus ç¼–码的二进制帧双向传输语音流,支持多种协议版本。
- **JSON æ¶ˆæ¯**:使用 `"type"` ä¸ºæ ¸å¿ƒå­—段标识不同业务逻辑,包括 TTS、STT、MCP、WakeWord、System、Custom ç­‰ã€‚
- **扩展性**:可根据实际需求在 JSON æ¶ˆæ¯ä¸­æ·»åŠ å­—æ®µï¼Œæˆ–åœ¨ headers é‡Œè¿›è¡Œé¢å¤–鉴权。
服务器与设备端需提前约定各类消息的字段含义、时序逻辑以及错误处理规则,方能保证通信顺畅。上述信息可作为基础文档,便于后续对接、开发或扩展。
xiaozhi-esp32-2.2.6/main/CMakeLists.txt
New file
@@ -0,0 +1,1200 @@
# Define source files
set(SOURCES "audio/audio_codec.cc"
            "audio/audio_service.cc"
            "audio/demuxer/ogg_demuxer.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/es8389_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"
            "display/display.cc"
            "display/lcd_display.cc"
            "display/oled_display.cc"
            "display/lvgl_display/lvgl_display.cc"
            "display/emote_display.cc"
            "display/lvgl_display/emoji_collection.cc"
            "display/lvgl_display/lvgl_theme.cc"
            "display/lvgl_display/lvgl_font.cc"
            "display/lvgl_display/lvgl_image.cc"
            "display/lvgl_display/gif/lvgl_gif.cc"
            "display/lvgl_display/gif/gifdec.c"
            "display/lvgl_display/jpg/image_to_jpeg.cpp"
            "display/lvgl_display/jpg/jpeg_to_image.c"
            "protocols/protocol.cc"
            "protocols/mqtt_protocol.cc"
            "protocols/websocket_protocol.cc"
            "mcp_server.cc"
            "system_info.cc"
            "application.cc"
            "ota.cc"
            "settings.cc"
            "device_state_machine.cc"
            "assets.cc"
            "main.cc"
            )
set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "audio/demuxer" "protocols")
# Add board common files
list(APPEND SOURCES
    "boards/common/board.cc"
    "boards/common/wifi_board.cc"
    "boards/common/ml307_board.cc"
    "boards/common/nt26_board.cc"
    "boards/common/dual_network_board.cc"
    "boards/common/adc_battery_monitor.cc"
    "boards/common/afsk_demod.cc"
    "boards/common/axp2101.cc"
    "boards/common/backlight.cc"
    "boards/common/button.cc"
    "boards/common/i2c_device.cc"
    "boards/common/knob.cc"
    "boards/common/power_save_timer.cc"
    "boards/common/press_to_talk_mcp_tool.cc"
    "boards/common/sleep_timer.cc"
    "boards/common/sy6970.cc"
    "boards/common/system_reset.cc"
)
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common)
idf_build_get_property(build_components BUILD_COMPONENTS)
# Function to find component dynamically by pattern
function(find_component_by_pattern PATTERN COMPONENT_VAR PATH_VAR)
    foreach(COMPONENT ${build_components})
        if(COMPONENT MATCHES "${PATTERN}")
            set(${COMPONENT_VAR} ${COMPONENT} PARENT_SCOPE)
            idf_component_get_property(COMPONENT_PATH ${COMPONENT} COMPONENT_DIR)
            set(${PATH_VAR} "${COMPONENT_PATH}" PARENT_SCOPE)
            break()
        endif()
    endforeach()
endfunction()
# Set default BUILTIN_TEXT_FONT and BUILTIN_ICON_FONT
set(BUILTIN_TEXT_FONT font_puhui_14_1)
set(BUILTIN_ICON_FONT font_awesome_14_1)
set(EMOTE_RESOLUTION "320_240")
# Add board files according to BOARD_TYPE
# Set default assets if the board uses partition table V2
if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI)
    set(BOARD_TYPE "bread-compact-wifi")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
    set(BOARD_TYPE "bread-compact-ml307")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_NT26)
    set(BOARD_TYPE "bread-compact-nt26")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32)
    set(BOARD_TYPE "bread-compact-esp32")
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
    set(BOARD_TYPE "bread-compact-esp32-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_DF_K10)
    set(BOARD_TYPE "df-k10")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM)
    set(BOARD_TYPE "df-s3-ai-cam")
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
    set(BOARD_TYPE "esp-box-3")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
    set(EMOTE_RESOLUTION "320_240")
elseif(CONFIG_BOARD_TYPE_ESP_BOX)
    set(BOARD_TYPE "esp-box")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
    set(EMOTE_RESOLUTION "320_240")
elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE)
    set(BOARD_TYPE "esp-box-lite")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2)
    set(BOARD_TYPE "kevin-box-2")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_KEVIN_C3)
    set(BOARD_TYPE "kevin-c3")
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV)
    set(BOARD_TYPE "kevin-sp-v3-dev")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV)
    set(BOARD_TYPE "kevin-sp-v4-dev")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
    set(BOARD_TYPE "kevin-yuying-313lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3)
    set(BOARD_TYPE "lichuang-dev")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
    set(BOARD_TYPE "lichuang-c3-dev")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_EDA_TV_PRO)
    set(MANUFACTURER "lceda-course-examples")
    set(BOARD_TYPE "eda-tv-pro")
elseif(CONFIG_BOARD_TYPE_EDA_ROBOT_PRO)
    set(MANUFACTURER "lceda-course-examples")
    set(BOARD_TYPE "eda-robot-pro")
    set(BUILTIN_TEXT_FONT font_puhui_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
elseif(CONFIG_BOARD_TYPE_EDA_SUPER_BEAR)
    set(MANUFACTURER "lceda-course-examples")
    set(BOARD_TYPE "eda-super-bear")
elseif(CONFIG_BOARD_TYPE_MAGICLICK_S3_2P4)
    set(BOARD_TYPE "magiclick-2p4")
    set(BUILTIN_TEXT_FONT font_noto_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_64)
elseif(CONFIG_BOARD_TYPE_MAGICLICK_S3_2P5)
    set(BOARD_TYPE "magiclick-2p5")
    set(BUILTIN_TEXT_FONT font_noto_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_64)
elseif(CONFIG_BOARD_TYPE_MAGICLICK_C3)
    set(BOARD_TYPE "magiclick-c3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_MAGICLICK_C3_V2)
    set(BOARD_TYPE "magiclick-c3-v2")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_S3)
    set(BOARD_TYPE "m5stack-core-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5)
    set(BOARD_TYPE "m5stack-tab5")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_S3_ECHO_BASE)
    set(BOARD_TYPE "atoms3-echo-base")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_S3R_ECHO_BASE)
    set(BOARD_TYPE "atoms3r-echo-base")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_S3R_CAM_M12_ECHO_BASE)
    set(BOARD_TYPE "atoms3r-cam-m12-echo-base")
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_ECHOS3R)
    set(BOARD_TYPE "atom-echos3r")
elseif(CONFIG_BOARD_TYPE_M5STACK_CARDPUTER_ADV)
    set(BOARD_TYPE "m5stack-cardputer-adv")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE)
    set(BOARD_TYPE "atommatrix-echo-base")
elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3)
    set(BOARD_TYPE "xmini-c3-v3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_XMINI_C3_4G)
    set(BOARD_TYPE "xmini-c3-4g")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_XMINI_C3)
    set(BOARD_TYPE "xmini-c3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_ESP_KORVO2_V3)
    set(BOARD_TYPE "esp32s3-korvo2-v3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ESP_KORVO2_V3_RNDIS)
    set(BOARD_TYPE "esp32s3-korvo2-v3-rndis")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT)
    set(BOARD_TYPE "esp-sparkbot")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3)
    set(BOARD_TYPE "esp-spot")
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_C5)
    set(BOARD_TYPE "esp-spot")
elseif(CONFIG_BOARD_TYPE_ESP_HI)
    set(BOARD_TYPE "esp-hi")
    # Set ESP_HI emoji directory for DEFAULT_ASSETS_EXTRA_FILES
    set(DEFAULT_ASSETS_EXTRA_FILES "${CMAKE_BINARY_DIR}/emoji")
elseif(CONFIG_BOARD_TYPE_ESP_VOCAT)
    set(BOARD_TYPE "esp-vocat")
    set(BUILTIN_TEXT_FONT font_puhui_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
    set(EMOTE_RESOLUTION "360_360")
    # set(EMOTE_EXTERNAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/boards/esp-vocat/assets")
elseif(CONFIG_BOARD_TYPE_ESP_SENSAIRSHUTTLE)
    set(BOARD_TYPE "esp-sensairshuttle")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_TOUCH_LCD_3_5)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-touch-lcd-3.5")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-cam")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_AUDIO_BOARD)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-audio-board")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_8)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-amoled-1.8")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_8)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-touch-amoled-1.8")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_2_06)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-amoled-2.06")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_2_06)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-touch-amoled-2.06")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_2_16)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-touch-amoled-2.16")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_43C)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-amoled-1.43c")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4B)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-4b")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4_3C)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-4.3c")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-amoled-1.75")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-amoled-1.75")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-1.83")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85C)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-1.85c")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-1.85")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_46)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-1.46")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_5)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-3.5")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_5B)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-3.5b")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_1_54_v1)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-epaper-1.54")
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_1_54_v2)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-epaper-1.54")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_3_97)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-epaper-3.97")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_RLCD_4_2)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-rlcd-4.2")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_49)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-3.49")
    set(LVGL_TEXT_FONT font_puhui_basic_30_4)
    set(LVGL_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_54)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-lcd-1.54")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_LCD_0_85)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-lcd-0.85")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_LCD_1_69)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-lcd-1.69")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_LCD_1_83)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-touch-lcd-1.83")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_43)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-touch-amoled-1.43")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_32)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-c6-touch-amoled-1.32")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_32)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-s3-touch-amoled-1.32")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_NANO)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-nano")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_5)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd-3.5")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1)
    set(MANUFACTURER "waveshare")
    set(BOARD_TYPE "esp32-p4-wifi6-touch-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ESP_P4_FUNCTION_EV_BOARD)
    set(BOARD_TYPE "esp-p4-function-ev-board")
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD)
    set(BOARD_TYPE "bread-compact-wifi-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_TUDOUZI)
    set(BOARD_TYPE "tudouzi")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_LILYGO_T_CIRCLE_S3)
    set(BOARD_TYPE "lilygo-t-circle-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1)
    set(BOARD_TYPE "lilygo-t-cameraplus-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2)
    set(BOARD_TYPE "lilygo-t-cameraplus-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA)
    set(BOARD_TYPE "lilygo-t-display-s3-pro-mvsrlora")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY)
    set(BOARD_TYPE "lilygo-t-display-s3-pro-mvsrlora")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_P4)
    set(BOARD_TYPE "lilygo-t-display-p4")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3)
    set(BOARD_TYPE "movecall-moji-esp32s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI2_ESP32C5)
    set(BOARD_TYPE "movecall-moji2-esp32c5")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
    set(BOARD_TYPE "movecall-cuican-esp32s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3)
    set(BOARD_TYPE "atk-dnesp32s3")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
    set(BOARD_TYPE "atk-dnesp32s3-box")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0)
    set(BOARD_TYPE "atk-dnesp32s3-box0")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI)
    set(BOARD_TYPE "atk-dnesp32s3-box2-wifi")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G)
    set(BOARD_TYPE "atk-dnesp32s3-box2-4g")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI)
    set(BOARD_TYPE "atk-dnesp32s3m-wifi")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_4G)
    set(BOARD_TYPE "atk-dnesp32s3m-4g")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX3)
    set(BOARD_TYPE "atk-dnesp32s3-box3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_DU_CHATX)
    set(BOARD_TYPE "du-chatx")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_TAIJI_PI_S3)
    set(BOARD_TYPE "taiji-pi-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_85TFT_WIFI)
    set(BOARD_TYPE "xingzhi-cube-0.85tft-wifi")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_85TFT_ML307)
    set(BOARD_TYPE "xingzhi-cube-0.85tft-ml307")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_96OLED_WIFI)
    set(BOARD_TYPE "xingzhi-cube-0.96oled-wifi")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_96OLED_ML307)
    set(BOARD_TYPE "xingzhi-cube-0.96oled-ml307")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_WIFI)
    set(BOARD_TYPE "xingzhi-cube-1.54tft-wifi")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307)
    set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_XINGZHI_METAL_1_54_WIFI)
    set(BOARD_TYPE "xingzhi-metal-1.54-wifi")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_XINGZHI_ABS_2_0)
    set(BOARD_TYPE "xingzhi-abs-2.0")
    set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER)
    set(BOARD_TYPE "sensecap-watcher")
    set(BUILTIN_TEXT_FONT font_noto_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX)
    set(BOARD_TYPE "doit-s3-aibox")
elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA)
    set(BOARD_TYPE "mixgo-nova")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_GENJUTECH_S3_1_54TFT)
    set(BOARD_TYPE "genjutech-s3-1.54tft")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_CGC)
    set(BOARD_TYPE "esp32-cgc")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_CGC_144)
    set(BOARD_TYPE "esp32-cgc-144")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board)
    set(BOARD_TYPE "esp-s3-lcd-ev-board")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board_2)
    set(BOARD_TYPE "esp-s3-lcd-ev-board-2")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI)
    set(BOARD_TYPE "zhengchen-1.54tft-wifi")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_MINSI_K08_DUAL)
    set(BOARD_TYPE "minsi-k08-dual")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307)
    set(BOARD_TYPE "zhengchen-1.54tft-ml307")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_CAM)
    set(BOARD_TYPE "zhengchen-cam")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_CAM_ML307)
    set(BOARD_TYPE "zhengchen-cam-ml307")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_SPOTPEAR_ESP32_S3_1_54_MUMA)
    set(BOARD_TYPE "sp-esp32-s3-1.54-muma")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_SPOTPEAR_ESP32_S3_1_28_BOX)
    set(BOARD_TYPE "sp-esp32-s3-1.28-box")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_OTTO_ROBOT)
    set(BOARD_TYPE "otto-robot")
    set(BUILTIN_TEXT_FONT font_puhui_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION otto-gif)
elseif(CONFIG_BOARD_TYPE_ELECTRON_BOT)
    set(BOARD_TYPE "electron-bot")
    set(BUILTIN_TEXT_FONT font_puhui_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION otto-gif)
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM)
    set(BOARD_TYPE "bread-compact-wifi-s3cam")
    set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
    set(BUILTIN_ICON_FONT font_awesome_16_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_JIUCHUAN)
    set(BOARD_TYPE "jiuchuan-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LABPLUS_MPYTHON_V3)
    set(BOARD_TYPE "labplus-mpython-v3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2)
    set(BOARD_TYPE "labplus-ledong-v2")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_SURFER_C3_1_14TFT)
    set(BOARD_TYPE "surfer-c3-1.14tft")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_YUNLIAO_S3)
    set(BOARD_TYPE "yunliao-s3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S)
    set(BOARD_TYPE "wireless-tag-wtp4c5mp07s")
    set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
    set(BUILTIN_ICON_FONT font_awesome_30_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_AIPI_LITE)
    set(BOARD_TYPE "aipi-lite")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
    set(DEFAULT_EMOJI_COLLECTION twemoji_32)
elseif(CONFIG_BOARD_TYPE_HU_087)
    set(BOARD_TYPE "hu-087")
    set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
    set(BUILTIN_ICON_FONT font_awesome_14_1)
elseif(CONFIG_BOARD_TYPE_Freenove_ESP32S3_DISPLAY_2_8_LCD)
    set(BOARD_TYPE "freenove-esp32s3-display-2.8-lcd")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
elseif(CONFIG_BOARD_TYPE_AI_VOX3)
    set(BOARD_TYPE "nulllab-ai-vox-v3")
    set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
    set(BUILTIN_ICON_FONT font_awesome_20_4)
    set(DEFAULT_EMOJI_COLLECTION twemoji_64)
endif()
if(MANUFACTURER)
    file(GLOB BOARD_SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/boards/${MANUFACTURER}/${BOARD_TYPE}/*.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/boards/${MANUFACTURER}/${BOARD_TYPE}/*.c
    )
else()
    file(GLOB BOARD_SOURCES
        ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc
        ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.c
    )
endif()
list(APPEND SOURCES ${BOARD_SOURCES})
# Select audio processor according to Kconfig
if(CONFIG_USE_AUDIO_PROCESSOR)
    list(APPEND SOURCES "audio/processors/afe_audio_processor.cc")
else()
    list(APPEND SOURCES "audio/processors/no_audio_processor.cc")
endif()
if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4)
    list(APPEND SOURCES "audio/wake_words/afe_wake_word.cc")
    list(APPEND SOURCES "audio/wake_words/custom_wake_word.cc")
else()
    list(APPEND SOURCES "audio/wake_words/esp_wake_word.cc")
endif()
# Auto Select Additional Sources
if (CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING)
    list(APPEND SOURCES "boards/common/blufi.cpp")
endif ()
# Select language directory according to Kconfig
if(CONFIG_LANGUAGE_ZH_CN)
    set(LANG_DIR "zh-CN")
elseif(CONFIG_LANGUAGE_ZH_TW)
    set(LANG_DIR "zh-TW")
elseif(CONFIG_LANGUAGE_EN_US)
    set(LANG_DIR "en-US")
elseif(CONFIG_LANGUAGE_JA_JP)
    set(LANG_DIR "ja-JP")
elseif(CONFIG_LANGUAGE_KO_KR)
    set(LANG_DIR "ko-KR")
elseif(CONFIG_LANGUAGE_VI_VN)
    set(LANG_DIR "vi-VN")
elseif(CONFIG_LANGUAGE_TH_TH)
    set(LANG_DIR "th-TH")
elseif(CONFIG_LANGUAGE_DE_DE)
    set(LANG_DIR "de-DE")
elseif(CONFIG_LANGUAGE_FR_FR)
    set(LANG_DIR "fr-FR")
elseif(CONFIG_LANGUAGE_ES_ES)
    set(LANG_DIR "es-ES")
elseif(CONFIG_LANGUAGE_IT_IT)
    set(LANG_DIR "it-IT")
elseif(CONFIG_LANGUAGE_RU_RU)
    set(LANG_DIR "ru-RU")
elseif(CONFIG_LANGUAGE_AR_SA)
    set(LANG_DIR "ar-SA")
elseif(CONFIG_LANGUAGE_HI_IN)
    set(LANG_DIR "hi-IN")
elseif(CONFIG_LANGUAGE_PT_PT)
    set(LANG_DIR "pt-PT")
elseif(CONFIG_LANGUAGE_PL_PL)
    set(LANG_DIR "pl-PL")
elseif(CONFIG_LANGUAGE_CS_CZ)
    set(LANG_DIR "cs-CZ")
elseif(CONFIG_LANGUAGE_FI_FI)
    set(LANG_DIR "fi-FI")
elseif(CONFIG_LANGUAGE_TR_TR)
    set(LANG_DIR "tr-TR")
elseif(CONFIG_LANGUAGE_ID_ID)
    set(LANG_DIR "id-ID")
elseif(CONFIG_LANGUAGE_UK_UA)
    set(LANG_DIR "uk-UA")
elseif(CONFIG_LANGUAGE_RO_RO)
    set(LANG_DIR "ro-RO")
elseif(CONFIG_LANGUAGE_BG_BG)
    set(LANG_DIR "bg-BG")
elseif(CONFIG_LANGUAGE_CA_ES)
    set(LANG_DIR "ca-ES")
elseif(CONFIG_LANGUAGE_DA_DK)
    set(LANG_DIR "da-DK")
elseif(CONFIG_LANGUAGE_EL_GR)
    set(LANG_DIR "el-GR")
elseif(CONFIG_LANGUAGE_FA_IR)
    set(LANG_DIR "fa-IR")
elseif(CONFIG_LANGUAGE_FIL_PH)
    set(LANG_DIR "fil-PH")
elseif(CONFIG_LANGUAGE_HE_IL)
    set(LANG_DIR "he-IL")
elseif(CONFIG_LANGUAGE_HR_HR)
    set(LANG_DIR "hr-HR")
elseif(CONFIG_LANGUAGE_HU_HU)
    set(LANG_DIR "hu-HU")
elseif(CONFIG_LANGUAGE_MS_MY)
    set(LANG_DIR "ms-MY")
elseif(CONFIG_LANGUAGE_NB_NO)
    set(LANG_DIR "nb-NO")
elseif(CONFIG_LANGUAGE_NL_NL)
    set(LANG_DIR "nl-NL")
elseif(CONFIG_LANGUAGE_SK_SK)
    set(LANG_DIR "sk-SK")
elseif(CONFIG_LANGUAGE_SL_SI)
    set(LANG_DIR "sl-SI")
elseif(CONFIG_LANGUAGE_SV_SE)
    set(LANG_DIR "sv-SE")
elseif(CONFIG_LANGUAGE_SR_RS)
    set(LANG_DIR "sr-RS")
endif()
# Define generation path
set(LANG_JSON "${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/${LANG_DIR}/language.json")
set(LANG_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/assets/lang_config.h")
# Collect current language audio files
file(GLOB LANG_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/${LANG_DIR}/*.ogg)
# If not en-US, collect en-US audio files as fallback for missing files
if(NOT LANG_DIR STREQUAL "en-US")
    file(GLOB EN_US_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/locales/en-US/*.ogg)
    # Extract filenames (without path) from current language
    set(EXISTING_NAMES "")
    foreach(SOUND_FILE ${LANG_SOUNDS})
        get_filename_component(FILENAME ${SOUND_FILE} NAME)
        list(APPEND EXISTING_NAMES ${FILENAME})
    endforeach()
    # Only add en-US audio files that are missing in current language
    foreach(EN_SOUND ${EN_US_SOUNDS})
        get_filename_component(FILENAME ${EN_SOUND} NAME)
        if(NOT ${FILENAME} IN_LIST EXISTING_NAMES)
            list(APPEND LANG_SOUNDS ${EN_SOUND})
            message(STATUS "Using en-US fallback for missing audio: ${FILENAME}")
        endif()
    endforeach()
endif()
file(GLOB COMMON_SOUNDS ${CMAKE_CURRENT_SOURCE_DIR}/assets/common/*.ogg)
# If target chip is ESP32, exclude specific files to avoid build errors
if(CONFIG_IDF_TARGET_ESP32)
    list(REMOVE_ITEM SOURCES "audio/codecs/box_audio_codec.cc"
                             "audio/codecs/es8388_audio_codec.cc"
                             "audio/codecs/es8389_audio_codec.cc"
                             "led/gpio_led.cc"
                             "display/lvgl_display/jpg/image_to_jpeg.cpp"
                             "display/lvgl_display/jpg/jpeg_to_image.c"
                             "boards/common/nt26_board.cc"
                             "boards/common/ml307_board.cc"
                             "boards/common/dual_network_board.cc"
                             )
endif()
# Include EspVideo if target is ESP32S3 or ESP32P4
if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4)
    list(APPEND SOURCES "boards/common/esp_video.cc"
                        "boards/common/rndis_board.cc"
                        )
endif()
# Include Esp32Camera if target is ESP32S3
if(CONFIG_IDF_TARGET_ESP32S3)
    list(APPEND SOURCES "boards/common/esp32_camera.cc")
endif()
set(MAIN_PRIV_REQUIRES_EXTRA "")
if(CONFIG_BOARD_TYPE_ESP_VOCAT)
    list(APPEND MAIN_PRIV_REQUIRES_EXTRA
        espressif__touch_slider_sensor
        espressif__touch_button_sensor
    )
endif()
idf_component_register(SRCS ${SOURCES}
                    EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS}
                    INCLUDE_DIRS ${INCLUDE_DIRS}
                    WHOLE_ARCHIVE
                    PRIV_REQUIRES
                        esp_pm
                        esp_psram
                        esp_netif
                        esp_driver_gpio
                        esp_driver_uart
                        esp_driver_spi
                        esp_driver_i2c
                        esp_driver_i2s
                        esp_driver_jpeg
                        esp_driver_ppa
                        esp_app_format
                        app_update
                        spi_flash
                        console
                        efuse
                        bt
                        fatfs
                        ${MAIN_PRIV_REQUIRES_EXTRA}
                    )
# Use target_compile_definitions to define BOARD_TYPE, BOARD_NAME
# If BOARD_NAME is empty, use BOARD_TYPE
if(NOT BOARD_NAME)
    set(BOARD_NAME ${BOARD_TYPE})
endif()
target_compile_definitions(${COMPONENT_LIB}
                    PRIVATE BOARD_TYPE=\"${BOARD_TYPE}\" BOARD_NAME=\"${BOARD_NAME}\"
                    PRIVATE BUILTIN_TEXT_FONT=${BUILTIN_TEXT_FONT} BUILTIN_ICON_FONT=${BUILTIN_ICON_FONT}
                    )
# Add generation rules
add_custom_command(
    OUTPUT ${LANG_HEADER}
    COMMAND python ${PROJECT_DIR}/scripts/gen_lang.py
            --language "${LANG_DIR}"
            --output "${LANG_HEADER}"
    DEPENDS
        ${LANG_JSON}
        ${PROJECT_DIR}/scripts/gen_lang.py
    COMMENT "Generating ${LANG_DIR} language config"
)
# Force build generation dependencies
add_custom_target(lang_header ALL
    DEPENDS ${LANG_HEADER}
)
# Find ESP-SR component dynamically
find_component_by_pattern("espressif__esp-sr" ESP_SR_COMPONENT ESP_SR_COMPONENT_PATH)
if(ESP_SR_COMPONENT_PATH)
    set(ESP_SR_MODEL_PATH "${ESP_SR_COMPONENT_PATH}/model")
endif()
# Find xiaozhi-fonts component dynamically
find_component_by_pattern("xiaozhi-fonts" XIAOZHI_FONTS_COMPONENT XIAOZHI_FONTS_COMPONENT_PATH)
if(XIAOZHI_FONTS_COMPONENT_PATH)
    set(XIAOZHI_FONTS_PATH "${XIAOZHI_FONTS_COMPONENT_PATH}")
endif()
if(CONFIG_BOARD_TYPE_ESP_HI)
set(URL "https://github.com/espressif2022/image_player/raw/main/test_apps/test_8bit")
set(EMOJI_DIR "${CMAKE_BINARY_DIR}/emoji")
file(MAKE_DIRECTORY ${EMOJI_DIR})
# List all files to download
set(FILES_TO_DOWNLOAD "")
list(APPEND FILES_TO_DOWNLOAD "Anger_enter.aaf" "Anger_loop.aaf" "Anger_return.aaf")
list(APPEND FILES_TO_DOWNLOAD "happy_enter.aaf" "happy_loop.aaf" "happ_return.aaf")
list(APPEND FILES_TO_DOWNLOAD "sad_enter.aaf" "sad_loop.aaf" "sad_return.aaf")
list(APPEND FILES_TO_DOWNLOAD "scorn_enter.aaf" "scorn_loop.aaf" "scorn_return.aaf")
list(APPEND FILES_TO_DOWNLOAD "left_enter.aaf" "left_loop.aaf" "left_return.aaf")
list(APPEND FILES_TO_DOWNLOAD "right_enter.aaf" "right_loop.aaf" "right_return.aaf")
list(APPEND FILES_TO_DOWNLOAD "asking.aaf" "blink_once.aaf" "blink_quick.aaf")
list(APPEND FILES_TO_DOWNLOAD "connecting.aaf" "panic_enter.aaf" "panic_loop.aaf")
list(APPEND FILES_TO_DOWNLOAD "panic_return.aaf" "wake.aaf")
foreach(FILENAME IN LISTS FILES_TO_DOWNLOAD)
    set(REMOTE_FILE "${URL}/${FILENAME}")
    set(LOCAL_FILE "${EMOJI_DIR}/${FILENAME}")
    # Check if local file exists
    if(EXISTS ${LOCAL_FILE})
        message(STATUS "File ${FILENAME} already exists, skipping download")
    else()
        message(STATUS "Downloading ${FILENAME}")
        file(DOWNLOAD ${REMOTE_FILE} ${LOCAL_FILE}
             STATUS DOWNLOAD_STATUS)
        list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
        if(NOT STATUS_CODE EQUAL 0)
            message(FATAL_ERROR "Failed to download ${FILENAME} from ${URL}")
        endif()
    endif()
endforeach()
endif()
# Function to build default assets based on configuration
function(build_default_assets_bin)
    # Set output path for generated assets.bin
    set(GENERATED_ASSETS_BIN "${CMAKE_BINARY_DIR}/generated_assets.bin")
    # Prepare arguments for build script
    set(BUILD_ARGS
        "--sdkconfig" "${SDKCONFIG}"
        "--output" "${GENERATED_ASSETS_BIN}"
    )
    # Add builtin text font if defined
    if(BUILTIN_TEXT_FONT)
        list(APPEND BUILD_ARGS "--builtin_text_font" "${BUILTIN_TEXT_FONT}")
    endif()
    # Add default emoji collection if defined
    if(DEFAULT_EMOJI_COLLECTION)
        list(APPEND BUILD_ARGS "--emoji_collection" "${DEFAULT_EMOJI_COLLECTION}")
    endif()
    # Add default assets extra files if defined
    if(DEFAULT_ASSETS_EXTRA_FILES)
        list(APPEND BUILD_ARGS "--extra_files" "${DEFAULT_ASSETS_EXTRA_FILES}")
    endif()
    list(APPEND BUILD_ARGS "--esp_sr_model_path" "${ESP_SR_MODEL_PATH}")
    list(APPEND BUILD_ARGS "--xiaozhi_fonts_path" "${XIAOZHI_FONTS_PATH}")
    # Create custom command to build assets
    add_custom_command(
        OUTPUT ${GENERATED_ASSETS_BIN}
        COMMAND python ${PROJECT_DIR}/scripts/build_default_assets.py ${BUILD_ARGS}
        DEPENDS
            ${SDKCONFIG}
            ${PROJECT_DIR}/scripts/build_default_assets.py
        COMMENT "Building default assets.bin based on configuration"
        VERBATIM
    )
    # Create target for generated assets
    add_custom_target(generated_default_assets ALL
        DEPENDS ${GENERATED_ASSETS_BIN}
    )
    # Set the generated file path in parent scope
    set(GENERATED_ASSETS_LOCAL_FILE ${GENERATED_ASSETS_BIN} PARENT_SCOPE)
    message(STATUS "Default assets build configured: ${GENERATED_ASSETS_BIN}")
endfunction()
# Function to get local assets file path (handles both URL and local file)
function(get_assets_local_file assets_source assets_local_file_var)
    # Check if it's a URL (starts with http:// or https://)
    if(assets_source MATCHES "^https?://")
        # It's a URL, download it
        get_filename_component(ASSETS_FILENAME "${assets_source}" NAME)
        set(ASSETS_LOCAL_FILE "${CMAKE_BINARY_DIR}/${ASSETS_FILENAME}")
        set(ASSETS_TEMP_FILE "${CMAKE_BINARY_DIR}/${ASSETS_FILENAME}.tmp")
        # Check if local file exists
        if(EXISTS ${ASSETS_LOCAL_FILE})
            message(STATUS "Assets file ${ASSETS_FILENAME} already exists, skipping download")
        else()
            message(STATUS "Downloading ${ASSETS_FILENAME}")
            # Clean up any existing temp file
            if(EXISTS ${ASSETS_TEMP_FILE})
                file(REMOVE ${ASSETS_TEMP_FILE})
            endif()
            # Download to temporary file first
            file(DOWNLOAD ${assets_source} ${ASSETS_TEMP_FILE}
                 STATUS DOWNLOAD_STATUS)
            list(GET DOWNLOAD_STATUS 0 STATUS_CODE)
            if(NOT STATUS_CODE EQUAL 0)
                # Clean up temp file on failure
                if(EXISTS ${ASSETS_TEMP_FILE})
                    file(REMOVE ${ASSETS_TEMP_FILE})
                endif()
                message(FATAL_ERROR "Failed to download ${ASSETS_FILENAME} from ${assets_source}")
            endif()
            # Move temp file to final location (atomic operation)
            file(RENAME ${ASSETS_TEMP_FILE} ${ASSETS_LOCAL_FILE})
            message(STATUS "Successfully downloaded ${ASSETS_FILENAME}")
        endif()
    else()
        # It's a local file path
        if(IS_ABSOLUTE "${assets_source}")
            set(ASSETS_LOCAL_FILE "${assets_source}")
        else()
            set(ASSETS_LOCAL_FILE "${CMAKE_CURRENT_SOURCE_DIR}/${assets_source}")
        endif()
        # Check if local file exists
        if(NOT EXISTS ${ASSETS_LOCAL_FILE})
            message(FATAL_ERROR "Assets file not found: ${ASSETS_LOCAL_FILE}")
        endif()
        message(STATUS "Using assets file: ${ASSETS_LOCAL_FILE}")
    endif()
    set(${assets_local_file_var} ${ASSETS_LOCAL_FILE} PARENT_SCOPE)
endfunction()
partition_table_get_partition_info(size "--partition-name assets" "size")
partition_table_get_partition_info(offset "--partition-name assets" "offset")
if ("${size}" AND "${offset}")
    # Flash assets based on configuration
    if(CONFIG_FLASH_DEFAULT_ASSETS)
        # Build default assets based on configuration
        build_default_assets_bin()
        esptool_py_flash_to_partition(flash "assets" "${GENERATED_ASSETS_LOCAL_FILE}")
        message(STATUS "Generated default assets flash configured: ${GENERATED_ASSETS_LOCAL_FILE} -> assets partition")
    elseif(CONFIG_FLASH_CUSTOM_ASSETS)
        # Flash custom assets
        get_assets_local_file("${CONFIG_CUSTOM_ASSETS_FILE}" ASSETS_LOCAL_FILE)
        esptool_py_flash_to_partition(flash "assets" "${ASSETS_LOCAL_FILE}")
        message(STATUS "Custom assets flash configured: ${ASSETS_LOCAL_FILE} -> assets partition")
    elseif(CONFIG_FLASH_EXPRESSION_ASSETS)
        set(ASSETS_NAME "expression_assets")
        set(ASSETS_PARTITION "assets")
        set(ASSETS_FILE "${CMAKE_BINARY_DIR}/${ASSETS_NAME}.bin")
        build_speaker_assets_bin("${ASSETS_PARTITION}" ${EMOTE_RESOLUTION} ${ASSETS_FILE} ${CONFIG_MMAP_FILE_NAME_LENGTH})
        message(STATUS "Generated emote assets: ${ASSETS_FILE} -> ${ASSETS_PARTITION} partition")
        esptool_py_flash_to_partition(flash "${ASSETS_PARTITION}" "${ASSETS_FILE}")
    elseif(CONFIG_FLASH_NONE_ASSETS)
        message(STATUS "Assets flashing disabled (FLASH_NONE_ASSETS)")
    endif()
else()
    message(STATUS "Assets partition not found, using v1 partition table")
endif()
xiaozhi-esp32-2.2.6/main/Kconfig.projbuild
New file
@@ -0,0 +1,962 @@
menu "Xiaozhi Assistant"
config OTA_URL
    string "Default OTA URL"
    default "https://api.tenclass.net/xiaozhi/ota/"
    help
        The application will access this URL to check for new firmwares and server address.
choice
    prompt "Flash Assets"
    default FLASH_DEFAULT_ASSETS if !USE_EMOTE_MESSAGE_STYLE
    default FLASH_EXPRESSION_ASSETS if USE_EMOTE_MESSAGE_STYLE
    help
        Select the assets to flash.
    config FLASH_NONE_ASSETS
        bool "Do not flash assets"
    config FLASH_DEFAULT_ASSETS
        bool "Flash Default Assets"
        depends on !USE_EMOTE_MESSAGE_STYLE
    config FLASH_CUSTOM_ASSETS
        bool "Flash Custom Assets"
    config FLASH_EXPRESSION_ASSETS
        bool "Flash Emote Assets"
        depends on USE_EMOTE_MESSAGE_STYLE
endchoice
config CUSTOM_ASSETS_FILE
    depends on FLASH_CUSTOM_ASSETS
    string "Custom Assets File"
    default "assets.bin"
    help
        The custom assets file to flash.
        It can be a local file relative to the project directory or a remote url.
choice
    prompt "Default Language"
    default LANGUAGE_ZH_CN
    help
        Select device display language
    config LANGUAGE_ZH_CN
        bool "Chinese"
    config LANGUAGE_ZH_TW
        bool "Chinese Traditional"
    config LANGUAGE_EN_US
        bool "English"
    config LANGUAGE_JA_JP
        bool "Japanese"
    config LANGUAGE_KO_KR
        bool "Korean"
    config LANGUAGE_VI_VN
        bool "Vietnamese"
    config LANGUAGE_TH_TH
        bool "Thai"
    config LANGUAGE_DE_DE
        bool "German"
    config LANGUAGE_FR_FR
        bool "French"
    config LANGUAGE_ES_ES
        bool "Spanish"
    config LANGUAGE_IT_IT
        bool "Italian"
    config LANGUAGE_RU_RU
        bool "Russian"
    config LANGUAGE_AR_SA
        bool "Arabic"
    config LANGUAGE_HI_IN
        bool "Hindi"
    config LANGUAGE_PT_PT
        bool "Portuguese"
    config LANGUAGE_PL_PL
        bool "Polish"
    config LANGUAGE_CS_CZ
        bool "Czech"
    config LANGUAGE_FI_FI
        bool "Finnish"
    config LANGUAGE_TR_TR
        bool "Turkish"
    config LANGUAGE_ID_ID
        bool "Indonesian"
    config LANGUAGE_UK_UA
        bool "Ukrainian"
    config LANGUAGE_RO_RO
        bool "Romanian"
    config LANGUAGE_BG_BG
        bool "Bulgarian"
    config LANGUAGE_CA_ES
        bool "Catalan"
    config LANGUAGE_DA_DK
        bool "Danish"
    config LANGUAGE_EL_GR
        bool "Greek"
    config LANGUAGE_FA_IR
        bool "Persian"
    config LANGUAGE_FIL_PH
        bool "Filipino"
    config LANGUAGE_HE_IL
        bool "Hebrew"
    config LANGUAGE_HR_HR
        bool "Croatian"
    config LANGUAGE_HU_HU
        bool "Hungarian"
    config LANGUAGE_MS_MY
        bool "Malay"
    config LANGUAGE_NB_NO
        bool "Norwegian"
    config LANGUAGE_NL_NL
        bool "Dutch"
    config LANGUAGE_SK_SK
        bool "Slovak"
    config LANGUAGE_SL_SI
        bool "Slovenian"
    config LANGUAGE_SV_SE
        bool "Swedish"
    config LANGUAGE_SR_RS
        bool "Serbian"
endchoice
choice BOARD_TYPE
    prompt "Board Type"
    default BOARD_TYPE_BREAD_COMPACT_WIFI
    help
        Board type. å¼€å‘板类型
    config BOARD_TYPE_BREAD_COMPACT_WIFI
        bool "Bread Compact WiFi (面包板)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_BREAD_COMPACT_WIFI_LCD
        bool "Bread Compact WiFi + LCD (面包板)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_BREAD_COMPACT_WIFI_CAM
        bool "Bread Compact WiFi + LCD + Camera (面包板)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_BREAD_COMPACT_ML307
        bool "Bread Compact ML307/EC801E (面包板 4G)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_BREAD_COMPACT_NT26
        bool "Bread Compact NT26 (面包板 4G)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_BREAD_COMPACT_ESP32
        bool "Bread Compact ESP32 DevKit (面包板)"
        depends on IDF_TARGET_ESP32
    config BOARD_TYPE_BREAD_COMPACT_ESP32_LCD
        bool "Bread Compact ESP32 DevKit + LCD (面包板)"
        depends on IDF_TARGET_ESP32
    config BOARD_TYPE_XMINI_C3_V3
        bool "Xmini C3 V3"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_XMINI_C3_4G
        bool "Xmini C3 4G"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_XMINI_C3
        bool "Xmini C3"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_ESP_KORVO2_V3
        bool "Espressif Korvo2 V3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_KORVO2_V3_RNDIS
        bool "Espressif Korvo2 V3 RNDIS"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_SPARKBOT
        bool "Espressif SparkBot"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_SENSAIRSHUTTLE
        bool "Espressif ESP-SensairShuttle"
        depends on IDF_TARGET_ESP32C5
    config BOARD_TYPE_ESP_SPOT_S3
        bool "Espressif Spot-S3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_SPOT_C5
        bool "Espressif Spot-C5"
        depends on IDF_TARGET_ESP32C5
    config BOARD_TYPE_ESP_HI
        bool "Espressif ESP-HI"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_ESP_BOX_3
        bool "Espressif ESP-BOX-3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_BOX
        bool "Espressif ESP-BOX"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_BOX_LITE
        bool "Espressif ESP-BOX-Lite"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_P4_FUNCTION_EV_BOARD
        bool "Espressif ESP-P4-Function-EV-Board"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_ESP_VOCAT
        bool "Espressif ESP-VoCat"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_KEVIN_BOX_2
        bool "Kevin Box 2"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_KEVIN_C3
        bool "Kevin C3"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_KEVIN_SP_V3_DEV
        bool "Kevin SP V3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_KEVIN_SP_V4_DEV
        bool "Kevin SP V4"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_KEVIN_YUYING_313LCD
        bool "鱼鹰科技 3.13LCD"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_CGC
        bool "CGC"
        depends on IDF_TARGET_ESP32
    config BOARD_TYPE_CGC_144
        bool "CGC 144"
        depends on IDF_TARGET_ESP32
    config BOARD_TYPE_LICHUANG_DEV_S3
        bool "立创·实战派 ESP32-S3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LICHUANG_DEV_C3
        bool "立创·实战派 ESP32-C3"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_EDA_TV_PRO
        bool "EDA课程案例 EDA-TV-Pro"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_EDA_ROBOT_PRO
        bool "EDA课程案例 EDA-Robot-Pro"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_EDA_SUPER_BEAR
        bool "EDA课程案例 EDA-Super-Bear"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_DF_K10
        bool "DFRobot è¡Œç©ºæ¿ k10"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_DF_S3_AI_CAM
        bool "DFRobot ESP32-S3 AI智能摄像头模块"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_MAGICLICK_S3_2P4
        bool "神奇按钮 Magiclick_2.4"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_MAGICLICK_S3_2P5
        bool "神奇按钮 Magiclick_2.5"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_MAGICLICK_C3
        bool "神奇按钮 Magiclick_C3"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_MAGICLICK_C3_V2
        bool "神奇按钮 Magiclick_C3_v2"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_M5STACK_CORE_S3
        bool "M5Stack CoreS3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_M5STACK_CORE_TAB5
        bool "M5Stack Tab5"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_M5STACK_ATOM_S3_ECHO_BASE
        bool "M5Stack AtomS3 + Echo Base"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_M5STACK_ATOM_S3R_ECHO_BASE
        bool "M5Stack AtomS3R + Echo Base"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_M5STACK_ATOM_S3R_CAM_M12_ECHO_BASE
        bool "M5Stack AtomS3R CAM/M12 + Echo Base"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_M5STACK_ATOM_ECHOS3R
        bool "M5Stack AtomEchoS3R"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_M5STACK_CARDPUTER_ADV
        bool "M5Stack Cardputer Adv"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE
        bool "M5Stack AtomMatrix + Echo Base"
        depends on IDF_TARGET_ESP32
    config BOARD_TYPE_WAVESHARE_ESP32_TOUCH_LCD_3_5
        bool "Waveshare ESP32-Touch-LCD-3.5"
        depends on IDF_TARGET_ESP32
    config BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX
        bool "Waveshare ESP32-S3-CAM"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_AUDIO_BOARD
        bool "Waveshare ESP32-S3-Audio-Board"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_8
        bool "Waveshare ESP32-S3-Touch-AMOLED-1.8"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_43C
        bool "Waveshare ESP32-S3-Touch-AMOLED-1.43C"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_2_06
        bool "Waveshare ESP32-S3-Touch-AMOLED-2.06"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_2_06
        bool "Waveshare ESP32-C6-Touch-AMOLED-2.06"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_2_16
        bool "Waveshare ESP32-C6-Touch-AMOLED-2.16"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75
        bool "Waveshare ESP32-S3-Touch-AMOLED-1.75"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C
        bool "Waveshare ESP32-S3-Touch-AMOLED-1.75C"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83
        bool "Waveshare ESP32-S3-Touch-LCD-1.83"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4B
        bool "Waveshare ESP32-S3-Touch-LCD-4B"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4_3C
        bool "Waveshare ESP32-S3-Touch-LCD-4.3C"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85C
        bool "Waveshare ESP32-S3-Touch-LCD-1.85C"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85
        bool "Waveshare ESP32-S3-Touch-LCD-1.85"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_46
        bool "Waveshare ESP32-S3-Touch-LCD-1.46"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_C6_LCD_1_69
        bool "Waveshare ESP32-C6-LCD-1.69"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_LCD_1_83
        bool "Waveshare ESP32-C6-Touch-LCD-1.83"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_43
        bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_32
        bool "Waveshare ESP32-C6-Touch-AMOLOED-1.32"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_C6_TOUCH_AMOLED_1_8
        bool "Waveshare ESP32-C6-Touch-AMOLED-1.8"
        depends on IDF_TARGET_ESP32C6
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_32
        bool "Waveshare ESP32-S3-Touch-AMOLOED-1.32"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_49
        bool "Waveshare ESP32-S3-Touch-LCD-3.49"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_5
        bool "Waveshare ESP32-S3-Touch-LCD-3.5"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_1_54_v1
        bool "Waveshare ESP32-S3-ePaper-1.54_v1"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_1_54_v2
        bool "Waveshare ESP32-S3-ePaper-1.54_v2"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_ePaper_3_97
        bool "Waveshare ESP32-S3-ePaper-3.97"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_RLCD_4_2
        bool "Waveshare ESP32-S3-RLCD-4.2"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_5B
        bool "Waveshare ESP32-S3-Touch-LCD-3.5B"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_54
        bool "Waveshare ESP32-S3-Touch-LCD-1.54"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_S3_LCD_0_85
        bool "Waveshare ESP32-S3-LCD-0.85"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WAVESHARE_ESP32_P4_NANO
        bool "Waveshare ESP32-P4-NANO"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4B"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4.3"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-7B"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_5
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-3.5"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-4C"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-7"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-8"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1
        bool "Waveshare ESP32-P4-WIFI6-Touch-LCD-10.1"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_TUDOUZI
        bool "土豆子"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LILYGO_T_CIRCLE_S3
        bool "LILYGO T-Circle-S3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1
        bool "LILYGO T-CameraPlus-S3_V1_0_V1_1"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
        bool "LILYGO T-CameraPlus-S3_V1_2"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA
        bool "LILYGO T-Display-S3-Pro-MVSRLora"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY
        bool "LILYGO T-Display-S3-Pro-MVSRLora_No_Battery"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LILYGO_T_DISPLAY_P4
        bool "LILYGO T-Display-P4"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_MOVECALL_MOJI_ESP32S3
        bool "Movecall Moji å°æ™ºAI衍生版"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_MOVECALL_MOJI2_ESP32C5
        bool "Movecall Moji2.0 å°æ™ºAI衍生版"
        depends on IDF_TARGET_ESP32C5
    config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3
        bool "Movecall CuiCan ç’€ç’¨Â·AI吊坠"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3
        bool "正点原子DNESP32S3开发板"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3_BOX
        bool "正点原子DNESP32S3-BOX"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3_BOX0
        bool "正点原子DNESP32S3-BOX0"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI
        bool "正点原子DNESP32S3-BOX2-WIFI"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3_BOX2_4G
        bool "正点原子DNESP32S3-BOX2-4G"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3M_WIFI
        bool "正点原子DNESP32S3M-WIFI"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3M_4G
        bool "正点原子DNESP32S3M-4G"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ATK_DNESP32S3_BOX3
        bool "正点原子DNESP32S3-BOX3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_DU_CHATX
        bool "嘟嘟开发板CHATX(wifi)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_TAIJI_PI_S3
        bool "太极小派esp32s3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_CUBE_0_85TFT_WIFI
        bool "无名科技星智0.85(WIFI)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_CUBE_0_85TFT_ML307
        bool "无名科技星智0.85(ML307)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_CUBE_0_96OLED_WIFI
        bool "无名科技星智0.96(WIFI)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_CUBE_0_96OLED_ML307
        bool "无名科技星智0.96(ML307)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_CUBE_1_54TFT_WIFI
        bool "无名科技星智1.54(WIFI)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307
        bool "无名科技星智1.54(ML307)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_METAL_1_54_WIFI
        bool "无名科技星智1.54 METAL(wifi)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_XINGZHI_ABS_2_0
        bool "无名科技星智ABS 2.0"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER
        bool "Seeed Studio SenseCAP Watcher"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_DOIT_S3_AIBOX
        bool "四博智联AI陪伴盒子"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_MIXGO_NOVA
        bool "元控·青春"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_GENJUTECH_S3_1_54TFT
        bool "亘具科技1.54(s3)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_S3_LCD_EV_Board
        bool "乐鑫ESP S3 LCD EV Board开发板"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ESP_S3_LCD_EV_Board_2
        bool "乐鑫ESP S3 LCD EV Board 2开发板"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI
        bool "征辰科技1.54(WIFI)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307
        bool "征辰科技1.54(ML307)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ZHENGCHEN_CAM
        bool "征辰科技AI Camera"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ZHENGCHEN_CAM_ML307
        bool "征辰科技AI Camera(ML307)"
    depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_MINSI_K08_DUAL
        bool "敏思科技K08(DUAL)"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_SPOTPEAR_ESP32_S3_1_54_MUMA
        bool "Spotpear ESP32-S3-1.54-MUMA"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_SPOTPEAR_ESP32_S3_1_28_BOX
        bool "Spotpear ESP32-S3-1.28-BOX"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_OTTO_ROBOT
        bool "ottoRobot"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_ELECTRON_BOT
        bool "electronBot"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_JIUCHUAN
        bool "九川智能"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LABPLUS_MPYTHON_V3
        bool "labplus mpython_v3 board"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_LABPLUS_LEDONG_V2
        bool "labplus ledong_v2 board"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_SURFER_C3_1_14TFT
        bool "Surfer-C3-1.14TFT"
        depends on IDF_TARGET_ESP32C3
    config BOARD_TYPE_YUNLIAO_S3
        bool "小智云聊-S3"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_WIRELESS_TAG_WTP4C5MP07S
        bool "Wireless-Tag WTP4C5MP07S"
        depends on IDF_TARGET_ESP32P4
    config BOARD_TYPE_AIPI_LITE
        bool "AIPI-Lite"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_HU_087
        bool "HU-087"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_Freenove_ESP32S3_DISPLAY_2_8_LCD
        bool "Freenove ESP32S3 Display 2.8 LCD"
        depends on IDF_TARGET_ESP32S3
    config BOARD_TYPE_AI_VOX3
        bool "NULLLAB-AI-VOX3"
        depends on IDF_TARGET_ESP32S3
endchoice
choice
    depends on BOARD_TYPE_LILYGO_T_DISPLAY_P4
    prompt "Select the screen type"
    default SCREEN_TYPE_HI8561
        config SCREEN_TYPE_HI8561
            bool "HI8561"
        config SCREEN_TYPE_RM69A10
            bool "RM69A10"
endchoice
choice
    depends on BOARD_TYPE_LILYGO_T_DISPLAY_P4
    prompt "Select the color format of the screen"
    default SCREEN_PIXEL_FORMAT_RGB565
        config SCREEN_PIXEL_FORMAT_RGB565
            bool "RGB565"
        config SCREEN_PIXEL_FORMAT_RGB888
            bool "RGB888"
endchoice
choice ESP_S3_LCD_EV_Board_Version_TYPE
    depends on BOARD_TYPE_ESP_S3_LCD_EV_Board
    prompt "EV_BOARD Type"
    default ESP_S3_LCD_EV_Board_1p4
    config ESP_S3_LCD_EV_Board_1p4
        bool "乐鑫ESP32_S3_LCD_EV_Board-MB_V1.4"
    config ESP_S3_LCD_EV_Board_1p5
        bool "乐鑫ESP32_S3_LCD_EV_Board-MB_V1.5"
endchoice
choice DISPLAY_OLED_TYPE
    depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_NT26 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
    prompt "OLED Type"
    default OLED_SSD1306_128X32
    help
        OLED Monochrome Display Type
    config OLED_SSD1306_128X32
        bool "SSD1306 128*32"
    config OLED_SSD1306_128X64
        bool "SSD1306 128*64"
    config OLED_SH1106_128X64
        bool "SH1106 128*64"
endchoice
choice DISPLAY_LCD_TYPE
    depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_CGC || BOARD_TYPE_BREAD_COMPACT_WIFI_CAM
    prompt "LCD Type"
    default LCD_ST7789_240X320
    help
        LCD Display Type
    config LCD_ST7789_240X320
        bool "ST7789 240*320, IPS"
    config LCD_ST7789_240X320_NO_IPS
        bool "ST7789 240*320, Non-IPS"
    config LCD_ST7789_170X320
        bool "ST7789 170*320"
    config LCD_ST7789_172X320
        bool "ST7789 172*320"
    config LCD_ST7789_240X280
        bool "ST7789 240*280"
    config LCD_ST7789_240X240
        bool "ST7789 240*240"
    config LCD_ST7789_240X240_7PIN
        bool "ST7789 240*240, 7PIN"
    config LCD_ST7789_240X135
        bool "ST7789 240*135"
    config LCD_ST7735_128X160
        bool "ST7735 128*160"
    config LCD_ST7735_128X128
        bool "ST7735 128*128"
    config LCD_ST7796_320X480
        bool "ST7796 320*480 IPS"
    config LCD_ST7796_320X480_NO_IPS
        bool "ST7796 320*480, Non-IPS"
    config LCD_ILI9341_240X320
        bool "ILI9341 240*320"
    config LCD_ILI9341_240X320_NO_IPS
        bool "ILI9341 240*320, Non-IPS"
    config LCD_GC9A01_240X240
        bool "GC9A01 240*240 Circle"
    config LCD_CUSTOM
        bool "Custom LCD (自定义屏幕参数)"
endchoice
choice DISPLAY_ESP32S3_KORVO2_V3
    depends on BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_ESP_KORVO2_V3_RNDIS
    prompt "ESP32S3_KORVO2_V3 LCD Type"
    default ESP32S3_KORVO2_V3_LCD_ST7789
    help
        LCD Display Type
    config ESP32S3_KORVO2_V3_LCD_ST7789
        bool "ST7789 240*280"
    config ESP32S3_KORVO2_V3_LCD_ILI9341
        bool "ILI9341 240*320"
endchoice
choice DISPLAY_ESP32S3_CAM_XXXX
    depends on BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX
    prompt "ESP32S3_CAM LCD Type"
    default BSP_LCD_SIZE_2INCH
    help
        LCD Display Type
    config BSP_LCD_SIZE_2INCH
        bool "2inch Touch LCD Module"
    config BSP_LCD_SIZE_2_8INCH
        bool "2.8inch Touch LCD Module"
    config BSP_LCD_SIZE_3_5INCH
        bool "3.5inch Touch LCD Module"
    config BSP_LCD_SIZE_1_83INCH
        bool "1.83inch Touch LCD Module"
endchoice
choice DISPLAY_ESP32S3_AUDIO_BOARD
    depends on BOARD_TYPE_WAVESHARE_ESP32_S3_AUDIO_BOARD
    prompt "ESP32S3_AUDIO_BOARD LCD Type"
    default AUDIO_BOARD_LCD_JD9853
    help
        LCD Display Type
    config AUDIO_BOARD_LCD_JD9853
        bool "JD9853 320*172"
    config AUDIO_BOARD_LCD_ST7789
        bool "ST7789 240*320"
endchoice
choice DISPLAY_ESP32S3_TOUCH_LCD_1_85C
    depends on BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_85C
    prompt "ESP32S3_TOUCH_LCD_1_85C version"
    default VERSION_2_0
    help
        hardware version
    config VERSION_1_0
        bool "version 1.0"
    config VERSION_2_0
        bool "version 2.0"
endchoice
choice DISPLAY_STYLE
    prompt "Select display style"
    default USE_DEFAULT_MESSAGE_STYLE
    help
        Select display style for Xiaozhi device
    config USE_DEFAULT_MESSAGE_STYLE
        bool "Enable default message style"
    config USE_WECHAT_MESSAGE_STYLE
        bool "Enable WeChat Message Style"
    config USE_EMOTE_MESSAGE_STYLE
        bool "Emote animation style"
        depends on BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_3 \
            || BOARD_TYPE_ESP_VOCAT || BOARD_TYPE_LICHUANG_DEV_S3 \
            || BOARD_TYPE_ESP_SENSAIRSHUTTLE
endchoice
config USE_MULTILINE_CHAT_MESSAGE
    bool "Use multiline chat message display (default mode only)"
    depends on USE_DEFAULT_MESSAGE_STYLE
    default n
    help
        When enabled, the chat message area in the default display mode shows
        multiple wrapped lines that grow upward from the bottom of the screen,
        with auto-adaptive height.
        When disabled (default), a single-line horizontally scrolling label
        is shown at the bottom of the screen.
choice WAKE_WORD_TYPE
    prompt "Wake Word Implementation Type"
    default USE_AFE_WAKE_WORD if (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM
    default WAKE_WORD_DISABLED
    help
        Choose the type of wake word implementation to use
    config WAKE_WORD_DISABLED
        bool "Disabled"
        help
            Disable wake word detection
    config USE_ESP_WAKE_WORD
        bool "Wakenet model without AFE"
        depends on IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32C6 || (IDF_TARGET_ESP32 && SPIRAM)
        help
            Support ESP32 C3、ESP32 C5 ä¸Ž ESP32 C6, and (ESP32 with PSRAM)
    config USE_AFE_WAKE_WORD
        bool "Wakenet model with AFE"
        depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM
        help
            Support AEC if available, requires ESP32 S3 and PSRAM
    config USE_CUSTOM_WAKE_WORD
        bool "Multinet model (Custom Wake Word)"
        depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM
        help
            Requires ESP32 S3 and PSRAM
endchoice
config CUSTOM_WAKE_WORD
    string "Custom Wake Word"
    default "xiao tu dou"
    depends on USE_CUSTOM_WAKE_WORD
    help
        Custom Wake Word, use pinyin for Chinese, separated by spaces
config CUSTOM_WAKE_WORD_DISPLAY
    string "Custom Wake Word Display"
    default "小土豆"
    depends on USE_CUSTOM_WAKE_WORD
    help
        Greeting sent to the server after wake word detection
config CUSTOM_WAKE_WORD_THRESHOLD
    int "Custom Wake Word Threshold (%)"
    default 20
    range 1 99
    depends on USE_CUSTOM_WAKE_WORD
    help
        Custom Wake Word Threshold, range 1-99, the smaller the more sensitive, default 20
config SEND_WAKE_WORD_DATA
    bool "Send Wake Word Data"
    default y
    depends on USE_AFE_WAKE_WORD || USE_CUSTOM_WAKE_WORD
    help
        Send wake word data to the server as the first message of the conversation and wait for response
config WAKE_WORD_DETECTION_IN_LISTENING
    bool "Enable Wake Word Detection in Listening Mode"
    default n
    depends on USE_AFE_WAKE_WORD || USE_CUSTOM_WAKE_WORD
    help
        Enable wake word detection while in listening mode.
        When enabled, the device can detect wake word during listening,
        which allows interrupting the current conversation.
        When disabled (default), wake word detection is turned off during listening.
config USE_AUDIO_PROCESSOR
    bool "Enable Audio Noise Reduction"
    default y
    depends on (IDF_TARGET_ESP32S3 || IDF_TARGET_ESP32P4) && SPIRAM
    help
        Requires ESP32 S3 and PSRAM
config USE_DEVICE_AEC
    bool "Enable Device-Side AEC"
    default n
        depends on USE_AUDIO_PROCESSOR && (BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_LITE \
        || BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75|| BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_83\
        || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_2_06 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3 \
        || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B \
        || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \
        || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7 || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8 || BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1 \
        || BOARD_TYPE_ESP_VOCAT || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_3_49 || BOARD_TYPE_WAVESHARE_ESP32_S3_RLCD_4_2 || BOARD_TYPE_ZHENGCHEN_CAM || BOARD_TYPE_ZHENGCHEN_CAM_ML307 \
        || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_4_3C || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_LCD_1_54 || BOARD_TYPE_WAVESHARE_ESP32_S3_LCD_0_85 || BOARD_TYPE_AI_VOX3 || BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_43C)
    help
        To work properly, device-side AEC requires a clean output reference path from the speaker signal and physical acoustic isolation between the microphone and speaker.
config USE_SERVER_AEC
    bool "Enable Server-Side AEC (Unstable)"
    default n
    depends on USE_AUDIO_PROCESSOR
    help
        To work perperly, server-side AEC requires server support
config USE_AUDIO_DEBUGGER
    bool "Enable Audio Debugger"
    default n
    help
        Enable audio debugger, send audio data through UDP to the host machine
menu "WiFi Configuration Method"
    help
        WiFi Configuration Method Selection
    config USE_HOTSPOT_WIFI_PROVISIONING
        bool "Hotspot"
        default y
        help
            Use WiFi Hotspot to transmit WiFi configuration data
    config USE_ACOUSTIC_WIFI_PROVISIONING
        bool "Acoustic"
        help
            Use audio signal to transmit WiFi configuration data
    config USE_ESP_BLUFI_WIFI_PROVISIONING
        bool "Esp Blufi"
        help
            Use esp blufi protocol to transmit WiFi configuration data
        select BT_ENABLED
        select BT_BLE_42_FEATURES_SUPPORTED
        select BT_BLE_BLUFI_ENABLE
        select MBEDTLS_DHM_C
endmenu
config AUDIO_DEBUG_UDP_SERVER
    string "Audio Debug UDP Server Address"
    default "192.168.2.100:8000"
    depends on USE_AUDIO_DEBUGGER
    help
        UDP server address, format: IP:PORT, used to receive audio debugging data
config RECEIVE_CUSTOM_MESSAGE
    bool "Enable Custom Message Reception"
    default n
    help
        Enable custom message reception, allow the device to receive custom messages from the server (preferably through the MQTT protocol)
menu "Camera Configuration"
    depends on !IDF_TARGET_ESP32
    comment "Warning: Please read the help text before modifying these settings."
    config XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
        bool "Allow JPEG Input"
        default n
        help
            Allow JPEG Input format for the camera.
            This option may need to be enabled when using a USB camera.
            Not currently supported when used simultaneously with XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE.
    config XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
        bool "Enable Hardware JPEG Encoder"
        default y
        depends on SOC_JPEG_ENCODE_SUPPORTED
        help
            Use hardware JPEG encoder on ESP32-P4 to encode image to JPEG.
            See https://docs.espressif.com/projects/esp-idf/en/stable/esp32p4/api-reference/peripherals/jpeg.html for more details.
    config XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER
        bool "Enable Hardware JPEG Decoder"
        default n
        depends on SOC_JPEG_DECODE_SUPPORTED && XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
        help
            Use hardware JPEG decoder on ESP32-P4 to decode JPEG to image.
            See https://docs.espressif.com/projects/esp-idf/en/stable/esp32p4/api-reference/peripherals/jpeg.html for more details.
    config XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
        bool "Enable Camera Debug Mode"
        default n
        help
            Enable camera debug mode, print camera debug information to the console.
            Only works on boards that support camera.
    config XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP
        bool "Enable software camera buffer endianness swapping"
        default n
        depends on !CAMERA_SENSOR_SWAP_PIXEL_BYTE_ORDER
        help
            This option treats the camera buffer as a uint16_t[] array and performs byte-swapping (endianness conversion) on each element.
            Should only be modified by development board integration engineers.
            **Incorrect usage may result in incorrect image colors!**
            ATTENTION: If the option CAMERA_SENSOR_SWAP_PIXEL_BYTE_ORDER is available for your sensor, please use that instead.
    menuconfig XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
        bool "Enable Camera Image Rotation"
        default n
        depends on !XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
        help
            Enable camera image rotation, rotate the camera image to the correct orientation.
            - On ESP32-P4, rotation is handled by PPA hardware.
            - On other chips, rotation is done in software with performance cost.
            - For 180° rotation, use HFlip + VFlip instead of this option.
            Not currently supported when used simultaneously with XIAOZHI_CAMERA_ALLOW_JPEG_INPUT.
    if XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
        choice XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE
            prompt "Camera Image Rotation Angle (clockwise)"
            default XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_90
            help
                Camera image rotation angle.
            config XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_90
                bool "90°"
            config XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_270
                bool "270°"
            comment "For 180° rotation, use HFlip + VFlip instead of this option"
        endchoice
    endif
endmenu
menu "TAIJIPAI_S3_CONFIG"
    depends on BOARD_TYPE_TAIJI_PI_S3
    choice I2S_TYPE_TAIJIPI_S3
        prompt "taiji-pi-S3 I2S Type"
        default TAIJIPAI_I2S_TYPE_STD
        help
            I2S ç±»åž‹é€‰æ‹©
        config TAIJIPAI_I2S_TYPE_STD
            bool "I2S Type STD"
        config TAIJIPAI_I2S_TYPE_PDM
            bool "I2S Type PDM"
    endchoice
    config I2S_USE_2SLOT
    bool "Enable I2S 2 Slot"
    default y
    help
        å¯åŠ¨åŒå£°é“
endmenu
endmenu
xiaozhi-esp32-2.2.6/main/application.cc
New file
@@ -0,0 +1,1116 @@
#include "application.h"
#include "board.h"
#include "display.h"
#include "system_info.h"
#include "audio_codec.h"
#include "mqtt_protocol.h"
#include "websocket_protocol.h"
#include "assets/lang_config.h"
#include "mcp_server.h"
#include "assets.h"
#include "settings.h"
#include <cstring>
#include <esp_log.h>
#include <cJSON.h>
#include <driver/gpio.h>
#include <arpa/inet.h>
#include <font_awesome.h>
#define TAG "Application"
Application::Application() {
    event_group_ = xEventGroupCreate();
#if CONFIG_USE_DEVICE_AEC && CONFIG_USE_SERVER_AEC
#error "CONFIG_USE_DEVICE_AEC and CONFIG_USE_SERVER_AEC cannot be enabled at the same time"
#elif CONFIG_USE_DEVICE_AEC
    aec_mode_ = kAecOnDeviceSide;
#elif CONFIG_USE_SERVER_AEC
    aec_mode_ = kAecOnServerSide;
#else
    aec_mode_ = kAecOff;
#endif
    esp_timer_create_args_t clock_timer_args = {
        .callback = [](void* arg) {
            Application* app = (Application*)arg;
            xEventGroupSetBits(app->event_group_, MAIN_EVENT_CLOCK_TICK);
        },
        .arg = this,
        .dispatch_method = ESP_TIMER_TASK,
        .name = "clock_timer",
        .skip_unhandled_events = true
    };
    esp_timer_create(&clock_timer_args, &clock_timer_handle_);
}
Application::~Application() {
    if (clock_timer_handle_ != nullptr) {
        esp_timer_stop(clock_timer_handle_);
        esp_timer_delete(clock_timer_handle_);
    }
    vEventGroupDelete(event_group_);
}
bool Application::SetDeviceState(DeviceState state) {
    return state_machine_.TransitionTo(state);
}
void Application::Initialize() {
    auto& board = Board::GetInstance();
    SetDeviceState(kDeviceStateStarting);
    // Setup the display
    auto display = board.GetDisplay();
    display->SetupUI();
    // Print board name/version info
    display->SetChatMessage("system", SystemInfo::GetUserAgent().c_str());
    // Setup the audio service
    auto codec = board.GetAudioCodec();
    audio_service_.Initialize(codec);
    audio_service_.Start();
    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);
    // Add state change listeners
    state_machine_.AddStateChangeListener([this](DeviceState old_state, DeviceState new_state) {
        xEventGroupSetBits(event_group_, MAIN_EVENT_STATE_CHANGED);
    });
    // Start the clock timer to update the status bar
    esp_timer_start_periodic(clock_timer_handle_, 1000000);
    // Add MCP common tools (only once during initialization)
    auto& mcp_server = McpServer::GetInstance();
    mcp_server.AddCommonTools();
    mcp_server.AddUserOnlyTools();
    // Set network event callback for UI updates and network state handling
    board.SetNetworkEventCallback([this](NetworkEvent event, const std::string& data) {
        auto display = Board::GetInstance().GetDisplay();
        switch (event) {
            case NetworkEvent::Scanning:
                display->ShowNotification(Lang::Strings::SCANNING_WIFI, 30000);
                xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_DISCONNECTED);
                break;
            case NetworkEvent::Connecting: {
                if (data.empty()) {
                    // Cellular network - registering without carrier info yet
                    display->SetStatus(Lang::Strings::REGISTERING_NETWORK);
                } else {
                    // WiFi or cellular with carrier info
                    std::string msg = Lang::Strings::CONNECT_TO;
                    msg += data;
                    msg += "...";
                    display->ShowNotification(msg.c_str(), 30000);
                }
                break;
            }
            case NetworkEvent::Connected: {
                std::string msg = Lang::Strings::CONNECTED_TO;
                msg += data;
                display->ShowNotification(msg.c_str(), 30000);
                xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_CONNECTED);
                break;
            }
            case NetworkEvent::Disconnected:
                xEventGroupSetBits(event_group_, MAIN_EVENT_NETWORK_DISCONNECTED);
                break;
            case NetworkEvent::WifiConfigModeEnter:
                // WiFi config mode enter is handled by WifiBoard internally
                break;
            case NetworkEvent::WifiConfigModeExit:
                // WiFi config mode exit is handled by WifiBoard internally
                break;
            // Cellular modem specific events
            case NetworkEvent::ModemDetecting:
                display->SetStatus(Lang::Strings::DETECTING_MODULE);
                break;
            case NetworkEvent::ModemErrorNoSim:
                Alert(Lang::Strings::ERROR, Lang::Strings::PIN_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_PIN);
                break;
            case NetworkEvent::ModemErrorRegDenied:
                Alert(Lang::Strings::ERROR, Lang::Strings::REG_ERROR, "triangle_exclamation", Lang::Sounds::OGG_ERR_REG);
                break;
            case NetworkEvent::ModemErrorInitFailed:
                Alert(Lang::Strings::ERROR, Lang::Strings::MODEM_INIT_ERROR, "triangle_exclamation", Lang::Sounds::OGG_EXCLAMATION);
                break;
            case NetworkEvent::ModemErrorTimeout:
                display->SetStatus(Lang::Strings::REGISTERING_NETWORK);
                break;
        }
    });
    // Start network asynchronously
    board.StartNetwork();
    // Update the status bar immediately to show the network state
    display->UpdateStatusBar(true);
}
void Application::Run() {
    // Set the priority of the main task to 10
    vTaskPrioritySet(nullptr, 10);
    const EventBits_t ALL_EVENTS =
        MAIN_EVENT_SCHEDULE |
        MAIN_EVENT_SEND_AUDIO |
        MAIN_EVENT_WAKE_WORD_DETECTED |
        MAIN_EVENT_VAD_CHANGE |
        MAIN_EVENT_CLOCK_TICK |
        MAIN_EVENT_ERROR |
        MAIN_EVENT_NETWORK_CONNECTED |
        MAIN_EVENT_NETWORK_DISCONNECTED |
        MAIN_EVENT_TOGGLE_CHAT |
        MAIN_EVENT_START_LISTENING |
        MAIN_EVENT_STOP_LISTENING |
        MAIN_EVENT_ACTIVATION_DONE |
        MAIN_EVENT_STATE_CHANGED;
    while (true) {
        auto bits = xEventGroupWaitBits(event_group_, ALL_EVENTS, pdTRUE, pdFALSE, portMAX_DELAY);
        if (bits & MAIN_EVENT_ERROR) {
            SetDeviceState(kDeviceStateIdle);
            Alert(Lang::Strings::ERROR, last_error_message_.c_str(), "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
        }
        if (bits & MAIN_EVENT_NETWORK_CONNECTED) {
            HandleNetworkConnectedEvent();
        }
        if (bits & MAIN_EVENT_NETWORK_DISCONNECTED) {
            HandleNetworkDisconnectedEvent();
        }
        if (bits & MAIN_EVENT_ACTIVATION_DONE) {
            HandleActivationDoneEvent();
        }
        if (bits & MAIN_EVENT_STATE_CHANGED) {
            HandleStateChangedEvent();
        }
        if (bits & MAIN_EVENT_TOGGLE_CHAT) {
            HandleToggleChatEvent();
        }
        if (bits & MAIN_EVENT_START_LISTENING) {
            HandleStartListeningEvent();
        }
        if (bits & MAIN_EVENT_STOP_LISTENING) {
            HandleStopListeningEvent();
        }
        if (bits & MAIN_EVENT_SEND_AUDIO) {
            while (auto packet = audio_service_.PopPacketFromSendQueue()) {
                if (protocol_ && !protocol_->SendAudio(std::move(packet))) {
                    break;
                }
            }
        }
        if (bits & MAIN_EVENT_WAKE_WORD_DETECTED) {
            HandleWakeWordDetectedEvent();
        }
        if (bits & MAIN_EVENT_VAD_CHANGE) {
            if (GetDeviceState() == kDeviceStateListening) {
                auto led = Board::GetInstance().GetLed();
                led->OnStateChanged();
            }
        }
        if (bits & MAIN_EVENT_SCHEDULE) {
            std::unique_lock<std::mutex> lock(mutex_);
            auto tasks = std::move(main_tasks_);
            lock.unlock();
            for (auto& task : tasks) {
                task();
            }
        }
        if (bits & MAIN_EVENT_CLOCK_TICK) {
            clock_ticks_++;
            auto display = Board::GetInstance().GetDisplay();
            display->UpdateStatusBar();
            // Print debug info every 10 seconds
            if (clock_ticks_ % 10 == 0) {
                SystemInfo::PrintHeapStats();
            }
        }
    }
}
void Application::HandleNetworkConnectedEvent() {
    ESP_LOGI(TAG, "Network connected");
    auto state = GetDeviceState();
    if (state == kDeviceStateStarting || state == kDeviceStateWifiConfiguring) {
        // Network is ready, start activation
        SetDeviceState(kDeviceStateActivating);
        if (activation_task_handle_ != nullptr) {
            ESP_LOGW(TAG, "Activation task already running");
            return;
        }
        xTaskCreate([](void* arg) {
            Application* app = static_cast<Application*>(arg);
            app->ActivationTask();
            app->activation_task_handle_ = nullptr;
            vTaskDelete(NULL);
        }, "activation", 4096 * 2, this, 2, &activation_task_handle_);
    }
    // Update the status bar immediately to show the network state
    auto display = Board::GetInstance().GetDisplay();
    display->UpdateStatusBar(true);
}
void Application::HandleNetworkDisconnectedEvent() {
    // Close current conversation when network disconnected
    auto state = GetDeviceState();
    if (state == kDeviceStateConnecting || state == kDeviceStateListening || state == kDeviceStateSpeaking) {
        ESP_LOGI(TAG, "Closing audio channel due to network disconnection");
        protocol_->CloseAudioChannel();
    }
    // Update the status bar immediately to show the network state
    auto display = Board::GetInstance().GetDisplay();
    display->UpdateStatusBar(true);
}
void Application::HandleActivationDoneEvent() {
    ESP_LOGI(TAG, "Activation done");
    SystemInfo::PrintHeapStats();
    SetDeviceState(kDeviceStateIdle);
    has_server_time_ = ota_->HasServerTime();
    auto display = Board::GetInstance().GetDisplay();
    std::string message = std::string(Lang::Strings::VERSION) + ota_->GetCurrentVersion();
    display->ShowNotification(message.c_str());
    display->SetChatMessage("system", "");
    // Release OTA object after activation is complete
    ota_.reset();
    auto& board = Board::GetInstance();
    board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
    Schedule([this]() {
        // Play the success sound to indicate the device is ready
        audio_service_.PlaySound(Lang::Sounds::OGG_SUCCESS);
    });
}
void Application::ActivationTask() {
    // Create OTA object for activation process
    ota_ = std::make_unique<Ota>();
    // Check for new assets version
    CheckAssetsVersion();
    // Check for new firmware version
    CheckNewVersion();
    // Initialize the protocol
    InitializeProtocol();
    // Signal completion to main loop
    xEventGroupSetBits(event_group_, MAIN_EVENT_ACTIVATION_DONE);
}
void Application::CheckAssetsVersion() {
    // Only allow CheckAssetsVersion to be called once
    if (assets_version_checked_) {
        return;
    }
    assets_version_checked_ = true;
    auto& board = Board::GetInstance();
    auto display = board.GetDisplay();
    auto& assets = Assets::GetInstance();
    if (!assets.partition_valid()) {
        ESP_LOGW(TAG, "Assets partition is disabled for board %s", BOARD_NAME);
        return;
    }
    Settings settings("assets", true);
    // Check if there is a new assets need to be downloaded
    std::string download_url = settings.GetString("download_url");
    if (!download_url.empty()) {
        settings.EraseKey("download_url");
        char message[256];
        snprintf(message, sizeof(message), Lang::Strings::FOUND_NEW_ASSETS, download_url.c_str());
        Alert(Lang::Strings::LOADING_ASSETS, message, "cloud_arrow_down", Lang::Sounds::OGG_UPGRADE);
        // Wait for the audio service to be idle for 3 seconds
        vTaskDelay(pdMS_TO_TICKS(3000));
        SetDeviceState(kDeviceStateUpgrading);
        board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
        display->SetChatMessage("system", Lang::Strings::PLEASE_WAIT);
        bool success = assets.Download(download_url, [this, display](int progress, size_t speed) -> void {
            char buffer[32];
            snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024);
            Schedule([display, message = std::string(buffer)]() {
                display->SetChatMessage("system", message.c_str());
            });
        });
        board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
        vTaskDelay(pdMS_TO_TICKS(1000));
        if (!success) {
            Alert(Lang::Strings::ERROR, Lang::Strings::DOWNLOAD_ASSETS_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
            vTaskDelay(pdMS_TO_TICKS(2000));
            SetDeviceState(kDeviceStateActivating);
            return;
        }
    }
    // Apply assets
    assets.Apply();
    display->SetChatMessage("system", "");
    display->SetEmotion("microchip_ai");
}
void Application::CheckNewVersion() {
    const int MAX_RETRY = 10;
    int retry_count = 0;
    int retry_delay = 10; // Initial retry delay in seconds
    auto& board = Board::GetInstance();
    while (true) {
        auto display = board.GetDisplay();
        display->SetStatus(Lang::Strings::CHECKING_NEW_VERSION);
        esp_err_t err = ota_->CheckVersion();
        if (err != ESP_OK) {
            retry_count++;
            if (retry_count >= MAX_RETRY) {
                ESP_LOGE(TAG, "Too many retries, exit version check");
                return;
            }
            char error_message[128];
            snprintf(error_message, sizeof(error_message), "code=%d, url=%s", err, ota_->GetCheckVersionUrl().c_str());
            char buffer[256];
            snprintf(buffer, sizeof(buffer), Lang::Strings::CHECK_NEW_VERSION_FAILED, retry_delay, error_message);
            Alert(Lang::Strings::ERROR, buffer, "cloud_slash", Lang::Sounds::OGG_EXCLAMATION);
            ESP_LOGW(TAG, "Check new version failed, retry in %d seconds (%d/%d)", retry_delay, retry_count, MAX_RETRY);
            for (int i = 0; i < retry_delay; i++) {
                vTaskDelay(pdMS_TO_TICKS(1000));
                if (GetDeviceState() == kDeviceStateIdle) {
                    break;
                }
            }
            retry_delay *= 2; // Double the retry delay
            continue;
        }
        retry_count = 0;
        retry_delay = 10; // Reset retry delay
        if (ota_->HasNewVersion()) {
            if (UpgradeFirmware(ota_->GetFirmwareUrl(), ota_->GetFirmwareVersion())) {
                return; // This line will never be reached after reboot
            }
            // If upgrade failed, continue to normal operation
        }
        // No new version, mark the current version as valid
        ota_->MarkCurrentVersionValid();
        if (!ota_->HasActivationCode() && !ota_->HasActivationChallenge()) {
            // Exit the loop if done checking new version
            break;
        }
        display->SetStatus(Lang::Strings::ACTIVATION);
        // Activation code is shown to the user and waiting for the user to input
        if (ota_->HasActivationCode()) {
            ShowActivationCode(ota_->GetActivationCode(), ota_->GetActivationMessage());
        }
        // This will block the loop until the activation is done or timeout
        for (int i = 0; i < 10; ++i) {
            ESP_LOGI(TAG, "Activating... %d/%d", i + 1, 10);
            esp_err_t err = ota_->Activate();
            if (err == ESP_OK) {
                break;
            } else if (err == ESP_ERR_TIMEOUT) {
                vTaskDelay(pdMS_TO_TICKS(3000));
            } else {
                vTaskDelay(pdMS_TO_TICKS(10000));
            }
            if (GetDeviceState() == kDeviceStateIdle) {
                break;
            }
        }
    }
}
void Application::InitializeProtocol() {
    auto& board = Board::GetInstance();
    auto display = board.GetDisplay();
    auto codec = board.GetAudioCodec();
    display->SetStatus(Lang::Strings::LOADING_PROTOCOL);
    if (ota_->HasMqttConfig()) {
        protocol_ = std::make_unique<MqttProtocol>();
    } else if (ota_->HasWebsocketConfig()) {
        protocol_ = std::make_unique<WebsocketProtocol>();
    } else {
        ESP_LOGW(TAG, "No protocol specified in the OTA config, using MQTT");
        protocol_ = std::make_unique<MqttProtocol>();
    }
    protocol_->OnConnected([this]() {
        DismissAlert();
    });
    protocol_->OnNetworkError([this](const std::string& message) {
        last_error_message_ = message;
        xEventGroupSetBits(event_group_, MAIN_EVENT_ERROR);
    });
    protocol_->OnIncomingAudio([this](std::unique_ptr<AudioStreamPacket> packet) {
        if (GetDeviceState() == kDeviceStateSpeaking) {
            audio_service_.PushPacketToDecodeQueue(std::move(packet));
        }
    });
    protocol_->OnAudioChannelOpened([this, codec, &board]() {
        board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
        if (protocol_->server_sample_rate() != codec->output_sample_rate()) {
            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());
        }
    });
    protocol_->OnAudioChannelClosed([this, &board]() {
        board.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
        Schedule([this]() {
            auto display = Board::GetInstance().GetDisplay();
            display->SetChatMessage("system", "");
            SetDeviceState(kDeviceStateIdle);
        });
    });
    protocol_->OnIncomingJson([this, display](const cJSON* root) {
        // Parse JSON data
        auto type = cJSON_GetObjectItem(root, "type");
        if (strcmp(type->valuestring, "tts") == 0) {
            auto state = cJSON_GetObjectItem(root, "state");
            if (strcmp(state->valuestring, "start") == 0) {
                Schedule([this]() {
                    aborted_ = false;
                    SetDeviceState(kDeviceStateSpeaking);
                });
            } else if (strcmp(state->valuestring, "stop") == 0) {
                Schedule([this]() {
                    if (GetDeviceState() == kDeviceStateSpeaking) {
                        if (listening_mode_ == kListeningModeManualStop) {
                            SetDeviceState(kDeviceStateIdle);
                        } else {
                            SetDeviceState(kDeviceStateListening);
                        }
                    }
                });
            } else if (strcmp(state->valuestring, "sentence_start") == 0) {
                auto text = cJSON_GetObjectItem(root, "text");
                if (cJSON_IsString(text)) {
                    ESP_LOGI(TAG, "<< %s", text->valuestring);
                    Schedule([display, message = std::string(text->valuestring)]() {
                        display->SetChatMessage("assistant", message.c_str());
                    });
                }
            }
        } else if (strcmp(type->valuestring, "stt") == 0) {
            auto text = cJSON_GetObjectItem(root, "text");
            if (cJSON_IsString(text)) {
                ESP_LOGI(TAG, ">> %s", text->valuestring);
                Schedule([display, message = std::string(text->valuestring)]() {
                    display->SetChatMessage("user", message.c_str());
                });
            }
        } else if (strcmp(type->valuestring, "llm") == 0) {
            auto emotion = cJSON_GetObjectItem(root, "emotion");
            if (cJSON_IsString(emotion)) {
                Schedule([display, emotion_str = std::string(emotion->valuestring)]() {
                    display->SetEmotion(emotion_str.c_str());
                });
            }
        } else if (strcmp(type->valuestring, "mcp") == 0) {
            auto payload = cJSON_GetObjectItem(root, "payload");
            if (cJSON_IsObject(payload)) {
                McpServer::GetInstance().ParseMessage(payload);
            }
        } else if (strcmp(type->valuestring, "system") == 0) {
            auto command = cJSON_GetObjectItem(root, "command");
            if (cJSON_IsString(command)) {
                ESP_LOGI(TAG, "System command: %s", command->valuestring);
                if (strcmp(command->valuestring, "reboot") == 0) {
                    // Do a reboot if user requests a OTA update
                    Schedule([this]() {
                        Reboot();
                    });
                } else {
                    ESP_LOGW(TAG, "Unknown system command: %s", command->valuestring);
                }
            }
        } else if (strcmp(type->valuestring, "alert") == 0) {
            auto status = cJSON_GetObjectItem(root, "status");
            auto message = cJSON_GetObjectItem(root, "message");
            auto emotion = cJSON_GetObjectItem(root, "emotion");
            if (cJSON_IsString(status) && cJSON_IsString(message) && cJSON_IsString(emotion)) {
                Alert(status->valuestring, message->valuestring, emotion->valuestring, Lang::Sounds::OGG_VIBRATION);
            } 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);
        }
    });
    protocol_->Start();
}
void Application::ShowActivationCode(const std::string& code, const std::string& message) {
    struct digit_sound {
        char digit;
        const std::string_view& sound;
    };
    static const std::array<digit_sound, 10> digit_sounds{{
        digit_sound{'0', Lang::Sounds::OGG_0},
        digit_sound{'1', Lang::Sounds::OGG_1},
        digit_sound{'2', Lang::Sounds::OGG_2},
        digit_sound{'3', Lang::Sounds::OGG_3},
        digit_sound{'4', Lang::Sounds::OGG_4},
        digit_sound{'5', Lang::Sounds::OGG_5},
        digit_sound{'6', Lang::Sounds::OGG_6},
        digit_sound{'7', Lang::Sounds::OGG_7},
        digit_sound{'8', Lang::Sounds::OGG_8},
        digit_sound{'9', Lang::Sounds::OGG_9}
    }};
    // This sentence uses 9KB of SRAM, so we need to wait for it to finish
    Alert(Lang::Strings::ACTIVATION, message.c_str(), "link", Lang::Sounds::OGG_ACTIVATION);
    for (const auto& digit : code) {
        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()) {
            audio_service_.PlaySound(it->sound);
        }
    }
}
void Application::Alert(const char* status, const char* message, const char* emotion, const std::string_view& sound) {
    ESP_LOGW(TAG, "Alert [%s] %s: %s", emotion, status, message);
    auto display = Board::GetInstance().GetDisplay();
    display->SetStatus(status);
    display->SetEmotion(emotion);
    display->SetChatMessage("system", message);
    if (!sound.empty()) {
        audio_service_.PlaySound(sound);
    }
}
void Application::DismissAlert() {
    if (GetDeviceState() == kDeviceStateIdle) {
        auto display = Board::GetInstance().GetDisplay();
        display->SetStatus(Lang::Strings::STANDBY);
        display->SetEmotion("neutral");
        display->SetChatMessage("system", "");
    }
}
void Application::ToggleChatState() {
    xEventGroupSetBits(event_group_, MAIN_EVENT_TOGGLE_CHAT);
}
void Application::StartListening() {
    xEventGroupSetBits(event_group_, MAIN_EVENT_START_LISTENING);
}
void Application::StopListening() {
    xEventGroupSetBits(event_group_, MAIN_EVENT_STOP_LISTENING);
}
void Application::HandleToggleChatEvent() {
    auto state = GetDeviceState();
    if (state == kDeviceStateActivating) {
        SetDeviceState(kDeviceStateIdle);
        return;
    } else if (state == kDeviceStateWifiConfiguring) {
        audio_service_.EnableAudioTesting(true);
        SetDeviceState(kDeviceStateAudioTesting);
        return;
    } else if (state == kDeviceStateAudioTesting) {
        audio_service_.EnableAudioTesting(false);
        SetDeviceState(kDeviceStateWifiConfiguring);
        return;
    }
    if (!protocol_) {
        ESP_LOGE(TAG, "Protocol not initialized");
        return;
    }
    if (state == kDeviceStateIdle) {
        ListeningMode mode = GetDefaultListeningMode();
        if (!protocol_->IsAudioChannelOpened()) {
            SetDeviceState(kDeviceStateConnecting);
            // Schedule to let the state change be processed first (UI update)
            Schedule([this, mode]() {
                ContinueOpenAudioChannel(mode);
            });
            return;
        }
        SetListeningMode(mode);
    } else if (state == kDeviceStateSpeaking) {
        AbortSpeaking(kAbortReasonNone);
    } else if (state == kDeviceStateListening) {
        protocol_->CloseAudioChannel();
    }
}
void Application::ContinueOpenAudioChannel(ListeningMode mode) {
    // Check state again in case it was changed during scheduling
    if (GetDeviceState() != kDeviceStateConnecting) {
        return;
    }
    if (!protocol_->IsAudioChannelOpened()) {
        if (!protocol_->OpenAudioChannel()) {
            return;
        }
    }
    SetListeningMode(mode);
}
void Application::HandleStartListeningEvent() {
    auto state = GetDeviceState();
    if (state == kDeviceStateActivating) {
        SetDeviceState(kDeviceStateIdle);
        return;
    } else if (state == kDeviceStateWifiConfiguring) {
        audio_service_.EnableAudioTesting(true);
        SetDeviceState(kDeviceStateAudioTesting);
        return;
    }
    if (!protocol_) {
        ESP_LOGE(TAG, "Protocol not initialized");
        return;
    }
    if (state == kDeviceStateIdle) {
        if (!protocol_->IsAudioChannelOpened()) {
            SetDeviceState(kDeviceStateConnecting);
            // Schedule to let the state change be processed first (UI update)
            Schedule([this]() {
                ContinueOpenAudioChannel(kListeningModeManualStop);
            });
            return;
        }
        SetListeningMode(kListeningModeManualStop);
    } else if (state == kDeviceStateSpeaking) {
        AbortSpeaking(kAbortReasonNone);
        SetListeningMode(kListeningModeManualStop);
    }
}
void Application::HandleStopListeningEvent() {
    auto state = GetDeviceState();
    if (state == kDeviceStateAudioTesting) {
        audio_service_.EnableAudioTesting(false);
        SetDeviceState(kDeviceStateWifiConfiguring);
        return;
    } else if (state == kDeviceStateListening) {
        if (protocol_) {
            protocol_->SendStopListening();
        }
        SetDeviceState(kDeviceStateIdle);
    }
}
void Application::HandleWakeWordDetectedEvent() {
    if (!protocol_) {
        return;
    }
    auto state = GetDeviceState();
    auto wake_word = audio_service_.GetLastWakeWord();
    ESP_LOGI(TAG, "Wake word detected: %s (state: %d)", wake_word.c_str(), (int)state);
    if (state == kDeviceStateIdle) {
        audio_service_.EncodeWakeWord();
        auto wake_word = audio_service_.GetLastWakeWord();
        if (!protocol_->IsAudioChannelOpened()) {
            SetDeviceState(kDeviceStateConnecting);
            // Schedule to let the state change be processed first (UI update),
            // then continue with OpenAudioChannel which may block for ~1 second
            Schedule([this, wake_word]() {
                ContinueWakeWordInvoke(wake_word);
            });
            return;
        }
        // Channel already opened, continue directly
        ContinueWakeWordInvoke(wake_word);
    } else if (state == kDeviceStateSpeaking || state == kDeviceStateListening) {
        AbortSpeaking(kAbortReasonWakeWordDetected);
        // Clear send queue to avoid sending residues to server
        while (audio_service_.PopPacketFromSendQueue());
        if (state == kDeviceStateListening) {
            protocol_->SendStartListening(GetDefaultListeningMode());
            audio_service_.ResetDecoder();
            audio_service_.PlaySound(Lang::Sounds::OGG_POPUP);
            // Re-enable wake word detection as it was stopped by the detection itself
            audio_service_.EnableWakeWordDetection(true);
        } else {
            // Play popup sound and start listening again
            play_popup_on_listening_ = true;
            SetListeningMode(GetDefaultListeningMode());
        }
    } else if (state == kDeviceStateActivating) {
        // Restart the activation check if the wake word is detected during activation
        SetDeviceState(kDeviceStateIdle);
    }
}
void Application::ContinueWakeWordInvoke(const std::string& wake_word) {
    // Check state again in case it was changed during scheduling
    if (GetDeviceState() != kDeviceStateConnecting) {
        return;
    }
    if (!protocol_->IsAudioChannelOpened()) {
        if (!protocol_->OpenAudioChannel()) {
            audio_service_.EnableWakeWordDetection(true);
            return;
        }
    }
    ESP_LOGI(TAG, "Wake word detected: %s", wake_word.c_str());
#if CONFIG_SEND_WAKE_WORD_DATA
    // Encode and send the wake word data to the server
    while (auto packet = audio_service_.PopWakeWordPacket()) {
        protocol_->SendAudio(std::move(packet));
    }
    // Set the chat state to wake word detected
    protocol_->SendWakeWordDetected(wake_word);
    SetListeningMode(GetDefaultListeningMode());
#else
    // Set flag to play popup sound after state changes to listening
    // (PlaySound here would be cleared by ResetDecoder in EnableVoiceProcessing)
    play_popup_on_listening_ = true;
    SetListeningMode(GetDefaultListeningMode());
#endif
}
void Application::HandleStateChangedEvent() {
    DeviceState new_state = state_machine_.GetState();
    clock_ticks_ = 0;
    auto& board = Board::GetInstance();
    auto display = board.GetDisplay();
    auto led = board.GetLed();
    led->OnStateChanged();
    switch (new_state) {
        case kDeviceStateUnknown:
        case kDeviceStateIdle:
            display->SetStatus(Lang::Strings::STANDBY);
            display->ClearChatMessages();  // Clear messages first
            display->SetEmotion("neutral"); // Then set emotion (wechat mode checks child count)
            audio_service_.EnableVoiceProcessing(false);
            audio_service_.EnableWakeWordDetection(true);
            break;
        case kDeviceStateConnecting:
            display->SetStatus(Lang::Strings::CONNECTING);
            display->SetEmotion("neutral");
            display->SetChatMessage("system", "");
            break;
        case kDeviceStateListening:
            display->SetStatus(Lang::Strings::LISTENING);
            display->SetEmotion("neutral");
            // Make sure the audio processor is running
            if (play_popup_on_listening_ || !audio_service_.IsAudioProcessorRunning()) {
                // For auto mode, wait for playback queue to be empty before enabling voice processing
                // This prevents audio truncation when STOP arrives late due to network jitter
                if (listening_mode_ == kListeningModeAutoStop) {
                    audio_service_.WaitForPlaybackQueueEmpty();
                }
                // Send the start listening command
                protocol_->SendStartListening(listening_mode_);
                audio_service_.EnableVoiceProcessing(true);
            }
#ifdef CONFIG_WAKE_WORD_DETECTION_IN_LISTENING
            // Enable wake word detection in listening mode (configured via Kconfig)
            audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
#else
            // Disable wake word detection in listening mode
            audio_service_.EnableWakeWordDetection(false);
#endif
            // Play popup sound after ResetDecoder (in EnableVoiceProcessing) has been called
            if (play_popup_on_listening_) {
                play_popup_on_listening_ = false;
                audio_service_.PlaySound(Lang::Sounds::OGG_POPUP);
            }
            break;
        case kDeviceStateSpeaking:
            display->SetStatus(Lang::Strings::SPEAKING);
            if (listening_mode_ != kListeningModeRealtime) {
                audio_service_.EnableVoiceProcessing(false);
                // Only AFE wake word can be detected in speaking mode
                audio_service_.EnableWakeWordDetection(audio_service_.IsAfeWakeWord());
            }
            audio_service_.ResetDecoder();
            break;
        case kDeviceStateWifiConfiguring:
            audio_service_.EnableVoiceProcessing(false);
            audio_service_.EnableWakeWordDetection(false);
            break;
        default:
            // Do nothing
            break;
    }
}
void Application::Schedule(std::function<void()>&& callback) {
    {
        std::lock_guard<std::mutex> lock(mutex_);
        main_tasks_.push_back(std::move(callback));
    }
    xEventGroupSetBits(event_group_, MAIN_EVENT_SCHEDULE);
}
void Application::AbortSpeaking(AbortReason reason) {
    ESP_LOGI(TAG, "Abort speaking");
    aborted_ = true;
    if (protocol_) {
        protocol_->SendAbortSpeaking(reason);
    }
}
void Application::SetListeningMode(ListeningMode mode) {
    listening_mode_ = mode;
    SetDeviceState(kDeviceStateListening);
}
ListeningMode Application::GetDefaultListeningMode() const {
    return aec_mode_ == kAecOff ? kListeningModeAutoStop : kListeningModeRealtime;
}
void Application::Reboot() {
    ESP_LOGI(TAG, "Rebooting...");
    // Disconnect the audio channel
    if (protocol_ && protocol_->IsAudioChannelOpened()) {
        protocol_->CloseAudioChannel();
    }
    protocol_.reset();
    audio_service_.Stop();
    vTaskDelay(pdMS_TO_TICKS(1000));
    esp_restart();
}
bool Application::UpgradeFirmware(const std::string& url, const std::string& version) {
    auto& board = Board::GetInstance();
    auto display = board.GetDisplay();
    std::string upgrade_url = url;
    std::string version_info = version.empty() ? "(Manual upgrade)" : version;
    // Close audio channel if it's open
    if (protocol_ && protocol_->IsAudioChannelOpened()) {
        ESP_LOGI(TAG, "Closing audio channel before firmware upgrade");
        protocol_->CloseAudioChannel();
    }
    ESP_LOGI(TAG, "Starting firmware upgrade from URL: %s", upgrade_url.c_str());
    Alert(Lang::Strings::OTA_UPGRADE, Lang::Strings::UPGRADING, "download", Lang::Sounds::OGG_UPGRADE);
    vTaskDelay(pdMS_TO_TICKS(3000));
    SetDeviceState(kDeviceStateUpgrading);
    std::string message = std::string(Lang::Strings::NEW_VERSION) + version_info;
    display->SetChatMessage("system", message.c_str());
    board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
    audio_service_.Stop();
    vTaskDelay(pdMS_TO_TICKS(1000));
    bool upgrade_success = Ota::Upgrade(upgrade_url, [this, display](int progress, size_t speed) {
        char buffer[32];
        snprintf(buffer, sizeof(buffer), "%d%% %uKB/s", progress, speed / 1024);
        Schedule([display, message = std::string(buffer)]() {
            display->SetChatMessage("system", message.c_str());
        });
    });
    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.SetPowerSaveLevel(PowerSaveLevel::LOW_POWER); // Restore power save level
        Alert(Lang::Strings::ERROR, Lang::Strings::UPGRADE_FAILED, "circle_xmark", Lang::Sounds::OGG_EXCLAMATION);
        vTaskDelay(pdMS_TO_TICKS(3000));
        return false;
    } 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 true;
    }
}
void Application::WakeWordInvoke(const std::string& wake_word) {
    if (!protocol_) {
        return;
    }
    auto state = GetDeviceState();
    if (state == kDeviceStateIdle) {
        audio_service_.EncodeWakeWord();
        if (!protocol_->IsAudioChannelOpened()) {
            SetDeviceState(kDeviceStateConnecting);
            // Schedule to let the state change be processed first (UI update)
            Schedule([this, wake_word]() {
                ContinueWakeWordInvoke(wake_word);
            });
            return;
        }
        // Channel already opened, continue directly
        ContinueWakeWordInvoke(wake_word);
    } else if (state == kDeviceStateSpeaking) {
        Schedule([this]() {
            AbortSpeaking(kAbortReasonNone);
        });
    } else if (state == kDeviceStateListening) {
        Schedule([this]() {
            if (protocol_) {
                protocol_->CloseAudioChannel();
            }
        });
    }
}
bool Application::CanEnterSleepMode() {
    if (GetDeviceState() != kDeviceStateIdle) {
        return false;
    }
    if (protocol_ && protocol_->IsAudioChannelOpened()) {
        return false;
    }
    if (!audio_service_.IsIdle()) {
        return false;
    }
    // Now it is safe to enter sleep mode
    return true;
}
void Application::SendMcpMessage(const std::string& payload) {
    // Always schedule to run in main task for thread safety
    Schedule([this, payload = std::move(payload)]() {
        if (protocol_) {
            protocol_->SendMcpMessage(payload);
        }
    });
}
void Application::SetAecMode(AecMode mode) {
    aec_mode_ = mode;
    Schedule([this]() {
        auto& board = Board::GetInstance();
        auto display = board.GetDisplay();
        switch (aec_mode_) {
        case kAecOff:
            audio_service_.EnableDeviceAec(false);
            display->ShowNotification(Lang::Strings::RTC_MODE_OFF);
            break;
        case kAecOnServerSide:
            audio_service_.EnableDeviceAec(false);
            display->ShowNotification(Lang::Strings::RTC_MODE_ON);
            break;
        case kAecOnDeviceSide:
            audio_service_.EnableDeviceAec(true);
            display->ShowNotification(Lang::Strings::RTC_MODE_ON);
            break;
        }
        // If the AEC mode is changed, close the audio channel
        if (protocol_ && protocol_->IsAudioChannelOpened()) {
            protocol_->CloseAudioChannel();
        }
    });
}
void Application::PlaySound(const std::string_view& sound) {
    audio_service_.PlaySound(sound);
}
void Application::ResetProtocol() {
    Schedule([this]() {
        // Close audio channel if opened
        if (protocol_ && protocol_->IsAudioChannelOpened()) {
            protocol_->CloseAudioChannel();
        }
        // Reset protocol
        protocol_.reset();
    });
}
xiaozhi-esp32-2.2.6/main/application.h
New file
@@ -0,0 +1,189 @@
#ifndef _APPLICATION_H_
#define _APPLICATION_H_
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <freertos/task.h>
#include <esp_timer.h>
#include <string>
#include <mutex>
#include <deque>
#include <memory>
#include "protocol.h"
#include "ota.h"
#include "audio_service.h"
#include "device_state.h"
#include "device_state_machine.h"
// Main event bits
#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_ACTIVATION_DONE      (1 << 5)
#define MAIN_EVENT_CLOCK_TICK           (1 << 6)
#define MAIN_EVENT_NETWORK_CONNECTED    (1 << 7)
#define MAIN_EVENT_NETWORK_DISCONNECTED (1 << 8)
#define MAIN_EVENT_TOGGLE_CHAT          (1 << 9)
#define MAIN_EVENT_START_LISTENING      (1 << 10)
#define MAIN_EVENT_STOP_LISTENING       (1 << 11)
#define MAIN_EVENT_STATE_CHANGED        (1 << 12)
enum AecMode {
    kAecOff,
    kAecOnDeviceSide,
    kAecOnServerSide,
};
class Application {
public:
    static Application& GetInstance() {
        static Application instance;
        return instance;
    }
    // Delete copy constructor and assignment operator
    Application(const Application&) = delete;
    Application& operator=(const Application&) = delete;
    /**
     * Initialize the application
     * This sets up display, audio, network callbacks, etc.
     * Network connection starts asynchronously.
     */
    void Initialize();
    /**
     * Run the main event loop
     * This function runs in the main task and never returns.
     * It handles all events including network, state changes, and user interactions.
     */
    void Run();
    DeviceState GetDeviceState() const { return state_machine_.GetState(); }
    bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); }
    /**
     * Request state transition
     * Returns true if transition was successful
     */
    bool SetDeviceState(DeviceState state);
    /**
     * Schedule a callback to be executed in the main task
     */
    void Schedule(std::function<void()>&& callback);
    /**
     * Alert with status, message, emotion and optional sound
     */
    void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");
    void DismissAlert();
    void AbortSpeaking(AbortReason reason);
    /**
     * Toggle chat state (event-based, thread-safe)
     * Sends MAIN_EVENT_TOGGLE_CHAT to be handled in Run()
     */
    void ToggleChatState();
    /**
     * Start listening (event-based, thread-safe)
     * Sends MAIN_EVENT_START_LISTENING to be handled in Run()
     */
    void StartListening();
    /**
     * Stop listening (event-based, thread-safe)
     * Sends MAIN_EVENT_STOP_LISTENING to be handled in Run()
     */
    void StopListening();
    void Reboot();
    void WakeWordInvoke(const std::string& wake_word);
    bool UpgradeFirmware(const std::string& url, const std::string& version = "");
    bool CanEnterSleepMode();
    void SendMcpMessage(const std::string& payload);
    void SetAecMode(AecMode mode);
    AecMode GetAecMode() const { return aec_mode_; }
    void PlaySound(const std::string_view& sound);
    AudioService& GetAudioService() { return audio_service_; }
    /**
     * Reset protocol resources (thread-safe)
     * Can be called from any task to release resources allocated after network connected
     * This includes closing audio channel, resetting protocol and ota objects
     */
    void ResetProtocol();
private:
    Application();
    ~Application();
    std::mutex mutex_;
    std::deque<std::function<void()>> main_tasks_;
    std::unique_ptr<Protocol> protocol_;
    EventGroupHandle_t event_group_ = nullptr;
    esp_timer_handle_t clock_timer_handle_ = nullptr;
    DeviceStateMachine state_machine_;
    ListeningMode listening_mode_ = kListeningModeAutoStop;
    AecMode aec_mode_ = kAecOff;
    std::string last_error_message_;
    AudioService audio_service_;
    std::unique_ptr<Ota> ota_;
    bool has_server_time_ = false;
    bool aborted_ = false;
    bool assets_version_checked_ = false;
    bool play_popup_on_listening_ = false;  // Flag to play popup sound after state changes to listening
    int clock_ticks_ = 0;
    TaskHandle_t activation_task_handle_ = nullptr;
    // Event handlers
    void HandleStateChangedEvent();
    void HandleToggleChatEvent();
    void HandleStartListeningEvent();
    void HandleStopListeningEvent();
    void HandleNetworkConnectedEvent();
    void HandleNetworkDisconnectedEvent();
    void HandleActivationDoneEvent();
    void HandleWakeWordDetectedEvent();
    void ContinueOpenAudioChannel(ListeningMode mode);
    void ContinueWakeWordInvoke(const std::string& wake_word);
    // Activation task (runs in background)
    void ActivationTask();
    // Helper methods
    void CheckAssetsVersion();
    void CheckNewVersion();
    void InitializeProtocol();
    void ShowActivationCode(const std::string& code, const std::string& message);
    void SetListeningMode(ListeningMode mode);
    ListeningMode GetDefaultListeningMode() const;
    // State change handler called by state machine
    void OnStateChanged(DeviceState old_state, DeviceState new_state);
};
class TaskPriorityReset {
public:
    TaskPriorityReset(BaseType_t priority) {
        original_priority_ = uxTaskPriorityGet(NULL);
        vTaskPrioritySet(NULL, priority);
    }
    ~TaskPriorityReset() {
        vTaskPrioritySet(NULL, original_priority_);
    }
private:
    BaseType_t original_priority_;
};
#endif // _APPLICATION_H_
xiaozhi-esp32-2.2.6/main/assets.cc
New file
@@ -0,0 +1,562 @@
#include "assets.h"
#include "board.h"
#include "display.h"
#include "application.h"
#include "lvgl_theme.h"
#include "emote_display.h"
#include "expression_emote.h"
#if HAVE_LVGL
#include "display/lcd_display.h"
#include <spi_flash_mmap.h>
#endif
#include <esp_log.h>
#include <esp_timer.h>
#include <esp_heap_caps.h>
#include <cbin_font.h>
#define TAG "Assets"
#define PARTITION_LABEL "assets"
struct mmap_assets_table {
    char asset_name[32];          /*!< Name of the asset */
    uint32_t asset_size;          /*!< Size of the asset */
    uint32_t asset_offset;        /*!< Offset of the asset */
    uint16_t asset_width;         /*!< Width of the asset */
    uint16_t asset_height;        /*!< Height of the asset */
};
Assets::Assets() {
#if HAVE_LVGL
    strategy_ = std::make_unique<Assets::LvglStrategy>();
#else
    strategy_ = std::make_unique<Assets::EmoteStrategy>();
#endif
    // Initialize the partition
    InitializePartition();
}
Assets::~Assets() {
    UnApplyPartition();
}
bool Assets::FindPartition(Assets* assets) {
    assets->partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, PARTITION_LABEL);
    if (assets->partition_ == nullptr) {
        ESP_LOGI(TAG, "No assets partition found");
        return false;
    }
    return true;
}
bool Assets::Apply(bool refresh_display_theme) {
    return strategy_ ? strategy_->Apply(this, refresh_display_theme) : false;
}
bool Assets::InitializePartition() {
    return strategy_ ? strategy_->InitializePartition(this) : false;
}
void Assets::UnApplyPartition() {
    if (strategy_) {
        strategy_->UnApplyPartition(this);
    }
}
bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) {
    return strategy_ ? strategy_->GetAssetData(this, name, ptr, size) : false;
}
bool Assets::LoadSrmodelsFromIndex(Assets* assets, cJSON* root) {
    void* ptr = nullptr;
    size_t size = 0;
    bool need_delete_root = false;
    // If root is not provided, parse index.json
    if (root == nullptr) {
        if (!assets->GetAssetData("index.json", ptr, size)) {
            ESP_LOGE(TAG, "The index.json file is not found");
            return false;
        }
        root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
        if (root == nullptr) {
            ESP_LOGE(TAG, "The index.json file is not valid");
            return false;
        }
        need_delete_root = true;
    }
    cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels");
    if (cJSON_IsString(srmodels)) {
        std::string srmodels_file = srmodels->valuestring;
        if (assets->GetAssetData(srmodels_file, ptr, size)) {
            if (assets->models_list_ != nullptr) {
                esp_srmodel_deinit(assets->models_list_);
                assets->models_list_ = nullptr;
            }
            assets->models_list_ = srmodel_load(static_cast<uint8_t*>(ptr));
            if (assets->models_list_ != nullptr) {
                auto& app = Application::GetInstance();
                app.GetAudioService().SetModelsList(assets->models_list_);
                if (need_delete_root) {
                    cJSON_Delete(root);
                }
                return true;
            } else {
                ESP_LOGE(TAG, "Failed to load srmodels.bin");
            }
        } else {
            ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str());
        }
    }
    if (need_delete_root) {
        cJSON_Delete(root);
    }
    return false;
}
#if HAVE_LVGL
uint32_t Assets::LvglStrategy::CalculateChecksum(const char* data, uint32_t length) {
    uint32_t checksum = 0;
    for (uint32_t i = 0; i < length; i++) {
        checksum += data[i];
    }
    return checksum & 0xFFFF;
}
bool Assets::LvglStrategy::InitializePartition(Assets* assets) {
    assets->partition_valid_ = false;
    assets_.clear();
    if (!Assets::FindPartition(assets)) {
        return false;
    }
    int free_pages = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
    uint32_t storage_size = free_pages * 64 * 1024;
    ESP_LOGI(TAG, "The storage free size is %ld KB", storage_size / 1024);
    ESP_LOGI(TAG, "The partition size is %ld KB", assets->partition_->size / 1024);
    if (storage_size < assets->partition_->size) {
        ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, assets->partition_->size / 1024);
        return false;
    }
    esp_err_t err = esp_partition_mmap(assets->partition_, 0, assets->partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_);
    if (err != ESP_OK) {
        ESP_LOGE(TAG, "Failed to mmap assets partition: %s", esp_err_to_name(err));
        return false;
    }
    assets->partition_valid_ = true;
    uint32_t stored_files = *(uint32_t*)(mmap_root_ + 0);
    uint32_t stored_chksum = *(uint32_t*)(mmap_root_ + 4);
    uint32_t stored_len = *(uint32_t*)(mmap_root_ + 8);
    if (stored_len > assets->partition_->size - 12) {
        ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, assets->partition_->size);
        return false;
    }
    auto start_time = esp_timer_get_time();
    uint32_t calculated_checksum = CalculateChecksum(mmap_root_ + 12, stored_len);
    auto end_time = esp_timer_get_time();
    ESP_LOGI(TAG, "The checksum calculation time is %d ms", int((end_time - start_time) / 1000));
    if (calculated_checksum != stored_chksum) {
        ESP_LOGE(TAG, "The calculated checksum (0x%lx) does not match the stored checksum (0x%lx)", calculated_checksum, stored_chksum);
        return false;
    }
    checksum_valid_ = true;
    for (uint32_t i = 0; i < stored_files; i++) {
        auto item = (const mmap_assets_table*)(mmap_root_ + 12 + i * sizeof(mmap_assets_table));
        auto asset = Asset{
            .size = static_cast<size_t>(item->asset_size),
            .offset = static_cast<size_t>(12 + sizeof(mmap_assets_table) * stored_files + item->asset_offset)
        };
        assets_[item->asset_name] = asset;
    }
    return checksum_valid_;
}
void Assets::LvglStrategy::UnApplyPartition(Assets* assets) {
    if (mmap_handle_ != 0) {
        esp_partition_munmap(mmap_handle_);
        mmap_handle_ = 0;
        mmap_root_ = nullptr;
    }
    checksum_valid_ = false;
    assets_.clear();
    (void)assets; // Unused parameter
}
bool Assets::LvglStrategy::GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) {
    auto asset = assets_.find(name);
    if (asset == assets_.end()) {
        return false;
    }
    auto data = (const char*)(mmap_root_ + asset->second.offset);
    if (data[0] != 'Z' || data[1] != 'Z') {
        ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]);
        return false;
    }
    ptr = static_cast<void*>(const_cast<char*>(data + 2));
    size = asset->second.size;
    return true;
}
bool Assets::LvglStrategy::Apply(Assets* assets, bool refresh_display_theme) {
    void* ptr = nullptr;
    size_t size = 0;
    if (!assets->GetAssetData("index.json", ptr, size)) {
        ESP_LOGE(TAG, "The index.json file is not found");
        return false;
    }
    cJSON* root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
    if (root == nullptr) {
        ESP_LOGE(TAG, "The index.json file is not valid");
        return false;
    }
    cJSON* version = cJSON_GetObjectItem(root, "version");
    if (cJSON_IsNumber(version)) {
        if (version->valuedouble > 1) {
            ESP_LOGE(TAG, "The assets version %d is not supported, please upgrade the firmware", version->valueint);
            return false;
        }
    }
    Assets::LoadSrmodelsFromIndex(assets, root);
    auto& theme_manager = LvglThemeManager::GetInstance();
    auto light_theme = theme_manager.GetTheme("light");
    auto dark_theme = theme_manager.GetTheme("dark");
    cJSON* font = cJSON_GetObjectItem(root, "text_font");
    if (cJSON_IsString(font)) {
        std::string fonts_text_file = font->valuestring;
        if (assets->GetAssetData(fonts_text_file, ptr, size)) {
            auto text_font = std::make_shared<LvglCBinFont>(ptr);
            if (text_font->font() == nullptr) {
                ESP_LOGE(TAG, "Failed to load fonts.bin");
                return false;
            }
            if (light_theme != nullptr) {
                light_theme->set_text_font(text_font);
            }
            if (dark_theme != nullptr) {
                dark_theme->set_text_font(text_font);
            }
        } else {
            ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str());
        }
    }
    cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection");
    if (cJSON_IsArray(emoji_collection)) {
        auto custom_emoji_collection = std::make_shared<EmojiCollection>();
        int emoji_count = cJSON_GetArraySize(emoji_collection);
        for (int i = 0; i < emoji_count; i++) {
            cJSON* emoji = cJSON_GetArrayItem(emoji_collection, i);
            if (cJSON_IsObject(emoji)) {
                cJSON* name = cJSON_GetObjectItem(emoji, "name");
                cJSON* file = cJSON_GetObjectItem(emoji, "file");
                cJSON* eaf = cJSON_GetObjectItem(emoji, "eaf");
                if (cJSON_IsString(name) && cJSON_IsString(file) && (NULL== eaf)) {
                    if (!assets->GetAssetData(file->valuestring, ptr, size)) {
                        ESP_LOGE(TAG, "Emoji %s image file %s is not found", name->valuestring, file->valuestring);
                        continue;
                    }
                    custom_emoji_collection->AddEmoji(name->valuestring, new LvglRawImage(ptr, size));
                }
            }
        }
        if (light_theme != nullptr) {
            light_theme->set_emoji_collection(custom_emoji_collection);
        }
        if (dark_theme != nullptr) {
            dark_theme->set_emoji_collection(custom_emoji_collection);
        }
    }
    cJSON* skin = cJSON_GetObjectItem(root, "skin");
    if (cJSON_IsObject(skin)) {
        cJSON* light_skin = cJSON_GetObjectItem(skin, "light");
        if (cJSON_IsObject(light_skin) && light_theme != nullptr) {
            cJSON* text_color = cJSON_GetObjectItem(light_skin, "text_color");
            cJSON* background_color = cJSON_GetObjectItem(light_skin, "background_color");
            cJSON* background_image = cJSON_GetObjectItem(light_skin, "background_image");
            if (cJSON_IsString(text_color)) {
                light_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring));
            }
            if (cJSON_IsString(background_color)) {
                light_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring));
                light_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
            }
            if (cJSON_IsString(background_image)) {
                if (!assets->GetAssetData(background_image->valuestring, ptr, size)) {
                    ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
                    return false;
                }
                auto background_image = std::make_shared<LvglCBinImage>(ptr);
                light_theme->set_background_image(background_image);
            }
        }
        cJSON* dark_skin = cJSON_GetObjectItem(skin, "dark");
        if (cJSON_IsObject(dark_skin) && dark_theme != nullptr) {
            cJSON* text_color = cJSON_GetObjectItem(dark_skin, "text_color");
            cJSON* background_color = cJSON_GetObjectItem(dark_skin, "background_color");
            cJSON* background_image = cJSON_GetObjectItem(dark_skin, "background_image");
            if (cJSON_IsString(text_color)) {
                dark_theme->set_text_color(LvglTheme::ParseColor(text_color->valuestring));
            }
            if (cJSON_IsString(background_color)) {
                dark_theme->set_background_color(LvglTheme::ParseColor(background_color->valuestring));
                dark_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
            }
            if (cJSON_IsString(background_image)) {
                if (!assets->GetAssetData(background_image->valuestring, ptr, size)) {
                    ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
                    return false;
                }
                auto background_image = std::make_shared<LvglCBinImage>(ptr);
                dark_theme->set_background_image(background_image);
            }
        }
    }
    if (refresh_display_theme) {
        auto display = Board::GetInstance().GetDisplay();
        ESP_LOGI(TAG, "Refreshing display theme...");
        auto current_theme = display->GetTheme();
        if (current_theme != nullptr) {
            display->SetTheme(current_theme);
        }
        // Parse hide_subtitle configuration
        cJSON* hide_subtitle = cJSON_GetObjectItem(root, "hide_subtitle");
        if (cJSON_IsBool(hide_subtitle)) {
            bool hide = cJSON_IsTrue(hide_subtitle);
            auto lcd_display = dynamic_cast<LcdDisplay*>(display);
            if (lcd_display != nullptr) {
                lcd_display->SetHideSubtitle(hide);
                ESP_LOGI(TAG, "Set hide_subtitle to %s", hide ? "true" : "false");
            }
        }
    }
    cJSON_Delete(root);
    return true;
}
#endif // HAVE_LVGL
bool Assets::EmoteStrategy::InitializePartition(Assets* assets) {
    assets->partition_valid_ = false;
    if (!Assets::FindPartition(assets)) {
        return false;
    }
    esp_err_t ret = ESP_ERR_INVALID_STATE;
    auto display = Board::GetInstance().GetDisplay();
    auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
    if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
        const emote_data_t data = {
            .type = EMOTE_SOURCE_PARTITION,
            .source = {
                .partition_label = PARTITION_LABEL,
            },
            .flags = {
                .mmap_enable = true, //must be true here!!!
            },
        };
        ret = emote_mount_assets(emote_display->GetEmoteHandle(), &data);
    } else {
        ESP_LOGE(TAG, "Emote display is not initialized");
    }
    assets->partition_valid_ = ((ret == ESP_OK) ? true : false);
    return assets->partition_valid_;
}
void Assets::EmoteStrategy::UnApplyPartition(Assets* assets) {
    auto display = Board::GetInstance().GetDisplay();
    auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
    if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
        emote_unmount_assets(emote_display->GetEmoteHandle());
    }
    (void)assets; // Unused parameter
}
bool Assets::EmoteStrategy::GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) {
    auto display = Board::GetInstance().GetDisplay();
    auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
    if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
        const uint8_t* data = nullptr;
        size_t data_size = 0;
        if (ESP_OK == emote_get_asset_data_by_name(emote_display->GetEmoteHandle(), name.c_str(), &data, &data_size)) {
            ptr = const_cast<void*>(static_cast<const void*>(data));
            size = data_size;
            return true;
        }
        ESP_LOGE(TAG, "Failed to get asset data by name: %s", name.c_str());
        return false;
    }
    (void)assets; // Unused parameter
    return false;
}
bool Assets::EmoteStrategy::Apply(Assets* assets, bool refresh_display_theme) {
    Assets::LoadSrmodelsFromIndex(assets);
    auto display = Board::GetInstance().GetDisplay();
    auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
    if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
        emote_load_assets(emote_display->GetEmoteHandle());
    }
    return true;
}
bool Assets::Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback) {
    ESP_LOGI(TAG, "Downloading new version of assets from %s", url.c_str());
    // å–消当前资源分区的内存映射
    UnApplyPartition();
    // ä¸‹è½½æ–°çš„资源文件
    auto network = Board::GetInstance().GetNetwork();
    auto http = network->CreateHttp(0);
    if (!http->Open("GET", url)) {
        ESP_LOGE(TAG, "Failed to open HTTP connection");
        return false;
    }
    if (http->GetStatusCode() != 200) {
        ESP_LOGE(TAG, "Failed to get assets, status code: %d", http->GetStatusCode());
        return false;
    }
    size_t content_length = http->GetBodyLength();
    if (content_length == 0) {
        ESP_LOGE(TAG, "Failed to get content length");
        return false;
    }
    if (content_length > partition_->size) {
        ESP_LOGE(TAG, "Assets file size (%u) is larger than partition size (%lu)", content_length, partition_->size);
        return false;
    }
    // å®šä¹‰æ‰‡åŒºå¤§å°ä¸º4KB(ESP32的标准扇区大小)
    const size_t SECTOR_SIZE = esp_partition_get_main_flash_sector_size();
    // è®¡ç®—需要擦除的扇区数量
    size_t sectors_to_erase = (content_length + SECTOR_SIZE - 1) / SECTOR_SIZE; // å‘上取整
    size_t total_erase_size = sectors_to_erase * SECTOR_SIZE;
    ESP_LOGI(TAG, "Sector size: %u, content length: %u, sectors to erase: %u, total erase size: %u",
             SECTOR_SIZE, content_length, sectors_to_erase, total_erase_size);
    // å†™å…¥æ–°çš„资源文件到分区,一边erase一边写入
    char* buffer = (char*)heap_caps_malloc(SECTOR_SIZE, MALLOC_CAP_INTERNAL);
    if (buffer == nullptr) {
        ESP_LOGE(TAG, "Failed to allocate buffer");
        return false;
    }
    size_t total_written = 0;
    size_t recent_written = 0;
    size_t current_sector = 0;
    auto last_calc_time = esp_timer_get_time();
    while (true) {
        int ret = http->Read(buffer, SECTOR_SIZE);
        if (ret < 0) {
            ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
            heap_caps_free(buffer);
            return false;
        }
        if (ret == 0) {
            break;
        }
        // æ£€æŸ¥æ˜¯å¦éœ€è¦æ“¦é™¤æ–°çš„æ‰‡åŒº
        size_t write_end_offset = total_written + ret;
        size_t needed_sectors = (write_end_offset + SECTOR_SIZE - 1) / SECTOR_SIZE;
        // æ“¦é™¤éœ€è¦çš„æ–°æ‰‡åŒº
        while (current_sector < needed_sectors) {
            size_t sector_start = current_sector * SECTOR_SIZE;
            size_t sector_end = (current_sector + 1) * SECTOR_SIZE;
            // ç¡®ä¿æ“¦é™¤èŒƒå›´ä¸è¶…过分区大小
            if (sector_end > partition_->size) {
                ESP_LOGE(TAG, "Sector end (%u) exceeds partition size (%lu)", sector_end, partition_->size);
                heap_caps_free(buffer);
                return false;
            }
            ESP_LOGD(TAG, "Erasing sector %u (offset: %u, size: %u)", current_sector, sector_start, SECTOR_SIZE);
            esp_err_t err = esp_partition_erase_range(partition_, sector_start, SECTOR_SIZE);
            if (err != ESP_OK) {
                ESP_LOGE(TAG, "Failed to erase sector %u at offset %u: %s", current_sector, sector_start, esp_err_to_name(err));
                heap_caps_free(buffer);
                return false;
            }
            current_sector++;
        }
        // å†™å…¥æ•°æ®åˆ°åˆ†åŒº
        esp_err_t err = esp_partition_write(partition_, total_written, buffer, ret);
        if (err != ESP_OK) {
            ESP_LOGE(TAG, "Failed to write to assets partition at offset %u: %s", total_written, esp_err_to_name(err));
            heap_caps_free(buffer);
            return false;
        }
        total_written += ret;
        recent_written += ret;
        // è®¡ç®—进度和速度
        if (esp_timer_get_time() - last_calc_time >= 1000000 || total_written == content_length || ret == 0) {
            size_t progress = total_written * 100 / content_length;
            size_t speed = recent_written; // æ¯ç§’的字节数
            ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %u B/s, Sectors erased: %u",
                     progress, total_written, content_length, speed, current_sector);
            if (progress_callback) {
                progress_callback(progress, speed);
            }
            last_calc_time = esp_timer_get_time();
            recent_written = 0; // é‡ç½®æœ€è¿‘写入的字节数
        }
    }
    http->Close();
    heap_caps_free(buffer);
    if (total_written != content_length) {
        ESP_LOGE(TAG, "Downloaded size (%u) does not match expected size (%u)", total_written, content_length);
        return false;
    }
    ESP_LOGI(TAG, "Assets download completed, total written: %u bytes, total sectors erased: %u",
             total_written, current_sector);
    // é‡æ–°åˆå§‹åŒ–资源分区
    if (!InitializePartition()) {
        ESP_LOGE(TAG, "Failed to re-initialize assets partition");
        return false;
    }
    return true;
}
xiaozhi-esp32-2.2.6/main/assets.h
New file
@@ -0,0 +1,89 @@
#ifndef ASSETS_H
#define ASSETS_H
#include <string>
#include <functional>
#include <memory>
#include <cJSON.h>
#include <esp_partition.h>
#include <model_path.h>
#include <map>
#include <string>
#if HAVE_LVGL
#include <spi_flash_mmap.h>
#endif
struct Asset {
    size_t size;
    size_t offset;
};
class Assets {
public:
    static Assets& GetInstance() {
        static Assets instance;
        return instance;
    }
    ~Assets();
    bool Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback);
    bool Apply(bool refresh_display_theme = true);
    bool GetAssetData(const std::string& name, void*& ptr, size_t& size);
    inline bool partition_valid() const { return partition_valid_; }
    inline std::string default_assets_url() const { return default_assets_url_; }
private:
    Assets();
    Assets(const Assets&) = delete;
    Assets& operator=(const Assets&) = delete;
    bool InitializePartition();
    void UnApplyPartition();
    static bool FindPartition(Assets* assets);
    static bool LoadSrmodelsFromIndex(Assets* assets, cJSON* root = nullptr);
    class AssetStrategy {
    public:
        virtual ~AssetStrategy() = default;
        virtual bool Apply(Assets* assets, bool refresh_display_theme = true) = 0;
        virtual bool InitializePartition(Assets* assets) = 0;
        virtual void UnApplyPartition(Assets* assets) = 0;
        virtual bool GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) = 0;
    };
    class LvglStrategy : public AssetStrategy {
    public:
        bool Apply(Assets* assets, bool refresh_display_theme = true) override;
        bool InitializePartition(Assets* assets) override;
        void UnApplyPartition(Assets* assets) override;
        bool GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) override;
    private:
        static uint32_t CalculateChecksum(const char* data, uint32_t length);
        std::map<std::string, Asset> assets_;
        esp_partition_mmap_handle_t mmap_handle_ = 0;
        const char* mmap_root_ = nullptr;
        bool checksum_valid_ = false;
    };
    class EmoteStrategy : public AssetStrategy {
    public:
        bool Apply(Assets* assets, bool refresh_display_theme = true) override;
        bool InitializePartition(Assets* assets) override;
        void UnApplyPartition(Assets* assets) override;
        bool GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) override;
    };
    // Strategy instance
    std::unique_ptr<AssetStrategy> strategy_;
protected:
    const esp_partition_t* partition_ = nullptr;
    bool partition_valid_ = false;
    std::string default_assets_url_;
    srmodel_list_t* models_list_ = nullptr;
};
#endif
xiaozhi-esp32-2.2.6/main/assets/common/exclamation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/common/low_battery.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/common/popup.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/common/success.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/common/vibration.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ar-SA"
    },
    "strings": {
        "WARNING": "تحذير",
        "INFO": "معلومات",
        "ERROR": "خطأ",
        "VERSION": "الإصدار ",
        "LOADING_PROTOCOL": "الاتصال Ø¨Ø§Ù„خادم...",
        "INITIALIZING": "التهيئة...",
        "PIN_ERROR": "يرجى Ø¥Ø¯Ø®Ø§Ù„ Ø¨Ø·Ø§Ù‚Ø© SIM",
        "REG_ERROR": "لا ÙŠÙ…كن Ø§Ù„وصول Ø¥Ù„Ù‰ Ø§Ù„شبكة، ÙŠØ±Ø¬Ù‰ Ø§Ù„تحقق Ù…Ù† Ø­Ø§Ù„Ø© Ø¨Ø·Ø§Ù‚Ø© Ø§Ù„بيانات",
        "DETECTING_MODULE": "اكتشاف Ø§Ù„وحدة...",
        "REGISTERING_NETWORK": "انتظار Ø§Ù„شبكة...",
        "CHECKING_NEW_VERSION": "فحص Ø§Ù„إصدار Ø§Ù„جديد...",
        "CHECK_NEW_VERSION_FAILED": "فشل ÙØ­Øµ Ø§Ù„إصدار Ø§Ù„جديد، Ø³ÙŠØªÙ… Ø§Ù„محاولة Ø®Ù„ال %d Ø«Ø§Ù†ÙŠØ©: %s",
        "SWITCH_TO_WIFI_NETWORK": "التبديل Ø¥Ù„Ù‰ Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "التبديل Ø¥Ù„Ù‰ 4G...",
        "STANDBY": "في Ø§Ù„انتظار",
        "CONNECT_TO": "الاتصال Ø¨Ù€ ",
        "CONNECTING": "جاري Ø§Ù„اتصال...",
        "CONNECTED_TO": "متصل Ø¨Ù€ ",
        "LISTENING": "الاستماع...",
        "SPEAKING": "التحدث...",
        "SERVER_NOT_FOUND": "البحث Ø¹Ù† Ø®Ø¯Ù…Ø© Ù…تاحة",
        "SERVER_NOT_CONNECTED": "لا ÙŠÙ…كن Ø§Ù„اتصال Ø¨Ø§Ù„خدمة، ÙŠØ±Ø¬Ù‰ Ø§Ù„محاولة Ù„احقاً",
        "SERVER_TIMEOUT": "انتهت Ù…هلة Ø§Ù„استجابة",
        "SERVER_ERROR": "فشل Ø§Ù„إرسال، ÙŠØ±Ø¬Ù‰ Ø§Ù„تحقق Ù…Ù† Ø§Ù„شبكة",
        "CONNECT_TO_HOTSPOT": "اتصل Ø§Ù„هاتف Ø¨Ù†Ù‚طة Ø§Ù„اتصال ",
        "ACCESS_VIA_BROWSER": "،الوصول Ø¹Ø¨Ø± Ø§Ù„متصفح ",
        "WIFI_CONFIG_MODE": "وضع ØªÙƒÙˆÙŠÙ† Ø§Ù„شبكة",
        "ENTERING_WIFI_CONFIG_MODE": "الدخول ÙÙŠ ÙˆØ¶Ø¹ ØªÙƒÙˆÙŠÙ† Ø§Ù„شبكة...",
        "SCANNING_WIFI": "فحص Wi-Fi...",
        "NEW_VERSION": "إصدار Ø¬Ø¯ÙŠØ¯ ",
        "OTA_UPGRADE": "تحديث OTA",
        "UPGRADING": "تحديث Ø§Ù„نظام...",
        "UPGRADE_FAILED": "فشل Ø§Ù„تحديث",
        "ACTIVATION": "تفعيل Ø§Ù„جهاز",
        "BATTERY_LOW": "البطارية Ù…نخفضة",
        "BATTERY_CHARGING": "جاري Ø§Ù„شحن",
        "BATTERY_FULL": "البطارية Ù…متلئة",
        "BATTERY_NEED_CHARGE": "البطارية Ù…نخفضة، ÙŠØ±Ø¬Ù‰ Ø§Ù„شحن",
        "VOLUME": "الصوت ",
        "MUTED": "صامت",
        "MAX_VOLUME": "أقصى ØµÙˆØª",
        "RTC_MODE_OFF": "AEC Ù…ُوقف",
        "RTC_MODE_ON": "AEC Ù…ُشغل",
        "DOWNLOAD_ASSETS_FAILED": "فشل ÙÙŠ ØªÙ†Ø²ÙŠÙ„ Ø§Ù„موارد",
        "LOADING_ASSETS": "جاري ØªØ­Ù…يل Ø§Ù„موارد...",
        "PLEASE_WAIT": "يرجى Ø§Ù„انتظار...",
        "FOUND_NEW_ASSETS": "تم Ø§Ù„عثور Ø¹Ù„Ù‰ Ù…وارد Ø¬Ø¯ÙŠØ¯Ø©: %s",
        "HELLO_MY_FRIEND": "مرحباً، ØµØ¯ÙŠÙ‚ÙŠ!",
        "CONNECTION_SUCCESSFUL": "تم Ø§Ù„اتصال Ø¨Ù†Ø¬Ø§Ø­",
        "FLIGHT_MODE_OFF": "وضع Ø§Ù„طيران Ù…عطل",
        "FLIGHT_MODE_ON": "وضع Ø§Ù„طيران Ù‚يد Ø§Ù„تشغيل",
        "MODEM_INIT_ERROR": "فشل ØªÙ‡ÙŠØ¦Ø© Ø§Ù„مودم"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "bg-BG"
    },
    "strings": {
        "WARNING": "Предупреждение",
        "INFO": "Информация",
        "ERROR": "Грешка",
        "VERSION": "Версия ",
        "LOADING_PROTOCOL": "Влизане Ð² ÑÐ¸ÑÑ‚емата...",
        "INITIALIZING": "Инициализация...",
        "PIN_ERROR": "Моля, Ð¿Ð¾ÑÑ‚авете SIM ÐºÐ°Ñ€Ñ‚а",
        "REG_ERROR": "Не Ð¼Ð¾Ð¶Ðµ Ð´Ð° ÑÐµ Ð¾ÑÑŠÑ‰ÐµÑÑ‚ви Ð´Ð¾ÑÑ‚ъп Ð´Ð¾ Ð¼Ñ€ÐµÐ¶Ð°Ñ‚а, Ð¼Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚е ÑÑ‚атуса Ð½Ð° SIM ÐºÐ°Ñ€Ñ‚ата",
        "DETECTING_MODULE": "Откриване Ð½Ð° Ð¼Ð¾Ð´ÑƒÐ»...",
        "REGISTERING_NETWORK": "Изчакване Ð½Ð° Ð¼Ñ€ÐµÐ¶Ð°Ñ‚а...",
        "CHECKING_NEW_VERSION": "Проверка Ð·Ð° Ð½Ð¾Ð²Ð° Ð²ÐµÑ€ÑÐ¸Ñ...",
        "CHECK_NEW_VERSION_FAILED": "Проверката Ð·Ð° Ð½Ð¾Ð²Ð° Ð²ÐµÑ€ÑÐ¸Ñ Ðµ Ð½ÐµÑƒÑÐ¿ÐµÑˆÐ½Ð°, Ñ‰Ðµ ÑÐµ Ð¾Ð¿Ð¸Ñ‚а Ð¾Ñ‚ново ÑÐ»ÐµÐ´ %d ÑÐµÐºÑƒÐ½Ð´Ð¸: %s",
        "SWITCH_TO_WIFI_NETWORK": "Превключване ÐºÑŠÐ¼ Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Превключване ÐºÑŠÐ¼ 4G...",
        "STANDBY": "Режим Ð½Ð° Ð³Ð¾Ñ‚овност",
        "CONNECT_TO": "Свързване ÐºÑŠÐ¼ ",
        "CONNECTING": "Свързване...",
        "CONNECTION_SUCCESSFUL": "Успешно ÑÐ²ÑŠÑ€Ð·Ð²Ð°Ð½Ðµ",
        "CONNECTED_TO": "Свързан ÐºÑŠÐ¼ ",
        "LISTENING": "Слушане...",
        "SPEAKING": "Говорене...",
        "SERVER_NOT_FOUND": "Търсене Ð½Ð° Ð½Ð°Ð»Ð¸Ñ‡Ð½Ð° ÑƒÑÐ»ÑƒÐ³Ð°",
        "SERVER_NOT_CONNECTED": "Не Ð¼Ð¾Ð¶Ðµ Ð´Ð° ÑÐµ ÑÐ²ÑŠÑ€Ð¶Ðµ Ñ ÑƒÑÐ»ÑƒÐ³Ð°Ñ‚а, Ð¼Ð¾Ð»Ñ Ð¾Ð¿Ð¸Ñ‚айте Ð¿Ð¾-късно",
        "SERVER_TIMEOUT": "Времето Ð·Ð° Ð¸Ð·Ñ‡Ð°ÐºÐ²Ð°Ð½Ðµ Ð½Ð° Ð¾Ñ‚говор Ð¸Ð·Ñ‚ече",
        "SERVER_ERROR": "Неуспешно Ð¸Ð·Ð¿Ñ€Ð°Ñ‰Ð°Ð½Ðµ, Ð¼Ð¾Ð»Ñ Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐµÑ‚е Ð¼Ñ€ÐµÐ¶Ð°Ñ‚а",
        "CONNECT_TO_HOTSPOT": "Горещa Ñ‚очка: ",
        "ACCESS_VIA_BROWSER": " ÐšÐ¾Ð½Ñ„игурационен URL: ",
        "WIFI_CONFIG_MODE": "Режим Ð½Ð° ÐºÐ¾Ð½Ñ„игуриране Ð½Ð° Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Влизане Ð² Ñ€ÐµÐ¶Ð¸Ð¼ Ð½Ð° ÐºÐ¾Ð½Ñ„игуриране Ð½Ð° Wi-Fi...",
        "SCANNING_WIFI": "Сканиране Ð½Ð° Wi-Fi...",
        "NEW_VERSION": "Нова Ð²ÐµÑ€ÑÐ¸Ñ ",
        "OTA_UPGRADE": "OTA Ð½Ð°Ð´ÑÑ‚ройка",
        "UPGRADING": "Системата ÑÐµ Ð½Ð°Ð´ÑÑ‚ройва...",
        "UPGRADE_FAILED": "Надстройката Ðµ Ð½ÐµÑƒÑÐ¿ÐµÑˆÐ½Ð°",
        "ACTIVATION": "Активация",
        "BATTERY_LOW": "Слаба Ð±Ð°Ñ‚ерия",
        "BATTERY_CHARGING": "Зарядна",
        "BATTERY_FULL": "Батерията Ðµ Ð¿ÑŠÐ»Ð½Ð°",
        "BATTERY_NEED_CHARGE": "Слаба Ð±Ð°Ñ‚ерия, Ð¼Ð¾Ð»Ñ Ð·Ð°Ñ€ÐµÐ´ÐµÑ‚е",
        "VOLUME": "Сила Ð½Ð° Ð·Ð²ÑƒÐºÐ° ",
        "MUTED": "Заглушено",
        "MAX_VOLUME": "Максимална ÑÐ¸Ð»Ð° Ð½Ð° Ð·Ð²ÑƒÐºÐ°",
        "RTC_MODE_OFF": "AEC Ð¸Ð·ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "RTC_MODE_ON": "AEC Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "PLEASE_WAIT": "Моля, Ð¸Ð·Ñ‡Ð°ÐºÐ°Ð¹Ñ‚е...",
        "FOUND_NEW_ASSETS": "Намерени Ð½Ð¾Ð²Ð¸ Ñ€ÐµÑÑƒÑ€ÑÐ¸: %s",
        "DOWNLOAD_ASSETS_FAILED": "Неуспешно Ð¸Ð·Ñ‚егляне Ð½Ð° Ñ€ÐµÑÑƒÑ€ÑÐ¸",
        "LOADING_ASSETS": "Зареждане Ð½Ð° Ñ€ÐµÑÑƒÑ€ÑÐ¸...",
        "HELLO_MY_FRIEND": "Здравей, Ð¼Ð¾Ð¹ Ð¿Ñ€Ð¸ÑÑ‚ел!",
        "FLIGHT_MODE_OFF": "Режим Ð½Ð° ÑÐ°Ð¼Ð¾Ð»ÐµÑ‚ Ðµ Ð¸Ð·ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "FLIGHT_MODE_ON": "Режим Ð½Ð° ÑÐ°Ð¼Ð¾Ð»ÐµÑ‚ Ðµ Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "MODEM_INIT_ERROR": "Неуспешна Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ñ Ð½Ð° Ð¼Ð¾Ð´ÐµÐ¼Ð°"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ca-ES"
    },
    "strings": {
        "WARNING": "Advertència",
        "INFO": "Informació",
        "ERROR": "Error",
        "VERSION": "Versió ",
        "LOADING_PROTOCOL": "Iniciant sessió...",
        "INITIALIZING": "Inicialitzant...",
        "PIN_ERROR": "Si us plau, inseriu la targeta SIM",
        "REG_ERROR": "No es pot accedir a la xarxa, si us plau comproveu l'estat de la targeta SIM",
        "DETECTING_MODULE": "Detectant mòdul...",
        "REGISTERING_NETWORK": "Esperant la xarxa...",
        "CHECKING_NEW_VERSION": "Comprovant nova versió...",
        "CHECK_NEW_VERSION_FAILED": "La comprovació de nova versió ha fallat, es tornarà a intentar en %d segons: %s",
        "SWITCH_TO_WIFI_NETWORK": "Canviant a Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Canviant a 4G...",
        "STANDBY": "En espera",
        "CONNECT_TO": "Connectar a ",
        "CONNECTING": "Connectant...",
        "CONNECTION_SUCCESSFUL": "Connexió exitosa",
        "CONNECTED_TO": "Connectat a ",
        "LISTENING": "Escoltant...",
        "SPEAKING": "Parlant...",
        "SERVER_NOT_FOUND": "Buscant servei disponible",
        "SERVER_NOT_CONNECTED": "No es pot connectar al servei, si us plau intenteu-ho més tard",
        "SERVER_TIMEOUT": "Temps d'espera de resposta exhaurit",
        "SERVER_ERROR": "L'enviament ha fallat, si us plau comproveu la xarxa",
        "CONNECT_TO_HOTSPOT": "Punt d'accés: ",
        "ACCESS_VIA_BROWSER": " URL de configuració: ",
        "WIFI_CONFIG_MODE": "Mode de configuració Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Entrant en mode de configuració Wi-Fi...",
        "SCANNING_WIFI": "Escanejant Wi-Fi...",
        "NEW_VERSION": "Nova versió ",
        "OTA_UPGRADE": "Actualització OTA",
        "UPGRADING": "El sistema s'està actualitzant...",
        "UPGRADE_FAILED": "L'actualització ha fallat",
        "ACTIVATION": "Activació",
        "BATTERY_LOW": "Bateria baixa",
        "BATTERY_CHARGING": "Carregant",
        "BATTERY_FULL": "Bateria plena",
        "BATTERY_NEED_CHARGE": "Bateria baixa, si us plau carregueu",
        "VOLUME": "Volum ",
        "MUTED": "Silenciat",
        "MAX_VOLUME": "Volum màxim",
        "RTC_MODE_OFF": "AEC desactivat",
        "RTC_MODE_ON": "AEC activat",
        "PLEASE_WAIT": "Si us plau, espereu...",
        "FOUND_NEW_ASSETS": "S'han trobat nous recursos: %s",
        "DOWNLOAD_ASSETS_FAILED": "No s'han pogut descarregar els recursos",
        "LOADING_ASSETS": "Carregant recursos...",
        "HELLO_MY_FRIEND": "Hola, amic meu!",
        "FLIGHT_MODE_OFF": "El mode avió està desactivat",
        "FLIGHT_MODE_ON": "El mode avió està activat",
        "MODEM_INIT_ERROR": "Error d'inicialització del mòdem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "cs-CZ"
    },
    "strings": {
        "WARNING": "Varování",
        "INFO": "Informace",
        "ERROR": "Chyba",
        "VERSION": "Verze ",
        "LOADING_PROTOCOL": "Připojování k serveru...",
        "INITIALIZING": "Inicializace...",
        "PIN_ERROR": "Prosím vložte SIM kartu",
        "REG_ERROR": "Nelze se připojit k síti, zkontrolujte stav datové karty",
        "DETECTING_MODULE": "Detekce modulu...",
        "REGISTERING_NETWORK": "Čekání na síť...",
        "CHECKING_NEW_VERSION": "Kontrola nové verze...",
        "CHECK_NEW_VERSION_FAILED": "Kontrola nové verze selhala, opakování za %d sekund: %s",
        "SWITCH_TO_WIFI_NETWORK": "Přepínání na Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Přepínání na 4G...",
        "STANDBY": "Pohotovost",
        "CONNECT_TO": "Připojit k ",
        "CONNECTING": "Připojování...",
        "CONNECTED_TO": "Připojeno k ",
        "LISTENING": "Naslouchání...",
        "SPEAKING": "Mluvení...",
        "SERVER_NOT_FOUND": "Hledání dostupné služby",
        "SERVER_NOT_CONNECTED": "Nelze se připojit ke službě, zkuste to později",
        "SERVER_TIMEOUT": "Čas odpovědi vyprÅ¡el",
        "SERVER_ERROR": "Odeslání selhalo, zkontrolujte síť",
        "CONNECT_TO_HOTSPOT": "Připojte telefon k hotspotu ",
        "ACCESS_VIA_BROWSER": ",přístup přes prohlížeč ",
        "WIFI_CONFIG_MODE": "Režim konfigurace sítě",
        "ENTERING_WIFI_CONFIG_MODE": "Vstup do režimu konfigurace sítě...",
        "SCANNING_WIFI": "Skenování Wi-Fi...",
        "NEW_VERSION": "Nová verze ",
        "OTA_UPGRADE": "OTA upgrade",
        "UPGRADING": "Aktualizace systému...",
        "UPGRADE_FAILED": "Upgrade selhal",
        "ACTIVATION": "Aktivace zařízení",
        "BATTERY_LOW": "Slabá baterie",
        "BATTERY_CHARGING": "Nabíjení",
        "BATTERY_FULL": "Baterie plná",
        "BATTERY_NEED_CHARGE": "Slabá baterie, prosím nabijte",
        "VOLUME": "Hlasitost ",
        "MUTED": "Ztlumeno",
        "MAX_VOLUME": "Maximální hlasitost",
        "RTC_MODE_OFF": "AEC vypnuto",
        "RTC_MODE_ON": "AEC zapnuto",
        "DOWNLOAD_ASSETS_FAILED": "Nepodařilo se stáhnout prostředky",
        "LOADING_ASSETS": "Načítání prostředků...",
        "PLEASE_WAIT": "Prosím Äekejte...",
        "FOUND_NEW_ASSETS": "Nalezeny nové prostředky: %s",
        "HELLO_MY_FRIEND": "Ahoj, můj příteli!",
        "CONNECTION_SUCCESSFUL": "Připojení Ãºspěšné",
        "FLIGHT_MODE_OFF": "Letecký režim je vypnutý",
        "FLIGHT_MODE_ON": "Letecký režim je zapnutý",
        "MODEM_INIT_ERROR": "Chyba inicializace modemu"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "da-DK"
    },
    "strings": {
        "WARNING": "Advarsel",
        "INFO": "Information",
        "ERROR": "Fejl",
        "VERSION": "Version ",
        "LOADING_PROTOCOL": "Logger ind...",
        "INITIALIZING": "Initialiserer...",
        "PIN_ERROR": "Indsæt venligst SIM-kort",
        "REG_ERROR": "Kan ikke fÃ¥ adgang til netværket, tjek venligst SIM-kortets status",
        "DETECTING_MODULE": "Detekterer modul...",
        "REGISTERING_NETWORK": "Venter pÃ¥ netværk...",
        "CHECKING_NEW_VERSION": "Tjekker for ny version...",
        "CHECK_NEW_VERSION_FAILED": "Tjek for ny version mislykkedes, prøver igen om %d sekunder: %s",
        "SWITCH_TO_WIFI_NETWORK": "Skifter til Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Skifter til 4G...",
        "STANDBY": "Standby",
        "CONNECT_TO": "Forbind til ",
        "CONNECTING": "Forbinder...",
        "CONNECTION_SUCCESSFUL": "Forbindelse lykkedes",
        "CONNECTED_TO": "Forbundet til ",
        "LISTENING": "Lytter...",
        "SPEAKING": "Taler...",
        "SERVER_NOT_FOUND": "Søger efter tilgængelig tjeneste",
        "SERVER_NOT_CONNECTED": "Kan ikke forbinde til tjeneste, prøv venligst igen senere",
        "SERVER_TIMEOUT": "Timeout ved venten pÃ¥ svar",
        "SERVER_ERROR": "Afsendelse mislykkedes, tjek venligst netværket",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Konfigurations-URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi-konfigurationstilstand",
        "ENTERING_WIFI_CONFIG_MODE": "GÃ¥r ind i Wi-Fi-konfigurationstilstand...",
        "SCANNING_WIFI": "Scanner Wi-Fi...",
        "NEW_VERSION": "Ny version ",
        "OTA_UPGRADE": "OTA-opgradering",
        "UPGRADING": "Systemet opgraderes...",
        "UPGRADE_FAILED": "Opgradering mislykkedes",
        "ACTIVATION": "Aktivering",
        "BATTERY_LOW": "Lavt batteri",
        "BATTERY_CHARGING": "Oplader",
        "BATTERY_FULL": "Batteriet er fuldt",
        "BATTERY_NEED_CHARGE": "Lavt batteri, oplad venligst",
        "VOLUME": "Lydstyrke ",
        "MUTED": "Lydløs",
        "MAX_VOLUME": "Maksimal lydstyrke",
        "RTC_MODE_OFF": "AEC slukket",
        "RTC_MODE_ON": "AEC tændt",
        "PLEASE_WAIT": "Vent venligst...",
        "FOUND_NEW_ASSETS": "Fandt nye ressourcer: %s",
        "DOWNLOAD_ASSETS_FAILED": "Download af ressourcer mislykkedes",
        "LOADING_ASSETS": "Indlæser ressourcer...",
        "HELLO_MY_FRIEND": "Hej, min ven!",
        "FLIGHT_MODE_OFF": "Flytilstand er slukket",
        "FLIGHT_MODE_ON": "Flytilstand er tændt",
        "MODEM_INIT_ERROR": "Modeminitialisering mislykkedes"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "de-DE"
    },
    "strings": {
        "WARNING": "Warnung",
        "INFO": "Information",
        "ERROR": "Fehler",
        "VERSION": "Version ",
        "LOADING_PROTOCOL": "Verbindung zum Server...",
        "INITIALIZING": "Initialisierung...",
        "PIN_ERROR": "Bitte SIM-Karte einlegen",
        "REG_ERROR": "Netzwerkverbindung fehlgeschlagen, bitte Datenkartenstatus prüfen",
        "DETECTING_MODULE": "Modul erkennen...",
        "REGISTERING_NETWORK": "Auf Netzwerk warten...",
        "CHECKING_NEW_VERSION": "Neue Version prüfen...",
        "CHECK_NEW_VERSION_FAILED": "Neue Version prüfen fehlgeschlagen, Wiederholung in %d Sekunden: %s",
        "SWITCH_TO_WIFI_NETWORK": "Zu Wi-Fi wechseln...",
        "SWITCH_TO_4G_NETWORK": "Zu 4G wechseln...",
        "STANDBY": "Bereitschaft",
        "CONNECT_TO": "Verbinden zu ",
        "CONNECTING": "Verbindung wird hergestellt...",
        "CONNECTED_TO": "Verbunden mit ",
        "LISTENING": "Zuhören...",
        "SPEAKING": "Sprechen...",
        "SERVER_NOT_FOUND": "Verfügbaren Service suchen",
        "SERVER_NOT_CONNECTED": "Service-Verbindung fehlgeschlagen, bitte später versuchen",
        "SERVER_TIMEOUT": "Antwort-Timeout",
        "SERVER_ERROR": "Senden fehlgeschlagen, bitte Netzwerk prüfen",
        "CONNECT_TO_HOTSPOT": "Handy mit Hotspot verbinden ",
        "ACCESS_VIA_BROWSER": ",Browser Ã¶ffnen ",
        "WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus",
        "ENTERING_WIFI_CONFIG_MODE": "Netzwerkkonfigurationsmodus eingeben...",
        "SCANNING_WIFI": "Wi-Fi scannen...",
        "NEW_VERSION": "Neue Version ",
        "OTA_UPGRADE": "OTA-Upgrade",
        "UPGRADING": "System wird aktualisiert...",
        "UPGRADE_FAILED": "Upgrade fehlgeschlagen",
        "ACTIVATION": "Gerät aktivieren",
        "BATTERY_LOW": "Niedriger Batteriestand",
        "BATTERY_CHARGING": "Wird geladen",
        "BATTERY_FULL": "Batterie voll",
        "BATTERY_NEED_CHARGE": "Niedriger Batteriestand, bitte aufladen",
        "VOLUME": "Lautstärke ",
        "MUTED": "Stummgeschaltet",
        "MAX_VOLUME": "Maximale Lautstärke",
        "RTC_MODE_OFF": "AEC aus",
        "RTC_MODE_ON": "AEC ein",
        "DOWNLOAD_ASSETS_FAILED": "Fehler beim Herunterladen der Ressourcen",
        "LOADING_ASSETS": "Ressourcen werden geladen...",
        "PLEASE_WAIT": "Bitte warten...",
        "FOUND_NEW_ASSETS": "Neue Ressourcen gefunden: %s",
        "HELLO_MY_FRIEND": "Hallo, mein Freund!",
        "CONNECTION_SUCCESSFUL": "Verbindung erfolgreich",
        "FLIGHT_MODE_OFF": "Flugmodus ist deaktiviert",
        "FLIGHT_MODE_ON": "Flugmodus ist aktiviert",
        "MODEM_INIT_ERROR": "Modem-Initialisierung fehlgeschlagen"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "el-GR"
    },
    "strings": {
        "WARNING": "Προειδοποίηση",
        "INFO": "Πληροφορίες",
        "ERROR": "Σφάλμα",
        "VERSION": "Έκδοση ",
        "LOADING_PROTOCOL": "Σύνδεση...",
        "INITIALIZING": "Αρχικοποίηση...",
        "PIN_ERROR": "Παρακαλώ ÎµÎ¹ÏƒÎ±Î³Î¬Î³ÎµÏ„ε ÎºÎ¬ÏÏ„α SIM",
        "REG_ERROR": "Αδυναμία Ï€ÏÏŒÏƒÎ²Î±ÏƒÎ·Ï‚ ÏƒÏ„ο Î´Î¯ÎºÏ„υο, ÎµÎ»Î­Î³Î¾Ï„ε Ï„ην ÎºÎ±Ï„άσταση Ï„ης ÎºÎ¬ÏÏ„ας SIM",
        "DETECTING_MODULE": "Ανίχνευση Î¼Î¿Î½Î¬Î´Î±Ï‚...",
        "REGISTERING_NETWORK": "Αναμονή Î´Î¹ÎºÏ„ÏÎ¿Ï…...",
        "CHECKING_NEW_VERSION": "Έλεγχος Î³Î¹Î± Î½Î­Î± Î­ÎºÎ´Î¿ÏƒÎ·...",
        "CHECK_NEW_VERSION_FAILED": "Ο Î­Î»ÎµÎ³Ï‡Î¿Ï‚ Î³Î¹Î± Î½Î­Î± Î­ÎºÎ´Î¿ÏƒÎ· Î±Ï€Î­Ï„υχε, Î¸Î± ÎµÏ€Î±Î½Î±Î»Î·Ï†Î¸ÎµÎ¯ ÏƒÎµ %d Î´ÎµÏ…τερόλεπτα: %s",
        "SWITCH_TO_WIFI_NETWORK": "Μετάβαση ÏƒÎµ Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Μετάβαση ÏƒÎµ 4G...",
        "STANDBY": "Αναμονή",
        "CONNECT_TO": "Σύνδεση ÏƒÎµ ",
        "CONNECTING": "Σύνδεση...",
        "CONNECTION_SUCCESSFUL": "Επιτυχής ÏƒÏÎ½Î´ÎµÏƒÎ·",
        "CONNECTED_TO": "Συνδέθηκε ÏƒÎµ ",
        "LISTENING": "Ακρόαση...",
        "SPEAKING": "Ομιλία...",
        "SERVER_NOT_FOUND": "Αναζήτηση Î´Î¹Î±Î¸Î­ÏƒÎ¹Î¼Î·Ï‚ Ï…πηρεσίας",
        "SERVER_NOT_CONNECTED": "Αδυναμία ÏƒÏÎ½Î´ÎµÏƒÎ·Ï‚ ÏƒÏ„ην Ï…πηρεσία, Ï€Î±ÏÎ±ÎºÎ±Î»ÏŽ Î´Î¿ÎºÎ¹Î¼Î¬ÏƒÏ„Îµ Î±ÏÎ³ÏŒÏ„ερα",
        "SERVER_TIMEOUT": "Λήξη Ï‡ÏÏŒÎ½Î¿Ï… Î±Î½Î±Î¼Î¿Î½Î®Ï‚ Î±Ï€ÏŒÎºÏÎ¹ÏƒÎ·Ï‚",
        "SERVER_ERROR": "Η Î±Ï€Î¿ÏƒÏ„Î¿Î»Î® Î±Ï€Î­Ï„υχε, ÎµÎ»Î­Î³Î¾Ï„ε Ï„ο Î´Î¯ÎºÏ„υο",
        "CONNECT_TO_HOTSPOT": "Σημείο Ï€ÏÏŒÏƒÎ²Î±ÏƒÎ·Ï‚: ",
        "ACCESS_VIA_BROWSER": " URL Î´Î¹Î±Î¼ÏŒÏÏ†Ï‰ÏƒÎ·Ï‚: ",
        "WIFI_CONFIG_MODE": "Λειτουργία Î´Î¹Î±Î¼ÏŒÏÏ†Ï‰ÏƒÎ·Ï‚ Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Είσοδος ÏƒÎµ Î»ÎµÎ¹Ï„ουργία Î´Î¹Î±Î¼ÏŒÏÏ†Ï‰ÏƒÎ·Ï‚ Wi-Fi...",
        "SCANNING_WIFI": "Σάρωση Wi-Fi...",
        "NEW_VERSION": "Νέα Î­ÎºÎ´Î¿ÏƒÎ· ",
        "OTA_UPGRADE": "Αναβάθμιση OTA",
        "UPGRADING": "Το ÏƒÏÏƒÏ„ημα Î±Î½Î±Î²Î±Î¸Î¼Î¯Î¶ÎµÏ„αι...",
        "UPGRADE_FAILED": "Η Î±Î½Î±Î²Î¬Î¸Î¼Î¹ÏƒÎ· Î±Ï€Î­Ï„υχε",
        "ACTIVATION": "Ενεργοποίηση",
        "BATTERY_LOW": "Χαμηλή Î¼Ï€Î±Ï„Î±ÏÎ¯Î±",
        "BATTERY_CHARGING": "Φόρτιση",
        "BATTERY_FULL": "Πλήρης Î¼Ï€Î±Ï„αρία",
        "BATTERY_NEED_CHARGE": "Χαμηλή Î¼Ï€Î±Ï„Î±ÏÎ¯Î±, Ï€Î±ÏÎ±ÎºÎ±Î»ÏŽ Ï†Î¿ÏÏ„Î¯ÏƒÏ„Îµ",
        "VOLUME": "Ένταση ",
        "MUTED": "Σίγαση",
        "MAX_VOLUME": "Μέγιστη Î­Î½Ï„αση",
        "RTC_MODE_OFF": "AEC Î±Ï€ÎµÎ½ÎµÏÎ³Î¿Ï€Î¿Î¹Î·Î¼Î­Î½Î¿",
        "RTC_MODE_ON": "AEC ÎµÎ½ÎµÏÎ³Î¿Ï€Î¿Î¹Î·Î¼Î­Î½Î¿",
        "PLEASE_WAIT": "Παρακαλώ Ï€ÎµÏÎ¹Î¼Î­Î½ÎµÏ„ε...",
        "FOUND_NEW_ASSETS": "Βρέθηκαν Î½Î­Î¿Î¹ Ï€ÏŒÏÎ¿Î¹: %s",
        "DOWNLOAD_ASSETS_FAILED": "Αποτυχία Î»Î®ÏˆÎ·Ï‚ Ï€ÏŒÏÏ‰Î½",
        "LOADING_ASSETS": "Φόρτωση Ï€ÏŒÏÏ‰Î½...",
        "HELLO_MY_FRIEND": "Γεια ÏƒÎ¿Ï…, Ï†Î¯Î»Îµ Î¼Î¿Ï…!",
        "FLIGHT_MODE_OFF": "Η Î»ÎµÎ¹Ï„Î¿Ï…ÏÎ³Î¯Î± Ï€Ï„ήσης ÎµÎ¯Î½Î±Î¹ Î±Ï€ÎµÎ½ÎµÏÎ³Î¿Ï€Î¿Î¹Î·Î¼Î­Î½Î·",
        "FLIGHT_MODE_ON": "Η Î»ÎµÎ¹Ï„Î¿Ï…ÏÎ³Î¯Î± Ï€Ï„ήσης ÎµÎ¯Î½Î±Î¹ ÎµÎ½ÎµÏÎ³Î®",
        "MODEM_INIT_ERROR": "Αποτυχία Î±ÏÏ‡Î¹ÎºÎ¿Ï€Î¿Î¯Î·ÏƒÎ·Ï‚ modem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "en-US"
    },
    "strings": {
        "WARNING": "Warning",
        "INFO": "Information",
        "ERROR": "Error",
        "VERSION": "Ver ",
        "LOADING_PROTOCOL": "Logging in...",
        "INITIALIZING": "Initializing...",
        "PIN_ERROR": "Please insert SIM card",
        "REG_ERROR": "Unable to access network, please check SIM card status",
        "MODEM_INIT_ERROR": "Modem initialization failed",
        "DETECTING_MODULE": "Detecting module...",
        "FLIGHT_MODE_ON": "Flight mode is on",
        "FLIGHT_MODE_OFF": "Flight mode is off",
        "REGISTERING_NETWORK": "Waiting for network...",
        "CHECKING_NEW_VERSION": "Checking for new version...",
        "CHECK_NEW_VERSION_FAILED": "Check for new version failed, will retry in %d seconds: %s",
        "SWITCH_TO_WIFI_NETWORK": "Switching to Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Switching to 4G...",
        "STANDBY": "Standby",
        "CONNECT_TO": "Connect to ",
        "CONNECTING": "Connecting...",
        "CONNECTION_SUCCESSFUL": "Connection Successful",
        "CONNECTED_TO": "Connected to ",
        "LISTENING": "Listening...",
        "SPEAKING": "Speaking...",
        "SERVER_NOT_FOUND": "Looking for available service",
        "SERVER_NOT_CONNECTED": "Unable to connect to service, please try again later",
        "SERVER_TIMEOUT": "Waiting for response timeout",
        "SERVER_ERROR": "Sending failed, please check the network",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Config URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi Configuration Mode",
        "ENTERING_WIFI_CONFIG_MODE": "Entering Wi-Fi configuration mode...",
        "SCANNING_WIFI": "Scanning Wi-Fi...",
        "NEW_VERSION": "New version ",
        "OTA_UPGRADE": "OTA Upgrade",
        "UPGRADING": "System is upgrading...",
        "UPGRADE_FAILED": "Upgrade failed",
        "ACTIVATION": "Activation",
        "BATTERY_LOW": "Low battery",
        "BATTERY_CHARGING": "Charging",
        "BATTERY_FULL": "Battery full",
        "BATTERY_NEED_CHARGE": "Low battery, please charge",
        "VOLUME": "Volume ",
        "MUTED": "Muted",
        "MAX_VOLUME": "Max volume",
        "RTC_MODE_OFF": "AEC Off",
        "RTC_MODE_ON": "AEC On",
        "PLEASE_WAIT": "Please wait...",
        "FOUND_NEW_ASSETS": "Found new assets: %s",
        "DOWNLOAD_ASSETS_FAILED": "Failed to download assets",
        "LOADING_ASSETS": "Loading assets...",
        "HELLO_MY_FRIEND": "Hello, my friend!"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "es-ES"
    },
    "strings": {
        "WARNING": "Advertencia",
        "INFO": "Información",
        "ERROR": "Error",
        "VERSION": "Versión ",
        "LOADING_PROTOCOL": "Conectando al servidor...",
        "INITIALIZING": "Inicializando...",
        "PIN_ERROR": "Por favor inserte la tarjeta SIM",
        "REG_ERROR": "No se puede acceder a la red, verifique el estado de la tarjeta de datos",
        "DETECTING_MODULE": "Detectando módulo...",
        "REGISTERING_NETWORK": "Esperando red...",
        "CHECKING_NEW_VERSION": "Verificando nueva versión...",
        "CHECK_NEW_VERSION_FAILED": "Error al verificar nueva versión, reintentando en %d segundos: %s",
        "SWITCH_TO_WIFI_NETWORK": "Cambiando a Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Cambiando a 4G...",
        "STANDBY": "En espera",
        "CONNECT_TO": "Conectar a ",
        "CONNECTING": "Conectando...",
        "CONNECTED_TO": "Conectado a ",
        "LISTENING": "Escuchando...",
        "SPEAKING": "Hablando...",
        "SERVER_NOT_FOUND": "Buscando servicio disponible",
        "SERVER_NOT_CONNECTED": "No se puede conectar al servicio, inténtelo más tarde",
        "SERVER_TIMEOUT": "Tiempo de espera agotado",
        "SERVER_ERROR": "Error de envío, verifique la red",
        "CONNECT_TO_HOTSPOT": "Conectar teléfono al punto de acceso ",
        "ACCESS_VIA_BROWSER": ",acceder mediante navegador ",
        "WIFI_CONFIG_MODE": "Modo configuración de red",
        "ENTERING_WIFI_CONFIG_MODE": "Entrando en modo configuración de red...",
        "SCANNING_WIFI": "Escaneando Wi-Fi...",
        "NEW_VERSION": "Nueva versión ",
        "OTA_UPGRADE": "Actualización OTA",
        "UPGRADING": "Actualizando sistema...",
        "UPGRADE_FAILED": "Actualización fallida",
        "ACTIVATION": "Activación del dispositivo",
        "BATTERY_LOW": "Batería baja",
        "BATTERY_CHARGING": "Cargando",
        "BATTERY_FULL": "Batería llena",
        "BATTERY_NEED_CHARGE": "Batería baja, por favor cargar",
        "VOLUME": "Volumen ",
        "MUTED": "Silenciado",
        "MAX_VOLUME": "Volumen máximo",
        "RTC_MODE_OFF": "AEC desactivado",
        "RTC_MODE_ON": "AEC activado",
        "DOWNLOAD_ASSETS_FAILED": "Error al descargar recursos",
        "LOADING_ASSETS": "Cargando recursos...",
        "PLEASE_WAIT": "Por favor espere...",
        "FOUND_NEW_ASSETS": "Encontrados nuevos recursos: %s",
        "HELLO_MY_FRIEND": "¡Hola, mi amigo!",
        "CONNECTION_SUCCESSFUL": "Conexión exitosa",
        "FLIGHT_MODE_OFF": "El modo avión está desactivado",
        "FLIGHT_MODE_ON": "El modo avión está activado",
        "MODEM_INIT_ERROR": "Error de inicialización del módem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "fa-IR"
    },
    "strings": {
        "WARNING": "هشدار",
        "INFO": "اطلاعات",
        "ERROR": "خطا",
        "VERSION": "نسخه ",
        "LOADING_PROTOCOL": "ورود Ø¨Ù‡ Ø³ÛŒØ³ØªÙ…...",
        "INITIALIZING": "در Ø­Ø§Ù„ Ø±Ø§Ù‡â€ŒØ§Ù†Ø¯Ø§Ø²ÛŒ...",
        "PIN_ERROR": "لطفاً Ø³ÛŒÙ… Ú©Ø§Ø±Øª Ø±Ø§ ÙˆØ§Ø±Ø¯ Ú©Ù†ÛŒØ¯",
        "REG_ERROR": "عدم Ø¯Ø³ØªØ±Ø³ÛŒ Ø¨Ù‡ Ø´Ø¨Ú©Ù‡ØŒ Ù„طفاً ÙˆØ¶Ø¹ÛŒØª Ø³ÛŒÙ… Ú©Ø§Ø±Øª Ø±Ø§ Ø¨Ø±Ø±Ø³ÛŒ Ú©Ù†ÛŒØ¯",
        "DETECTING_MODULE": "شناسایی Ù…اژول...",
        "REGISTERING_NETWORK": "در Ø§Ù†ØªØ¸Ø§Ø± Ø´Ø¨Ú©Ù‡...",
        "CHECKING_NEW_VERSION": "بررسی Ù†Ø³Ø®Ù‡ Ø¬Ø¯ÛŒØ¯...",
        "CHECK_NEW_VERSION_FAILED": "بررسی Ù†Ø³Ø®Ù‡ Ø¬Ø¯ÛŒØ¯ Ù†Ø§Ù…وفق Ø¨ÙˆØ¯ØŒ Ù¾Ø³ Ø§Ø² %d Ø«Ø§Ù†ÛŒÙ‡ Ù…جدداً ØªÙ„اش Ù…ی‌شود: %s",
        "SWITCH_TO_WIFI_NETWORK": "تغییر Ø¨Ù‡ Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "تغییر Ø¨Ù‡ 4G...",
        "STANDBY": "آماده Ø¨Ù‡ Ú©Ø§Ø±",
        "CONNECT_TO": "اتصال Ø¨Ù‡ ",
        "CONNECTING": "در Ø­Ø§Ù„ Ø§ØªØµØ§Ù„...",
        "CONNECTION_SUCCESSFUL": "اتصال Ù…وفق",
        "CONNECTED_TO": "متصل Ø¨Ù‡ ",
        "LISTENING": "در Ø­Ø§Ù„ Ú¯ÙˆØ´ Ø¯Ø§Ø¯Ù†...",
        "SPEAKING": "در Ø­Ø§Ù„ ØµØ­Ø¨Øª...",
        "SERVER_NOT_FOUND": "جستجوی Ø³Ø±ÙˆÛŒØ³ Ø¯Ø± Ø¯Ø³ØªØ±Ø³",
        "SERVER_NOT_CONNECTED": "اتصال Ø¨Ù‡ Ø³Ø±ÙˆÛŒØ³ Ø¨Ø±Ù‚رار Ù†Ø´Ø¯ØŒ Ù„طفاً Ø¨Ø¹Ø¯Ø§Ù‹ ØªÙ„اش Ú©Ù†ÛŒØ¯",
        "SERVER_TIMEOUT": "زمان Ø§Ù†ØªØ¸Ø§Ø± Ø¨Ø±Ø§ÛŒ Ù¾Ø§Ø³Ø® Ø¨Ù‡ Ù¾Ø§ÛŒØ§Ù† Ø±Ø³ÛŒØ¯",
        "SERVER_ERROR": "ارسال Ù†Ø§Ù…وفق، Ù„طفاً Ø´Ø¨Ú©Ù‡ Ø±Ø§ Ø¨Ø±Ø±Ø³ÛŒ Ú©Ù†ÛŒØ¯",
        "CONNECT_TO_HOTSPOT": "نقطه Ø§ØªØµØ§Ù„: ",
        "ACCESS_VIA_BROWSER": " Ø¢Ø¯Ø±Ø³ Ù¾ÛŒÚ©Ø±Ø¨Ù†Ø¯ÛŒ: ",
        "WIFI_CONFIG_MODE": "حالت Ù¾ÛŒÚ©Ø±Ø¨Ù†Ø¯ÛŒ Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "ورود Ø¨Ù‡ Ø­Ø§Ù„ت Ù¾ÛŒÚ©Ø±Ø¨Ù†Ø¯ÛŒ Wi-Fi...",
        "SCANNING_WIFI": "جستجوی Wi-Fi...",
        "NEW_VERSION": "نسخه Ø¬Ø¯ÛŒØ¯ ",
        "OTA_UPGRADE": "به‌روزرسانی OTA",
        "UPGRADING": "سیستم Ø¯Ø± Ø­Ø§Ù„ Ø¨Ù‡â€ŒØ±ÙˆØ²Ø±Ø³Ø§Ù†ÛŒ Ø§Ø³Øª...",
        "UPGRADE_FAILED": "به‌روزرسانی Ù†Ø§Ù…وفق Ø¨ÙˆØ¯",
        "ACTIVATION": "فعال‌سازی",
        "BATTERY_LOW": "شارژ Ø¨Ø§ØªØ±ÛŒ Ú©Ù…",
        "BATTERY_CHARGING": "در Ø­Ø§Ù„ Ø´Ø§Ø±Ú˜",
        "BATTERY_FULL": "باتری Ù¾Ø± Ø§Ø³Øª",
        "BATTERY_NEED_CHARGE": "شارژ Ø¨Ø§ØªØ±ÛŒ Ú©Ù…ØŒ Ù„طفاً Ø´Ø§Ø±Ú˜ Ú©Ù†ÛŒØ¯",
        "VOLUME": "صدا ",
        "MUTED": "بی‌صدا",
        "MAX_VOLUME": "حداکثر ØµØ¯Ø§",
        "RTC_MODE_OFF": "AEC Ø®Ø§Ù…وش",
        "RTC_MODE_ON": "AEC Ø±ÙˆØ´Ù†",
        "PLEASE_WAIT": "لطفاً ØµØ¨Ø± Ú©Ù†ÛŒØ¯...",
        "FOUND_NEW_ASSETS": "منابع Ø¬Ø¯ÛŒØ¯ ÛŒØ§ÙØª Ø´Ø¯: %s",
        "DOWNLOAD_ASSETS_FAILED": "دانلود Ù…نابع Ù†Ø§Ù…وفق Ø¨ÙˆØ¯",
        "LOADING_ASSETS": "بارگذاری Ù…نابع...",
        "HELLO_MY_FRIEND": "سلام، Ø¯ÙˆØ³Øª Ù…Ù†!",
        "FLIGHT_MODE_OFF": "حالت Ù¾Ø±ÙˆØ§Ø² Ø®Ø§Ù…وش Ø§Ø³Øª",
        "FLIGHT_MODE_ON": "حالت Ù¾Ø±ÙˆØ§Ø² Ø±ÙˆØ´Ù† Ø§Ø³Øª",
        "MODEM_INIT_ERROR": "خطا Ø¯Ø± Ø±Ø§Ù‡â€ŒØ§Ù†Ø¯Ø§Ø²ÛŒ Ù…ودم"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "fi-FI"
    },
    "strings": {
        "WARNING": "Varoitus",
        "INFO": "Tieto",
        "ERROR": "Virhe",
        "VERSION": "Versio ",
        "LOADING_PROTOCOL": "Yhdistetään palvelimeen...",
        "INITIALIZING": "Alustetaan...",
        "PIN_ERROR": "Ole hyvä ja aseta SIM-kortti",
        "REG_ERROR": "Ei voi muodostaa yhteyttä verkkoon, tarkista datakortin tila",
        "DETECTING_MODULE": "Tunnistetaan moduuli...",
        "REGISTERING_NETWORK": "Odotetaan verkkoa...",
        "CHECKING_NEW_VERSION": "Tarkistetaan uutta versiota...",
        "CHECK_NEW_VERSION_FAILED": "Uuden version tarkistus epäonnistui, yritetään uudelleen %d sekunnin kuluttua: %s",
        "SWITCH_TO_WIFI_NETWORK": "Vaihdetaan Wi-Fi:hin...",
        "SWITCH_TO_4G_NETWORK": "Vaihdetaan 4G:hen...",
        "STANDBY": "Valmiustila",
        "CONNECT_TO": "Yhdistä ",
        "CONNECTING": "Yhdistetään...",
        "CONNECTED_TO": "Yhdistetty ",
        "LISTENING": "Kuunnellaan...",
        "SPEAKING": "Puhutaan...",
        "SERVER_NOT_FOUND": "Etsitään käytettävissä olevaa palvelua",
        "SERVER_NOT_CONNECTED": "Ei voi yhdistää palveluun, yritä myöhemmin",
        "SERVER_TIMEOUT": "Vastauksen aikakatkaisu",
        "SERVER_ERROR": "Lähetys epäonnistui, tarkista verkko",
        "CONNECT_TO_HOTSPOT": "Yhdistä puhelin hotspottiin ",
        "ACCESS_VIA_BROWSER": ",pääsy selaimen kautta ",
        "WIFI_CONFIG_MODE": "Verkon konfigurointitila",
        "ENTERING_WIFI_CONFIG_MODE": "Siirrytään verkon konfigurointitilaan...",
        "SCANNING_WIFI": "Skannataan Wi-Fi...",
        "NEW_VERSION": "Uusi versio ",
        "OTA_UPGRADE": "OTA-päivitys",
        "UPGRADING": "Päivitetään järjestelmää...",
        "UPGRADE_FAILED": "Päivitys epäonnistui",
        "ACTIVATION": "Laitteen aktivointi",
        "BATTERY_LOW": "Akku vähissä",
        "BATTERY_CHARGING": "Ladataan",
        "BATTERY_FULL": "Akku täynnä",
        "BATTERY_NEED_CHARGE": "Akku vähissä, ole hyvä ja lataa",
        "VOLUME": "Äänenvoimakkuus ",
        "MUTED": "Mykistetty",
        "MAX_VOLUME": "Maksimi Ã¤Ã¤nenvoimakkuus",
        "RTC_MODE_OFF": "AEC pois päältä",
        "RTC_MODE_ON": "AEC päällä",
        "DOWNLOAD_ASSETS_FAILED": "Resurssien lataaminen epäonnistui",
        "LOADING_ASSETS": "Ladataan resursseja...",
        "PLEASE_WAIT": "Odota hetki...",
        "FOUND_NEW_ASSETS": "Löydetty uusia resursseja: %s",
        "HELLO_MY_FRIEND": "Hei, ystäväni!",
        "CONNECTION_SUCCESSFUL": "Yhteys onnistui",
        "FLIGHT_MODE_OFF": "Lentotila on pois päältä",
        "FLIGHT_MODE_ON": "Lentotila on päällä",
        "MODEM_INIT_ERROR": "Modeemin alustus epäonnistui"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "fil-PH"
    },
    "strings": {
        "WARNING": "Babala",
        "INFO": "Impormasyon",
        "ERROR": "Error",
        "VERSION": "Bersyon ",
        "LOADING_PROTOCOL": "Nagla-log in...",
        "INITIALIZING": "Nag-i-initialize...",
        "PIN_ERROR": "Mangyaring ilagay ang SIM card",
        "REG_ERROR": "Hindi ma-access ang network, mangyaring tingnan ang estado ng SIM card",
        "DETECTING_MODULE": "Tinutuklas ang module...",
        "REGISTERING_NETWORK": "Naghihintay ng network...",
        "CHECKING_NEW_VERSION": "Sinusuri ang bagong bersyon...",
        "CHECK_NEW_VERSION_FAILED": "Nabigo ang pagsuri ng bagong bersyon, susubukan muli sa %d segundo: %s",
        "SWITCH_TO_WIFI_NETWORK": "Lumilipat sa Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Lumilipat sa 4G...",
        "STANDBY": "Standby",
        "CONNECT_TO": "Kumonekta sa ",
        "CONNECTING": "Kumokonekta...",
        "CONNECTION_SUCCESSFUL": "Matagumpay na koneksyon",
        "CONNECTED_TO": "Nakakonekta sa ",
        "LISTENING": "Nakikinig...",
        "SPEAKING": "Nagsasalita...",
        "SERVER_NOT_FOUND": "Naghahanap ng available na serbisyo",
        "SERVER_NOT_CONNECTED": "Hindi makakonekta sa serbisyo, mangyaring subukan muli mamaya",
        "SERVER_TIMEOUT": "Nag-timeout ang paghihintay ng tugon",
        "SERVER_ERROR": "Nabigo ang pagpapadala, mangyaring tingnan ang network",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " URL ng configuration: ",
        "WIFI_CONFIG_MODE": "Wi-Fi Configuration Mode",
        "ENTERING_WIFI_CONFIG_MODE": "Pumapasok sa Wi-Fi configuration mode...",
        "SCANNING_WIFI": "Nag-i-scan ng Wi-Fi...",
        "NEW_VERSION": "Bagong bersyon ",
        "OTA_UPGRADE": "OTA Upgrade",
        "UPGRADING": "Nag-a-upgrade ang system...",
        "UPGRADE_FAILED": "Nabigo ang upgrade",
        "ACTIVATION": "Activation",
        "BATTERY_LOW": "Mababang baterya",
        "BATTERY_CHARGING": "Nagcha-charge",
        "BATTERY_FULL": "Puno ang baterya",
        "BATTERY_NEED_CHARGE": "Mababang baterya, mangyaring mag-charge",
        "VOLUME": "Volume ",
        "MUTED": "Naka-mute",
        "MAX_VOLUME": "Maximum volume",
        "RTC_MODE_OFF": "AEC Off",
        "RTC_MODE_ON": "AEC On",
        "PLEASE_WAIT": "Mangyaring maghintay...",
        "FOUND_NEW_ASSETS": "Nakahanap ng mga bagong assets: %s",
        "DOWNLOAD_ASSETS_FAILED": "Nabigo ang pag-download ng mga assets",
        "LOADING_ASSETS": "Nilo-load ang mga assets...",
        "HELLO_MY_FRIEND": "Kumusta, kaibigan ko!",
        "FLIGHT_MODE_OFF": "Naka-off ang flight mode",
        "FLIGHT_MODE_ON": "Naka-on ang flight mode",
        "MODEM_INIT_ERROR": "Nabigo ang pag-initialize ng modem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "fr-FR"
    },
    "strings": {
        "WARNING": "Avertissement",
        "INFO": "Information",
        "ERROR": "Erreur",
        "VERSION": "Version ",
        "LOADING_PROTOCOL": "Connexion au serveur...",
        "INITIALIZING": "Initialisation...",
        "PIN_ERROR": "Veuillez insérer la carte SIM",
        "REG_ERROR": "Impossible d'accéder au réseau, veuillez vérifier l'état de la carte de données",
        "DETECTING_MODULE": "Détection du module...",
        "REGISTERING_NETWORK": "En attente du réseau...",
        "CHECKING_NEW_VERSION": "Vérification de nouvelle version...",
        "CHECK_NEW_VERSION_FAILED": "Échec de vérification de nouvelle version, nouvelle tentative dans %d secondes : %s",
        "SWITCH_TO_WIFI_NETWORK": "Basculer vers Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Basculer vers 4G...",
        "STANDBY": "En attente",
        "CONNECT_TO": "Se connecter Ã  ",
        "CONNECTING": "Connexion en cours...",
        "CONNECTED_TO": "Connecté Ã  ",
        "LISTENING": "Écoute...",
        "SPEAKING": "Parole...",
        "SERVER_NOT_FOUND": "Recherche d'un service disponible",
        "SERVER_NOT_CONNECTED": "Impossible de se connecter au service, veuillez réessayer plus tard",
        "SERVER_TIMEOUT": "Délai d'attente de réponse",
        "SERVER_ERROR": "Échec d'envoi, veuillez vérifier le réseau",
        "CONNECT_TO_HOTSPOT": "Connecter le téléphone au point d'accès ",
        "ACCESS_VIA_BROWSER": ",accéder via le navigateur ",
        "WIFI_CONFIG_MODE": "Mode configuration réseau",
        "ENTERING_WIFI_CONFIG_MODE": "Entrer en mode configuration réseau...",
        "SCANNING_WIFI": "Scan Wi-Fi...",
        "NEW_VERSION": "Nouvelle version ",
        "OTA_UPGRADE": "Mise Ã  jour OTA",
        "UPGRADING": "Mise Ã  jour du système...",
        "UPGRADE_FAILED": "Échec de mise Ã  jour",
        "ACTIVATION": "Activation de l'appareil",
        "BATTERY_LOW": "Batterie faible",
        "BATTERY_CHARGING": "En charge",
        "BATTERY_FULL": "Batterie pleine",
        "BATTERY_NEED_CHARGE": "Batterie faible, veuillez charger",
        "VOLUME": "Volume ",
        "MUTED": "Muet",
        "MAX_VOLUME": "Volume maximum",
        "RTC_MODE_OFF": "AEC désactivé",
        "RTC_MODE_ON": "AEC activé",
        "DOWNLOAD_ASSETS_FAILED": "Échec du téléchargement des ressources",
        "LOADING_ASSETS": "Chargement des ressources...",
        "PLEASE_WAIT": "Veuillez patienter...",
        "FOUND_NEW_ASSETS": "Nouvelles ressources trouvées: %s",
        "HELLO_MY_FRIEND": "Bonjour, mon ami !",
        "CONNECTION_SUCCESSFUL": "Connexion réussie",
        "FLIGHT_MODE_OFF": "Le mode avion est désactivé",
        "FLIGHT_MODE_ON": "Le mode avion est activé",
        "MODEM_INIT_ERROR": "Échec de l'initialisation du modem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "he-IL"
    },
    "strings": {
        "WARNING": "אזהרה",
        "INFO": "מידע",
        "ERROR": "שגיאה",
        "VERSION": "גרסה ",
        "LOADING_PROTOCOL": "מתחבר...",
        "INITIALIZING": "מאתחל...",
        "PIN_ERROR": "אנא ×”כנס ×›×¨×˜×™×¡ SIM",
        "REG_ERROR": "לא × ×™×ª×Ÿ ×œ×’שת ×œ×¨×©×ª, ×× × ×‘דוק ××ª ×ž×¦×‘ ×›×¨×˜×™×¡ ×”-SIM",
        "DETECTING_MODULE": "מזהה ×ž×•דול...",
        "REGISTERING_NETWORK": "ממתין ×œ×¨×©×ª...",
        "CHECKING_NEW_VERSION": "בודק ×’רסה ×—דשה...",
        "CHECK_NEW_VERSION_FAILED": "בדיקת ×’רסה ×—דשה × ×›×©×œ×”, ×™× ×¡×” ×©×•ב ×‘עוד %d ×©× ×™×•ת: %s",
        "SWITCH_TO_WIFI_NETWORK": "עובר ×œ-Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "עובר ×œ-4G...",
        "STANDBY": "המתנה",
        "CONNECT_TO": "התחבר ×œ-",
        "CONNECTING": "מתחבר...",
        "CONNECTION_SUCCESSFUL": "התחברות ×”צליחה",
        "CONNECTED_TO": "מחובר ×œ-",
        "LISTENING": "מקשיב...",
        "SPEAKING": "מדבר...",
        "SERVER_NOT_FOUND": "מחפש ×©×™×¨×•ת ×–מין",
        "SERVER_NOT_CONNECTED": "לא × ×™×ª×Ÿ ×œ×”תחבר ×œ×©×™×¨×•ת, ×× × × ×¡×” ×©×•ב ×ž××•חר ×™×•תר",
        "SERVER_TIMEOUT": "פג ×–מן ×”המתנה ×œ×ª×’ובה",
        "SERVER_ERROR": "השליחה × ×›×©×œ×”, ×× × ×‘דוק ××ª ×”רשת",
        "CONNECT_TO_HOTSPOT": "נקודה ×—מה: ",
        "ACCESS_VIA_BROWSER": " ×›×ª×•בת ×ª×¦×•רה: ",
        "WIFI_CONFIG_MODE": "מצב ×ª×¦×•רת Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "נכנס ×œ×ž×¦×‘ ×ª×¦×•רת Wi-Fi...",
        "SCANNING_WIFI": "סורק Wi-Fi...",
        "NEW_VERSION": "גרסה ×—דשה ",
        "OTA_UPGRADE": "שדרוג OTA",
        "UPGRADING": "המערכת ×ž×©×ª×“רגת...",
        "UPGRADE_FAILED": "השדרוג × ×›×©×œ",
        "ACTIVATION": "הפעלה",
        "BATTERY_LOW": "סוללה ×—לשה",
        "BATTERY_CHARGING": "נטען",
        "BATTERY_FULL": "הסוללה ×ž×œ××”",
        "BATTERY_NEED_CHARGE": "סוללה ×—לשה, ×× × ×˜×¢×Ÿ",
        "VOLUME": "עוצמת ×§×•ל ",
        "MUTED": "מושתק",
        "MAX_VOLUME": "עוצמת ×§×•ל ×ž×§×¡×™×ž×œ×™×ª",
        "RTC_MODE_OFF": "AEC ×›×‘וי",
        "RTC_MODE_ON": "AEC ×“לוק",
        "PLEASE_WAIT": "אנא ×”מתן...",
        "FOUND_NEW_ASSETS": "נמצאו ×ž×©××‘ים ×—דשים: %s",
        "DOWNLOAD_ASSETS_FAILED": "הורדת ×ž×©××‘ים × ×›×©×œ×”",
        "LOADING_ASSETS": "טוען ×ž×©××‘ים...",
        "HELLO_MY_FRIEND": "שלום, ×™×“ידי!",
        "FLIGHT_MODE_OFF": "מצב ×˜×™×¡×” ×›×‘וי",
        "FLIGHT_MODE_ON": "מצב ×˜×™×¡×” ×ž×•פעל",
        "MODEM_INIT_ERROR": "אתחול ×”מודם × ×›×©×œ"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "hi-IN"
    },
    "strings": {
        "WARNING": "चेतावनी",
        "INFO": "जानकारी",
        "ERROR": "त्रुटि",
        "VERSION": "संस्करण ",
        "LOADING_PROTOCOL": "सर्वर à¤¸à¥‡ à¤•नेक्ट à¤¹à¥‹ à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "INITIALIZING": "आरंभीकरण...",
        "PIN_ERROR": "कृपया à¤¸à¤¿à¤® à¤•ार्ड à¤¡à¤¾à¤²à¥‡à¤‚",
        "REG_ERROR": "नेटवर्क à¤¤à¤• à¤ªà¤¹à¥à¤‚च à¤¨à¤¹à¥€à¤‚ à¤¹à¥‹ à¤¸à¤•ती, à¤•ृपया à¤¡à¥‡à¤Ÿà¤¾ à¤•ार्ड à¤¸à¥à¤¥à¤¿à¤¤à¤¿ à¤œà¤¾à¤‚चें",
        "DETECTING_MODULE": "मॉड्यूल à¤•ा à¤ªà¤¤à¤¾ à¤²à¤—ाया à¤œà¤¾ à¤°à¤¹à¤¾ à¤¹à¥ˆ...",
        "REGISTERING_NETWORK": "नेटवर्क à¤•ी à¤ªà¥à¤°à¤¤à¥€à¤•्षा...",
        "CHECKING_NEW_VERSION": "नया à¤¸à¤‚स्करण à¤œà¤¾à¤à¤š à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "CHECK_NEW_VERSION_FAILED": "नया à¤¸à¤‚स्करण à¤œà¤¾à¤à¤šà¤¨à¤¾ à¤…सफल, %d à¤¸à¥‡à¤•ंड à¤®à¥‡à¤‚ à¤ªà¥à¤¨à¤ƒ à¤ªà¥à¤°à¤¯à¤¾à¤¸: %s",
        "SWITCH_TO_WIFI_NETWORK": "Wi-Fi à¤ªà¤° à¤¸à¥à¤µà¤¿à¤š à¤•र à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "SWITCH_TO_4G_NETWORK": "4G à¤ªà¤° à¤¸à¥à¤µà¤¿à¤š à¤•र à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "STANDBY": "स्टैंडबाय",
        "CONNECT_TO": "कनेक्ट à¤•रें ",
        "CONNECTING": "कनेक्ट à¤¹à¥‹ à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "CONNECTED_TO": "कनेक्ट à¤¹à¥‹ à¤—ए ",
        "LISTENING": "सुन à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "SPEAKING": "बोल à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "SERVER_NOT_FOUND": "उपलब्ध à¤¸à¥‡à¤µà¤¾ à¤–ोज à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚",
        "SERVER_NOT_CONNECTED": "सेवा à¤¸à¥‡ à¤•नेक्ट à¤¨à¤¹à¥€à¤‚ à¤¹à¥‹ à¤¸à¤•ते, à¤•ृपया à¤¬à¤¾à¤¦ à¤®à¥‡à¤‚ à¤•ोशिश à¤•रें",
        "SERVER_TIMEOUT": "प्रतिक्रिया à¤•ा à¤¸à¤®à¤¯ à¤¸à¤®à¤¾à¤ªà¥à¤¤",
        "SERVER_ERROR": "भेजना à¤…सफल, à¤•ृपया à¤¨à¥‡à¤Ÿà¤µà¤°à¥à¤• à¤œà¤¾à¤‚चें",
        "CONNECT_TO_HOTSPOT": "फोन à¤•ो à¤¹à¥‰à¤Ÿà¤¸à¥à¤ªà¥‰à¤Ÿ à¤¸à¥‡ à¤•नेक्ट à¤•रें ",
        "ACCESS_VIA_BROWSER": ",ब्राउज़र à¤•े à¤®à¤¾à¤§à¥à¤¯à¤® à¤¸à¥‡ à¤ªà¤¹à¥à¤‚चें ",
        "WIFI_CONFIG_MODE": "नेटवर्क à¤•ॉन्फ़िगरेशन à¤®à¥‹à¤¡",
        "ENTERING_WIFI_CONFIG_MODE": "नेटवर्क à¤•ॉन्फ़िगरेशन à¤®à¥‹à¤¡ à¤®à¥‡à¤‚ à¤ªà¥à¤°à¤µà¥‡à¤¶...",
        "SCANNING_WIFI": "Wi-Fi à¤¸à¥à¤•ैन à¤•र à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "NEW_VERSION": "नया à¤¸à¤‚स्करण ",
        "OTA_UPGRADE": "OTA à¤…पग्रेड",
        "UPGRADING": "सिस्टम à¤…पग्रेड à¤¹à¥‹ à¤°à¤¹à¤¾ à¤¹à¥ˆ...",
        "UPGRADE_FAILED": "अपग्रेड à¤…सफल",
        "ACTIVATION": "डिवाइस à¤¸à¤•्रियण",
        "BATTERY_LOW": "बैटरी à¤•म",
        "BATTERY_CHARGING": "चार्ज à¤¹à¥‹ à¤°à¤¹à¥€ à¤¹à¥ˆ",
        "BATTERY_FULL": "बैटरी à¤«à¥à¤²",
        "BATTERY_NEED_CHARGE": "बैटरी à¤•म à¤¹à¥ˆ, à¤•ृपया à¤šà¤¾à¤°à¥à¤œ à¤•रें",
        "VOLUME": "आवाज़ ",
        "MUTED": "म्यूट",
        "MAX_VOLUME": "अधिकतम à¤†à¤µà¤¾à¤œà¤¼",
        "RTC_MODE_OFF": "AEC à¤¬à¤‚द",
        "RTC_MODE_ON": "AEC à¤šà¤¾à¤²à¥‚",
        "DOWNLOAD_ASSETS_FAILED": "संसाधन à¤¡à¤¾à¤‰à¤¨à¤²à¥‹à¤¡ à¤•रने à¤®à¥‡à¤‚ à¤µà¤¿à¤«à¤²",
        "LOADING_ASSETS": "संसाधन à¤²à¥‹à¤¡ à¤¹à¥‹ à¤°à¤¹à¥‡ à¤¹à¥ˆà¤‚...",
        "PLEASE_WAIT": "कृपया à¤ªà¥à¤°à¤¤à¥€à¤•्षा à¤•रें...",
        "FOUND_NEW_ASSETS": "नए à¤¸à¤‚साधन à¤®à¤¿à¤²à¥‡: %s",
        "HELLO_MY_FRIEND": "नमस्ते, à¤®à¥‡à¤°à¥‡ à¤¦à¥‹à¤¸à¥à¤¤!",
        "CONNECTION_SUCCESSFUL": "कनेक्शन à¤¸à¤«à¤²",
        "FLIGHT_MODE_OFF": "फ़्लाइट à¤®à¥‹à¤¡ à¤¬à¤‚द à¤¹à¥ˆ",
        "FLIGHT_MODE_ON": "फ़्लाइट à¤®à¥‹à¤¡ à¤šà¤¾à¤²à¥‚ à¤¹à¥ˆ",
        "MODEM_INIT_ERROR": "मॉडेम à¤†à¤°à¤‚भीकरण à¤µà¤¿à¤«à¤²"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "hr-HR"
    },
    "strings": {
        "WARNING": "Upozorenje",
        "INFO": "Informacije",
        "ERROR": "GreÅ¡ka",
        "VERSION": "Verzija ",
        "LOADING_PROTOCOL": "Prijava...",
        "INITIALIZING": "Inicijalizacija...",
        "PIN_ERROR": "Molimo umetnite SIM karticu",
        "REG_ERROR": "Nije moguć pristup mreži, molimo provjerite status SIM kartice",
        "DETECTING_MODULE": "Otkrivanje modula...",
        "REGISTERING_NETWORK": "Čekanje mreže...",
        "CHECKING_NEW_VERSION": "Provjera nove verzije...",
        "CHECK_NEW_VERSION_FAILED": "Provjera nove verzije nije uspjela, ponovit Ä‡e se za %d sekundi: %s",
        "SWITCH_TO_WIFI_NETWORK": "Prebacivanje na Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Prebacivanje na 4G...",
        "STANDBY": "Pripravnost",
        "CONNECT_TO": "Poveži se s ",
        "CONNECTING": "Povezivanje...",
        "CONNECTION_SUCCESSFUL": "UspjeÅ¡no povezivanje",
        "CONNECTED_TO": "Povezano s ",
        "LISTENING": "SluÅ¡anje...",
        "SPEAKING": "Govorenje...",
        "SERVER_NOT_FOUND": "Traženje dostupne usluge",
        "SERVER_NOT_CONNECTED": "Nije moguće povezati se s uslugom, molimo pokuÅ¡ajte kasnije",
        "SERVER_TIMEOUT": "Istek vremena Äekanja odgovora",
        "SERVER_ERROR": "Slanje nije uspjelo, molimo provjerite mrežu",
        "CONNECT_TO_HOTSPOT": "Pristupna točka: ",
        "ACCESS_VIA_BROWSER": " URL konfiguracije: ",
        "WIFI_CONFIG_MODE": "Način konfiguracije Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Ulazak u način konfiguracije Wi-Fi...",
        "SCANNING_WIFI": "Skeniranje Wi-Fi...",
        "NEW_VERSION": "Nova verzija ",
        "OTA_UPGRADE": "OTA nadogradnja",
        "UPGRADING": "Sustav se nadograđuje...",
        "UPGRADE_FAILED": "Nadogradnja nije uspjela",
        "ACTIVATION": "Aktivacija",
        "BATTERY_LOW": "Niska baterija",
        "BATTERY_CHARGING": "Punjenje",
        "BATTERY_FULL": "Baterija puna",
        "BATTERY_NEED_CHARGE": "Niska baterija, molimo napunite",
        "VOLUME": "Glasnoća ",
        "MUTED": "UtiÅ¡ano",
        "MAX_VOLUME": "Maksimalna glasnoća",
        "RTC_MODE_OFF": "AEC isključen",
        "RTC_MODE_ON": "AEC uključen",
        "PLEASE_WAIT": "Molimo pričekajte...",
        "FOUND_NEW_ASSETS": "Pronađeni novi resursi: %s",
        "DOWNLOAD_ASSETS_FAILED": "Preuzimanje resursa nije uspjelo",
        "LOADING_ASSETS": "Učitavanje resursa...",
        "HELLO_MY_FRIEND": "Bok, moj prijatelju!",
        "FLIGHT_MODE_OFF": "Način rada u zrakoplovu je isključen",
        "FLIGHT_MODE_ON": "Način rada u zrakoplovu je uključen",
        "MODEM_INIT_ERROR": "Neuspjela inicijalizacija modema"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "hu-HU"
    },
    "strings": {
        "WARNING": "Figyelmeztetés",
        "INFO": "Információ",
        "ERROR": "Hiba",
        "VERSION": "Verzió ",
        "LOADING_PROTOCOL": "Bejelentkezés...",
        "INITIALIZING": "Inicializálás...",
        "PIN_ERROR": "Kérjük, helyezze be a SIM kártyát",
        "REG_ERROR": "A hálózat nem Ã©rhető el, kérjük, ellenőrizze a SIM kártya Ã¡llapotát",
        "DETECTING_MODULE": "Modul Ã©szlelése...",
        "REGISTERING_NETWORK": "Várakozás a hálózatra...",
        "CHECKING_NEW_VERSION": "Új verzió keresése...",
        "CHECK_NEW_VERSION_FAILED": "Az Ãºj verzió keresése sikertelen, Ãºjrapróbálás %d másodperc múlva: %s",
        "SWITCH_TO_WIFI_NETWORK": "Váltás Wi-Fi-re...",
        "SWITCH_TO_4G_NETWORK": "Váltás 4G-re...",
        "STANDBY": "Készenlét",
        "CONNECT_TO": "Csatlakozás: ",
        "CONNECTING": "Csatlakozás...",
        "CONNECTION_SUCCESSFUL": "Sikeres csatlakozás",
        "CONNECTED_TO": "Csatlakozva: ",
        "LISTENING": "Figyelés...",
        "SPEAKING": "Beszéd...",
        "SERVER_NOT_FOUND": "Elérhető szolgáltatás keresése",
        "SERVER_NOT_CONNECTED": "A szolgáltatáshoz nem sikerült csatlakozni, kérjük, próbálja Ãºjra később",
        "SERVER_TIMEOUT": "A várakozási idő lejárt",
        "SERVER_ERROR": "A küldés sikertelen, kérjük, ellenőrizze a hálózatot",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Konfigurációs URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi konfigurációs mód",
        "ENTERING_WIFI_CONFIG_MODE": "Belépés Wi-Fi konfigurációs módba...",
        "SCANNING_WIFI": "Wi-Fi keresése...",
        "NEW_VERSION": "Új verzió ",
        "OTA_UPGRADE": "OTA frissítés",
        "UPGRADING": "A rendszer frissítése folyamatban...",
        "UPGRADE_FAILED": "A frissítés sikertelen",
        "ACTIVATION": "Aktiválás",
        "BATTERY_LOW": "Alacsony akkumulátor",
        "BATTERY_CHARGING": "Töltés",
        "BATTERY_FULL": "Akkumulátor tele",
        "BATTERY_NEED_CHARGE": "Alacsony akkumulátor, kérjük, töltse fel",
        "VOLUME": "Hangerő ",
        "MUTED": "Némítva",
        "MAX_VOLUME": "Maximális hangerő",
        "RTC_MODE_OFF": "AEC kikapcsolva",
        "RTC_MODE_ON": "AEC bekapcsolva",
        "PLEASE_WAIT": "Kérjük, várjon...",
        "FOUND_NEW_ASSETS": "Új erőforrások találva: %s",
        "DOWNLOAD_ASSETS_FAILED": "Az erőforrások letöltése sikertelen",
        "LOADING_ASSETS": "Erőforrások betöltése...",
        "HELLO_MY_FRIEND": "Helló, barátom!",
        "FLIGHT_MODE_OFF": "A repülési mód ki van kapcsolva",
        "FLIGHT_MODE_ON": "A repülési mód be van kapcsolva",
        "MODEM_INIT_ERROR": "A modem inicializálása sikertelen"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "id-ID"
    },
    "strings": {
        "WARNING": "Peringatan",
        "INFO": "Informasi",
        "ERROR": "Kesalahan",
        "VERSION": "Versi ",
        "LOADING_PROTOCOL": "Menghubungkan ke server...",
        "INITIALIZING": "Menginisialisasi...",
        "PIN_ERROR": "Silakan masukkan kartu SIM",
        "REG_ERROR": "Tidak dapat mengakses jaringan, periksa status kartu data",
        "DETECTING_MODULE": "Mendeteksi modul...",
        "REGISTERING_NETWORK": "Menunggu jaringan...",
        "CHECKING_NEW_VERSION": "Memeriksa versi baru...",
        "CHECK_NEW_VERSION_FAILED": "Pemeriksaan versi baru gagal, mencoba lagi dalam %d detik: %s",
        "SWITCH_TO_WIFI_NETWORK": "Beralih ke Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Beralih ke 4G...",
        "STANDBY": "Siaga",
        "CONNECT_TO": "Hubungkan ke ",
        "CONNECTING": "Menghubungkan...",
        "CONNECTED_TO": "Terhubung ke ",
        "LISTENING": "Mendengarkan...",
        "SPEAKING": "Berbicara...",
        "SERVER_NOT_FOUND": "Mencari layanan yang tersedia",
        "SERVER_NOT_CONNECTED": "Tidak dapat terhubung ke layanan, coba lagi nanti",
        "SERVER_TIMEOUT": "Waktu respons habis",
        "SERVER_ERROR": "Pengiriman gagal, periksa jaringan",
        "CONNECT_TO_HOTSPOT": "Hubungkan ponsel ke hotspot ",
        "ACCESS_VIA_BROWSER": ",akses melalui browser ",
        "WIFI_CONFIG_MODE": "Mode konfigurasi jaringan",
        "ENTERING_WIFI_CONFIG_MODE": "Memasuki mode konfigurasi jaringan...",
        "SCANNING_WIFI": "Memindai Wi-Fi...",
        "NEW_VERSION": "Versi baru ",
        "OTA_UPGRADE": "Pembaruan OTA",
        "UPGRADING": "Memperbarui sistem...",
        "UPGRADE_FAILED": "Pembaruan gagal",
        "ACTIVATION": "Aktivasi perangkat",
        "BATTERY_LOW": "Baterai lemah",
        "BATTERY_CHARGING": "Mengisi",
        "BATTERY_FULL": "Baterai penuh",
        "BATTERY_NEED_CHARGE": "Baterai lemah, silakan isi",
        "VOLUME": "Volume ",
        "MUTED": "Bisu",
        "MAX_VOLUME": "Volume maksimum",
        "RTC_MODE_OFF": "AEC mati",
        "RTC_MODE_ON": "AEC nyala",
        "DOWNLOAD_ASSETS_FAILED": "Gagal mengunduh aset",
        "LOADING_ASSETS": "Memuat aset...",
        "PLEASE_WAIT": "Mohon tunggu...",
        "FOUND_NEW_ASSETS": "Ditemukan aset baru: %s",
        "HELLO_MY_FRIEND": "Halo, teman saya!",
        "CONNECTION_SUCCESSFUL": "Koneksi berhasil",
        "FLIGHT_MODE_OFF": "Mode pesawat nonaktif",
        "FLIGHT_MODE_ON": "Mode pesawat aktif",
        "MODEM_INIT_ERROR": "Gagal menginisialisasi modem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "it-IT"
    },
    "strings": {
        "WARNING": "Avviso",
        "INFO": "Informazione",
        "ERROR": "Errore",
        "VERSION": "Versione ",
        "LOADING_PROTOCOL": "Connessione al server...",
        "INITIALIZING": "Inizializzazione...",
        "PIN_ERROR": "Inserire la scheda SIM",
        "REG_ERROR": "Impossibile accedere alla rete, controllare lo stato della scheda dati",
        "DETECTING_MODULE": "Rilevamento modulo...",
        "REGISTERING_NETWORK": "In attesa della rete...",
        "CHECKING_NEW_VERSION": "Controllo nuova versione...",
        "CHECK_NEW_VERSION_FAILED": "Controllo nuova versione fallito, riprovo tra %d secondi: %s",
        "SWITCH_TO_WIFI_NETWORK": "Passaggio a Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Passaggio a 4G...",
        "STANDBY": "In attesa",
        "CONNECT_TO": "Connetti a ",
        "CONNECTING": "Connessione...",
        "CONNECTED_TO": "Connesso a ",
        "LISTENING": "In ascolto...",
        "SPEAKING": "Parlando...",
        "SERVER_NOT_FOUND": "Ricerca servizio disponibile",
        "SERVER_NOT_CONNECTED": "Impossibile connettersi al servizio, riprovare più tardi",
        "SERVER_TIMEOUT": "Timeout risposta",
        "SERVER_ERROR": "Invio fallito, controllare la rete",
        "CONNECT_TO_HOTSPOT": "Connetti telefono al hotspot ",
        "ACCESS_VIA_BROWSER": ",accedi tramite browser ",
        "WIFI_CONFIG_MODE": "Modalità configurazione rete",
        "ENTERING_WIFI_CONFIG_MODE": "Entrata in modalità configurazione rete...",
        "SCANNING_WIFI": "Scansione Wi-Fi...",
        "NEW_VERSION": "Nuova versione ",
        "OTA_UPGRADE": "Aggiornamento OTA",
        "UPGRADING": "Aggiornamento sistema...",
        "UPGRADE_FAILED": "Aggiornamento fallito",
        "ACTIVATION": "Attivazione dispositivo",
        "BATTERY_LOW": "Batteria scarica",
        "BATTERY_CHARGING": "In carica",
        "BATTERY_FULL": "Batteria piena",
        "BATTERY_NEED_CHARGE": "Batteria scarica, ricaricare",
        "VOLUME": "Volume ",
        "MUTED": "Silenziato",
        "MAX_VOLUME": "Volume massimo",
        "RTC_MODE_OFF": "AEC disattivato",
        "RTC_MODE_ON": "AEC attivato",
        "DOWNLOAD_ASSETS_FAILED": "Impossibile scaricare le risorse",
        "LOADING_ASSETS": "Caricamento risorse...",
        "PLEASE_WAIT": "Attendere prego...",
        "FOUND_NEW_ASSETS": "Trovate nuove risorse: %s",
        "HELLO_MY_FRIEND": "Ciao, amico mio!",
        "CONNECTION_SUCCESSFUL": "Connessione riuscita",
        "FLIGHT_MODE_OFF": "La modalità aereo Ã¨ disattivata",
        "FLIGHT_MODE_ON": "La modalità aereo Ã¨ attiva",
        "MODEM_INIT_ERROR": "Inizializzazione modem non riuscita"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ja-JP"
    },
    "strings": {
        "WARNING": "警告",
        "INFO": "情報",
        "ERROR": "エラー",
        "VERSION": "バージョン ",
        "LOADING_PROTOCOL": "サーバーにログイン中...",
        "INITIALIZING": "初期化中...",
        "PIN_ERROR": "SIMカードを挿入してください",
        "REG_ERROR": "ネットワークに接続できません。ネットワーク状態を確認してください",
        "DETECTING_MODULE": "モジュールを検出中...",
        "REGISTERING_NETWORK": "ネットワーク接続待機中...",
        "CHECKING_NEW_VERSION": "新しいバージョンを確認中...",
        "CHECK_NEW_VERSION_FAILED": "更新確認に失敗しました。%d ç§’後に再試行します: %s",
        "SWITCH_TO_WIFI_NETWORK": "Wi-Fiに切り替え中...",
        "SWITCH_TO_4G_NETWORK": "4Gに切り替え中...",
        "STANDBY": "待機中",
        "CONNECT_TO": "接続先 ",
        "CONNECTING": "接続中...",
        "CONNECTED_TO": "接続完了 ",
        "LISTENING": "リスニング中...",
        "SPEAKING": "話しています...",
        "SERVER_NOT_FOUND": "利用可能なサーバーを探しています",
        "SERVER_NOT_CONNECTED": "サーバーに接続できません。後でもう一度お試しください",
        "SERVER_TIMEOUT": "応答待機時間が終了しました",
        "SERVER_ERROR": "送信に失敗しました。ネットワークを確認してください",
        "CONNECT_TO_HOTSPOT": "スマートフォンをWi-Fi ",
        "ACCESS_VIA_BROWSER": " ã«æŽ¥ç¶šã—、ブラウザでアクセスしてください ",
        "WIFI_CONFIG_MODE": "ネットワーク設定モード",
        "ENTERING_WIFI_CONFIG_MODE": "ネットワーク設定中...",
        "SCANNING_WIFI": "Wi-Fiをスキャン中...",
        "NEW_VERSION": "新しいバージョン ",
        "OTA_UPGRADE": "OTAアップグレード",
        "UPGRADING": "システムをアップグレード中...",
        "UPGRADE_FAILED": "アップグレード失敗",
        "ACTIVATION": "デバイスをアクティベート",
        "BATTERY_LOW": "バッテリーが少なくなっています",
        "BATTERY_CHARGING": "充電中",
        "BATTERY_FULL": "バッテリー満タン",
        "BATTERY_NEED_CHARGE": "バッテリーが低下しています。充電してください",
        "VOLUME": "音量 ",
        "MUTED": "ミュートされています",
        "MAX_VOLUME": "最大音量",
        "RTC_MODE_OFF": "AEC ç„¡åй",
        "RTC_MODE_ON": "AEC æœ‰åй",
        "DOWNLOAD_ASSETS_FAILED": "アセットのダウンロードに失敗しました",
        "LOADING_ASSETS": "アセットを読み込み中...",
        "PLEASE_WAIT": "お待ちください...",
        "FOUND_NEW_ASSETS": "新しいアセットが見つかりました: %s",
        "HELLO_MY_FRIEND": "こんにちは、友達!",
        "CONNECTION_SUCCESSFUL": "接続成功",
        "FLIGHT_MODE_OFF": "機内モードがオフです",
        "FLIGHT_MODE_ON": "機内モードがオンです",
        "MODEM_INIT_ERROR": "モデムの初期化に失敗しました"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ko-KR"
    },
    "strings": {
        "WARNING": "경고",
        "INFO": "정보",
        "ERROR": "오류",
        "VERSION": "버전 ",
        "LOADING_PROTOCOL": "로그인 ì¤‘...",
        "INITIALIZING": "초기화 ì¤‘...",
        "PIN_ERROR": "SIM ì¹´ë“œë¥¼ ì‚½ìž…하세요",
        "REG_ERROR": "네트워크에 ì ‘속할 ìˆ˜ ì—†ìŠµë‹ˆë‹¤. SIM ì¹´ë“œ ìƒíƒœë¥¼ í™•인하세요",
        "DETECTING_MODULE": "모듈 ê°ì§€ ì¤‘...",
        "REGISTERING_NETWORK": "네트워크 ëŒ€ê¸° ì¤‘...",
        "CHECKING_NEW_VERSION": "새 ë²„ì „ í™•인 ì¤‘...",
        "CHECK_NEW_VERSION_FAILED": "새 ë²„ì „ í™•인에 ì‹¤íŒ¨í–ˆìŠµë‹ˆë‹¤. %d초 í›„에 ë‹¤ì‹œ ì‹œë„합니다: %s",
        "SWITCH_TO_WIFI_NETWORK": "Wi-Fi로 ì „환 ì¤‘...",
        "SWITCH_TO_4G_NETWORK": "4G로 ì „환 ì¤‘...",
        "STANDBY": "대기",
        "CONNECT_TO": "연결 ëŒ€ìƒ: ",
        "CONNECTING": "연결 ì¤‘...",
        "CONNECTION_SUCCESSFUL": "연결 ì„±ê³µ",
        "CONNECTED_TO": "연결됨: ",
        "LISTENING": "듣는 ì¤‘...",
        "SPEAKING": "말하는 ì¤‘...",
        "SERVER_NOT_FOUND": "사용 ê°€ëŠ¥í•œ ì„œë¹„스를 ì°¾ëŠ” ì¤‘",
        "SERVER_NOT_CONNECTED": "서비스에 ì—°ê²°í•  ìˆ˜ ì—†ìŠµë‹ˆë‹¤. ë‚˜ì¤‘에 ë‹¤ì‹œ ì‹œë„하세요",
        "SERVER_TIMEOUT": "응답 ëŒ€ê¸° ì‹œê°„ ì´ˆê³¼",
        "SERVER_ERROR": "전송 ì‹¤íŒ¨, ë„¤íŠ¸ì›Œí¬ë¥¼ í™•인하세요",
        "CONNECT_TO_HOTSPOT": "핫스팟: ",
        "ACCESS_VIA_BROWSER": " ì„¤ì • URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi ì„¤ì • ëª¨ë“œ",
        "ENTERING_WIFI_CONFIG_MODE": "Wi-Fi ì„¤ì • ëª¨ë“œ ì§„ìž… ì¤‘...",
        "SCANNING_WIFI": "Wi-Fi ìŠ¤ìº” ì¤‘...",
        "NEW_VERSION": "새 ë²„ì „ ",
        "OTA_UPGRADE": "OTA ì—…그레이드",
        "UPGRADING": "시스템 ì—…그레이드 ì¤‘...",
        "UPGRADE_FAILED": "업그레이드 ì‹¤íŒ¨",
        "ACTIVATION": "활성화",
        "BATTERY_LOW": "배터리 ë¶€ì¡±",
        "BATTERY_CHARGING": "충전 ì¤‘",
        "BATTERY_FULL": "배터리 ì™„ì¶©",
        "BATTERY_NEED_CHARGE": "배터리 ë¶€ì¡±, ì¶©ì „하세요",
        "VOLUME": "볼륨 ",
        "MUTED": "음소거",
        "MAX_VOLUME": "최대 ë³¼ë¥¨",
        "RTC_MODE_OFF": "AEC ë„기",
        "RTC_MODE_ON": "AEC ì¼œê¸°",
        "DOWNLOAD_ASSETS_FAILED": "에셋 ë‹¤ìš´ë¡œë“œ ì‹¤íŒ¨",
        "LOADING_ASSETS": "에셋 ë¡œë”© ì¤‘...",
        "PLEASE_WAIT": "잠시 ê¸°ë‹¤ë ¤ ì£¼ì„¸ìš”...",
        "FOUND_NEW_ASSETS": "새로운 ì—ì…‹ì„ ë°œê²¬í–ˆìŠµë‹ˆë‹¤: %s",
        "HELLO_MY_FRIEND": "안녕하세요, ì¹œêµ¬!",
        "FLIGHT_MODE_OFF": "비행기 ëª¨ë“œê°€ êº¼ì ¸ ìžˆìŠµë‹ˆë‹¤",
        "FLIGHT_MODE_ON": "비행기 ëª¨ë“œê°€ ì¼œì ¸ ìžˆìŠµë‹ˆë‹¤",
        "MODEM_INIT_ERROR": "모뎀 ì´ˆê¸°í™” ì‹¤íŒ¨"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ms-MY"
    },
    "strings": {
        "WARNING": "Amaran",
        "INFO": "Maklumat",
        "ERROR": "Ralat",
        "VERSION": "Versi ",
        "LOADING_PROTOCOL": "Log masuk...",
        "INITIALIZING": "Memulakan...",
        "PIN_ERROR": "Sila masukkan kad SIM",
        "REG_ERROR": "Tidak dapat mengakses rangkaian, sila semak status kad SIM",
        "DETECTING_MODULE": "Mengesan modul...",
        "REGISTERING_NETWORK": "Menunggu rangkaian...",
        "CHECKING_NEW_VERSION": "Memeriksa versi baharu...",
        "CHECK_NEW_VERSION_FAILED": "Pemeriksaan versi baharu gagal, akan cuba semula dalam %d saat: %s",
        "SWITCH_TO_WIFI_NETWORK": "Bertukar ke Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Bertukar ke 4G...",
        "STANDBY": "Bersedia",
        "CONNECT_TO": "Sambung ke ",
        "CONNECTING": "Menyambung...",
        "CONNECTION_SUCCESSFUL": "Sambungan berjaya",
        "CONNECTED_TO": "Disambungkan ke ",
        "LISTENING": "Mendengar...",
        "SPEAKING": "Bercakap...",
        "SERVER_NOT_FOUND": "Mencari perkhidmatan yang tersedia",
        "SERVER_NOT_CONNECTED": "Tidak dapat menyambung ke perkhidmatan, sila cuba lagi kemudian",
        "SERVER_TIMEOUT": "Masa tamat menunggu respons",
        "SERVER_ERROR": "Penghantaran gagal, sila semak rangkaian",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " URL konfigurasi: ",
        "WIFI_CONFIG_MODE": "Mod Konfigurasi Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Memasuki mod konfigurasi Wi-Fi...",
        "SCANNING_WIFI": "Mengimbas Wi-Fi...",
        "NEW_VERSION": "Versi baharu ",
        "OTA_UPGRADE": "Peningkatan OTA",
        "UPGRADING": "Sistem sedang dinaik taraf...",
        "UPGRADE_FAILED": "Peningkatan gagal",
        "ACTIVATION": "Pengaktifan",
        "BATTERY_LOW": "Bateri lemah",
        "BATTERY_CHARGING": "Mengecas",
        "BATTERY_FULL": "Bateri penuh",
        "BATTERY_NEED_CHARGE": "Bateri lemah, sila cas",
        "VOLUME": "Kelantangan ",
        "MUTED": "Dibisukan",
        "MAX_VOLUME": "Kelantangan maksimum",
        "RTC_MODE_OFF": "AEC Dimatikan",
        "RTC_MODE_ON": "AEC Dihidupkan",
        "PLEASE_WAIT": "Sila tunggu...",
        "FOUND_NEW_ASSETS": "Menemui aset baharu: %s",
        "DOWNLOAD_ASSETS_FAILED": "Gagal memuat turun aset",
        "LOADING_ASSETS": "Memuatkan aset...",
        "HELLO_MY_FRIEND": "Hai, kawan saya!",
        "FLIGHT_MODE_OFF": "Mod penerbangan dimatikan",
        "FLIGHT_MODE_ON": "Mod penerbangan dihidupkan",
        "MODEM_INIT_ERROR": "Modem gagal dimulakan"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "nb-NO"
    },
    "strings": {
        "WARNING": "Advarsel",
        "INFO": "Informasjon",
        "ERROR": "Feil",
        "VERSION": "Versjon ",
        "LOADING_PROTOCOL": "Logger inn...",
        "INITIALIZING": "Initialiserer...",
        "PIN_ERROR": "Vennligst sett inn SIM-kort",
        "REG_ERROR": "Kan ikke fÃ¥ tilgang til nettverket, vennligst sjekk SIM-kortets status",
        "DETECTING_MODULE": "Oppdager modul...",
        "REGISTERING_NETWORK": "Venter pÃ¥ nettverk...",
        "CHECKING_NEW_VERSION": "Sjekker for ny versjon...",
        "CHECK_NEW_VERSION_FAILED": "Sjekk for ny versjon mislyktes, prøver pÃ¥ nytt om %d sekunder: %s",
        "SWITCH_TO_WIFI_NETWORK": "Bytter til Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Bytter til 4G...",
        "STANDBY": "Standby",
        "CONNECT_TO": "Koble til ",
        "CONNECTING": "Kobler til...",
        "CONNECTION_SUCCESSFUL": "Tilkobling vellykket",
        "CONNECTED_TO": "Koblet til ",
        "LISTENING": "Lytter...",
        "SPEAKING": "Snakker...",
        "SERVER_NOT_FOUND": "Søker etter tilgjengelig tjeneste",
        "SERVER_NOT_CONNECTED": "Kan ikke koble til tjeneste, vennligst prøv igjen senere",
        "SERVER_TIMEOUT": "Tidsavbrudd for Ã¥ vente pÃ¥ svar",
        "SERVER_ERROR": "Sending mislyktes, vennligst sjekk nettverket",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Konfigurasjon-URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi konfigurasjonsmodus",
        "ENTERING_WIFI_CONFIG_MODE": "GÃ¥r inn i Wi-Fi konfigurasjonsmodus...",
        "SCANNING_WIFI": "Skanner Wi-Fi...",
        "NEW_VERSION": "Ny versjon ",
        "OTA_UPGRADE": "OTA-oppgradering",
        "UPGRADING": "Systemet oppgraderes...",
        "UPGRADE_FAILED": "Oppgradering mislyktes",
        "ACTIVATION": "Aktivering",
        "BATTERY_LOW": "Lavt batteri",
        "BATTERY_CHARGING": "Lader",
        "BATTERY_FULL": "Batteriet er fullt",
        "BATTERY_NEED_CHARGE": "Lavt batteri, vennligst lad",
        "VOLUME": "Volum ",
        "MUTED": "Dempet",
        "MAX_VOLUME": "Maksimalt volum",
        "RTC_MODE_OFF": "AEC av",
        "RTC_MODE_ON": "AEC pÃ¥",
        "PLEASE_WAIT": "Vennligst vent...",
        "FOUND_NEW_ASSETS": "Fant nye ressurser: %s",
        "DOWNLOAD_ASSETS_FAILED": "Nedlasting av ressurser mislyktes",
        "LOADING_ASSETS": "Laster ressurser...",
        "HELLO_MY_FRIEND": "Hei, min venn!",
        "FLIGHT_MODE_OFF": "Flymodus er av",
        "FLIGHT_MODE_ON": "Flymodus er pÃ¥",
        "MODEM_INIT_ERROR": "Modeminitialisering mislyktes"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "nl-NL"
    },
    "strings": {
        "WARNING": "Waarschuwing",
        "INFO": "Informatie",
        "ERROR": "Fout",
        "VERSION": "Versie ",
        "LOADING_PROTOCOL": "Inloggen...",
        "INITIALIZING": "Initialiseren...",
        "PIN_ERROR": "Plaats een SIM-kaart",
        "REG_ERROR": "Geen toegang tot netwerk, controleer de SIM-kaartstatus",
        "DETECTING_MODULE": "Module detecteren...",
        "REGISTERING_NETWORK": "Wachten op netwerk...",
        "CHECKING_NEW_VERSION": "Controleren op nieuwe versie...",
        "CHECK_NEW_VERSION_FAILED": "Controle op nieuwe versie mislukt, opnieuw proberen over %d seconden: %s",
        "SWITCH_TO_WIFI_NETWORK": "Overschakelen naar Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Overschakelen naar 4G...",
        "STANDBY": "Stand-by",
        "CONNECT_TO": "Verbinden met ",
        "CONNECTING": "Verbinden...",
        "CONNECTION_SUCCESSFUL": "Verbinding geslaagd",
        "CONNECTED_TO": "Verbonden met ",
        "LISTENING": "Luisteren...",
        "SPEAKING": "Spreken...",
        "SERVER_NOT_FOUND": "Zoeken naar beschikbare service",
        "SERVER_NOT_CONNECTED": "Kan niet verbinden met service, probeer later opnieuw",
        "SERVER_TIMEOUT": "Timeout bij wachten op reactie",
        "SERVER_ERROR": "Verzenden mislukt, controleer het netwerk",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Configuratie-URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi-configuratiemodus",
        "ENTERING_WIFI_CONFIG_MODE": "Wi-Fi-configuratiemodus openen...",
        "SCANNING_WIFI": "Wi-Fi scannen...",
        "NEW_VERSION": "Nieuwe versie ",
        "OTA_UPGRADE": "OTA-upgrade",
        "UPGRADING": "Systeem wordt geüpgraded...",
        "UPGRADE_FAILED": "Upgrade mislukt",
        "ACTIVATION": "Activering",
        "BATTERY_LOW": "Batterij bijna leeg",
        "BATTERY_CHARGING": "Opladen",
        "BATTERY_FULL": "Batterij vol",
        "BATTERY_NEED_CHARGE": "Batterij bijna leeg, laad op",
        "VOLUME": "Volume ",
        "MUTED": "Gedempt",
        "MAX_VOLUME": "Maximaal volume",
        "RTC_MODE_OFF": "AEC uit",
        "RTC_MODE_ON": "AEC aan",
        "PLEASE_WAIT": "Een ogenblik geduld...",
        "FOUND_NEW_ASSETS": "Nieuwe bronnen gevonden: %s",
        "DOWNLOAD_ASSETS_FAILED": "Downloaden van bronnen mislukt",
        "LOADING_ASSETS": "Bronnen laden...",
        "HELLO_MY_FRIEND": "Hallo, mijn vriend!",
        "FLIGHT_MODE_OFF": "Vliegtuigmodus is uitgeschakeld",
        "FLIGHT_MODE_ON": "Vliegtuigmodus is ingeschakeld",
        "MODEM_INIT_ERROR": "Modeminitialisatie mislukt"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "pl-PL"
    },
    "strings": {
        "WARNING": "Ostrzeżenie",
        "INFO": "Informacja",
        "ERROR": "Błąd",
        "VERSION": "Wersja ",
        "LOADING_PROTOCOL": "Łączenie z serwerem...",
        "INITIALIZING": "Inicjalizacja...",
        "PIN_ERROR": "Proszę włożyć kartę SIM",
        "REG_ERROR": "Nie można uzyskać dostępu do sieci, sprawdź stan karty danych",
        "DETECTING_MODULE": "Wykrywanie modułu...",
        "REGISTERING_NETWORK": "Oczekiwanie na sieć...",
        "CHECKING_NEW_VERSION": "Sprawdzanie nowej wersji...",
        "CHECK_NEW_VERSION_FAILED": "Sprawdzanie nowej wersji nie powiodło się, ponowna próba za %d sekund: %s",
        "SWITCH_TO_WIFI_NETWORK": "Przełączanie na Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Przełączanie na 4G...",
        "STANDBY": "Gotowość",
        "CONNECT_TO": "Połącz z ",
        "CONNECTING": "Łączenie...",
        "CONNECTED_TO": "Połączono z ",
        "LISTENING": "Słuchanie...",
        "SPEAKING": "Mówienie...",
        "SERVER_NOT_FOUND": "Szukanie dostępnej usługi",
        "SERVER_NOT_CONNECTED": "Nie można połączyć się z usługą, spróbuj ponownie później",
        "SERVER_TIMEOUT": "Przekroczono czas oczekiwania na odpowiedź",
        "SERVER_ERROR": "Wysyłanie nie powiodło się, sprawdź sieć",
        "CONNECT_TO_HOTSPOT": "Podłącz telefon do hotspotu ",
        "ACCESS_VIA_BROWSER": ",dostęp przez przeglądarkę ",
        "WIFI_CONFIG_MODE": "Tryb konfiguracji sieci",
        "ENTERING_WIFI_CONFIG_MODE": "Wchodzenie w tryb konfiguracji sieci...",
        "SCANNING_WIFI": "Skanowanie Wi-Fi...",
        "NEW_VERSION": "Nowa wersja ",
        "OTA_UPGRADE": "Aktualizacja OTA",
        "UPGRADING": "Aktualizacja systemu...",
        "UPGRADE_FAILED": "Aktualizacja nie powiodła się",
        "ACTIVATION": "Aktywacja urządzenia",
        "BATTERY_LOW": "Niski poziom baterii",
        "BATTERY_CHARGING": "Ładowanie",
        "BATTERY_FULL": "Bateria pełna",
        "BATTERY_NEED_CHARGE": "Niski poziom baterii, proszę naładować",
        "VOLUME": "Głośność ",
        "MUTED": "Wyciszony",
        "MAX_VOLUME": "Maksymalna głośność",
        "RTC_MODE_OFF": "AEC wyłączony",
        "RTC_MODE_ON": "AEC włączony",
        "DOWNLOAD_ASSETS_FAILED": "Nie udało się pobrać zasobów",
        "LOADING_ASSETS": "Ładowanie zasobów...",
        "PLEASE_WAIT": "Proszę czekać...",
        "FOUND_NEW_ASSETS": "Znaleziono nowe zasoby: %s",
        "HELLO_MY_FRIEND": "Cześć, mój przyjacielu!",
        "CONNECTION_SUCCESSFUL": "Połączenie udane",
        "FLIGHT_MODE_OFF": "Tryb samolotowy jest wyłączony",
        "FLIGHT_MODE_ON": "Tryb samolotowy jest włączony",
        "MODEM_INIT_ERROR": "Inicjalizacja modemu nie powiodła się"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "pt-PT"
    },
    "strings": {
        "WARNING": "Aviso",
        "INFO": "Informação",
        "ERROR": "Erro",
        "VERSION": "Versão ",
        "LOADING_PROTOCOL": "Ligando ao servidor...",
        "INITIALIZING": "A inicializar...",
        "PIN_ERROR": "Por favor insira o cartão SIM",
        "REG_ERROR": "Não Ã© possível aceder Ã  rede, verifique o estado do cartão de dados",
        "DETECTING_MODULE": "A detectar módulo...",
        "REGISTERING_NETWORK": "À espera da rede...",
        "CHECKING_NEW_VERSION": "A verificar nova versão...",
        "CHECK_NEW_VERSION_FAILED": "Falha na verificação de nova versão, nova tentativa em %d segundos: %s",
        "SWITCH_TO_WIFI_NETWORK": "A mudar para Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "A mudar para 4G...",
        "STANDBY": "Em espera",
        "CONNECT_TO": "Ligar a ",
        "CONNECTING": "A ligar...",
        "CONNECTED_TO": "Ligado a ",
        "LISTENING": "A escutar...",
        "SPEAKING": "A falar...",
        "SERVER_NOT_FOUND": "A procurar serviço disponível",
        "SERVER_NOT_CONNECTED": "Não Ã© possível ligar ao serviço, tente mais tarde",
        "SERVER_TIMEOUT": "Tempo limite de resposta",
        "SERVER_ERROR": "Falha no envio, verifique a rede",
        "CONNECT_TO_HOTSPOT": "Ligue o telefone ao hotspot ",
        "ACCESS_VIA_BROWSER": ",aceder através do navegador ",
        "WIFI_CONFIG_MODE": "Modo de configuração de rede",
        "ENTERING_WIFI_CONFIG_MODE": "A entrar no modo de configuração de rede...",
        "SCANNING_WIFI": "A procurar Wi-Fi...",
        "NEW_VERSION": "Nova versão ",
        "OTA_UPGRADE": "Atualização OTA",
        "UPGRADING": "A atualizar sistema...",
        "UPGRADE_FAILED": "Atualização falhada",
        "ACTIVATION": "Ativação do dispositivo",
        "BATTERY_LOW": "Bateria fraca",
        "BATTERY_CHARGING": "A carregar",
        "BATTERY_FULL": "Bateria cheia",
        "BATTERY_NEED_CHARGE": "Bateria fraca, por favor carregue",
        "VOLUME": "Volume ",
        "MUTED": "Silenciado",
        "MAX_VOLUME": "Volume máximo",
        "RTC_MODE_OFF": "AEC desligado",
        "RTC_MODE_ON": "AEC ligado",
        "DOWNLOAD_ASSETS_FAILED": "Falha ao descarregar recursos",
        "LOADING_ASSETS": "A carregar recursos...",
        "PLEASE_WAIT": "Por favor aguarde...",
        "FOUND_NEW_ASSETS": "Encontrados novos recursos: %s",
        "HELLO_MY_FRIEND": "Olá, meu amigo!",
        "CONNECTION_SUCCESSFUL": "Ligação bem-sucedida",
        "FLIGHT_MODE_OFF": "O modo avião está desativado",
        "FLIGHT_MODE_ON": "O modo avião está ativado",
        "MODEM_INIT_ERROR": "Falha na inicialização do modem"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ro-RO"
    },
    "strings": {
        "WARNING": "Avertisment",
        "INFO": "Informație",
        "ERROR": "Eroare",
        "VERSION": "Versiune ",
        "LOADING_PROTOCOL": "Se conectează la server...",
        "INITIALIZING": "Se inițializează...",
        "PIN_ERROR": "Vă rugăm să introduceți cardul SIM",
        "REG_ERROR": "Nu se poate accesa rețeaua, verificați starea cardului de date",
        "DETECTING_MODULE": "Se detectează modulul...",
        "REGISTERING_NETWORK": "Se așteaptă rețeaua...",
        "CHECKING_NEW_VERSION": "Se verifică versiunea nouă...",
        "CHECK_NEW_VERSION_FAILED": "Verificarea versiunii noi a eșuat, se reîncearcă Ã®n %d secunde: %s",
        "SWITCH_TO_WIFI_NETWORK": "Se comută la Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Se comută la 4G...",
        "STANDBY": "În așteptare",
        "CONNECT_TO": "Conectare la ",
        "CONNECTING": "Se conectează...",
        "CONNECTED_TO": "Conectat la ",
        "LISTENING": "Se ascultă...",
        "SPEAKING": "Se vorbește...",
        "SERVER_NOT_FOUND": "Se caută serviciul disponibil",
        "SERVER_NOT_CONNECTED": "Nu se poate conecta la serviciu, Ã®ncercați mai târziu",
        "SERVER_TIMEOUT": "Timpul de răspuns a expirat",
        "SERVER_ERROR": "Trimiterea a eșuat, verificați rețeaua",
        "CONNECT_TO_HOTSPOT": "Conectați telefonul la hotspot ",
        "ACCESS_VIA_BROWSER": ",accesați prin browser ",
        "WIFI_CONFIG_MODE": "Modul de configurare rețea",
        "ENTERING_WIFI_CONFIG_MODE": "Se intră Ã®n modul de configurare rețea...",
        "SCANNING_WIFI": "Se scanează Wi-Fi...",
        "NEW_VERSION": "Versiune nouă ",
        "OTA_UPGRADE": "Actualizare OTA",
        "UPGRADING": "Se actualizează sistemul...",
        "UPGRADE_FAILED": "Actualizarea a eșuat",
        "ACTIVATION": "Activarea dispozitivului",
        "BATTERY_LOW": "Baterie scăzută",
        "BATTERY_CHARGING": "Se Ã®ncarcă",
        "BATTERY_FULL": "Baterie plină",
        "BATTERY_NEED_CHARGE": "Baterie scăzută, vă rugăm să Ã®ncărcați",
        "VOLUME": "Volum ",
        "MUTED": "Silențios",
        "MAX_VOLUME": "Volum maxim",
        "RTC_MODE_OFF": "AEC oprit",
        "RTC_MODE_ON": "AEC pornit",
        "DOWNLOAD_ASSETS_FAILED": "Eșec la descărcarea resurselor",
        "LOADING_ASSETS": "Se Ã®ncarcă resursele...",
        "PLEASE_WAIT": "Vă rugăm să așteptați...",
        "FOUND_NEW_ASSETS": "S-au găsit resurse noi: %s",
        "HELLO_MY_FRIEND": "Salut, prietenul meu!",
        "CONNECTION_SUCCESSFUL": "Conexiune reușită",
        "FLIGHT_MODE_OFF": "Modul avion este dezactivat",
        "FLIGHT_MODE_ON": "Modul avion este activat",
        "MODEM_INIT_ERROR": "Inițializarea modemului a eșuat"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "ru-RU"
    },
    "strings": {
        "WARNING": "Предупреждение",
        "INFO": "Информация",
        "ERROR": "Ошибка",
        "VERSION": "Версия ",
        "LOADING_PROTOCOL": "Подключение Ðº ÑÐµÑ€Ð²ÐµÑ€Ñƒ...",
        "INITIALIZING": "Инициализация...",
        "PIN_ERROR": "Пожалуйста, Ð²ÑÑ‚авьте SIM-карту",
        "REG_ERROR": "Невозможно Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ься Ðº ÑÐµÑ‚и, Ð¿Ñ€Ð¾Ð²ÐµÑ€ÑŒÑ‚е ÑÐ¾ÑÑ‚ояние ÐºÐ°Ñ€Ñ‚Ñ‹ Ð´Ð°Ð½Ð½Ñ‹Ñ…",
        "DETECTING_MODULE": "Обнаружение Ð¼Ð¾Ð´ÑƒÐ»Ñ...",
        "REGISTERING_NETWORK": "Ожидание ÑÐµÑ‚и...",
        "CHECKING_NEW_VERSION": "Проверка Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ€ÑÐ¸Ð¸...",
        "CHECK_NEW_VERSION_FAILED": "Ошибка Ð¿Ñ€Ð¾Ð²ÐµÑ€ÐºÐ¸ Ð½Ð¾Ð²Ð¾Ð¹ Ð²ÐµÑ€ÑÐ¸Ð¸, Ð¿Ð¾Ð²Ñ‚ор Ñ‡ÐµÑ€ÐµÐ· %d ÑÐµÐºÑƒÐ½Ð´: %s",
        "SWITCH_TO_WIFI_NETWORK": "Переключение Ð½Ð° Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Переключение Ð½Ð° 4G...",
        "STANDBY": "Ожидание",
        "CONNECT_TO": "Подключение Ðº ",
        "CONNECTING": "Подключение...",
        "CONNECTED_TO": "Подключено Ðº ",
        "LISTENING": "Прослушивание...",
        "SPEAKING": "Говорение...",
        "SERVER_NOT_FOUND": "Поиск Ð´Ð¾ÑÑ‚упного ÑÐµÑ€Ð²Ð¸ÑÐ°",
        "SERVER_NOT_CONNECTED": "Невозможно Ð¿Ð¾Ð´ÐºÐ»ÑŽÑ‡Ð¸Ñ‚ься Ðº ÑÐµÑ€Ð²Ð¸ÑÑƒ, Ð¿Ð¾Ð¿Ñ€Ð¾Ð±ÑƒÐ¹Ñ‚е Ð¿Ð¾Ð·Ð¶Ðµ",
        "SERVER_TIMEOUT": "Тайм-аут Ð¾Ñ‚вета",
        "SERVER_ERROR": "Ошибка Ð¾Ñ‚правки, Ð¿Ñ€Ð¾Ð²ÐµÑ€ÑŒÑ‚е ÑÐµÑ‚ÑŒ",
        "CONNECT_TO_HOTSPOT": "Подключите Ñ‚елефон Ðº Ñ‚очке Ð´Ð¾ÑÑ‚упа ",
        "ACCESS_VIA_BROWSER": ",доступ Ñ‡ÐµÑ€ÐµÐ· Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€ ",
        "WIFI_CONFIG_MODE": "Режим Ð½Ð°ÑÑ‚ройки ÑÐµÑ‚и",
        "ENTERING_WIFI_CONFIG_MODE": "Вход Ð² Ñ€ÐµÐ¶Ð¸Ð¼ Ð½Ð°ÑÑ‚ройки ÑÐµÑ‚и...",
        "SCANNING_WIFI": "Сканирование Wi-Fi...",
        "NEW_VERSION": "Новая Ð²ÐµÑ€ÑÐ¸Ñ ",
        "OTA_UPGRADE": "Обновление OTA",
        "UPGRADING": "Обновление ÑÐ¸ÑÑ‚емы...",
        "UPGRADE_FAILED": "Обновление Ð½Ðµ ÑƒÐ´Ð°Ð»Ð¾ÑÑŒ",
        "ACTIVATION": "Активация ÑƒÑÑ‚ройства",
        "BATTERY_LOW": "Низкий Ð·Ð°Ñ€ÑÐ´ Ð±Ð°Ñ‚ареи",
        "BATTERY_CHARGING": "Зарядка",
        "BATTERY_FULL": "Батарея Ð¿Ð¾Ð»Ð½Ð°Ñ",
        "BATTERY_NEED_CHARGE": "Низкий Ð·Ð°Ñ€ÑÐ´, Ð¿Ð¾Ð¶Ð°Ð»ÑƒÐ¹ÑÑ‚а, Ð·Ð°Ñ€ÑÐ´Ð¸Ñ‚е",
        "VOLUME": "Громкость ",
        "MUTED": "Звук Ð¾Ñ‚ключен",
        "MAX_VOLUME": "Максимальная Ð³Ñ€Ð¾Ð¼ÐºÐ¾ÑÑ‚ÑŒ",
        "RTC_MODE_OFF": "AEC Ð²Ñ‹ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "RTC_MODE_ON": "AEC Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "DOWNLOAD_ASSETS_FAILED": "Не ÑƒÐ´Ð°Ð»Ð¾ÑÑŒ Ð·Ð°Ð³Ñ€ÑƒÐ·Ð¸Ñ‚ÑŒ Ñ€ÐµÑÑƒÑ€ÑÑ‹",
        "LOADING_ASSETS": "Загрузка Ñ€ÐµÑÑƒÑ€ÑÐ¾Ð²...",
        "PLEASE_WAIT": "Пожалуйста, Ð¿Ð¾Ð´Ð¾Ð¶Ð´Ð¸Ñ‚е...",
        "FOUND_NEW_ASSETS": "Найдены Ð½Ð¾Ð²Ñ‹Ðµ Ñ€ÐµÑÑƒÑ€ÑÑ‹: %s",
        "HELLO_MY_FRIEND": "Привет, Ð¼Ð¾Ð¹ Ð´Ñ€ÑƒÐ³!",
        "CONNECTION_SUCCESSFUL": "Подключение ÑƒÑÐ¿ÐµÑˆÐ½Ð¾",
        "FLIGHT_MODE_OFF": "Режим Ð¿Ð¾Ð»ÐµÑ‚а Ð²Ñ‹ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "FLIGHT_MODE_ON": "Режим Ð¿Ð¾Ð»ÐµÑ‚а Ð²ÐºÐ»ÑŽÑ‡ÐµÐ½",
        "MODEM_INIT_ERROR": "Ошибка Ð¸Ð½Ð¸Ñ†Ð¸Ð°Ð»Ð¸Ð·Ð°Ñ†Ð¸Ð¸ Ð¼Ð¾Ð´ÐµÐ¼Ð°"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "sk-SK"
    },
    "strings": {
        "WARNING": "Upozornenie",
        "INFO": "Informácie",
        "ERROR": "Chyba",
        "VERSION": "Verzia ",
        "LOADING_PROTOCOL": "Prihlasovanie...",
        "INITIALIZING": "Inicializácia...",
        "PIN_ERROR": "Vložte SIM kartu",
        "REG_ERROR": "Nie je možné pristúpiÅ¥ k sieti, skontrolujte stav SIM karty",
        "DETECTING_MODULE": "ZisÅ¥ovanie modulu...",
        "REGISTERING_NETWORK": "Čakanie na sieÅ¥...",
        "CHECKING_NEW_VERSION": "Kontrola novej verzie...",
        "CHECK_NEW_VERSION_FAILED": "Kontrola novej verzie zlyhala, opakuje sa o %d sekúnd: %s",
        "SWITCH_TO_WIFI_NETWORK": "Prepínanie na Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Prepínanie na 4G...",
        "STANDBY": "Pohotovostný režim",
        "CONNECT_TO": "Pripojenie k ",
        "CONNECTING": "Pripája sa...",
        "CONNECTION_SUCCESSFUL": "Pripojenie ÃºspeÅ¡né",
        "CONNECTED_TO": "Pripojené k ",
        "LISTENING": "Načúvanie...",
        "SPEAKING": "Hovorenie...",
        "SERVER_NOT_FOUND": "Hľadanie dostupnej služby",
        "SERVER_NOT_CONNECTED": "Nie je možné pripojiÅ¥ sa k službe, skúste to neskôr",
        "SERVER_TIMEOUT": "Časový limit Äakania na odpoveď vyprÅ¡al",
        "SERVER_ERROR": "Odosielanie zlyhalo, skontrolujte sieÅ¥",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Konfiguračná URL: ",
        "WIFI_CONFIG_MODE": "Režim konfigurácie Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Vstup do režimu konfigurácie Wi-Fi...",
        "SCANNING_WIFI": "Skenovanie Wi-Fi...",
        "NEW_VERSION": "Nová verzia ",
        "OTA_UPGRADE": "OTA aktualizácia",
        "UPGRADING": "Systém sa aktualizuje...",
        "UPGRADE_FAILED": "Aktualizácia zlyhala",
        "ACTIVATION": "Aktivácia",
        "BATTERY_LOW": "Slabá batéria",
        "BATTERY_CHARGING": "Nabíjanie",
        "BATTERY_FULL": "Batéria plná",
        "BATTERY_NEED_CHARGE": "Slabá batéria, nabite ju",
        "VOLUME": "HlasitosÅ¥ ",
        "MUTED": "Stíšené",
        "MAX_VOLUME": "Maximálna hlasitosÅ¥",
        "RTC_MODE_OFF": "AEC vypnuté",
        "RTC_MODE_ON": "AEC zapnuté",
        "PLEASE_WAIT": "Počkajte prosím...",
        "FOUND_NEW_ASSETS": "Nájdené nové zdroje: %s",
        "DOWNLOAD_ASSETS_FAILED": "SÅ¥ahovanie zdrojov zlyhalo",
        "LOADING_ASSETS": "Načítavanie zdrojov...",
        "HELLO_MY_FRIEND": "Ahoj, môj priateľ!",
        "FLIGHT_MODE_OFF": "Letecký režim je vypnutý",
        "FLIGHT_MODE_ON": "Letecký režim je zapnutý",
        "MODEM_INIT_ERROR": "Chyba inicializácie modemu"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "sl-SI"
    },
    "strings": {
        "WARNING": "Opozorilo",
        "INFO": "Informacije",
        "ERROR": "Napaka",
        "VERSION": "Različica ",
        "LOADING_PROTOCOL": "Prijava...",
        "INITIALIZING": "Inicializacija...",
        "PIN_ERROR": "Vstavite SIM kartico",
        "REG_ERROR": "Ni mogoče dostopati do omrežja, preverite stanje SIM kartice",
        "DETECTING_MODULE": "Zaznavanje modula...",
        "REGISTERING_NETWORK": "Čakanje na omrežje...",
        "CHECKING_NEW_VERSION": "Preverjanje nove različice...",
        "CHECK_NEW_VERSION_FAILED": "Preverjanje nove različice ni uspelo, ponovni poskus Äez %d sekund: %s",
        "SWITCH_TO_WIFI_NETWORK": "Preklop na Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Preklop na 4G...",
        "STANDBY": "Pripravljenost",
        "CONNECT_TO": "Poveži se z ",
        "CONNECTING": "Povezovanje...",
        "CONNECTION_SUCCESSFUL": "Povezava uspeÅ¡na",
        "CONNECTED_TO": "Povezano z ",
        "LISTENING": "PosluÅ¡anje...",
        "SPEAKING": "Govorjenje...",
        "SERVER_NOT_FOUND": "Iskanje razpoložljive storitve",
        "SERVER_NOT_CONNECTED": "Ni se mogoče povezati s storitvijo, poskusite kasneje",
        "SERVER_TIMEOUT": "Časovna omejitev Äakanja na odgovor",
        "SERVER_ERROR": "PoÅ¡iljanje ni uspelo, preverite omrežje",
        "CONNECT_TO_HOTSPOT": "Dostopna točka: ",
        "ACCESS_VIA_BROWSER": " Konfiguracijski URL: ",
        "WIFI_CONFIG_MODE": "Način konfiguracije Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Vstop v način konfiguracije Wi-Fi...",
        "SCANNING_WIFI": "Skeniranje Wi-Fi...",
        "NEW_VERSION": "Nova različica ",
        "OTA_UPGRADE": "OTA nadgradnja",
        "UPGRADING": "Sistem se nadgrajuje...",
        "UPGRADE_FAILED": "Nadgradnja ni uspela",
        "ACTIVATION": "Aktivacija",
        "BATTERY_LOW": "Nizka baterija",
        "BATTERY_CHARGING": "Polnjenje",
        "BATTERY_FULL": "Baterija polna",
        "BATTERY_NEED_CHARGE": "Nizka baterija, napolnite",
        "VOLUME": "Glasnost ",
        "MUTED": "UtiÅ¡ano",
        "MAX_VOLUME": "Največja glasnost",
        "RTC_MODE_OFF": "AEC izklopljen",
        "RTC_MODE_ON": "AEC vklopljen",
        "PLEASE_WAIT": "Počakajte...",
        "FOUND_NEW_ASSETS": "Najdeni novi viri: %s",
        "DOWNLOAD_ASSETS_FAILED": "Prenos virov ni uspel",
        "LOADING_ASSETS": "Nalaganje virov...",
        "HELLO_MY_FRIEND": "Pozdravljeni, moj prijatelj!",
        "FLIGHT_MODE_OFF": "Način leta je izklopljen",
        "FLIGHT_MODE_ON": "Način leta je vklopljen",
        "MODEM_INIT_ERROR": "Inicializacija modema ni uspela"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sr-RS/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "sr-RS"
    },
    "strings": {
        "WARNING": "Упозорење",
        "INFO": "Информације",
        "ERROR": "Грешка",
        "VERSION": "Верзија ",
        "LOADING_PROTOCOL": "Пријављивање...",
        "INITIALIZING": "Иницијализација...",
        "PIN_ERROR": "Молимо ÑƒÐ±Ð°Ñ†Ð¸Ñ‚е SIM ÐºÐ°Ñ€Ñ‚ицу",
        "REG_ERROR": "Није Ð¼Ð¾Ð³ÑƒÑ›Ðµ Ð¿Ñ€Ð¸ÑÑ‚упити Ð¼Ñ€ÐµÐ¶Ð¸, Ð¿Ñ€Ð¾Ð²ÐµÑ€Ð¸Ñ‚е ÑÑ‚атус SIM ÐºÐ°Ñ€Ñ‚ице",
        "DETECTING_MODULE": "Откривање Ð¼Ð¾Ð´ÑƒÐ»Ð°...",
        "REGISTERING_NETWORK": "Чекање Ð¼Ñ€ÐµÐ¶Ðµ...",
        "CHECKING_NEW_VERSION": "Провера Ð½Ð¾Ð²Ðµ Ð²ÐµÑ€Ð·Ð¸Ñ˜Ðµ...",
        "CHECK_NEW_VERSION_FAILED": "Провера Ð½Ð¾Ð²Ðµ Ð²ÐµÑ€Ð·Ð¸Ñ˜Ðµ Ð½Ð¸Ñ˜Ðµ ÑƒÑÐ¿ÐµÐ»Ð°, Ð¿Ð¾ÐºÑƒÑˆÐ°Ñ›Ðµ ÑÐµ Ð¿Ð¾Ð½Ð¾Ð²Ð¾ Ð·Ð° %d ÑÐµÐºÑƒÐ½Ð´Ð¸: %s",
        "SWITCH_TO_WIFI_NETWORK": "Пребацивање Ð½Ð° Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Пребацивање Ð½Ð° 4G...",
        "STANDBY": "Спремност",
        "CONNECT_TO": "Повежи ÑÐµ ÑÐ° ",
        "CONNECTING": "Повезивање...",
        "CONNECTION_SUCCESSFUL": "Успешно Ð¿Ð¾Ð²ÐµÐ·Ð¸Ð²Ð°ÑšÐµ",
        "CONNECTED_TO": "Повезано ÑÐ° ",
        "LISTENING": "Слушање...",
        "SPEAKING": "Говорење...",
        "SERVER_NOT_FOUND": "Тражење Ð´Ð¾ÑÑ‚упне ÑƒÑÐ»ÑƒÐ³Ðµ",
        "SERVER_NOT_CONNECTED": "Није Ð¼Ð¾Ð³ÑƒÑ›Ðµ Ð¿Ð¾Ð²ÐµÐ·Ð°Ñ‚и ÑÐµ ÑÐ° ÑƒÑÐ»ÑƒÐ³Ð¾Ð¼, Ð¿Ð¾ÐºÑƒÑˆÐ°Ñ˜Ñ‚е ÐºÐ°ÑÐ½Ð¸Ñ˜Ðµ",
        "SERVER_TIMEOUT": "Истекло Ð²Ñ€ÐµÐ¼Ðµ Ñ‡ÐµÐºÐ°ÑšÐ° Ð¾Ð´Ð³Ð¾Ð²Ð¾Ñ€Ð°",
        "SERVER_ERROR": "Слање Ð½Ð¸Ñ˜Ðµ ÑƒÑÐ¿ÐµÐ»Ð¾, Ð¿Ñ€Ð¾Ð²ÐµÑ€Ð¸Ñ‚е Ð¼Ñ€ÐµÐ¶Ñƒ",
        "CONNECT_TO_HOTSPOT": "Приступна Ñ‚ачка: ",
        "ACCESS_VIA_BROWSER": " URL Ð·Ð° ÐºÐ¾Ð½Ñ„игурацију: ",
        "WIFI_CONFIG_MODE": "Режим ÐºÐ¾Ð½Ñ„игурације Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Улазак Ñƒ Ñ€ÐµÐ¶Ð¸Ð¼ ÐºÐ¾Ð½Ñ„игурације Wi-Fi...",
        "SCANNING_WIFI": "Скенирање Wi-Fi...",
        "NEW_VERSION": "Нова Ð²ÐµÑ€Ð·Ð¸Ñ˜Ð° ",
        "OTA_UPGRADE": "OTA Ð½Ð°Ð´Ð¾Ð³Ñ€Ð°Ð´ÑšÐ°",
        "UPGRADING": "Систем ÑÐµ Ð½Ð°Ð´Ð¾Ð³Ñ€Ð°Ñ’ује...",
        "UPGRADE_FAILED": "Надоградња Ð½Ð¸Ñ˜Ðµ ÑƒÑÐ¿ÐµÐ»Ð°",
        "ACTIVATION": "Активација",
        "BATTERY_LOW": "Слаба Ð±Ð°Ñ‚ерија",
        "BATTERY_CHARGING": "Пуњење",
        "BATTERY_FULL": "Батерија Ð¿ÑƒÐ½Ð°",
        "BATTERY_NEED_CHARGE": "Слаба Ð±Ð°Ñ‚ерија, Ð¼Ð¾Ð»Ð¸Ð¼Ð¾ Ð½Ð°Ð¿ÑƒÐ½Ð¸Ñ‚е",
        "VOLUME": "Јачина Ð·Ð²ÑƒÐºÐ° ",
        "MUTED": "Утишано",
        "MAX_VOLUME": "Максимална Ñ˜Ð°Ñ‡Ð¸Ð½Ð° Ð·Ð²ÑƒÐºÐ°",
        "RTC_MODE_OFF": "AEC Ð¸ÑÐºÑ™ÑƒÑ‡ÐµÐ½",
        "RTC_MODE_ON": "AEC ÑƒÐºÑ™ÑƒÑ‡ÐµÐ½",
        "PLEASE_WAIT": "Молимо ÑÐ°Ñ‡ÐµÐºÐ°Ñ˜Ñ‚е...",
        "FOUND_NEW_ASSETS": "Пронађени Ð½Ð¾Ð²Ð¸ Ñ€ÐµÑÑƒÑ€ÑÐ¸: %s",
        "DOWNLOAD_ASSETS_FAILED": "Преузимање Ñ€ÐµÑÑƒÑ€ÑÐ° Ð½Ð¸Ñ˜Ðµ ÑƒÑÐ¿ÐµÐ»Ð¾",
        "LOADING_ASSETS": "Учитавање Ñ€ÐµÑÑƒÑ€ÑÐ°...",
        "HELLO_MY_FRIEND": "Здраво, Ð¿Ñ€Ð¸Ñ˜Ð°Ñ‚ељу!",
        "FLIGHT_MODE_OFF": "Режим Ð»ÐµÑ‚а Ñ˜Ðµ Ð¸ÑÐºÑ™ÑƒÑ‡ÐµÐ½",
        "FLIGHT_MODE_ON": "Режим Ð»ÐµÑ‚а Ñ˜Ðµ ÑƒÐºÑ™ÑƒÑ‡ÐµÐ½",
        "MODEM_INIT_ERROR": "Иницијализација Ð¼Ð¾Ð´ÐµÐ¼Ð° Ð½Ð¸Ñ˜Ðµ ÑƒÑÐ¿ÐµÐ»Ð°"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "sv-SE"
    },
    "strings": {
        "WARNING": "Varning",
        "INFO": "Information",
        "ERROR": "Fel",
        "VERSION": "Version ",
        "LOADING_PROTOCOL": "Loggar in...",
        "INITIALIZING": "Initierar...",
        "PIN_ERROR": "Vänligen sätt in SIM-kort",
        "REG_ERROR": "Kan inte komma Ã¥t nätverket, kontrollera SIM-kortets status",
        "DETECTING_MODULE": "Detekterar modul...",
        "REGISTERING_NETWORK": "Väntar pÃ¥ nätverk...",
        "CHECKING_NEW_VERSION": "Kontrollerar ny version...",
        "CHECK_NEW_VERSION_FAILED": "Kontroll av ny version misslyckades, försöker igen om %d sekunder: %s",
        "SWITCH_TO_WIFI_NETWORK": "Byter till Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Byter till 4G...",
        "STANDBY": "Standby",
        "CONNECT_TO": "Anslut till ",
        "CONNECTING": "Ansluter...",
        "CONNECTION_SUCCESSFUL": "Anslutningen lyckades",
        "CONNECTED_TO": "Ansluten till ",
        "LISTENING": "Lyssnar...",
        "SPEAKING": "Talar...",
        "SERVER_NOT_FOUND": "Söker efter tillgänglig tjänst",
        "SERVER_NOT_CONNECTED": "Kan inte ansluta till tjänsten, försök igen senare",
        "SERVER_TIMEOUT": "Timeout vid väntan pÃ¥ svar",
        "SERVER_ERROR": "Skickandet misslyckades, kontrollera nätverket",
        "CONNECT_TO_HOTSPOT": "Hotspot: ",
        "ACCESS_VIA_BROWSER": " Konfigurations-URL: ",
        "WIFI_CONFIG_MODE": "Wi-Fi-konfigurationsläge",
        "ENTERING_WIFI_CONFIG_MODE": "GÃ¥r in i Wi-Fi-konfigurationsläge...",
        "SCANNING_WIFI": "Skannar Wi-Fi...",
        "NEW_VERSION": "Ny version ",
        "OTA_UPGRADE": "OTA-uppgradering",
        "UPGRADING": "Systemet uppgraderas...",
        "UPGRADE_FAILED": "Uppgraderingen misslyckades",
        "ACTIVATION": "Aktivering",
        "BATTERY_LOW": "LÃ¥gt batteri",
        "BATTERY_CHARGING": "Laddar",
        "BATTERY_FULL": "Batteriet fullt",
        "BATTERY_NEED_CHARGE": "LÃ¥gt batteri, vänligen ladda",
        "VOLUME": "Volym ",
        "MUTED": "Tystad",
        "MAX_VOLUME": "Maximal volym",
        "RTC_MODE_OFF": "AEC av",
        "RTC_MODE_ON": "AEC pÃ¥",
        "PLEASE_WAIT": "Vänligen vänta...",
        "FOUND_NEW_ASSETS": "Hittade nya resurser: %s",
        "DOWNLOAD_ASSETS_FAILED": "Nedladdning av resurser misslyckades",
        "LOADING_ASSETS": "Laddar resurser...",
        "HELLO_MY_FRIEND": "Hej, min vän!",
        "FLIGHT_MODE_OFF": "Flygläge Ã¤r av",
        "FLIGHT_MODE_ON": "Flygläge Ã¤r pÃ¥",
        "MODEM_INIT_ERROR": "Modeminitiering misslyckades"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "th-TH"
    },
    "strings": {
        "WARNING": "คำเตือน",
        "INFO": "ข้อมูล",
        "ERROR": "ข้อผิดพลาด",
        "VERSION": "เวอร์ชัน ",
        "LOADING_PROTOCOL": "กำลังเข้าสู่ระบบ...",
        "INITIALIZING": "กำลังเริ่มต้นระบบ...",
        "PIN_ERROR": "กรุณาใส่ซิมการ์ด",
        "REG_ERROR": "ไม่สามารถเข้าถึงเครือข่ายได้ à¸à¸£à¸¸à¸“าตรวจสอบสถานะซิมการ์ด",
        "DETECTING_MODULE": "กำลังตรวจจับโมดูล...",
        "REGISTERING_NETWORK": "กำลังรอเครือข่าย...",
        "CHECKING_NEW_VERSION": "กำลังตรวจสอบเวอร์ชันใหม่...",
        "CHECK_NEW_VERSION_FAILED": "การตรวจสอบเวอร์ชันใหม่ล้มเหลว à¸ˆà¸°à¸¥à¸­à¸‡à¹ƒà¸«à¸¡à¹ˆà¹ƒà¸™ %d à¸§à¸´à¸™à¸²à¸—ี: %s",
        "SWITCH_TO_WIFI_NETWORK": "กำลังเปลี่ยนเป็น Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "กำลังเปลี่ยนเป็น 4G...",
        "STANDBY": "พร้อม",
        "CONNECT_TO": "เชื่อมต่อกับ ",
        "CONNECTING": "กำลังเชื่อมต่อ...",
        "CONNECTION_SUCCESSFUL": "เชื่อมต่อสำเร็จ",
        "CONNECTED_TO": "เชื่อมต่อกับ ",
        "LISTENING": "กำลังฟัง...",
        "SPEAKING": "กำลังพูด...",
        "SERVER_NOT_FOUND": "กำลังค้นหาบริการที่ใช้งานได้",
        "SERVER_NOT_CONNECTED": "ไม่สามารถเชื่อมต่อกับบริการได้ à¸à¸£à¸¸à¸“าลองใหม่ในภายหลัง",
        "SERVER_TIMEOUT": "หมดเวลารอการตอบกลับ",
        "SERVER_ERROR": "การส่งข้อมูลล้มเหลว à¸à¸£à¸¸à¸“าตรวจสอบเครือข่าย",
        "CONNECT_TO_HOTSPOT": "ฮอตสปอต: ",
        "ACCESS_VIA_BROWSER": " URL à¸à¸²à¸£à¸•ั้งค่า: ",
        "WIFI_CONFIG_MODE": "โหมดการตั้งค่า Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "กำลังเข้าสู่โหมดการตั้งค่า Wi-Fi...",
        "SCANNING_WIFI": "กำลังสแกน Wi-Fi...",
        "NEW_VERSION": "เวอร์ชันใหม่ ",
        "OTA_UPGRADE": "การอัปเกรด OTA",
        "UPGRADING": "ระบบกำลังอัปเกรด...",
        "UPGRADE_FAILED": "การอัปเกรดล้มเหลว",
        "ACTIVATION": "การเปิดใช้งาน",
        "BATTERY_LOW": "แบตเตอรี่ต่ำ",
        "BATTERY_CHARGING": "กำลังชาร์จ",
        "BATTERY_FULL": "แบตเตอรี่เต็ม",
        "BATTERY_NEED_CHARGE": "แบตเตอรี่ต่ำ à¸à¸£à¸¸à¸“าชาร์จ",
        "VOLUME": "เสียง ",
        "MUTED": "ปิดเสียง",
        "MAX_VOLUME": "เสียงสูงสุด",
        "RTC_MODE_OFF": "ปิด AEC",
        "RTC_MODE_ON": "เปิด AEC",
        "DOWNLOAD_ASSETS_FAILED": "ดาวน์โหลดทรัพยากรล้มเหลว",
        "LOADING_ASSETS": "กำลังโหลดทรัพยากร...",
        "PLEASE_WAIT": "กรุณารอสักครู่...",
        "FOUND_NEW_ASSETS": "พบทรัพยากรใหม่: %s",
        "HELLO_MY_FRIEND": "สวัสดี à¹€à¸žà¸·à¹ˆà¸­à¸™à¸‚องฉัน!",
        "FLIGHT_MODE_OFF": "โหมดเครื่องบินปิดอยู่",
        "FLIGHT_MODE_ON": "โหมดเครื่องบินเปิดอยู่",
        "MODEM_INIT_ERROR": "การเริ่มต้นโมเด็มล้มเหลว"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "tr-TR"
    },
    "strings": {
        "WARNING": "Uyarı",
        "INFO": "Bilgi",
        "ERROR": "Hata",
        "VERSION": "Sürüm ",
        "LOADING_PROTOCOL": "Sunucuya bağlanıyor...",
        "INITIALIZING": "Başlatılıyor...",
        "PIN_ERROR": "Lütfen SIM kartı takın",
        "REG_ERROR": "Ağa erişilemiyor, veri kartı durumunu kontrol edin",
        "DETECTING_MODULE": "Modül algılanıyor...",
        "REGISTERING_NETWORK": "Ağ bekleniyor...",
        "CHECKING_NEW_VERSION": "Yeni sürüm kontrol ediliyor...",
        "CHECK_NEW_VERSION_FAILED": "Yeni sürüm kontrolü başarısız, %d saniye sonra tekrar denenecek: %s",
        "SWITCH_TO_WIFI_NETWORK": "Wi-Fi'ye geçiliyor...",
        "SWITCH_TO_4G_NETWORK": "4G'ye geçiliyor...",
        "STANDBY": "Bekleme",
        "CONNECT_TO": "Bağlan ",
        "CONNECTING": "Bağlanıyor...",
        "CONNECTED_TO": "Bağlandı ",
        "LISTENING": "Dinleniyor...",
        "SPEAKING": "Konuşuluyor...",
        "SERVER_NOT_FOUND": "Mevcut hizmet aranıyor",
        "SERVER_NOT_CONNECTED": "Hizmete bağlanılamıyor, lütfen daha sonra deneyin",
        "SERVER_TIMEOUT": "Yanıt zaman aşımı",
        "SERVER_ERROR": "Gönderme başarısız, ağı kontrol edin",
        "CONNECT_TO_HOTSPOT": "Telefonu hotspot'a bağlayın ",
        "ACCESS_VIA_BROWSER": ",tarayıcı Ã¼zerinden erişin ",
        "WIFI_CONFIG_MODE": "Ağ yapılandırma modu",
        "ENTERING_WIFI_CONFIG_MODE": "Ağ yapılandırma moduna giriliyor...",
        "SCANNING_WIFI": "Wi-Fi taranıyor...",
        "NEW_VERSION": "Yeni sürüm ",
        "OTA_UPGRADE": "OTA güncelleme",
        "UPGRADING": "Sistem güncelleniyor...",
        "UPGRADE_FAILED": "Güncelleme başarısız",
        "ACTIVATION": "Cihaz aktivasyonu",
        "BATTERY_LOW": "Pil düşük",
        "BATTERY_CHARGING": "Şarj oluyor",
        "BATTERY_FULL": "Pil dolu",
        "BATTERY_NEED_CHARGE": "Pil düşük, lütfen ÅŸarj edin",
        "VOLUME": "Ses ",
        "MUTED": "Sessiz",
        "MAX_VOLUME": "Maksimum ses",
        "RTC_MODE_OFF": "AEC kapalı",
        "RTC_MODE_ON": "AEC açık",
        "DOWNLOAD_ASSETS_FAILED": "Varlıklar indirilemedi",
        "LOADING_ASSETS": "Varlıklar yükleniyor...",
        "PLEASE_WAIT": "Lütfen bekleyin...",
        "FOUND_NEW_ASSETS": "Yeni varlıklar bulundu: %s",
        "HELLO_MY_FRIEND": "Merhaba, arkadaşım!",
        "CONNECTION_SUCCESSFUL": "Bağlantı başarılı",
        "FLIGHT_MODE_OFF": "Uçak modu kapalı",
        "FLIGHT_MODE_ON": "Uçak modu açık",
        "MODEM_INIT_ERROR": "Modem başlatma hatası"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "uk-UA"
    },
    "strings": {
        "WARNING": "Попередження",
        "INFO": "Інформація",
        "ERROR": "Помилка",
        "VERSION": "Версія ",
        "LOADING_PROTOCOL": "Підключення Ð´Ð¾ ÑÐµÑ€Ð²ÐµÑ€Ð°...",
        "INITIALIZING": "Ініціалізація...",
        "PIN_ERROR": "Будь Ð»Ð°ÑÐºÐ°, Ð²ÑÑ‚авте SIM-карту",
        "REG_ERROR": "Неможливо Ð¾Ñ‚римати Ð´Ð¾ÑÑ‚уп Ð´Ð¾ Ð¼ÐµÑ€ÐµÐ¶Ñ–, Ð¿ÐµÑ€ÐµÐ²Ñ–рте ÑÑ‚ан ÐºÐ°Ñ€Ñ‚и Ð´Ð°Ð½Ð¸Ñ…",
        "DETECTING_MODULE": "Виявлення Ð¼Ð¾Ð´ÑƒÐ»Ñ...",
        "REGISTERING_NETWORK": "Очікування Ð¼ÐµÑ€ÐµÐ¶Ñ–...",
        "CHECKING_NEW_VERSION": "Перевірка Ð½Ð¾Ð²Ð¾Ñ— Ð²ÐµÑ€ÑÑ–Ñ—...",
        "CHECK_NEW_VERSION_FAILED": "Перевірка Ð½Ð¾Ð²Ð¾Ñ— Ð²ÐµÑ€ÑÑ–Ñ— Ð½Ðµ Ð²Ð´Ð°Ð»Ð°ÑÑ, Ð¿Ð¾Ð²Ñ‚ор Ñ‡ÐµÑ€ÐµÐ· %d ÑÐµÐºÑƒÐ½Ð´: %s",
        "SWITCH_TO_WIFI_NETWORK": "Перемикання Ð½Ð° Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Перемикання Ð½Ð° 4G...",
        "STANDBY": "Очікування",
        "CONNECT_TO": "Підключитися Ð´Ð¾ ",
        "CONNECTING": "Підключення...",
        "CONNECTED_TO": "Підключено Ð´Ð¾ ",
        "LISTENING": "Прослуховування...",
        "SPEAKING": "Говоріння...",
        "SERVER_NOT_FOUND": "Пошук Ð´Ð¾ÑÑ‚упного ÑÐµÑ€Ð²Ñ–су",
        "SERVER_NOT_CONNECTED": "Неможливо Ð¿Ñ–дключитися Ð´Ð¾ ÑÐµÑ€Ð²Ñ–су, ÑÐ¿Ñ€Ð¾Ð±ÑƒÐ¹Ñ‚е Ð¿Ñ–зніше",
        "SERVER_TIMEOUT": "Час Ð¾Ñ‡Ñ–кування Ð²Ñ–дповіді",
        "SERVER_ERROR": "Помилка Ð²Ñ–дправки, Ð¿ÐµÑ€ÐµÐ²Ñ–рте Ð¼ÐµÑ€ÐµÐ¶Ñƒ",
        "CONNECT_TO_HOTSPOT": "Підключіть Ñ‚елефон Ð´Ð¾ Ñ‚очки Ð´Ð¾ÑÑ‚упу ",
        "ACCESS_VIA_BROWSER": ",доступ Ñ‡ÐµÑ€ÐµÐ· Ð±Ñ€Ð°ÑƒÐ·ÐµÑ€ ",
        "WIFI_CONFIG_MODE": "Режим Ð½Ð°Ð»Ð°ÑˆÑ‚ування Ð¼ÐµÑ€ÐµÐ¶Ñ–",
        "ENTERING_WIFI_CONFIG_MODE": "Вхід Ñƒ Ñ€ÐµÐ¶Ð¸Ð¼ Ð½Ð°Ð»Ð°ÑˆÑ‚ування Ð¼ÐµÑ€ÐµÐ¶Ñ–...",
        "SCANNING_WIFI": "Сканування Wi-Fi...",
        "NEW_VERSION": "Нова Ð²ÐµÑ€ÑÑ–я ",
        "OTA_UPGRADE": "Оновлення OTA",
        "UPGRADING": "Оновлення ÑÐ¸ÑÑ‚еми...",
        "UPGRADE_FAILED": "Оновлення Ð½Ðµ Ð²Ð´Ð°Ð»Ð¾ÑÑ",
        "ACTIVATION": "Активація Ð¿Ñ€Ð¸ÑÑ‚рою",
        "BATTERY_LOW": "Низький Ð·Ð°Ñ€ÑÐ´ Ð±Ð°Ñ‚ареї",
        "BATTERY_CHARGING": "Зарядка",
        "BATTERY_FULL": "Батарея Ð¿Ð¾Ð²Ð½Ð°",
        "BATTERY_NEED_CHARGE": "Низький Ð·Ð°Ñ€ÑÐ´, Ð±ÑƒÐ´ÑŒ Ð»Ð°ÑÐºÐ°, Ð·Ð°Ñ€ÑÐ´Ñ–ть",
        "VOLUME": "Гучність ",
        "MUTED": "Звук Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾",
        "MAX_VOLUME": "Максимальна Ð³ÑƒÑ‡Ð½Ñ–сть",
        "RTC_MODE_OFF": "AEC Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾",
        "RTC_MODE_ON": "AEC ÑƒÐ²Ñ–мкнено",
        "DOWNLOAD_ASSETS_FAILED": "Не Ð²Ð´Ð°Ð»Ð¾ÑÑ Ð·Ð°Ð²Ð°Ð½Ñ‚ажити Ñ€ÐµÑÑƒÑ€ÑÐ¸",
        "LOADING_ASSETS": "Завантаження Ñ€ÐµÑÑƒÑ€ÑÑ–в...",
        "PLEASE_WAIT": "Будь Ð»Ð°ÑÐºÐ°, Ð·Ð°Ñ‡ÐµÐºÐ°Ð¹Ñ‚е...",
        "FOUND_NEW_ASSETS": "Знайдено Ð½Ð¾Ð²Ñ– Ñ€ÐµÑÑƒÑ€ÑÐ¸: %s",
        "HELLO_MY_FRIEND": "Привіт, Ð¼Ñ–й Ð´Ñ€ÑƒÐ¶Ðµ!",
        "CONNECTION_SUCCESSFUL": "Підключення ÑƒÑÐ¿Ñ–шне",
        "FLIGHT_MODE_OFF": "Режим Ð¿Ð¾Ð»ÑŒÐ¾Ñ‚у Ð²Ð¸Ð¼ÐºÐ½ÐµÐ½Ð¾",
        "FLIGHT_MODE_ON": "Режим Ð¿Ð¾Ð»ÑŒÐ¾Ñ‚у ÑƒÐ²Ñ–мкнено",
        "MODEM_INIT_ERROR": "Помилка Ñ–ніціалізації Ð¼Ð¾Ð´ÐµÐ¼Ñƒ"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "vi-VN"
    },
    "strings": {
        "WARNING": "Cảnh báo",
        "INFO": "Thông tin",
        "ERROR": "Lỗi",
        "VERSION": "Phiên bản ",
        "LOADING_PROTOCOL": "Đang Ä‘ăng nhập...",
        "INITIALIZING": "Đang khởi tạo...",
        "PIN_ERROR": "Vui lòng cắm thẻ SIM",
        "REG_ERROR": "Không thể truy cập mạng, vui lòng kiểm tra trạng thái thẻ SIM",
        "DETECTING_MODULE": "Đang phát hiện module...",
        "REGISTERING_NETWORK": "Đang chờ mạng...",
        "CHECKING_NEW_VERSION": "Đang kiểm tra phiên bản mới...",
        "CHECK_NEW_VERSION_FAILED": "Kiểm tra phiên bản mới thất bại, sẽ thá»­ lại sau %d giây: %s",
        "SWITCH_TO_WIFI_NETWORK": "Đang chuyển sang Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "Đang chuyển sang 4G...",
        "STANDBY": "Chờ",
        "CONNECT_TO": "Kết nối Ä‘ến ",
        "CONNECTING": "Đang kết nối...",
        "CONNECTION_SUCCESSFUL": "Kết nối thành công",
        "CONNECTED_TO": "Đã kết nối Ä‘ến ",
        "LISTENING": "Đang lắng nghe...",
        "SPEAKING": "Đang nói...",
        "SERVER_NOT_FOUND": "Đang tìm dịch vụ khả dụng",
        "SERVER_NOT_CONNECTED": "Không thể kết nối Ä‘ến dịch vụ, vui lòng thá»­ lại sau",
        "SERVER_TIMEOUT": "Hết thời gian chờ phản hồi",
        "SERVER_ERROR": "Gá»­i thất bại, vui lòng kiểm tra mạng",
        "CONNECT_TO_HOTSPOT": "Điểm phát sóng: ",
        "ACCESS_VIA_BROWSER": " URL cấu hình: ",
        "WIFI_CONFIG_MODE": "Chế Ä‘á»™ cấu hình Wi-Fi",
        "ENTERING_WIFI_CONFIG_MODE": "Đang vào chế Ä‘á»™ cấu hình Wi-Fi...",
        "SCANNING_WIFI": "Đang quét Wi-Fi...",
        "NEW_VERSION": "Phiên bản mới ",
        "OTA_UPGRADE": "Nâng cấp OTA",
        "UPGRADING": "Hệ thống Ä‘ang nâng cấp...",
        "UPGRADE_FAILED": "Nâng cấp thất bại",
        "ACTIVATION": "Kích hoạt",
        "BATTERY_LOW": "Pin yếu",
        "BATTERY_CHARGING": "Đang sạc",
        "BATTERY_FULL": "Pin Ä‘ầy",
        "BATTERY_NEED_CHARGE": "Pin yếu, vui lòng sạc",
        "VOLUME": "Âm lượng ",
        "MUTED": "Tắt tiếng",
        "MAX_VOLUME": "Âm lượng tối Ä‘a",
        "RTC_MODE_OFF": "Tắt AEC",
        "RTC_MODE_ON": "Bật AEC",
        "DOWNLOAD_ASSETS_FAILED": "Tải xuống tài nguyên thất bại",
        "LOADING_ASSETS": "Đang tải tài nguyên...",
        "PLEASE_WAIT": "Vui lòng Ä‘ợi...",
        "FOUND_NEW_ASSETS": "Tìm thấy tài nguyên mới: %s",
        "HELLO_MY_FRIEND": "Xin chào, bạn cá»§a tôi!",
        "FLIGHT_MODE_OFF": "Chế Ä‘á»™ máy bay Ä‘ang tắt",
        "FLIGHT_MODE_ON": "Chế Ä‘á»™ máy bay Ä‘ang bật",
        "MODEM_INIT_ERROR": "Khởi tạo modem thất bại"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "zh-CN"
    },
    "strings": {
        "WARNING": "警告",
        "INFO": "信息",
        "ERROR": "错误",
        "VERSION": "版本 ",
        "LOADING_PROTOCOL": "登录服务器...",
        "INITIALIZING": "正在初始化...",
        "PIN_ERROR": "请插入 SIM å¡",
        "REG_ERROR": "无法接入网络,请检查流量卡状态",
        "MODEM_INIT_ERROR": "模组初始化失败",
        "DETECTING_MODULE": "检测模组...",
        "REGISTERING_NETWORK": "等待网络...",
        "CHECKING_NEW_VERSION": "检查新版本...",
        "CHECK_NEW_VERSION_FAILED": "检查新版本失败,将在 %d ç§’后重试:%s",
        "SWITCH_TO_WIFI_NETWORK": "切换到 Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "切换到 4G...",
        "STANDBY": "待命",
        "CONNECT_TO": "连接 ",
        "CONNECTING": "连接中...",
        "CONNECTED_TO": "已连接 ",
        "LISTENING": "聆听中...",
        "SPEAKING": "说话中...",
        "SERVER_NOT_FOUND": "正在寻找可用服务",
        "SERVER_NOT_CONNECTED": "无法连接服务,请稍后再试",
        "SERVER_TIMEOUT": "等待响应超时",
        "SERVER_ERROR": "发送失败,请检查网络",
        "CONNECT_TO_HOTSPOT": "手机连接热点 ",
        "ACCESS_VIA_BROWSER": ",浏览器访问 ",
        "WIFI_CONFIG_MODE": "配网模式",
        "ENTERING_WIFI_CONFIG_MODE": "进入配网模式...",
        "SCANNING_WIFI": "扫描 Wi-Fi...",
        "NEW_VERSION": "新版本 ",
        "OTA_UPGRADE": "OTA å‡çº§",
        "UPGRADING": "正在升级系统...",
        "UPGRADE_FAILED": "升级失败",
        "ACTIVATION": "激活设备",
        "BATTERY_LOW": "电量不足",
        "BATTERY_CHARGING": "正在充电",
        "BATTERY_FULL": "电量已满",
        "BATTERY_NEED_CHARGE": "电量低,请充电",
        "VOLUME": "音量 ",
        "MUTED": "已静音",
        "MAX_VOLUME": "最大音量",
        "RTC_MODE_OFF": "AEC å…³é—­",
        "RTC_MODE_ON": "AEC å¼€å¯",
        "DOWNLOAD_ASSETS_FAILED": "下载资源失败",
        "LOADING_ASSETS": "加载资源...",
        "PLEASE_WAIT": "请稍候...",
        "FOUND_NEW_ASSETS": "发现新资源: %s",
        "HELLO_MY_FRIEND": "你好,我的朋友!",
        "CONNECTION_SUCCESSFUL": "连接成功",
        "FLIGHT_MODE_OFF": "飞行模式已关闭",
        "FLIGHT_MODE_ON": "飞行模式已开启"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/0.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/1.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/2.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/3.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/4.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/5.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/6.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/7.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/8.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/9.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/activation.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_pin.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_reg.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/language.json
New file
@@ -0,0 +1,59 @@
{
    "language": {
        "type": "zh-TW"
    },
    "strings": {
        "WARNING": "警告",
        "INFO": "資訊",
        "ERROR": "錯誤",
        "VERSION": "版本 ",
        "LOADING_PROTOCOL": "登入伺服器...",
        "INITIALIZING": "正在初始化...",
        "PIN_ERROR": "請插入 SIM å¡",
        "REG_ERROR": "無法接入網絡,請檢查網路狀態",
        "DETECTING_MODULE": "檢測模組...",
        "REGISTERING_NETWORK": "等待網絡...",
        "CHECKING_NEW_VERSION": "檢查新版本...",
        "CHECK_NEW_VERSION_FAILED": "檢查新版本失敗,將在 %d ç§’後重試:%s",
        "SWITCH_TO_WIFI_NETWORK": "切換到 Wi-Fi...",
        "SWITCH_TO_4G_NETWORK": "切換到 4G...",
        "STANDBY": "待命",
        "CONNECT_TO": "連接 ",
        "CONNECTING": "連接中...",
        "CONNECTED_TO": "已連接 ",
        "LISTENING": "聆聽中...",
        "SPEAKING": "說話中...",
        "SERVER_NOT_FOUND": "正在尋找可用服務",
        "SERVER_NOT_CONNECTED": "無法連接服務,請稍後再試",
        "SERVER_TIMEOUT": "等待響應超時",
        "SERVER_ERROR": "發送失敗,請檢查網絡",
        "CONNECT_TO_HOTSPOT": "手機連接WiFi ",
        "ACCESS_VIA_BROWSER": ",瀏覽器訪問 ",
        "WIFI_CONFIG_MODE": "網路設定模式",
        "ENTERING_WIFI_CONFIG_MODE": "正在設定網路...",
        "SCANNING_WIFI": "掃描 Wi-Fi...",
        "NEW_VERSION": "新版本 ",
        "OTA_UPGRADE": "OTA å‡ç´š",
        "UPGRADING": "正在升級系統...",
        "UPGRADE_FAILED": "升級失敗",
        "ACTIVATION": "啟用設備",
        "BATTERY_LOW": "電量不足",
        "BATTERY_CHARGING": "正在充電",
        "BATTERY_FULL": "電量已滿",
        "BATTERY_NEED_CHARGE": "電量低,請充電",
        "VOLUME": "音量 ",
        "MUTED": "已靜音",
        "MAX_VOLUME": "最大音量",
        "RTC_MODE_OFF": "AEC é—œé–‰",
        "RTC_MODE_ON": "AEC é–‹å•Ÿ",
        "DOWNLOAD_ASSETS_FAILED": "下載資源失敗",
        "LOADING_ASSETS": "載入資源...",
        "PLEASE_WAIT": "請稍候...",
        "FOUND_NEW_ASSETS": "發現新資源: %s",
        "HELLO_MY_FRIEND": "你好,我的朋友!",
        "CONNECTION_SUCCESSFUL": "連線成功",
        "FLIGHT_MODE_OFF": "飛航模式已關閉",
        "FLIGHT_MODE_ON": "飛航模式已開啟",
        "MODEM_INIT_ERROR": "模組初始化失敗"
    }
}
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/upgrade.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/welcome.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/wificonfig.ogg
Binary files differ
xiaozhi-esp32-2.2.6/main/audio/README.md
New file
@@ -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.
xiaozhi-esp32-2.2.6/main/audio/audio_codec.cc
New file
@@ -0,0 +1,67 @@
#include "audio_codec.h"
#include "board.h"
#include "settings.h"
#include <esp_log.h>
#include <cstring>
#include <driver/i2s_common.h>
#define TAG "AudioCodec"
AudioCodec::AudioCodec() {
}
AudioCodec::~AudioCodec() {
}
void AudioCodec::OutputData(std::vector<int16_t>& data) {
    Write(data.data(), data.size());
}
bool AudioCodec::InputData(std::vector<int16_t>& data) {
    int samples = Read(data.data(), data.size());
    if (samples > 0) {
        return true;
    }
    return false;
}
void AudioCodec::Start() {
    Settings settings("audio", false);
    output_volume_ = settings.GetInt("output_volume", output_volume_);
    if (output_volume_ <= 0) {
        ESP_LOGW(TAG, "Output volume value (%d) is too small, setting to default (10)", output_volume_);
        output_volume_ = 10;
    }
    ESP_LOGI(TAG, "Audio codec started");
}
void AudioCodec::SetOutputVolume(int volume) {
    output_volume_ = volume;
    ESP_LOGI(TAG, "Set output volume to %d", output_volume_);
    Settings settings("audio", true);
    settings.SetInt("output_volume", output_volume_);
}
void AudioCodec::SetInputGain(float gain) {
    input_gain_ = gain;
    ESP_LOGI(TAG, "Set input gain to %.1f", input_gain_);
}
void AudioCodec::EnableInput(bool enable) {
    if (enable == input_enabled_) {
        return;
    }
    input_enabled_ = enable;
    ESP_LOGI(TAG, "Set input enable to %s", enable ? "true" : "false");
}
void AudioCodec::EnableOutput(bool enable) {
    if (enable == output_enabled_) {
        return;
    }
    output_enabled_ = enable;
    ESP_LOGI(TAG, "Set output enable to %s", enable ? "true" : "false");
}
xiaozhi-esp32-2.2.6/main/audio/audio_codec.h
New file
@@ -0,0 +1,61 @@
#ifndef _AUDIO_CODEC_H
#define _AUDIO_CODEC_H
#include <freertos/FreeRTOS.h>
#include <freertos/event_groups.h>
#include <driver/i2s_std.h>
#include <vector>
#include <string>
#include <functional>
#include "board.h"
#define AUDIO_CODEC_DMA_DESC_NUM 6
#define AUDIO_CODEC_DMA_FRAME_NUM 240
class AudioCodec {
public:
    AudioCodec();
    virtual ~AudioCodec();
    virtual void SetOutputVolume(int volume);
    virtual void SetInputGain(float gain);
    virtual void EnableInput(bool enable);
    virtual void EnableOutput(bool enable);
    virtual void OutputData(std::vector<int16_t>& data);
    virtual bool InputData(std::vector<int16_t>& data);
    virtual void Start();
    inline bool duplex() const { return duplex_; }
    inline bool input_reference() const { return input_reference_; }
    inline int input_sample_rate() const { return input_sample_rate_; }
    inline int output_sample_rate() const { return output_sample_rate_; }
    inline int input_channels() const { return input_channels_; }
    inline int output_channels() const { return output_channels_; }
    inline int output_volume() const { return output_volume_; }
    inline float input_gain() const { return input_gain_; }
    inline bool input_enabled() const { return input_enabled_; }
    inline bool output_enabled() const { return output_enabled_; }
protected:
    i2s_chan_handle_t tx_handle_ = nullptr;
    i2s_chan_handle_t rx_handle_ = nullptr;
    bool duplex_ = false;
    bool input_reference_ = false;
    bool input_enabled_ = false;
    bool output_enabled_ = false;
    int input_sample_rate_ = 0;
    int output_sample_rate_ = 0;
    int input_channels_ = 1;
    int output_channels_ = 1;
    int output_volume_ = 70;
    float input_gain_ = 0.0;
    virtual int Read(int16_t* dest, int samples) = 0;
    virtual int Write(const int16_t* data, int samples) = 0;
};
#endif // _AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/audio_processor.h
New file
@@ -0,0 +1,26 @@
#ifndef AUDIO_PROCESSOR_H
#define AUDIO_PROCESSOR_H
#include <string>
#include <vector>
#include <functional>
#include <model_path.h>
#include "audio_codec.h"
class AudioProcessor {
public:
    virtual ~AudioProcessor() = default;
    virtual void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) = 0;
    virtual void Feed(std::vector<int16_t>&& data) = 0;
    virtual void Start() = 0;
    virtual void Stop() = 0;
    virtual bool IsRunning() = 0;
    virtual void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) = 0;
    virtual void OnVadStateChange(std::function<void(bool speaking)> callback) = 0;
    virtual size_t GetFeedSize() = 0;
    virtual void EnableDeviceAec(bool enable) = 0;
};
#endif
xiaozhi-esp32-2.2.6/main/audio/audio_service.cc
New file
@@ -0,0 +1,734 @@
#include "audio_service.h"
#include <esp_log.h>
#include <cstring>
#define RATE_CVT_CFG(_src_rate, _dest_rate, _channel)        \
    (esp_ae_rate_cvt_cfg_t)                                  \
    {                                                        \
        .src_rate        = (uint32_t)(_src_rate),            \
        .dest_rate       = (uint32_t)(_dest_rate),           \
        .channel         = (uint8_t)(_channel),              \
        .bits_per_sample = ESP_AUDIO_BIT16,                  \
        .complexity      = 2,                                \
        .perf_type       = ESP_AE_RATE_CVT_PERF_TYPE_SPEED,  \
    }
#define OPUS_DEC_CFG(_sample_rate, _frame_duration_ms)                                                    \
    (esp_opus_dec_cfg_t)                                                                                  \
    {                                                                                                     \
        .sample_rate    = (uint32_t)(_sample_rate),                                                       \
        .channel        = ESP_AUDIO_MONO,                                                                 \
        .frame_duration = (esp_opus_dec_frame_duration_t)AS_OPUS_GET_FRAME_DRU_ENUM(_frame_duration_ms),  \
        .self_delimited = false,                                                                          \
    }
#if CONFIG_USE_AUDIO_PROCESSOR
#include "processors/afe_audio_processor.h"
#else
#include "processors/no_audio_processor.h"
#endif
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
#include "wake_words/afe_wake_word.h"
#include "wake_words/custom_wake_word.h"
#else
#include "wake_words/esp_wake_word.h"
#endif
#define TAG "AudioService"
AudioService::AudioService() {
    event_group_ = xEventGroupCreate();
}
AudioService::~AudioService() {
    if (event_group_ != nullptr) {
        vEventGroupDelete(event_group_);
    }
    if (opus_encoder_ != nullptr) {
        esp_opus_enc_close(opus_encoder_);
    }
    if (opus_decoder_ != nullptr) {
        esp_opus_dec_close(opus_decoder_);
    }
    if (input_resampler_ != nullptr) {
        esp_ae_rate_cvt_close(input_resampler_);
    }
    if (output_resampler_ != nullptr) {
        esp_ae_rate_cvt_close(output_resampler_);
    }
}
void AudioService::Initialize(AudioCodec* codec) {
    codec_ = codec;
    codec_->Start();
    esp_opus_dec_cfg_t opus_dec_cfg = OPUS_DEC_CFG(codec->output_sample_rate(), OPUS_FRAME_DURATION_MS);
    auto ret = esp_opus_dec_open(&opus_dec_cfg, sizeof(esp_opus_dec_cfg_t), &opus_decoder_);
    if (opus_decoder_ == nullptr) {
        ESP_LOGE(TAG, "Failed to create audio decoder, error code: %d", ret);
    } else {
        decoder_sample_rate_ = codec->output_sample_rate();
        decoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
        decoder_frame_size_ = decoder_sample_rate_ / 1000 * OPUS_FRAME_DURATION_MS;
    }
    esp_opus_enc_config_t opus_enc_cfg = AS_OPUS_ENC_CONFIG();
    ret = esp_opus_enc_open(&opus_enc_cfg, sizeof(esp_opus_enc_config_t), &opus_encoder_);
    if (opus_encoder_ == nullptr) {
        ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", ret);
    } else {
        encoder_sample_rate_ = 16000;
        encoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
        esp_opus_enc_get_frame_size(opus_encoder_, &encoder_frame_size_, &encoder_outbuf_size_);
        encoder_frame_size_ = encoder_frame_size_ / sizeof(int16_t);
    }
    if (codec->input_sample_rate() != 16000) {
        esp_ae_rate_cvt_cfg_t input_resampler_cfg = RATE_CVT_CFG(
            codec->input_sample_rate(), ESP_AUDIO_SAMPLE_RATE_16K, codec->input_channels());
        auto resampler_ret = esp_ae_rate_cvt_open(&input_resampler_cfg, &input_resampler_);
        if (input_resampler_ == nullptr) {
            ESP_LOGE(TAG, "Failed to create input resampler, error code: %d", resampler_ret);
        }
    }
#if CONFIG_USE_AUDIO_PROCESSOR
    audio_processor_ = std::make_unique<AfeAudioProcessor>();
#else
    audio_processor_ = std::make_unique<NoAudioProcessor>();
#endif
    audio_processor_->OnOutput([this](std::vector<int16_t>&& 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);
        }
    });
    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);
#if CONFIG_USE_AUDIO_PROCESSOR
    /* Start the audio input task */
    xTaskCreatePinnedToCore([](void* arg) {
        AudioService* audio_service = (AudioService*)arg;
        audio_service->AudioInputTask();
        vTaskDelete(NULL);
    }, "audio_input", 2048 * 3, this, 8, &audio_input_task_handle_, 0);
    /* Start the audio output task */
    xTaskCreate([](void* arg) {
        AudioService* audio_service = (AudioService*)arg;
        audio_service->AudioOutputTask();
        vTaskDelete(NULL);
    }, "audio_output", 2048 * 2, this, 4, &audio_output_task_handle_);
#else
    /* Start the audio input task */
    xTaskCreate([](void* arg) {
        AudioService* audio_service = (AudioService*)arg;
        audio_service->AudioInputTask();
        vTaskDelete(NULL);
    }, "audio_input", 2048 * 2, this, 8, &audio_input_task_handle_);
    /* Start the audio output task */
    xTaskCreate([](void* arg) {
        AudioService* audio_service = (AudioService*)arg;
        audio_service->AudioOutputTask();
        vTaskDelete(NULL);
    }, "audio_output", 2048, this, 4, &audio_output_task_handle_);
#endif
    /* Start the opus codec task */
    xTaskCreate([](void* arg) {
        AudioService* audio_service = (AudioService*)arg;
        audio_service->OpusCodecTask();
        vTaskDelete(NULL);
    }, "opus_codec", 2048 * 12, 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<std::mutex> 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<int16_t>& data, int sample_rate, int samples) {
    if (!codec_->input_enabled()) {
        esp_timer_stop(audio_power_timer_);
        esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000);
        codec_->EnableInput(true);
    }
    if (codec_->input_sample_rate() != sample_rate) {
        data.resize(samples * codec_->input_sample_rate() / sample_rate * codec_->input_channels());
        if (!codec_->InputData(data)) {
            return false;
        }
        if (input_resampler_ != nullptr) {
            std::lock_guard<std::mutex> lock(input_resampler_mutex_);
            uint32_t in_sample_num = data.size() / codec_->input_channels();
            uint32_t output_samples = 0;
            esp_ae_rate_cvt_get_max_out_sample_num(input_resampler_, in_sample_num, &output_samples);
            auto resampled = std::vector<int16_t>(output_samples * codec_->input_channels());
            uint32_t actual_output = output_samples;
            esp_ae_rate_cvt_process(input_resampler_, (esp_ae_sample_t)data.data(), in_sample_num,
                                   (esp_ae_sample_t)resampled.data(), &actual_output);
            resampled.resize(actual_output * codec_->input_channels());
            data = std::move(resampled);
        }
    } else {
        data.resize(samples * codec_->input_channels());
        if (!codec_->InputData(data)) {
            return false;
        }
    }
    /* Update the last input time */
    last_input_time_ = std::chrono::steady_clock::now();
    debug_statistics_.input_count++;
#if CONFIG_USE_AUDIO_DEBUGGER
    // éŸ³é¢‘调试:发送原始音频数据
    if (audio_debugger_ == nullptr) {
        audio_debugger_ = std::make_unique<AudioDebugger>();
    }
    audio_debugger_->Feed(data);
#endif
    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<int16_t> 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<int16_t>(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 and/or audio processor */
        if (bits & (AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING)) {
            int samples = 160; // 10ms
            std::vector<int16_t> data;
            if (ReadAudioData(data, 16000, samples)) {
                if (bits & AS_EVENT_WAKE_WORD_RUNNING) {
                    wake_word_->Feed(data);
                }
                if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) {
                    audio_processor_->Feed(std::move(data));
                }
                continue;
            }
        }
        // Read timeout/error should not terminate the input task.
        vTaskDelay(pdMS_TO_TICKS(10));
    }
    ESP_LOGW(TAG, "Audio input task stopped");
}
void AudioService::AudioOutputTask() {
    while (true) {
        std::unique_lock<std::mutex> 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()) {
            esp_timer_stop(audio_power_timer_);
            esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000);
            codec_->EnableOutput(true);
        }
        codec_->OutputData(task->pcm);
        /* Update the last output time */
        last_output_time_ = std::chrono::steady_clock::now();
        debug_statistics_.playback_count++;
#if CONFIG_USE_SERVER_AEC
        /* Record the timestamp for server AEC */
        if (task->timestamp > 0) {
            lock.lock();
            timestamp_queue_.push_back(task->timestamp);
        }
#endif
    }
    ESP_LOGW(TAG, "Audio output task stopped");
}
void AudioService::OpusCodecTask() {
    while (true) {
        std::unique_lock<std::mutex> 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<AudioTask>();
            task->type = kAudioTaskTypeDecodeToPlaybackQueue;
            task->timestamp = packet->timestamp;
            SetDecodeSampleRate(packet->sample_rate, packet->frame_duration);
            if (opus_decoder_ != nullptr) {
                task->pcm.resize(decoder_frame_size_);
                esp_audio_dec_in_raw_t raw = {
                    .buffer = (uint8_t *)(packet->payload.data()),
                    .len = (uint32_t)(packet->payload.size()),
                    .consumed = 0,
                    .frame_recover = ESP_AUDIO_DEC_RECOVERY_NONE,
                };
                esp_audio_dec_out_frame_t out_frame = {
                    .buffer = (uint8_t *)(task->pcm.data()),
                    .len = (uint32_t)(task->pcm.size() * sizeof(int16_t)),
                    .decoded_size = 0,
                };
                esp_audio_dec_info_t dec_info = {};
                std::unique_lock<std::mutex> decoder_lock(decoder_mutex_);
                auto ret = esp_opus_dec_decode(opus_decoder_, &raw, &out_frame, &dec_info);
                decoder_lock.unlock();
                if (ret == ESP_AUDIO_ERR_OK) {
                    task->pcm.resize(out_frame.decoded_size / sizeof(int16_t));
                    if (decoder_sample_rate_ != codec_->output_sample_rate() && output_resampler_ != nullptr) {
                        uint32_t target_size = 0;
                        esp_ae_rate_cvt_get_max_out_sample_num(output_resampler_, task->pcm.size(), &target_size);
                        std::vector<int16_t> resampled(target_size);
                        uint32_t actual_output = target_size;
                        esp_ae_rate_cvt_process(output_resampler_, (esp_ae_sample_t)task->pcm.data(), task->pcm.size(),
                                                (esp_ae_sample_t)resampled.data(), &actual_output);
                        resampled.resize(actual_output);
                        task->pcm = std::move(resampled);
                    }
                    lock.lock();
                    audio_playback_queue_.push_back(std::move(task));
                    audio_queue_cv_.notify_all();
                    debug_statistics_.decode_count++;
                } else {
                    ESP_LOGE(TAG, "Failed to decode audio after resize, error code: %d", ret);
                    lock.lock();
                }
            } else {
                ESP_LOGE(TAG, "Audio decoder is not configured");
                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();
            auto packet = std::make_unique<AudioStreamPacket>();
            packet->frame_duration = OPUS_FRAME_DURATION_MS;
            packet->sample_rate = 16000;
            packet->timestamp = task->timestamp;
            if (opus_encoder_ != nullptr && task->pcm.size() == encoder_frame_size_) {
                std::vector<uint8_t> buf(encoder_outbuf_size_);
                esp_audio_enc_in_frame_t in = {
                    .buffer = (uint8_t *)(task->pcm.data()),
                    .len = (uint32_t)(encoder_frame_size_ * sizeof(int16_t)),
                };
                esp_audio_enc_out_frame_t out = {
                    .buffer = buf.data(),
                    .len = (uint32_t)encoder_outbuf_size_,
                    .encoded_bytes = 0,
                };
                auto ret = esp_opus_enc_process(opus_encoder_, &in, &out);
                if (ret == ESP_AUDIO_ERR_OK) {
                    packet->payload.assign(buf.data(), buf.data() + out.encoded_bytes);
                    if (task->type == kAudioTaskTypeEncodeToSendQueue) {
                        {
                            std::lock_guard<std::mutex> lock2(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<std::mutex> lock2(audio_queue_mutex_);
                        audio_testing_queue_.push_back(std::move(packet));
                    }
                    debug_statistics_.encode_count++;
                } else {
                    ESP_LOGE(TAG, "Failed to encode audio, error code: %d", ret);
                }
            } else {
                ESP_LOGE(TAG, "Failed to encode audio: encoder not configured or invalid frame size (got %u, expected %u)",
                         task->pcm.size(), encoder_frame_size_);
            }
            lock.lock();
        }
    }
    ESP_LOGW(TAG, "Opus codec task stopped");
}
void AudioService::SetDecodeSampleRate(int sample_rate, int frame_duration) {
    if (decoder_sample_rate_ == sample_rate && decoder_duration_ms_ == frame_duration) {
        return;
    }
    std::unique_lock<std::mutex> decoder_lock(decoder_mutex_);
    if (opus_decoder_ != nullptr) {
        esp_opus_dec_close(opus_decoder_);
        opus_decoder_ = nullptr;
    }
    decoder_lock.unlock();
    esp_opus_dec_cfg_t opus_dec_cfg = OPUS_DEC_CFG(sample_rate, frame_duration);
    auto ret = esp_opus_dec_open(&opus_dec_cfg, sizeof(esp_opus_dec_cfg_t), &opus_decoder_);
    if (opus_decoder_ == nullptr) {
        ESP_LOGE(TAG, "Failed to create audio decoder, error code: %d", ret);
        return;
    }
    decoder_sample_rate_ = sample_rate;
    decoder_duration_ms_ = frame_duration;
    decoder_frame_size_ = decoder_sample_rate_ / 1000 * frame_duration;
    auto codec = Board::GetInstance().GetAudioCodec();
    if (decoder_sample_rate_ != codec->output_sample_rate()) {
        ESP_LOGI(TAG, "Resampling audio from %d to %d", decoder_sample_rate_, codec->output_sample_rate());
        if (output_resampler_ != nullptr) {
            esp_ae_rate_cvt_close(output_resampler_);
            output_resampler_ = nullptr;
        }
        esp_ae_rate_cvt_cfg_t output_resampler_cfg = RATE_CVT_CFG(
            decoder_sample_rate_, codec->output_sample_rate(), ESP_AUDIO_MONO);
        auto resampler_ret = esp_ae_rate_cvt_open(&output_resampler_cfg, &output_resampler_);
        if (output_resampler_ == nullptr) {
            ESP_LOGE(TAG, "Failed to create output resampler, error code: %d", resampler_ret);
        }
    }
}
void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t>&& pcm) {
    auto task = std::make_unique<AudioTask>();
    task->type = type;
    task->pcm = std::move(pcm);
    /* Push the task to the encode queue */
    std::unique_lock<std::mutex> lock(audio_queue_mutex_);
    /* If the task is to send queue, we need to set the timestamp */
    if (type == kAudioTaskTypeEncodeToSendQueue && !timestamp_queue_.empty()) {
        if (timestamp_queue_.size() <= MAX_TIMESTAMPS_IN_QUEUE) {
            task->timestamp = timestamp_queue_.front();
        } else {
            ESP_LOGW(TAG, "Timestamp queue (%u) is full, dropping timestamp", timestamp_queue_.size());
        }
        timestamp_queue_.pop_front();
    }
    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<AudioStreamPacket> packet, bool wait) {
    std::unique_lock<std::mutex> 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<AudioStreamPacket> AudioService::PopPacketFromSendQueue() {
    std::lock_guard<std::mutex> 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<AudioStreamPacket> AudioService::PopWakeWordPacket() {
    auto packet = std::make_unique<AudioStreamPacket>();
    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_) {
            if (!wake_word_->Initialize(codec_, models_list_)) {
                ESP_LOGE(TAG, "Failed to initialize wake word");
                return;
            }
            wake_word_initialized_ = true;
        }
        // Reset input resampler to clear cached data from previous mode (e.g. AudioProcessor)
        // This prevents buffer overflow when switching between different feed sizes
        {
            std::lock_guard<std::mutex> lock(input_resampler_mutex_);
            if (input_resampler_ != nullptr) {
                esp_ae_rate_cvt_reset(input_resampler_);
            }
        }
        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_, OPUS_FRAME_DURATION_MS, models_list_);
            audio_processor_initialized_ = true;
        }
        /* We should make sure no audio is playing */
        ResetDecoder();
        audio_input_need_warmup_ = true;
        // Reset input resampler to clear cached data from previous mode (e.g. WakeWord)
        // This prevents buffer overflow when switching between different feed sizes
        {
            std::lock_guard<std::mutex> lock(input_resampler_mutex_);
            if (input_resampler_ != nullptr) {
                esp_ae_rate_cvt_reset(input_resampler_);
            }
        }
        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<std::mutex> 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");
    if (!audio_processor_initialized_) {
        audio_processor_->Initialize(codec_, OPUS_FRAME_DURATION_MS, models_list_);
        audio_processor_initialized_ = true;
    }
    audio_processor_->EnableDeviceAec(enable);
}
void AudioService::SetCallbacks(AudioServiceCallbacks& callbacks) {
    callbacks_ = callbacks;
}
void AudioService::PlaySound(const std::string_view& ogg) {
    if (!codec_->output_enabled()) {
        esp_timer_stop(audio_power_timer_);
        esp_timer_start_periodic(audio_power_timer_, AUDIO_POWER_CHECK_INTERVAL_MS * 1000);
        codec_->EnableOutput(true);
    }
    const auto* buf = reinterpret_cast<const uint8_t*>(ogg.data());
    size_t size = ogg.size();
    auto demuxer = std::make_unique<OggDemuxer>();
    demuxer->OnDemuxerFinished([this](const uint8_t* data, int sample_rate, size_t size){
        auto packet = std::make_unique<AudioStreamPacket>();
        packet->sample_rate = sample_rate;
        packet->frame_duration = 60;
        packet->payload.resize(size);
        std::memcpy(packet->payload.data(), data, size);
        PushPacketToDecodeQueue(std::move(packet), true);
    });
    demuxer->Reset();
    demuxer->Process(buf, size);
}
bool AudioService::IsIdle() {
    std::lock_guard<std::mutex> lock(audio_queue_mutex_);
    return audio_encode_queue_.empty() && audio_decode_queue_.empty() && audio_playback_queue_.empty() && audio_testing_queue_.empty();
}
void AudioService::WaitForPlaybackQueueEmpty() {
    std::unique_lock<std::mutex> lock(audio_queue_mutex_);
    audio_queue_cv_.wait(lock, [this]() {
        return service_stopped_ || (audio_decode_queue_.empty() && audio_playback_queue_.empty());
    });
}
void AudioService::ResetDecoder() {
    std::lock_guard<std::mutex> lock(audio_queue_mutex_);
    std::unique_lock<std::mutex> decoder_lock(decoder_mutex_);
    if (opus_decoder_ != nullptr) {
        esp_opus_dec_reset(opus_decoder_);
    }
    decoder_lock.unlock();
    timestamp_queue_.clear();
    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<std::chrono::milliseconds>(now - last_input_time_).count();
    auto output_elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(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()) {
        // Keep TX clock when duplex RX is active; otherwise RX may stall on some boards.
        if (!(codec_->duplex() && codec_->input_enabled())) {
            codec_->EnableOutput(false);
        }
    }
    if (!codec_->input_enabled() && !codec_->output_enabled()) {
        esp_timer_stop(audio_power_timer_);
    }
}
void AudioService::SetModelsList(srmodel_list_t* models_list) {
    models_list_ = models_list;
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
    if (esp_srmodel_filter(models_list_, ESP_MN_PREFIX, NULL) != nullptr) {
        wake_word_ = std::make_unique<CustomWakeWord>();
    } else if (esp_srmodel_filter(models_list_, ESP_WN_PREFIX, NULL) != nullptr) {
        wake_word_ = std::make_unique<AfeWakeWord>();
    } else {
        wake_word_ = nullptr;
    }
#else
    if (esp_srmodel_filter(models_list_, ESP_WN_PREFIX, NULL) != nullptr) {
        wake_word_ = std::make_unique<EspWakeWord>();
    } else {
        wake_word_ = nullptr;
    }
#endif
    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);
            }
        });
    }
}
bool AudioService::IsAfeWakeWord() {
#if CONFIG_IDF_TARGET_ESP32S3 || CONFIG_IDF_TARGET_ESP32P4
    return wake_word_ != nullptr && dynamic_cast<AfeWakeWord*>(wake_word_.get()) != nullptr;
#else
    return false;
#endif
}
xiaozhi-esp32-2.2.6/main/audio/audio_service.h
New file
@@ -0,0 +1,195 @@
#ifndef AUDIO_SERVICE_H
#define AUDIO_SERVICE_H
#include <memory>
#include <deque>
#include <condition_variable>
#include <chrono>
#include <mutex>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <freertos/event_groups.h>
#include <esp_timer.h>
#include <model_path.h>
#include "esp_audio_enc.h"
#include "esp_opus_enc.h"
#include "esp_opus_dec.h"
#include "esp_ae_rate_cvt.h"
#include "esp_audio_types.h"
#include "audio_codec.h"
#include "audio_processor.h"
#include "processors/audio_debugger.h"
#include "wake_word.h"
#include "protocol.h"
#include "ogg_demuxer.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 MAX_TIMESTAMPS_IN_QUEUE 3
#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)
#define AS_OPUS_GET_FRAME_DRU_ENUM(duration_ms)                   \
    ((duration_ms) == 5 ? ESP_OPUS_ENC_FRAME_DURATION_5_MS :      \
     (duration_ms) == 10 ? ESP_OPUS_ENC_FRAME_DURATION_10_MS :    \
     (duration_ms) == 20 ? ESP_OPUS_ENC_FRAME_DURATION_20_MS :    \
     (duration_ms) == 40 ? ESP_OPUS_ENC_FRAME_DURATION_40_MS :    \
     (duration_ms) == 60 ? ESP_OPUS_ENC_FRAME_DURATION_60_MS :    \
     (duration_ms) == 80 ? ESP_OPUS_ENC_FRAME_DURATION_80_MS :    \
     (duration_ms) == 100 ? ESP_OPUS_ENC_FRAME_DURATION_100_MS :  \
     (duration_ms) == 120 ? ESP_OPUS_ENC_FRAME_DURATION_120_MS : -1)
#define AS_OPUS_ENC_CONFIG() {                                                                                    \
        .sample_rate        = ESP_AUDIO_SAMPLE_RATE_16K,                                                          \
        .channel            = ESP_AUDIO_MONO,                                                                     \
        .bits_per_sample    = ESP_AUDIO_BIT16,                                                                    \
        .bitrate            = ESP_OPUS_BITRATE_AUTO,                                                              \
        .frame_duration     = (esp_opus_enc_frame_duration_t)AS_OPUS_GET_FRAME_DRU_ENUM(OPUS_FRAME_DURATION_MS),  \
        .application_mode   = ESP_OPUS_ENC_APPLICATION_AUDIO,                                                     \
        .complexity         = 0,                                                                                  \
        .enable_fec         = false,                                                                              \
        .enable_dtx         = true,                                                                               \
        .enable_vbr         = true,                                                                               \
    }
struct AudioServiceCallbacks {
    std::function<void(void)> on_send_queue_available;
    std::function<void(const std::string&)> on_wake_word_detected;
    std::function<void(bool)> on_vad_change;
    std::function<void(void)> on_audio_testing_queue_full;
};
enum AudioTaskType {
    kAudioTaskTypeEncodeToSendQueue,
    kAudioTaskTypeEncodeToTestingQueue,
    kAudioTaskTypeDecodeToPlaybackQueue,
};
struct AudioTask {
    AudioTaskType type;
    std::vector<int16_t> pcm;
    uint32_t timestamp;
};
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<AudioStreamPacket> PopWakeWordPacket();
    const std::string& GetLastWakeWord() const;
    bool IsVoiceDetected() const { return voice_detected_; }
    bool IsIdle();
    void WaitForPlaybackQueueEmpty();
    bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; }
    bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; }
    bool IsAfeWakeWord();
    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<AudioStreamPacket> packet, bool wait = false);
    std::unique_ptr<AudioStreamPacket> PopPacketFromSendQueue();
    void PlaySound(const std::string_view& sound);
    bool ReadAudioData(std::vector<int16_t>& data, int sample_rate, int samples);
    void ResetDecoder();
    void SetModelsList(srmodel_list_t* models_list);
private:
    AudioCodec* codec_ = nullptr;
    AudioServiceCallbacks callbacks_;
    std::unique_ptr<AudioProcessor> audio_processor_;
    std::unique_ptr<WakeWord> wake_word_;
    std::unique_ptr<AudioDebugger> audio_debugger_;
    void* opus_encoder_ = nullptr;
    void* opus_decoder_ = nullptr;
    std::mutex decoder_mutex_;
    std::mutex input_resampler_mutex_;
    esp_ae_rate_cvt_handle_t input_resampler_ = nullptr;
    esp_ae_rate_cvt_handle_t output_resampler_ = nullptr;
    // Encoder/Decoder state
    int encoder_sample_rate_ = 16000;
    int encoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
    int encoder_frame_size_ = 0;
    int encoder_outbuf_size_ = 0;
    int decoder_sample_rate_ = 0;
    int decoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
    int decoder_frame_size_ = 0;
    DebugStatistics debug_statistics_;
    srmodel_list_t* models_list_ = nullptr;
    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<std::unique_ptr<AudioStreamPacket>> audio_decode_queue_;
    std::deque<std::unique_ptr<AudioStreamPacket>> audio_send_queue_;
    std::deque<std::unique_ptr<AudioStreamPacket>> audio_testing_queue_;
    std::deque<std::unique_ptr<AudioTask>> audio_encode_queue_;
    std::deque<std::unique_ptr<AudioTask>> audio_playback_queue_;
    // For server AEC
    std::deque<uint32_t> timestamp_queue_;
    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<int16_t>&& pcm);
    void SetDecodeSampleRate(int sample_rate, int frame_duration);
    void CheckAndUpdateAudioPowerState();
};
#endif
xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.cc
New file
@@ -0,0 +1,247 @@
#include "box_audio_codec.h"
#include <esp_log.h>
#include <driver/i2c_master.h>
#include <driver/i2s_tdm.h>
#define TAG "BoxAudioCodec"
BoxAudioCodec::BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate,
    gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
    gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference) {
    duplex_ = true; // æ˜¯å¦åŒå·¥
    input_reference_ = input_reference; // æ˜¯å¦ä½¿ç”¨å‚考输入,实现回声消除
    input_channels_ = input_reference_ ? 2 : 1; // è¾“入通道数
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    input_gain_ = 30;
    CreateDuplexChannels(mclk, bclk, ws, dout, din);
    // Do initialize of related interface: data_if, ctrl_if and gpio_if
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = I2S_NUM_0,
        .rx_handle = rx_handle_,
        .tx_handle = tx_handle_,
    };
    data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
    assert(data_if_ != NULL);
    // Output
    audio_codec_i2c_cfg_t i2c_cfg = {
        .port = (i2c_port_t)1,
        .addr = es8311_addr,
        .bus_handle = i2c_master_handle,
    };
    out_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
    assert(out_ctrl_if_ != NULL);
    gpio_if_ = audio_codec_new_gpio();
    assert(gpio_if_ != NULL);
    es8311_codec_cfg_t es8311_cfg = {};
    es8311_cfg.ctrl_if = out_ctrl_if_;
    es8311_cfg.gpio_if = gpio_if_;
    es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC;
    es8311_cfg.pa_pin = pa_pin;
    es8311_cfg.use_mclk = true;
    es8311_cfg.hw_gain.pa_voltage = 5.0;
    es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
    out_codec_if_ = es8311_codec_new(&es8311_cfg);
    assert(out_codec_if_ != NULL);
    esp_codec_dev_cfg_t dev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_OUT,
        .codec_if = out_codec_if_,
        .data_if = data_if_,
    };
    output_dev_ = esp_codec_dev_new(&dev_cfg);
    assert(output_dev_ != NULL);
    // Input
    i2c_cfg.addr = es7210_addr;
    in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
    assert(in_ctrl_if_ != NULL);
    es7210_codec_cfg_t es7210_cfg = {};
    es7210_cfg.ctrl_if = in_ctrl_if_;
    es7210_cfg.mic_selected = ES7210_SEL_MIC1 | ES7210_SEL_MIC2 | ES7210_SEL_MIC3 | ES7210_SEL_MIC4;
    in_codec_if_ = es7210_codec_new(&es7210_cfg);
    assert(in_codec_if_ != NULL);
    dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
    dev_cfg.codec_if = in_codec_if_;
    input_dev_ = esp_codec_dev_new(&dev_cfg);
    assert(input_dev_ != NULL);
    ESP_LOGI(TAG, "BoxAudioDevice initialized");
}
BoxAudioCodec::~BoxAudioCodec() {
    ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    esp_codec_dev_delete(output_dev_);
    ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    esp_codec_dev_delete(input_dev_);
    audio_codec_delete_codec_if(in_codec_if_);
    audio_codec_delete_ctrl_if(in_ctrl_if_);
    audio_codec_delete_codec_if(out_codec_if_);
    audio_codec_delete_ctrl_if(out_ctrl_if_);
    audio_codec_delete_gpio_if(gpio_if_);
    audio_codec_delete_data_if(data_if_);
}
void BoxAudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
    assert(input_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,
            .ext_clk_freq_hz = 0,
            .mclk_multiple = I2S_MCLK_MULTIPLE_256
        },
        .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,
            .left_align = true,
            .big_endian = false,
            .bit_order_lsb = false
        },
        .gpio_cfg = {
            .mclk = mclk,
            .bclk = bclk,
            .ws = ws,
            .dout = dout,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false
            }
        }
    };
    i2s_tdm_config_t tdm_cfg = {
        .clk_cfg = {
            .sample_rate_hz = (uint32_t)input_sample_rate_,
            .clk_src = I2S_CLK_SRC_DEFAULT,
            .ext_clk_freq_hz = 0,
            .mclk_multiple = I2S_MCLK_MULTIPLE_256,
            .bclk_div = 8,
        },
        .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_tdm_slot_mask_t(I2S_TDM_SLOT0 | I2S_TDM_SLOT1 | I2S_TDM_SLOT2 | I2S_TDM_SLOT3),
            .ws_width = I2S_TDM_AUTO_WS_WIDTH,
            .ws_pol = false,
            .bit_shift = true,
            .left_align = false,
            .big_endian = false,
            .bit_order_lsb = false,
            .skip_mask = false,
            .total_slot = I2S_TDM_AUTO_SLOT_NUM
        },
        .gpio_cfg = {
            .mclk = mclk,
            .bclk = bclk,
            .ws = ws,
            .dout = I2S_GPIO_UNUSED,
            .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_tdm_mode(rx_handle_, &tdm_cfg));
    ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
    ESP_LOGI(TAG, "Duplex channels created");
}
void BoxAudioCodec::SetOutputVolume(int volume) {
    ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
    AudioCodec::SetOutputVolume(volume);
}
void BoxAudioCodec::EnableInput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == input_enabled_) {
        return;
    }
    if (enable) {
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 4,
            .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
            .sample_rate = (uint32_t)output_sample_rate_,
            .mclk_multiple = 0,
        };
        if (input_reference_) {
            fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
        }
        ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_in_channel_gain(input_dev_, ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0), input_gain_));
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    }
    AudioCodec::EnableInput(enable);
}
void BoxAudioCodec::EnableOutput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == output_enabled_) {
        return;
    }
    if (enable) {
        // Play 16bit 1 channel
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)output_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    }
    AudioCodec::EnableOutput(enable);
}
int BoxAudioCodec::Read(int16_t* dest, int samples) {
    if (input_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
    }
    return samples;
}
int BoxAudioCodec::Write(const int16_t* data, int samples) {
    if (output_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
    }
    return samples;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.h
New file
@@ -0,0 +1,40 @@
#ifndef _BOX_AUDIO_CODEC_H
#define _BOX_AUDIO_CODEC_H
#include "audio_codec.h"
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <mutex>
class BoxAudioCodec : public AudioCodec {
private:
    const audio_codec_data_if_t* data_if_ = nullptr;
    const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr;
    const audio_codec_if_t* out_codec_if_ = nullptr;
    const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr;
    const audio_codec_if_t* in_codec_if_ = nullptr;
    const audio_codec_gpio_if_t* gpio_if_ = nullptr;
    esp_codec_dev_handle_t output_dev_ = nullptr;
    esp_codec_dev_handle_t input_dev_ = nullptr;
    std::mutex data_if_mutex_;
    void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
    virtual int Read(int16_t* dest, int samples) override;
    virtual int Write(const int16_t* data, int samples) override;
public:
    BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate,
        gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
        gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference);
    virtual ~BoxAudioCodec();
    virtual void SetOutputVolume(int volume) override;
    virtual void EnableInput(bool enable) override;
    virtual void EnableOutput(bool enable) override;
};
#endif // _BOX_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.cc
New file
@@ -0,0 +1,20 @@
#include "dummy_audio_codec.h"
DummyAudioCodec::DummyAudioCodec(int input_sample_rate, int output_sample_rate) {
    duplex_ = true;
    input_reference_ = false;
    input_channels_ = 1;
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
}
DummyAudioCodec::~DummyAudioCodec() {
}
int DummyAudioCodec::Read(int16_t* dest, int samples) {
    return 0;
}
int DummyAudioCodec::Write(const int16_t* data, int samples) {
    return 0;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.h
New file
@@ -0,0 +1,16 @@
#ifndef _DUMMY_AUDIO_CODEC_H
#define _DUMMY_AUDIO_CODEC_H
#include "audio_codec.h"
class DummyAudioCodec : public AudioCodec {
private:
    virtual int Read(int16_t* dest, int samples) override;
    virtual int Write(const int16_t* data, int samples) override;
public:
    DummyAudioCodec(int input_sample_rate, int output_sample_rate);
    virtual ~DummyAudioCodec();
};
#endif // _DUMMY_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.cc
New file
@@ -0,0 +1,202 @@
#include "es8311_audio_codec.h"
#include <esp_log.h>
#define TAG "Es8311AudioCodec"
Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
    gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
    gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk, bool pa_inverted) {
    duplex_ = true; // æ˜¯å¦åŒå·¥
    input_reference_ = false; // æ˜¯å¦ä½¿ç”¨å‚考输入,实现回声消除
    input_channels_ = 1; // è¾“入通道数
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    pa_pin_ = pa_pin;
    pa_inverted_ = pa_inverted;
    input_gain_ = 30;
    assert(input_sample_rate_ == output_sample_rate_);
    CreateDuplexChannels(mclk, bclk, ws, dout, din);
    // Do initialize of related interface: data_if, ctrl_if and gpio_if
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = I2S_NUM_0,
        .rx_handle = rx_handle_,
        .tx_handle = tx_handle_,
    };
    data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
    assert(data_if_ != NULL);
    // Output
    audio_codec_i2c_cfg_t i2c_cfg = {
        .port = i2c_port,
        .addr = es8311_addr,
        .bus_handle = i2c_master_handle,
    };
    ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
    assert(ctrl_if_ != NULL);
    gpio_if_ = audio_codec_new_gpio();
    assert(gpio_if_ != NULL);
    es8311_codec_cfg_t es8311_cfg = {};
    es8311_cfg.ctrl_if = ctrl_if_;
    es8311_cfg.gpio_if = gpio_if_;
    es8311_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
    es8311_cfg.pa_pin = pa_pin;
    es8311_cfg.use_mclk = use_mclk;
    es8311_cfg.hw_gain.pa_voltage = 5.0;
    es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
    es8311_cfg.pa_reverted = pa_inverted_;
    codec_if_ = es8311_codec_new(&es8311_cfg);
    if (codec_if_ == nullptr) {
        ESP_LOGE(TAG, "Failed to create Es8311AudioCodec");
    } else {
        ESP_LOGI(TAG, "Es8311AudioCodec initialized");
    }
}
Es8311AudioCodec::~Es8311AudioCodec() {
    esp_codec_dev_delete(dev_);
    audio_codec_delete_codec_if(codec_if_);
    audio_codec_delete_ctrl_if(ctrl_if_);
    audio_codec_delete_gpio_if(gpio_if_);
    audio_codec_delete_data_if(data_if_);
}
void Es8311AudioCodec::UpdateDeviceState() {
    if ((input_enabled_ || output_enabled_) && dev_ == nullptr) {
        esp_codec_dev_cfg_t dev_cfg = {
            .dev_type = ESP_CODEC_DEV_TYPE_IN_OUT,
            .codec_if = codec_if_,
            .data_if = data_if_,
        };
        dev_ = esp_codec_dev_new(&dev_cfg);
        assert(dev_ != NULL);
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)input_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(dev_, input_gain_));
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, output_volume_));
    } else if (!input_enabled_ && !output_enabled_ && dev_ != nullptr) {
        esp_codec_dev_close(dev_);
        dev_ = nullptr;
    }
    if (pa_pin_ != GPIO_NUM_NC) {
        int level = output_enabled_ ? 1 : 0;
        gpio_set_level(pa_pin_, pa_inverted_ ? !level : level);
    }
}
void Es8311AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
    assert(input_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 = mclk,
            .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_ERROR_CHECK(i2s_channel_enable(tx_handle_));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
    ESP_LOGI(TAG, "Duplex channels created");
}
void Es8311AudioCodec::SetOutputVolume(int volume) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (dev_ != nullptr) {
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(dev_, volume));
    }
    AudioCodec::SetOutputVolume(volume);
}
void Es8311AudioCodec::EnableInput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (codec_if_ == nullptr) {
        return;
    }
    if (enable == input_enabled_) {
        return;
    }
    AudioCodec::EnableInput(enable);
    UpdateDeviceState();
}
void Es8311AudioCodec::EnableOutput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (codec_if_ == nullptr) {
        return;
    }
    if (enable == output_enabled_) {
        return;
    }
    AudioCodec::EnableOutput(enable);
    UpdateDeviceState();
}
int Es8311AudioCodec::Read(int16_t* dest, int samples) {
    if (input_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(dev_, (void*)dest, samples * sizeof(int16_t)));
    }
    return samples;
}
int Es8311AudioCodec::Write(const int16_t* data, int samples) {
    if (output_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(dev_, (void*)data, samples * sizeof(int16_t)));
    }
    return samples;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.h
New file
@@ -0,0 +1,42 @@
#ifndef _ES8311_AUDIO_CODEC_H
#define _ES8311_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/i2c_master.h>
#include <driver/gpio.h>
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <mutex>
class Es8311AudioCodec : public AudioCodec {
private:
    const audio_codec_data_if_t* data_if_ = nullptr;
    const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
    const audio_codec_if_t* codec_if_ = nullptr;
    const audio_codec_gpio_if_t* gpio_if_ = nullptr;
    esp_codec_dev_handle_t dev_ = nullptr;
    gpio_num_t pa_pin_ = GPIO_NUM_NC;
    bool pa_inverted_ = false;
    std::mutex data_if_mutex_;
    void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
    void UpdateDeviceState();
    virtual int Read(int16_t* dest, int samples) override;
    virtual int Write(const int16_t* data, int samples) override;
public:
    Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
        gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
        gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true, bool pa_inverted = false);
    virtual ~Es8311AudioCodec();
    virtual void SetOutputVolume(int volume) override;
    virtual void EnableInput(bool enable) override;
    virtual void EnableOutput(bool enable) override;
};
#endif // _ES8311_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.cc
New file
@@ -0,0 +1,200 @@
#include "es8374_audio_codec.h"
#include <esp_log.h>
#define TAG "Es8374AudioCodec"
Es8374AudioCodec::Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
    gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
    gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk) {
    duplex_ = true; // æ˜¯å¦åŒå·¥
    input_reference_ = false; // æ˜¯å¦ä½¿ç”¨å‚考输入,实现回声消除
    input_channels_ = 1; // è¾“入通道数
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    input_gain_ = 30;
    pa_pin_ = pa_pin;
    CreateDuplexChannels(mclk, bclk, ws, dout, din);
    // Do initialize of related interface: data_if, ctrl_if and gpio_if
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = I2S_NUM_0,
        .rx_handle = rx_handle_,
        .tx_handle = tx_handle_,
    };
    data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
    assert(data_if_ != NULL);
    // Output
    audio_codec_i2c_cfg_t i2c_cfg = {
        .port = i2c_port,
        .addr = es8374_addr,
        .bus_handle = i2c_master_handle,
    };
    ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
    assert(ctrl_if_ != NULL);
    gpio_if_ = audio_codec_new_gpio();
    assert(gpio_if_ != NULL);
    es8374_codec_cfg_t es8374_cfg = {};
    es8374_cfg.ctrl_if = ctrl_if_;
    es8374_cfg.gpio_if = gpio_if_;
    es8374_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
    es8374_cfg.pa_pin = pa_pin;
    codec_if_ = es8374_codec_new(&es8374_cfg);
    assert(codec_if_ != NULL);
    esp_codec_dev_cfg_t dev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_OUT,
        .codec_if = codec_if_,
        .data_if = data_if_,
    };
    output_dev_ = esp_codec_dev_new(&dev_cfg);
    assert(output_dev_ != NULL);
    dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_IN;
    input_dev_ = esp_codec_dev_new(&dev_cfg);
    assert(input_dev_ != NULL);
    esp_codec_set_disable_when_closed(output_dev_, false);
    esp_codec_set_disable_when_closed(input_dev_, false);
    ESP_LOGI(TAG, "Es8374AudioCodec initialized");
}
Es8374AudioCodec::~Es8374AudioCodec() {
    ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    esp_codec_dev_delete(output_dev_);
    ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    esp_codec_dev_delete(input_dev_);
    audio_codec_delete_codec_if(codec_if_);
    audio_codec_delete_ctrl_if(ctrl_if_);
    audio_codec_delete_gpio_if(gpio_if_);
    audio_codec_delete_data_if(data_if_);
}
void Es8374AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
    assert(input_sample_rate_ == output_sample_rate_);
    i2s_chan_config_t chan_cfg = {
        .id = I2S_NUM_0,
        .role = I2S_ROLE_MASTER,
        .dma_desc_num = 6,
        .dma_frame_num = 240,
        .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 = mclk,
            .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_ERROR_CHECK(i2s_channel_enable(tx_handle_));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
    ESP_LOGI(TAG, "Duplex channels created");
}
void Es8374AudioCodec::SetOutputVolume(int volume) {
    ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
    AudioCodec::SetOutputVolume(volume);
}
void Es8374AudioCodec::EnableInput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == input_enabled_) {
        return;
    }
    if (enable) {
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)input_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, input_gain_));
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    }
    AudioCodec::EnableInput(enable);
}
void Es8374AudioCodec::EnableOutput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == output_enabled_) {
        return;
    }
    if (enable) {
        // Play 16bit 1 channel
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)output_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
        if (pa_pin_ != GPIO_NUM_NC) {
            gpio_set_level(pa_pin_, 1);
        }
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
        if (pa_pin_ != GPIO_NUM_NC) {
            gpio_set_level(pa_pin_, 0);
        }
    }
    AudioCodec::EnableOutput(enable);
}
int Es8374AudioCodec::Read(int16_t* dest, int samples) {
    if (input_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
    }
    return samples;
}
int Es8374AudioCodec::Write(const int16_t* data, int samples) {
    if (output_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
    }
    return samples;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.h
New file
@@ -0,0 +1,41 @@
#ifndef _ES8374_AUDIO_CODEC_H
#define _ES8374_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/i2c.h>
#include <driver/gpio.h>
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <mutex>
class Es8374AudioCodec : public AudioCodec {
private:
    const audio_codec_data_if_t* data_if_ = nullptr;
    const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
    const audio_codec_if_t* codec_if_ = nullptr;
    const audio_codec_gpio_if_t* gpio_if_ = nullptr;
    esp_codec_dev_handle_t output_dev_ = nullptr;
    esp_codec_dev_handle_t input_dev_ = nullptr;
    gpio_num_t pa_pin_ = GPIO_NUM_NC;
    std::mutex data_if_mutex_;
    void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
    virtual int Read(int16_t* dest, int samples) override;
    virtual int Write(const int16_t* data, int samples) override;
public:
    Es8374AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
        gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
        gpio_num_t pa_pin, uint8_t es8374_addr, bool use_mclk = true);
    virtual ~Es8374AudioCodec();
    virtual void SetOutputVolume(int volume) override;
    virtual void EnableInput(bool enable) override;
    virtual void EnableOutput(bool enable) override;
};
#endif // _ES8374_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.cc
New file
@@ -0,0 +1,220 @@
#include "es8388_audio_codec.h"
#include <esp_log.h>
#define TAG "Es8388AudioCodec"
Es8388AudioCodec::Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
    gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
    gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference) {
    duplex_ = true; // æ˜¯å¦åŒå·¥
    input_reference_ = input_reference; // æ˜¯å¦ä½¿ç”¨å‚考输入,实现回声消除
    input_channels_ = input_reference_ ? 2 : 1; // è¾“入通道数
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    input_gain_ = 24;
    pa_pin_ = pa_pin;
    CreateDuplexChannels(mclk, bclk, ws, dout, din);
    // Do initialize of related interface: data_if, ctrl_if and gpio_if
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = I2S_NUM_0,
        .rx_handle = rx_handle_,
        .tx_handle = tx_handle_,
    };
    data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
    assert(data_if_ != NULL);
    // Output
    audio_codec_i2c_cfg_t i2c_cfg = {
        .port = i2c_port,
        .addr = es8388_addr,
        .bus_handle = i2c_master_handle,
    };
    ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
    assert(ctrl_if_ != NULL);
    gpio_if_ = audio_codec_new_gpio();
    assert(gpio_if_ != NULL);
    es8388_codec_cfg_t es8388_cfg = {};
    es8388_cfg.ctrl_if = ctrl_if_;
    es8388_cfg.gpio_if = gpio_if_;
    es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
    es8388_cfg.master_mode = true;
    es8388_cfg.pa_pin = pa_pin;
    es8388_cfg.pa_reverted = false;
    es8388_cfg.hw_gain.pa_voltage = 5.0;
    es8388_cfg.hw_gain.codec_dac_voltage = 3.3;
    codec_if_ = es8388_codec_new(&es8388_cfg);
    assert(codec_if_ != NULL);
    esp_codec_dev_cfg_t outdev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_OUT,
        .codec_if = codec_if_,
        .data_if = data_if_,
    };
    output_dev_ = esp_codec_dev_new(&outdev_cfg);
    assert(output_dev_ != NULL);
    esp_codec_dev_cfg_t indev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_IN,
        .codec_if = codec_if_,
        .data_if = data_if_,
    };
    input_dev_ = esp_codec_dev_new(&indev_cfg);
    assert(input_dev_ != NULL);
    esp_codec_set_disable_when_closed(output_dev_, false);
    esp_codec_set_disable_when_closed(input_dev_, false);
    ESP_LOGI(TAG, "Es8388AudioCodec initialized");
}
Es8388AudioCodec::~Es8388AudioCodec() {
    ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    esp_codec_dev_delete(output_dev_);
    ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    esp_codec_dev_delete(input_dev_);
    audio_codec_delete_codec_if(codec_if_);
    audio_codec_delete_ctrl_if(ctrl_if_);
    audio_codec_delete_gpio_if(gpio_if_);
    audio_codec_delete_data_if(data_if_);
}
void Es8388AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din){
    assert(input_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,
            .ext_clk_freq_hz = 0,
            .mclk_multiple = I2S_MCLK_MULTIPLE_256
        },
        .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,
            .left_align = true,
            .big_endian = false,
            .bit_order_lsb = false
        },
        .gpio_cfg = {
            .mclk = mclk,
            .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_ERROR_CHECK(i2s_channel_enable(tx_handle_));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
    ESP_LOGI(TAG, "Duplex channels created");
}
void Es8388AudioCodec::SetOutputVolume(int volume) {
    ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
    AudioCodec::SetOutputVolume(volume);
}
void Es8388AudioCodec::EnableInput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == input_enabled_) {
        return;
    }
    if (enable) {
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = (uint8_t) input_channels_,
            .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
            .sample_rate = (uint32_t)input_sample_rate_,
            .mclk_multiple = 0,
        };
        if (input_reference_) {
            fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1);
        }
        ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
        if (input_reference_) {
            uint8_t gain = (11 << 4) + 0;
            ctrl_if_->write_reg(ctrl_if_, 0x09, 1, &gain, 1);
        }else{
            ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, input_gain_));
        }
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    }
    AudioCodec::EnableInput(enable);
}
void Es8388AudioCodec::EnableOutput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == output_enabled_) {
        return;
    }
    if (enable) {
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)output_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
        // Set analog output volume to 0dB, default is -45dB
        uint8_t reg_val = 30; // 0dB
        uint8_t regs[] = { 46, 47, 48, 49 }; // HP_LVOL, HP_RVOL, SPK_LVOL, SPK_RVOL
        for (uint8_t reg : regs) {
            ctrl_if_->write_reg(ctrl_if_, reg, 1, &reg_val, 1);
        }
        if (pa_pin_ != GPIO_NUM_NC) {
            gpio_set_level(pa_pin_, 1);
        }
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
        if (pa_pin_ != GPIO_NUM_NC) {
            gpio_set_level(pa_pin_, 0);
        }
    }
    AudioCodec::EnableOutput(enable);
}
int Es8388AudioCodec::Read(int16_t* dest, int samples) {
    if (input_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
    }
    return samples;
}
int Es8388AudioCodec::Write(const int16_t* data, int samples) {
    if (output_enabled_ && output_dev_ && data != nullptr) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
    }
    return samples;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.h
New file
@@ -0,0 +1,40 @@
#ifndef _ES8388_AUDIO_CODEC_H
#define _ES8388_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/i2c_master.h>
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <mutex>
class Es8388AudioCodec : public AudioCodec {
private:
    const audio_codec_data_if_t* data_if_ = nullptr;
    const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
    const audio_codec_if_t* codec_if_ = nullptr;
    const audio_codec_gpio_if_t* gpio_if_ = nullptr;
    esp_codec_dev_handle_t output_dev_ = nullptr;
    esp_codec_dev_handle_t input_dev_ = nullptr;
    gpio_num_t pa_pin_ = GPIO_NUM_NC;
    std::mutex data_if_mutex_;
    void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
    virtual int Read(int16_t* dest, int samples) override;
    virtual int Write(const int16_t* data, int samples) override;
public:
    Es8388AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
        gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
        gpio_num_t pa_pin, uint8_t es8388_addr, bool input_reference = false);
    virtual ~Es8388AudioCodec();
    virtual void SetOutputVolume(int volume) override;
    virtual void EnableInput(bool enable) override;
    virtual void EnableOutput(bool enable) override;
};
#endif // _ES8388_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.cc
New file
@@ -0,0 +1,206 @@
#include "es8389_audio_codec.h"
#include <esp_log.h>
static const char TAG[] = "Es8389AudioCodec";
Es8389AudioCodec::Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
    gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
    gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk) {
    duplex_ = true; // æ˜¯å¦åŒå·¥
    input_reference_ = false; // æ˜¯å¦ä½¿ç”¨å‚考输入,实现回声消除
    input_channels_ = 1; // è¾“入通道数
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    input_gain_ = 40;
    pa_pin_ = pa_pin;
    CreateDuplexChannels(mclk, bclk, ws, dout, din);
    // Do initialize of related interface: data_if, ctrl_if and gpio_if
    audio_codec_i2s_cfg_t i2s_cfg = {
        .port = I2S_NUM_0,
        .rx_handle = rx_handle_,
        .tx_handle = tx_handle_,
    };
    data_if_ = audio_codec_new_i2s_data(&i2s_cfg);
    assert(data_if_ != NULL);
    // Output
    audio_codec_i2c_cfg_t i2c_cfg = {
        .port = i2c_port,
        .addr = es8389_addr,
        .bus_handle = i2c_master_handle,
    };
    ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
    assert(ctrl_if_ != NULL);
    gpio_if_ = audio_codec_new_gpio();
    assert(gpio_if_ != NULL);
    es8389_codec_cfg_t es8389_cfg = {};
    es8389_cfg.ctrl_if = ctrl_if_;
    es8389_cfg.gpio_if = gpio_if_;
    es8389_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_BOTH;
    es8389_cfg.pa_pin = pa_pin;
    es8389_cfg.use_mclk = use_mclk;
    es8389_cfg.hw_gain.pa_voltage = 5.0;
    es8389_cfg.hw_gain.codec_dac_voltage = 3.3;
    codec_if_ = es8389_codec_new(&es8389_cfg);
    assert(codec_if_ != NULL);
    esp_codec_dev_cfg_t outdev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_OUT,
        .codec_if = codec_if_,
        .data_if = data_if_,
    };
    output_dev_ = esp_codec_dev_new(&outdev_cfg);
    assert(output_dev_ != NULL);
    esp_codec_dev_cfg_t indev_cfg = {
        .dev_type = ESP_CODEC_DEV_TYPE_IN,
        .codec_if = codec_if_,
        .data_if = data_if_,
    };
    input_dev_ = esp_codec_dev_new(&indev_cfg);
    assert(input_dev_ != NULL);
    esp_codec_set_disable_when_closed(output_dev_, false);
    esp_codec_set_disable_when_closed(input_dev_, false);
    ESP_LOGI(TAG, "Es8389AudioCodec initialized");
}
Es8389AudioCodec::~Es8389AudioCodec() {
    ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
    esp_codec_dev_delete(output_dev_);
    ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    esp_codec_dev_delete(input_dev_);
    audio_codec_delete_codec_if(codec_if_);
    audio_codec_delete_ctrl_if(ctrl_if_);
    audio_codec_delete_gpio_if(gpio_if_);
    audio_codec_delete_data_if(data_if_);
}
void Es8389AudioCodec::CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
    assert(input_sample_rate_ == output_sample_rate_);
    i2s_chan_config_t chan_cfg = {
        .id = I2S_NUM_0,
        .role = I2S_ROLE_MASTER,
        .dma_desc_num = 6,
        .dma_frame_num = 240,
        .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,
            .left_align = true,
            .big_endian = false,
            .bit_order_lsb = false
        },
        .gpio_cfg = {
            .mclk = mclk,
            .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_ERROR_CHECK(i2s_channel_enable(tx_handle_));
    ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
    ESP_LOGI(TAG, "Duplex channels created");
}
void Es8389AudioCodec::SetOutputVolume(int volume) {
    ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
    AudioCodec::SetOutputVolume(volume);
}
void Es8389AudioCodec::EnableInput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == input_enabled_) {
        return;
    }
    if (enable) {
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)input_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, input_gain_));
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
    }
    AudioCodec::EnableInput(enable);
}
void Es8389AudioCodec::EnableOutput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == output_enabled_) {
        return;
    }
    if (enable) {
        // Play 16bit 1 channel
        esp_codec_dev_sample_info_t fs = {
            .bits_per_sample = 16,
            .channel = 1,
            .channel_mask = 0,
            .sample_rate = (uint32_t)output_sample_rate_,
            .mclk_multiple = 0,
        };
        ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
        ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
        if (pa_pin_ != GPIO_NUM_NC) {
            gpio_set_level(pa_pin_, 1);
        }
    } else {
        ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
        if (pa_pin_ != GPIO_NUM_NC) {
            gpio_set_level(pa_pin_, 0);
        }
    }
    AudioCodec::EnableOutput(enable);
}
int Es8389AudioCodec::Read(int16_t* dest, int samples) {
    if (input_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
    }
    return samples;
}
int Es8389AudioCodec::Write(const int16_t* data, int samples) {
    if (output_enabled_) {
        ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
    }
    return samples;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.h
New file
@@ -0,0 +1,40 @@
#ifndef _ES8389_AUDIO_CODEC_H
#define _ES8389_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/i2c.h>
#include <driver/gpio.h>
#include <esp_codec_dev.h>
#include <esp_codec_dev_defaults.h>
#include <mutex>
class Es8389AudioCodec : public AudioCodec {
private:
    const audio_codec_data_if_t* data_if_ = nullptr;
    const audio_codec_ctrl_if_t* ctrl_if_ = nullptr;
    const audio_codec_if_t* codec_if_ = nullptr;
    const audio_codec_gpio_if_t* gpio_if_ = nullptr;
    esp_codec_dev_handle_t output_dev_ = nullptr;
    esp_codec_dev_handle_t input_dev_ = nullptr;
    gpio_num_t pa_pin_ = GPIO_NUM_NC;
    std::mutex data_if_mutex_;
    void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
    virtual int Read(int16_t* dest, int samples) override;
    virtual int Write(const int16_t* data, int samples) override;
public:
    Es8389AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
        gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
        gpio_num_t pa_pin, uint8_t es8389_addr, bool use_mclk = true);
    virtual ~Es8389AudioCodec();
    virtual void SetOutputVolume(int volume) override;
    virtual void EnableInput(bool enable) override;
    virtual void EnableOutput(bool enable) override;
};
#endif // _ES8389_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.cc
New file
@@ -0,0 +1,385 @@
#include "no_audio_codec.h"
#include <esp_log.h>
#include <cmath>
#include <cstring>
#define TAG "NoAudioCodec"
NoAudioCodec::~NoAudioCodec() {
    if (rx_handle_ != nullptr) {
        ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
    }
    if (tx_handle_ != nullptr) {
        ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_));
    }
}
NoAudioCodecDuplex::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_32BIT,
            .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
            .slot_mode = I2S_SLOT_MODE_MONO,
            .slot_mask = I2S_STD_SLOT_LEFT,
            .ws_width = I2S_DATA_BIT_WIDTH_32BIT,
            .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;
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    // Create a new channel for speaker
    i2s_chan_config_t chan_cfg = {
        .id = (i2s_port_t)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_, nullptr));
    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_32BIT,
            .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
            .slot_mode = I2S_SLOT_MODE_MONO,
            .slot_mask = I2S_STD_SLOT_LEFT,
            .ws_width = I2S_DATA_BIT_WIDTH_32BIT,
            .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 = spk_bclk,
            .ws = spk_ws,
            .dout = spk_dout,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false
            }
        }
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
    // Create a new channel for MIC
    chan_cfg.id = (i2s_port_t)1;
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_));
    std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_;
    std_cfg.gpio_cfg.bclk = mic_sck;
    std_cfg.gpio_cfg.ws = mic_ws;
    std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED;
    std_cfg.gpio_cfg.din = mic_din;
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
    ESP_LOGI(TAG, "Simplex 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, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask){
    duplex_ = false;
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    // Create a new channel for speaker
    i2s_chan_config_t chan_cfg = {
        .id = (i2s_port_t)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_, nullptr));
    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_32BIT,
            .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
            .slot_mode = I2S_SLOT_MODE_MONO,
            .slot_mask = spk_slot_mask,
            .ws_width = I2S_DATA_BIT_WIDTH_32BIT,
            .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 = spk_bclk,
            .ws = spk_ws,
            .dout = spk_dout,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv = false
            }
        }
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
    // Create a new channel for MIC
    chan_cfg.id = (i2s_port_t)1;
    ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, nullptr, &rx_handle_));
    std_cfg.clk_cfg.sample_rate_hz = (uint32_t)input_sample_rate_;
    std_cfg.slot_cfg.slot_mask = mic_slot_mask;
    std_cfg.gpio_cfg.bclk = mic_sck;
    std_cfg.gpio_cfg.ws = mic_ws;
    std_cfg.gpio_cfg.dout = I2S_GPIO_UNUSED;
    std_cfg.gpio_cfg.din = mic_din;
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
    ESP_LOGI(TAG, "Simplex channels created");
}
int NoAudioCodec::Write(const int16_t* data, int samples) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    std::vector<int32_t> buffer(samples);
    // output_volume_: 0-100
    // volume_factor_: 0-65536
    int32_t volume_factor = pow(double(output_volume_) / 100.0, 2) * 65536;
    for (int i = 0; i < samples; i++) {
        int64_t temp = int64_t(data[i]) * volume_factor; // ä½¿ç”¨ int64_t è¿›è¡Œä¹˜æ³•运算
        if (temp > INT32_MAX) {
            buffer[i] = INT32_MAX;
        } else if (temp < INT32_MIN) {
            buffer[i] = INT32_MIN;
        } else {
            buffer[i] = static_cast<int32_t>(temp);
        }
    }
    size_t bytes_written;
    ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), samples * sizeof(int32_t), &bytes_written, portMAX_DELAY));
    return bytes_written / sizeof(int32_t);
}
int NoAudioCodec::Read(int16_t* dest, int samples) {
    size_t bytes_read;
    constexpr TickType_t kReadTimeoutTicks = pdMS_TO_TICKS(200);
    std::vector<int32_t> bit32_buffer(samples);
    if (i2s_channel_read(rx_handle_, bit32_buffer.data(), samples * sizeof(int32_t), &bytes_read, kReadTimeoutTicks) != ESP_OK) {
        return 0;
    }
    samples = bytes_read / sizeof(int32_t);
    for (int i = 0; i < samples; i++) {
        int32_t value = bit32_buffer[i] >> 12;
        dest[i] = (value > INT16_MAX) ? INT16_MAX : (value < -INT16_MAX) ? -INT16_MAX : (int16_t)value;
    }
    return samples;
}
void NoAudioCodec::EnableInput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == input_enabled_) {
        return;
    }
    if (enable) {
        ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_));
    } else {
        ESP_ERROR_CHECK(i2s_channel_disable(rx_handle_));
    }
    AudioCodec::EnableInput(enable);
}
void NoAudioCodec::EnableOutput(bool enable) {
    std::lock_guard<std::mutex> lock(data_if_mutex_);
    if (enable == output_enabled_) {
        return;
    }
    if (enable) {
        ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
    } else {
        ESP_ERROR_CHECK(i2s_channel_disable(tx_handle_));
    }
    AudioCodec::EnableOutput(enable);
}
// Delegating constructor: calls the main constructor with default slot mask
NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(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_din)
    : NoAudioCodecSimplexPdm(input_sample_rate, output_sample_rate, spk_bclk, spk_ws, spk_dout, I2S_STD_SLOT_LEFT, mic_sck, mic_din) {
    // All initialization is handled by the delegated constructor
}
NoAudioCodecSimplexPdm::NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_din) {
    duplex_ = false;
    input_sample_rate_ = input_sample_rate;
    output_sample_rate_ = output_sample_rate;
    // Create a new channel for speaker
    i2s_chan_config_t tx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)1, I2S_ROLE_MASTER);
    tx_chan_cfg.dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM;
    tx_chan_cfg.dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM;
    tx_chan_cfg.auto_clear_after_cb = true;
    tx_chan_cfg.auto_clear_before_cb = false;
    tx_chan_cfg.intr_priority = 0;
    ESP_ERROR_CHECK(i2s_new_channel(&tx_chan_cfg, &tx_handle_, NULL));
    i2s_std_config_t tx_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_32BIT,
            .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
            .slot_mode = I2S_SLOT_MODE_MONO,
            .slot_mask = spk_slot_mask,
            .ws_width = I2S_DATA_BIT_WIDTH_32BIT,
            .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 = spk_bclk,
            .ws = spk_ws,
            .dout = spk_dout,
            .din = I2S_GPIO_UNUSED,
            .invert_flags = {
                .mclk_inv = false,
                .bclk_inv = false,
                .ws_inv   = false,
            },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &tx_std_cfg));
#if SOC_I2S_SUPPORTS_PDM_RX
    // Create a new channel for MIC in PDM mode
    i2s_chan_config_t rx_chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG((i2s_port_t)0, I2S_ROLE_MASTER);
    ESP_ERROR_CHECK(i2s_new_channel(&rx_chan_cfg, NULL, &rx_handle_));
    i2s_pdm_rx_config_t pdm_rx_cfg = {
        .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG((uint32_t)input_sample_rate_),
        /* The data bit-width of PDM mode is fixed to 16 */
        .slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
        .gpio_cfg = {
            .clk = mic_sck,
            .din = mic_din,
            .invert_flags = {
                .clk_inv = false,
            },
        },
    };
    ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &pdm_rx_cfg));
#else
    ESP_LOGE(TAG, "PDM is not supported");
#endif
    ESP_LOGI(TAG, "Simplex channels created");
}
int NoAudioCodecSimplexPdm::Read(int16_t* dest, int samples) {
    size_t bytes_read;
    // 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);
    if (input_gain_ > 0) {
        int gain_factor = (int)input_gain_;
        for (int i = 0; i < samples; i++) {
            int32_t amplified = dest[i] * gain_factor;
            dest[i] = (amplified > INT16_MAX) ? INT16_MAX : (amplified < -INT16_MAX) ? -INT16_MAX : (int16_t)amplified;
        }
    }
    return samples;
}
xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.h
New file
@@ -0,0 +1,41 @@
#ifndef _NO_AUDIO_CODEC_H
#define _NO_AUDIO_CODEC_H
#include "audio_codec.h"
#include <driver/gpio.h>
#include <driver/i2s_pdm.h>
#include <mutex>
class NoAudioCodec : public AudioCodec {
protected:
    std::mutex data_if_mutex_;
    virtual int Write(const int16_t* data, int samples) override;
    virtual int Read(int16_t* dest, int samples) override;
    virtual void EnableInput(bool enable) override;
    virtual void EnableOutput(bool enable) override;
public:
    virtual ~NoAudioCodec();
};
class NoAudioCodecDuplex : public NoAudioCodec {
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 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);
    NoAudioCodecSimplex(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck, gpio_num_t mic_ws, gpio_num_t mic_din, i2s_std_slot_mask_t mic_slot_mask);
};
class NoAudioCodecSimplexPdm : public NoAudioCodec {
public:
    NoAudioCodecSimplexPdm(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_din);
    NoAudioCodecSimplexPdm(int input_sample_rate, int output_sample_rate, gpio_num_t spk_bclk, gpio_num_t spk_ws, gpio_num_t spk_dout, i2s_std_slot_mask_t spk_slot_mask, gpio_num_t mic_sck,  gpio_num_t mic_din);
    int Read(int16_t* dest, int samples);
};
#endif // _NO_AUDIO_CODEC_H
xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.cc
New file
@@ -0,0 +1,311 @@
#include "ogg_demuxer.h"
#include "esp_log.h"
#define TAG "OggDemuxer"
/// @brief é‡ç½®è§£å°å™¨
void OggDemuxer::Reset()
{
    opus_info_ = {
        .head_seen = false,
        .tags_seen = false,
        .sample_rate = 48000
    };
    state_ = ParseState::FIND_PAGE;
    ctx_.packet_len = 0;
    ctx_.seg_count = 0;
    ctx_.seg_index = 0;
    ctx_.data_offset = 0;
    ctx_.bytes_needed = 4;          // éœ€è¦4字节"OggS"
    ctx_.seg_remaining = 0;
    ctx_.body_size = 0;
    ctx_.body_offset = 0;
    ctx_.packet_continued = false;
    // æ¸…空缓冲区数据
    memset(ctx_.header, 0, sizeof(ctx_.header));
    memset(ctx_.seg_table, 0, sizeof(ctx_.seg_table));
    memset(ctx_.packet_buf, 0, sizeof(ctx_.packet_buf));
}
/// @brief å¤„理数据块
/// @param data è¾“入数据
/// @param size è¾“入数据大小
/// @return å·²å¤„理的字节数
size_t OggDemuxer::Process(const uint8_t* data, size_t size)
{
    size_t processed = 0;  // å·²å¤„理的字节数
    while (processed < size) {
        switch (state_) {
          case ParseState::FIND_PAGE: {
            // å¯»æ‰¾é¡µå¤´"OggS"
            if (ctx_.bytes_needed < 4) {
                // å¤„理不完整的"OggS"匹配(跨数据块)
                size_t to_copy = std::min(size - processed, ctx_.bytes_needed);
                memcpy(ctx_.header + (4 - ctx_.bytes_needed), data + processed, to_copy);
                processed += to_copy;
                ctx_.bytes_needed -= to_copy;
                if (ctx_.bytes_needed == 0) {
                    // æ£€æŸ¥æ˜¯å¦åŒ¹é…"OggS"
                    if (memcmp(ctx_.header, "OggS", 4) == 0) {
                        state_ = ParseState::PARSE_HEADER;
                        ctx_.data_offset = 4;
                        ctx_.bytes_needed = 27 - 4;  // è¿˜éœ€è¦23字节完成页头
                    } else {
                        // åŒ¹é…å¤±è´¥ï¼Œæ»‘动1字节继续匹配
                        memmove(ctx_.header, ctx_.header + 1, 3);
                        ctx_.bytes_needed = 1;
                    }
                } else {
                    // æ•°æ®ä¸è¶³ï¼Œç­‰å¾…更多数据
                    return processed;
                }
            } else if (ctx_.bytes_needed == 4) {
                // åœ¨æ•°æ®å—中查找完整的"OggS"
                bool found = false;
                size_t i = 0;
                size_t remaining = size - processed;
                // æœç´¢"OggS"
                for (; i + 4 <= remaining; i++) {
                    if (memcmp(data + processed + i, "OggS", 4) == 0) {
                        found = true;
                        break;
                    }
                }
                if (found) {
                    // æ‰¾åˆ°"OggS",跳过已搜索的字节
                    processed += i;
                    // ä¸è®°å½•找到的"OggS",无必要
                    // memcpy(ctx_.header, data + processed, 4);
                    processed += 4;
                    state_ = ParseState::PARSE_HEADER;
                    ctx_.data_offset = 4;
                    ctx_.bytes_needed = 27 - 4;  // è¿˜éœ€è¦23字节
                } else {
                    // æ²¡æœ‰æ‰¾åˆ°å®Œæ•´"OggS",保存可能的部分匹配
                    size_t partial_len = remaining - i;
                    if (partial_len > 0) {
                        memcpy(ctx_.header, data + processed + i, partial_len);
                        ctx_.bytes_needed = 4 - partial_len;
                        processed += i + partial_len;
                    } else {
                        processed += i;  // å·²æœç´¢æ‰€æœ‰å­—节
                    }
                    return processed;  // è¿”回已处理的字节数
                }
            } else {
                ESP_LOGE(TAG, "OggDemuxer run in error state: bytes_needed=%zu", ctx_.bytes_needed);
                Reset();
                return processed;
            }
            break;
          }
          case ParseState::PARSE_HEADER: {
            size_t available = size - processed;
            if (available < ctx_.bytes_needed) {
                // æ•°æ®ä¸è¶³ï¼Œå¤åˆ¶å¯ç”¨çš„部分
                memcpy(ctx_.header + ctx_.data_offset,
                        data + processed, available);
                ctx_.data_offset += available;
                ctx_.bytes_needed -= available;
                processed += available;
                return processed;  // ç­‰å¾…更多数据
            } else {
                // æœ‰è¶³å¤Ÿçš„æ•°æ®å®Œæˆé¡µå¤´
                size_t to_copy = ctx_.bytes_needed;
                memcpy(ctx_.header + ctx_.data_offset,
                        data + processed, to_copy);
                processed += to_copy;
                ctx_.data_offset += to_copy;
                ctx_.bytes_needed = 0;
                // éªŒè¯é¡µå¤´
                if (ctx_.header[4] != 0) {
                    ESP_LOGE(TAG, "无效的Ogg版本: %d", ctx_.header[4]);
                    state_ = ParseState::FIND_PAGE;
                    ctx_.bytes_needed = 4;
                    ctx_.data_offset = 0;
                    break;
                }
                ctx_.seg_count = ctx_.header[26];
                if (ctx_.seg_count > 0 && ctx_.seg_count <= 255) {
                    state_ = ParseState::PARSE_SEGMENTS;
                    ctx_.bytes_needed = ctx_.seg_count;
                    ctx_.data_offset = 0;
                } else if (ctx_.seg_count == 0) {
                    // æ²¡æœ‰æ®µï¼Œç›´æŽ¥è·³åˆ°ä¸‹ä¸€ä¸ªé¡µé¢
                    state_ = ParseState::FIND_PAGE;
                    ctx_.bytes_needed = 4;
                    ctx_.data_offset = 0;
                } else {
                    ESP_LOGE(TAG, "无效的段数: %u", ctx_.seg_count);
                    state_ = ParseState::FIND_PAGE;
                    ctx_.bytes_needed = 4;
                    ctx_.data_offset = 0;
                }
            }
            break;
        }
          case ParseState::PARSE_SEGMENTS: {
            size_t available = size - processed;
            if (available < ctx_.bytes_needed) {
                memcpy(ctx_.seg_table + ctx_.data_offset,
                        data + processed, available);
                ctx_.data_offset += available;
                ctx_.bytes_needed -= available;
                processed += available;
                return processed;  // ç­‰å¾…更多数据
            } else {
                size_t to_copy = ctx_.bytes_needed;
                memcpy(ctx_.seg_table + ctx_.data_offset,
                        data + processed, to_copy);
                processed += to_copy;
                ctx_.data_offset += to_copy;
                ctx_.bytes_needed = 0;
                state_ = ParseState::PARSE_DATA;
                ctx_.seg_index = 0;
                ctx_.data_offset = 0;
                // è®¡ç®—数据体总大小
                ctx_.body_size = 0;
                for (size_t i = 0; i < ctx_.seg_count; ++i) {
                    ctx_.body_size += ctx_.seg_table[i];
                }
                ctx_.body_offset = 0;
                ctx_.seg_remaining = 0;
            }
            break;
        }
          case ParseState::PARSE_DATA: {
            while (ctx_.seg_index < ctx_.seg_count && processed < size) {
                uint8_t seg_len = ctx_.seg_table[ctx_.seg_index];
                // æ£€æŸ¥æ®µæ•°æ®æ˜¯å¦å·²ç»éƒ¨åˆ†è¯»å–
                if (ctx_.seg_remaining > 0) {
                    seg_len = ctx_.seg_remaining;
                } else {
                    ctx_.seg_remaining = seg_len;
                }
                // æ£€æŸ¥ç¼“冲区是否足够
                if (ctx_.packet_len + seg_len > sizeof(ctx_.packet_buf)) {
                    ESP_LOGE(TAG, "包缓冲区溢出: %zu + %u > %zu", ctx_.packet_len, seg_len, sizeof(ctx_.packet_buf));
                    state_ = ParseState::FIND_PAGE;
                    ctx_.packet_len = 0;
                    ctx_.packet_continued = false;
                    ctx_.seg_remaining = 0;
                    ctx_.bytes_needed = 4;
                    return processed;
                }
                // å¤åˆ¶æ•°æ®
                size_t to_copy = std::min(size - processed, (size_t)seg_len);
                memcpy(ctx_.packet_buf + ctx_.packet_len, data + processed, to_copy);
                processed += to_copy;
                ctx_.packet_len += to_copy;
                ctx_.body_offset += to_copy;
                ctx_.seg_remaining -= to_copy;
                // æ£€æŸ¥æ®µæ˜¯å¦å®Œæ•´
                if (ctx_.seg_remaining > 0) {
                    // æ®µä¸å®Œæ•´ï¼Œç­‰å¾…更多数据
                    return processed;
                }
                // æ®µå®Œæ•´
                bool seg_continued = (ctx_.seg_table[ctx_.seg_index] == 255);
                if (!seg_continued) {
                    // åŒ…结束
                    if (ctx_.packet_len) {
                        if (!opus_info_.head_seen) {
                            if (ctx_.packet_len >=8 && memcmp(ctx_.packet_buf, "OpusHead", 8) == 0) {
                                opus_info_.head_seen = true;
                                if (ctx_.packet_len >= 19) {
                                    opus_info_.sample_rate = ctx_.packet_buf[12] |
                                                            (ctx_.packet_buf[13] << 8) |
                                                            (ctx_.packet_buf[14] << 16) |
                                                            (ctx_.packet_buf[15] << 24);
                                    ESP_LOGD(TAG, "OpusHead found, sample_rate=%d", opus_info_.sample_rate);
                                }
                                ctx_.packet_len = 0;
                                ctx_.packet_continued = false;
                                ctx_.seg_index++;
                                ctx_.seg_remaining = 0;
                                continue;
                            }
                        }
                        if (!opus_info_.tags_seen) {
                            if (ctx_.packet_len >= 8 && memcmp(ctx_.packet_buf, "OpusTags", 8) == 0) {
                                opus_info_.tags_seen = true;
                                ESP_LOGD(TAG, "OpusTags found.");
                                ctx_.packet_len = 0;
                                ctx_.packet_continued = false;
                                ctx_.seg_index++;
                                ctx_.seg_remaining = 0;
                                continue;
                            }
                        }
                        if (opus_info_.head_seen && opus_info_.tags_seen) {
                            if (on_demuxer_finished_) {
                                on_demuxer_finished_(ctx_.packet_buf, opus_info_.sample_rate, ctx_.packet_len);
                            }
                        } else {
                            ESP_LOGW(TAG, "当前Ogg容器未解析到OpusHead/OpusTags,丢弃");
                        }
                    }
                    ctx_.packet_len = 0;
                    ctx_.packet_continued = false;
                } else {
                    ctx_.packet_continued = true;
                }
                ctx_.seg_index++;
                ctx_.seg_remaining = 0;
            }
            if (ctx_.seg_index == ctx_.seg_count) {
                // æ£€æŸ¥æ˜¯å¦æ‰€æœ‰æ•°æ®ä½“都已读取
                if (ctx_.body_offset < ctx_.body_size) {
                    ESP_LOGW(TAG, "数据体不完整: %zu/%zu",
                            ctx_.body_offset, ctx_.body_size);
                }
                // å¦‚果包跨页,保持packet_len和packet_continued
                if (!ctx_.packet_continued) {
                    ctx_.packet_len = 0;
                }
                // è¿›å…¥ä¸‹ä¸€é¡µé¢
                state_ = ParseState::FIND_PAGE;
                ctx_.bytes_needed = 4;
                ctx_.data_offset = 0;
            }
            break;
        }
        }
    }
    return processed;
}
Diff truncated after the above file
xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.h xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.cc xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.h xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.cc xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.h xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.cc xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.h xiaozhi-esp32-2.2.6/main/audio/wake_word.h xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.cc xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.h xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.cc xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.h xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.cc xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.h xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README.md xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README_en.md xiaozhi-esp32-2.2.6/main/boards/aipi-lite/aipi-lite.cc xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.h xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.json xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config_en.json xiaozhi-esp32-2.2.6/main/boards/aipi-lite/power_manager.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.json xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.json xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/power_manager.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.json xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/power_manager.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.json xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/power_manager.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.json xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/config.h xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/README.md xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/atom_echos3r.cc xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.h xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.json xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/README.md xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.h xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.json xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/README.md xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/atoms3_echo_base.cc xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.h xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.json xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/README.md xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.h xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.json xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/README.md xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.h xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.json xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.json xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/README.md xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.json xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/esp32_bread_board.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/compact_ml307_board.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.json xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/compact_nt26_board.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.json xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/README.md xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/compact_wifi_board.cc xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.cc xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.h xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.cc xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.h xiaozhi-esp32-2.2.6/main/boards/common/axp2101.cc xiaozhi-esp32-2.2.6/main/boards/common/axp2101.h xiaozhi-esp32-2.2.6/main/boards/common/backlight.cc xiaozhi-esp32-2.2.6/main/boards/common/backlight.h xiaozhi-esp32-2.2.6/main/boards/common/blufi.cpp xiaozhi-esp32-2.2.6/main/boards/common/blufi.h xiaozhi-esp32-2.2.6/main/boards/common/board.cc xiaozhi-esp32-2.2.6/main/boards/common/board.h xiaozhi-esp32-2.2.6/main/boards/common/button.cc xiaozhi-esp32-2.2.6/main/boards/common/button.h xiaozhi-esp32-2.2.6/main/boards/common/camera.h xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.cc xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.h xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.cc xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.h xiaozhi-esp32-2.2.6/main/boards/common/esp_video.cc xiaozhi-esp32-2.2.6/main/boards/common/esp_video.h xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.cc xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.h xiaozhi-esp32-2.2.6/main/boards/common/knob.cc xiaozhi-esp32-2.2.6/main/boards/common/knob.h xiaozhi-esp32-2.2.6/main/boards/common/lamp_controller.h xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.cc xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.h xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.cc xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.h xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.cc xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.h xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.cc xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.h xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.cc xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.h xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.cc xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.h xiaozhi-esp32-2.2.6/main/boards/common/sy6970.cc xiaozhi-esp32-2.2.6/main/boards/common/sy6970.h xiaozhi-esp32-2.2.6/main/boards/common/system_reset.cc xiaozhi-esp32-2.2.6/main/boards/common/system_reset.h xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.cc xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.h xiaozhi-esp32-2.2.6/main/boards/df-k10/README.md xiaozhi-esp32-2.2.6/main/boards/df-k10/config.h xiaozhi-esp32-2.2.6/main/boards/df-k10/config.json xiaozhi-esp32-2.2.6/main/boards/df-k10/df_k10_board.cc xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.cc xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.h xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/README.md xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.h xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.json xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/README.md xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.h xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.json xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/doit_s3_aibox.cc xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.h xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.json xiaozhi-esp32-2.2.6/main/boards/du-chatx/du-chatx-wifi.cc xiaozhi-esp32-2.2.6/main/boards/du-chatx/power_manager.h xiaozhi-esp32-2.2.6/main/boards/electron-bot/README.md xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.h xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.json xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot.cc xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot_controller.cc xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.cc xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.h xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.cc xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.h xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.cc xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.h xiaozhi-esp32-2.2.6/main/boards/electron-bot/otto_icon_font.c xiaozhi-esp32-2.2.6/main/boards/electron-bot/power_manager.h xiaozhi-esp32-2.2.6/main/boards/esp-box-3/README.md xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.h xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.json xiaozhi-esp32-2.2.6/main/boards/esp-box-3/esp_box3_board.cc xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.cc xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.h xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.h xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.json xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/esp_box_lite_board.cc xiaozhi-esp32-2.2.6/main/boards/esp-box/config.h xiaozhi-esp32-2.2.6/main/boards/esp-box/config.json xiaozhi-esp32-2.2.6/main/boards/esp-box/esp_box_board.cc xiaozhi-esp32-2.2.6/main/boards/esp-hi/README.md xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.h xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.json xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.cc xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.h xiaozhi-esp32-2.2.6/main/boards/esp-hi/esp_hi.cc xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/README.md xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.h xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.json xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/README.md xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.h xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.json xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/pin_config.h xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/README.md xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.h xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.json xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/pin_config.h xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/README.md xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.h xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.json xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/esp-sensairshuttle.cc xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.h xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.json xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/esp_sparkbot_board.cc xiaozhi-esp32-2.2.6/main/boards/esp-spot/README.md xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.h xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.json xiaozhi-esp32-2.2.6/main/boards/esp-spot/esp_spot_board.cc xiaozhi-esp32-2.2.6/main/boards/esp-vocat/README.md xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.h xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.json xiaozhi-esp32-2.2.6/main/boards/esp-vocat/esp_vocat.cc xiaozhi-esp32-2.2.6/main/boards/esp-vocat/touch.h xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/README.md xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.h xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.json xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager.h xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager_lite.h xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/README.md xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.h xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.json xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/esp32_cgc_board.cc xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.h xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.json xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/esp32s3_korvo2_v3_board.cc xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/power_manager.h xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.h xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.json xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/power_manager.h xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/ReadMe.md xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.h xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.json xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.h xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.json xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/power_manager.h xiaozhi-esp32-2.2.6/main/boards/hu-087/README.md xiaozhi-esp32-2.2.6/main/boards/hu-087/config.h xiaozhi-esp32-2.2.6/main/boards/hu-087/config.json xiaozhi-esp32-2.2.6/main/boards/hu-087/hu_087_board.cc xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/README.md xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.h xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.json xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/gpio_manager.h xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_controller.h xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_manager.h xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.h xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.json xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/kevin_box_board.cc xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.h xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.json xiaozhi-esp32-2.2.6/main/boards/kevin-c3/kevin_c3_board.cc xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.cc xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.h xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/config.h xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.h xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.json xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.h xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.json xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/pin_config.h xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/README.md xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.h xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.json xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/README.md xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.h xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.json xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/mpython_pro.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/README.md xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.json xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_controller.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_robot_pro.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/README.md xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.json xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_controller.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.cc xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/README.md xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.h xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.json xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/eda-tv-pro.cc xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/README.md xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.h xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.json xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.h xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.json xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/lichuang_dev_board.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/README.md xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.json xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/pin_config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/README.md xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.json xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/pin_config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/README.md xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/t_display_p4_config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/README.md xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.h xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.json xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.h xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.h xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/README.md xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.h xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.json xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/m5stack_core_s3.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/README.md xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.h xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.json xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.c xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.h xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/m5stack_tab5.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/sdkconfig.tab5 xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.h xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.json xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/magiclick_2p4_board.cc xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.h xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.json xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/magiclick_2p5_board.cc xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/power_manager.h xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.h xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.json xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.h xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.json xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/magiclick_c3_board.cc xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/README.md xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.h xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.json xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/minsi_k08_dual.cc xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/power_manager.h xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/README.md xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.h xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.json xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/mixgo-nova.cc xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/README.md xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.h xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.json xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/README.md xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.h xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.json xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README.md xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README_zh.md xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.h xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.json xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/README.md xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_board.cc xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.h xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.json xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/power_manager.h xiaozhi-esp32-2.2.6/main/boards/otto-robot/README.md xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.h xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.json xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.cc xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.h xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_controller.cc xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.cc xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.h xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_icon_font.c xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.cc xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.h xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_robot.cc xiaozhi-esp32-2.2.6/main/boards/otto-robot/power_manager.h xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.cc xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.h xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README.md xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README_en.md xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.h xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.json xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config_en.json xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.cc xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.h xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_watcher.cc xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.cc xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.h xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/README.md xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.h xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.json xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/power_manager.h xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/README.md xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.h xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.json xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/power_manager.h xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/README.md xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.h xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.json xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/power_manager.h xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/README.md xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.h xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.json xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/taiji_pi_s3.cc xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.h xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.json xiaozhi-esp32-2.2.6/main/boards/tudouzi/kevin_box_board.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/esp32-c6-lcd-1.69.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/power_manager.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/esp32-c6-touch-amoled-1.8.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/esp32-c6-touch-amoled-2.16.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/esp32-c6-touch-lcd-1.83.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/lcd_init_cmds.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/esp32-p4-wifi6-touch-lcd-3.5.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/esp32-p4-wifi6-touch-lcd.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/lcd_init_cmds.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp32-s3-audio_board.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.c xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/esp32-s3-cam-xxxx.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/waveshare-s3-epaper-3.97.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/esp32-s3-lcd-0.85.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/power_manager.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/waveshare-s3-rlcd-4.2.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/esp32-s3-touch-amoled-1.43c.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/esp32-s3-touch-lcd-1.54.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/power_manager.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/esp32_s3_touch_lcd_4.3c.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/README.md xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.h xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.json xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/esp32-touch-lcd-3.5.cc xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/README.md xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.h xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.json xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/README.md xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/customlcddisplay.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/power_manager.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/xingzhi-abs-2.0.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/README.md xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/power_manager.h xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/README.md xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.h xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.json xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/README.md xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.h xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.json xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/xmini_c3_board.cc xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.h xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.json xiaozhi-esp32-2.2.6/main/boards/xmini-c3/xmini_c3_board.cc xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/README.md xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.h xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.json xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.cc xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.h xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/yunliao_s3.cc xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/README.md xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.json xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/README.md xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.json xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/power_manager.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/README.md xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.json xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/mcp_controller.cc xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/power_manager.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/zhengchen_cam_board_ml307.cc xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/README.md xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.json xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/mcp_controller.cc xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/power_manager.h xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/zhengchen_cam_board.cc xiaozhi-esp32-2.2.6/main/device_state.h xiaozhi-esp32-2.2.6/main/device_state_machine.cc xiaozhi-esp32-2.2.6/main/device_state_machine.h xiaozhi-esp32-2.2.6/main/display/display.cc xiaozhi-esp32-2.2.6/main/display/display.h xiaozhi-esp32-2.2.6/main/display/emote_display.cc xiaozhi-esp32-2.2.6/main/display/emote_display.h xiaozhi-esp32-2.2.6/main/display/lcd_display.cc xiaozhi-esp32-2.2.6/main/display/lcd_display.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.cc xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/LICENSE.txt xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/README.md xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.c xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec_mve.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.cc xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.cpp xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.c xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.cc xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.cc xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.cc xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.h xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.cc xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.h xiaozhi-esp32-2.2.6/main/display/oled_display.cc xiaozhi-esp32-2.2.6/main/display/oled_display.h xiaozhi-esp32-2.2.6/main/idf_component.yml xiaozhi-esp32-2.2.6/main/led/circular_strip.cc xiaozhi-esp32-2.2.6/main/led/circular_strip.h xiaozhi-esp32-2.2.6/main/led/gpio_led.cc xiaozhi-esp32-2.2.6/main/led/gpio_led.h xiaozhi-esp32-2.2.6/main/led/led.h xiaozhi-esp32-2.2.6/main/led/single_led.cc xiaozhi-esp32-2.2.6/main/led/single_led.h xiaozhi-esp32-2.2.6/main/main.cc xiaozhi-esp32-2.2.6/main/mcp_server.cc xiaozhi-esp32-2.2.6/main/mcp_server.h xiaozhi-esp32-2.2.6/main/ota.cc xiaozhi-esp32-2.2.6/main/ota.h xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.cc xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.h xiaozhi-esp32-2.2.6/main/protocols/protocol.cc xiaozhi-esp32-2.2.6/main/protocols/protocol.h xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.cc xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.h xiaozhi-esp32-2.2.6/main/settings.cc xiaozhi-esp32-2.2.6/main/settings.h xiaozhi-esp32-2.2.6/main/system_info.cc xiaozhi-esp32-2.2.6/main/system_info.h xiaozhi-esp32-2.2.6/partitions/v1/16m.csv xiaozhi-esp32-2.2.6/partitions/v1/16m_custom_wakeword.csv xiaozhi-esp32-2.2.6/partitions/v1/16m_echoear.csv xiaozhi-esp32-2.2.6/partitions/v1/32m.csv xiaozhi-esp32-2.2.6/partitions/v1/4m.csv xiaozhi-esp32-2.2.6/partitions/v1/4m_esp-hi.csv xiaozhi-esp32-2.2.6/partitions/v1/8m.csv xiaozhi-esp32-2.2.6/partitions/v2/16m.csv xiaozhi-esp32-2.2.6/partitions/v2/16m_c3.csv xiaozhi-esp32-2.2.6/partitions/v2/32m.csv xiaozhi-esp32-2.2.6/partitions/v2/4m.csv xiaozhi-esp32-2.2.6/partitions/v2/8m.csv xiaozhi-esp32-2.2.6/partitions/v2/README.md xiaozhi-esp32-2.2.6/scripts/Image_Converter/LVGLImage.py xiaozhi-esp32-2.2.6/scripts/Image_Converter/README.md xiaozhi-esp32-2.2.6/scripts/Image_Converter/lvgl_tools_gui.py xiaozhi-esp32-2.2.6/scripts/Image_Converter/requirements.txt xiaozhi-esp32-2.2.6/scripts/acoustic_check/demod.py xiaozhi-esp32-2.2.6/scripts/acoustic_check/graphic.py xiaozhi-esp32-2.2.6/scripts/acoustic_check/main.py xiaozhi-esp32-2.2.6/scripts/acoustic_check/readme.md xiaozhi-esp32-2.2.6/scripts/acoustic_check/requirements.txt xiaozhi-esp32-2.2.6/scripts/audio_debug_server.py xiaozhi-esp32-2.2.6/scripts/build_default_assets.py xiaozhi-esp32-2.2.6/scripts/download_github_runs.py xiaozhi-esp32-2.2.6/scripts/gen_lang.py xiaozhi-esp32-2.2.6/scripts/mp3_to_ogg.sh xiaozhi-esp32-2.2.6/scripts/ogg_converter/README.md xiaozhi-esp32-2.2.6/scripts/ogg_converter/xiaozhi_ogg_converter.py xiaozhi-esp32-2.2.6/scripts/p3_tools/README.md xiaozhi-esp32-2.2.6/scripts/p3_tools/batch_convert_gui.py xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_audio_to_p3.py xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_p3_to_audio.py xiaozhi-esp32-2.2.6/scripts/p3_tools/img/img.png xiaozhi-esp32-2.2.6/scripts/p3_tools/p3_gui_player.py xiaozhi-esp32-2.2.6/scripts/p3_tools/play_p3.py xiaozhi-esp32-2.2.6/scripts/p3_tools/requirements.txt xiaozhi-esp32-2.2.6/scripts/release.py xiaozhi-esp32-2.2.6/scripts/sonic_wifi_config.html xiaozhi-esp32-2.2.6/scripts/spiffs_assets/README.md xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build.py xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build_all.py xiaozhi-esp32-2.2.6/scripts/spiffs_assets/pack_model.py xiaozhi-esp32-2.2.6/scripts/spiffs_assets/spiffs_assets_gen.py xiaozhi-esp32-2.2.6/scripts/versions.py xiaozhi-esp32-2.2.6/sdkconfig.defaults xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32 xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c3 xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c5 xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c6 xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32p4 xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32s3