From 24ff11ba18235d2c33ef73921ad01b9c5e7d4f21 Mon Sep 17 00:00:00 2001
From: wangrong <wangrong@shsening.com>
Date: 星期一, 24 八月 2026 15:09:56 +0800
Subject: [PATCH] Merge branch 'master' of http://124.222.109.197:10000/r/xiaozhiAI
---
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.json | 12
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.h | 55
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/power_manager.h | 195
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/README.md | 12
smartlearning/data/courses/course02/course.json | 70
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/blufi.cpp | 899
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/README.md | 42
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/6.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.h | 31
xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.h | 35
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.json | 17
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/power_manager.h | 212
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.json | 12
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.json | 22
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.cpp | 467
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/README.md | 45
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/0.ogg | 0
smartlearning/data/courses/course02/chapter02/resources/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc | 283
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/2.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.cc | 263
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc | 201
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h | 47
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/df-k10/df_k10_board.cc | 299
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/9.ogg | 0
smartlearning/data/courses/course01/chapter02/resources/002.mp3 | 0
smartlearning/data/courses/course02/chapter02/resources/002.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/README.md | 50
xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.h | 47
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.h | 44
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/activation.ogg | 0
smartlearning/data/courses/course01/chapter03/resources/003.jpg | 0
xiaozhi-esp32-2.2.6/scripts/audio_debug_server.py | 54
xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.h | 48
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.h | 24
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.json | 23
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/README.md | 4
xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.h | 42
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/esp32-p4-wifi6-touch-lcd-3.5.cc | 191
smartlearning/server/app/main.py | 76
xiaozhi-esp32-2.2.6/main/boards/tudouzi/kevin_box_board.cc | 255
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/4.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/audio_service.cc | 734
xiaozhi-esp32-2.2.6/scripts/acoustic_check/graphic.py | 444
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.cc | 964
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.json | 17
xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.cc | 220
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.h | 52
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.cc | 161
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.h | 71
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/activation.ogg | 0
smartlearning/server/app/services/__pycache__/auto_init.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc | 363
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.json | 15
xiaozhi-esp32-2.2.6/docs/v1/xmini-c3.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/README.md | 45
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/README.md | 72
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.c | 821
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c | 504
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/eda-tv-pro.cc | 122
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.h | 68
xiaozhi-esp32-2.2.6/scripts/Image_Converter/LVGLImage.py | 1426
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h | 146
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.h | 28
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h | 195
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/esp_vocat.cc | 1017
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/5.ogg | 0
smartlearning/data/courses/course02/chapter03/resources/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.h | 61
smartlearning/data/courses/course02/chapter03/resources/002.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc | 280
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc | 150
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.h | 325
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc | 212
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.json | 11
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/README.md | 82
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.h | 104
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/README.md | 49
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/9.ogg | 0
smartlearning/server/app/services/__pycache__/scene_engine.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/6.ogg | 0
xiaozhi-esp32-2.2.6/scripts/ogg_converter/README.md | 36
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/README.md | 54
xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.cc | 254
xiaozhi-esp32-2.2.6/scripts/sonic_wifi_config.html | 208
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/README.md | 3
xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.cc | 57
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc | 213
smartlearning/data/courses/course01/chapter03/resources/001.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/power_manager.h | 201
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.cc | 149
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/README.md | 4
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.cc | 134
xiaozhi-esp32-2.2.6/main/device_state.h | 18
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/9.ogg | 0
smartlearning/server/app/__pycache__/main.cpython-313.pyc | 0
smartlearning/server/app/routes/__pycache__/devices.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/power_manager.h | 151
xiaozhi-esp32-2.2.6/main/boards/common/knob.h | 25
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc | 491
xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.cc | 357
smartlearning/server/app/routes/__pycache__/dialogue.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.json | 12
smartlearning/server/app/services/__pycache__/__init__.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.h | 56
smartlearning/data/courses/index.json | 12
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/7.ogg | 0
xiaozhi-esp32-2.2.6/main/settings.cc | 108
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/8.ogg | 0
smartlearning/data/courses/course02/chapter01/resources/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.json | 16
smartlearning/data/courses/course01/chapter01/resources/002.jpg | 0
xiaozhi-esp32-2.2.6/README_zh.md | 169
xiaozhi-esp32-2.2.6/main/assets/common/vibration.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/README.md | 22
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.cc | 247
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/activation.ogg | 0
smartlearning/server/runtime/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.cc | 743
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.h | 41
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.h | 45
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.cc | 124
xiaozhi-esp32-2.2.6/docs/v1/movecall-moji-esp32s3.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/README.md | 4
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/README.md | 26
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.h | 58
xiaozhi-esp32-2.2.6/main/audio/audio_processor.h | 26
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.h | 45
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.json | 14
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config_en.json | 20
xiaozhi-esp32-2.2.6/partitions/v2/4m.csv | 7
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.h | 70
xiaozhi-esp32-2.2.6/.clang-format | 126
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.h | 91
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/README.md | 43
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.json | 12
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/9.ogg | 0
xiaozhi-esp32-2.2.6/docs/v1/waveshare.jpg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/README.md | 17
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/kevin_c3_board.cc | 90
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc | 225
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.json | 16
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/wake_word.h | 26
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/README.md | 25
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README_zh.md | 57
xiaozhi-esp32-2.2.6/scripts/download_github_runs.py | 294
smartlearning/server/app/__pycache__/config.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/docs/v0/AtomMatrix-echo-base.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.h | 46
xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.cc | 250
xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.h | 32
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.h | 50
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/atoms3_echo_base.cc | 232
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/power_manager.h | 150
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/README.md | 6
xiaozhi-esp32-2.2.6/partitions/v1/16m.csv | 8
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/4.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.cc | 200
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.h | 45
xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.h | 33
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/README.md | 49
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.h | 62
xiaozhi-esp32-2.2.6/docs/mcp-usage.md | 185
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/7.ogg | 0
smartlearning/data/courses/course02/chapter01/resources/003.mp3 | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.h | 53
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-hi/esp_hi.cc | 422
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc | 183
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.h | 50
xiaozhi-esp32-2.2.6/main/system_info.cc | 156
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.h | 40
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc | 223
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.h | 79
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_reg.ogg | 0
smartlearning/server/app/models/__init__.py | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/ReadMe.md | 5
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/common/exclamation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/hu-087/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.json | 15
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/config.h | 53
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/esp_box3_board.cc | 175
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/README.md | 48
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.h | 40
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.h | 74
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/esp32-s3-touch-lcd-1.54.cc | 355
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc | 150
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/0.ogg | 0
xiaozhi-esp32-2.2.6/main/CMakeLists.txt | 1200
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.json | 14
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.h | 56
xiaozhi-esp32-2.2.6/main/boards/esp-box/config.json | 9
xiaozhi-esp32-2.2.6/scripts/acoustic_check/requirements.txt | 4
xiaozhi-esp32-2.2.6/main/boards/df-k10/config.json | 15
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/zhengchen_cam_board.cc | 333
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.cc | 763
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/README.md | 75
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.json | 10
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.json | 12
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/axp2101.h | 20
smartlearning/server/app/services/__pycache__/dialogue_service.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc | 233
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.h | 94
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc | 258
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.h | 44
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.h | 66
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c | 154
xiaozhi-esp32-2.2.6/docs/v0/atoms3r-echo-base.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/esp32_s3_touch_lcd_4.3c.cc | 252
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h | 90
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.cc | 68
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/welcome.ogg | 0
smartlearning/server/app/routes/scene.py | 22
smartlearning/data/courses/course01/chapter01/resources/003.mp3 | 0
xiaozhi-esp32-2.2.6/main/boards/esp-box/esp_box_board.cc | 174
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/touch.h | 51
xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.cc | 269
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/lamp_controller.h | 48
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/esp_video.cc | 1041
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.cc | 130
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.h | 38
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/esp32_cgc_board.cc | 180
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/esp32_bread_board.cc | 162
xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_0.96.jpg | 0
smartlearning/data/courses/course02/chapter01/resources/001.mp3 | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/6.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.cc | 303
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.json | 13
xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.h | 41
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/button.h | 49
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/5.ogg | 0
smartlearning/docs/smartlearing_prd_v1.01.txt | 201
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/du-chatx/du-chatx-wifi.cc | 164
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/0.ogg | 0
xiaozhi-esp32-2.2.6/docs/v0/waveshare-esp32-s3-touch-amoled-1.8.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.h | 54
smartlearning/server/app/services/__pycache__/init_registry.cpython-313.pyc | 0
smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.h | 47
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/4.ogg | 0
smartlearning/server/run.py | 11
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.h | 29
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/partitions/v1/32m.csv | 10
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.h | 133
xiaozhi-esp32-2.2.6/main/led/gpio_led.h | 49
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/df-k10/config.h | 76
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.h | 95
xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.cc | 385
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/pin_config.h | 62
xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.cc | 202
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/pack_model.py | 123
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.h | 47
xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.h | 99
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.h | 39
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/3.ogg | 0
smartlearning/server/app/models/__pycache__/__init__.cpython-313.pyc | 0
smartlearning/data/courses/course01/chapter03/resources/002.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/lichuang_dev_board.cc | 296
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.h | 45
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/compact_wifi_board.cc | 188
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/README.md | 30
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.h | 64
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.json | 10
smartlearning/server/app/routes/__pycache__/scene.cpython-310.pyc | 0
smartlearning/data/courses/course01/chapter03/resources/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.cc | 71
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.h | 71
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc | 365
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/README.md | 61
xiaozhi-esp32-2.2.6/main/audio/audio_service.h | 195
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.json | 9
xiaozhi-esp32-2.2.6/docs/v0/m5stack-cores3.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc | 477
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README.md | 52
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/mcp_controller.cc | 94
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h | 66
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.cc | 64
smartlearning/data/courses/course02/chapter03/resources/002.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.cc | 577
xiaozhi-esp32-2.2.6/main/display/oled_display.h | 41
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/power_manager.h | 195
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc | 200
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config_en.json | 17
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.h | 66
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.h | 46
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.json | 10
xiaozhi-esp32-2.2.6/main/boards/common/sy6970.cc | 65
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.h | 47
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/esp_box_lite_board.cc | 237
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/README.md | 34
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/esp_video.h | 53
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.h | 45
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc | 322
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.h | 46
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_board.cc | 205
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/m5stack_tab5.cc | 574
xiaozhi-esp32-2.2.6/main/protocols/protocol.cc | 90
xiaozhi-esp32-2.2.6/docs/mqtt-udp_zh.md | 393
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.json | 16
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.h | 37
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.h | 43
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/README.md | 49
xiaozhi-esp32-2.2.6/README.md | 173
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/language.json | 59
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32p4 | 31
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/README.md | 61
xiaozhi-esp32-2.2.6/main/assets/locales/sr-RS/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.h | 65
xiaozhi-esp32-2.2.6/main/device_state_machine.h | 83
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/power_manager.h | 186
smartlearning/data/courses/course02/chapter02/resources/001.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/7.ogg | 0
smartlearning/server/app/routes/courses.py | 79
smartlearning/data/courses/course01/chapter01/resources/002.mp3 | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/otto_icon_font.c | 121
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/compact_nt26_board.cc | 181
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_audio_codec.h | 237
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.h | 70
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/3.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/LICENSE.txt | 2
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h | 99
smartlearning/server/app/services/__pycache__/init_registry.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c | 353
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/esp32-c6-touch-amoled-2.16.cc | 352
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/power_manager.h | 187
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/0.ogg | 0
xiaozhi-esp32-2.2.6/sdkconfig.defaults | 78
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.h | 57
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.cc | 377
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.json | 14
xiaozhi-esp32-2.2.6/docs/v0/esp32s3-box3.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.cc | 227
xiaozhi-esp32-2.2.6/main/boards/xmini-c3/xmini_c3_board.cc | 182
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc | 325
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc | 245
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/language.json | 59
xiaozhi-esp32-2.2.6/partitions/v1/16m_custom_wakeword.csv | 8
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot.cc | 123
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.json | 13
xiaozhi-esp32-2.2.6/docs/mcp-protocol.md | 270
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/README.md | 4
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/zhengchen_cam_board_ml307.cc | 340
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32s3 | 31
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/esp32-s3-cam-xxxx.cc | 282
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/README.md | 3
xiaozhi-esp32-2.2.6/docs/v1/lilygo-t-circle-s3.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/scripts/versions.py | 250
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/esp32-c6-touch-amoled-1.8.cc | 352
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.json | 12
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.cc | 161
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.h | 43
smartlearning/server/app/routes/__pycache__/devices.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/9.ogg | 0
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/spiffs_assets_gen.py | 647
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/aipi-lite.cc | 246
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.h | 92
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_controller.cc | 399
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.json | 19
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h | 44
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.cc | 252
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.h | 46
smartlearning/docs/mqtt消息规则_V1.0.pdf | 0
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.cc | 216
xiaozhi-esp32-2.2.6/docs/v1/lichuang-s3.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc | 233
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.h | 117
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/t_display_p4_config.h | 233
smartlearning/data/courses/course01/chapter02/resources/002.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.json | 14
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.h | 29
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.json | 10
xiaozhi-esp32-2.2.6/docs/v1/movecall-cuican-esp32s3.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/customlcddisplay.h | 94
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc | 239
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.h | 75
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc | 407
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/power_manager.h | 305
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.h | 155
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/8.ogg | 0
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/README.md | 110
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.h | 44
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/esp32-touch-lcd-3.5.cc | 341
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/README.md | 31
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/xmini_c3_board.cc | 201
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/README.md | 10
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_controller.cc | 849
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.h | 44
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/0.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.cc | 206
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README_en.md | 53
xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.h | 40
smartlearning/data/courses/course02/chapter02/resources/003.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/board.cc | 178
smartlearning/server/app/services/__pycache__/device_store.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.cc | 246
xiaozhi-esp32-2.2.6/main/assets.h | 89
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.h | 41
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.json | 16
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/3.ogg | 0
xiaozhi-esp32-2.2.6/scripts/acoustic_check/readme.md | 23
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets.cc | 562
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.h | 39
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/8.ogg | 0
xiaozhi-esp32-2.2.6/docs/v1/wiring2.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/partitions/v2/8m.csv | 8
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/pin_config.h | 154
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/config.h | 52
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc | 192
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/README.md | 36
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.h | 43
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_robot.cc | 417
smartlearning/server/app/__pycache__/__init__.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.h | 66
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc | 454
xiaozhi-esp32-2.2.6/partitions/v2/32m.csv | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.json | 17
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/mpython_pro.cc | 143
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/minsi_k08_dual.cc | 246
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc | 471
xiaozhi-esp32-2.2.6/main/boards/electron-bot/power_manager.h | 128
smartlearning/server/runtime/.gitignore | 1
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.cc | 393
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/board.h | 92
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h | 186
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.h | 53
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/1.ogg | 0
smartlearning/data/courses/course02/chapter03/resources/003.mp3 | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.h | 23
xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.cc | 470
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.cc | 286
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/yunliao_s3.cc | 295
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.cc | 403
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/compact_ml307_board.cc | 191
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc | 274
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.cc | 338
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/1.ogg | 0
smartlearning/server/app/routes/__pycache__/courses.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.cc | 98
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.h | 78
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md | 32
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/activation.ogg | 0
smartlearning/server/app/services/__pycache__/scene_engine.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.h | 55
xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.cc | 133
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/esp-sensairshuttle.cc | 315
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/6.ogg | 0
xiaozhi-esp32-2.2.6/scripts/Image_Converter/requirements.txt | 3
xiaozhi-esp32-2.2.6/scripts/p3_tools/play_p3.py | 71
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.h | 40
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/mcp_controller.cc | 94
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc | 440
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/pin_config.h | 52
xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.h | 62
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc | 147
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.c | 460
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.h | 62
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.h | 102
xiaozhi-esp32-2.2.6/main/application.cc | 1116
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/README.md | 43
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.h | 49
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.c | 264
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/9.ogg | 0
xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_1.54.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/README.md | 3
xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.h | 18
xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.h | 34
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/README.md | 45
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/power_manager.h | 193
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/1.ogg | 0
smartlearning/server/app/mqtt/__init__.py | 0
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.h | 58
xiaozhi-esp32-2.2.6/docs/websocket.md | 530
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/camera.h | 16
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.json | 11
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/3.ogg | 0
smartlearning/server/app/mqtt/__pycache__/__init__.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/docs/mqtt-udp.md | 409
xiaozhi-esp32-2.2.6/scripts/p3_tools/img/img.png | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/README.md | 13
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/4.ogg | 0
xiaozhi-esp32-2.2.6/scripts/Image_Converter/README.md | 45
xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.h | 41
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/2.ogg | 0
smartlearning/server/app/routes/__pycache__/__init__.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/config.h | 76
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.h | 68
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_controller.cc | 493
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc | 163
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/5.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lcd_display.cc | 1310
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/README.md | 66
smartlearning/data/courses/course02/chapter01/resources/002.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.cc | 178
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.json | 20
xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/kevin_box_board.cc | 270
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear.cc | 58
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc | 228
xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.h | 63
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.json | 17
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc | 129
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/led/single_led.cc | 167
smartlearning/server/app/__init__.py | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.h | 68
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.h | 19
xiaozhi-esp32-2.2.6/main/assets/common/popup.ogg | 0
smartlearning/server/app/models/__pycache__/schemas.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.h | 36
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/README.md | 4
xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.cc | 132
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.json | 12
smartlearning/data/courses/course01/chapter01/resources/003.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/README.md | 31
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.json | 10
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/5.ogg | 0
xiaozhi-esp32-2.2.6/docs/v0/magiclick-2p4.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.json | 13
smartlearning/data/courses/course01/course.json | 70
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/esp32s3_korvo2_v3_board.cc | 449
smartlearning/docs/requires.txt | 368
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c | 478
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/README.md | 42
xiaozhi-esp32-2.2.6/docs/v1/sensecap_watcher.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h | 145
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.h | 38
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/2.ogg | 0
xiaozhi-esp32-2.2.6/docs/custom-board.md | 473
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc | 253
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/README.md | 47
xiaozhi-esp32-2.2.6/main/led/circular_strip.cc | 245
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/5.ogg | 0
smartlearning/server/app/services/scene_engine.py | 57
smartlearning/server/app/services/__pycache__/dialogue_service.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.h | 83
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.h | 32
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/3.ogg | 0
smartlearning/server/app/routes/__pycache__/dialogue.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/power_manager.h | 237
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/6.ogg | 0
smartlearning/server/app/mqtt/__pycache__/__init__.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.cc | 705
xiaozhi-esp32-2.2.6/docs/v0/lichuang-s3.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.json | 10
smartlearning/server/app/config.py | 34
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.h | 53
smartlearning/server/app/mqtt/client.py | 125
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc | 172
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/5.ogg | 0
smartlearning/server/app/__pycache__/config.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.json | 11
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.cc | 129
xiaozhi-esp32-2.2.6/main/boards/hu-087/config.h | 28
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/language.json | 59
smartlearning/data/courses/course01/courseTask.json | 32
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.h | 18
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc | 157
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/mixgo-nova.cc | 168
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.h | 47
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/README.md | 45
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.json | 12
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/config.h | 308
smartlearning/data/courses/course01/chapter03/resources/002.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/wificonfig.ogg | 0
smartlearning/data/courses/course01/chapter02/resources/001.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/8.ogg | 0
xiaozhi-esp32-2.2.6/scripts/Image_Converter/lvgl_tools_gui.py | 253
smartlearning/data/courses/course02/chapter02/resources/003.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/1.ogg | 0
xiaozhi-esp32-2.2.6/main/main.cc | 29
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc | 123
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/README.md | 39
smartlearning/server/app/routes/__pycache__/__init__.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build_all.py | 148
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc | 210
xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.h | 86
xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_audio_to_p3.py | 62
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/waveshare-s3-rlcd-4.2.cc | 170
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.cc | 418
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/README.md | 98
xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.h | 74
xiaozhi-esp32-2.2.6/main/display/emote_display.cc | 250
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.cc | 274
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/0.ogg | 0
smartlearning/server/app/services/device_store.py | 45
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.h | 278
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32 | 6
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/README.md | 3
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/power_manager.h | 237
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/README.md | 28
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.h | 17
xiaozhi-esp32-2.2.6/partitions/v2/README.md | 107
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc | 352
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.json | 10
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/6.ogg | 0
xiaozhi-esp32-2.2.6/partitions/v1/4m.csv | 7
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.json | 13
xiaozhi-esp32-2.2.6/main/led/led.h | 17
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/README.md | 15
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.h | 73
smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.h | 80
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc | 225
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.h | 81
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc | 230
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.h | 66
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/welcome.ogg | 0
smartlearning/server/app/services/auto_init.py | 83
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/common/sy6970.h | 21
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/README.md | 45
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.h | 53
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/README.md | 48
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/8.ogg | 0
smartlearning/data/courses/course01/chapter01/resources/001.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.h | 41
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot_controller.cc | 376
smartlearning/server/app/services/__pycache__/course_service.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.json | 14
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.h | 46
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/activation.ogg | 0
xiaozhi-esp32-2.2.6/scripts/build_default_assets.py | 934
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/welcome.ogg | 0
smartlearning/docs/mqtt消息规则.pdf | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc | 240
xiaozhi-esp32-2.2.6/main/mcp_server.h | 344
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/README.md | 5
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.h | 152
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.json | 12
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/welcome.ogg | 0
xiaozhi-esp32-2.2.6/README_ja.md | 169
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/welcome.ogg | 0
smartlearning/data/courses/course02/chapter03/resources/001.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/0.ogg | 0
xiaozhi-esp32-2.2.6/docs/mcp-based-graph.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.h | 62
xiaozhi-esp32-2.2.6/main/boards/du-chatx/power_manager.h | 186
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.h | 35
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.h | 23
xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/atom_echos3r.cc | 91
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.h | 27
xiaozhi-esp32-2.2.6/main/system_info.h | 23
xiaozhi-esp32-2.2.6/docs/code_style.md | 90
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/Kconfig.projbuild | 962
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/magiclick_c3_board.cc | 191
xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.h | 18
xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/taiji_pi_s3.cc | 663
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.h | 49
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_reg.ogg | 0
smartlearning/docs/mqtt_interface.md | 429
xiaozhi-esp32-2.2.6/docs/v1/electron-bot.png | 0
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.json | 11
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/5.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.h | 16
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/doit_s3_aibox.cc | 137
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc | 203
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/README.md | 3
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.h | 44
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/4.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/audio_codec.h | 61
xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.h | 28
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.h | 70
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.cc | 245
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/README.md | 56
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.h | 115
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.json | 118
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/1.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/audio_codec.cc | 67
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/sdkconfig.tab5 | 14
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/8.ogg | 0
smartlearning/server/app/models/__pycache__/__init__.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.h | 61
xiaozhi-esp32-2.2.6/main/boards/esp-box/config.h | 41
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/power_manager.h | 212
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-spot/esp_spot_board.cc | 436
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc | 234
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README_en.md | 40
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/df-k10/README.md | 57
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.json | 17
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/m5stack_core_s3.cc | 396
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.h | 81
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/README.md | 14
smartlearning/server/app/routes/dialogue.py | 31
smartlearning/docs/mqtt消息规则 v2 - 副本.pdf | 0
xiaozhi-esp32-2.2.6/docs/code_style_zh.md | 91
xiaozhi-esp32-2.2.6/scripts/p3_tools/p3_gui_player.py | 241
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/2.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.h | 62
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.json | 15
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/language.json | 59
xiaozhi-esp32-2.2.6/main/protocols/protocol.h | 98
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.h | 24
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc | 456
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.h | 69
xiaozhi-esp32-2.2.6/main/boards/common/backlight.h | 36
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/README.md | 78
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc | 393
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.h | 69
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.h | 57
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.h | 40
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.cc | 278
xiaozhi-esp32-2.2.6/scripts/gen_lang.py | 187
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc | 370
xiaozhi-esp32-2.2.6/scripts/p3_tools/README.md | 95
xiaozhi-esp32-2.2.6/main/display/display.h | 87
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.json | 17
xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.cc | 322
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.json | 11
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/activation.ogg | 0
smartlearning/data/courses/course01/chapter01/resources/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/3.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.cc | 20
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/README.md | 76
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_manager.h | 201
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/README.md | 14
xiaozhi-esp32-2.2.6/main/assets/common/low_battery.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-hi/README.md | 51
xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc | 306
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.h | 90
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.json | 9
xiaozhi-esp32-2.2.6/docs/v1/otto-robot.png | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/scripts/ogg_converter/xiaozhi_ogg_converter.py | 230
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c6 | 5
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/0.ogg | 0
smartlearning/server/app/mqtt/__pycache__/client.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc | 397
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c3 | 15
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp32-s3-audio_board.cc | 239
xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c5 | 14
xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.h | 45
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec_mve.h | 140
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.h | 30
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.json | 17
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.h | 125
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/power_manager.h | 249
smartlearning/data/courses/course02/chapter01/resources/002.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.c | 381
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.json | 9
xiaozhi-esp32-2.2.6/main/ota.cc | 492
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/README.md | 11
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/power_manager.h | 238
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.cc | 274
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/2.ogg | 0
smartlearning/docs/mqtt消息规则V1.1.pdf | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.json | 11
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_reg.ogg | 0
smartlearning/server/app/routes/devices.py | 75
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/1.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.cc | 123
xiaozhi-esp32-2.2.6/main/ota.h | 58
xiaozhi-esp32-2.2.6/main/boards/esp-spot/README.md | 63
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/3.ogg | 0
xiaozhi-esp32-2.2.6/docs/mcp-protocol_zh.md | 269
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.h | 29
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.h | 41
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.json | 20
xiaozhi-esp32-2.2.6/docs/blufi_zh.md | 37
smartlearning/data/courses/course01/chapter03/resources/003.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.json | 19
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc | 163
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.json | 9
smartlearning/data/courses/course01/chapter02/resources/.gitkeep | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/power_manager.h | 195
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/pin_config.h | 47
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.h | 59
xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.json | 9
smartlearning/server/app/services/dialogue_service.py | 81
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.json | 38
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.h | 11
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/hu-087/hu_087_board.cc | 151
xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.cc | 311
xiaozhi-esp32-2.2.6/main/display/lcd_display.h | 85
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.json | 20
xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.cc | 116
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/README.md | 36
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.cc | 404
xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/mcp_server.cc | 563
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.json | 9
xiaozhi-esp32-2.2.6/docs/v1/espbox3.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.h | 57
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.h | 18
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/lcd_init_cmds.h | 1124
xiaozhi-esp32-2.2.6/main/assets/locales/en-US/5.ogg | 0
xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.cc | 389
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc | 169
xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.h | 37
xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.h | 40
xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.h | 51
smartlearning/server/app/models/schemas.py | 61
smartlearning/data/courses/course01/chapter02/resources/003.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.cc | 161
xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.cc | 247
xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.json | 13
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/README.md | 49
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/power_manager.h | 174
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager_lite.h | 185
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.json | 12
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.h | 89
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/language.json | 59
smartlearning/server/app/mqtt/__pycache__/client.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.h | 177
xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.h | 59
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/power_manager.h | 186
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/blufi.h | 147
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.json | 29
xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.cc | 110
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/7.ogg | 0
smartlearning/server/app/routes/__pycache__/courses.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.json | 10
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.json | 11
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc | 350
xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.h | 65
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/2.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/README.md | 88
xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.h | 60
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.cc | 105
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/esp32-c6-touch-lcd-1.83.cc | 274
xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_p3_to_audio.py | 51
xiaozhi-esp32-2.2.6/scripts/mp3_to_ogg.sh | 3
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/application.h | 189
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/esp32-s3-lcd-0.85.cc | 311
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.h | 47
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc | 299
smartlearning/data/courses/course02/chapter01/resources/001.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/6.ogg | 0
smartlearning/data/courses/course01/chapter01/resources/001.mp3 | 0
xiaozhi-esp32-2.2.6/docs/v1/esp-sparkbot.jpg | 0
smartlearning/data/courses/course01/chapter03/resources/001.mp3 | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/3.ogg | 0
xiaozhi-esp32-2.2.6/scripts/release.py | 456
xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.h | 44
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/language.json | 59
smartlearning/data/courses/course02/chapter03/resources/001.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/README.md | 11
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.json | 12
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.json | 10
xiaozhi-esp32-2.2.6/main/boards/esp-box-3/README.md | 132
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/axp2101.cc | 41
smartlearning/docs/mqtt消息规则_V1.1.xlsx | 0
smartlearning/data/tasklist.json | 37
xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_icon_font.c | 121
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/magiclick_2p5_board.cc | 424
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/esp32-c6-lcd-1.69.cc | 262
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc | 250
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/language.json | 59
xiaozhi-esp32-2.2.6/main/boards/common/button.cc | 125
xiaozhi-esp32-2.2.6/LICENSE | 22
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/system_reset.cc | 72
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.cc | 30
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/power_manager.h | 250
xiaozhi-esp32-2.2.6/main/display/emote_display.h | 42
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.h | 53
smartlearning/data/courses/course01/chapter02/resources/003.jpg | 0
xiaozhi-esp32-2.2.6/docs/v1/esp-hi.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc | 388
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.h | 105
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.h | 176
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README.md | 57
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.h | 37
smartlearning/server/app/__pycache__/main.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/README.md | 75
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.h | 42
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.json | 10
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.h | 29
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.json | 10
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/magiclick_2p4_board.cc | 272
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/README.md | 18
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.h | 84
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/esp32-p4-wifi6-touch-lcd.cc | 458
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_controller.h | 58
smartlearning/server/requirements.txt | 7
xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.h | 40
xiaozhi-esp32-2.2.6/docs/blufi.md | 34
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc | 112
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_watcher.cc | 668
xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.h | 88
xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.json | 17
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/README.md | 207
xiaozhi-esp32-2.2.6/docs/custom-board_zh.md | 453
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.json | 12
xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.h | 34
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c | 3306 +
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.cc | 192
xiaozhi-esp32-2.2.6/main/display/display.cc | 60
xiaozhi-esp32-2.2.6/main/settings.h | 28
xiaozhi-esp32-2.2.6/main/idf_component.yml | 136
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/README.md | 55
xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/9.ogg | 0
smartlearning/server/app/services/__init__.py | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc | 244
smartlearning/data/courses/course02/chapter02/resources/002.mp3 | 0
xiaozhi-esp32-2.2.6/main/led/gpio_led.cc | 263
xiaozhi-esp32-2.2.6/partitions/v1/8m.csv | 8
smartlearning/server/app/services/course_service.py | 72
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.h | 29
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/README.md | 11
smartlearning/data/courses/course02/courseTask.json | 81
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.h | 43
xiaozhi-esp32-2.2.6/scripts/p3_tools/requirements.txt | 7
xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.cc | 35
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/README.md | 60
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.h | 75
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc | 173
smartlearning/data/courses/course02/chapter03/resources/003.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.h | 53
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.h | 22
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/1.ogg | 0
smartlearning/server/app/mqtt/init_dispatcher.py | 48
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc | 220
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.json | 23
xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/README.md | 41
xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.cc | 13
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/9.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/9.ogg | 0
xiaozhi-esp32-2.2.6/CMakeLists.txt | 13
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.h | 56
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.json | 15
xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.h | 68
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/welcome.ogg | 0
xiaozhi-esp32-2.2.6/docs/v0/wiring.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.json | 12
smartlearning/server/app/services/init_registry.py | 92
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.json | 13
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.h | 42
xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.json | 9
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/8.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.json | 17
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.h | 91
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc | 190
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc | 447
xiaozhi-esp32-2.2.6/main/led/circular_strip.h | 52
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.h | 56
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/upgrade.ogg | 0
smartlearning/data/courses/course01/chapter02/resources/001.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/common/knob.cc | 52
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc | 382
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.h | 45
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/README.md | 49
xiaozhi-esp32-2.2.6/scripts/acoustic_check/demod.py | 280
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.h | 42
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.cc | 147
xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc | 211
xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.json | 34
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/gpio_manager.h | 62
xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/README.md | 62
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.h | 35
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/3.ogg | 0
xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.cc | 201
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.json | 11
xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc | 259
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h | 31
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/7.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/backlight.cc | 121
smartlearning/server/app/routes/__init__.py | 0
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c | 384
xiaozhi-esp32-2.2.6/main/boards/hu-087/README.md | 7
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/pin_config.h | 47
smartlearning/server/app/models/__pycache__/schemas.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/common/success.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.h | 98
xiaozhi-esp32-2.2.6/main/display/oled_display.cc | 408
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/config.h | 289
xiaozhi-esp32-2.2.6/main/device_state_machine.cc | 161
xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.h | 41
xiaozhi-esp32-2.2.6/docs/websocket_zh.md | 495
xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/welcome.ogg | 0
xiaozhi-esp32-2.2.6/partitions/v1/16m_echoear.csv | 9
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/README.md | 44
smartlearning/server/app/services/__pycache__/auto_init.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/power_manager.h | 314
xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/8.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.json | 9
smartlearning/server/app/services/__pycache__/__init__.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.json | 15
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h | 80
xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.json | 10
xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_robot_pro.cc | 133
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.json | 19
xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/xingzhi-abs-2.0.cc | 389
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager.h | 186
smartlearning/server/app/services/__pycache__/course_service.cpython-310.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/9.ogg | 0
xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build.py | 400
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/README.md | 29
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/7.ogg | 0
xiaozhi-esp32-2.2.6/main/led/single_led.h | 38
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.h | 94
smartlearning/server/app/services/__pycache__/device_store.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.json | 14
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/README.md | 48
xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.h | 271
smartlearning/server/app/routes/__pycache__/scene.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/1.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc | 223
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h | 167
xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.json | 9
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.h | 59
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/README.md | 43
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.h | 34
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README.md | 41
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/README.md | 4
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc | 358
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/esp_sparkbot_board.cc | 302
smartlearning/docs/interface.md | 1363
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/4.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c | 504
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc | 395
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/0.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/5.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/README.md | 39
xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/6.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc | 214
xiaozhi-esp32-2.2.6/.gitignore | 20
xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/power_manager.h | 189
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/README.md | 11
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.h | 69
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/lcd_init_cmds.h | 224
xiaozhi-esp32-2.2.6/scripts/p3_tools/batch_convert_gui.py | 221
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/README.md | 48
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/6.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/waveshare-s3-epaper-3.97.cc | 178
xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/3.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.h | 89
xiaozhi-esp32-2.2.6/docs/v1/atoms3r.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc | 229
xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/README.md | 48
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.cc | 59
xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/config.h | 64
xiaozhi-esp32-2.2.6/main/boards/common/system_reset.h | 21
xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.h | 49
xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/4.ogg | 0
xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.h | 117
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/README.md | 25
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/README.md | 48
xiaozhi-esp32-2.2.6/docs/v1/m5cores3.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc | 284
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.h | 90
xiaozhi-esp32-2.2.6/partitions/v2/16m.csv | 8
xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.h | 30
xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.json | 14
xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc | 312
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/README.md | 3
xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/4.ogg | 0
xiaozhi-esp32-2.2.6/docs/v0/ESP32-BreadBoard.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/5.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/2.ogg | 0
smartlearning/data/courses/course02/chapter01/resources/003.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/8.ogg | 0
xiaozhi-esp32-2.2.6/docs/v1/magiclick.jpg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc | 133
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/README.md | 3
xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.h | 54
xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.h | 50
xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/wificonfig.ogg | 0
xiaozhi-esp32-2.2.6/scripts/acoustic_check/main.py | 18
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/upgrade.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/1.ogg | 0
smartlearning/Interface.md | 1362
smartlearning/server/app/__pycache__/__init__.cpython-313.pyc | 0
xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.h | 41
smartlearning/data/courses/course02/chapter02/resources/001.jpg | 0
xiaozhi-esp32-2.2.6/main/boards/aipi-lite/power_manager.h | 187
xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.h | 40
xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.cc | 375
xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/9.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.h | 63
xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/7.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/1.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/language.json | 59
xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_reg.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/3.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.cc | 270
xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/2.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.h | 91
xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/4.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc | 201
xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/wificonfig.ogg | 0
smartlearning/server/.env | 22
xiaozhi-esp32-2.2.6/docs/mcp-usage_zh.md | 115
xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/0.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc | 292
xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.json | 14
xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_pin.ogg | 0
xiaozhi-esp32-2.2.6/partitions/v2/16m_c3.csv | 8
xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/2.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/welcome.ogg | 0
xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/activation.ogg | 0
xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/esp32-s3-touch-amoled-1.43c.cc | 203
xiaozhi-esp32-2.2.6/partitions/v1/4m_esp-hi.csv | 8
smartlearning/README.md | 736
1,627 files changed, 126,552 insertions(+), 0 deletions(-)
diff --git a/smartlearning/Interface.md b/smartlearning/Interface.md
new file mode 100644
index 0000000..a1f2508
--- /dev/null
+++ b/smartlearning/Interface.md
@@ -0,0 +1,1362 @@
+# SmartLearning 鎺ュ彛鏂囨。
+
+> 鏈嶅姟榛樿鍦板潃锛歚http://{host}:{port}`锛堥粯璁� `http://0.0.0.0:9500`锛�
+> 浜や簰寮� API 鏂囨。锛氬惎鍔ㄦ湇鍔″悗璁块棶 `/docs`
+> 鍗忚鐗堟湰锛欻TTP API v1 / MQTT snlp/v1
+
+---
+
+## 鐩綍
+
+- [涓�銆侀�氱敤璇存槑](#涓�閫氱敤璇存槑)
+- [浜屻�丠TTP 鎺ュ彛](#浜宧ttp-鎺ュ彛)
+ - [2.1 绯荤粺](#21-绯荤粺)
+ - [2.2 闈欐�佽祫婧怾(#22-闈欐�佽祫婧�)
+ - [2.3 鍦烘櫙璇嗗埆](#23-鍦烘櫙璇嗗埆)
+ - [2.4 瀵硅瘽娴乚(#24-瀵硅瘽娴�)
+ - [2.5 璇剧▼涓庝换鍔(#25-璇剧▼涓庝换鍔�)
+ - [2.6 璁惧鍒濆鍖朷(#26-璁惧鍒濆鍖�)
+ - [2.7 璁惧绠$悊](#27-璁惧绠$悊)
+- [涓夈�丮QTT 鎺ュ彛](#涓塵qtt-鎺ュ彛)
+ - [3.1 Topic 瑙勫垯](#31-topic-瑙勫垯)
+ - [3.2 娑堟伅閫氱敤鏍煎紡](#32-娑堟伅閫氱敤鏍煎紡)
+ - [3.3 缁堢涓婃姤锛坮pt锛塢(#33-缁堢涓婃姤rpt)
+ - [3.4 浜戠涓嬪彂锛坈md锛塢(#34-浜戠涓嬪彂cmd)
+ - [3.5 鏈嶅姟绔嚜鍔ㄤ笅鍙戞祦绋媇(#35-鏈嶅姟绔嚜鍔ㄤ笅鍙戞祦绋�)
+- [鍥涖�佹帴鍙i�熸煡琛╙(#鍥涙帴鍙i�熸煡琛�)
+
+---
+
+## 涓�銆侀�氱敤璇存槑
+
+### 1.1 Base URL
+
+| 鐜 | 鍦板潃 |
+|------|------|
+| 鏈湴寮�鍙� | `http://localhost:9500` |
+| 鐢熶骇閮ㄧ讲 | 鐢� `.env` 涓� `BASE_URL` 閰嶇疆锛岀粓绔笅杞借祫婧愰』浣跨敤璇ュ湴鍧� |
+
+### 1.2 璇锋眰澶�
+
+JSON 鎺ュ彛缁熶竴浣跨敤锛�
+
+```
+Content-Type: application/json
+```
+
+SSE 瀵硅瘽娴佹帴鍙h繑鍥烇細
+
+```
+Content-Type: text/event-stream
+```
+
+### 1.3 閿欒鍝嶅簲
+
+FastAPI 鏍囧噯閿欒鏍煎紡锛�
+
+```json
+{
+ "detail": "Course not found: course99"
+}
+```
+
+甯歌 HTTP 鐘舵�佺爜锛�
+
+| 鐘舵�佺爜 | 璇存槑 |
+|--------|------|
+| 200 | 鎴愬姛 |
+| 400 | 璇锋眰鍙傛暟閿欒 |
+| 404 | 璧勬簮涓嶅瓨鍦� |
+| 422 | 璇锋眰浣撴牎楠屽け璐� |
+
+---
+
+## 浜屻�丠TTP 鎺ュ彛
+
+### 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锛圫erver-Sent Events锛夋祦寮忚繑鍥炴暀瀛﹀璇濆唴瀹广��
+
+**Query 鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 榛樿鍊� | 璇存槑 |
+|------|------|------|--------|------|
+| dialogue_id | string | 鍚� | 鑷姩鐢熸垚 UUID | 瀵硅瘽 ID |
+| user_text | string | 鍚� | 鈥� | 鐢ㄦ埛鍥炲鏂囨湰锛圓SR 缁撴灉锛� |
+| 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"
+```
+
+**鍝嶅簲鏍煎紡锛圫SE锛夛細**
+
+```
+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 | 鍚� | 鈥� | 鏈帴鍙d笉浣跨敤 |
+
+**璇锋眰绀轰緥锛�**
+
+```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": []
+ }
+}
+```
+
+---
+
+## 涓夈�丮QTT 鎺ュ彛
+
+### 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 娑堟伅閫氱敤鏍煎紡
+
+#### 浜戠涓嬪彂锛坈md/ctl銆乧md/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锛圲UID锛夛紝缁堢 ack 鏃堕』鍥炰紶 |
+| timestamp | int | Unix 鏃堕棿鎴� |
+| ttl | int | 鎸囦护鏈夋晥鏈燂紙绉掞級 |
+
+#### 缁堢涓婃姤锛坮pt/*锛�
+
+```json
+{
+ "header": {
+ "device_id": "SN-88293021",
+ "timestamp": 1718365205
+ },
+ "msg_type": "state",
+ "payload": {}
+}
+```
+
+---
+
+### 3.3 缁堢涓婃姤锛坮pt锛�
+
+#### 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 浜戠涓嬪彂锛坈md锛�
+
+#### 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 鍙戝竷绀轰緥锛坢osquitto_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":[]}}'
+```
+
+---
+
+## 鍥涖�佹帴鍙i�熸煡琛�
+
+### 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*
diff --git a/smartlearning/README.md b/smartlearning/README.md
new file mode 100644
index 0000000..5b68360
--- /dev/null
+++ b/smartlearning/README.md
@@ -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銆丼SE 瀵硅瘽娴併�侀潤鎬佽祫婧愭墭绠� |
+| 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锛氬熀纭�璇剧▼璧勬簮鍚屾锛坈md/task锛� 鈹�
+鈹� 路 UPDATE_MANIFEST 涓嬪彂瀹屾暣璧勬簮娓呭崟 鈹�
+鈹� 路 DOWNLOAD 脳 N 閫愭枃浠朵笅杞借祫婧愬寘 鈹�
+鈹� 锛坕ndex.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"
+}
+```
+
+璇ユ帴鍙h鍙� `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` | 浜嬩欢锛堝敜閱掋�丄SR 缁撴灉绛夛級 |
+| 缁堢 鈫� 浜� | `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`
+浜や簰寮忔枃妗o細`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銆丮QTT 绛�
+```
+
+> **閲嶈锛�** `BASE_URL` 蹇呴』璁剧疆涓虹粓绔兘璁块棶鍒扮殑鍦板潃锛堝叕缃� IP 鎴栧煙鍚嶏級锛屽惁鍒� `DOWNLOAD` 鎸囦护涓殑璧勬簮 URL 鏃犳晥銆�
+
+**4. 鍑嗗璇剧▼璧勬簮**
+
+灏嗚棰戞枃浠舵斁鍏� `data/courses/` 瀵瑰簲鐩綍锛岀‘淇濅笌 `course.json` 涓� `path` 涓�鑷淬��
+
+**5. 浣跨敤 systemd 瀹堟姢杩涚▼锛圠inux锛�**
+
+鍒涘缓 `/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 鍙嶅悜浠g悊锛堝彲閫夛級**
+
+```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. 鏇存柊浠g爜
+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 浜や簰寮忔枃妗o細鍚姩鏈嶅姟鍚庤闂� `/docs`
+
+---
+
+## 鐗堟湰
+
+- 鏈嶅姟绔増鏈細1.0.0
+- 鍗忚鐗堟湰锛歴nlp/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}'
diff --git a/smartlearning/data/courses/course01/chapter01/resources/.gitkeep b/smartlearning/data/courses/course01/chapter01/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/.gitkeep
diff --git a/smartlearning/data/courses/course01/chapter01/resources/001.jpg b/smartlearning/data/courses/course01/chapter01/resources/001.jpg
new file mode 100644
index 0000000..8fb414a
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/001.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter01/resources/001.mp3 b/smartlearning/data/courses/course01/chapter01/resources/001.mp3
new file mode 100644
index 0000000..8eb76e6
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/001.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter01/resources/002.jpg b/smartlearning/data/courses/course01/chapter01/resources/002.jpg
new file mode 100644
index 0000000..e53f985
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/002.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter01/resources/002.mp3 b/smartlearning/data/courses/course01/chapter01/resources/002.mp3
new file mode 100644
index 0000000..c2db0db
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/002.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter01/resources/003.jpg b/smartlearning/data/courses/course01/chapter01/resources/003.jpg
new file mode 100644
index 0000000..fcda377
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/003.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter01/resources/003.mp3 b/smartlearning/data/courses/course01/chapter01/resources/003.mp3
new file mode 100644
index 0000000..803605f
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter01/resources/003.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter02/resources/.gitkeep b/smartlearning/data/courses/course01/chapter02/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/.gitkeep
diff --git a/smartlearning/data/courses/course01/chapter02/resources/001.jpg b/smartlearning/data/courses/course01/chapter02/resources/001.jpg
new file mode 100644
index 0000000..8fb414a
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/001.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter02/resources/001.mp3 b/smartlearning/data/courses/course01/chapter02/resources/001.mp3
new file mode 100644
index 0000000..3eeda7a
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/001.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter02/resources/002.jpg b/smartlearning/data/courses/course01/chapter02/resources/002.jpg
new file mode 100644
index 0000000..e53f985
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/002.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter02/resources/002.mp3 b/smartlearning/data/courses/course01/chapter02/resources/002.mp3
new file mode 100644
index 0000000..e92a4e1
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/002.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter02/resources/003.jpg b/smartlearning/data/courses/course01/chapter02/resources/003.jpg
new file mode 100644
index 0000000..fcda377
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/003.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter02/resources/003.mp3 b/smartlearning/data/courses/course01/chapter02/resources/003.mp3
new file mode 100644
index 0000000..70528cb
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter02/resources/003.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter03/resources/.gitkeep b/smartlearning/data/courses/course01/chapter03/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/.gitkeep
diff --git a/smartlearning/data/courses/course01/chapter03/resources/001.jpg b/smartlearning/data/courses/course01/chapter03/resources/001.jpg
new file mode 100644
index 0000000..8fb414a
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/001.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter03/resources/001.mp3 b/smartlearning/data/courses/course01/chapter03/resources/001.mp3
new file mode 100644
index 0000000..3eeda7a
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/001.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter03/resources/002.jpg b/smartlearning/data/courses/course01/chapter03/resources/002.jpg
new file mode 100644
index 0000000..e53f985
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/002.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter03/resources/002.mp3 b/smartlearning/data/courses/course01/chapter03/resources/002.mp3
new file mode 100644
index 0000000..e92a4e1
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/002.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter03/resources/003.jpg b/smartlearning/data/courses/course01/chapter03/resources/003.jpg
new file mode 100644
index 0000000..fcda377
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/003.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course01/chapter03/resources/003.mp3 b/smartlearning/data/courses/course01/chapter03/resources/003.mp3
new file mode 100644
index 0000000..70528cb
--- /dev/null
+++ b/smartlearning/data/courses/course01/chapter03/resources/003.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course01/course.json b/smartlearning/data/courses/course01/course.json
new file mode 100644
index 0000000..5246fd6
--- /dev/null
+++ b/smartlearning/data/courses/course01/course.json
@@ -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"
+ }
+ ]
+ }
+ ]
+}
diff --git a/smartlearning/data/courses/course01/courseTask.json b/smartlearning/data/courses/course01/courseTask.json
new file mode 100644
index 0000000..aebe43e
--- /dev/null
+++ b/smartlearning/data/courses/course01/courseTask.json
@@ -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"
+ }
+ }
+ ]
+}
diff --git a/smartlearning/data/courses/course02/chapter01/resources/.gitkeep b/smartlearning/data/courses/course02/chapter01/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/.gitkeep
diff --git a/smartlearning/data/courses/course02/chapter01/resources/001.jpg b/smartlearning/data/courses/course02/chapter01/resources/001.jpg
new file mode 100644
index 0000000..8fb414a
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/001.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter01/resources/001.mp3 b/smartlearning/data/courses/course02/chapter01/resources/001.mp3
new file mode 100644
index 0000000..8eb76e6
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/001.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter01/resources/002.jpg b/smartlearning/data/courses/course02/chapter01/resources/002.jpg
new file mode 100644
index 0000000..e53f985
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/002.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter01/resources/002.mp3 b/smartlearning/data/courses/course02/chapter01/resources/002.mp3
new file mode 100644
index 0000000..c2db0db
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/002.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter01/resources/003.jpg b/smartlearning/data/courses/course02/chapter01/resources/003.jpg
new file mode 100644
index 0000000..fcda377
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/003.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter01/resources/003.mp3 b/smartlearning/data/courses/course02/chapter01/resources/003.mp3
new file mode 100644
index 0000000..803605f
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter01/resources/003.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter02/resources/.gitkeep b/smartlearning/data/courses/course02/chapter02/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/.gitkeep
diff --git a/smartlearning/data/courses/course02/chapter02/resources/001.jpg b/smartlearning/data/courses/course02/chapter02/resources/001.jpg
new file mode 100644
index 0000000..8fb414a
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/001.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter02/resources/001.mp3 b/smartlearning/data/courses/course02/chapter02/resources/001.mp3
new file mode 100644
index 0000000..3eeda7a
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/001.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter02/resources/002.jpg b/smartlearning/data/courses/course02/chapter02/resources/002.jpg
new file mode 100644
index 0000000..e53f985
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/002.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter02/resources/002.mp3 b/smartlearning/data/courses/course02/chapter02/resources/002.mp3
new file mode 100644
index 0000000..e92a4e1
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/002.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter02/resources/003.jpg b/smartlearning/data/courses/course02/chapter02/resources/003.jpg
new file mode 100644
index 0000000..fcda377
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/003.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter02/resources/003.mp3 b/smartlearning/data/courses/course02/chapter02/resources/003.mp3
new file mode 100644
index 0000000..70528cb
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter02/resources/003.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter03/resources/.gitkeep b/smartlearning/data/courses/course02/chapter03/resources/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/.gitkeep
diff --git a/smartlearning/data/courses/course02/chapter03/resources/001.jpg b/smartlearning/data/courses/course02/chapter03/resources/001.jpg
new file mode 100644
index 0000000..8fb414a
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/001.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter03/resources/001.mp3 b/smartlearning/data/courses/course02/chapter03/resources/001.mp3
new file mode 100644
index 0000000..3eeda7a
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/001.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter03/resources/002.jpg b/smartlearning/data/courses/course02/chapter03/resources/002.jpg
new file mode 100644
index 0000000..e53f985
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/002.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter03/resources/002.mp3 b/smartlearning/data/courses/course02/chapter03/resources/002.mp3
new file mode 100644
index 0000000..e92a4e1
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/002.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter03/resources/003.jpg b/smartlearning/data/courses/course02/chapter03/resources/003.jpg
new file mode 100644
index 0000000..fcda377
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/003.jpg
Binary files differ
diff --git a/smartlearning/data/courses/course02/chapter03/resources/003.mp3 b/smartlearning/data/courses/course02/chapter03/resources/003.mp3
new file mode 100644
index 0000000..70528cb
--- /dev/null
+++ b/smartlearning/data/courses/course02/chapter03/resources/003.mp3
Binary files differ
diff --git a/smartlearning/data/courses/course02/course.json b/smartlearning/data/courses/course02/course.json
new file mode 100644
index 0000000..f86eb09
--- /dev/null
+++ b/smartlearning/data/courses/course02/course.json
@@ -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": "姝e紡閭�璇峰嚱鍐欎綔",
+ "path": "courses/course02/chapter03/resources/002.mp3"
+ },
+ {
+ "id": "003",
+ "title": "椤圭洰杩涘害姹囨姤",
+ "path": "courses/course02/chapter03/resources/003.mp3"
+ }
+ ]
+ }
+ ]
+}
diff --git a/smartlearning/data/courses/course02/courseTask.json b/smartlearning/data/courses/course02/courseTask.json
new file mode 100644
index 0000000..52e66f1
--- /dev/null
+++ b/smartlearning/data/courses/course02/courseTask.json
@@ -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"
+ }
+ ]
+
+}
\ No newline at end of file
diff --git a/smartlearning/data/courses/index.json b/smartlearning/data/courses/index.json
new file mode 100644
index 0000000..51d73fe
--- /dev/null
+++ b/smartlearning/data/courses/index.json
@@ -0,0 +1,12 @@
+{
+ "courses": [
+ {
+ "course_id": "course01",
+ "config": "courses/course01/course.json"
+ },
+ {
+ "course_id": "course02",
+ "config": "courses/course02/course.json"
+ }
+ ]
+}
diff --git a/smartlearning/data/tasklist.json b/smartlearning/data/tasklist.json
new file mode 100644
index 0000000..990d3b8
--- /dev/null
+++ b/smartlearning/data/tasklist.json
@@ -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"
+ }
+ ]
+ }
+ }
+ ]
+}
diff --git a/smartlearning/docs/interface.md b/smartlearning/docs/interface.md
new file mode 100644
index 0000000..3f81959
--- /dev/null
+++ b/smartlearning/docs/interface.md
@@ -0,0 +1,1363 @@
+# SmartLearning 鎺ュ彛鏂囨。
+
+> 鏈嶅姟榛樿鍦板潃锛歚http://{host}:{port}`锛堥粯璁� `http://0.0.0.0:2101`锛�
+> 浜や簰寮� API 鏂囨。锛氬惎鍔ㄦ湇鍔″悗璁块棶 `/docs`
+> 鍗忚鐗堟湰锛欻TTP API v1 / MQTT snlp/v1
+
+---
+
+## 鐩綍
+
+- [涓�銆侀�氱敤璇存槑](#涓�閫氱敤璇存槑)
+- [浜屻�丠TTP 鎺ュ彛](#浜宧ttp-鎺ュ彛)
+ - [2.1 绯荤粺](#21-绯荤粺)
+ - [2.2 闈欐�佽祫婧怾(#22-闈欐�佽祫婧�)
+ - [2.3 鍦烘櫙璇嗗埆](#23-鍦烘櫙璇嗗埆)
+ - [2.4 瀵硅瘽娴乚(#24-瀵硅瘽娴�)
+ - [2.5 璇剧▼涓庝换鍔(#25-璇剧▼涓庝换鍔�)
+ - [2.6 璁惧鍒濆鍖朷(#26-璁惧鍒濆鍖�)
+ - [2.7 璁惧绠$悊](#27-璁惧绠$悊)
+- [涓夈�丮QTT 鎺ュ彛](#涓塵qtt-鎺ュ彛)
+ - [3.1 Topic 瑙勫垯](#31-topic-瑙勫垯)
+ - [3.2 娑堟伅閫氱敤鏍煎紡](#32-娑堟伅閫氱敤鏍煎紡)
+ - [3.3 缁堢涓婃姤锛坮pt锛塢(#33-缁堢涓婃姤rpt)
+ - [3.4 浜戠涓嬪彂锛坈md锛塢(#34-浜戠涓嬪彂cmd)
+ - [3.5 鏈嶅姟绔嚜鍔ㄤ笅鍙戞祦绋媇(#35-鏈嶅姟绔嚜鍔ㄤ笅鍙戞祦绋�)
+- [鍥涖�佹帴鍙i�熸煡琛╙(#鍥涙帴鍙i�熸煡琛�)
+
+---
+
+## 涓�銆侀�氱敤璇存槑
+
+### 1.1 Base URL
+
+| 鐜 | 鍦板潃 |
+|------|------|
+| 鏈湴寮�鍙� | `http://124.222.109.197:2101` |
+| 鐢熶骇閮ㄧ讲 | 鐢� `.env` 涓� `BASE_URL` 閰嶇疆锛岀粓绔笅杞借祫婧愰』浣跨敤璇ュ湴鍧� |
+
+### 1.2 璇锋眰澶�
+
+JSON 鎺ュ彛缁熶竴浣跨敤锛�
+
+```
+Content-Type: application/json
+```
+
+SSE 瀵硅瘽娴佹帴鍙h繑鍥烇細
+
+```
+Content-Type: text/event-stream
+```
+
+### 1.3 閿欒鍝嶅簲
+
+FastAPI 鏍囧噯閿欒鏍煎紡锛�
+
+```json
+{
+ "detail": "Course not found: course99"
+}
+```
+
+甯歌 HTTP 鐘舵�佺爜锛�
+
+| 鐘舵�佺爜 | 璇存槑 |
+|--------|------|
+| 200 | 鎴愬姛 |
+| 400 | 璇锋眰鍙傛暟閿欒 |
+| 404 | 璧勬簮涓嶅瓨鍦� |
+| 422 | 璇锋眰浣撴牎楠屽け璐� |
+
+---
+
+## 浜屻�丠TTP 鎺ュ彛
+
+### 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锛圫erver-Sent Events锛夋祦寮忚繑鍥炴暀瀛﹀璇濆唴瀹广��
+
+**Query 鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 榛樿鍊� | 璇存槑 |
+|------|------|------|--------|------|
+| dialogue_id | string | 鍚� | 鑷姩鐢熸垚 UUID | 瀵硅瘽 ID |
+| user_text | string | 鍚� | 鈥� | 鐢ㄦ埛鍥炲鏂囨湰锛圓SR 缁撴灉锛� |
+| 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"
+```
+
+**鍝嶅簲鏍煎紡锛圫SE锛夛細**
+
+```
+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 | 鍚� | 鈥� | 鏈帴鍙d笉浣跨敤 |
+
+**璇锋眰绀轰緥锛�**
+
+```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": []
+ }
+}
+```
+
+---
+
+## 涓夈�丮QTT 鎺ュ彛
+
+### 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 娑堟伅閫氱敤鏍煎紡
+
+#### 浜戠涓嬪彂锛坈md/ctl銆乧md/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锛圲UID锛夛紝缁堢 ack 鏃堕』鍥炰紶 |
+| timestamp | int | Unix 鏃堕棿鎴� |
+| ttl | int | 鎸囦护鏈夋晥鏈燂紙绉掞級 |
+
+#### 缁堢涓婃姤锛坮pt/*锛�
+
+```json
+{
+ "header": {
+ "device_id": "SN-88293021",
+ "timestamp": 1718365205
+ },
+ "msg_type": "state",
+ "payload": {}
+}
+```
+
+---
+
+### 3.3 缁堢涓婃姤锛坮pt锛�
+
+#### 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 浜戠涓嬪彂锛坈md锛�
+
+#### 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 鍙戝竷绀轰緥锛坢osquitto_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":[]}}'
+```
+
+---
+
+## 鍥涖�佹帴鍙i�熸煡琛�
+
+### 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*
+
diff --git a/smartlearning/docs/mqtt_interface.md b/smartlearning/docs/mqtt_interface.md
new file mode 100644
index 0000000..21becbb
--- /dev/null
+++ b/smartlearning/docs/mqtt_interface.md
@@ -0,0 +1,429 @@
+# 鎺ュ彛鏂囨。
+
+> 浜や簰寮� API 鏂囨。锛氬惎鍔ㄦ湇鍔″悗璁块棶 `/docs`
+> 鍗忚鐗堟湰锛�/ MQTT snlp/v1
+
+---
+
+## 鐩綍
+- [涓�銆丮QTT 鎺ュ彛](#涓�mqtt-鎺ュ彛)
+ - [1.1 Topic 瑙勫垯](#11-topic-瑙勫垯)
+ - [1.2 娑堟伅閫氱敤鏍煎紡](#12-娑堟伅閫氱敤鏍煎紡)
+ - [1.3 缁堢涓婃姤锛坮pt锛塢(#13-缁堢涓婃姤rpt)
+ - [1.4 浜戠涓嬪彂锛坈md锛塢(#14-浜戠涓嬪彂cmd)
+ - [1.5 鏈嶅姟绔嚜鍔ㄤ笅鍙戞祦绋媇(#15-鏈嶅姟绔嚜鍔ㄤ笅鍙戞祦绋�)
+- [浜屻�佹帴鍙i�熸煡琛╙(#浜屾帴鍙i�熸煡琛�)
+
+---
+
+
+## 涓�銆丮QTT 鎺ュ彛
+
+### 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 娑堟伅閫氱敤鏍煎紡
+
+#### 浜戠涓嬪彂锛坈md/ctl銆乧md/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锛圲UID锛夛紝缁堢 ack 鏃堕』鍥炰紶 |
+| timestamp | int | Unix 鏃堕棿鎴� |
+| ttl | int | 鎸囦护鏈夋晥鏈燂紙绉掞級 |
+
+#### 缁堢涓婃姤锛坮pt/*锛�
+
+```json
+{
+ "header": {
+ "device_id": "SN-88293021",
+ "timestamp": 1718365205
+ },
+ "msg_type": "state",
+ "payload": {}
+}
+```
+
+---
+
+### 1.3 缁堢涓婃姤锛坮pt锛�
+
+#### 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 浜戠涓嬪彂锛坈md锛�
+
+#### 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 鍙戝竷绀轰緥锛坢osquitto_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":[]}}'
+```
+
+---
+
+## 浜屻�佹帴鍙i�熸煡琛�
+
+### 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*
+
diff --git "a/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231 v2 - \345\211\257\346\234\254.pdf" "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231 v2 - \345\211\257\346\234\254.pdf"
new file mode 100644
index 0000000..ea6b869
--- /dev/null
+++ "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231 v2 - \345\211\257\346\234\254.pdf"
Binary files differ
diff --git "a/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231.pdf" "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231.pdf"
new file mode 100644
index 0000000..ef75f8a
--- /dev/null
+++ "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231.pdf"
Binary files differ
diff --git "a/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231V1.1.pdf" "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231V1.1.pdf"
new file mode 100644
index 0000000..033b819
--- /dev/null
+++ "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231V1.1.pdf"
Binary files differ
diff --git "a/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231_V1.0.pdf" "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231_V1.0.pdf"
new file mode 100644
index 0000000..a4e7bfb
--- /dev/null
+++ "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231_V1.0.pdf"
Binary files differ
diff --git "a/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231_V1.1.xlsx" "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231_V1.1.xlsx"
new file mode 100644
index 0000000..10424e6
--- /dev/null
+++ "b/smartlearning/docs/mqtt\346\266\210\346\201\257\350\247\204\345\210\231_V1.1.xlsx"
Binary files differ
diff --git a/smartlearning/docs/requires.txt b/smartlearning/docs/requires.txt
new file mode 100644
index 0000000..c4f20e6
--- /dev/null
+++ b/smartlearning/docs/requires.txt
@@ -0,0 +1,368 @@
+绗竴閮ㄥ垎锛氫骇鍝侀渶姹傝鏍艰鏄庝功锛圥RD锛夛細鎯呭鎰熺煡鏅鸿兘鑻辫瀛︿範绯荤粺
+
+1. 椤圭洰姒傝堪
+1.1 椤圭洰鑳屾櫙
+鐜版湁鑻辫瀛︿範浜у搧澶氫负绂绘暎鐨勨�滈搴撳紡鈥濇垨鈥滆鏈紡鈥濇暀瀛︼紝缂轰箯璇涓庡簲鐢ㄧ粨鍚堛�傛湰浜у搧閫氳繃寮曞叆杈圭紭璁$畻涓庡ぇ妯″瀷鎶�鏈紝瀹炵幇鍩轰簬鐗╃悊鐜鎰熺煡鐨勬矇娴稿紡鑻辫鏁欏锛岃В鍐斥�滃鑰屼笉鐢ㄢ�濈殑闂銆�
+
+1.2 浜у搧鐩爣
+- 瀹炵幇鈥滃満鏅劅鐭� -> 鎰忓浘鍒嗘瀽 -> 鏅鸿兘瀵硅瘽 -> 瀛︿範鍙嶉鈥濈殑闂幆绯荤粺銆�
+- 鎵撻�犲叿澶� IoT 鍩虹鎺у埗銆佸鍔熻兘闆嗘垚涓庨珮闅愮淇濇姢鐨勫涔犵粓绔��
+- 鏋勫缓浜戠 AI 鏁欏鐭╅樀锛屾敮鎸佽嚜閫傚簲闅惧害璋冩暣涓庝釜鎬у寲瀛︿範杞ㄨ抗銆�
+
+2. 缁堢璁惧璇︾粏闇�姹�
+2.1 浜や簰鐘舵�佹満锛團SM锛�
+- 寰呮満锛圛dle锛夛細瀹炴椂杩涜 VAD 妫�娴嬶紝浣庡姛鑰楄繍琛屻��
+- 瀵硅瘽妯″紡锛圖ialogue锛夛細澶勭悊鍏ㄥ弻宸ヨ闊虫祦锛屾敮鎸佹墦鏂紙Barge-in锛夈��
+- 缈昏瘧妯″紡锛圱ranslate锛夛細鐭椂褰曢煶锛屼簯绔垨鏈湴妯″瀷杩涜缈昏瘧骞舵挱鏀俱��
+- 鏁欏妯″紡锛圫tudy锛夛細鍔犺浇棰勮璇剧▼鍐呭锛屾敮鎸佺偣璇汇�佽窡璇汇�佽瘎鍒嗐��
+- 浼戠湢锛圫leep锛夛細浠呬繚鐣欏績璺虫娴嬩笌棰勮鍞ら啋璇嶇洃鍚紝绂佺敤楹﹀厠椋庨樀鍒椼��
+
+2.2 鏍稿績鍔熻兘鎶�鏈寚鏍�
+- 璇煶閲囬泦锛氭敮鎸� 3 绫宠繙鍦烘嬀闊筹紝淇″櫔姣� (SNR) > 15dB锛屾敮鎸佸0婧愬畾浣� (DoA)銆�
+- 闄嶅櫔/鍥炲0娑堥櫎 (AEC)锛氭敮鎸� 50dB 浠ヤ笂鍥炲0娑堥櫎锛岀‘淇濈粓绔挱鏀鹃煶棰戞椂浠嶅彲鎷惧彇鐢ㄦ埛璇煶銆�
+- 鏈湴缈昏瘧锛氭敮鎸佺绾夸腑鑻变簰璇戯紙棣栧瓧寤惰繜 < 300ms锛夈��
+- 鍝嶅簲閫熷害锛氫粠閲囬泦鍒版湇鍔$涓嬪彂鍝嶅簲鐨� RTT (Round Trip Time) 闇�鎺у埗鍦� 1.5s 浠ュ唴銆�
+
+2.3 IoT 涓庣郴缁熼泦鎴�
+- 鐢垫簮绠$悊锛氭敮鎸� RTC 瀹氭椂锛屾敮鎸佸紓甯告柇鐢佃嚜鍚姩锛屾敮鎸佺數閲忎綆鏃惰嚜鍔ㄨ繘鍏ョ渷鐢垫ā寮忋��
+- 缃戠粶杩炴帴锛氳嚜鍔ㄥ垏鎹� Wi-Fi/4G/5G锛屾敮鎸佹柇缃戞湰鍦扮紦瀛橈紙鏈�澶ф敮鎸� 100MB 璇煶鐗囨缂撳瓨锛夈��
+- 瀹夊叏鎬э細鐗╃悊楹﹀厠椋庨潤闊冲紑鍏筹紙纭欢绾ф柇寮�锛夛紝绔晶鏁版嵁鍔犲瘑瀛樺偍 (AES-128)銆�
+
+3. 鏈嶅姟绔钩鍙拌缁嗛渶姹�
+3.1 鍦烘櫙璇嗗埆寮曟搸 (AI Context Engine)
+- 鍦烘櫙瑙f瀽锛氶�氳繃 LLM 瀵圭幆澧冮煶璇箟鐞嗚В锛屽垎绫诲櫒瀹氫箟涓猴細灞呭(鍗у/鍘ㄦ埧/涔︽埧)銆佹牎鍐呫�侀�氬嫟銆佽繍鍔ㄧ瓑銆�
+- 瀵硅瘽鐢熸垚锛氶泦鎴� LLM Agent锛屽熀浜庣敤鎴风敾鍍忓巻鍙蹭笌褰撳墠鍦烘櫙锛屽姩鎬佺敓鎴愪笅涓�杞彁闂紙鏀寔澶氳疆涓婁笅鏂囩鐞嗭級銆�
+- 缃俊搴︽満鍒讹細鑻ュ満鏅瘑鍒疆淇″害 < 0.7锛岃Е鍙戦�氱敤闂�欑瓥鐣ワ紝閬垮厤骞叉壈鐢ㄦ埛銆�
+
+3.2 鏁欏绠℃帶绯荤粺
+- 鐢ㄦ埛妯″瀷锛氳褰曠敤鎴风殑CEFR绛夌骇銆佽瘝姹囧簱銆侀敊璇巼锛岄�氳繃鍙嶉寰幆鍔ㄦ�佽皟鑺傞毦搴︺��
+- 鏁版嵁瀛樺偍锛氱鍚� GDPR/PIPL 闅愮鍚堣锛屾晱鎰熻闊虫暟鎹畾鏈熻劚鏁忓鐞嗭紝涓嶇暀瀛樻湭缁忚鍙殑鍘熷闊抽銆�
+
+4. 闈炲姛鑳芥�ч渶姹傦紙SLA锛�
+- 鍙敤鎬э細绯荤粺鍙敤鎬� 99.9%銆�
+- 鎬ц兘鐡堕锛氬ぇ妯″瀷鎺ㄧ悊鎺ュ彛鏀寔鑷冲皯 1000 QPS 骞跺彂銆�
+- 瀹夊叏鍚堣锛氶�氳繃鍔犲瘑閫氶亾锛圱LS 1.3锛夐�氫俊锛涜澶囩粦瀹氬叿澶囧敮涓�鐨勫畨鍏ㄨ姱鐗囨牎楠岋紙Secure Element锛夈��
+- 鍙墿灞曟�э細鏀寔閫氳繃鎻掍欢鏈哄埗澧炲姞鏂拌绉嶆垨鍦烘櫙妯℃澘銆�
+
+5. 浜や簰鍗忚涓庢暟鎹祦锛圓PI瑙勮寖锛�
+- 璇锋眰鎺ュ彛锛�/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 涓嬪彂鎺у埗鎸囦护锛堝锛歋etVolume, Sleep, UpdateFirmware锛夈��
+
+6. 鍏稿瀷鍦烘櫙閫昏緫娴佺▼瀹氫箟
+- 鍦烘櫙璇嗗埆锛氭娴嬪埌鐗瑰畾鐜闊崇汗 -> 鍒ゅ畾涓衡�滅敤椁愬満鏅�� -> 鍙戦�佹寚浠よ嚦缁堢鎾斁寮曞鎻愮ず闊炽��
+- 鍥炲澶勭悊锛氭娴嬬敤鎴峰洖澶� -> ASR璇嗗埆 -> 鍒嗘瀽璇硶涓庢纭�� -> 杩斿洖鍙嶉锛堣禐璧忔垨绾犻敊锛�-> 杩涘叆涓嬩竴杞彁闂��
+
+
+
+绗簩閮ㄥ垎锛氬弬鐓т互涓婇渶姹傦紝鍒濇涓�濂梞qtt娑堟伅鐨則opic瑙勫垯濡備笅:
+涓�銆佹秷鎭痶opic瀹氫箟锛� snlp/v1/{device_id}/{direct}/{msg_type}
+琛ㄦ牸濡備笅锛�
+鏂瑰悜 Topic 璺緞 msg_type 璇存槑
+rpt (缁堢->浜�) snlp/v1/{device_id}/rpt/state 鐘舵�佷笂鎶� 鍖呭惈蹇冭烦銆佺數閲忋�佺綉缁溿�佸綋鍓嶅伐浣滄ā寮�
+ snlp/v1/{device_id}/rpt/event 浜嬩欢涓婃姤 鍞ら啋/浼戠湢瑙﹀彂銆丄SR璇嗗埆缁撴灉銆佸紓甯告姤璀�
+ snlp/v1/{device_id}/rpt/log 绯荤粺鏃ュ織 鐢ㄤ簬杩滅▼鎺掗殰鐨勭郴缁熻繍琛屾棩蹇�
+ snlp/v1/{device_id}/rpt/ack 鎸囦护鍝嶅簲 鍏抽敭锛� 瀵瑰簲 CMD 鐨勬墽琛岀粨鏋� (鎴愬姛/澶辫触/杩涘害)
+ snlp/v1/{device_id}/rpt/progress 杩囩▼杩涘害 閽堝鈥滃紓姝ユ枃浠朵笅杞解�濆拰鈥淥TA鍗囩骇鈥�
+cmd (浜�->缁堢) snlp/v1/{device_id}/cmd/ctl 瀹炴椂鎸囦护 瀹炴椂鎸囦护锛堥煶閲忋�佷紤鐪犮�丯TP锛�
+ snlp/v1/{device_id}/cmd/task 寮傛浠诲姟鎸囦护 寮傛浠诲姟鎸囦护锛圤TA銆佷笅杞借祫婧愬寘锛�
+
+v1 : 琛ㄧず鐗堟湰鍙�1锛� 鐢ㄤ簬鐗堟湰杩囨浮鏃剁殑鍏煎鎬у鐞�
+device_id锛� 鏄澶囩殑鍞竴id锛屽彧璇伙紝鏄笉鑳借淇敼鐨� 锛堜互鍚庡啀鑰冭檻澧炲姞娉ㄥ唽鏈哄埗,鎹㈠彇姝e紡鐨� Access Token锛�
+direct锛� rpt (缁堢->浜�) 锛� cmd (浜�->缁堢)
+msg_type: state, event,log,ack,progress, task, ctl
+
+
+浜屻�丳ayload 璁捐瑙勮寖锛屼负浜嗕繚鎸佷竴鑷存�э紝鎵�鏈� 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": { ... } // 鍏蜂綋鎿嶄綔鍙傛暟
+}
+鍏蜂綋鐨刾ayload绀轰緥锛�
+鎾斁闊抽 (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": { ... }
+}
+鍏蜂綋鐨刾ayload绀轰緥锛�
+蹇冭烦/鍋ュ悍 (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 鏁扮粍锛岀粓绔敹鍒板悗瀛樺叆鏈湴鏂囦欢锛屽疄鐜拌祫婧愮殑绠$悊涓庡姣斻��
+
+浜斻�� 鍗忚瀹炴柦寤鸿锛圕hecklist锛�
+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 鎺ュ彛锛屼互渚垮悗缁揩閫熷鍔犲鈥滆繙绋嬭皟璇曗�濄�佲�滄棩蹇楁媺鍙栤�濈瓑鍔熻兘锛屾棤闇�淇敼鍘熸湁瑙f瀽鏋舵瀯銆�
+
+绗笁閮ㄥ垎锛氳棰戞枃浠剁洰褰曠粨鏋勫拰缁勬垚
+1. 鎵�鏈夎绋嬬殑鏍圭洰褰曞湪锛�./data/courses锛� ./data/coursesindex.json鏂囦欢鍐呭鏄凡纭畾鍙戝竷鐨勮绋嬶紝缁撴瀯濡傦細
+{
+ "courses": [
+ {
+ "course_id": "course01",
+ "config": "courses/course01/course.json"
+ },
+ {
+ "course_id": "course02",
+ "config": "courses/course02/course.json"
+ }
+ ]
+}
+2. 姣忎釜璇剧▼鐩綍涓嬫湁涓�浠絚ourse.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(鍒跺畾鏌愪竴闂ㄨ绋嬩换鍔℃竻鍗曪紝鐢ㄤ簬涓嬪彂鍒扮粓绔澶囪繘琛岃绋嬪垵濮嬪寲)锛屼娇瀹㈡埛绔拰鏈嶅姟绔彲浠ユ湁涓�濂楄嚜鍔ㄦ満鍒讹紝
+ 瀹㈡埛绔拰鏈嶅姟绔彲浠ヨ嚜鍔ㄥ寲鐨勯厤鍚堝畬鎴愬父瑙勭殑鍔熻兘鍜屾甯哥殑宸ヤ綔娴佺▼锛�
+
+
+
+
+浠ヤ笅鏄笉鏀逛唬鐮佹儏鍐典笅鐨�**鎵嬪姩鏇存柊璇剧▼**鎿嶄綔璇存槑锛屽垎涓夌鏂瑰紡銆�
+
+---
+
+## 鏂瑰紡涓�锛欻TTP 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. 閲嶆柊鍚屾鍏ㄩ儴鍩虹璇剧▼锛坕ndex.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銆乧ourse02 鍏� 2 鏉★級銆�
+
+---
+
+## 鏂瑰紡浜岋細鐩存帴鍙� MQTT 娑堟伅
+
+鐢� MQTT 瀹㈡埛绔紙MQTTX銆乵osquitto_pub 绛夛級杩炴帴 Broker锛屽悜缁堢鍙戝竷娑堟伅銆�
+
+**Topic锛�**
+```
+snlp/v1/{device_id}/cmd/task
+```
+
+**绀轰緥锛坈ourse01锛夛細**
+
+```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` 涓婃姤缁撴灉锛堣嫢缁堢宸插疄鐜帮級
+
+
+
+
+
+
+
+
+
+
diff --git a/smartlearning/docs/smartlearing_prd_v1.01.txt b/smartlearning/docs/smartlearing_prd_v1.01.txt
new file mode 100644
index 0000000..06892fe
--- /dev/null
+++ b/smartlearning/docs/smartlearing_prd_v1.01.txt
@@ -0,0 +1,201 @@
+绗竴閮ㄥ垎锛氫骇鍝侀渶姹傝鏍艰鏄庝功锛圥RD锛夛細鎯呭鎰熺煡鏅鸿兘鑻辫瀛︿範绯荤粺
+
+1. 椤圭洰姒傝堪
+1.1 椤圭洰鑳屾櫙
+鐜版湁鑻辫瀛︿範浜у搧澶氫负绂绘暎鐨勨�滈搴撳紡鈥濇垨鈥滆鏈紡鈥濇暀瀛︼紝缂轰箯璇涓庡簲鐢ㄧ粨鍚堛�傛湰浜у搧閫氳繃寮曞叆杈圭紭璁$畻涓庡ぇ妯″瀷鎶�鏈紝瀹炵幇鍩轰簬鐗╃悊鐜鎰熺煡鐨勬矇娴稿紡鑻辫鏁欏锛岃В鍐斥�滃鑰屼笉鐢ㄢ�濈殑闂銆�
+
+1.2 浜у搧鐩爣
+- 瀹炵幇鈥滃満鏅劅鐭� -> 鎰忓浘鍒嗘瀽 -> 鏅鸿兘瀵硅瘽 -> 瀛︿範鍙嶉鈥濈殑闂幆绯荤粺銆�
+- 鎵撻�犲叿澶� IoT 鍩虹鎺у埗銆佸鍔熻兘闆嗘垚涓庨珮闅愮淇濇姢鐨勫涔犵粓绔��
+- 鏋勫缓浜戠 AI 鏁欏鐭╅樀锛屾敮鎸佽嚜閫傚簲闅惧害璋冩暣涓庝釜鎬у寲瀛︿範杞ㄨ抗銆�
+
+2. 缁堢璁惧璇︾粏闇�姹�
+2.1 浜や簰鐘舵�佹満锛團SM锛�
+- 寰呮満锛圛dle锛夛細瀹炴椂杩涜 VAD 妫�娴嬶紝浣庡姛鑰楄繍琛屻��
+- 瀵硅瘽妯″紡锛圖ialogue锛夛細澶勭悊鍏ㄥ弻宸ヨ闊虫祦锛屾敮鎸佹墦鏂紙Barge-in锛夈��
+- 缈昏瘧妯″紡锛圱ranslate锛夛細鐭椂褰曢煶锛屼簯绔垨鏈湴妯″瀷杩涜缈昏瘧骞舵挱鏀俱��
+- 鏁欏妯″紡锛圫tudy锛夛細鍔犺浇棰勮璇剧▼鍐呭锛屾敮鎸佺偣璇汇�佽窡璇汇�佽瘎鍒嗐��
+- 浼戠湢锛圫leep锛夛細浠呬繚鐣欏績璺虫娴嬩笌棰勮鍞ら啋璇嶇洃鍚紝绂佺敤楹﹀厠椋庨樀鍒椼��
+
+2.2 鏍稿績鍔熻兘鎶�鏈寚鏍�
+- 璇煶閲囬泦锛氭敮鎸� 3 绫宠繙鍦烘嬀闊筹紝淇″櫔姣� (SNR) > 15dB锛屾敮鎸佸0婧愬畾浣� (DoA)銆�
+- 闄嶅櫔/鍥炲0娑堥櫎 (AEC)锛氭敮鎸� 50dB 浠ヤ笂鍥炲0娑堥櫎锛岀‘淇濈粓绔挱鏀鹃煶棰戞椂浠嶅彲鎷惧彇鐢ㄦ埛璇煶銆�
+- 鏈湴缈昏瘧锛氭敮鎸佺绾夸腑鑻变簰璇戯紙棣栧瓧寤惰繜 < 300ms锛夈��
+- 鍝嶅簲閫熷害锛氫粠閲囬泦鍒版湇鍔$涓嬪彂鍝嶅簲鐨� RTT (Round Trip Time) 闇�鎺у埗鍦� 1.5s 浠ュ唴銆�
+
+2.3 IoT 涓庣郴缁熼泦鎴�
+- 鐢垫簮绠$悊锛氭敮鎸� RTC 瀹氭椂锛屾敮鎸佸紓甯告柇鐢佃嚜鍚姩锛屾敮鎸佺數閲忎綆鏃惰嚜鍔ㄨ繘鍏ョ渷鐢垫ā寮忋��
+- 缃戠粶杩炴帴锛氳嚜鍔ㄥ垏鎹� Wi-Fi/4G/5G锛屾敮鎸佹柇缃戞湰鍦扮紦瀛橈紙鏈�澶ф敮鎸� 100MB 璇煶鐗囨缂撳瓨锛夈��
+- 瀹夊叏鎬э細鐗╃悊楹﹀厠椋庨潤闊冲紑鍏筹紙纭欢绾ф柇寮�锛夛紝绔晶鏁版嵁鍔犲瘑瀛樺偍 (AES-128)銆�
+
+3. 鏈嶅姟绔钩鍙拌缁嗛渶姹�
+3.1 鍦烘櫙璇嗗埆寮曟搸 (AI Context Engine)
+- 鍦烘櫙瑙f瀽锛氶�氳繃 LLM 瀵圭幆澧冮煶璇箟鐞嗚В锛屽垎绫诲櫒瀹氫箟涓猴細灞呭(鍗у/鍘ㄦ埧/涔︽埧)銆佹牎鍐呫�侀�氬嫟銆佽繍鍔ㄧ瓑銆�
+- 瀵硅瘽鐢熸垚锛氶泦鎴� LLM Agent锛屽熀浜庣敤鎴风敾鍍忓巻鍙蹭笌褰撳墠鍦烘櫙锛屽姩鎬佺敓鎴愪笅涓�杞彁闂紙鏀寔澶氳疆涓婁笅鏂囩鐞嗭級銆�
+- 缃俊搴︽満鍒讹細鑻ュ満鏅瘑鍒疆淇″害 < 0.7锛岃Е鍙戦�氱敤闂�欑瓥鐣ワ紝閬垮厤骞叉壈鐢ㄦ埛銆�
+
+3.2 鏁欏绠℃帶绯荤粺
+- 鐢ㄦ埛妯″瀷锛氳褰曠敤鎴风殑CEFR绛夌骇銆佽瘝姹囧簱銆侀敊璇巼锛岄�氳繃鍙嶉寰幆鍔ㄦ�佽皟鑺傞毦搴︺��
+- 鏁版嵁瀛樺偍锛氱鍚� GDPR/PIPL 闅愮鍚堣锛屾晱鎰熻闊虫暟鎹畾鏈熻劚鏁忓鐞嗭紝涓嶇暀瀛樻湭缁忚鍙殑鍘熷闊抽銆�
+
+4. 闈炲姛鑳芥�ч渶姹傦紙SLA锛�
+- 鍙敤鎬э細绯荤粺鍙敤鎬� 99.9%銆�
+- 鎬ц兘鐡堕锛氬ぇ妯″瀷鎺ㄧ悊鎺ュ彛鏀寔鑷冲皯 1000 QPS 骞跺彂銆�
+- 瀹夊叏鍚堣锛氶�氳繃鍔犲瘑閫氶亾锛圱LS 1.3锛夐�氫俊锛涜澶囩粦瀹氬叿澶囧敮涓�鐨勫畨鍏ㄨ姱鐗囨牎楠岋紙Secure Element锛夈��
+- 鍙墿灞曟�э細鏀寔閫氳繃鎻掍欢鏈哄埗澧炲姞鏂拌绉嶆垨鍦烘櫙妯℃澘銆�
+
+5. 浜や簰鍗忚涓庢暟鎹祦锛圓PI瑙勮寖锛�
+- 璇锋眰鎺ュ彛锛�/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 涓嬪彂鎺у埗鎸囦护锛堝锛歋etVolume, Sleep, UpdateFirmware锛夈��
+
+6. 鍏稿瀷鍦烘櫙閫昏緫娴佺▼瀹氫箟
+- 鍦烘櫙璇嗗埆锛氭娴嬪埌鐗瑰畾鐜闊崇汗 -> 鍒ゅ畾涓衡�滅敤椁愬満鏅�� -> 鍙戦�佹寚浠よ嚦缁堢鎾斁寮曞鎻愮ず闊炽��
+- 鍥炲澶勭悊锛氭娴嬬敤鎴峰洖澶� -> ASR璇嗗埆 -> 鍒嗘瀽璇硶涓庢纭�� -> 杩斿洖鍙嶉锛堣禐璧忔垨绾犻敊锛�-> 杩涘叆涓嬩竴杞彁闂��
+
+
+
+绗簩閮ㄥ垎锛氬弬鐓т互涓婇渶姹傦紝鍒濇涓�濂梞qtt娑堟伅鐨則opic瑙勫垯濡備笅:
+涓�銆佹秷鎭痶opic瀹氫箟锛� snlp/v1/{device_id}/{direct}/{msg_type}
+琛ㄦ牸濡備笅锛�
+鏂瑰悜 Topic 璺緞 msg_type 璇存槑
+rpt (缁堢->浜�) snlp/v1/{device_id}/rpt/state 鐘舵�佷笂鎶� 鍖呭惈蹇冭烦銆佺數閲忋�佺綉缁溿�佸綋鍓嶅伐浣滄ā寮�
+ snlp/v1/{device_id}/rpt/event 浜嬩欢涓婃姤 鍞ら啋/浼戠湢瑙﹀彂銆丄SR璇嗗埆缁撴灉銆佸紓甯告姤璀�
+ snlp/v1/{device_id}/rpt/log 绯荤粺鏃ュ織 鐢ㄤ簬杩滅▼鎺掗殰鐨勭郴缁熻繍琛屾棩蹇�
+ snlp/v1/{device_id}/rpt/ack 鎸囦护鍝嶅簲 鍏抽敭锛� 瀵瑰簲 CMD 鐨勬墽琛岀粨鏋� (鎴愬姛/澶辫触/杩涘害)
+ snlp/v1/{device_id}/rpt/progress 杩囩▼杩涘害 閽堝鈥滃紓姝ユ枃浠朵笅杞解�濆拰鈥淥TA鍗囩骇鈥�
+cmd (浜�->缁堢) snlp/v1/{device_id}/cmd/ctl 瀹炴椂鎸囦护 瀹炴椂鎸囦护锛堥煶閲忋�佷紤鐪犮�丯TP锛�
+ snlp/v1/{device_id}/cmd/task 寮傛浠诲姟鎸囦护 寮傛浠诲姟鎸囦护锛圤TA銆佷笅杞借祫婧愬寘锛�
+
+v1 : 琛ㄧず鐗堟湰鍙�1锛� 鐢ㄤ簬鐗堟湰杩囨浮鏃剁殑鍏煎鎬у鐞�
+device_id锛� 鏄澶囩殑鍞竴id锛屽彧璇伙紝鏄笉鑳借淇敼鐨� 锛堜互鍚庡啀鑰冭檻澧炲姞娉ㄥ唽鏈哄埗,鎹㈠彇姝e紡鐨� Access Token锛�
+direct锛� rpt (缁堢->浜�) 锛� cmd (浜�->缁堢)
+msg_type: state, event,log,ack,progress, task, ctl
+
+
+浜屻�丳ayload 璁捐瑙勮寖锛屼负浜嗕繚鎸佷竴鑷存�э紝鎵�鏈� 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": { ... } // 鍏蜂綋鎿嶄綔鍙傛暟
+}
+鍏蜂綋鐨刾ayload绀轰緥锛�
+鎾斁闊抽 (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": { ... }
+}
+鍏蜂綋鐨刾ayload绀轰緥锛�
+蹇冭烦/鍋ュ悍 (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 鏁扮粍锛岀粓绔敹鍒板悗瀛樺叆鏈湴鏂囦欢锛屽疄鐜拌祫婧愮殑绠$悊涓庡姣斻��
+
+浜斻�� 鍗忚瀹炴柦寤鸿锛圕hecklist锛�
+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 鎺ュ彛锛屼互渚垮悗缁揩閫熷鍔犲鈥滆繙绋嬭皟璇曗�濄�佲�滄棩蹇楁媺鍙栤�濈瓑鍔熻兘锛屾棤闇�淇敼鍘熸湁瑙f瀽鏋舵瀯銆�
+
+绗笁閮ㄥ垎锛氳棰戞枃浠剁洰褰曠粨鏋勫拰缁勬垚
+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. 姣忎釜璇剧▼鐩綍涓嬫湁涓�浠絚ourse.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(鍒跺畾鏌愪竴闂ㄨ绋嬩换鍔℃竻鍗曪紝鐢ㄤ簬涓嬪彂鍒扮粓绔澶囪繘琛岃绋嬪垵濮嬪寲)锛屼娇瀹㈡埛绔拰鏈嶅姟绔彲浠ユ湁涓�濂楄嚜鍔ㄦ満鍒讹紝
+ 瀹㈡埛绔拰鏈嶅姟绔彲浠ヨ嚜鍔ㄥ寲鐨勯厤鍚堝畬鎴愬父瑙勭殑鍔熻兘鍜屾甯哥殑宸ヤ綔娴佺▼锛�
diff --git a/smartlearning/server/.env b/smartlearning/server/.env
new file mode 100644
index 0000000..6ee98b9
--- /dev/null
+++ b/smartlearning/server/.env
@@ -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
diff --git a/smartlearning/server/app/__init__.py b/smartlearning/server/app/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/server/app/__init__.py
diff --git a/smartlearning/server/app/__pycache__/__init__.cpython-310.pyc b/smartlearning/server/app/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..e08d8f2
--- /dev/null
+++ b/smartlearning/server/app/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/__pycache__/__init__.cpython-313.pyc b/smartlearning/server/app/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..d60a6f2
--- /dev/null
+++ b/smartlearning/server/app/__pycache__/__init__.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/__pycache__/config.cpython-310.pyc b/smartlearning/server/app/__pycache__/config.cpython-310.pyc
new file mode 100644
index 0000000..6d51140
--- /dev/null
+++ b/smartlearning/server/app/__pycache__/config.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/__pycache__/config.cpython-313.pyc b/smartlearning/server/app/__pycache__/config.cpython-313.pyc
new file mode 100644
index 0000000..670b9e3
--- /dev/null
+++ b/smartlearning/server/app/__pycache__/config.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/__pycache__/main.cpython-310.pyc b/smartlearning/server/app/__pycache__/main.cpython-310.pyc
new file mode 100644
index 0000000..58be6a0
--- /dev/null
+++ b/smartlearning/server/app/__pycache__/main.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/__pycache__/main.cpython-313.pyc b/smartlearning/server/app/__pycache__/main.cpython-313.pyc
new file mode 100644
index 0000000..89b0905
--- /dev/null
+++ b/smartlearning/server/app/__pycache__/main.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/config.py b/smartlearning/server/app/config.py
new file mode 100644
index 0000000..f27b2d5
--- /dev/null
+++ b/smartlearning/server/app/config.py
@@ -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()
diff --git a/smartlearning/server/app/main.py b/smartlearning/server/app/main.py
new file mode 100644
index 0000000..fad741a
--- /dev/null
+++ b/smartlearning/server/app/main.py
@@ -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,
+ }
diff --git a/smartlearning/server/app/models/__init__.py b/smartlearning/server/app/models/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/server/app/models/__init__.py
diff --git a/smartlearning/server/app/models/__pycache__/__init__.cpython-310.pyc b/smartlearning/server/app/models/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..d0a2e8c
--- /dev/null
+++ b/smartlearning/server/app/models/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/models/__pycache__/__init__.cpython-313.pyc b/smartlearning/server/app/models/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..c54c08b
--- /dev/null
+++ b/smartlearning/server/app/models/__pycache__/__init__.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/models/__pycache__/schemas.cpython-310.pyc b/smartlearning/server/app/models/__pycache__/schemas.cpython-310.pyc
new file mode 100644
index 0000000..48b0bf3
--- /dev/null
+++ b/smartlearning/server/app/models/__pycache__/schemas.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/models/__pycache__/schemas.cpython-313.pyc b/smartlearning/server/app/models/__pycache__/schemas.cpython-313.pyc
new file mode 100644
index 0000000..d6f587e
--- /dev/null
+++ b/smartlearning/server/app/models/__pycache__/schemas.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/models/schemas.py b/smartlearning/server/app/models/schemas.py
new file mode 100644
index 0000000..706a8a1
--- /dev/null
+++ b/smartlearning/server/app/models/schemas.py
@@ -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
diff --git a/smartlearning/server/app/mqtt/__init__.py b/smartlearning/server/app/mqtt/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__init__.py
diff --git a/smartlearning/server/app/mqtt/__pycache__/__init__.cpython-310.pyc b/smartlearning/server/app/mqtt/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..acd03f7
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/mqtt/__pycache__/__init__.cpython-313.pyc b/smartlearning/server/app/mqtt/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..f305467
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__pycache__/__init__.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/mqtt/__pycache__/client.cpython-310.pyc b/smartlearning/server/app/mqtt/__pycache__/client.cpython-310.pyc
new file mode 100644
index 0000000..3b2a890
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__pycache__/client.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/mqtt/__pycache__/client.cpython-313.pyc b/smartlearning/server/app/mqtt/__pycache__/client.cpython-313.pyc
new file mode 100644
index 0000000..08d93cd
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__pycache__/client.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-310.pyc b/smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-310.pyc
new file mode 100644
index 0000000..23d668b
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-313.pyc b/smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-313.pyc
new file mode 100644
index 0000000..37c6df6
--- /dev/null
+++ b/smartlearning/server/app/mqtt/__pycache__/init_dispatcher.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/mqtt/client.py b/smartlearning/server/app/mqtt/client.py
new file mode 100644
index 0000000..868445c
--- /dev/null
+++ b/smartlearning/server/app/mqtt/client.py
@@ -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
diff --git a/smartlearning/server/app/mqtt/init_dispatcher.py b/smartlearning/server/app/mqtt/init_dispatcher.py
new file mode 100644
index 0000000..590490d
--- /dev/null
+++ b/smartlearning/server/app/mqtt/init_dispatcher.py
@@ -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)
diff --git a/smartlearning/server/app/routes/__init__.py b/smartlearning/server/app/routes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/server/app/routes/__init__.py
diff --git a/smartlearning/server/app/routes/__pycache__/__init__.cpython-310.pyc b/smartlearning/server/app/routes/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..83ee8e9
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/__init__.cpython-313.pyc b/smartlearning/server/app/routes/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..01370e9
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/__init__.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/courses.cpython-310.pyc b/smartlearning/server/app/routes/__pycache__/courses.cpython-310.pyc
new file mode 100644
index 0000000..36b3b6d
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/courses.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/courses.cpython-313.pyc b/smartlearning/server/app/routes/__pycache__/courses.cpython-313.pyc
new file mode 100644
index 0000000..7663a4c
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/courses.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/devices.cpython-310.pyc b/smartlearning/server/app/routes/__pycache__/devices.cpython-310.pyc
new file mode 100644
index 0000000..76efe36
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/devices.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/devices.cpython-313.pyc b/smartlearning/server/app/routes/__pycache__/devices.cpython-313.pyc
new file mode 100644
index 0000000..f905d08
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/devices.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/dialogue.cpython-310.pyc b/smartlearning/server/app/routes/__pycache__/dialogue.cpython-310.pyc
new file mode 100644
index 0000000..d682bfe
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/dialogue.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/dialogue.cpython-313.pyc b/smartlearning/server/app/routes/__pycache__/dialogue.cpython-313.pyc
new file mode 100644
index 0000000..d1a2717
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/dialogue.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/scene.cpython-310.pyc b/smartlearning/server/app/routes/__pycache__/scene.cpython-310.pyc
new file mode 100644
index 0000000..e7e2450
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/scene.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/__pycache__/scene.cpython-313.pyc b/smartlearning/server/app/routes/__pycache__/scene.cpython-313.pyc
new file mode 100644
index 0000000..921abb8
--- /dev/null
+++ b/smartlearning/server/app/routes/__pycache__/scene.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/routes/courses.py b/smartlearning/server/app/routes/courses.py
new file mode 100644
index 0000000..7ba8e85
--- /dev/null
+++ b/smartlearning/server/app/routes/courses.py
@@ -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",
+ }
diff --git a/smartlearning/server/app/routes/devices.py b/smartlearning/server/app/routes/devices.py
new file mode 100644
index 0000000..600fc0e
--- /dev/null
+++ b/smartlearning/server/app/routes/devices.py
@@ -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
diff --git a/smartlearning/server/app/routes/dialogue.py b/smartlearning/server/app/routes/dialogue.py
new file mode 100644
index 0000000..220a226
--- /dev/null
+++ b/smartlearning/server/app/routes/dialogue.py
@@ -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())
diff --git a/smartlearning/server/app/routes/scene.py b/smartlearning/server/app/routes/scene.py
new file mode 100644
index 0000000..0a6be9e
--- /dev/null
+++ b/smartlearning/server/app/routes/scene.py
@@ -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"],
+ )
diff --git a/smartlearning/server/app/services/__init__.py b/smartlearning/server/app/services/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/server/app/services/__init__.py
diff --git a/smartlearning/server/app/services/__pycache__/__init__.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/__init__.cpython-310.pyc
new file mode 100644
index 0000000..f439fa0
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/__init__.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/__init__.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/__init__.cpython-313.pyc
new file mode 100644
index 0000000..04d8f9e
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/__init__.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/auto_init.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/auto_init.cpython-310.pyc
new file mode 100644
index 0000000..966a31b
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/auto_init.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/auto_init.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/auto_init.cpython-313.pyc
new file mode 100644
index 0000000..283ddbd
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/auto_init.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/course_service.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/course_service.cpython-310.pyc
new file mode 100644
index 0000000..ef3830d
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/course_service.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/course_service.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/course_service.cpython-313.pyc
new file mode 100644
index 0000000..db29e6f
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/course_service.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/device_store.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/device_store.cpython-310.pyc
new file mode 100644
index 0000000..2e1fc23
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/device_store.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/device_store.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/device_store.cpython-313.pyc
new file mode 100644
index 0000000..1eb1963
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/device_store.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/dialogue_service.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/dialogue_service.cpython-310.pyc
new file mode 100644
index 0000000..e06194c
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/dialogue_service.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/dialogue_service.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/dialogue_service.cpython-313.pyc
new file mode 100644
index 0000000..b0a761d
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/dialogue_service.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/init_registry.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/init_registry.cpython-310.pyc
new file mode 100644
index 0000000..842f11d
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/init_registry.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/init_registry.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/init_registry.cpython-313.pyc
new file mode 100644
index 0000000..3e5320b
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/init_registry.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/scene_engine.cpython-310.pyc b/smartlearning/server/app/services/__pycache__/scene_engine.cpython-310.pyc
new file mode 100644
index 0000000..17dc10d
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/scene_engine.cpython-310.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/__pycache__/scene_engine.cpython-313.pyc b/smartlearning/server/app/services/__pycache__/scene_engine.cpython-313.pyc
new file mode 100644
index 0000000..33d1672
--- /dev/null
+++ b/smartlearning/server/app/services/__pycache__/scene_engine.cpython-313.pyc
Binary files differ
diff --git a/smartlearning/server/app/services/auto_init.py b/smartlearning/server/app/services/auto_init.py
new file mode 100644
index 0000000..2f463df
--- /dev/null
+++ b/smartlearning/server/app/services/auto_init.py
@@ -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
diff --git a/smartlearning/server/app/services/course_service.py b/smartlearning/server/app/services/course_service.py
new file mode 100644
index 0000000..fd2aecc
--- /dev/null
+++ b/smartlearning/server/app/services/course_service.py
@@ -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", [])]
diff --git a/smartlearning/server/app/services/device_store.py b/smartlearning/server/app/services/device_store.py
new file mode 100644
index 0000000..0abc5e8
--- /dev/null
+++ b/smartlearning/server/app/services/device_store.py
@@ -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, []))
diff --git a/smartlearning/server/app/services/dialogue_service.py b/smartlearning/server/app/services/dialogue_service.py
new file mode 100644
index 0000000..dbe113a
--- /dev/null
+++ b/smartlearning/server/app/services/dialogue_service.py
@@ -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?",
+ }
diff --git a/smartlearning/server/app/services/init_registry.py b/smartlearning/server/app/services/init_registry.py
new file mode 100644
index 0000000..83532a2
--- /dev/null
+++ b/smartlearning/server/app/services/init_registry.py
@@ -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
diff --git a/smartlearning/server/app/services/scene_engine.py b/smartlearning/server/app/services/scene_engine.py
new file mode 100644
index 0000000..0ee4475
--- /dev/null
+++ b/smartlearning/server/app/services/scene_engine.py
@@ -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,
+ }
diff --git a/smartlearning/server/requirements.txt b/smartlearning/server/requirements.txt
new file mode 100644
index 0000000..c51aa55
--- /dev/null
+++ b/smartlearning/server/requirements.txt
@@ -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
diff --git a/smartlearning/server/run.py b/smartlearning/server/run.py
new file mode 100644
index 0000000..fe7ece6
--- /dev/null
+++ b/smartlearning/server/run.py
@@ -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()
diff --git a/smartlearning/server/runtime/.gitignore b/smartlearning/server/runtime/.gitignore
new file mode 100644
index 0000000..933daa6
--- /dev/null
+++ b/smartlearning/server/runtime/.gitignore
@@ -0,0 +1 @@
+*.json
diff --git a/smartlearning/server/runtime/.gitkeep b/smartlearning/server/runtime/.gitkeep
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/smartlearning/server/runtime/.gitkeep
diff --git a/xiaozhi-esp32-2.2.6/.clang-format b/xiaozhi-esp32-2.2.6/.clang-format
new file mode 100644
index 0000000..9b71362
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/.clang-format
@@ -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
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/.gitignore b/xiaozhi-esp32-2.2.6/.gitignore
new file mode 100644
index 0000000..5326d47
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/.gitignore
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/CMakeLists.txt b/xiaozhi-esp32-2.2.6/CMakeLists.txt
new file mode 100755
index 0000000..371b15d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/CMakeLists.txt
@@ -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)
diff --git a/xiaozhi-esp32-2.2.6/LICENSE b/xiaozhi-esp32-2.2.6/LICENSE
new file mode 100644
index 0000000..e90b554
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/LICENSE
@@ -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.
diff --git a/xiaozhi-esp32-2.2.6/README.md b/xiaozhi-esp32-2.2.6/README.md
new file mode 100644
index 0000000..de296d1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/README.md
@@ -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銆恇ilibili銆慮(https://www.bilibili.com/video/BV1bpjgzKEhd/)
+
+馃憠 [Handcraft your AI girlfriend, beginner's guide銆恇ilibili銆慮(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:
+
+
+
+### 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>
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/README_ja.md b/xiaozhi-esp32-2.2.6/README_ja.md
new file mode 100644
index 0000000..d052162
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/README_ja.md
@@ -0,0 +1,169 @@
+# MCP 銉欍兗銈广伄銉併儯銉冦儓銉溿儍銉�
+
+锛堟棩鏈獮 | [涓枃](README_zh.md) | [English](README.md)锛�
+
+## 銇仒銈併伀
+
+馃憠 [浜洪枔锛欰I銇偒銉°儵銈掕鐫� vs AI锛氥仢銇牬銇ч<銇勪富銇�3鏃ラ枔楂倰娲椼仯銇︺亜銇亜銇撱仺銈掔櫤瑕嬨�恇ilibili銆慮(https://www.bilibili.com/video/BV1bpjgzKEhd/)
+
+馃憠 [鎵嬩綔銈娿仹AI銈兗銉儠銉兂銉夈倰浣溿倠銆佸垵蹇冭�呭叆闁�銉併儱銉笺儓銉偄銉�恇ilibili銆慮(https://www.bilibili.com/video/BV1XnmFYLEJN/)
+
+銈枫儯銈偢銉糀I銉併儯銉冦儓銉溿儍銉堛伅闊冲0銈ゃ兂銈裤儵銈偡銉с兂銇叆鍙c仺銇椼仸銆丵wen / DeepSeek銇仼銇ぇ瑕忔ā銉€儑銉伄AI鑳藉姏銈掓椿鐢ㄣ仐銆丮CP銉椼儹銉堛偝銉倰閫氥仒銇︺優銉儊銈ㄣ兂銉夊埗寰°倰瀹熺従銇椼伨銇欍��
+
+<img src="docs/mcp-based-graph.jpg" alt="MCP銇с亗銈夈倖銈嬨倐銇倰鍒跺尽" width="320">
+
+## 銉愩兗銈搞儳銉炽儙銉笺儓
+
+鐝惧湪銇畍2銉愩兗銈搞儳銉炽伅v1銉戙兗銉嗐偅銈枫儳銉炽儐銉笺儢銉仺浜掓彌鎬с亴銇亜銇熴倎銆乿1銇嬨倝v2銇窸TA銇с偄銉冦儣銈般儸銉笺儔銇欍倠銇撱仺銇仹銇嶃伨銇涖倱銆傘儜銉笺儐銈c偡銉с兂銉嗐兗銉栥儷銇┏绱般伀銇ゃ亜銇︺伅銆乕partitions/v2/README.md](partitions/v2/README.md)銈掋仈鍙傜収銇忋仩銇曘亜銆�
+
+v1銈掑疅琛屻仐銇︺亜銈嬨仚銇广仸銇儚銉笺儔銈︺偋銈€伅銆併儠銈°兗銉犮偊銈с偄銈掓墜鍕曘仹鏇搞亶杈笺個銇撱仺銇2銇偄銉冦儣銈般儸銉笺儔銇с亶銇俱仚銆�
+
+v1銇畨瀹氱増銇�1.9.2銇с仚銆俙git checkout v1`銇1銇垏銈婃浛銇堛倠銇撱仺銇屻仹銇嶃伨銇欍�倂1銉栥儵銉炽儊銇�2026骞�2鏈堛伨銇х稒缍氱殑銇儭銉炽儐銉娿兂銈广仌銈屻伨銇欍��
+
+### 瀹熻娓堛伩姗熻兘
+
+- Wi-Fi / ML307 Cat.1 4G
+- 銈儠銉┿偆銉抽煶澹般偊銈с偆銈偄銉冦儣 [ESP-SR](https://github.com/espressif/esp-sr)
+- 2绋銇�氫俊銉椼儹銉堛偝銉伀瀵惧繙锛圼Websocket](docs/websocket.md) 銇俱仧銇� MQTT+UDP锛�
+- OPUS銈兗銉囥偅銈偝銉笺儑銉冦偗銈掓帯鐢�
+- 銈广儓銉兗銉熴兂銈癆SR + LLM + TTS銈€兗銈儐銈儊銉c伀鍩恒仴銇忛煶澹般偆銉炽偪銉┿偗銈枫儳銉�
+- 瑭辫�呰獚璀樸�佺従鍦ㄨ┍銇椼仸銇勩倠浜恒倰璀樺垾 [3D Speaker](https://github.com/modelscope/3D-Speaker)
+- OLED / LCD銉囥偅銈广儣銉偆銆佽〃鎯呰〃绀哄蹇�
+- 銉愩儍銉嗐儶銉艰〃绀恒仺闆绘簮绠$悊
+- 澶氳█瑾炲蹇滐紙涓浗瑾炪�佽嫳瑾炪�佹棩鏈獮锛�
+- ESP32-C3銆丒SP32-S3銆丒SP32-P4銉併儍銉椼儣銉┿儍銉堛儠銈┿兗銉犲蹇�
+- 銉囥儛銈ゃ偣鍋碝CP銇倛銈嬨儑銉愩偆銈瑰埗寰★紙闊抽噺銉绘槑銈嬨仌瑾挎暣銆併偄銈偡銉с兂鍒跺尽銇仼锛�
+- 銈儵銈︺儔鍋碝CP銇уぇ瑕忔ā銉€儑銉兘鍔涖倰鎷″嫉锛堛偣銉炪兗銉堛儧銉笺儬鍒跺尽銆丳C銉囥偣銈儓銉冦儣鎿嶄綔銆佺煡璀樻绱€�併儭銉笺儷閫佸彈淇°仾銇╋級
+- 銈偣銈裤優銈ゃ偤鍙兘銇偊銈с偆銈儻銉笺儔銆併儠銈┿兂銉堛�佺档鏂囧瓧銆併儊銉c儍銉堣儗鏅�併偑銉炽儵銈ゃ兂Web绶ㄩ泦銇蹇� ([銈偣銈裤儬銈€偦銉冦儓銈搞偋銉嶃儸銉笺偪銉糫(https://github.com/78/xiaozhi-assets-generator))
+
+## 銉忋兗銉夈偊銈с偄
+
+### 銉栥儸銉冦儔銉溿兗銉夋墜浣溿倞瀹熻返
+
+Feishu銉夈偔銉ャ儭銉炽儓銉併儱銉笺儓銉偄銉倰銇旇Η銇忋仩銇曘亜锛�
+
+馃憠 [銆屻偡銉c偑銈搞兗AI銉併儯銉冦儓銉溿儍銉堢櫨绉戜簨鍏搞�峕(https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink)
+
+銉栥儸銉冦儔銉溿兗銉夈伄銉囥儮锛�
+
+ 銈点兗銉愩兗銇帴缍氥仐銇俱仚銆傚�嬩汉銉︺兗銈躲兗銇偄銈偊銉炽儓鐧婚尣銇wen銉偄銉偪銈ゃ儬銉€儑銉倰鐒℃枡銇у埄鐢ㄣ仹銇嶃伨銇欍��
+
+馃憠 [鍒濆績鑰呭悜銇戙儠銈°兗銉犮偊銈с偄鏇搞亶杈笺伩銈偆銉塢(https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS)
+
+### 闁嬬櫤鐠板
+
+- Cursor 銇俱仧銇� VSCode
+- ESP-IDF銉椼儵銈般偆銉炽倰銈ゃ兂銈广儓銉笺儷銇椼�丼DK銉愩兗銈搞儳銉�5.4浠ヤ笂銈掗伕鎶�
+- Linux銇疻indows銈堛倞銈傚劒銈屻仸銇娿倞銆併偝銉炽儜銈ゃ儷銇岄�熴亸銆併儔銉┿偆銉愩伄鍟忛銈傚皯銇亜
+- 鏈儣銉偢銈с偗銉堛伅Google C++銈炽兗銉夈偣銈裤偆銉倰鎺$敤銆併偝銉笺儔鎻愬嚭鏅傘伅婧栨嫚銈掔⒑瑾嶃仐銇︺亸銇犮仌銇�
+
+### 闁嬬櫤鑰呫儔銈儱銉°兂銉�
+
+- [銈偣銈裤儬闁嬬櫤銉溿兗銉夈偓銈ゃ儔](docs/custom-board.md) - 銈枫儯銈偢銉糀I鐢ㄣ伄銈偣銈裤儬闁嬬櫤銉溿兗銉変綔鎴愭柟娉�
+- [MCP銉椼儹銉堛偝銉獻oT鍒跺尽浣跨敤娉昡(docs/mcp-usage.md) - MCP銉椼儹銉堛偝銉仹IoT銉囥儛銈ゃ偣銈掑埗寰°仚銈嬫柟娉�
+- [MCP銉椼儹銉堛偝銉偆銉炽偪銉┿偗銈枫儳銉炽儠銉兗](docs/mcp-protocol.md) - 銉囥儛銈ゃ偣鍋碝CP銉椼儹銉堛偝銉伄瀹熻鏂规硶
+- [MQTT + UDP 銉忋偆銉栥儶銉冦儔閫氫俊銉椼儹銉堛偝銉儔銈儱銉°兂銉圿(docs/mqtt-udp.md)
+- [瑭崇窗銇猈ebSocket閫氫俊銉椼儹銉堛偝銉儔銈儱銉°兂銉圿(docs/websocket.md)
+
+## 澶ц妯°儮銉囥儷瑷畾
+
+銇欍仹銇偡銉c偑銈搞兗AI銉併儯銉冦儓銉溿儍銉堛儑銉愩偆銈广倰銇婃寔銇°仹銆佸叕寮忋偟銉笺儛銉笺伀鎺ョ稓娓堛伩銇牬鍚堛伅銆乕xiaozhi.me](https://xiaozhi.me) 銈炽兂銈姐兗銉仹瑷畾銇с亶銇俱仚銆�
+
+馃憠 [銉愩儍銈偍銉炽儔鎿嶄綔銉撱儑銈儊銉ャ兗銉堛儶銈€儷锛堟棫銈ゃ兂銈裤兗銉曘偋銉笺偣锛塢(https://www.bilibili.com/video/BV1jUCUY2EKM/)
+
+## 闁㈤�c偑銉笺儣銉炽偨銉笺偣銉椼儹銈搞偋銈儓
+
+鍊嬩汉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) 鐧惧晱绉戞妧鎻愪緵銇甃inux銈儵銈ゃ偄銉炽儓
+- [78/xiaozhi-sf32](https://github.com/78/xiaozhi-sf32) 鎬濇緢绉戞妧銇瓸luetooth銉併儍銉椼儠銈°兗銉犮偊銈с偄
+- [QuecPython/solution-xiaozhiAI](https://github.com/QuecPython/solution-xiaozhiAI) 绉婚仩鎻愪緵銇甉uecPython銉曘偂銉笺儬銈︺偋銈�
+
+## 銉椼儹銈搞偋銈儓銇仱銇勩仸
+
+銇撱倢銇偍銉撳厔銇曘倱銇屻偑銉笺儣銉炽偨銉笺偣銇у叕闁嬨仐銇︺亜銈婨SP32銉椼儹銈搞偋銈儓銇с�丮IT銉┿偆銈汇兂銈广伄銈傘仺銆佽銇с倐鐒℃枡銇с�佸晢鐢ㄥ埄鐢ㄣ倐鍙兘銇с仚銆�
+
+銇撱伄銉椼儹銈搞偋銈儓銈掗�氥仒銇︺�丄I銉忋兗銉夈偊銈с偄闁嬬櫤銈掔悊瑙c仐銆佹�ラ�熴伀閫插寲銇欍倠澶ц妯¤█瑾炪儮銉囥儷銈掑疅闅涖伄銉忋兗銉夈偊銈с偄銉囥儛銈ゃ偣銇繙鐢ㄣ仹銇嶃倠銈堛亞銇仾銈嬨亾銇ㄣ倰鐩寚銇椼仸銇勩伨銇欍��
+
+銇旀剰瑕嬨倓銇旀彁妗堛亴銇傘倢銇般�併亜銇ゃ仹銈侷ssue銈掓彁鍑恒仚銈嬨亱銆乕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>
diff --git a/xiaozhi-esp32-2.2.6/README_zh.md b/xiaozhi-esp32-2.2.6/README_zh.md
new file mode 100644
index 0000000..de2c463
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/README_zh.md
@@ -0,0 +1,169 @@
+# An MCP-based Chatbot
+
+锛堜腑鏂� | [English](README.md) | [鏃ユ湰瑾瀅(README_ja.md)锛�
+
+## 浠嬬粛
+
+馃憠 [浜虹被锛氱粰 AI 瑁呮憚鍍忓ご vs AI锛氬綋鍦哄彂鐜颁富浜轰笁澶╂病娲楀ご銆恇ilibili銆慮(https://www.bilibili.com/video/BV1bpjgzKEhd/)
+
+馃憠 [鎵嬪伐鎵撻�犱綘鐨� AI 濂冲弸锛屾柊鎵嬪叆闂ㄦ暀绋嬨�恇ilibili銆慮(https://www.bilibili.com/video/BV1XnmFYLEJN/)
+
+灏忔櫤 AI 鑱婂ぉ鏈哄櫒浜轰綔涓轰竴涓闊充氦浜掑叆鍙o紝鍒╃敤 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銆丒SP32-S3銆丒SP32-P4 鑺墖骞冲彴
+- 閫氳繃璁惧绔� MCP 瀹炵幇璁惧鎺у埗锛堥煶閲忋�佺伅鍏夈�佺數鏈恒�丟PIO 绛夛級
+- 閫氳繃浜戠 MCP 鎵╁睍澶фā鍨嬭兘鍔涳紙鏅鸿兘瀹跺眳鎺у埗銆丳C妗岄潰鎿嶄綔銆佺煡璇嗘悳绱€�侀偖浠舵敹鍙戠瓑锛�
+- 鑷畾涔夊敜閱掕瘝銆佸瓧浣撱�佽〃鎯呬笌鑱婂ぉ鑳屾櫙锛屾敮鎸佺綉椤电鍦ㄧ嚎淇敼 ([鑷畾涔堿ssets鐢熸垚鍣╙(https://github.com/78/xiaozhi-assets-generator))
+
+## 纭欢
+
+### 闈㈠寘鏉挎墜宸ュ埗浣滃疄璺�
+
+璇﹁椋炰功鏂囨。鏁欑▼锛�
+
+馃憠 [銆婂皬鏅� AI 鑱婂ぉ鏈哄櫒浜虹櫨绉戝叏涔︺�媇(https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink)
+
+闈㈠寘鏉挎晥鏋滃浘濡備笅锛�
+
+
+
+### 鏀寔 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">鐠�鐠稟I 鍚婂潬</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++ 浠g爜椋庢牸锛屾彁浜や唬鐮佹椂璇风‘淇濈鍚堣鑼�
+
+### 寮�鍙戣�呮枃妗�
+
+- [鑷畾涔夊紑鍙戞澘鎸囧崡](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>
diff --git a/xiaozhi-esp32-2.2.6/docs/blufi.md b/xiaozhi-esp32-2.2.6/docs/blufi.md
new file mode 100644
index 0000000..024a830
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/blufi.md
@@ -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"`.
diff --git a/xiaozhi-esp32-2.2.6/docs/blufi_zh.md b/xiaozhi-esp32-2.2.6/docs/blufi_zh.md
new file mode 100644
index 0000000..992872d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/blufi_zh.md
@@ -0,0 +1,37 @@
+# BluFi 閰嶇綉锛堥泦鎴� esp-wifi-connect锛�
+
+鏈枃妗h鏄庡浣曞湪灏忔櫤鍥轰欢涓惎鐢ㄥ拰浣跨敤 BluFi锛圔LE Wi鈥慒i 閰嶇綉锛夛紝骞剁粨鍚堥」鐩唴缃殑 `esp-wifi-connect` 缁勪欢瀹屾垚 Wi鈥慒i 杩炴帴涓庡瓨鍌ㄣ�傚畼鏂�
+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銆丆ONFIG_BT_NIMBLE_ENABLED杩欎袱涓畯搴斾簩閫変竴锛屼笉鑳藉悓鏃跺惎鐢ㄣ��
+## 宸ヤ綔娴佺▼
+
+1) 鎵嬫満绔�氳繃 BluFi锛堝瀹樻柟 EspBlufi App 鎴栬嚜鐮斿鎴风锛夎繛鎺ヨ澶囷紝鍙戦�� Wi鈥慒i SSID/瀵嗙爜锛屾墜鏈虹鍙互閫氳繃blufi鍗忚鑾峰彇璁惧绔壂鎻忓埌鐨刉iFi鍒楄〃銆�
+2) 璁惧渚у湪 `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP` 涓皢鍑嵁鍐欏叆 `SsidManager`锛堝瓨鍌ㄥ埌 NVS锛屽睘浜� `esp-wifi-connect` 缁勪欢锛夈��
+3) 闅忓悗鍚姩 `WifiStation` 鎵弿骞惰繛鎺ワ紱鐘舵�侀�氳繃 BluFi 杩斿洖銆�
+4) 閰嶇綉鎴愬姛鍚庤澶囦細鑷姩杩炴帴鏂� Wi鈥慒i锛涘け璐ュ垯杩斿洖澶辫触鐘舵�併��
+
+## 浣跨敤姝ラ
+
+1. 閰嶇疆锛氬湪 menuconfig 寮�鍚� `Esp Blufi`銆傜紪璇戝苟鐑у綍鍥轰欢銆�
+2. 瑙﹀彂閰嶇綉锛氳澶囬娆″惎鍔ㄤ笖娌℃湁宸蹭繚瀛樼殑 Wi鈥慒i 鏃朵細鑷姩杩涘叆閰嶇綉銆�
+3. 鎵嬫満绔搷浣滐細鎵撳紑 EspBlufi App锛堟垨鍏朵粬 BluFi 瀹㈡埛绔級锛屾悳绱㈠苟杩炴帴璁惧锛屽彲浠ラ�夋嫨鏄惁鍔犲瘑锛屾寜鎻愮ず杈撳叆 Wi鈥慒i SSID/瀵嗙爜骞跺彂閫併��
+4. 瑙傚療缁撴灉锛�
+ - 鎴愬姛锛欱luFi 鎶ュ憡杩炴帴鎴愬姛锛岃澶囪嚜鍔ㄨ繛鎺� Wi鈥慒i銆�
+ - 澶辫触锛欱luFi 杩斿洖澶辫触鐘舵�侊紝鍙噸鏂板彂閫佹垨妫�鏌ヨ矾鐢卞櫒銆�
+
+## 娉ㄦ剰浜嬮」
+
+- BluFi 閰嶇綉涓嶆敮鎸佷笌鐑偣閰嶇綉鍚屾椂寮�鍚�傚鏋滅儹鐐归厤缃戝凡缁忓惎鍔紝鍒欓粯璁や娇鐢ㄧ儹鐐归厤缃戙�傝鍦� menuconfig 涓彧淇濈暀涓�绉嶉厤缃戞柟寮忋��
+- 鑻ュ娆℃祴璇曪紝寤鸿娓呴櫎鎴栬鐩栧瓨鍌ㄧ殑 SSID锛坄wifi` 鍛藉悕绌洪棿锛夛紝閬垮厤鏃ч厤缃共鎵般��
+- 濡傛灉浣跨敤鑷畾涔� BluFi 瀹㈡埛绔紝闇�閬靛惊瀹樻柟鍗忚甯ф牸寮忥紝鍙傝�冧笂鏂囧畼鏂规枃妗i摼鎺ャ��
+- 瀹樻柟鏂囨。涓凡鎻愪緵EspBlufi APP涓嬭浇鍦板潃
+- 鐢变簬IDF5.5.2鐨刡lufi鎺ュ彛鍙戠敓鍙樺寲,5.5.2鐗堟湰缂栬瘧鍚庤摑鐗欏悕绉颁负"Xiaozhi-Blufi",5.5.1鐗堟湰涓摑鐗欏悕绉颁负"BLUFI_DEVICE"
diff --git a/xiaozhi-esp32-2.2.6/docs/code_style.md b/xiaozhi-esp32-2.2.6/docs/code_style.md
new file mode 100644
index 0000000..d34a060
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/code_style.md
@@ -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.
diff --git a/xiaozhi-esp32-2.2.6/docs/code_style_zh.md b/xiaozhi-esp32-2.2.6/docs/code_style_zh.md
new file mode 100644
index 0000000..e096762
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/code_style_zh.md
@@ -0,0 +1,91 @@
+# 浠g爜椋庢牸鎸囧崡
+
+## 浠g爜鏍煎紡鍖栧伐鍏�
+
+鏈」鐩娇鐢� clang-format 宸ュ叿鏉ョ粺涓�浠g爜椋庢牸銆傛垜浠凡缁忓湪椤圭洰鏍圭洰褰曚笅鎻愪緵浜� `.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. 鎻愪氦浠g爜鍓嶈纭繚浠g爜宸茬粡杩囨牸寮忓寲
+2. 涓嶈鎵嬪姩璋冩暣宸叉牸寮忓寲鐨勪唬鐮佸榻�
+3. 濡傛灉鏌愭浠g爜涓嶅笇鏈涜鏍煎紡鍖栵紝鍙互浣跨敤浠ヤ笅娉ㄩ噴鍖呭洿锛�
+ ```cpp
+ // clang-format off
+ // 浣犵殑浠g爜
+ // clang-format on
+ ```
+
+### 甯歌闂
+
+1. **鏍煎紡鍖栧け璐�**锛�
+ - 妫�鏌� clang-format 鐗堟湰鏄惁杩囦綆
+ - 纭鏂囦欢缂栫爜涓� UTF-8
+ - 楠岃瘉 .clang-format 鏂囦欢璇硶鏄惁姝g‘
+
+2. **涓庢湡鏈涙牸寮忎笉绗�**锛�
+ - 妫�鏌ユ槸鍚︿娇鐢ㄤ簡椤圭洰鏍圭洰褰曚笅鐨� .clang-format 閰嶇疆
+ - 纭娌℃湁鍏朵粬浣嶇疆鐨� .clang-format 鏂囦欢琚紭鍏堜娇鐢�
+
+濡傛湁浠讳綍闂鎴栧缓璁紝娆㈣繋鎻愬嚭 issue 鎴� pull request銆�
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/docs/custom-board.md b/xiaozhi-esp32-2.2.6/docs/custom-board.md
new file mode 100644
index 0000000..4206869
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/custom-board.md
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/docs/custom-board_zh.md b/xiaozhi-esp32-2.2.6/docs/custom-board_zh.md
new file mode 100644
index 0000000..9e7bd8f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/custom-board_zh.md
@@ -0,0 +1,453 @@
+# 鑷畾涔夊紑鍙戞澘鎸囧崡
+
+鏈寚鍗椾粙缁嶅浣曚负灏忔櫤AI璇煶鑱婂ぉ鏈哄櫒浜洪」鐩畾鍒朵竴涓柊鐨勫紑鍙戞澘鍒濆鍖栫▼搴忋�傚皬鏅篈I鏀寔70澶氱ESP32绯诲垪寮�鍙戞澘锛屾瘡涓紑鍙戞澘鐨勫垵濮嬪寲浠g爜閮芥斁鍦ㄥ搴旂殑鐩綍涓嬨��
+
+## 閲嶈鎻愮ず
+
+> **璀﹀憡**: 瀵逛簬鑷畾涔夊紑鍙戞澘锛屽綋IO閰嶇疆涓庡師鏈夊紑鍙戞澘涓嶅悓鏃讹紝鍒囧嬁鐩存帴瑕嗙洊鍘熸湁寮�鍙戞澘鐨勯厤缃紪璇戝浐浠躲�傚繀椤诲垱寤烘柊鐨勫紑鍙戞澘绫诲瀷锛屾垨鑰呴�氳繃config.json鏂囦欢涓殑builds閰嶇疆涓嶅悓鐨刵ame鍜宻dkconfig瀹忓畾涔夋潵鍖哄垎銆備娇鐢� `python scripts/release.py [寮�鍙戞澘鐩綍鍚嶅瓧]` 鏉ョ紪璇戞墦鍖呭浐浠躲��
+>
+> 濡傛灉鐩存帴瑕嗙洊鍘熸湁閰嶇疆锛屽皢鏉TA鍗囩骇鏃讹紝鎮ㄧ殑鑷畾涔夊浐浠跺彲鑳戒細琚師鏈夊紑鍙戞澘鐨勬爣鍑嗗浐浠惰鐩栵紝瀵艰嚧鎮ㄧ殑璁惧鏃犳硶姝e父宸ヤ綔銆傛瘡涓紑鍙戞澘鏈夊敮涓�鐨勬爣璇嗗拰瀵瑰簲鐨勫浐浠跺崌绾ч�氶亾锛屼繚鎸佸紑鍙戞澘鏍囪瘑鐨勫敮涓�鎬ч潪甯搁噸瑕併��
+
+## 鐩綍缁撴瀯
+
+姣忎釜寮�鍙戞澘鐨勭洰褰曠粨鏋勯�氬父鍖呭惈浠ヤ笅鏂囦欢锛�
+
+- `xxx_board.cc` - 涓昏鐨勬澘绾у垵濮嬪寲浠g爜锛屽疄鐜颁簡鏉垮瓙鐩稿叧鐨勫垵濮嬪寲鍜屽姛鑳�
+- `config.h` - 鏉跨骇閰嶇疆鏂囦欢锛屽畾涔変簡纭欢绠¤剼鏄犲皠鍜屽叾浠栭厤缃」
+- `config.json` - 缂栬瘧閰嶇疆锛屾寚瀹氱洰鏍囪姱鐗囧拰鐗规畩鐨勭紪璇戦�夐」
+- `README.md` - 寮�鍙戞澘鐩稿叧鐨勮鏄庢枃妗�
+
+## 瀹氬埗寮�鍙戞澘姝ラ
+
+### 1. 鍒涘缓鏂扮殑寮�鍙戞澘鐩綍
+
+棣栧厛鍦╜boards/`鐩綍涓嬪垱寤轰竴涓柊鐨勭洰褰曪紝鍛藉悕鏂瑰紡搴斾娇鐢� `[鍝佺墝鍚峕-[寮�鍙戞澘绫诲瀷]` 鐨勫舰寮忥紝渚嬪 `m5stack-tab5`锛�
+
+```bash
+mkdir main/boards/my-custom-board
+```
+
+### 2. 鍒涘缓閰嶇疆鏂囦欢
+
+#### config.h
+
+鍦╜config.h`涓畾涔夋墍鏈夌殑纭欢閰嶇疆锛屽寘鎷�:
+
+- 闊抽閲囨牱鐜囧拰I2S寮曡剼閰嶇疆
+- 闊抽缂栬В鐮佽姱鐗囧湴鍧�鍜孖2C寮曡剼閰嶇疆
+- 鎸夐挳鍜孡ED寮曡剼閰嶇疆
+- 鏄剧ず灞忓弬鏁板拰寮曡剼閰嶇疆
+
+鍙傝�冪ず渚嬶紙鏉ヨ嚜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. **鍒濆鍖栧嚱鏁�**锛氬寘鎷琁2C銆佹樉绀哄睆銆佹寜閽�両oT绛夌粍浠剁殑鍒濆鍖�
+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
+
+鍦≧EADME.md涓鏄庡紑鍙戞澘鐨勭壒鎬с�佺‖浠惰姹傘�佺紪璇戝拰鐑у綍姝ラ锛�
+
+
+## 甯歌寮�鍙戞澘缁勪欢
+
+### 1. 鏄剧ず灞�
+
+椤圭洰鏀寔澶氱鏄剧ず灞忛┍鍔紝鍖呮嫭:
+- ST7789 (SPI)
+- ILI9341 (SPI)
+- SH8601 (QSPI)
+- 绛�...
+
+### 2. 闊抽缂栬В鐮佸櫒
+
+鏀寔鐨勭紪瑙g爜鍣ㄥ寘鎷�:
+- ES8311 (甯哥敤)
+- ES7210 (楹﹀厠椋庨樀鍒�)
+- AW88298 (鍔熸斁)
+- 绛�...
+
+### 3. 鐢垫簮绠$悊
+
+涓�浜涘紑鍙戞澘浣跨敤鐢垫簮绠$悊鑺墖:
+- AXP2101
+- 鍏朵粬鍙敤鐨凱MIC
+
+### 4. MCP璁惧鎺у埗
+
+鍙互娣诲姞鍚勭MCP宸ュ叿锛岃AI鑳藉浣跨敤:
+- Speaker (鎵0鍣ㄦ帶鍒�)
+- Screen (灞忓箷浜害璋冭妭)
+- Battery (鐢垫睜鐢甸噺璇诲彇)
+- Light (鐏厜鎺у埗)
+- 绛�...
+
+## 寮�鍙戞澘绫荤户鎵垮叧绯�
+
+- `Board` - 鍩虹鏉跨骇绫�
+ - `WifiBoard` - Wi-Fi杩炴帴鐨勫紑鍙戞澘
+ - `Ml307Board` - 浣跨敤4G妯″潡鐨勫紑鍙戞澘
+ - `DualNetworkBoard` - 鏀寔Wi-Fi涓�4G缃戠粶鍒囨崲鐨勫紑鍙戞澘
+
+## 寮�鍙戞妧宸�
+
+1. **鍙傝�冪浉浼肩殑寮�鍙戞澘**锛氬鏋滄偍鐨勬柊寮�鍙戞澘涓庣幇鏈夊紑鍙戞澘鏈夌浉浼间箣澶勶紝鍙互鍙傝�冪幇鏈夊疄鐜�
+2. **鍒嗘璋冭瘯**锛氬厛瀹炵幇鍩虹鍔熻兘锛堝鏄剧ず锛夛紝鍐嶆坊鍔犳洿澶嶆潅鐨勫姛鑳斤紙濡傞煶棰戯級
+3. **绠¤剼鏄犲皠**锛氱‘淇濆湪config.h涓纭厤缃墍鏈夌鑴氭槧灏�
+4. **妫�鏌ョ‖浠跺吋瀹规��**锛氱‘璁ゆ墍鏈夎姱鐗囧拰椹卞姩绋嬪簭鐨勫吋瀹规��
+
+## 鍙兘閬囧埌鐨勯棶棰�
+
+1. **鏄剧ず灞忎笉姝e父**锛氭鏌PI閰嶇疆銆侀暅鍍忚缃拰棰滆壊鍙嶈浆璁剧疆
+2. **闊抽鏃犺緭鍑�**锛氭鏌2S閰嶇疆銆丳A浣胯兘寮曡剼鍜岀紪瑙g爜鍣ㄥ湴鍧�
+3. **鏃犳硶杩炴帴缃戠粶**锛氭鏌i-Fi鍑嵁鍜岀綉缁滈厤缃�
+4. **鏃犳硶涓庢湇鍔″櫒閫氫俊**锛氭鏌QTT鎴朩ebSocket閰嶇疆
+
+## 鍙傝�冭祫鏂�
+
+- ESP-IDF 鏂囨。: https://docs.espressif.com/projects/esp-idf/
+- LVGL 鏂囨。: https://docs.lvgl.io/
+- ESP-SR 鏂囨。: https://github.com/espressif/esp-sr
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/docs/mcp-based-graph.jpg b/xiaozhi-esp32-2.2.6/docs/mcp-based-graph.jpg
new file mode 100644
index 0000000..af81cd2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mcp-based-graph.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/mcp-protocol.md b/xiaozhi-esp32-2.2.6/docs/mcp-protocol.md
new file mode 100644
index 0000000..847f27c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mcp-protocol.md
@@ -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.
diff --git a/xiaozhi-esp32-2.2.6/docs/mcp-protocol_zh.md b/xiaozhi-esp32-2.2.6/docs/mcp-protocol_zh.md
new file mode 100644
index 0000000..0c8ec90
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mcp-protocol_zh.md
@@ -0,0 +1,269 @@
+# MCP (Model Context Protocol) 浜や簰娴佺▼
+
+NOTICE: AI 杈呭姪鐢熸垚, 鍦ㄥ疄鐜板悗鍙版湇鍔℃椂, 璇峰弬鐓т唬鐮佺‘璁ょ粏鑺�!!
+
+鏈」鐩腑鐨� MCP 鍗忚鐢ㄤ簬鍚庡彴 API锛圡CP 瀹㈡埛绔級涓� ESP32 璁惧锛圡CP 鏈嶅姟鍣級涔嬮棿鐨勯�氫俊锛屼互渚垮悗鍙拌兘澶熷彂鐜板拰璋冪敤璁惧鎻愪緵鐨勫姛鑳斤紙宸ュ叿锛夈��
+
+## 鍗忚鏍煎紡
+
+鏍规嵁浠g爜 (`main/protocols/protocol.cc`, `main/mcp_server.cc`)锛孧CP 娑堟伅鏄皝瑁呭湪鍩虹閫氫俊鍗忚锛堝 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": "..." // 璁惧鏀跺埌鏈嶅姟鍣╤ello鍚庡彲鑳借缃�
+ }
+ ```
+
+2. **鍒濆鍖� MCP 浼氳瘽**
+
+ - **鏃舵満锛�** 鍚庡彴 API 鏀跺埌璁惧 "hello" 娑堟伅锛岀‘璁よ澶囨敮鎸� MCP 鍚庯紝閫氬父浣滀负 MCP 浼氳瘽鐨勭涓�涓姹傚彂閫併��
+ - **鍙戦�佹柟锛�** 鍚庡彴 API (瀹㈡埛绔�)銆�
+ - **鏂规硶锛�** `initialize`
+ - **娑堟伅 (MCP payload):**
+
+ ```json
+ {
+ "jsonrpc": "2.0",
+ "method": "initialize",
+ "params": {
+ "capabilities": {
+ // 瀹㈡埛绔兘鍔涳紝鍙��
+
+ // 鎽勫儚澶磋瑙夌浉鍏�
+ "vision": {
+ "url": "...", //鎽勫儚澶�: 鍥剧墖澶勭悊鍦板潃(蹇呴』鏄痟ttp鍦板潃, 涓嶆槸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" } // 绀轰緥锛歴et_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 鐨勪簨浠舵椂锛堜緥濡傦紝鐘舵�佸彉鍖栵紝铏界劧浠g爜绀轰緥涓病鏈夋槑纭殑宸ュ叿鍙戦�佹绫绘秷鎭紝浣� `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` 浠ュ強鍚勪釜宸ュ叿鐨勫疄鐜般��
diff --git a/xiaozhi-esp32-2.2.6/docs/mcp-usage.md b/xiaozhi-esp32-2.2.6/docs/mcp-usage.md
new file mode 100644
index 0000000..7025690
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mcp-usage.md
@@ -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).
diff --git a/xiaozhi-esp32-2.2.6/docs/mcp-usage_zh.md b/xiaozhi-esp32-2.2.6/docs/mcp-usage_zh.md
new file mode 100644
index 0000000..15b69ea
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mcp-usage_zh.md
@@ -0,0 +1,115 @@
+# MCP 鍗忚鐗╄仈缃戞帶鍒剁敤娉曡鏄�
+
+> 鏈枃妗d粙缁嶅浣曞熀浜� MCP 鍗忚瀹炵幇 ESP32 璁惧鐨勭墿鑱旂綉鎺у埗銆傝缁嗗崗璁祦绋嬭鍙傝�� [`mcp-protocol_zh.md`](./mcp-protocol_zh.md)銆�
+
+## 绠�浠�
+
+MCP锛圡odel Context Protocol锛夋槸鏂颁竴浠f帹鑽愮敤浜庣墿鑱旂綉鎺у埗鐨勫崗璁紝閫氳繃鏍囧噯 JSON-RPC 2.0 鏍煎紡鍦ㄥ悗鍙颁笌璁惧闂村彂鐜板拰璋冪敤"宸ュ叿"锛圱ool锛夛紝瀹炵幇鐏垫椿鐨勮澶囨帶鍒躲��
+
+## 鍏稿瀷浣跨敤娴佺▼
+
+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)銆�
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/docs/mqtt-udp.md b/xiaozhi-esp32-2.2.6/docs/mqtt-udp.md
new file mode 100644
index 0000000..086d5d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mqtt-udp.md
@@ -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.
diff --git a/xiaozhi-esp32-2.2.6/docs/mqtt-udp_zh.md b/xiaozhi-esp32-2.2.6/docs/mqtt-udp_zh.md
new file mode 100644
index 0000000..478e466
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/mqtt-udp_zh.md
@@ -0,0 +1,393 @@
+# MQTT + UDP 娣峰悎閫氫俊鍗忚鏂囨。
+
+鍩轰簬浠g爜瀹炵幇鏁寸悊鐨� MQTT + UDP 娣峰悎閫氫俊鍗忚鏂囨。锛屾杩拌澶囩涓庢湇鍔″櫒涔嬮棿濡備綍閫氳繃 MQTT 杩涜鎺у埗娑堟伅浼犺緭锛岄�氳繃 UDP 杩涜闊抽鏁版嵁浼犺緭鐨勪氦浜掓柟寮忋��
+
+---
+
+## 1. 鍗忚姒傝
+
+鏈崗璁噰鐢ㄦ贩鍚堜紶杈撴柟寮忥細
+- **MQTT**锛氱敤浜庢帶鍒舵秷鎭�佺姸鎬佸悓姝ャ�丣SON 鏁版嵁浜ゆ崲
+- **UDP**锛氱敤浜庡疄鏃堕煶棰戞暟鎹紶杈擄紝鏀寔鍔犲瘑
+
+### 1.1 鍗忚鐗圭偣
+
+- **鍙岄�氶亾璁捐**锛氭帶鍒朵笌鏁版嵁鍒嗙锛岀‘淇濆疄鏃舵��
+- **鍔犲瘑浼犺緭**锛歎DP 闊抽鏁版嵁浣跨敤 AES-CTR 鍔犲瘑
+- **搴忓垪鍙蜂繚鎶�**锛氶槻姝㈡暟鎹寘閲嶆斁鍜屼贡搴�
+- **鑷姩閲嶈繛**锛歁QTT 杩炴帴鏂紑鏃惰嚜鍔ㄩ噸杩�
+
+---
+
+## 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**锛歁QTT 鏈嶅姟鍣ㄥ湴鍧�鍜岀鍙�
+- **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`锛歎DP 鏈嶅姟鍣ㄥ湴鍧�
+- `udp.port`锛歎DP 鏈嶅姟鍣ㄧ鍙�
+- `udp.key`锛欰ES 鍔犲瘑瀵嗛挜锛堝崄鍏繘鍒跺瓧绗︿覆锛�
+- `udp.nonce`锛欰ES 鍔犲瘑闅忔満鏁帮紙鍗佸叚杩涘埗瀛楃涓诧級
+
+### 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. 瑙f瀽 UDP 鏈嶅姟鍣ㄥ湴鍧�鍜岀鍙�
+2. 瑙f瀽鍔犲瘑瀵嗛挜鍜岄殢鏈烘暟
+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. **瑙e瘑澶辫触**锛氳褰曢敊璇紝涓㈠純鏁版嵁鍖�
+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`锛歁QTT 鏈嶅姟鍣ㄥ湴鍧�
+- `client_id`锛氬鎴风鏍囪瘑绗�
+- `username`锛氱敤鎴峰悕
+- `password`锛氬瘑鐮�
+- `keepalive`锛氬績璺抽棿闅旓紙榛樿240绉掞級
+- `publish_topic`锛氬彂甯冧富棰�
+
+### 6.2 闊抽鍙傛暟
+
+- **鏍煎紡**锛歄pus
+- **閲囨牱鐜�**锛�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 鐩戞帶鎸囨爣
+
+- 杩炴帴鎴愬姛鐜�
+- 闊抽浼犺緭寤惰繜
+- 鏁版嵁鍖呬涪澶辩巼
+- 瑙e瘑澶辫触鐜�
+
+---
+
+## 12. 鎬荤粨
+
+MQTT + UDP 娣峰悎鍗忚閫氳繃浠ヤ笅璁捐瀹炵幇楂樻晥鐨勯煶瑙嗛閫氫俊锛�
+
+- **鍒嗙寮忔灦鏋�**锛氭帶鍒朵笌鏁版嵁閫氶亾鍒嗙锛屽悇鍙稿叾鑱�
+- **鍔犲瘑淇濇姢**锛欰ES-CTR 纭繚闊抽鏁版嵁瀹夊叏浼犺緭
+- **搴忓垪鍖栫鐞�**锛氶槻姝㈤噸鏀炬敾鍑诲拰鏁版嵁涔卞簭
+- **鑷姩鎭㈠**锛氭敮鎸佽繛鎺ユ柇寮�鍚庣殑鑷姩閲嶈繛
+- **鎬ц兘浼樺寲**锛歎DP 浼犺緭淇濊瘉闊抽鏁版嵁鐨勫疄鏃舵��
+
+璇ュ崗璁�傜敤浜庡瀹炴椂鎬ц姹傝緝楂樼殑璇煶浜や簰鍦烘櫙锛屼絾闇�瑕佸湪缃戠粶澶嶆潅搴﹀拰浼犺緭鎬ц兘涔嬮棿鍋氬嚭鏉冭 銆�
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/AtomMatrix-echo-base.jpg b/xiaozhi-esp32-2.2.6/docs/v0/AtomMatrix-echo-base.jpg
new file mode 100644
index 0000000..979cf81
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/AtomMatrix-echo-base.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/ESP32-BreadBoard.jpg b/xiaozhi-esp32-2.2.6/docs/v0/ESP32-BreadBoard.jpg
new file mode 100644
index 0000000..f7a6fd4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/ESP32-BreadBoard.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/atoms3r-echo-base.jpg b/xiaozhi-esp32-2.2.6/docs/v0/atoms3r-echo-base.jpg
new file mode 100755
index 0000000..961e72b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/atoms3r-echo-base.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/esp32s3-box3.jpg b/xiaozhi-esp32-2.2.6/docs/v0/esp32s3-box3.jpg
new file mode 100644
index 0000000..53c4b55
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/esp32s3-box3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/lichuang-s3.jpg b/xiaozhi-esp32-2.2.6/docs/v0/lichuang-s3.jpg
new file mode 100644
index 0000000..721e0a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/lichuang-s3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/m5stack-cores3.jpg b/xiaozhi-esp32-2.2.6/docs/v0/m5stack-cores3.jpg
new file mode 100644
index 0000000..b123f73
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/m5stack-cores3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/magiclick-2p4.jpg b/xiaozhi-esp32-2.2.6/docs/v0/magiclick-2p4.jpg
new file mode 100644
index 0000000..beffb3d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/magiclick-2p4.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/waveshare-esp32-s3-touch-amoled-1.8.jpg b/xiaozhi-esp32-2.2.6/docs/v0/waveshare-esp32-s3-touch-amoled-1.8.jpg
new file mode 100644
index 0000000..90f2744
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/waveshare-esp32-s3-touch-amoled-1.8.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v0/wiring.jpg b/xiaozhi-esp32-2.2.6/docs/v0/wiring.jpg
new file mode 100644
index 0000000..764c170
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v0/wiring.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/atoms3r.jpg b/xiaozhi-esp32-2.2.6/docs/v1/atoms3r.jpg
new file mode 100644
index 0000000..45cbb45
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/atoms3r.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/electron-bot.png b/xiaozhi-esp32-2.2.6/docs/v1/electron-bot.png
new file mode 100644
index 0000000..4d00d6d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/electron-bot.png
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/esp-hi.jpg b/xiaozhi-esp32-2.2.6/docs/v1/esp-hi.jpg
new file mode 100644
index 0000000..d6fc714
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/esp-hi.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/esp-sparkbot.jpg b/xiaozhi-esp32-2.2.6/docs/v1/esp-sparkbot.jpg
new file mode 100644
index 0000000..b738840
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/esp-sparkbot.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/espbox3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/espbox3.jpg
new file mode 100644
index 0000000..641d74b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/espbox3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/lichuang-s3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/lichuang-s3.jpg
new file mode 100644
index 0000000..a559070
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/lichuang-s3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/lilygo-t-circle-s3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/lilygo-t-circle-s3.jpg
new file mode 100644
index 0000000..45985d8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/lilygo-t-circle-s3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/m5cores3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/m5cores3.jpg
new file mode 100644
index 0000000..6a30cef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/m5cores3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/magiclick.jpg b/xiaozhi-esp32-2.2.6/docs/v1/magiclick.jpg
new file mode 100644
index 0000000..3c01463
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/magiclick.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/movecall-cuican-esp32s3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/movecall-cuican-esp32s3.jpg
new file mode 100755
index 0000000..ae70cfd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/movecall-cuican-esp32s3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/movecall-moji-esp32s3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/movecall-moji-esp32s3.jpg
new file mode 100644
index 0000000..dec4526
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/movecall-moji-esp32s3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/otto-robot.png b/xiaozhi-esp32-2.2.6/docs/v1/otto-robot.png
new file mode 100644
index 0000000..d61cbdc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/otto-robot.png
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/sensecap_watcher.jpg b/xiaozhi-esp32-2.2.6/docs/v1/sensecap_watcher.jpg
new file mode 100644
index 0000000..b1d7e4c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/sensecap_watcher.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/waveshare.jpg b/xiaozhi-esp32-2.2.6/docs/v1/waveshare.jpg
new file mode 100644
index 0000000..7dacf2f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/waveshare.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/wiring2.jpg b/xiaozhi-esp32-2.2.6/docs/v1/wiring2.jpg
new file mode 100644
index 0000000..f3a67ae
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/wiring2.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_0.96.jpg b/xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_0.96.jpg
new file mode 100644
index 0000000..24369cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_0.96.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_1.54.jpg b/xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_1.54.jpg
new file mode 100644
index 0000000..7456477
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/wmnologo_xingzhi_1.54.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/v1/xmini-c3.jpg b/xiaozhi-esp32-2.2.6/docs/v1/xmini-c3.jpg
new file mode 100644
index 0000000..f1ed8c2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/v1/xmini-c3.jpg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/docs/websocket.md b/xiaozhi-esp32-2.2.6/docs/websocket.md
new file mode 100644
index 0000000..d9d06ad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/websocket.md
@@ -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.
diff --git a/xiaozhi-esp32-2.2.6/docs/websocket_zh.md b/xiaozhi-esp32-2.2.6/docs/websocket_zh.md
new file mode 100644
index 0000000..fb335f8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/docs/websocket_zh.md
@@ -0,0 +1,495 @@
+浠ヤ笅鏄竴浠藉熀浜庝唬鐮佸疄鐜版暣鐞嗙殑 WebSocket 閫氫俊鍗忚鏂囨。锛屾杩拌澶囩涓庢湇鍔″櫒涔嬮棿濡備綍閫氳繃 WebSocket 杩涜浜や簰銆�
+
+璇ユ枃妗d粎鍩轰簬鎵�鎻愪緵鐨勪唬鐮佹帹鏂紝瀹為檯閮ㄧ讲鏃跺彲鑳介渶瑕佺粨鍚堟湇鍔″櫒绔疄鐜拌繘琛岃繘涓�姝ョ‘璁ゆ垨琛ュ厖銆�
+
+---
+
+## 1. 鎬讳綋娴佺▼姒傝
+
+1. **璁惧绔垵濮嬪寲**
+ - 璁惧涓婄數銆佸垵濮嬪寲 `Application`锛�
+ - 鍒濆鍖栭煶棰戠紪瑙g爜鍣ㄣ�佹樉绀哄睆銆丩ED 绛�
+ - 杩炴帴缃戠粶
+ - 鍒涘缓骞跺垵濮嬪寲瀹炵幇 `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 绉掞級鍐呮湭鏀跺埌姝g‘鍥炲锛岃涓鸿繛鎺ュけ璐ュ苟瑙﹀彂缃戠粶閿欒鍥炶皟銆�
+
+5. **鍚庣画娑堟伅浜や簰**
+ - 璁惧绔拰鏈嶅姟鍣ㄧ涔嬮棿鍙彂閫佷袱绉嶄富瑕佺被鍨嬬殑鏁版嵁锛�
+ 1. **浜岃繘鍒堕煶棰戞暟鎹�**锛圤pus 缂栫爜锛�
+ 2. **鏂囨湰 JSON 娑堟伅**锛堢敤浜庝紶杈撹亰澶╃姸鎬併�乀TS/STT 浜嬩欢銆丮CP 鍗忚娑堟伅绛夛級
+
+ - 鍦ㄤ唬鐮侀噷锛屾帴鏀跺洖璋冧富瑕佸垎涓猴細
+ - `OnData(...)`:
+ - 褰� `binary` 涓� `true` 鏃讹紝璁や负鏄煶棰戝抚锛涜澶囦細灏嗗叾褰撲綔 Opus 鏁版嵁杩涜瑙g爜銆�
+ - 褰� `binary` 涓� `false` 鏃讹紝璁や负鏄� JSON 鏂囨湰锛岄渶瑕佸湪璁惧绔敤 cJSON 杩涜瑙f瀽骞跺仛鐩稿簲涓氬姟閫昏緫澶勭悊锛堝鑱婂ぉ銆乀TS銆丮CP 鍗忚娑堟伅绛夛級銆�
+
+ - 褰撴湇鍔″櫒鎴栫綉缁滃嚭鐜版柇杩烇紝鍥炶皟 `OnDisconnected()` 琚Е鍙戯細
+ - 璁惧浼氳皟鐢� `on_audio_channel_closed_()`锛屽苟鏈�缁堝洖鍒扮┖闂茬姸鎬併��
+
+6. **鍏抽棴 WebSocket 杩炴帴**
+ - 璁惧鍦ㄩ渶瑕佺粨鏉熻闊充細璇濇椂锛屼細璋冪敤 `CloseAudioChannel()` 涓诲姩鏂紑杩炴帴锛屽苟鍥炲埌绌洪棽鐘舵�併��
+ - 鎴栬�呭鏋滄湇鍔″櫒绔富鍔ㄦ柇寮�锛屼篃浼氬紩鍙戝悓鏍风殑鍥炶皟娴佺▼銆�
+
+---
+
+## 2. 閫氱敤璇锋眰澶�
+
+鍦ㄥ缓绔� WebSocket 杩炴帴鏃讹紝浠g爜绀轰緥涓缃簡浠ヤ笅璇锋眰澶达細
+
+- `Authorization`: 鐢ㄤ簬瀛樻斁璁块棶浠ょ墝锛屽舰濡� `"Bearer <token>"`
+- `Protocol-Version`: 鍗忚鐗堟湰鍙凤紝涓� hello 娑堟伅浣撳唴鐨� `version` 瀛楁淇濇寔涓�鑷�
+- `Device-Id`: 璁惧鐗╃悊缃戝崱 MAC 鍦板潃
+- `Client-Id`: 杞欢鐢熸垚鐨� UUID锛堟摝闄� NVS 鎴栭噸鏂扮儳褰曞畬鏁村浐浠朵細閲嶇疆锛�
+
+杩欎簺澶翠細闅忕潃 WebSocket 鎻℃墜涓�璧峰彂閫佸埌鏈嶅姟鍣紝鏈嶅姟鍣ㄥ彲鏍规嵁闇�姹傝繘琛屾牎楠屻�佽璇佺瓑銆�
+
+---
+
+## 3. 浜岃繘鍒跺崗璁増鏈�
+
+璁惧鏀寔澶氱浜岃繘鍒跺崗璁増鏈紝閫氳繃閰嶇疆涓殑 `version` 瀛楁鎸囧畾锛�
+
+### 3.1 鐗堟湰1锛堥粯璁わ級
+鐩存帴鍙戦�� Opus 闊抽鏁版嵁锛屾棤棰濆鍏冩暟鎹�俉ebsocket 鍗忚浼氬尯鍒� 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; // 鏃堕棿鎴筹紙姣锛岀敤浜庢湇鍔″櫒绔疉EC锛�
+ 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**
+ - 缁堟褰撳墠璇磋瘽锛圱TS 鎾斁锛夋垨璇煶閫氶亾銆�
+ - 渚嬶細
+ ```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**
+ - 鎺ㄨ崘鐢ㄤ簬鐗╄仈缃戞帶鍒剁殑鏂颁竴浠e崗璁�傛墍鏈夎澶囪兘鍔涘彂鐜般�佸伐鍏疯皟鐢ㄧ瓑鍧囬�氳繃 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" 鐨勬秷鎭笅鍙戠墿鑱旂綉鐩稿叧鐨勬帶鍒舵寚浠ゆ垨杩斿洖璋冪敤缁撴灉锛宲ayload 缁撴瀯鍚屼笂銆�
+
+ - **鏈嶅姟鍣ㄥ埌璁惧绔彂閫� 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 缂栫爜锛夋椂锛岃澶囩瑙g爜骞舵挱鏀俱��
+ - 鑻ヨ澶囩姝e湪澶勪簬 "listening" 锛堝綍闊筹級鐘舵�侊紝鏀跺埌鐨勯煶棰戝抚浼氳蹇界暐鎴栨竻绌轰互闃插啿绐併��
+
+---
+
+## 5. 闊抽缂栬В鐮�
+
+1. **璁惧绔彂閫佸綍闊虫暟鎹�**
+ - 闊抽杈撳叆缁忚繃鍙兘鐨勫洖澹版秷闄ゃ�侀檷鍣垨闊抽噺澧炵泭鍚庯紝閫氳繃 Opus 缂栫爜鎵撳寘涓轰簩杩涘埗甯у彂閫佺粰鏈嶅姟鍣ㄣ��
+ - 鏍规嵁鍗忚鐗堟湰锛屽彲鑳界洿鎺ュ彂閫� Opus 鏁版嵁锛堢増鏈�1锛夋垨浣跨敤甯﹀厓鏁版嵁鐨勪簩杩涘埗鍗忚锛堢増鏈�2/3锛夈��
+
+2. **璁惧绔挱鏀炬敹鍒扮殑闊抽**
+ - 鏀跺埌鏈嶅姟鍣ㄧ殑浜岃繘鍒跺抚鏃讹紝鍚屾牱璁ゅ畾鏄� Opus 鏁版嵁銆�
+ - 璁惧绔細杩涜瑙g爜锛岀劧鍚庝氦鐢遍煶棰戣緭鍑烘帴鍙f挱鏀俱��
+ - 濡傛灉鏈嶅姟鍣ㄧ殑闊抽閲囨牱鐜囦笌璁惧涓嶄竴鑷达紝浼氬湪瑙g爜鍚庡啀杩涜閲嶉噰鏍枫��
+
+---
+
+## 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. **浼氳瘽鎺у埗**
+ - 浠g爜涓儴鍒嗘秷鎭寘鍚� `session_id`锛岀敤浜庡尯鍒嗙嫭绔嬬殑瀵硅瘽鎴栨搷浣溿�傛湇鍔$鍙牴鎹渶瑕佸涓嶅悓浼氳瘽鍋氬垎绂诲鐞嗐��
+
+3. **闊抽璐熻浇**
+ - 浠g爜閲岄粯璁や娇鐢� Opus 鏍煎紡锛屽苟璁剧疆 `sample_rate = 16000`锛屽崟澹伴亾銆傚抚鏃堕暱鐢� `OPUS_FRAME_DURATION_MS` 鎺у埗锛屼竴鑸负 60ms銆傚彲鏍规嵁甯﹀鎴栨�ц兘鍋氶�傚綋璋冩暣銆備负浜嗚幏寰楁洿濂界殑闊充箰鎾斁鏁堟灉锛屾湇鍔″櫒涓嬭闊抽鍙兘浣跨敤 24000 閲囨牱鐜囥��
+
+4. **鍗忚鐗堟湰閰嶇疆**
+ - 閫氳繃璁剧疆涓殑 `version` 瀛楁閰嶇疆浜岃繘鍒跺崗璁増鏈紙1銆�2 鎴� 3锛�
+ - 鐗堟湰1锛氱洿鎺ュ彂閫� Opus 鏁版嵁
+ - 鐗堟湰2锛氫娇鐢ㄥ甫鏃堕棿鎴崇殑浜岃繘鍒跺崗璁紝閫傜敤浜庢湇鍔″櫒绔� AEC
+ - 鐗堟湰3锛氫娇鐢ㄧ畝鍖栫殑浜岃繘鍒跺崗璁�
+
+5. **鐗╄仈缃戞帶鍒舵帹鑽� MCP 鍗忚**
+ - 璁惧涓庢湇鍔″櫒涔嬮棿鐨勭墿鑱旂綉鑳藉姏鍙戠幇銆佺姸鎬佸悓姝ャ�佹帶鍒舵寚浠ょ瓑锛屽缓璁叏閮ㄩ�氳繃 MCP 鍗忚锛坱ype: "mcp"锛夊疄鐜般�傚師鏈夌殑 type: "iot" 鏂规宸插簾寮冦��
+ - MCP 鍗忚鍙湪 WebSocket銆丮QTT 绛夊绉嶅簳灞傚崗璁笂浼犺緭锛屽叿澶囨洿濂界殑鎵╁睍鎬у拰鏍囧噯鍖栬兘鍔涖��
+ - 璇︾粏鐢ㄦ硶璇峰弬鑰� [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. **鏈嶅姟鍣� 鈫� 璁惧绔�**锛圓SR 缁撴灉锛�
+ ```json
+ {
+ "session_id": "xxx",
+ "type": "stt",
+ "text": "鐢ㄦ埛璇寸殑璇�"
+ }
+ ```
+
+5. **鏈嶅姟鍣� 鈫� 璁惧绔�**锛圱TS寮�濮嬶級
+ ```json
+ {
+ "session_id": "xxx",
+ "type": "tts",
+ "state": "start"
+ }
+ ```
+ 鎺ョ潃鏈嶅姟鍣ㄥ彂閫佷簩杩涘埗闊抽甯х粰璁惧绔挱鏀俱��
+
+6. **鏈嶅姟鍣� 鈫� 璁惧绔�**锛圱TS缁撴潫锛�
+ ```json
+ {
+ "session_id": "xxx",
+ "type": "tts",
+ "state": "stop"
+ }
+ ```
+ 璁惧绔仠姝㈡挱鏀鹃煶棰戯紝鑻ユ棤鏇村鎸囦护锛屽垯鍥炲埌绌洪棽鐘舵�併��
+
+---
+
+## 10. 鎬荤粨
+
+鏈崗璁�氳繃鍦� WebSocket 涓婂眰浼犺緭 JSON 鏂囨湰涓庝簩杩涘埗闊抽甯э紝瀹屾垚鍔熻兘鍖呮嫭闊抽娴佷笂浼犮�乀TS 闊抽鎾斁銆佽闊宠瘑鍒笌鐘舵�佺鐞嗐�丮CP 鎸囦护涓嬪彂绛夈�傚叾鏍稿績鐗瑰緛锛�
+
+- **鎻℃墜闃舵**锛氬彂閫� `"type":"hello"`锛岀瓑寰呮湇鍔″櫒杩斿洖銆�
+- **闊抽閫氶亾**锛氶噰鐢� Opus 缂栫爜鐨勪簩杩涘埗甯у弻鍚戜紶杈撹闊虫祦锛屾敮鎸佸绉嶅崗璁増鏈��
+- **JSON 娑堟伅**锛氫娇鐢� `"type"` 涓烘牳蹇冨瓧娈垫爣璇嗕笉鍚屼笟鍔¢�昏緫锛屽寘鎷� TTS銆丼TT銆丮CP銆乄akeWord銆丼ystem銆丆ustom 绛夈��
+- **鎵╁睍鎬�**锛氬彲鏍规嵁瀹為檯闇�姹傚湪 JSON 娑堟伅涓坊鍔犲瓧娈碉紝鎴栧湪 headers 閲岃繘琛岄澶栭壌鏉冦��
+
+鏈嶅姟鍣ㄤ笌璁惧绔渶鎻愬墠绾﹀畾鍚勭被娑堟伅鐨勫瓧娈靛惈涔夈�佹椂搴忛�昏緫浠ュ強閿欒澶勭悊瑙勫垯锛屾柟鑳戒繚璇侀�氫俊椤虹晠銆備笂杩颁俊鎭彲浣滀负鍩虹鏂囨。锛屼究浜庡悗缁鎺ャ�佸紑鍙戞垨鎵╁睍銆�
diff --git a/xiaozhi-esp32-2.2.6/main/CMakeLists.txt b/xiaozhi-esp32-2.2.6/main/CMakeLists.txt
new file mode 100644
index 0000000..3d90620
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/CMakeLists.txt
@@ -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()
diff --git a/xiaozhi-esp32-2.2.6/main/Kconfig.projbuild b/xiaozhi-esp32-2.2.6/main/Kconfig.projbuild
new file mode 100644
index 0000000..e69337b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/Kconfig.projbuild
@@ -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 鐠�鐠稟I鍚婂潬"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3
+ bool "姝g偣鍘熷瓙DNESP32S3寮�鍙戞澘"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3_BOX
+ bool "姝g偣鍘熷瓙DNESP32S3-BOX"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3_BOX0
+ bool "姝g偣鍘熷瓙DNESP32S3-BOX0"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI
+ bool "姝g偣鍘熷瓙DNESP32S3-BOX2-WIFI"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3_BOX2_4G
+ bool "姝g偣鍘熷瓙DNESP32S3-BOX2-4G"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3M_WIFI
+ bool "姝g偣鍘熷瓙DNESP32S3M-WIFI"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3M_4G
+ bool "姝g偣鍘熷瓙DNESP32S3M-4G"
+ depends on IDF_TARGET_ESP32S3
+ config BOARD_TYPE_ATK_DNESP32S3_BOX3
+ bool "姝g偣鍘熷瓙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銆丒SP32 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
+ 鍚姩鍙屽0閬�
+endmenu
+
+endmenu
diff --git a/xiaozhi-esp32-2.2.6/main/application.cc b/xiaozhi-esp32-2.2.6/main/application.cc
new file mode 100644
index 0000000..0a809fa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/application.cc
@@ -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();
+ });
+}
+
diff --git a/xiaozhi-esp32-2.2.6/main/application.h b/xiaozhi-esp32-2.2.6/main/application.h
new file mode 100644
index 0000000..7ca7af4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/application.h
@@ -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_
diff --git a/xiaozhi-esp32-2.2.6/main/assets.cc b/xiaozhi-esp32-2.2.6/main/assets.cc
new file mode 100644
index 0000000..f9bf9e6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets.cc
@@ -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锛圗SP32鐨勬爣鍑嗘墖鍖哄ぇ灏忥級
+ 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);
+
+ // 鍐欏叆鏂扮殑璧勬簮鏂囦欢鍒板垎鍖猴紝涓�杈筫rase涓�杈瑰啓鍏�
+ 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;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/assets.h b/xiaozhi-esp32-2.2.6/main/assets.h
new file mode 100644
index 0000000..2c1b7a1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/assets/common/exclamation.ogg b/xiaozhi-esp32-2.2.6/main/assets/common/exclamation.ogg
new file mode 100644
index 0000000..9a4cf58
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/common/exclamation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/common/low_battery.ogg b/xiaozhi-esp32-2.2.6/main/assets/common/low_battery.ogg
new file mode 100644
index 0000000..968b3ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/common/low_battery.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/common/popup.ogg b/xiaozhi-esp32-2.2.6/main/assets/common/popup.ogg
new file mode 100644
index 0000000..ea73b6f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/common/popup.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/common/success.ogg b/xiaozhi-esp32-2.2.6/main/assets/common/success.ogg
new file mode 100644
index 0000000..ced1902
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/common/success.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/common/vibration.ogg b/xiaozhi-esp32-2.2.6/main/assets/common/vibration.ogg
new file mode 100644
index 0000000..0076436
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/common/vibration.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/0.ogg
new file mode 100644
index 0000000..c656582
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/1.ogg
new file mode 100644
index 0000000..dca3e06
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/2.ogg
new file mode 100644
index 0000000..6d1f4d7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/3.ogg
new file mode 100644
index 0000000..12667d9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/4.ogg
new file mode 100644
index 0000000..e12dd7a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/5.ogg
new file mode 100644
index 0000000..7dae30d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/6.ogg
new file mode 100644
index 0000000..697278b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/7.ogg
new file mode 100644
index 0000000..f83701d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/8.ogg
new file mode 100644
index 0000000..bf3d2a1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/9.ogg
new file mode 100644
index 0000000..6d62228
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/activation.ogg
new file mode 100644
index 0000000..17aa008
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_pin.ogg
new file mode 100644
index 0000000..8c624f5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_reg.ogg
new file mode 100644
index 0000000..b540a47
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/language.json
new file mode 100644
index 0000000..31d3078
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/language.json
@@ -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": "賮卮賱 鬲賴賷卅丞 丕賱賲賵丿賲"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/upgrade.ogg
new file mode 100644
index 0000000..f60d374
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/welcome.ogg
new file mode 100644
index 0000000..77028ad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/wificonfig.ogg
new file mode 100644
index 0000000..b82f7a8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ar-SA/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/0.ogg
new file mode 100644
index 0000000..0cf6483
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/1.ogg
new file mode 100644
index 0000000..68bc66b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/2.ogg
new file mode 100644
index 0000000..71f874d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/3.ogg
new file mode 100644
index 0000000..95aaceb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/4.ogg
new file mode 100644
index 0000000..af31cf7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/5.ogg
new file mode 100644
index 0000000..bae3dd8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/6.ogg
new file mode 100644
index 0000000..7271edc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/7.ogg
new file mode 100644
index 0000000..707f7f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/8.ogg
new file mode 100644
index 0000000..52c4513
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/9.ogg
new file mode 100644
index 0000000..dd5a37f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/activation.ogg
new file mode 100644
index 0000000..d9bfb78
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_pin.ogg
new file mode 100644
index 0000000..5a1f5cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_reg.ogg
new file mode 100644
index 0000000..992557c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/language.json
new file mode 100644
index 0000000..d222be6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/language.json
@@ -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": "袧械褍褋锌械褕薪邪 懈薪懈褑懈邪谢懈蟹邪褑懈褟 薪邪 屑芯写械屑邪"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/upgrade.ogg
new file mode 100644
index 0000000..9c1a885
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/welcome.ogg
new file mode 100644
index 0000000..71e547b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/wificonfig.ogg
new file mode 100644
index 0000000..7d84033
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/bg-BG/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/0.ogg
new file mode 100644
index 0000000..2934044
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/1.ogg
new file mode 100644
index 0000000..2069b40
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/2.ogg
new file mode 100644
index 0000000..926011c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/3.ogg
new file mode 100644
index 0000000..ce93833
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/4.ogg
new file mode 100644
index 0000000..8a3b4a9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/5.ogg
new file mode 100644
index 0000000..87144f5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/6.ogg
new file mode 100644
index 0000000..b640829
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/7.ogg
new file mode 100644
index 0000000..f6039c5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/8.ogg
new file mode 100644
index 0000000..c6cca34
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/9.ogg
new file mode 100644
index 0000000..704dca0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/activation.ogg
new file mode 100644
index 0000000..082e561
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_pin.ogg
new file mode 100644
index 0000000..ea11662
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_reg.ogg
new file mode 100644
index 0000000..3e9b042
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/language.json
new file mode 100644
index 0000000..566ceab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/upgrade.ogg
new file mode 100644
index 0000000..1bb2f28
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/welcome.ogg
new file mode 100644
index 0000000..530d04e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/wificonfig.ogg
new file mode 100644
index 0000000..a4b77cd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ca-ES/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/0.ogg
new file mode 100644
index 0000000..96891e6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/1.ogg
new file mode 100644
index 0000000..a12d638
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/2.ogg
new file mode 100644
index 0000000..a18d97e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/3.ogg
new file mode 100644
index 0000000..a2d9d3d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/4.ogg
new file mode 100644
index 0000000..76646ed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/5.ogg
new file mode 100644
index 0000000..2d9ab25
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/6.ogg
new file mode 100644
index 0000000..571ff30
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/7.ogg
new file mode 100644
index 0000000..e8f0f94
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/8.ogg
new file mode 100644
index 0000000..0f72a58
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/9.ogg
new file mode 100644
index 0000000..c102667
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/activation.ogg
new file mode 100644
index 0000000..b4334a4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_pin.ogg
new file mode 100644
index 0000000..07818a1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_reg.ogg
new file mode 100644
index 0000000..6d4752e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/language.json
new file mode 100644
index 0000000..7386fdf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/language.json
@@ -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": "锛宲艡铆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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/upgrade.ogg
new file mode 100644
index 0000000..e29edc5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/welcome.ogg
new file mode 100644
index 0000000..7a0625e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/wificonfig.ogg
new file mode 100644
index 0000000..bc945a9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/cs-CZ/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/0.ogg
new file mode 100644
index 0000000..b27207c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/1.ogg
new file mode 100644
index 0000000..5fd2fce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/2.ogg
new file mode 100644
index 0000000..f3be2ac
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/3.ogg
new file mode 100644
index 0000000..3ad6f10
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/4.ogg
new file mode 100644
index 0000000..8684e63
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/5.ogg
new file mode 100644
index 0000000..29559fd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/6.ogg
new file mode 100644
index 0000000..d0b9ad7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/7.ogg
new file mode 100644
index 0000000..28ecaca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/8.ogg
new file mode 100644
index 0000000..2d71851
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/9.ogg
new file mode 100644
index 0000000..11af485
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/activation.ogg
new file mode 100644
index 0000000..58b9b43
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_pin.ogg
new file mode 100644
index 0000000..391d984
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_reg.ogg
new file mode 100644
index 0000000..199f807
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/language.json
new file mode 100644
index 0000000..96c9d72
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/upgrade.ogg
new file mode 100644
index 0000000..b3e1c29
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/welcome.ogg
new file mode 100644
index 0000000..f211cc4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/wificonfig.ogg
new file mode 100644
index 0000000..33ea9c5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/da-DK/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/0.ogg
new file mode 100644
index 0000000..a3cdb23
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/1.ogg
new file mode 100644
index 0000000..fb4fd93
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/2.ogg
new file mode 100644
index 0000000..fb09b60
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/3.ogg
new file mode 100644
index 0000000..d3f7549
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/4.ogg
new file mode 100644
index 0000000..46db0ce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/5.ogg
new file mode 100644
index 0000000..3ceee0f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/6.ogg
new file mode 100644
index 0000000..8942550
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/7.ogg
new file mode 100644
index 0000000..3b6957d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/8.ogg
new file mode 100644
index 0000000..421f018
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/9.ogg
new file mode 100644
index 0000000..40b7876
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/activation.ogg
new file mode 100644
index 0000000..3dfccbd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_pin.ogg
new file mode 100644
index 0000000..8d2eef9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_reg.ogg
new file mode 100644
index 0000000..430b876
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/language.json
new file mode 100644
index 0000000..20dfbca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/language.json
@@ -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": "锛孊rowser 枚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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/upgrade.ogg
new file mode 100644
index 0000000..716d7cb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/welcome.ogg
new file mode 100644
index 0000000..69271ce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/wificonfig.ogg
new file mode 100644
index 0000000..a3d753d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/de-DE/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/0.ogg
new file mode 100644
index 0000000..b06bdb5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/1.ogg
new file mode 100644
index 0000000..4a9b7d2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/2.ogg
new file mode 100644
index 0000000..eeb149d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/3.ogg
new file mode 100644
index 0000000..5364664
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/4.ogg
new file mode 100644
index 0000000..cbdf259
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/5.ogg
new file mode 100644
index 0000000..1e93b72
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/6.ogg
new file mode 100644
index 0000000..a831a42
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/7.ogg
new file mode 100644
index 0000000..1c64b31
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/8.ogg
new file mode 100644
index 0000000..d7a9696
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/9.ogg
new file mode 100644
index 0000000..8ff0b07
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/activation.ogg
new file mode 100644
index 0000000..aa8b401
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_pin.ogg
new file mode 100644
index 0000000..7c10086
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_reg.ogg
new file mode 100644
index 0000000..f65513a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/language.json
new file mode 100644
index 0000000..287db5e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/upgrade.ogg
new file mode 100644
index 0000000..81bef14
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/welcome.ogg
new file mode 100644
index 0000000..c7a2c7c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/wificonfig.ogg
new file mode 100644
index 0000000..fe3eb66
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/el-GR/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/0.ogg
new file mode 100644
index 0000000..51dff41
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/1.ogg
new file mode 100644
index 0000000..814753e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/2.ogg
new file mode 100644
index 0000000..420a994
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/3.ogg
new file mode 100644
index 0000000..1d3fa8a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/4.ogg
new file mode 100644
index 0000000..8e364ad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/5.ogg
new file mode 100644
index 0000000..5be437d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/6.ogg
new file mode 100644
index 0000000..e398409
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/7.ogg
new file mode 100644
index 0000000..879aebd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/8.ogg
new file mode 100644
index 0000000..0e9efd2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/9.ogg
new file mode 100644
index 0000000..434c320
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/activation.ogg
new file mode 100644
index 0000000..0e9b843
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_pin.ogg
new file mode 100644
index 0000000..c52fdf4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_reg.ogg
new file mode 100644
index 0000000..95593f4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/language.json
new file mode 100644
index 0000000..8bb764a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/language.json
@@ -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!"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/upgrade.ogg
new file mode 100644
index 0000000..f27ec7a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/welcome.ogg
new file mode 100644
index 0000000..4d4d8b9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/wificonfig.ogg
new file mode 100644
index 0000000..f4ed3d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/en-US/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/0.ogg
new file mode 100644
index 0000000..98fbba2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/1.ogg
new file mode 100644
index 0000000..2c2f4fb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/2.ogg
new file mode 100644
index 0000000..8e962b9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/3.ogg
new file mode 100644
index 0000000..6b6ba47
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/4.ogg
new file mode 100644
index 0000000..ae5909c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/5.ogg
new file mode 100644
index 0000000..a8a4ca3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/6.ogg
new file mode 100644
index 0000000..bcc46c0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/7.ogg
new file mode 100644
index 0000000..09e7df4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/8.ogg
new file mode 100644
index 0000000..e06e023
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/9.ogg
new file mode 100644
index 0000000..4108458
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/activation.ogg
new file mode 100644
index 0000000..8d50682
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_pin.ogg
new file mode 100644
index 0000000..b3e55f8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_reg.ogg
new file mode 100644
index 0000000..509451e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/language.json
new file mode 100644
index 0000000..9722fc4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/language.json
@@ -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": "锛宎cceder 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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/upgrade.ogg
new file mode 100644
index 0000000..c29826c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/welcome.ogg
new file mode 100644
index 0000000..a506db5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/wificonfig.ogg
new file mode 100644
index 0000000..11755fa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/es-ES/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/0.ogg
new file mode 100644
index 0000000..ed0881f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/1.ogg
new file mode 100644
index 0000000..401059d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/2.ogg
new file mode 100644
index 0000000..5ee8bfd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/3.ogg
new file mode 100644
index 0000000..35d7e13
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/4.ogg
new file mode 100644
index 0000000..5e19fe1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/5.ogg
new file mode 100644
index 0000000..2ac536b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/6.ogg
new file mode 100644
index 0000000..6b9f40a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/7.ogg
new file mode 100644
index 0000000..568e6c3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/8.ogg
new file mode 100644
index 0000000..055f395
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/9.ogg
new file mode 100644
index 0000000..42023e8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/activation.ogg
new file mode 100644
index 0000000..6bce7f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_pin.ogg
new file mode 100644
index 0000000..0450947
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_reg.ogg
new file mode 100644
index 0000000..d293376
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/language.json
new file mode 100644
index 0000000..597334e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/language.json
@@ -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": "禺胤丕 丿乇 乇丕賴鈥屫з嗀ж槽� 賲賵丿賲"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/upgrade.ogg
new file mode 100644
index 0000000..9eff121
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/welcome.ogg
new file mode 100644
index 0000000..108a62d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/wificonfig.ogg
new file mode 100644
index 0000000..179fa5e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fa-IR/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/0.ogg
new file mode 100644
index 0000000..d368787
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/1.ogg
new file mode 100644
index 0000000..f496dd6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/2.ogg
new file mode 100644
index 0000000..96a2529
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/3.ogg
new file mode 100644
index 0000000..1fcdcf2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/4.ogg
new file mode 100644
index 0000000..6ba9ded
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/5.ogg
new file mode 100644
index 0000000..b94d4a2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/6.ogg
new file mode 100644
index 0000000..ffe8276
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/7.ogg
new file mode 100644
index 0000000..ed3197a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/8.ogg
new file mode 100644
index 0000000..7f81a62
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/9.ogg
new file mode 100644
index 0000000..aaedd05
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/activation.ogg
new file mode 100644
index 0000000..6347745
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_pin.ogg
new file mode 100644
index 0000000..8c6eb51
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_reg.ogg
new file mode 100644
index 0000000..32561bf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/language.json
new file mode 100644
index 0000000..7ca8db7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/language.json
@@ -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": "锛宲盲盲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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/upgrade.ogg
new file mode 100644
index 0000000..5ff02a6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/welcome.ogg
new file mode 100644
index 0000000..1430372
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/wificonfig.ogg
new file mode 100644
index 0000000..deb233a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fi-FI/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/0.ogg
new file mode 100644
index 0000000..1f8c2fc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/1.ogg
new file mode 100644
index 0000000..bc38364
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/2.ogg
new file mode 100644
index 0000000..01cb65b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/3.ogg
new file mode 100644
index 0000000..0f40545
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/4.ogg
new file mode 100644
index 0000000..a476dc3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/5.ogg
new file mode 100644
index 0000000..1c39dab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/6.ogg
new file mode 100644
index 0000000..b672cbd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/7.ogg
new file mode 100644
index 0000000..f02aa1e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/8.ogg
new file mode 100644
index 0000000..b699c1e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/9.ogg
new file mode 100644
index 0000000..fc0adc8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/activation.ogg
new file mode 100644
index 0000000..d8b6a62
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_pin.ogg
new file mode 100644
index 0000000..527ca8c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_reg.ogg
new file mode 100644
index 0000000..94632fc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/language.json
new file mode 100644
index 0000000..6907698
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/upgrade.ogg
new file mode 100644
index 0000000..8930d78
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/welcome.ogg
new file mode 100644
index 0000000..ed59bce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/wificonfig.ogg
new file mode 100644
index 0000000..cef030d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fil-PH/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/0.ogg
new file mode 100644
index 0000000..79b8faf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/1.ogg
new file mode 100644
index 0000000..e517324
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/2.ogg
new file mode 100644
index 0000000..311697c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/3.ogg
new file mode 100644
index 0000000..5b4acc1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/4.ogg
new file mode 100644
index 0000000..832aeab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/5.ogg
new file mode 100644
index 0000000..ddcce17
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/6.ogg
new file mode 100644
index 0000000..aced771
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/7.ogg
new file mode 100644
index 0000000..293d1c0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/8.ogg
new file mode 100644
index 0000000..acec112
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/9.ogg
new file mode 100644
index 0000000..fe27acd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/activation.ogg
new file mode 100644
index 0000000..b48f215
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_pin.ogg
new file mode 100644
index 0000000..1a3d84b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_reg.ogg
new file mode 100644
index 0000000..c3256c5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/language.json
new file mode 100644
index 0000000..d1e16da
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/language.json
@@ -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": "锛宎cc茅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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/upgrade.ogg
new file mode 100644
index 0000000..b6ac1f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/welcome.ogg
new file mode 100644
index 0000000..4492de7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/wificonfig.ogg
new file mode 100644
index 0000000..9e2f99b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/fr-FR/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/0.ogg
new file mode 100644
index 0000000..efea905
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/1.ogg
new file mode 100644
index 0000000..b0984b6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/2.ogg
new file mode 100644
index 0000000..c70358a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/3.ogg
new file mode 100644
index 0000000..0398e82
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/4.ogg
new file mode 100644
index 0000000..78630a6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/5.ogg
new file mode 100644
index 0000000..f8bd938
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/6.ogg
new file mode 100644
index 0000000..d30c9e5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/7.ogg
new file mode 100644
index 0000000..610c469
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/8.ogg
new file mode 100644
index 0000000..ce834f6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/9.ogg
new file mode 100644
index 0000000..d452e95
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/activation.ogg
new file mode 100644
index 0000000..31eb4ac
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_pin.ogg
new file mode 100644
index 0000000..5943f96
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_reg.ogg
new file mode 100644
index 0000000..a860ec1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/language.json
new file mode 100644
index 0000000..cac29a4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/language.json
@@ -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": "讗转讞讜诇 讛诪讜讚诐 谞讻砖诇"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/upgrade.ogg
new file mode 100644
index 0000000..ff36a86
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/welcome.ogg
new file mode 100644
index 0000000..6594d37
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/wificonfig.ogg
new file mode 100644
index 0000000..6cf0597
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/he-IL/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/0.ogg
new file mode 100644
index 0000000..e531348
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/1.ogg
new file mode 100644
index 0000000..cb2f9d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/2.ogg
new file mode 100644
index 0000000..2244d45
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/3.ogg
new file mode 100644
index 0000000..63fde6d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/4.ogg
new file mode 100644
index 0000000..76caee9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/5.ogg
new file mode 100644
index 0000000..c96a386
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/6.ogg
new file mode 100644
index 0000000..88f7643
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/7.ogg
new file mode 100644
index 0000000..3a997d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/8.ogg
new file mode 100644
index 0000000..56f125e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/9.ogg
new file mode 100644
index 0000000..25e64c0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/activation.ogg
new file mode 100644
index 0000000..aa262df
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_pin.ogg
new file mode 100644
index 0000000..00669fa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_reg.ogg
new file mode 100644
index 0000000..6194739
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/language.json
new file mode 100644
index 0000000..25af4eb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/language.json
@@ -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": "啶啶∴啶� 啶嗋ぐ啶傕き啷�啶曕ぐ啶� 啶掂た啶げ"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/upgrade.ogg
new file mode 100644
index 0000000..8de0693
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/welcome.ogg
new file mode 100644
index 0000000..3352a57
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/wificonfig.ogg
new file mode 100644
index 0000000..5a9853f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hi-IN/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/0.ogg
new file mode 100644
index 0000000..5ee48d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/1.ogg
new file mode 100644
index 0000000..13b1dbe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/2.ogg
new file mode 100644
index 0000000..8a9c764
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/3.ogg
new file mode 100644
index 0000000..18080eb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/4.ogg
new file mode 100644
index 0000000..0f33e69
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/5.ogg
new file mode 100644
index 0000000..e9bbedc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/6.ogg
new file mode 100644
index 0000000..10dff31
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/7.ogg
new file mode 100644
index 0000000..6e82da9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/8.ogg
new file mode 100644
index 0000000..cfac805
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/9.ogg
new file mode 100644
index 0000000..ed4edfd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/activation.ogg
new file mode 100644
index 0000000..221f102
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_pin.ogg
new file mode 100644
index 0000000..d3387bd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_reg.ogg
new file mode 100644
index 0000000..a732ee9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/language.json
new file mode 100644
index 0000000..25e5821
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/upgrade.ogg
new file mode 100644
index 0000000..0aa6134
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/welcome.ogg
new file mode 100644
index 0000000..8a41e8d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/wificonfig.ogg
new file mode 100644
index 0000000..8949622
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hr-HR/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/0.ogg
new file mode 100644
index 0000000..0fabaf1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/1.ogg
new file mode 100644
index 0000000..8187037
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/2.ogg
new file mode 100644
index 0000000..bf417f3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/3.ogg
new file mode 100644
index 0000000..78192eb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/4.ogg
new file mode 100644
index 0000000..e93ccbf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/5.ogg
new file mode 100644
index 0000000..d43909d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/6.ogg
new file mode 100644
index 0000000..17344e3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/7.ogg
new file mode 100644
index 0000000..5167c43
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/8.ogg
new file mode 100644
index 0000000..ba82c28
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/9.ogg
new file mode 100644
index 0000000..4c1e62a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/activation.ogg
new file mode 100644
index 0000000..7e85192
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_pin.ogg
new file mode 100644
index 0000000..fa7d637
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_reg.ogg
new file mode 100644
index 0000000..d8db153
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/language.json
new file mode 100644
index 0000000..e6594ef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/upgrade.ogg
new file mode 100644
index 0000000..d894582
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/welcome.ogg
new file mode 100644
index 0000000..51d257b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/wificonfig.ogg
new file mode 100644
index 0000000..c0e6119
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/hu-HU/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/0.ogg
new file mode 100644
index 0000000..1d8dea3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/1.ogg
new file mode 100644
index 0000000..cd8d7cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/2.ogg
new file mode 100644
index 0000000..69cdce8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/3.ogg
new file mode 100644
index 0000000..2730364
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/4.ogg
new file mode 100644
index 0000000..f26010f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/5.ogg
new file mode 100644
index 0000000..67b41f7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/6.ogg
new file mode 100644
index 0000000..48f0343
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/7.ogg
new file mode 100644
index 0000000..c56df9f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/8.ogg
new file mode 100644
index 0000000..cb9e15b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/9.ogg
new file mode 100644
index 0000000..e088c86
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/activation.ogg
new file mode 100644
index 0000000..a468767
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_pin.ogg
new file mode 100644
index 0000000..419ff8a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_reg.ogg
new file mode 100644
index 0000000..d756ddf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/language.json
new file mode 100644
index 0000000..012b1f5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/language.json
@@ -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": "锛宎kses 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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/upgrade.ogg
new file mode 100644
index 0000000..cc2557a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/welcome.ogg
new file mode 100644
index 0000000..57beac6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/wificonfig.ogg
new file mode 100644
index 0000000..477acdd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/id-ID/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/0.ogg
new file mode 100644
index 0000000..ae426e3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/1.ogg
new file mode 100644
index 0000000..7c97b52
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/2.ogg
new file mode 100644
index 0000000..3d7995a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/3.ogg
new file mode 100644
index 0000000..b22ad1e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/4.ogg
new file mode 100644
index 0000000..ef01a46
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/5.ogg
new file mode 100644
index 0000000..d240b88
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/6.ogg
new file mode 100644
index 0000000..c52a2d7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/7.ogg
new file mode 100644
index 0000000..dce632c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/8.ogg
new file mode 100644
index 0000000..acd19e8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/9.ogg
new file mode 100644
index 0000000..807c226
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/activation.ogg
new file mode 100644
index 0000000..6381325
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_pin.ogg
new file mode 100644
index 0000000..3fb7107
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_reg.ogg
new file mode 100644
index 0000000..3bbe56e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/language.json
new file mode 100644
index 0000000..f67c7ea
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/language.json
@@ -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": "锛宎ccedi 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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/upgrade.ogg
new file mode 100644
index 0000000..79c0048
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/welcome.ogg
new file mode 100644
index 0000000..702def3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/wificonfig.ogg
new file mode 100644
index 0000000..7aae624
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/it-IT/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/0.ogg
new file mode 100644
index 0000000..5b24d6a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/1.ogg
new file mode 100644
index 0000000..e31933d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/2.ogg
new file mode 100644
index 0000000..f75a934
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/3.ogg
new file mode 100644
index 0000000..e414b59
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/4.ogg
new file mode 100644
index 0000000..a977c97
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/5.ogg
new file mode 100644
index 0000000..52ccf1b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/6.ogg
new file mode 100644
index 0000000..361f2e6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/7.ogg
new file mode 100644
index 0000000..e98be57
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/8.ogg
new file mode 100644
index 0000000..2f33874
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/9.ogg
new file mode 100644
index 0000000..c150a18
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/activation.ogg
new file mode 100644
index 0000000..995a489
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_pin.ogg
new file mode 100644
index 0000000..120bd6c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_reg.ogg
new file mode 100644
index 0000000..27ccc93
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/language.json
new file mode 100644
index 0000000..20dbe5d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/language.json
@@ -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": "銉囥儛銈ゃ偣銈掋偄銈儐銈c儥銉笺儓",
+ "BATTERY_LOW": "銉愩儍銉嗐儶銉笺亴灏戙仾銇忋仾銇c仸銇勩伨銇�",
+ "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": "銉€儑銉犮伄鍒濇湡鍖栥伀澶辨晽銇椼伨銇椼仧"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/upgrade.ogg
new file mode 100644
index 0000000..1994026
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/welcome.ogg
new file mode 100644
index 0000000..f2a4f17
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/wificonfig.ogg
new file mode 100644
index 0000000..16fbf94
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ja-JP/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/0.ogg
new file mode 100644
index 0000000..7c29fe5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/1.ogg
new file mode 100644
index 0000000..031d5d8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/2.ogg
new file mode 100644
index 0000000..7a12499
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/3.ogg
new file mode 100644
index 0000000..c3b7a52
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/4.ogg
new file mode 100644
index 0000000..79f515e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/5.ogg
new file mode 100644
index 0000000..2cc2c65
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/6.ogg
new file mode 100644
index 0000000..84653eb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/7.ogg
new file mode 100644
index 0000000..e3e6515
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/8.ogg
new file mode 100644
index 0000000..b9b7607
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/9.ogg
new file mode 100644
index 0000000..9060b21
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/activation.ogg
new file mode 100644
index 0000000..1af58af
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_pin.ogg
new file mode 100644
index 0000000..508e926
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_reg.ogg
new file mode 100644
index 0000000..68561fe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/language.json
new file mode 100644
index 0000000..4ccc066
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/language.json
@@ -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": "霌k姅 欷�...",
+ "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": "氇巰 齑堦赴頇� 鞁ろ尐"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/upgrade.ogg
new file mode 100644
index 0000000..0656a78
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/welcome.ogg
new file mode 100644
index 0000000..59621f4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/wificonfig.ogg
new file mode 100644
index 0000000..e1610af
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ko-KR/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/0.ogg
new file mode 100644
index 0000000..2a6fc02
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/1.ogg
new file mode 100644
index 0000000..01103be
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/2.ogg
new file mode 100644
index 0000000..52e8ac6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/3.ogg
new file mode 100644
index 0000000..16e3d76
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/4.ogg
new file mode 100644
index 0000000..3a3ec0a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/5.ogg
new file mode 100644
index 0000000..3dd6c06
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/6.ogg
new file mode 100644
index 0000000..a8e3aed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/7.ogg
new file mode 100644
index 0000000..d2fc950
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/8.ogg
new file mode 100644
index 0000000..20e94da
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/9.ogg
new file mode 100644
index 0000000..61f7b96
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/activation.ogg
new file mode 100644
index 0000000..8fea3ae
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_pin.ogg
new file mode 100644
index 0000000..8221763
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_reg.ogg
new file mode 100644
index 0000000..51547b6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/language.json
new file mode 100644
index 0000000..4797cfc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/upgrade.ogg
new file mode 100644
index 0000000..0bfcdcc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/welcome.ogg
new file mode 100644
index 0000000..7cd32e0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/wificonfig.ogg
new file mode 100644
index 0000000..a257b1d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ms-MY/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/0.ogg
new file mode 100644
index 0000000..cc14677
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/1.ogg
new file mode 100644
index 0000000..1d32c29
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/2.ogg
new file mode 100644
index 0000000..00d3536
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/3.ogg
new file mode 100644
index 0000000..d8d4711
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/4.ogg
new file mode 100644
index 0000000..eafc4ea
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/5.ogg
new file mode 100644
index 0000000..61d36bc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/6.ogg
new file mode 100644
index 0000000..cfa848e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/7.ogg
new file mode 100644
index 0000000..9391485
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/8.ogg
new file mode 100644
index 0000000..946b7ac
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/9.ogg
new file mode 100644
index 0000000..8311b58
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/activation.ogg
new file mode 100644
index 0000000..8d7be02
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_pin.ogg
new file mode 100644
index 0000000..1b6c81c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_reg.ogg
new file mode 100644
index 0000000..c505fff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/language.json
new file mode 100644
index 0000000..4540e63
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/upgrade.ogg
new file mode 100644
index 0000000..dc914d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/welcome.ogg
new file mode 100644
index 0000000..0e25dc9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/wificonfig.ogg
new file mode 100644
index 0000000..2d0b4a4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nb-NO/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/0.ogg
new file mode 100644
index 0000000..aba888e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/1.ogg
new file mode 100644
index 0000000..31b8273
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/2.ogg
new file mode 100644
index 0000000..a4e5457
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/3.ogg
new file mode 100644
index 0000000..95a59cd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/4.ogg
new file mode 100644
index 0000000..8d21287
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/5.ogg
new file mode 100644
index 0000000..307fbfb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/6.ogg
new file mode 100644
index 0000000..f03555b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/7.ogg
new file mode 100644
index 0000000..759a596
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/8.ogg
new file mode 100644
index 0000000..3d4183a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/9.ogg
new file mode 100644
index 0000000..af53ac4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/activation.ogg
new file mode 100644
index 0000000..273d65a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_pin.ogg
new file mode 100644
index 0000000..a2f83c4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_reg.ogg
new file mode 100644
index 0000000..1f61dfd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/language.json
new file mode 100644
index 0000000..1863b71
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/upgrade.ogg
new file mode 100644
index 0000000..8879792
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/welcome.ogg
new file mode 100644
index 0000000..6fcc6dd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/wificonfig.ogg
new file mode 100644
index 0000000..ada5f98
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/nl-NL/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/0.ogg
new file mode 100644
index 0000000..7351d96
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/1.ogg
new file mode 100644
index 0000000..342c9cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/2.ogg
new file mode 100644
index 0000000..8fa7c87
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/3.ogg
new file mode 100644
index 0000000..b656411
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/4.ogg
new file mode 100644
index 0000000..ea49088
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/5.ogg
new file mode 100644
index 0000000..8d4b3b9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/6.ogg
new file mode 100644
index 0000000..7c8fefd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/7.ogg
new file mode 100644
index 0000000..7139591
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/8.ogg
new file mode 100644
index 0000000..8e2dd56
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/9.ogg
new file mode 100644
index 0000000..b88ab87
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/activation.ogg
new file mode 100644
index 0000000..7c1ff9d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_pin.ogg
new file mode 100644
index 0000000..4d35f3c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_reg.ogg
new file mode 100644
index 0000000..35d1452
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/language.json
new file mode 100644
index 0000000..ba02fb9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/language.json
@@ -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": "锛宒ost臋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臋"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/upgrade.ogg
new file mode 100644
index 0000000..041eee8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/welcome.ogg
new file mode 100644
index 0000000..140db7d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/wificonfig.ogg
new file mode 100644
index 0000000..0d2222f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pl-PL/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/0.ogg
new file mode 100644
index 0000000..80c9f8b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/1.ogg
new file mode 100644
index 0000000..4299bc8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/2.ogg
new file mode 100644
index 0000000..fa67197
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/3.ogg
new file mode 100644
index 0000000..da2b131
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/4.ogg
new file mode 100644
index 0000000..2c593f6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/5.ogg
new file mode 100644
index 0000000..62d670d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/6.ogg
new file mode 100644
index 0000000..32ea27b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/7.ogg
new file mode 100644
index 0000000..c8542ed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/8.ogg
new file mode 100644
index 0000000..8e54738
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/9.ogg
new file mode 100644
index 0000000..e97c8fc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/activation.ogg
new file mode 100644
index 0000000..1c980ac
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_pin.ogg
new file mode 100644
index 0000000..ceb2223
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_reg.ogg
new file mode 100644
index 0000000..2fa4350
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/language.json
new file mode 100644
index 0000000..d2c125a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/language.json
@@ -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": "锛宎ceder 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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/upgrade.ogg
new file mode 100644
index 0000000..76bac60
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/welcome.ogg
new file mode 100644
index 0000000..74daf23
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/wificonfig.ogg
new file mode 100644
index 0000000..784939f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/pt-PT/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/0.ogg
new file mode 100644
index 0000000..67fd181
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/1.ogg
new file mode 100644
index 0000000..291be66
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/2.ogg
new file mode 100644
index 0000000..54caf95
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/3.ogg
new file mode 100644
index 0000000..b93d5c1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/4.ogg
new file mode 100644
index 0000000..3feff21
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/5.ogg
new file mode 100644
index 0000000..fe56930
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/6.ogg
new file mode 100644
index 0000000..ed8aee9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/7.ogg
new file mode 100644
index 0000000..9ce02c0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/8.ogg
new file mode 100644
index 0000000..c5d3184
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/9.ogg
new file mode 100644
index 0000000..1d156c7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/activation.ogg
new file mode 100644
index 0000000..d2d91fb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_pin.ogg
new file mode 100644
index 0000000..202000c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_reg.ogg
new file mode 100644
index 0000000..eafcabc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/language.json
new file mode 100644
index 0000000..65ac52b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/language.json
@@ -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": "锛宎ccesa葲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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/upgrade.ogg
new file mode 100644
index 0000000..f4b5618
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/welcome.ogg
new file mode 100644
index 0000000..673f3d8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/wificonfig.ogg
new file mode 100644
index 0000000..d536cdc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ro-RO/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/0.ogg
new file mode 100644
index 0000000..8b3d47b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/1.ogg
new file mode 100644
index 0000000..a9a3b23
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/2.ogg
new file mode 100644
index 0000000..d2f6b4d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/3.ogg
new file mode 100644
index 0000000..744ae4e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/4.ogg
new file mode 100644
index 0000000..f82ee5d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/5.ogg
new file mode 100644
index 0000000..9c976d7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/6.ogg
new file mode 100644
index 0000000..61d7192
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/7.ogg
new file mode 100644
index 0000000..bbb3cf3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/8.ogg
new file mode 100644
index 0000000..f73c590
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/9.ogg
new file mode 100644
index 0000000..3863b89
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/activation.ogg
new file mode 100644
index 0000000..c3ff8e4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_pin.ogg
new file mode 100644
index 0000000..1fea3f6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_reg.ogg
new file mode 100644
index 0000000..3d585f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/language.json
new file mode 100644
index 0000000..4fb278d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/language.json
@@ -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": "袨褕懈斜泻邪 懈薪懈褑懈邪谢懈蟹邪褑懈懈 屑芯写械屑邪"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/upgrade.ogg
new file mode 100644
index 0000000..69fbbdc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/welcome.ogg
new file mode 100644
index 0000000..bb5f05c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/wificonfig.ogg
new file mode 100644
index 0000000..0a26e2b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/ru-RU/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/0.ogg
new file mode 100644
index 0000000..adef2c4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/1.ogg
new file mode 100644
index 0000000..8974741
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/2.ogg
new file mode 100644
index 0000000..7336f93
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/3.ogg
new file mode 100644
index 0000000..ce9635e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/4.ogg
new file mode 100644
index 0000000..3f8026d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/5.ogg
new file mode 100644
index 0000000..dd50dd3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/6.ogg
new file mode 100644
index 0000000..8ef182a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/7.ogg
new file mode 100644
index 0000000..6c7f4d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/8.ogg
new file mode 100644
index 0000000..4533f50
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/9.ogg
new file mode 100644
index 0000000..045d8f6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/activation.ogg
new file mode 100644
index 0000000..4c780fa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_pin.ogg
new file mode 100644
index 0000000..ee2dd69
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_reg.ogg
new file mode 100644
index 0000000..0c9237f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/language.json
new file mode 100644
index 0000000..e7d029c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/upgrade.ogg
new file mode 100644
index 0000000..ce12dbf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/welcome.ogg
new file mode 100644
index 0000000..05b8cad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/wificonfig.ogg
new file mode 100644
index 0000000..89906c6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sk-SK/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/0.ogg
new file mode 100644
index 0000000..48baa9a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/1.ogg
new file mode 100644
index 0000000..fcd0f70
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/2.ogg
new file mode 100644
index 0000000..d5c6360
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/3.ogg
new file mode 100644
index 0000000..fa0774a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/4.ogg
new file mode 100644
index 0000000..95fa91f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/5.ogg
new file mode 100644
index 0000000..d06e1de
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/6.ogg
new file mode 100644
index 0000000..d3de0e1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/7.ogg
new file mode 100644
index 0000000..36b701f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/8.ogg
new file mode 100644
index 0000000..8dbdc07
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/9.ogg
new file mode 100644
index 0000000..944f922
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/activation.ogg
new file mode 100644
index 0000000..73d10f3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_pin.ogg
new file mode 100644
index 0000000..dc692f0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_reg.ogg
new file mode 100644
index 0000000..e0ca35a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/language.json
new file mode 100644
index 0000000..8e5a45b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/upgrade.ogg
new file mode 100644
index 0000000..9c307df
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/welcome.ogg
new file mode 100644
index 0000000..bab8476
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/wificonfig.ogg
new file mode 100644
index 0000000..e29f077
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sl-SI/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sr-RS/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/sr-RS/language.json
new file mode 100644
index 0000000..172d3fe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sr-RS/language.json
@@ -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": "袠薪懈褑懈褬邪谢懈蟹邪褑懈褬邪 屑芯写械屑邪 薪懈褬械 褍褋锌械谢邪"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/0.ogg
new file mode 100644
index 0000000..af94e5b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/1.ogg
new file mode 100644
index 0000000..3246d37
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/2.ogg
new file mode 100644
index 0000000..db2a251
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/3.ogg
new file mode 100644
index 0000000..5bfb237
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/4.ogg
new file mode 100644
index 0000000..4ebccba
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/5.ogg
new file mode 100644
index 0000000..d45e216
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/6.ogg
new file mode 100644
index 0000000..3da60f5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/7.ogg
new file mode 100644
index 0000000..85ddfab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/8.ogg
new file mode 100644
index 0000000..318735f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/9.ogg
new file mode 100644
index 0000000..0f60d53
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/activation.ogg
new file mode 100644
index 0000000..08a507d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_pin.ogg
new file mode 100644
index 0000000..c3772c9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_reg.ogg
new file mode 100644
index 0000000..07fc197
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/language.json
new file mode 100644
index 0000000..d85fe5b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/language.json
@@ -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"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/upgrade.ogg
new file mode 100644
index 0000000..db4d02d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/welcome.ogg
new file mode 100644
index 0000000..9407896
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/wificonfig.ogg
new file mode 100644
index 0000000..f232108
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/sv-SE/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/0.ogg
new file mode 100644
index 0000000..15e2544
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/1.ogg
new file mode 100644
index 0000000..87753b3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/2.ogg
new file mode 100644
index 0000000..057a6f3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/3.ogg
new file mode 100644
index 0000000..104d51b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/4.ogg
new file mode 100644
index 0000000..a35befc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/5.ogg
new file mode 100644
index 0000000..8eb3b5b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/6.ogg
new file mode 100644
index 0000000..b8cbbe0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/7.ogg
new file mode 100644
index 0000000..e93c9a4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/8.ogg
new file mode 100644
index 0000000..becde0e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/9.ogg
new file mode 100644
index 0000000..319ef67
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/activation.ogg
new file mode 100644
index 0000000..983e4d8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_pin.ogg
new file mode 100644
index 0000000..059cc8f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_reg.ogg
new file mode 100644
index 0000000..042c47f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/language.json
new file mode 100644
index 0000000..6c05468
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/language.json
@@ -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": "喔佮覆喔`箑喔`复喙堗浮喔曕箟喔權箓喔∴箑喔斷箛喔∴弗喙夃浮喙�喔弗喔�"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/upgrade.ogg
new file mode 100644
index 0000000..e024638
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/welcome.ogg
new file mode 100644
index 0000000..0b32b80
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/wificonfig.ogg
new file mode 100644
index 0000000..984d217
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/th-TH/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/0.ogg
new file mode 100644
index 0000000..9ab9285
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/1.ogg
new file mode 100644
index 0000000..131d774
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/2.ogg
new file mode 100644
index 0000000..5e80f6e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/3.ogg
new file mode 100644
index 0000000..bdc0550
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/4.ogg
new file mode 100644
index 0000000..fd39d2d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/5.ogg
new file mode 100644
index 0000000..30d093e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/6.ogg
new file mode 100644
index 0000000..95a6f6d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/7.ogg
new file mode 100644
index 0000000..9ba5ebc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/8.ogg
new file mode 100644
index 0000000..9a4d8c8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/9.ogg
new file mode 100644
index 0000000..af30078
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/activation.ogg
new file mode 100644
index 0000000..f8bf34e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_pin.ogg
new file mode 100644
index 0000000..6e5831e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_reg.ogg
new file mode 100644
index 0000000..91fc7ff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/language.json
new file mode 100644
index 0000000..dacfa76
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/language.json
@@ -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": "锛宼aray谋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谋"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/upgrade.ogg
new file mode 100644
index 0000000..57215bd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/welcome.ogg
new file mode 100644
index 0000000..fedd21c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/wificonfig.ogg
new file mode 100644
index 0000000..c898adc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/tr-TR/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/0.ogg
new file mode 100644
index 0000000..f4ea62c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/1.ogg
new file mode 100644
index 0000000..94bfed5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/2.ogg
new file mode 100644
index 0000000..e6226d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/3.ogg
new file mode 100644
index 0000000..50f7faa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/4.ogg
new file mode 100644
index 0000000..d3f9d7f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/5.ogg
new file mode 100644
index 0000000..712b0a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/6.ogg
new file mode 100644
index 0000000..7a01d5f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/7.ogg
new file mode 100644
index 0000000..4f3d5b2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/8.ogg
new file mode 100644
index 0000000..9577bc7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/9.ogg
new file mode 100644
index 0000000..dbaf9d6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/activation.ogg
new file mode 100644
index 0000000..446e020
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_pin.ogg
new file mode 100644
index 0000000..dd7d402
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_reg.ogg
new file mode 100644
index 0000000..ffe1a4c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/language.json
new file mode 100644
index 0000000..3f07c2a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/language.json
@@ -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": "袩芯屑懈谢泻邪 褨薪褨褑褨邪谢褨蟹邪褑褨褩 屑芯写械屑褍"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/upgrade.ogg
new file mode 100644
index 0000000..5a41be8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/welcome.ogg
new file mode 100644
index 0000000..e04ca00
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/wificonfig.ogg
new file mode 100644
index 0000000..c1d5d90
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/uk-UA/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/0.ogg
new file mode 100644
index 0000000..c6c0418
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/1.ogg
new file mode 100644
index 0000000..4774b06
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/2.ogg
new file mode 100644
index 0000000..592e9a4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/3.ogg
new file mode 100644
index 0000000..6825973
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/4.ogg
new file mode 100644
index 0000000..983e48a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/5.ogg
new file mode 100644
index 0000000..fd950a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/6.ogg
new file mode 100644
index 0000000..8158cfc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/7.ogg
new file mode 100644
index 0000000..ca03b3e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/8.ogg
new file mode 100644
index 0000000..d359674
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/9.ogg
new file mode 100644
index 0000000..6c6dd0b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/activation.ogg
new file mode 100644
index 0000000..a3871b8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_pin.ogg
new file mode 100644
index 0000000..16e42a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_reg.ogg
new file mode 100644
index 0000000..00b6bab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/language.json
new file mode 100644
index 0000000..ece31d3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/language.json
@@ -0,0 +1,59 @@
+{
+ "language": {
+ "type": "vi-VN"
+ },
+ "strings": {
+ "WARNING": "C岷h b谩o",
+ "INFO": "Th么ng tin",
+ "ERROR": "L峄梚",
+ "VERSION": "Phi锚n b岷 ",
+ "LOADING_PROTOCOL": "膼ang 膽膬ng nh岷璸...",
+ "INITIALIZING": "膼ang kh峄焛 t岷...",
+ "PIN_ERROR": "Vui l貌ng c岷痬 th岷� SIM",
+ "REG_ERROR": "Kh么ng th峄� truy c岷璸 m岷g, vui l貌ng ki峄僲 tra tr岷g th谩i th岷� SIM",
+ "DETECTING_MODULE": "膼ang ph谩t hi峄噉 module...",
+ "REGISTERING_NETWORK": "膼ang ch峄� m岷g...",
+ "CHECKING_NEW_VERSION": "膼ang ki峄僲 tra phi锚n b岷 m峄沬...",
+ "CHECK_NEW_VERSION_FAILED": "Ki峄僲 tra phi锚n b岷 m峄沬 th岷 b岷, s岷� th峄� l岷 sau %d gi芒y: %s",
+ "SWITCH_TO_WIFI_NETWORK": "膼ang chuy峄僴 sang Wi-Fi...",
+ "SWITCH_TO_4G_NETWORK": "膼ang chuy峄僴 sang 4G...",
+ "STANDBY": "Ch峄�",
+ "CONNECT_TO": "K岷縯 n峄慽 膽岷縩 ",
+ "CONNECTING": "膼ang k岷縯 n峄慽...",
+ "CONNECTION_SUCCESSFUL": "K岷縯 n峄慽 th脿nh c么ng",
+ "CONNECTED_TO": "膼茫 k岷縯 n峄慽 膽岷縩 ",
+ "LISTENING": "膼ang l岷痭g nghe...",
+ "SPEAKING": "膼ang n贸i...",
+ "SERVER_NOT_FOUND": "膼ang t矛m d峄媍h v峄� kh岷� d峄g",
+ "SERVER_NOT_CONNECTED": "Kh么ng th峄� k岷縯 n峄慽 膽岷縩 d峄媍h v峄�, vui l貌ng th峄� l岷 sau",
+ "SERVER_TIMEOUT": "H岷縯 th峄漣 gian ch峄� ph岷 h峄搃",
+ "SERVER_ERROR": "G峄璱 th岷 b岷, vui l貌ng ki峄僲 tra m岷g",
+ "CONNECT_TO_HOTSPOT": "膼i峄僲 ph谩t s贸ng: ",
+ "ACCESS_VIA_BROWSER": " URL c岷 h矛nh: ",
+ "WIFI_CONFIG_MODE": "Ch岷� 膽峄� c岷 h矛nh Wi-Fi",
+ "ENTERING_WIFI_CONFIG_MODE": "膼ang v脿o ch岷� 膽峄� c岷 h矛nh Wi-Fi...",
+ "SCANNING_WIFI": "膼ang qu茅t Wi-Fi...",
+ "NEW_VERSION": "Phi锚n b岷 m峄沬 ",
+ "OTA_UPGRADE": "N芒ng c岷 OTA",
+ "UPGRADING": "H峄� th峄憂g 膽ang n芒ng c岷...",
+ "UPGRADE_FAILED": "N芒ng c岷 th岷 b岷",
+ "ACTIVATION": "K铆ch ho岷",
+ "BATTERY_LOW": "Pin y岷縰",
+ "BATTERY_CHARGING": "膼ang s岷",
+ "BATTERY_FULL": "Pin 膽岷",
+ "BATTERY_NEED_CHARGE": "Pin y岷縰, vui l貌ng s岷",
+ "VOLUME": "脗m l瓢峄g ",
+ "MUTED": "T岷痶 ti岷縩g",
+ "MAX_VOLUME": "脗m l瓢峄g t峄慽 膽a",
+ "RTC_MODE_OFF": "T岷痶 AEC",
+ "RTC_MODE_ON": "B岷璽 AEC",
+ "DOWNLOAD_ASSETS_FAILED": "T岷 xu峄憂g t脿i nguy锚n th岷 b岷",
+ "LOADING_ASSETS": "膼ang t岷 t脿i nguy锚n...",
+ "PLEASE_WAIT": "Vui l貌ng 膽峄...",
+ "FOUND_NEW_ASSETS": "T矛m th岷 t脿i nguy锚n m峄沬: %s",
+ "HELLO_MY_FRIEND": "Xin ch脿o, b岷 c峄 t么i!",
+ "FLIGHT_MODE_OFF": "Ch岷� 膽峄� m谩y bay 膽ang t岷痶",
+ "FLIGHT_MODE_ON": "Ch岷� 膽峄� m谩y bay 膽ang b岷璽",
+ "MODEM_INIT_ERROR": "Kh峄焛 t岷 modem th岷 b岷"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/upgrade.ogg
new file mode 100644
index 0000000..1d41e98
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/welcome.ogg
new file mode 100644
index 0000000..ae12cc3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/wificonfig.ogg
new file mode 100644
index 0000000..08c3cb5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/vi-VN/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/0.ogg
new file mode 100644
index 0000000..0cb1247
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/1.ogg
new file mode 100644
index 0000000..858af34
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/2.ogg
new file mode 100644
index 0000000..72f53aa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/3.ogg
new file mode 100644
index 0000000..848af11
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/4.ogg
new file mode 100644
index 0000000..39b3eee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/5.ogg
new file mode 100644
index 0000000..9230358
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/6.ogg
new file mode 100644
index 0000000..9ecb574
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/7.ogg
new file mode 100644
index 0000000..6348799
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/8.ogg
new file mode 100644
index 0000000..67fc7a9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/9.ogg
new file mode 100644
index 0000000..a7769d9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/activation.ogg
new file mode 100644
index 0000000..33291af
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_pin.ogg
new file mode 100644
index 0000000..e455244
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_reg.ogg
new file mode 100644
index 0000000..a3456f1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/language.json
new file mode 100644
index 0000000..a21f51a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/language.json
@@ -0,0 +1,59 @@
+{
+ "language": {
+ "type": "zh-CN"
+ },
+ "strings": {
+ "WARNING": "璀﹀憡",
+ "INFO": "淇℃伅",
+ "ERROR": "閿欒",
+ "VERSION": "鐗堟湰 ",
+ "LOADING_PROTOCOL": "鐧诲綍鏈嶅姟鍣�...",
+ "INITIALIZING": "姝e湪鍒濆鍖�...",
+ "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": "姝e湪瀵绘壘鍙敤鏈嶅姟",
+ "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": "姝e湪鍗囩骇绯荤粺...",
+ "UPGRADE_FAILED": "鍗囩骇澶辫触",
+ "ACTIVATION": "婵�娲昏澶�",
+ "BATTERY_LOW": "鐢甸噺涓嶈冻",
+ "BATTERY_CHARGING": "姝e湪鍏呯數",
+ "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": "椋炶妯″紡宸插紑鍚�"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/upgrade.ogg
new file mode 100644
index 0000000..1feb87b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/welcome.ogg
new file mode 100644
index 0000000..b2eeb4f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/wificonfig.ogg
new file mode 100644
index 0000000..7ef88e2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-CN/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/0.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/0.ogg
new file mode 100644
index 0000000..0cb1247
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/0.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/1.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/1.ogg
new file mode 100644
index 0000000..858af34
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/1.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/2.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/2.ogg
new file mode 100644
index 0000000..72f53aa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/2.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/3.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/3.ogg
new file mode 100644
index 0000000..848af11
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/3.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/4.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/4.ogg
new file mode 100644
index 0000000..39b3eee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/4.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/5.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/5.ogg
new file mode 100644
index 0000000..9230358
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/5.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/6.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/6.ogg
new file mode 100644
index 0000000..9ecb574
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/6.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/7.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/7.ogg
new file mode 100644
index 0000000..6348799
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/7.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/8.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/8.ogg
new file mode 100644
index 0000000..67fc7a9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/8.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/9.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/9.ogg
new file mode 100644
index 0000000..a7769d9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/9.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/activation.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/activation.ogg
new file mode 100644
index 0000000..33291af
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/activation.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_pin.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_pin.ogg
new file mode 100644
index 0000000..e455244
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_pin.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_reg.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_reg.ogg
new file mode 100644
index 0000000..a3456f1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/err_reg.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/language.json b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/language.json
new file mode 100644
index 0000000..bbabcd9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/language.json
@@ -0,0 +1,59 @@
+{
+ "language": {
+ "type": "zh-TW"
+ },
+ "strings": {
+ "WARNING": "璀﹀憡",
+ "INFO": "璩囪▕",
+ "ERROR": "閷",
+ "VERSION": "鐗堟湰 ",
+ "LOADING_PROTOCOL": "鐧诲叆浼烘湇鍣�...",
+ "INITIALIZING": "姝e湪鍒濆鍖�...",
+ "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": "閫f帴 ",
+ "CONNECTING": "閫f帴涓�...",
+ "CONNECTED_TO": "宸查�f帴 ",
+ "LISTENING": "鑱嗚伣涓�...",
+ "SPEAKING": "瑾┍涓�...",
+ "SERVER_NOT_FOUND": "姝e湪灏嬫壘鍙敤鏈嶅嫏",
+ "SERVER_NOT_CONNECTED": "鐒℃硶閫f帴鏈嶅嫏锛岃珛绋嶅緦鍐嶈│",
+ "SERVER_TIMEOUT": "绛夊緟闊挎噳瓒呮檪",
+ "SERVER_ERROR": "鐧奸�佸け鏁楋紝璜嬫鏌ョ恫绲�",
+ "CONNECT_TO_HOTSPOT": "鎵嬫閫f帴WiFi ",
+ "ACCESS_VIA_BROWSER": "锛岀�忚鍣ㄨí鍟� ",
+ "WIFI_CONFIG_MODE": "缍茶矾瑷畾妯″紡",
+ "ENTERING_WIFI_CONFIG_MODE": "姝e湪瑷畾缍茶矾...",
+ "SCANNING_WIFI": "鎺冩弿 Wi-Fi...",
+ "NEW_VERSION": "鏂扮増鏈� ",
+ "OTA_UPGRADE": "OTA 鍗囩礆",
+ "UPGRADING": "姝e湪鍗囩礆绯荤当...",
+ "UPGRADE_FAILED": "鍗囩礆澶辨晽",
+ "ACTIVATION": "鍟熺敤瑷倷",
+ "BATTERY_LOW": "闆婚噺涓嶈冻",
+ "BATTERY_CHARGING": "姝e湪鍏呴浕",
+ "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": "閫g窔鎴愬姛",
+ "FLIGHT_MODE_OFF": "椋涜埅妯″紡宸查棞闁�",
+ "FLIGHT_MODE_ON": "椋涜埅妯″紡宸查枊鍟�",
+ "MODEM_INIT_ERROR": "妯$祫鍒濆鍖栧け鏁�"
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/upgrade.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/upgrade.ogg
new file mode 100644
index 0000000..1feb87b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/upgrade.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/welcome.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/welcome.ogg
new file mode 100644
index 0000000..b2eeb4f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/welcome.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/wificonfig.ogg b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/wificonfig.ogg
new file mode 100644
index 0000000..7ef88e2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/assets/locales/zh-TW/wificonfig.ogg
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/main/audio/README.md b/xiaozhi-esp32-2.2.6/main/audio/README.md
new file mode 100644
index 0000000..6159f9a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/README.md
@@ -0,0 +1,88 @@
+# Audio Service Architecture
+
+The audio service is a core component responsible for managing all audio-related functionalities, including capturing audio from the microphone, processing it, encoding/decoding, and playing back audio through the speaker. It is designed to be modular and efficient, running its main operations in dedicated FreeRTOS tasks to ensure real-time performance.
+
+## Key Components
+
+- **`AudioService`**: The central orchestrator. It initializes and manages all other audio components, tasks, and data queues.
+- **`AudioCodec`**: A hardware abstraction layer (HAL) for the physical audio codec chip. It handles the raw I2S communication for audio input and output.
+- **`AudioProcessor`**: Performs real-time audio processing on the microphone input stream. This typically includes Acoustic Echo Cancellation (AEC), noise suppression, and Voice Activity Detection (VAD). `AfeAudioProcessor` is the default implementation, utilizing the ESP-ADF Audio Front-End.
+- **`WakeWord`**: Detects keywords (e.g., "浣犲ソ锛屽皬鏅�", "Hi, ESP") from the audio stream. It runs independently from the main audio processor until a wake word is detected.
+- **`OpusEncoderWrapper` / `OpusDecoderWrapper`**: Manages the encoding of PCM audio to the Opus format and decoding Opus packets back to PCM. Opus is used for its high compression and low latency, making it ideal for voice streaming.
+- **`OpusResampler`**: A utility to convert audio streams between different sample rates (e.g., resampling from the codec's native sample rate to the required 16kHz for processing).
+
+## Threading Model
+
+The service operates on three primary tasks to handle the different stages of the audio pipeline concurrently:
+
+1. **`AudioInputTask`**: Solely responsible for reading raw PCM data from the `AudioCodec`. It then feeds this data to either the `WakeWord` engine or the `AudioProcessor` based on the current state.
+2. **`AudioOutputTask`**: Responsible for playing audio. It retrieves decoded PCM data from the `audio_playback_queue_` and sends it to the `AudioCodec` to be played on the speaker.
+3. **`OpusCodecTask`**: A worker task that handles both encoding and decoding. It fetches raw audio from `audio_encode_queue_`, encodes it into Opus packets, and places them in the `audio_send_queue_`. Concurrently, it fetches Opus packets from `audio_decode_queue_`, decodes them into PCM, and places the result in the `audio_playback_queue_`.
+
+## Data Flow
+
+There are two primary data flows: audio input (uplink) and audio output (downlink).
+
+### 1. Audio Input (Uplink) Flow
+
+This flow captures audio from the microphone, processes it, encodes it, and prepares it for sending to a server.
+
+```mermaid
+graph TD
+ subgraph Device
+ Mic[("Microphone")] -->|I2S| Codec(AudioCodec)
+
+ subgraph AudioInputTask
+ Codec -->|Raw PCM| Read(ReadAudioData)
+ Read -->|16kHz PCM| Processor(AudioProcessor)
+ end
+
+ subgraph OpusCodecTask
+ Processor -->|Clean PCM| EncodeQueue(audio_encode_queue_)
+ EncodeQueue --> Encoder(OpusEncoder)
+ Encoder -->|Opus Packet| SendQueue(audio_send_queue_)
+ end
+
+ SendQueue --> |"PopPacketFromSendQueue()"| App(Application Layer)
+ end
+
+ App -->|Network| Server((Cloud Server))
+```
+
+- The `AudioInputTask` continuously reads raw PCM data from the `AudioCodec`.
+- This data is fed into an `AudioProcessor` for cleaning (AEC, VAD).
+- The processed PCM data is pushed into the `audio_encode_queue_`.
+- The `OpusCodecTask` picks up the PCM data, encodes it into Opus format, and pushes the resulting packet to the `audio_send_queue_`.
+- The application can then retrieve these Opus packets and send them over the network.
+
+### 2. Audio Output (Downlink) Flow
+
+This flow receives encoded audio data, decodes it, and plays it on the speaker.
+
+```mermaid
+graph TD
+ Server((Cloud Server)) -->|Network| App(Application Layer)
+
+ subgraph Device
+ App -->|"PushPacketToDecodeQueue()"| DecodeQueue(audio_decode_queue_)
+
+ subgraph OpusCodecTask
+ DecodeQueue -->|Opus Packet| Decoder(OpusDecoder)
+ Decoder -->|PCM| PlaybackQueue(audio_playback_queue_)
+ end
+
+ subgraph AudioOutputTask
+ PlaybackQueue -->|PCM| Codec(AudioCodec)
+ end
+
+ Codec -->|I2S| Speaker[("Speaker")]
+ end
+```
+
+- The application receives Opus packets from the network and pushes them into the `audio_decode_queue_`.
+- The `OpusCodecTask` retrieves these packets, decodes them back into PCM data, and pushes the data to the `audio_playback_queue_`.
+- The `AudioOutputTask` takes the PCM data from the queue and sends it to the `AudioCodec` for playback.
+
+## Power Management
+
+To conserve energy, the audio codec's input (ADC) and output (DAC) channels are automatically disabled after a period of inactivity (`AUDIO_POWER_TIMEOUT_MS`). A timer (`audio_power_timer_`) periodically checks for activity and manages the power state. The channels are automatically re-enabled when new audio needs to be captured or played.
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/audio_codec.cc
new file mode 100644
index 0000000..03e2baa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/audio_codec.cc
@@ -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");
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/audio_codec.h
new file mode 100644
index 0000000..819e646
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/audio_codec.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/audio/audio_processor.h b/xiaozhi-esp32-2.2.6/main/audio/audio_processor.h
new file mode 100644
index 0000000..543c7ae
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/audio_processor.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/audio/audio_service.cc b/xiaozhi-esp32-2.2.6/main/audio/audio_service.cc
new file mode 100644
index 0000000..350ac58
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/audio_service.cc
@@ -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
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/audio_service.h b/xiaozhi-esp32-2.2.6/main/audio/audio_service.h
new file mode 100644
index 0000000..99db546
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/audio_service.h
@@ -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
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.cc
new file mode 100644
index 0000000..8bbe07d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.cc
@@ -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; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.h
new file mode 100644
index 0000000..cb7d389
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/box_audio_codec.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.cc
new file mode 100644
index 0000000..5f646b3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.cc
@@ -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;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.h
new file mode 100644
index 0000000..158f140
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/dummy_audio_codec.h
@@ -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
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.cc
new file mode 100644
index 0000000..12ecd62
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.cc
@@ -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; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.h
new file mode 100644
index 0000000..dd0e639
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8311_audio_codec.h
@@ -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
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.cc
new file mode 100644
index 0000000..699e777
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.cc
@@ -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; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.h
new file mode 100644
index 0000000..7533c22
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8374_audio_codec.h
@@ -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
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.cc
new file mode 100644
index 0000000..821db78
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.cc
@@ -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; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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, ®_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;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.h
new file mode 100644
index 0000000..316dfce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8388_audio_codec.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.cc
new file mode 100644
index 0000000..48820f3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.cc
@@ -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; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.h
new file mode 100644
index 0000000..b55b427
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/es8389_audio_codec.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.cc
new file mode 100644
index 0000000..791b0f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.cc
@@ -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 瑙h皟鍚庣殑鏁版嵁浣嶅涓� 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;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.h b/xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.h
new file mode 100644
index 0000000..dec803f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/codecs/no_audio_codec.h
@@ -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
diff --git a/xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.cc b/xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.cc
new file mode 100644
index 0000000..6b60876
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.cc
@@ -0,0 +1,311 @@
+#include "ogg_demuxer.h"
+#include "esp_log.h"
+
+#define TAG "OggDemuxer"
+
+/// @brief 閲嶇疆瑙e皝鍣�
+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, "鏃犳晥鐨凮gg鐗堟湰: %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鍜宲acket_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 --git a/xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.h b/xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.h
new file mode 100644
index 0000000..1af4456
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/demuxer/ogg_demuxer.h
@@ -0,0 +1,63 @@
+#ifndef OGG_DEMUXER_H_
+#define OGG_DEMUXER_H_
+
+#include <functional>
+#include <cstdint>
+#include <cstring>
+#include <vector>
+
+class OggDemuxer {
+private:
+ enum ParseState : int8_t {
+ FIND_PAGE,
+ PARSE_HEADER,
+ PARSE_SEGMENTS,
+ PARSE_DATA
+ };
+
+ struct Opus_t {
+ bool head_seen{false};
+ bool tags_seen{false};
+ int sample_rate{48000};
+ };
+
+
+ // 浣跨敤鍥哄畾澶у皬鐨勭紦鍐插尯閬垮厤鍔ㄦ�佸垎閰�
+ struct context_t {
+ bool packet_continued{false}; // 褰撳墠鍖呮槸鍚﹁法澶氫釜娈�
+ uint8_t header[27]; // Ogg椤靛ご
+ uint8_t seg_table[255]; // 褰撳墠瀛樺偍鐨勬琛�
+ uint8_t packet_buf[8192]; // 8KB鍖呯紦鍐插尯
+ size_t packet_len = 0; // 缂撳啿鍖轰腑绱鐨勬暟鎹暱搴�
+ size_t seg_count = 0; // 褰撳墠椤垫鏁�
+ size_t seg_index = 0; // 褰撳墠澶勭悊鐨勬绱㈠紩
+ size_t data_offset = 0; // 瑙f瀽褰撳墠闃舵宸茶鍙栫殑瀛楄妭鏁�
+ size_t bytes_needed = 0; // 瑙f瀽褰撳墠瀛楁杩橀渶瑕佽鍙栫殑瀛楄妭鏁�
+ size_t seg_remaining = 0; // 褰撳墠娈靛墿浣欓渶瑕佽鍙栫殑瀛楄妭鏁�
+ size_t body_size = 0; // 鏁版嵁浣撴�诲ぇ灏�
+ size_t body_offset = 0; // 鏁版嵁浣撳凡璇诲彇鐨勫瓧鑺傛暟
+ };
+
+public:
+ OggDemuxer() {
+ Reset();
+ }
+
+ void Reset();
+
+ size_t Process(const uint8_t* data, size_t size);
+
+ /// @brief 璁剧疆瑙e皝瑁呭畬姣曞悗鍥炶皟澶勭悊鍑芥暟
+ /// @param on_demuxer_finished
+ void OnDemuxerFinished(std::function<void(const uint8_t* data, int sample_rate, size_t len)> on_demuxer_finished) {
+ on_demuxer_finished_ = on_demuxer_finished;
+ }
+private:
+
+ ParseState state_ = ParseState::FIND_PAGE;
+ context_t ctx_;
+ Opus_t opus_info_;
+ std::function<void(const uint8_t*, int, size_t)> on_demuxer_finished_;
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.cc b/xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.cc
new file mode 100644
index 0000000..15e9cc5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.cc
@@ -0,0 +1,201 @@
+#include "afe_audio_processor.h"
+#include <esp_log.h>
+
+#define PROCESSOR_RUNNING 0x01
+
+#define TAG "AfeAudioProcessor"
+
+AfeAudioProcessor::AfeAudioProcessor()
+ : afe_data_(nullptr) {
+ event_group_ = xEventGroupCreate();
+}
+
+void AfeAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) {
+ codec_ = codec;
+ frame_samples_ = frame_duration_ms * 16000 / 1000;
+
+ // Pre-allocate output buffer capacity
+ output_buffer_.reserve(frame_samples_);
+
+ int ref_num = codec_->input_reference() ? 1 : 0;
+
+ std::string input_format;
+ for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
+ input_format.push_back('M');
+ }
+ for (int i = 0; i < ref_num; i++) {
+ input_format.push_back('R');
+ }
+
+ srmodel_list_t *models;
+ if (models_list == nullptr) {
+ models = esp_srmodel_init("model");
+ } else {
+ models = models_list;
+ }
+
+ char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL);
+ char* vad_model_name = esp_srmodel_filter(models, ESP_VADN_PREFIX, NULL);
+
+ afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF);
+ afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF;
+ afe_config->vad_mode = VAD_MODE_0;
+ afe_config->vad_min_noise_ms = 100;
+ if (vad_model_name != nullptr) {
+ afe_config->vad_model_name = vad_model_name;
+ }
+
+ if (ns_model_name != nullptr) {
+ afe_config->ns_init = true;
+ afe_config->ns_model_name = ns_model_name;
+ afe_config->afe_ns_mode = AFE_NS_MODE_NET;
+ } else {
+ afe_config->ns_init = false;
+ }
+
+ afe_config->agc_init = false;
+ afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
+
+#ifdef CONFIG_USE_DEVICE_AEC
+ afe_config->aec_init = true;
+ afe_config->vad_init = false;
+#else
+ afe_config->aec_init = false;
+ afe_config->vad_init = true;
+#endif
+
+ afe_iface_ = esp_afe_handle_from_config(afe_config);
+ afe_data_ = afe_iface_->create_from_config(afe_config);
+
+ xTaskCreate([](void* arg) {
+ auto this_ = (AfeAudioProcessor*)arg;
+ this_->AudioProcessorTask();
+ vTaskDelete(NULL);
+ }, "audio_communication", 4096, this, 3, NULL);
+}
+
+AfeAudioProcessor::~AfeAudioProcessor() {
+ if (afe_data_ != nullptr) {
+ afe_iface_->destroy(afe_data_);
+ }
+ vEventGroupDelete(event_group_);
+}
+
+size_t AfeAudioProcessor::GetFeedSize() {
+ if (afe_data_ == nullptr) {
+ return 0;
+ }
+ return afe_iface_->get_feed_chunksize(afe_data_);
+}
+
+void AfeAudioProcessor::Feed(std::vector<int16_t>&& data) {
+ if (afe_data_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ // Check running state inside lock to avoid TOCTOU race with Stop()
+ if (!IsRunning()) {
+ return;
+ }
+ input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
+ size_t chunk_size = afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
+ while (input_buffer_.size() >= chunk_size) {
+ afe_iface_->feed(afe_data_, input_buffer_.data());
+ input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunk_size);
+ }
+}
+
+void AfeAudioProcessor::Start() {
+ xEventGroupSetBits(event_group_, PROCESSOR_RUNNING);
+}
+
+void AfeAudioProcessor::Stop() {
+ xEventGroupClearBits(event_group_, PROCESSOR_RUNNING);
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ if (afe_data_ != nullptr) {
+ afe_iface_->reset_buffer(afe_data_);
+ }
+ input_buffer_.clear();
+}
+
+bool AfeAudioProcessor::IsRunning() {
+ return xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING;
+}
+
+void AfeAudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) {
+ output_callback_ = callback;
+}
+
+void AfeAudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
+ vad_state_change_callback_ = callback;
+}
+
+void AfeAudioProcessor::AudioProcessorTask() {
+ auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
+ auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
+ ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d",
+ feed_size, fetch_size);
+
+ while (true) {
+ xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY);
+
+ auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
+ if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) {
+ continue;
+ }
+ if (res == nullptr || res->ret_value == ESP_FAIL) {
+ if (res != nullptr) {
+ ESP_LOGI(TAG, "Error code: %d", res->ret_value);
+ }
+ continue;
+ }
+
+ // VAD state change
+ if (vad_state_change_callback_) {
+ if (res->vad_state == VAD_SPEECH && !is_speaking_) {
+ is_speaking_ = true;
+ vad_state_change_callback_(true);
+ } else if (res->vad_state == VAD_SILENCE && is_speaking_) {
+ is_speaking_ = false;
+ vad_state_change_callback_(false);
+ }
+ }
+
+ if (output_callback_) {
+ size_t samples = res->data_size / sizeof(int16_t);
+
+ // Add data to buffer
+ output_buffer_.insert(output_buffer_.end(), res->data, res->data + samples);
+
+ // Output complete frames when buffer has enough data
+ while (output_buffer_.size() >= frame_samples_) {
+ if (output_buffer_.size() == frame_samples_) {
+ // If buffer size equals frame size, move the entire buffer
+ output_callback_(std::move(output_buffer_));
+ output_buffer_.clear();
+ output_buffer_.reserve(frame_samples_);
+ } else {
+ // If buffer size exceeds frame size, copy one frame and remove it
+ output_callback_(std::vector<int16_t>(output_buffer_.begin(), output_buffer_.begin() + frame_samples_));
+ output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_);
+ }
+ }
+ }
+ }
+}
+
+void AfeAudioProcessor::EnableDeviceAec(bool enable) {
+ if (enable) {
+#if CONFIG_USE_DEVICE_AEC
+ afe_iface_->disable_vad(afe_data_);
+ afe_iface_->enable_aec(afe_data_);
+#else
+ ESP_LOGE(TAG, "Device AEC is not supported");
+#endif
+ } else {
+ afe_iface_->disable_aec(afe_data_);
+ afe_iface_->enable_vad(afe_data_);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.h b/xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.h
new file mode 100644
index 0000000..fe2f025
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/processors/afe_audio_processor.h
@@ -0,0 +1,48 @@
+#ifndef AFE_AUDIO_PROCESSOR_H
+#define AFE_AUDIO_PROCESSOR_H
+
+#include <esp_afe_sr_models.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/event_groups.h>
+
+#include <string>
+#include <vector>
+#include <functional>
+#include <mutex>
+
+#include "audio_processor.h"
+#include "audio_codec.h"
+
+class AfeAudioProcessor : public AudioProcessor {
+public:
+ AfeAudioProcessor();
+ ~AfeAudioProcessor();
+
+ void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override;
+ void Feed(std::vector<int16_t>&& data) override;
+ void Start() override;
+ void Stop() override;
+ bool IsRunning() override;
+ void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) override;
+ void OnVadStateChange(std::function<void(bool speaking)> callback) override;
+ size_t GetFeedSize() override;
+ void EnableDeviceAec(bool enable) override;
+
+private:
+ EventGroupHandle_t event_group_ = nullptr;
+ const esp_afe_sr_iface_t* afe_iface_ = nullptr;
+ esp_afe_sr_data_t* afe_data_ = nullptr;
+ std::function<void(std::vector<int16_t>&& data)> output_callback_;
+ std::function<void(bool speaking)> vad_state_change_callback_;
+ AudioCodec* codec_ = nullptr;
+ int frame_samples_ = 0;
+ bool is_speaking_ = false;
+ std::vector<int16_t> input_buffer_;
+ std::mutex input_buffer_mutex_;
+ std::vector<int16_t> output_buffer_;
+
+ void AudioProcessorTask();
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.cc b/xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.cc
new file mode 100644
index 0000000..630057c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.cc
@@ -0,0 +1,68 @@
+#include "audio_debugger.h"
+#include "sdkconfig.h"
+
+#if CONFIG_USE_AUDIO_DEBUGGER
+#include <esp_log.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <errno.h>
+#include <cstring>
+#include <string>
+#endif
+
+#define TAG "AudioDebugger"
+
+
+AudioDebugger::AudioDebugger() {
+#if CONFIG_USE_AUDIO_DEBUGGER
+ udp_sockfd_ = socket(AF_INET, SOCK_DGRAM, 0);
+ if (udp_sockfd_ >= 0) {
+ // 瑙f瀽閰嶇疆鐨勬湇鍔″櫒鍦板潃 "IP:PORT"
+ std::string server_addr = CONFIG_AUDIO_DEBUG_UDP_SERVER;
+ size_t colon_pos = server_addr.find(':');
+
+ if (colon_pos != std::string::npos) {
+ std::string ip = server_addr.substr(0, colon_pos);
+ int port = std::stoi(server_addr.substr(colon_pos + 1));
+
+ memset(&udp_server_addr_, 0, sizeof(udp_server_addr_));
+ udp_server_addr_.sin_family = AF_INET;
+ udp_server_addr_.sin_port = htons(port);
+ inet_pton(AF_INET, ip.c_str(), &udp_server_addr_.sin_addr);
+
+ ESP_LOGI(TAG, "Initialized server address: %s", CONFIG_AUDIO_DEBUG_UDP_SERVER);
+ } else {
+ ESP_LOGW(TAG, "Invalid server address: %s, should be IP:PORT", CONFIG_AUDIO_DEBUG_UDP_SERVER);
+ close(udp_sockfd_);
+ udp_sockfd_ = -1;
+ }
+ } else {
+ ESP_LOGW(TAG, "Failed to create UDP socket: %d", errno);
+ }
+#endif
+}
+
+AudioDebugger::~AudioDebugger() {
+#if CONFIG_USE_AUDIO_DEBUGGER
+ if (udp_sockfd_ >= 0) {
+ close(udp_sockfd_);
+ ESP_LOGI(TAG, "Closed UDP socket");
+ }
+#endif
+}
+
+void AudioDebugger::Feed(const std::vector<int16_t>& data) {
+#if CONFIG_USE_AUDIO_DEBUGGER
+ if (udp_sockfd_ >= 0) {
+ ssize_t sent = sendto(udp_sockfd_, data.data(), data.size() * sizeof(int16_t), 0,
+ (struct sockaddr*)&udp_server_addr_, sizeof(udp_server_addr_));
+ if (sent < 0) {
+ ESP_LOGW(TAG, "Failed to send audio data to %s: %d", CONFIG_AUDIO_DEBUG_UDP_SERVER, errno);
+ } else {
+ ESP_LOGD(TAG, "Sent %d bytes audio data to %s", sent, CONFIG_AUDIO_DEBUG_UDP_SERVER);
+ }
+ }
+#endif
+}
+
+
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.h b/xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.h
new file mode 100644
index 0000000..a81336c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/processors/audio_debugger.h
@@ -0,0 +1,22 @@
+#ifndef AUDIO_DEBUGGER_H
+#define AUDIO_DEBUGGER_H
+
+#include <vector>
+#include <cstdint>
+
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+class AudioDebugger {
+public:
+ AudioDebugger();
+ ~AudioDebugger();
+
+ void Feed(const std::vector<int16_t>& data);
+
+private:
+ int udp_sockfd_ = -1;
+ struct sockaddr_in udp_server_addr_;
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.cc b/xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.cc
new file mode 100644
index 0000000..90f997a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.cc
@@ -0,0 +1,71 @@
+#include "no_audio_processor.h"
+#include <esp_log.h>
+
+#define TAG "NoAudioProcessor"
+
+void NoAudioProcessor::Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) {
+ codec_ = codec;
+ frame_samples_ = frame_duration_ms * 16000 / 1000;
+ output_buffer_.reserve(frame_samples_);
+}
+
+void NoAudioProcessor::Feed(std::vector<int16_t>&& data) {
+ if (!is_running_ || !output_callback_) {
+ return;
+ }
+
+ // Convert stereo to mono if needed
+ if (codec_->input_channels() == 2) {
+ for (size_t i = 0, j = 0; i < data.size() / 2; ++i, j += 2) {
+ output_buffer_.push_back(data[j]);
+ }
+ } else {
+ output_buffer_.insert(output_buffer_.end(), data.begin(), data.end());
+ }
+
+ // Output complete frames when buffer has enough data
+ while (output_buffer_.size() >= (size_t)frame_samples_) {
+ if (output_buffer_.size() == (size_t)frame_samples_) {
+ output_callback_(std::move(output_buffer_));
+ output_buffer_.clear();
+ output_buffer_.reserve(frame_samples_);
+ } else {
+ output_callback_(std::vector<int16_t>(output_buffer_.begin(), output_buffer_.begin() + frame_samples_));
+ output_buffer_.erase(output_buffer_.begin(), output_buffer_.begin() + frame_samples_);
+ }
+ }
+}
+
+void NoAudioProcessor::Start() {
+ is_running_ = true;
+}
+
+void NoAudioProcessor::Stop() {
+ is_running_ = false;
+ output_buffer_.clear();
+}
+
+bool NoAudioProcessor::IsRunning() {
+ return is_running_;
+}
+
+void NoAudioProcessor::OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) {
+ output_callback_ = callback;
+}
+
+void NoAudioProcessor::OnVadStateChange(std::function<void(bool speaking)> callback) {
+ vad_state_change_callback_ = callback;
+}
+
+size_t NoAudioProcessor::GetFeedSize() {
+ if (!codec_) {
+ return 0;
+ }
+ return frame_samples_;
+}
+
+void NoAudioProcessor::EnableDeviceAec(bool enable) {
+ if (enable) {
+ ESP_LOGE(TAG, "Device AEC is not supported");
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.h b/xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.h
new file mode 100644
index 0000000..70ef1d1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/processors/no_audio_processor.h
@@ -0,0 +1,35 @@
+#ifndef DUMMY_AUDIO_PROCESSOR_H
+#define DUMMY_AUDIO_PROCESSOR_H
+
+#include <vector>
+#include <functional>
+#include <atomic>
+
+#include "audio_processor.h"
+#include "audio_codec.h"
+
+class NoAudioProcessor : public AudioProcessor {
+public:
+ NoAudioProcessor() = default;
+ ~NoAudioProcessor() = default;
+
+ void Initialize(AudioCodec* codec, int frame_duration_ms, srmodel_list_t* models_list) override;
+ void Feed(std::vector<int16_t>&& data) override;
+ void Start() override;
+ void Stop() override;
+ bool IsRunning() override;
+ void OnOutput(std::function<void(std::vector<int16_t>&& data)> callback) override;
+ void OnVadStateChange(std::function<void(bool speaking)> callback) override;
+ size_t GetFeedSize() override;
+ void EnableDeviceAec(bool enable) override;
+
+private:
+ AudioCodec* codec_ = nullptr;
+ int frame_samples_ = 0;
+ std::vector<int16_t> output_buffer_;
+ std::function<void(std::vector<int16_t>&& data)> output_callback_;
+ std::function<void(bool speaking)> vad_state_change_callback_;
+ std::atomic<bool> is_running_ = false;
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_word.h b/xiaozhi-esp32-2.2.6/main/audio/wake_word.h
new file mode 100644
index 0000000..9b8986a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_word.h
@@ -0,0 +1,26 @@
+#ifndef WAKE_WORD_H
+#define WAKE_WORD_H
+
+#include <string>
+#include <vector>
+#include <functional>
+
+#include <model_path.h>
+#include "audio_codec.h"
+
+class WakeWord {
+public:
+ virtual ~WakeWord() = default;
+
+ virtual bool Initialize(AudioCodec* codec, srmodel_list_t* models_list) = 0;
+ virtual void Feed(const std::vector<int16_t>& data) = 0;
+ virtual void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) = 0;
+ virtual void Start() = 0;
+ virtual void Stop() = 0;
+ virtual size_t GetFeedSize() = 0;
+ virtual void EncodeWakeWordData() = 0;
+ virtual bool GetWakeWordOpus(std::vector<uint8_t>& opus) = 0;
+ virtual const std::string& GetLastDetectedWakeWord() const = 0;
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.cc b/xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.cc
new file mode 100644
index 0000000..8fc5fef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.cc
@@ -0,0 +1,263 @@
+#include "afe_wake_word.h"
+#include "audio_service.h"
+#include <esp_log.h>
+#include <sstream>
+
+#define DETECTION_RUNNING_EVENT 1
+
+#define TAG "AfeWakeWord"
+
+AfeWakeWord::AfeWakeWord()
+ : afe_data_(nullptr),
+ wake_word_pcm_(),
+ wake_word_opus_() {
+
+ event_group_ = xEventGroupCreate();
+}
+
+AfeWakeWord::~AfeWakeWord() {
+ if (afe_data_ != nullptr) {
+ afe_iface_->destroy(afe_data_);
+ }
+
+ if (wake_word_encode_task_stack_ != nullptr) {
+ heap_caps_free(wake_word_encode_task_stack_);
+ }
+
+ if (wake_word_encode_task_buffer_ != nullptr) {
+ heap_caps_free(wake_word_encode_task_buffer_);
+ }
+
+ if (models_ != nullptr) {
+ esp_srmodel_deinit(models_);
+ }
+
+ vEventGroupDelete(event_group_);
+}
+
+bool AfeWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
+ codec_ = codec;
+ int ref_num = codec_->input_reference() ? 1 : 0;
+
+ if (models_list == nullptr) {
+ models_ = esp_srmodel_init("model");
+ } else {
+ models_ = models_list;
+ }
+
+ if (models_ == nullptr || models_->num == -1) {
+ ESP_LOGE(TAG, "Failed to initialize wakenet model");
+ return false;
+ }
+ for (int i = 0; i < models_->num; i++) {
+ ESP_LOGI(TAG, "Model %d: %s", i, models_->model_name[i]);
+ if (strstr(models_->model_name[i], ESP_WN_PREFIX) != NULL) {
+ wakenet_model_ = models_->model_name[i];
+ auto words = esp_srmodel_get_wake_words(models_, wakenet_model_);
+ // split by ";" to get all wake words
+ std::stringstream ss(words);
+ std::string word;
+ while (std::getline(ss, word, ';')) {
+ wake_words_.push_back(word);
+ }
+ }
+ }
+
+ std::string input_format;
+ for (int i = 0; i < codec_->input_channels() - ref_num; i++) {
+ input_format.push_back('M');
+ }
+ for (int i = 0; i < ref_num; i++) {
+ input_format.push_back('R');
+ }
+ afe_config_t* afe_config = afe_config_init(input_format.c_str(), models_, AFE_TYPE_SR, AFE_MODE_HIGH_PERF);
+ afe_config->aec_init = codec_->input_reference();
+ afe_config->aec_mode = AEC_MODE_SR_HIGH_PERF;
+ afe_config->afe_perferred_core = 1;
+ afe_config->afe_perferred_priority = 1;
+ afe_config->memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM;
+
+ afe_iface_ = esp_afe_handle_from_config(afe_config);
+ afe_data_ = afe_iface_->create_from_config(afe_config);
+
+ xTaskCreate([](void* arg) {
+ auto this_ = (AfeWakeWord*)arg;
+ this_->AudioDetectionTask();
+ vTaskDelete(NULL);
+ }, "audio_detection", 4096, this, 3, nullptr);
+
+ return true;
+}
+
+void AfeWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
+ wake_word_detected_callback_ = callback;
+}
+
+void AfeWakeWord::Start() {
+ xEventGroupSetBits(event_group_, DETECTION_RUNNING_EVENT);
+}
+
+void AfeWakeWord::Stop() {
+ xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ if (afe_data_ != nullptr) {
+ afe_iface_->reset_buffer(afe_data_);
+ }
+ input_buffer_.clear();
+}
+
+void AfeWakeWord::Feed(const std::vector<int16_t>& data) {
+ if (afe_data_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ // Check running state inside lock to avoid TOCTOU race with Stop()
+ if (!(xEventGroupGetBits(event_group_) & DETECTION_RUNNING_EVENT)) {
+ return;
+ }
+ input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
+ size_t chunk_size = afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
+ while (input_buffer_.size() >= chunk_size) {
+ afe_iface_->feed(afe_data_, input_buffer_.data());
+ input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunk_size);
+ }
+}
+
+size_t AfeWakeWord::GetFeedSize() {
+ if (afe_data_ == nullptr) {
+ return 0;
+ }
+ return afe_iface_->get_feed_chunksize(afe_data_);
+}
+
+void AfeWakeWord::AudioDetectionTask() {
+ auto fetch_size = afe_iface_->get_fetch_chunksize(afe_data_);
+ auto feed_size = afe_iface_->get_feed_chunksize(afe_data_);
+ ESP_LOGI(TAG, "Audio detection task started, feed size: %d fetch size: %d",
+ feed_size, fetch_size);
+
+ while (true) {
+ xEventGroupWaitBits(event_group_, DETECTION_RUNNING_EVENT, pdFALSE, pdTRUE, portMAX_DELAY);
+
+ auto res = afe_iface_->fetch_with_delay(afe_data_, portMAX_DELAY);
+ if (res == nullptr || res->ret_value == ESP_FAIL) {
+ continue;;
+ }
+
+ // Store the wake word data for voice recognition, like who is speaking
+ StoreWakeWordData(res->data, res->data_size / sizeof(int16_t));
+
+ if (res->wakeup_state == WAKENET_DETECTED) {
+ Stop();
+ last_detected_wake_word_ = wake_words_[res->wakenet_model_index - 1];
+
+ if (wake_word_detected_callback_) {
+ wake_word_detected_callback_(last_detected_wake_word_);
+ }
+ }
+ }
+}
+
+void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
+ // store audio data to wake_word_pcm_
+ wake_word_pcm_.emplace_back(std::vector<int16_t>(data, data + samples));
+ // keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
+ while (wake_word_pcm_.size() > 2000 / 30) {
+ wake_word_pcm_.pop_front();
+ }
+}
+
+void AfeWakeWord::EncodeWakeWordData() {
+ const size_t stack_size = 4096 * 6;
+ wake_word_opus_.clear();
+ if (wake_word_encode_task_stack_ == nullptr) {
+ wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
+ assert(wake_word_encode_task_stack_ != nullptr);
+ }
+ if (wake_word_encode_task_buffer_ == nullptr) {
+ wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
+ assert(wake_word_encode_task_buffer_ != nullptr);
+ }
+
+ wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
+ auto this_ = (AfeWakeWord*)arg;
+ {
+ auto start_time = esp_timer_get_time();
+ // Create encoder
+ esp_opus_enc_config_t opus_enc_cfg = AS_OPUS_ENC_CONFIG();
+ void* encoder_handle = nullptr;
+ auto ret = esp_opus_enc_open(&opus_enc_cfg, sizeof(esp_opus_enc_config_t), &encoder_handle);
+ if (encoder_handle == nullptr) {
+ ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", ret);
+ std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
+ this_->wake_word_opus_.push_back(std::vector<uint8_t>());
+ this_->wake_word_cv_.notify_all();
+ return;
+ }
+
+ // Get frame size
+ int frame_size = 0;
+ int outbuf_size = 0;
+ esp_opus_enc_get_frame_size(encoder_handle, &frame_size, &outbuf_size);
+ frame_size = frame_size / sizeof(int16_t);
+
+ // Encode all PCM data
+ int packets = 0;
+ std::vector<int16_t> in_buffer;
+ esp_audio_enc_in_frame_t in = {};
+ esp_audio_enc_out_frame_t out = {};
+
+ for (auto& pcm: this_->wake_word_pcm_) {
+ if (in_buffer.empty()) {
+ in_buffer = std::move(pcm);
+ } else {
+ in_buffer.reserve(in_buffer.size() + pcm.size());
+ in_buffer.insert(in_buffer.end(), pcm.begin(), pcm.end());
+ }
+
+ while (in_buffer.size() >= frame_size) {
+ std::vector<uint8_t> opus_buf(outbuf_size);
+ in.buffer = (uint8_t *)(in_buffer.data());
+ in.len = (uint32_t)(frame_size * sizeof(int16_t));
+ out.buffer = opus_buf.data();
+ out.len = outbuf_size;
+ out.encoded_bytes = 0;
+
+ ret = esp_opus_enc_process(encoder_handle, &in, &out);
+ if (ret == ESP_AUDIO_ERR_OK) {
+ std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
+ this_->wake_word_opus_.emplace_back(opus_buf.data(), opus_buf.data() + out.encoded_bytes);
+ this_->wake_word_cv_.notify_all();
+ packets++;
+ } else {
+ ESP_LOGE(TAG, "Failed to encode audio, error code: %d", ret);
+ }
+
+ in_buffer.erase(in_buffer.begin(), in_buffer.begin() + frame_size);
+ }
+ }
+ this_->wake_word_pcm_.clear();
+ // Close encoder
+ esp_opus_enc_close(encoder_handle);
+ auto end_time = esp_timer_get_time();
+ ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
+
+ std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
+ this_->wake_word_opus_.push_back(std::vector<uint8_t>());
+ this_->wake_word_cv_.notify_all();
+ }
+ vTaskDelete(NULL);
+ }, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
+}
+
+bool AfeWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
+ std::unique_lock<std::mutex> lock(wake_word_mutex_);
+ wake_word_cv_.wait(lock, [this]() {
+ return !wake_word_opus_.empty();
+ });
+ opus.swap(wake_word_opus_.front());
+ wake_word_opus_.pop_front();
+ return !opus.empty();
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.h b/xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.h
new file mode 100644
index 0000000..6c2bf72
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_words/afe_wake_word.h
@@ -0,0 +1,62 @@
+#ifndef AFE_WAKE_WORD_H
+#define AFE_WAKE_WORD_H
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/event_groups.h>
+
+#include <esp_afe_sr_models.h>
+#include <esp_nsn_models.h>
+#include <model_path.h>
+
+#include <deque>
+#include <string>
+#include <vector>
+#include <functional>
+#include <mutex>
+#include <condition_variable>
+
+#include "audio_codec.h"
+#include "wake_word.h"
+
+class AfeWakeWord : public WakeWord {
+public:
+ AfeWakeWord();
+ ~AfeWakeWord();
+
+ bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
+ void Feed(const std::vector<int16_t>& data);
+ void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
+ void Start();
+ void Stop();
+ size_t GetFeedSize();
+ void EncodeWakeWordData();
+ bool GetWakeWordOpus(std::vector<uint8_t>& opus);
+ const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
+
+private:
+ srmodel_list_t *models_ = nullptr;
+ const esp_afe_sr_iface_t* afe_iface_ = nullptr;
+ esp_afe_sr_data_t* afe_data_ = nullptr;
+ char* wakenet_model_ = NULL;
+ std::vector<std::string> wake_words_;
+ EventGroupHandle_t event_group_;
+ std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
+ AudioCodec* codec_ = nullptr;
+ std::string last_detected_wake_word_;
+ std::vector<int16_t> input_buffer_;
+ std::mutex input_buffer_mutex_;
+
+ TaskHandle_t wake_word_encode_task_ = nullptr;
+ StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
+ StackType_t* wake_word_encode_task_stack_ = nullptr;
+ std::deque<std::vector<int16_t>> wake_word_pcm_;
+ std::deque<std::vector<uint8_t>> wake_word_opus_;
+ std::mutex wake_word_mutex_;
+ std::condition_variable wake_word_cv_;
+
+ void StoreWakeWordData(const int16_t* data, size_t size);
+ void AudioDetectionTask();
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.cc b/xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.cc
new file mode 100644
index 0000000..982bbe9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.cc
@@ -0,0 +1,303 @@
+#include "custom_wake_word.h"
+#include "audio_service.h"
+#include "system_info.h"
+#include "assets.h"
+
+#include <esp_log.h>
+#include <esp_mn_iface.h>
+#include <esp_mn_models.h>
+#include <esp_mn_speech_commands.h>
+#include <cJSON.h>
+
+#define TAG "CustomWakeWord"
+
+CustomWakeWord::CustomWakeWord()
+ : wake_word_pcm_(), wake_word_opus_() {
+}
+
+CustomWakeWord::~CustomWakeWord() {
+ if (multinet_model_data_ != nullptr && multinet_ != nullptr) {
+ multinet_->destroy(multinet_model_data_);
+ multinet_model_data_ = nullptr;
+ }
+
+ if (wake_word_encode_task_stack_ != nullptr) {
+ heap_caps_free(wake_word_encode_task_stack_);
+ }
+
+ if (wake_word_encode_task_buffer_ != nullptr) {
+ heap_caps_free(wake_word_encode_task_buffer_);
+ }
+
+ if (models_ != nullptr) {
+ esp_srmodel_deinit(models_);
+ }
+}
+
+void CustomWakeWord::ParseWakenetModelConfig() {
+ // Read index.json
+ auto& assets = Assets::GetInstance();
+ void* ptr = nullptr;
+ size_t size = 0;
+ if (!assets.GetAssetData("index.json", ptr, size)) {
+ ESP_LOGE(TAG, "Failed to read index.json");
+ return;
+ }
+ cJSON* root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
+ if (root == nullptr) {
+ ESP_LOGE(TAG, "Failed to parse index.json");
+ return;
+ }
+ cJSON* multinet_model = cJSON_GetObjectItem(root, "multinet_model");
+ if (cJSON_IsObject(multinet_model)) {
+ cJSON* language = cJSON_GetObjectItem(multinet_model, "language");
+ cJSON* duration = cJSON_GetObjectItem(multinet_model, "duration");
+ cJSON* threshold = cJSON_GetObjectItem(multinet_model, "threshold");
+ cJSON* commands = cJSON_GetObjectItem(multinet_model, "commands");
+ if (cJSON_IsString(language)) {
+ language_ = language->valuestring;
+ }
+ if (cJSON_IsNumber(duration)) {
+ duration_ = duration->valueint;
+ }
+ if (cJSON_IsNumber(threshold)) {
+ threshold_ = threshold->valuedouble;
+ }
+ if (cJSON_IsArray(commands)) {
+ for (int i = 0; i < cJSON_GetArraySize(commands); i++) {
+ cJSON* command = cJSON_GetArrayItem(commands, i);
+ if (cJSON_IsObject(command)) {
+ cJSON* command_name = cJSON_GetObjectItem(command, "command");
+ cJSON* text = cJSON_GetObjectItem(command, "text");
+ cJSON* action = cJSON_GetObjectItem(command, "action");
+ if (cJSON_IsString(command_name) && cJSON_IsString(text) && cJSON_IsString(action)) {
+ commands_.push_back({command_name->valuestring, text->valuestring, action->valuestring});
+ ESP_LOGI(TAG, "Command: %s, Text: %s, Action: %s", command_name->valuestring, text->valuestring, action->valuestring);
+ }
+ }
+ }
+ }
+ }
+ cJSON_Delete(root);
+}
+
+
+bool CustomWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
+ codec_ = codec;
+ commands_.clear();
+
+ if (models_list == nullptr) {
+ language_ = "cn";
+ models_ = esp_srmodel_init("model");
+#ifdef CONFIG_CUSTOM_WAKE_WORD
+ threshold_ = CONFIG_CUSTOM_WAKE_WORD_THRESHOLD / 100.0f;
+ commands_.push_back({CONFIG_CUSTOM_WAKE_WORD, CONFIG_CUSTOM_WAKE_WORD_DISPLAY, "wake"});
+#endif
+ } else {
+ models_ = models_list;
+ ParseWakenetModelConfig();
+ }
+
+ if (models_ == nullptr || models_->num == -1) {
+ ESP_LOGE(TAG, "Failed to initialize wakenet model");
+ return false;
+ }
+
+ // 鍒濆鍖� multinet (鍛戒护璇嶈瘑鍒�)
+ mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, language_.c_str());
+ if (mn_name_ == nullptr) {
+ ESP_LOGW(TAG, "Language '%s' multinet not found, falling back to any multinet model", language_.c_str());
+ mn_name_ = esp_srmodel_filter(models_, ESP_MN_PREFIX, NULL);
+ }
+ if (mn_name_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to initialize multinet, mn_name is nullptr");
+ ESP_LOGI(TAG, "Please refer to https://pcn7cs20v8cr.feishu.cn/wiki/CpQjwQsCJiQSWSkYEvrcxcbVnwh to add custom wake word");
+ return false;
+ }
+
+ multinet_ = esp_mn_handle_from_name(mn_name_);
+ multinet_model_data_ = multinet_->create(mn_name_, duration_);
+ multinet_->set_det_threshold(multinet_model_data_, threshold_);
+ esp_mn_commands_clear();
+ for (int i = 0; i < commands_.size(); i++) {
+ esp_mn_commands_add(i + 1, commands_[i].command.c_str());
+ }
+ esp_mn_commands_update();
+
+ multinet_->print_active_speech_commands(multinet_model_data_);
+ return true;
+}
+
+void CustomWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
+ wake_word_detected_callback_ = callback;
+}
+
+void CustomWakeWord::Start() {
+ running_ = true;
+}
+
+void CustomWakeWord::Stop() {
+ running_ = false;
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ input_buffer_.clear();
+}
+
+void CustomWakeWord::Feed(const std::vector<int16_t>& data) {
+ if (multinet_model_data_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ // Check running state inside lock to avoid TOCTOU race with Stop()
+ if (!running_) {
+ return;
+ }
+
+ // If input channels is 2, we need to fetch the left channel data
+ if (codec_->input_channels() == 2) {
+ for (size_t i = 0; i < data.size(); i += 2) {
+ input_buffer_.push_back(data[i]);
+ }
+ } else {
+ input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
+ }
+
+ int chunksize = multinet_->get_samp_chunksize(multinet_model_data_);
+ while (input_buffer_.size() >= chunksize) {
+ std::vector<int16_t> chunk(input_buffer_.begin(), input_buffer_.begin() + chunksize);
+ StoreWakeWordData(chunk);
+
+ esp_mn_state_t mn_state = multinet_->detect(multinet_model_data_, chunk.data());
+
+ if (mn_state == ESP_MN_STATE_DETECTED) {
+ esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
+ for (int i = 0; i < mn_result->num && running_; i++) {
+ ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
+ mn_result->command_id[i], mn_result->string, mn_result->prob[i]);
+ auto& command = commands_[mn_result->command_id[i] - 1];
+ if (command.action == "wake") {
+ last_detected_wake_word_ = command.text;
+ running_ = false;
+ input_buffer_.clear();
+
+ if (wake_word_detected_callback_) {
+ wake_word_detected_callback_(last_detected_wake_word_);
+ }
+ }
+ }
+ multinet_->clean(multinet_model_data_);
+ } else if (mn_state == ESP_MN_STATE_TIMEOUT) {
+ ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
+ multinet_->clean(multinet_model_data_);
+ }
+
+ if (!running_) {
+ break;
+ }
+ input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunksize);
+ }
+}
+
+size_t CustomWakeWord::GetFeedSize() {
+ if (multinet_model_data_ == nullptr) {
+ return 0;
+ }
+ return multinet_->get_samp_chunksize(multinet_model_data_);
+}
+
+void CustomWakeWord::StoreWakeWordData(const std::vector<int16_t>& data) {
+ // store audio data to wake_word_pcm_
+ wake_word_pcm_.push_back(data);
+ // keep about 2 seconds of data, detect duration is 30ms (sample_rate == 16000, chunksize == 512)
+ while (wake_word_pcm_.size() > 2000 / 30) {
+ wake_word_pcm_.pop_front();
+ }
+}
+
+void CustomWakeWord::EncodeWakeWordData() {
+ const size_t stack_size = 4096 * 7;
+ wake_word_opus_.clear();
+ if (wake_word_encode_task_stack_ == nullptr) {
+ wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
+ assert(wake_word_encode_task_stack_ != nullptr);
+ }
+ if (wake_word_encode_task_buffer_ == nullptr) {
+ wake_word_encode_task_buffer_ = (StaticTask_t*)heap_caps_malloc(sizeof(StaticTask_t), MALLOC_CAP_INTERNAL);
+ assert(wake_word_encode_task_buffer_ != nullptr);
+ }
+
+ wake_word_encode_task_ = xTaskCreateStatic([](void* arg) {
+ auto this_ = (CustomWakeWord*)arg;
+ {
+ auto start_time = esp_timer_get_time();
+ // Create encoder
+ esp_opus_enc_config_t opus_enc_cfg = AS_OPUS_ENC_CONFIG();
+ void* encoder_handle = nullptr;
+ auto ret = esp_opus_enc_open(&opus_enc_cfg, sizeof(esp_opus_enc_config_t), &encoder_handle);
+ if (encoder_handle == nullptr) {
+ ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", ret);
+ std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
+ this_->wake_word_opus_.push_back(std::vector<uint8_t>());
+ this_->wake_word_cv_.notify_all();
+ return;
+ }
+ // Get frame size
+ int frame_size = 0;
+ int outbuf_size = 0;
+ esp_opus_enc_get_frame_size(encoder_handle, &frame_size, &outbuf_size);
+ frame_size = frame_size / sizeof(int16_t);
+ // Encode all PCM data
+ int packets = 0;
+ std::vector<int16_t> in_buffer;
+ esp_audio_enc_in_frame_t in = {};
+ esp_audio_enc_out_frame_t out = {};
+ for (auto& pcm: this_->wake_word_pcm_) {
+ if (in_buffer.empty()) {
+ in_buffer = std::move(pcm);
+ } else {
+ in_buffer.reserve(in_buffer.size() + pcm.size());
+ in_buffer.insert(in_buffer.end(), pcm.begin(), pcm.end());
+ }
+ while (in_buffer.size() >= frame_size) {
+ std::vector<uint8_t> opus_buf(outbuf_size);
+ in.buffer = (uint8_t *)(in_buffer.data());
+ in.len = (uint32_t)(frame_size * sizeof(int16_t));
+ out.buffer = opus_buf.data();
+ out.len = outbuf_size;
+ out.encoded_bytes = 0;
+ ret = esp_opus_enc_process(encoder_handle, &in, &out);
+ if (ret == ESP_AUDIO_ERR_OK) {
+ std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
+ this_->wake_word_opus_.emplace_back(opus_buf.data(), opus_buf.data() + out.encoded_bytes);
+ this_->wake_word_cv_.notify_all();
+ packets++;
+ } else {
+ ESP_LOGE(TAG, "Failed to encode audio, error code: %d", ret);
+ }
+ in_buffer.erase(in_buffer.begin(), in_buffer.begin() + frame_size);
+ }
+ }
+ this_->wake_word_pcm_.clear();
+ // Close encoder
+ esp_opus_enc_close(encoder_handle);
+ auto end_time = esp_timer_get_time();
+ ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
+
+ std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
+ this_->wake_word_opus_.push_back(std::vector<uint8_t>());
+ this_->wake_word_cv_.notify_all();
+ }
+ vTaskDelete(NULL);
+ }, "encode_wake_word", stack_size, this, 2, wake_word_encode_task_stack_, wake_word_encode_task_buffer_);
+}
+
+bool CustomWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
+ std::unique_lock<std::mutex> lock(wake_word_mutex_);
+ wake_word_cv_.wait(lock, [this]() {
+ return !wake_word_opus_.empty();
+ });
+ opus.swap(wake_word_opus_.front());
+ wake_word_opus_.pop_front();
+ return !opus.empty();
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.h b/xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.h
new file mode 100644
index 0000000..645ad1b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_words/custom_wake_word.h
@@ -0,0 +1,71 @@
+#ifndef CUSTOM_WAKE_WORD_H
+#define CUSTOM_WAKE_WORD_H
+
+#include <esp_attr.h>
+#include <esp_mn_iface.h>
+#include <esp_mn_models.h>
+#include <model_path.h>
+
+#include <deque>
+#include <string>
+#include <vector>
+#include <functional>
+#include <mutex>
+#include <condition_variable>
+#include <atomic>
+
+#include "audio_codec.h"
+#include "wake_word.h"
+
+class CustomWakeWord : public WakeWord {
+public:
+ CustomWakeWord();
+ ~CustomWakeWord();
+
+ bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
+ void Feed(const std::vector<int16_t>& data);
+ void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
+ void Start();
+ void Stop();
+ size_t GetFeedSize();
+ void EncodeWakeWordData();
+ bool GetWakeWordOpus(std::vector<uint8_t>& opus);
+ const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
+
+private:
+ struct Command {
+ std::string command;
+ std::string text;
+ std::string action;
+ };
+
+ // multinet 鐩稿叧鎴愬憳鍙橀噺
+ esp_mn_iface_t* multinet_ = nullptr;
+ model_iface_data_t* multinet_model_data_ = nullptr;
+ srmodel_list_t *models_ = nullptr;
+ char* mn_name_ = nullptr;
+ std::string language_ = "cn";
+ int duration_ = 3000;
+ float threshold_ = 0.2;
+ std::deque<Command> commands_;
+
+ std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
+ AudioCodec* codec_ = nullptr;
+ std::string last_detected_wake_word_;
+ std::atomic<bool> running_ = false;
+ std::vector<int16_t> input_buffer_;
+ std::mutex input_buffer_mutex_;
+
+ TaskHandle_t wake_word_encode_task_ = nullptr;
+ StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
+ StackType_t* wake_word_encode_task_stack_ = nullptr;
+ std::deque<std::vector<int16_t>> wake_word_pcm_;
+ std::deque<std::vector<uint8_t>> wake_word_opus_;
+ std::mutex wake_word_mutex_;
+ std::condition_variable wake_word_cv_;
+
+ void StoreWakeWordData(const std::vector<int16_t>& data);
+ void ParseWakenetModelConfig();
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.cc b/xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.cc
new file mode 100644
index 0000000..930aa2d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.cc
@@ -0,0 +1,110 @@
+#include "esp_wake_word.h"
+#include <esp_log.h>
+
+
+#define TAG "EspWakeWord"
+
+EspWakeWord::EspWakeWord() {
+}
+
+EspWakeWord::~EspWakeWord() {
+ if (wakenet_data_ != nullptr) {
+ wakenet_iface_->destroy(wakenet_data_);
+ esp_srmodel_deinit(wakenet_model_);
+ }
+}
+
+bool EspWakeWord::Initialize(AudioCodec* codec, srmodel_list_t* models_list) {
+ codec_ = codec;
+
+ if (models_list == nullptr) {
+ wakenet_model_ = esp_srmodel_init("model");
+ } else {
+ wakenet_model_ = models_list;
+ }
+
+ if (wakenet_model_ == nullptr || wakenet_model_->num == -1) {
+ ESP_LOGE(TAG, "Failed to initialize wakenet model");
+ return false;
+ }
+ if(wakenet_model_->num > 1) {
+ ESP_LOGW(TAG, "More than one model found, using the first one");
+ } else if (wakenet_model_->num == 0) {
+ ESP_LOGE(TAG, "No model found");
+ return false;
+ }
+ char *model_name = wakenet_model_->model_name[0];
+ wakenet_iface_ = (esp_wn_iface_t*)esp_wn_handle_from_name(model_name);
+ wakenet_data_ = wakenet_iface_->create(model_name, DET_MODE_95);
+
+ int frequency = wakenet_iface_->get_samp_rate(wakenet_data_);
+ int audio_chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_);
+ ESP_LOGI(TAG, "Wake word(%s),freq: %d, chunksize: %d", model_name, frequency, audio_chunksize);
+
+ return true;
+}
+
+void EspWakeWord::OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback) {
+ wake_word_detected_callback_ = callback;
+}
+
+void EspWakeWord::Start() {
+ running_ = true;
+}
+
+void EspWakeWord::Stop() {
+ running_ = false;
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ input_buffer_.clear();
+}
+
+void EspWakeWord::Feed(const std::vector<int16_t>& data) {
+ if (wakenet_data_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(input_buffer_mutex_);
+ // Check running state inside lock to avoid TOCTOU race with Stop()
+ if (!running_) {
+ return;
+ }
+
+ if (codec_->input_channels() == 2) {
+ for (size_t i = 0; i < data.size(); i += 2) {
+ input_buffer_.push_back(data[i]);
+ }
+ } else {
+ input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
+ }
+
+ int chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_);
+ while (input_buffer_.size() >= chunksize) {
+ int res = wakenet_iface_->detect(wakenet_data_, input_buffer_.data());
+ if (res > 0) {
+ last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res);
+ running_ = false;
+ input_buffer_.clear();
+
+ if (wake_word_detected_callback_) {
+ wake_word_detected_callback_(last_detected_wake_word_);
+ }
+ break;
+ }
+ input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunksize);
+ }
+}
+
+size_t EspWakeWord::GetFeedSize() {
+ if (wakenet_data_ == nullptr) {
+ return 0;
+ }
+ return wakenet_iface_->get_samp_chunksize(wakenet_data_);
+}
+
+void EspWakeWord::EncodeWakeWordData() {
+}
+
+bool EspWakeWord::GetWakeWordOpus(std::vector<uint8_t>& opus) {
+ return false;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.h b/xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.h
new file mode 100644
index 0000000..87e792d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/audio/wake_words/esp_wake_word.h
@@ -0,0 +1,45 @@
+#ifndef ESP_WAKE_WORD_H
+#define ESP_WAKE_WORD_H
+
+#include <esp_wn_iface.h>
+#include <esp_wn_models.h>
+#include <model_path.h>
+
+#include <string>
+#include <vector>
+#include <functional>
+#include <atomic>
+#include <mutex>
+
+#include "audio_codec.h"
+#include "wake_word.h"
+
+class EspWakeWord : public WakeWord {
+public:
+ EspWakeWord();
+ ~EspWakeWord();
+
+ bool Initialize(AudioCodec* codec, srmodel_list_t* models_list);
+ void Feed(const std::vector<int16_t>& data);
+ void OnWakeWordDetected(std::function<void(const std::string& wake_word)> callback);
+ void Start();
+ void Stop();
+ size_t GetFeedSize();
+ void EncodeWakeWordData();
+ bool GetWakeWordOpus(std::vector<uint8_t>& opus);
+ const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; }
+
+private:
+ esp_wn_iface_t *wakenet_iface_ = nullptr;
+ model_iface_data_t *wakenet_data_ = nullptr;
+ srmodel_list_t *wakenet_model_ = nullptr;
+ AudioCodec* codec_ = nullptr;
+ std::atomic<bool> running_ = false;
+
+ std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
+ std::string last_detected_wake_word_;
+ std::vector<int16_t> input_buffer_;
+ std::mutex input_buffer_mutex_;
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README.md b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README.md
new file mode 100644
index 0000000..28f5666
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README.md
@@ -0,0 +1,41 @@
+# 缂栬瘧鍛戒护
+
+## 涓�閿紪璇�
+
+```bash
+python scripts/release.py aipi-lite
+```
+
+## 鎵嬪姩閰嶇疆缂栬瘧
+
+```bash
+idf.py set-target esp32s3
+```
+
+**閰嶇疆**
+
+```bash
+idf.py menuconfig
+```
+
+閫夋嫨鏉垮瓙
+
+```
+Xiaozhi Assistant -> Board Type -> AIPI-Lite
+```
+
+## 缂栬瘧鐑у叆
+
+```bash
+idf.py -DBOARD_NAME=aipi-lite build flash
+```
+
+娉ㄦ剰: 濡傛灉褰撳墠璁惧鍑鸿揣涔嬪墠鏄疉iPi-Lite 鍥轰欢(闈炲皬鏅虹増鏈�),璇风壒鍒皬蹇冨鐞嗛棯瀛樺浐浠跺垎鍖哄湴鍧�锛屼互閬垮厤閿欒鎿﹂櫎 AiPi-Lite 鐨勮嚜韬澶囦俊鎭紙EUI 绛夛級锛屽惁鍒欒澶囧嵆浣挎仮澶嶆垚Xorigin鍥轰欢涔熸棤娉曟纭繛鎺ュ埌 鏈嶅姟鍣紒鎵�浠ュ湪鍒峰啓鍥轰欢涔嬪墠锛岃鍔″繀璁板綍璁惧鐨勭浉鍏冲繀瑕佷俊鎭紝浠ョ‘淇濇湁鎭㈠鐨勬柟娉曪紒
+
+鎮ㄥ彲浠ヤ娇鐢ㄤ互涓嬪懡浠ゅ浠界敓浜т俊鎭�
+
+```bash
+# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
+esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
+
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README_en.md b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README_en.md
new file mode 100644
index 0000000..df9deb5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/README_en.md
@@ -0,0 +1,40 @@
+# Build Instructions
+
+## One-click Build
+
+```bash
+python scripts/release.py aipi-lite -c config_en.json
+```
+
+## Manual Configuration and Build
+
+```bash
+idf.py set-target esp32s3
+```
+
+**Configuration**
+
+```bash
+idf.py menuconfig
+```
+
+Select the board:
+
+```
+Xiaozhi Assistant -> Board Type -> AiPi-Lite
+```
+
+## Build and Flash
+
+```bash
+idf.py -DBOARD_NAME=aipi-lite build flash
+```
+
+Note: If your device was previously shipped with the AiPi-Lite firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the AiPi-Lite. Otherwise, even if you restore the AiPi-Lite firmware, the device may not be able to connect to the Xorigin server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it!
+
+You can use the following command to back up the factory information:
+
+```bash
+# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
+esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 16384 nvsfactory.bin
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/aipi-lite.cc b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/aipi-lite.cc
new file mode 100644
index 0000000..a257dfe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/aipi-lite.cc
@@ -0,0 +1,246 @@
+#include <driver/gpio.h>
+#include <driver/i2c_master.h>
+#include <driver/rtc_io.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include <esp_sleep.h>
+
+#include "application.h"
+#include "button.h"
+#include "codecs/es8311_audio_codec.h"
+#include "config.h"
+#include "display/lcd_display.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "power_manager.h"
+#include "power_save_timer.h"
+#include "system_reset.h"
+#include "wifi_board.h"
+
+#define TAG "AIPI-Lite"
+
+class AIPILite : public WifiBoard {
+ private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button power_button_;
+ LcdDisplay* display_;
+ PowerManager* power_manager_;
+ PowerSaveTimer* power_save_timer_;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(POWER_CHARGE_DETECT_PIN);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ esp_lcd_panel_disp_on_off(panel_, false); // 鍏抽棴鏄剧ず
+ rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
+ rtc_gpio_hold_dis(POWER_CONTROL_PIN);
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &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_SCLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_SPI_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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_, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ // 璁剧疆寮�鏈烘寜閽殑闀挎寜浜嬩欢锛堢洿鎺ヨ繘鍏ラ厤缃戞ā寮忥級
+ boot_button_.OnLongPress([this]() {
+ // 鍞ら啋鐢垫簮淇濆瓨瀹氭椂鍣�
+ power_save_timer_->WakeUp();
+ // 鑾峰彇搴旂敤绋嬪簭瀹炰緥
+ auto& app = Application::GetInstance();
+
+ // 杩涘叆閰嶇綉妯″紡
+ app.SetDeviceState(kDeviceStateWifiConfiguring);
+
+ // 閲嶇疆WiFi閰嶇疆浠ョ‘淇濊繘鍏ラ厤缃戞ā寮�
+ EnterWifiConfigMode();
+ });
+
+ power_button_.OnClick([this]() { power_save_timer_->WakeUp(); });
+ power_button_.OnLongPress([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() != kDeviceStateStarting &&
+ !(power_manager_->IsCharging() &&
+ power_manager_->GetBatteryLevel() < 100)) {
+ ESP_LOGI(TAG, "Power button long pressed, shutting down");
+ esp_lcd_panel_disp_on_off(panel_, false); // 鍏抽棴鏄剧ず
+ rtc_gpio_set_level(POWER_CONTROL_PIN, 0);
+ rtc_gpio_hold_dis(POWER_CONTROL_PIN);
+ esp_deep_sleep_start();
+ }
+ });
+ }
+
+ void InitializePowerCtl() {
+ ESP_LOGI(TAG, "Initialize Power Control GPIO");
+ rtc_gpio_init(POWER_CONTROL_PIN);
+ rtc_gpio_set_direction(POWER_CONTROL_PIN, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(POWER_CONTROL_PIN, 1);
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {}
+
+ public:
+ AIPILite()
+ : boot_button_(BOOT_BUTTON_GPIO), power_button_(POWER_BUTTON_GPIO) {
+ InitializePowerCtl();
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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, false);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override { return display_; }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN,
+ DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging,
+ bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(AIPILite);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.h b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.h
new file mode 100644
index 0000000..22aef8e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.h
@@ -0,0 +1,53 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// aipi-lite configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6 // MCLK
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_12 // LRCK
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 // SCLK
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_13 // DIN
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 // DOUT
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_9
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_46
+#define BOOT_BUTTON_GPIO GPIO_NUM_42
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_3
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_16
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_17
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_15
+#define DISPLAY_SPI_DC_PIN GPIO_NUM_7
+#define DISPLAY_SPI_RESET_PIN GPIO_NUM_18
+#define DISPLAY_SPI_MODE 0
+#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
+
+#define POWER_BUTTON_GPIO GPIO_NUM_1
+#define POWER_CONTROL_PIN GPIO_NUM_10
+#define POWER_CHARGE_DETECT_PIN GPIO_NUM_8
+#define POWER_ADC_UNIT ADC_UNIT_1
+#define POWER_ADC_CHANNEL ADC_CHANNEL_1
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.json b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.json
new file mode 100644
index 0000000..9ea0168
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config.json
@@ -0,0 +1,12 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "aipi-lite",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config_en.json b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config_en.json
new file mode 100644
index 0000000..6f71baf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/config_en.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "aipi-lite_en",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"",
+ "CONFIG_LANGUAGE_EN_US=y",
+ "CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=n",
+ "CONFIG_SR_WN_WN9_JARVIS_TTS=y",
+ "CONFIG_SR_WN_WN9_SOPHIA_TTS=y"
+ ]
+ }
+ ]
+}
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/power_manager.h
new file mode 100644
index 0000000..e93b944
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/aipi-lite/power_manager.h
@@ -0,0 +1,187 @@
+#pragma once
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_timer.h>
+
+#include <functional>
+#include <vector>
+
+class PowerManager {
+ private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = POWER_CHARGE_DETECT_PIN;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick
+ // 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(
+ adc_oneshot_read(adc_handle_, POWER_ADC_CHANNEL, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {{1480, 0}, {1581, 20}, {1663, 40},
+ {1750, 60}, {1840, 80}, {1980, 100}};
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc &&
+ average_adc < levels[i + 1].adc) {
+ float ratio =
+ static_cast<float>(average_adc - levels[i].adc) /
+ (levels[i + 1].adc - levels[i].adc);
+ battery_level_ =
+ levels[i].level +
+ ratio * (levels[i + 1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld",
+ adc_value, average_adc, battery_level_);
+ }
+
+ public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback =
+ [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 100000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(
+ adc_handle_, POWER_ADC_CHANNEL, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() { return battery_level_; }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc
new file mode 100644
index 0000000..c025389
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc
@@ -0,0 +1,299 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/timers.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_ops.h>
+
+#define TAG "atk_dnesp32s3_box"
+
+class ATK_NoAudioCodecDuplex : public NoAudioCodec {
+public:
+ ATK_NoAudioCodecDuplex(int input_sample_rate, int output_sample_rate, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din) {
+ duplex_ = true;
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ i2s_chan_config_t chan_cfg = {
+ .id = I2S_NUM_0,
+ .role = I2S_ROLE_MASTER,
+ .dma_desc_num = AUDIO_CODEC_DMA_DESC_NUM,
+ .dma_frame_num = AUDIO_CODEC_DMA_FRAME_NUM,
+ .auto_clear_after_cb = true,
+ .auto_clear_before_cb = false,
+ .intr_priority = 0,
+ };
+ ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, &rx_handle_));
+
+ i2s_std_config_t std_cfg = {
+ .clk_cfg = {
+ .sample_rate_hz = (uint32_t)output_sample_rate_,
+ .clk_src = I2S_CLK_SRC_DEFAULT,
+ .mclk_multiple = I2S_MCLK_MULTIPLE_256,
+ #ifdef I2S_HW_VERSION_2
+ .ext_clk_freq_hz = 0,
+ #endif
+ },
+ .slot_cfg = {
+ .data_bit_width = I2S_DATA_BIT_WIDTH_16BIT,
+ .slot_bit_width = I2S_SLOT_BIT_WIDTH_AUTO,
+ .slot_mode = I2S_SLOT_MODE_STEREO,
+ .slot_mask = I2S_STD_SLOT_BOTH,
+ .ws_width = I2S_DATA_BIT_WIDTH_16BIT,
+ .ws_pol = false,
+ .bit_shift = true,
+ #ifdef I2S_HW_VERSION_2
+ .left_align = true,
+ .big_endian = false,
+ .bit_order_lsb = false
+ #endif
+ },
+ .gpio_cfg = {
+ .mclk = I2S_GPIO_UNUSED,
+ .bclk = bclk,
+ .ws = ws,
+ .dout = dout,
+ .din = din,
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = false
+ }
+ }
+ };
+ ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &std_cfg));
+ ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &std_cfg));
+ ESP_LOGI(TAG, "Duplex channels created");
+ }
+};
+
+class XL9555_IN : public I2cDevice {
+public:
+ XL9555_IN(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x06, 0x3B);
+ WriteReg(0x07, 0xFE);
+ }
+
+ void xl9555_cfg(void) {
+ WriteReg(0x06, 0x1B);
+ WriteReg(0x07, 0xFE);
+ }
+
+ void SetOutputState(uint8_t bit, uint8_t level) {
+ uint16_t data;
+ int index = bit;
+
+ if (bit < 8) {
+ data = ReadReg(0x02);
+ } else {
+ data = ReadReg(0x03);
+ index -= 8;
+ }
+
+ data = (data & ~(1 << index)) | (level << index);
+
+ if (bit < 8) {
+ WriteReg(0x02, data);
+ } else {
+ WriteReg(0x03, data);
+ }
+ }
+
+ int GetPingState(uint16_t pin) {
+ uint8_t data;
+ if (pin <= 0x0080) {
+ data = ReadReg(0x00);
+ return (data & (uint8_t)(pin & 0xFF)) ? 1 : 0;
+ } else {
+ data = ReadReg(0x01);
+ return (data & (uint8_t)((pin >> 8) & 0xFF )) ? 1 : 0;
+ }
+
+ return 0;
+ }
+};
+
+class atk_dnesp32s3_box : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ i2c_master_dev_handle_t xl9555_handle_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ XL9555_IN* xl9555_in_;
+ bool es8311_detected_ = false;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = GPIO_NUM_48,
+ .scl_io_num = GPIO_NUM_45,
+ .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, &i2c_bus_));
+
+ // Initialize XL9555
+ xl9555_in_ = new XL9555_IN(i2c_bus_, 0x20);
+
+ if (xl9555_in_->GetPingState(0x0020) == 1) {
+ es8311_detected_ = true; /* 闊抽璁惧鏍囧織浣嶏紝SPK_CTRL_IO涓洪珮鐢靛钩鏃讹紝璇ユ爣蹇椾綅缃�1锛屼笖鍒ゅ畾涓篍S8311 */
+ } else {
+ es8311_detected_ = false; /* 闊抽璁惧鏍囧織浣嶏紝SPK_CTRL_IO涓轰綆鐢靛钩鏃讹紝璇ユ爣蹇椾綅缃�0锛屼笖鍒ゅ畾涓篘S4168 */
+ }
+
+ xl9555_in_->xl9555_cfg();
+ }
+
+ void InitializeATK_ST7789_80_Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ /* 閰嶇疆RD寮曡剼 */
+ gpio_config_t gpio_init_struct;
+ gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
+ gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
+ gpio_init_struct.pin_bit_mask = 1ull << LCD_NUM_RD;
+ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&gpio_init_struct);
+ gpio_set_level(LCD_NUM_RD, 1);
+
+ esp_lcd_i80_bus_handle_t i80_bus = NULL;
+ esp_lcd_i80_bus_config_t bus_config = {
+ .dc_gpio_num = LCD_NUM_DC,
+ .wr_gpio_num = LCD_NUM_WR,
+ .clk_src = LCD_CLK_SRC_DEFAULT,
+ .data_gpio_nums = {
+ GPIO_LCD_D0,
+ GPIO_LCD_D1,
+ GPIO_LCD_D2,
+ GPIO_LCD_D3,
+ GPIO_LCD_D4,
+ GPIO_LCD_D5,
+ GPIO_LCD_D6,
+ GPIO_LCD_D7,
+ },
+ .bus_width = 8,
+ .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
+ .psram_trans_align = 64,
+ .sram_trans_align = 4,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
+
+ esp_lcd_panel_io_i80_config_t io_config = {
+ .cs_gpio_num = LCD_NUM_CS,
+ .pclk_hz = (10 * 1000 * 1000),
+ .trans_queue_depth = 10,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .dc_levels = {
+ .dc_idle_level = 0,
+ .dc_cmd_level = 0,
+ .dc_dummy_level = 0,
+ .dc_data_level = 1,
+ },
+ .flags = {
+ .swap_color_bytes = 0,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
+
+ esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = LCD_NUM_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .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, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ esp_lcd_panel_set_gap(panel, 0, 0);
+ uint8_t data0[] = {0x00};
+ uint8_t data1[] = {0x65};
+ esp_lcd_panel_io_tx_param(panel_io, 0x36, data0, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x3A, data1, 1);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ atk_dnesp32s3_box() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeATK_ST7789_80_Display();
+ xl9555_in_->SetOutputState(5, 1);
+ xl9555_in_->SetOutputState(7, 1);
+ InitializeButtons();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ /* 鏍规嵁鎺㈡祴缁撴灉鍒濆鍖栫紪瑙g爜鍣� */
+ if (es8311_detected_) {
+ /* 浣跨敤ES8311 椹卞姩 */
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_0,
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ GPIO_NUM_NC,
+ AUDIO_I2S_GPIO_BCLK,
+ AUDIO_I2S_GPIO_WS,
+ AUDIO_I2S_GPIO_DOUT,
+ AUDIO_I2S_GPIO_DIN,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ return &audio_codec;
+ } else {
+ static ATK_NoAudioCodecDuplex audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK,
+ AUDIO_I2S_GPIO_WS,
+ AUDIO_I2S_GPIO_DOUT,
+ AUDIO_I2S_GPIO_DIN);
+ return &audio_codec;
+ }
+ return NULL;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3_box);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.h
new file mode 100644
index 0000000..4b8e002
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.h
@@ -0,0 +1,46 @@
+#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_WS GPIO_NUM_13
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_47
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_4
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+// Pin Definitions
+#define LCD_NUM_CS GPIO_NUM_1
+#define LCD_NUM_DC GPIO_NUM_2
+#define LCD_NUM_RD GPIO_NUM_41
+#define LCD_NUM_WR GPIO_NUM_42
+#define LCD_NUM_RST GPIO_NUM_NC
+
+#define GPIO_LCD_D0 GPIO_NUM_40
+#define GPIO_LCD_D1 GPIO_NUM_39
+#define GPIO_LCD_D2 GPIO_NUM_38
+#define GPIO_LCD_D3 GPIO_NUM_12
+#define GPIO_LCD_D4 GPIO_NUM_11
+#define GPIO_LCD_D5 GPIO_NUM_10
+#define GPIO_LCD_D6 GPIO_NUM_9
+#define GPIO_LCD_D7 GPIO_NUM_46
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.json b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.json
new file mode 100644
index 0000000..21e97d3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atk-dnesp32s3-box",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc
new file mode 100644
index 0000000..21c6e9d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/atk_dnesp32s3_box0.cc
@@ -0,0 +1,388 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#include "i2c_device.h"
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "atk_dnesp32s3_box0"
+
+class atk_dnesp32s3_box0 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button right_button_;
+ Button left_button_;
+ Button middle_button_;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ PowerSupply power_status_;
+ LcdStatus LcdStatus_ = kDevicelcdbacklightOn;
+ PowerSleep power_sleep_ = kDeviceNoSleep;
+ WakeStatus wake_status_ = kDeviceAwakened;
+ XiaozhiStatus XiaozhiStatus_ = kDevice_Exit_Distributionnetwork;
+ esp_timer_handle_t wake_timer_handle_;
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ int ticks_ = 0;
+ const int kChgCtrlInterval = 5;
+
+ void InitializeBoardPowerManager() {
+ gpio_config_t gpio_init_struct = {0};
+ gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
+ gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
+ gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_init_struct.pin_bit_mask = (1ull << CODEC_PWR_PIN) | (1ull << SYS_POW_PIN);
+ gpio_config(&gpio_init_struct);
+
+ gpio_set_level(CODEC_PWR_PIN, 1);
+ gpio_set_level(SYS_POW_PIN, 1);
+
+ gpio_config_t chg_init_struct = {0};
+
+ chg_init_struct.intr_type = GPIO_INTR_DISABLE;
+ chg_init_struct.mode = GPIO_MODE_INPUT;
+ chg_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ chg_init_struct.pin_bit_mask = 1ull << CHRG_PIN;
+ ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
+
+ chg_init_struct.mode = GPIO_MODE_OUTPUT;
+ chg_init_struct.pull_up_en = GPIO_PULLUP_DISABLE;
+ chg_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ chg_init_struct.pin_bit_mask = 1ull << CHG_CTRL_PIN;
+ ESP_ERROR_CHECK(gpio_config(&chg_init_struct));
+ gpio_set_level(CHG_CTRL_PIN, 1);
+
+ if (gpio_get_level(CHRG_PIN) == 0) {
+ power_status_ = kDeviceTypecSupply;
+ } else {
+ power_status_ = kDeviceBatterySupply;
+ }
+
+ esp_timer_create_args_t wake_display_timer_args = {
+ .callback = [](void *arg) {
+ atk_dnesp32s3_box0* self = static_cast<atk_dnesp32s3_box0*>(arg);
+ if (self->LcdStatus_ == kDevicelcdbacklightOff && Application::GetInstance().GetDeviceState() == kDeviceStateListening
+ && self->wake_status_ == kDeviceWaitWake) {
+
+ if (self->power_sleep_ == kDeviceNeutralSleep) {
+ self->power_save_timer_->WakeUp();
+ }
+
+ self->GetBacklight()->RestoreBrightness();
+ self->wake_status_ = kDeviceAwakened;
+ self->LcdStatus_ = kDevicelcdbacklightOn;
+ } else if (self->power_sleep_ == kDeviceNeutralSleep && Application::GetInstance().GetDeviceState() == kDeviceStateListening
+ && self->LcdStatus_ != kDevicelcdbacklightOff && self->wake_status_ == kDeviceAwakened) {
+ self->power_save_timer_->WakeUp();
+ self->power_sleep_ = kDeviceNoSleep;
+ } else {
+ self->ticks_ ++;
+ if (self->ticks_ % self->kChgCtrlInterval == 0) {
+ if (gpio_get_level(CHRG_PIN) == 0) {
+ self->power_status_ = kDeviceTypecSupply;
+ } else {
+ self->power_status_ = kDeviceBatterySupply;
+ }
+
+ if (self->power_manager_->low_voltage_ < 2877 && self->power_status_ != kDeviceTypecSupply) {
+ esp_timer_stop(self->power_manager_->timer_handle_);
+ gpio_set_level(CHG_CTRL_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ gpio_set_level(SYS_POW_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }
+ }
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "wake_update_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 300000));
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(CHRG_PIN);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ power_sleep_ = kDeviceNeutralSleep;
+ XiaozhiStatus_ = kDevice_join_Sleep;
+ GetDisplay()->SetPowerSaveMode(true);
+
+ if (LcdStatus_ != kDevicelcdbacklightOff) {
+ GetBacklight()->SetBrightness(1);
+ }
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ power_sleep_ = kDeviceNoSleep;
+ GetDisplay()->SetPowerSaveMode(false);
+
+ if (XiaozhiStatus_ != kDevice_Exit_Sleep) {
+ GetBacklight()->RestoreBrightness();
+ }
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ if (power_status_ == kDeviceBatterySupply) {
+ esp_timer_stop(power_manager_->timer_handle_);
+ gpio_set_level(CHG_CTRL_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ gpio_set_level(SYS_POW_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ // Initialize I2C peripheral
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = LCD_SCLK_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() {
+ middle_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+
+ if (LcdStatus_ != kDevicelcdbacklightOff) {
+ if (power_sleep_ == kDeviceNeutralSleep) {
+ power_save_timer_->WakeUp();
+ power_sleep_ = kDeviceNoSleep;
+ }
+
+ app.ToggleChatState();
+ }
+ });
+
+ middle_button_.OnPressUp([this]() {
+ if (LcdStatus_ == kDevicelcdbacklightOff) {
+ Application::GetInstance().StopListening();
+ Application::GetInstance().SetDeviceState(kDeviceStateIdle);
+ wake_status_ = kDeviceWaitWake;
+ }
+
+ if (XiaozhiStatus_ == kDevice_Distributionnetwork || XiaozhiStatus_ == kDevice_Exit_Sleep) {
+ esp_timer_stop(power_manager_->timer_handle_);
+ gpio_set_level(CHG_CTRL_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ gpio_set_level(SYS_POW_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ } else if (XiaozhiStatus_ == kDevice_join_Sleep) {
+ GetBacklight()->RestoreBrightness();
+ XiaozhiStatus_ = kDevice_null;
+ }
+ });
+
+ middle_button_.OnLongPress([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+
+ if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ if (app.GetDeviceState() == kDeviceStateWifiConfiguring && power_status_ != kDeviceTypecSupply) {
+ GetBacklight()->SetBrightness(0);
+ XiaozhiStatus_ = kDevice_Distributionnetwork;
+ } else if (power_status_ == kDeviceBatterySupply && LcdStatus_ != kDevicelcdbacklightOff) {
+ Application::GetInstance().StartListening();
+ GetBacklight()->SetBrightness(0);
+ XiaozhiStatus_ = kDevice_Exit_Sleep;
+ } else if (power_status_ == kDeviceTypecSupply && LcdStatus_ == kDevicelcdbacklightOn && Application::GetInstance().GetDeviceState() != kDeviceStateStarting) {
+ Application::GetInstance().StartListening();
+ GetBacklight()->SetBrightness(0);
+ LcdStatus_ = kDevicelcdbacklightOff;
+ } else if (LcdStatus_ == kDevicelcdbacklightOff && (power_status_ == kDeviceTypecSupply || power_status_ == kDeviceBatterySupply)) {
+ GetDisplay()->SetChatMessage("system", "");
+ GetBacklight()->RestoreBrightness();
+ wake_status_ = kDeviceAwakened;
+ LcdStatus_ = kDevicelcdbacklightOn;
+ }
+ }
+ });
+
+ left_button_.OnClick([this]() {
+ if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
+ power_save_timer_->WakeUp();
+ power_sleep_ = kDeviceNoSleep;
+ }
+
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ left_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ right_button_.OnClick([this]() {
+ if (power_sleep_ == kDeviceNeutralSleep && LcdStatus_ != kDevicelcdbacklightOff) {
+ power_save_timer_->WakeUp();
+ power_sleep_ = kDeviceNoSleep;
+ }
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ right_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGI(TAG, "Install panel IO");
+
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 80 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
+
+ ESP_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
+ esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_invert_color(panel, true);
+ esp_lcd_panel_init(panel);
+ 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);
+ }
+
+public:
+ atk_dnesp32s3_box0() :
+ right_button_(R_BUTTON_GPIO, false),
+ left_button_(L_BUTTON_GPIO, false),
+ middle_button_(M_BUTTON_GPIO, true) {
+ InitializeBoardPowerManager();
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_0,
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ GPIO_NUM_NC,
+ AUDIO_I2S_GPIO_BCLK,
+ AUDIO_I2S_GPIO_WS,
+ AUDIO_I2S_GPIO_DOUT,
+ AUDIO_I2S_GPIO_DIN,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3_box0);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.h
new file mode 100644
index 0000000..f2c940e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.h
@@ -0,0 +1,84 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+enum XiaozhiStatus {
+ kDevice_null,
+ kDevice_join_Sleep,
+ kDevice_Exit_Sleep,
+ kDevice_Distributionnetwork,
+ kDevice_Exit_Distributionnetwork,
+};
+
+enum LcdStatus {
+ kDevicelcdbacklightOn,
+ kDevicelcdbacklightOff,
+};
+
+enum WakeStatus {
+ kDeviceAwakened,
+ kDeviceWaitWake,
+ kDeviceSleeped,
+};
+
+enum PowerSupply {
+ kDeviceTypecSupply,
+ kDeviceBatterySupply,
+};
+
+enum PowerSleep {
+ kDeviceNoSleep,
+ kDeviceDeepSleep,
+ kDeviceNeutralSleep,
+};
+
+#define SYS_POW_PIN GPIO_NUM_2
+#define CHG_CTRL_PIN GPIO_NUM_47
+#define CODEC_PWR_PIN GPIO_NUM_14
+#define CHRG_PIN GPIO_NUM_48
+
+#define BAT_VSEN_PIN GPIO_NUM_1
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_11
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define AUDIO_SPK_GPIO_PIN GPIO_NUM_21
+
+#define R_BUTTON_GPIO GPIO_NUM_0
+#define M_BUTTON_GPIO GPIO_NUM_4
+#define L_BUTTON_GPIO GPIO_NUM_3
+
+#define BUILTIN_LED_GPIO GPIO_NUM_13
+
+#define LCD_SCLK_PIN GPIO_NUM_39
+#define LCD_MOSI_PIN GPIO_NUM_40
+#define LCD_MISO_PIN GPIO_NUM_NC
+#define LCD_DC_PIN GPIO_NUM_38
+#define LCD_CS_PIN GPIO_NUM_41
+#define LCD_RST_PIN GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.json b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.json
new file mode 100644
index 0000000..20849d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atk-dnesp32s3-box0",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/power_manager.h
new file mode 100644
index 0000000..e9a206c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box0/power_manager.h
@@ -0,0 +1,193 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 0;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ uint32_t temp_val = 0;
+
+ gpio_set_level(CHG_CTRL_PIN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ for(int t = 0; t < 10; t ++) {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
+ temp_val += adc_value;
+ }
+
+ gpio_set_level(CHG_CTRL_PIN, 1);
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ adc_value = temp_val / 10;
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2951, 0}, /* 3.80V */
+ {3019, 20},
+ {3037, 40},
+ {3091, 60}, /* 3.88 */
+ {3124, 80},
+ {3231, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ low_voltage_ = adc_value;
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ esp_timer_handle_t timer_handle_;
+ uint16_t low_voltage_ = 2877;
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc
new file mode 100644
index 0000000..7e888a7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/atk_dnesp32s3_box2.cc
@@ -0,0 +1,477 @@
+#include "dual_network_board.h"
+#include "codecs/es8389_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#include "i2c_device.h"
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include "esp_io_expander_tca95xx_16bit.h"
+
+#define TAG "atk_dnesp32s3_box2_4g"
+
+class atk_dnesp32s3_box2_4g : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ esp_io_expander_handle_t io_exp_handle;
+ button_handle_t btns;
+ button_driver_t* btn_driver_ = nullptr;
+ static atk_dnesp32s3_box2_4g* instance_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ PowerSupply power_status_;
+ esp_timer_handle_t wake_timer_handle_;
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ int ticks_ = 0;
+ const int kChgCtrlInterval = 5;
+
+ void InitializeBoardPowerManager() {
+ instance_ = this;
+
+ if (IoExpanderGetLevel(XIO_CHRG) == 0) {
+ power_status_ = kDeviceTypecSupply;
+ } else {
+ power_status_ = kDeviceBatterySupply;
+ }
+
+ esp_timer_create_args_t wake_display_timer_args = {
+ .callback = [](void *arg) {
+ atk_dnesp32s3_box2_4g* self = static_cast<atk_dnesp32s3_box2_4g*>(arg);
+
+ self->ticks_ ++;
+ if (self->ticks_ % self->kChgCtrlInterval == 0) {
+ if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
+ self->power_status_ = kDeviceTypecSupply;
+ } else {
+ self->power_status_ = kDeviceBatterySupply;
+ }
+
+ /* 浣庝簬鏌愪釜鐢甸噺锛屼細鑷姩鍏虫満 */
+ if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
+ esp_timer_stop(self->power_manager_->timer_handle_);
+
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "wake_update_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(io_exp_handle);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ if (power_status_ == kDeviceBatterySupply) {
+ GetBacklight()->SetBrightness(0);
+ esp_timer_stop(power_manager_->timer_handle_);
+ esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
+ }
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void audio_volume_change(bool direction) {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume();
+
+ if (direction) {
+ volume += 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ } else {
+ volume -= 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ }
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void audio_volume_minimum(){
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ }
+
+ void audio_volume_maxmum(){
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ }
+
+ esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
+ return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
+ }
+
+ uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
+ uint32_t pin_val = 0;
+ esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ pin_mask &= DRV_IO_EXP_INPUT_MASK;
+ return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
+ }
+
+ void InitializeIoExpander() {
+ esp_err_t ret = ESP_OK;
+ esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
+
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
+
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
+
+ assert(ret == ESP_OK);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeButtons() {
+ instance_ = this;
+
+ button_config_t l_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_config_t m_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_config_t r_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_driver_t* xio_l_btn_driver_ = nullptr;
+ button_driver_t* xio_m_btn_driver_ = nullptr;
+
+ button_handle_t l_btn_handle = NULL;
+ button_handle_t m_btn_handle = NULL;
+ button_handle_t r_btn_handle = NULL;
+
+ xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ xio_l_btn_driver_->enable_power_save = false;
+ xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !instance_->IoExpanderGetLevel(XIO_KEY_L);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
+
+ xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ xio_m_btn_driver_->enable_power_save = false;
+ xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return instance_->IoExpanderGetLevel(XIO_KEY_M);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
+
+ button_gpio_config_t r_cfg = {
+ .gpio_num = R_BUTTON_GPIO,
+ .active_level = BUTTON_INACTIVE,
+ .enable_power_save = false,
+ .disable_pull = false
+ };
+ ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
+
+ iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_change(false);
+ }, this);
+
+ iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_minimum();
+ }, this);
+
+ iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (self->GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ }
+ else {
+ app.ToggleChatState();
+ }
+ } else {
+ app.ToggleChatState();
+ }
+ }, this);
+
+ iot_button_register_cb(m_btn_handle, BUTTON_DOUBLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ self->SwitchNetworkType();
+ }
+ }, this);
+
+ iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+
+ auto& app = Application::GetInstance();
+ if (self->GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+
+ if (self->power_status_ == kDeviceBatterySupply) {
+ auto backlight = Board::GetInstance().GetBacklight();
+ backlight->SetBrightness(0);
+ esp_timer_stop(self->power_manager_->timer_handle_);
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }, this);
+
+ iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_change(true);
+ }, this);
+
+ iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_4g*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_maxmum();
+ }, this);
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGI(TAG, "Install panel IO");
+
+ /*RD PIN */
+ gpio_config_t gpio_init_struct;
+ gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
+ gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
+ gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
+ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&gpio_init_struct);
+ gpio_set_level(LCD_PIN_RD, 1);
+
+ /* BL PIN */
+ gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
+ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&gpio_init_struct);
+
+ esp_lcd_i80_bus_handle_t i80_bus = NULL;
+ esp_lcd_i80_bus_config_t bus_config = {
+ .dc_gpio_num = LCD_PIN_DC,
+ .wr_gpio_num = LCD_PIN_WR,
+ .clk_src = LCD_CLK_SRC_DEFAULT,
+ .data_gpio_nums = {
+ LCD_PIN_D0,
+ LCD_PIN_D1,
+ LCD_PIN_D2,
+ LCD_PIN_D3,
+ LCD_PIN_D4,
+ LCD_PIN_D5,
+ LCD_PIN_D6,
+ LCD_PIN_D7,
+ },
+ .bus_width = 8,
+ .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
+ .psram_trans_align = 64,
+ .sram_trans_align = 4,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
+
+ esp_lcd_panel_io_i80_config_t io_config = {
+ .cs_gpio_num = LCD_PIN_CS,
+ .pclk_hz = (20 * 1000 * 1000),
+ .trans_queue_depth = 7,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .dc_levels = {
+ .dc_idle_level = 1,
+ .dc_cmd_level = 0,
+ .dc_dummy_level = 0,
+ .dc_data_level = 1,
+ },
+ .flags = {
+ .cs_active_high = 0,
+ .pclk_active_neg = 0,
+ .pclk_idle_low = 0,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
+
+ esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = LCD_PIN_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .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_set_gap(panel, 0, 0);
+ esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
+ esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
+ esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
+ esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
+ esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
+ esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
+ esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
+ esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
+ esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
+ 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);
+ }
+
+public:
+ atk_dnesp32s3_box2_4g() :
+ DualNetworkBoard(Module_4G_TX_PIN, Module_4G_RX_PIN) {
+ InitializeI2c();
+ InitializeIoExpander();
+ InitializePowerSaveTimer();
+ InitializePowerManager();
+ InitializeSt7789Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ InitializeBoardPowerManager();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8389AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8389_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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3_box2_4g);
+
+// 瀹氫箟闈欐�佹垚鍛樺彉閲�
+atk_dnesp32s3_box2_4g* atk_dnesp32s3_box2_4g::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.h
new file mode 100644
index 0000000..70e9d4d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.h
@@ -0,0 +1,80 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+
+#include <driver/gpio.h>
+
+enum PowerSupply {
+ kDeviceTypecSupply,
+ kDeviceBatterySupply,
+};
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
+#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
+
+#define SPISD_PIN_MOSI GPIO_NUM_16
+#define SPISD_PIN_MISO GPIO_NUM_18
+#define SPISD_PIN_CLK GPIO_NUM_17
+#define SPISD_PIN_TS GPIO_NUM_15
+
+#define R_BUTTON_GPIO GPIO_NUM_0
+
+#define XL9555_INT_GPIO GPIO_NUM_2
+#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
+#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
+#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
+#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
+#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
+#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
+#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
+#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
+#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
+#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
+#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
+#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
+#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
+
+#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
+#define DRV_IO_EXP_INPUT_MASK 0xC0E7
+
+#define LCD_PIN_CS GPIO_NUM_14
+#define LCD_PIN_DC GPIO_NUM_12
+#define LCD_PIN_RD GPIO_NUM_10
+#define LCD_PIN_WR GPIO_NUM_11
+#define LCD_PIN_RST GPIO_NUM_NC
+#define LCD_PIN_D0 GPIO_NUM_13
+#define LCD_PIN_D1 GPIO_NUM_9
+#define LCD_PIN_D2 GPIO_NUM_8
+#define LCD_PIN_D3 GPIO_NUM_7
+#define LCD_PIN_D4 GPIO_NUM_6
+#define LCD_PIN_D5 GPIO_NUM_5
+#define LCD_PIN_D6 GPIO_NUM_4
+#define LCD_PIN_D7 GPIO_NUM_3
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define Module_4G_RX_PIN GPIO_NUM_44
+#define Module_4G_TX_PIN GPIO_NUM_43
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.json b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.json
new file mode 100644
index 0000000..076253a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atk-dnesp32s3-box2-4g",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/power_manager.h
new file mode 100644
index 0000000..103ce88
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-4g/power_manager.h
@@ -0,0 +1,195 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include "esp_io_expander_tca95xx_16bit.h"
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ esp_io_expander_handle_t xl9555_;
+ uint32_t pin_val = 0;
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ uint32_t temp_val = 0;
+
+ esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ for(int t = 0; t < 10; t ++) {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
+ temp_val += adc_value;
+ }
+
+ esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
+
+ adc_value = temp_val / 10;
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2696, 0}, /* 3.48V -灞忓箷闂睆 */
+ {2724, 20}, /* 3.53V */
+ {2861, 40}, /* 3.7V */
+ {3038, 60}, /* 3.90V */
+ {3150, 80}, /* 4.02V */
+ {3280, 100} /* 4.14V */
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ low_voltage_ = adc_value;
+
+ // ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ esp_timer_handle_t timer_handle_;
+ uint16_t low_voltage_ = 2630;
+ PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc
new file mode 100644
index 0000000..55e297f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/atk_dnesp32s3_box2.cc
@@ -0,0 +1,456 @@
+#include "wifi_board.h"
+#include "codecs/es8389_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#include "i2c_device.h"
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include "esp_io_expander_tca95xx_16bit.h"
+
+#define TAG "atk_dnesp32s3_box2_wifi"
+
+class atk_dnesp32s3_box2_wifi : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ esp_io_expander_handle_t io_exp_handle;
+ button_handle_t btns;
+ button_driver_t* btn_driver_ = nullptr;
+ static atk_dnesp32s3_box2_wifi* instance_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ PowerSupply power_status_;
+ esp_timer_handle_t wake_timer_handle_;
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ int ticks_ = 0;
+ const int kChgCtrlInterval = 5;
+
+ void InitializeBoardPowerManager() {
+ instance_ = this;
+
+ if (IoExpanderGetLevel(XIO_CHRG) == 0) {
+ power_status_ = kDeviceTypecSupply;
+ } else {
+ power_status_ = kDeviceBatterySupply;
+ }
+
+ esp_timer_create_args_t wake_display_timer_args = {
+ .callback = [](void *arg) {
+ atk_dnesp32s3_box2_wifi* self = static_cast<atk_dnesp32s3_box2_wifi*>(arg);
+
+ self->ticks_ ++;
+ if (self->ticks_ % self->kChgCtrlInterval == 0) {
+ if (self->IoExpanderGetLevel(XIO_CHRG) == 0) {
+ self->power_status_ = kDeviceTypecSupply;
+ } else {
+ self->power_status_ = kDeviceBatterySupply;
+ }
+
+ /* 浣庝簬鏌愪釜鐢甸噺锛屼細鑷姩鍏虫満 */
+ if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
+ esp_timer_stop(self->power_manager_->timer_handle_);
+
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "wake_update_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(io_exp_handle);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ if (power_status_ == kDeviceBatterySupply) {
+ GetBacklight()->SetBrightness(0);
+ esp_timer_stop(power_manager_->timer_handle_);
+ esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0);
+ }
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void audio_volume_change(bool direction) {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume();
+
+ if (direction) {
+ volume += 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ } else {
+ volume -= 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ }
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void audio_volume_minimum(){
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ }
+
+ void audio_volume_maxmum(){
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ }
+
+ esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
+ return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
+ }
+
+ uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
+ uint32_t pin_val = 0;
+ esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ pin_mask &= DRV_IO_EXP_INPUT_MASK;
+ return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
+ }
+
+ void InitializeIoExpander() {
+ esp_err_t ret = ESP_OK;
+ esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle);
+
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
+
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1);
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0);
+
+ assert(ret == ESP_OK);
+ }
+
+ // Initialize I2C peripheral
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeButtons() {
+ instance_ = this;
+
+ button_config_t l_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_config_t m_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_config_t r_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_driver_t* xio_l_btn_driver_ = nullptr;
+ button_driver_t* xio_m_btn_driver_ = nullptr;
+
+ button_handle_t l_btn_handle = NULL;
+ button_handle_t m_btn_handle = NULL;
+ button_handle_t r_btn_handle = NULL;
+
+ xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ xio_l_btn_driver_->enable_power_save = false;
+ xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !instance_->IoExpanderGetLevel(XIO_KEY_L);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle));
+
+ xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ xio_m_btn_driver_->enable_power_save = false;
+ xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return instance_->IoExpanderGetLevel(XIO_KEY_M);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle));
+
+ button_gpio_config_t r_cfg = {
+ .gpio_num = R_BUTTON_GPIO,
+ .active_level = BUTTON_INACTIVE,
+ .enable_power_save = false,
+ .disable_pull = false
+ };
+ ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle));
+
+ iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_change(false);
+ }, this);
+
+ iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_minimum();
+ }, this);
+
+ iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ }, this);
+
+ iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
+
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+
+ if (self->power_status_ == kDeviceBatterySupply) {
+ auto backlight = Board::GetInstance().GetBacklight();
+ backlight->SetBrightness(0);
+ esp_timer_stop(self->power_manager_->timer_handle_);
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }, this);
+
+ iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_change(true);
+ }, this);
+
+ iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_maxmum();
+ }, this);
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGI(TAG, "Install panel IO");
+
+ /* RD PIN */
+ gpio_config_t gpio_init_struct;
+ gpio_init_struct.intr_type = GPIO_INTR_DISABLE;
+ gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT;
+ gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD;
+ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&gpio_init_struct);
+ gpio_set_level(LCD_PIN_RD, 1);
+
+ /* BL PIN */
+ gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN;
+ gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&gpio_init_struct);
+
+ esp_lcd_i80_bus_handle_t i80_bus = NULL;
+ esp_lcd_i80_bus_config_t bus_config = {
+ .dc_gpio_num = LCD_PIN_DC,
+ .wr_gpio_num = LCD_PIN_WR,
+ .clk_src = LCD_CLK_SRC_DEFAULT,
+ .data_gpio_nums = {
+ LCD_PIN_D0,
+ LCD_PIN_D1,
+ LCD_PIN_D2,
+ LCD_PIN_D3,
+ LCD_PIN_D4,
+ LCD_PIN_D5,
+ LCD_PIN_D6,
+ LCD_PIN_D7,
+ },
+ .bus_width = 8,
+ .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t),
+ .psram_trans_align = 64,
+ .sram_trans_align = 4,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus));
+
+ esp_lcd_panel_io_i80_config_t io_config = {
+ .cs_gpio_num = LCD_PIN_CS,
+ .pclk_hz = (20 * 1000 * 1000),
+ .trans_queue_depth = 7,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .dc_levels = {
+ .dc_idle_level = 1,
+ .dc_cmd_level = 0,
+ .dc_dummy_level = 0,
+ .dc_data_level = 1,
+ },
+ .flags = {
+ .cs_active_high = 0,
+ .pclk_active_neg = 0,
+ .pclk_idle_low = 0,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io));
+
+ esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = LCD_PIN_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .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_set_gap(panel, 0, 0);
+ esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3);
+ esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4);
+ esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3);
+ esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5);
+ esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2);
+ esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2);
+ esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1);
+ esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14);
+ esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14);
+ esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1);
+ 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);
+ }
+
+public:
+ atk_dnesp32s3_box2_wifi() {
+ InitializeI2c();
+ InitializeIoExpander();
+ InitializePowerSaveTimer();
+ InitializePowerManager();
+ InitializeSt7789Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ InitializeBoardPowerManager();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8389AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8389_ADDR,
+ false);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3_box2_wifi);
+
+// 瀹氫箟闈欐�佹垚鍛樺彉閲�
+atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.h
new file mode 100644
index 0000000..a12384c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.h
@@ -0,0 +1,71 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+enum PowerSupply {
+ kDeviceTypecSupply,
+ kDeviceBatterySupply,
+};
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_48
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_47
+#define AUDIO_CODEC_ES8389_ADDR ES8389_CODEC_DEFAULT_ADDR
+
+#define R_BUTTON_GPIO GPIO_NUM_0
+
+#define XL9555_INT_GPIO GPIO_NUM_2
+#define XIO_IO_SBU2 (IO_EXPANDER_PIN_NUM_3)
+#define XIO_IO_SBU1 (IO_EXPANDER_PIN_NUM_4)
+#define XIO_KEY_L (IO_EXPANDER_PIN_NUM_5)
+#define XIO_KEY_Q (IO_EXPANDER_PIN_NUM_6)
+#define XIO_KEY_M (IO_EXPANDER_PIN_NUM_7)
+#define XIO_USB_SEL (IO_EXPANDER_PIN_NUM_8)
+#define XIO_SPK_EN (IO_EXPANDER_PIN_NUM_9)
+#define XIO_SYS_POW (IO_EXPANDER_PIN_NUM_10)
+#define XIO_VBUS_EN (IO_EXPANDER_PIN_NUM_11)
+#define XIO_EN_4G (IO_EXPANDER_PIN_NUM_12)
+#define XIO_EN_3V3A (IO_EXPANDER_PIN_NUM_13)
+#define XIO_CHG_CTRL (IO_EXPANDER_PIN_NUM_14)
+#define XIO_CHRG (IO_EXPANDER_PIN_NUM_15)
+
+#define DRV_IO_EXP_OUTPUT_MASK 0x3F18
+#define DRV_IO_EXP_INPUT_MASK 0xC0E7
+
+#define LCD_PIN_CS GPIO_NUM_14
+#define LCD_PIN_DC GPIO_NUM_12
+#define LCD_PIN_RD GPIO_NUM_10
+#define LCD_PIN_WR GPIO_NUM_11
+#define LCD_PIN_RST GPIO_NUM_NC
+#define LCD_PIN_D0 GPIO_NUM_13
+#define LCD_PIN_D1 GPIO_NUM_9
+#define LCD_PIN_D2 GPIO_NUM_8
+#define LCD_PIN_D3 GPIO_NUM_7
+#define LCD_PIN_D4 GPIO_NUM_6
+#define LCD_PIN_D5 GPIO_NUM_5
+#define LCD_PIN_D6 GPIO_NUM_4
+#define LCD_PIN_D7 GPIO_NUM_3
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_21
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.json
new file mode 100644
index 0000000..2add471
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atk-dnesp32s3-box2-wifi",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h
new file mode 100644
index 0000000..103ce88
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box2-wifi/power_manager.h
@@ -0,0 +1,195 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include "esp_io_expander_tca95xx_16bit.h"
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ esp_io_expander_handle_t xl9555_;
+ uint32_t pin_val = 0;
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ esp_io_expander_get_level(xl9555_, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ bool new_charging_status = ((uint8_t)((pin_val & XIO_CHRG) ? 1 : 0)) == 0;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ uint32_t temp_val = 0;
+
+ esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(xl9555_, XIO_CHG_CTRL, 0);
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ for(int t = 0; t < 10; t ++) {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
+ temp_val += adc_value;
+ }
+
+ esp_io_expander_set_dir(xl9555_, XIO_CHG_CTRL, IO_EXPANDER_INPUT);
+
+ adc_value = temp_val / 10;
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2696, 0}, /* 3.48V -灞忓箷闂睆 */
+ {2724, 20}, /* 3.53V */
+ {2861, 40}, /* 3.7V */
+ {3038, 60}, /* 3.90V */
+ {3150, 80}, /* 4.02V */
+ {3280, 100} /* 4.14V */
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ low_voltage_ = adc_value;
+
+ // ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ esp_timer_handle_t timer_handle_;
+ uint16_t low_voltage_ = 2630;
+ PowerManager(esp_io_expander_handle_t xl9555) : xl9555_(xl9555) {
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc
new file mode 100644
index 0000000..7bb1064
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/atk_dnesp32s3_box3.cc
@@ -0,0 +1,471 @@
+#include "wifi_board.h"
+#include "audio_codec.h"
+#include "codecs/es8311_audio_codec.h"
+#include "codecs/box_audio_codec.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+
+#include "i2c_device.h"
+#include "esp_video.h"
+#include <wifi_station.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/timers.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_ops.h>
+#include "power_save_timer.h"
+#include "power_manager.h"
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include "esp_io_expander_tca95xx_16bit.h"
+#include "assets/lang_config.h"
+#include <driver/spi_common.h>
+
+#define TAG "atk_dnesp32s3_box3"
+
+LV_FONT_DECLARE(font_puhui_20_4);
+LV_FONT_DECLARE(font_awesome_20_4);
+
+class atk_dnesp32s3_box3 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ EspVideo* camera_;
+ static atk_dnesp32s3_box3* instance_;
+ esp_io_expander_handle_t io_exp_handle;
+ button_handle_t btns;
+ button_driver_t* btn_driver_ = nullptr;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ PowerSupply power_status_;
+ esp_timer_handle_t wake_timer_handle_;
+ int ticks_ = 0;
+ const int kChgCtrlInterval = 5;
+
+ void InitializeBoardPowerManager() {
+ instance_ = this;
+
+ if (IoExpanderGetLevel(XIO_BAT_CHRG) == 0) {
+ power_status_ = kDeviceTypecSupply;
+ } else {
+ power_status_ = kDeviceBatterySupply;
+ }
+
+ esp_timer_create_args_t wake_display_timer_args = {
+ .callback = [](void *arg) {
+ atk_dnesp32s3_box3* self = static_cast<atk_dnesp32s3_box3*>(arg);
+
+ self->ticks_ ++;
+ if (self->ticks_ % self->kChgCtrlInterval == 0) {
+ if (self->IoExpanderGetLevel(XIO_BAT_CHRG) == 0) {
+ self->power_status_ = kDeviceTypecSupply;
+ } else {
+ self->power_status_ = kDeviceBatterySupply;
+ }
+
+ /* 浣庝簬鏌愪釜鐢甸噺锛屼細鑷姩鍏虫満 */
+ if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) {
+ esp_timer_stop(self->power_manager_->timer_handle_);
+
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_BAT_CHRG_EN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_INPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_BAT_CHRG_EN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "wake_update_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000));
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(io_exp_handle);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, -1, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ if (power_status_ == kDeviceBatterySupply) {
+ GetBacklight()->SetBrightness(0);
+ esp_timer_stop(power_manager_ ->timer_handle_);
+ esp_io_expander_set_dir(io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(io_exp_handle, XIO_BAT_CHRG_EN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_io_expander_set_level(io_exp_handle, XIO_VDD_3V3_EN, 0);
+ }
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void audio_volume_change(bool direction) {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume();
+
+ if (direction) {
+ volume += 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ } else {
+ volume -= 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ }
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void audio_volume_minimum(){
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ }
+
+ void audio_volume_maxmum(){
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ }
+
+ esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
+ return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
+ }
+
+ uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
+ uint32_t pin_val = 0;
+ esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ pin_mask &= DRV_IO_EXP_INPUT_MASK;
+ return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
+ }
+
+ void InitializeIoExpander() {
+ esp_err_t ret = ESP_OK;
+ esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, AW9523B_ADDR, &io_exp_handle);
+
+ // ret |= esp_io_expander_set_pullupdown(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_PULL_NONE);
+
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
+
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_VDD_2V8_EN, 1); /* 0308 */
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_VDD_3V3_EN, 1); /* 鐢垫簮 */
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_ESP_ADC_SEL, 1); /* ADC */
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_VDDA_3V3_EN, 1);/* 闊抽鐢垫簮 */
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBAT_EN, 1); /* 闊抽 */
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_PA_CTRL, 1); /* 闊抽鍔熸斁 */
+ ret |= esp_io_expander_set_level(io_exp_handle, XIO_LCD_BL, 0); /* LCD鑳屽厜 */
+
+ assert(ret == ESP_OK);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = LCD_MISO_PIN;
+ buscfg.sclk_io_num = LCD_SCLK_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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ ESP_LOGD(TAG, "Install panel IO");
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
+ 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, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ 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 InitializeButtons() {
+ instance_ = this;
+
+ button_config_t bo_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_config_t k1_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_config_t k2_btn_cfg = {
+ .long_press_time = 800,
+ .short_press_time = 500
+ };
+
+ button_driver_t* xio_k1_btn_driver_ = nullptr;
+ button_driver_t* xio_k2_btn_driver_ = nullptr;
+
+ button_handle_t bo_btn_handle = NULL;
+ button_handle_t k1_btn_handle = NULL;
+ button_handle_t k2_btn_handle = NULL;
+
+ xio_k1_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ xio_k1_btn_driver_->enable_power_save = false;
+ xio_k1_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !instance_->IoExpanderGetLevel(XIO_KEY_K1);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&k1_btn_cfg, xio_k1_btn_driver_, &k1_btn_handle));
+
+ xio_k2_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ xio_k2_btn_driver_->enable_power_save = false;
+ xio_k2_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return instance_->IoExpanderGetLevel(XIO_KEY_K2);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&k2_btn_cfg, xio_k2_btn_driver_, &k2_btn_handle));
+
+ button_gpio_config_t bo_cfg = {
+ .gpio_num = BOOT_BUTTON_GPIO,
+ .active_level = BUTTON_INACTIVE,
+ .enable_power_save = false,
+ .disable_pull = false
+ };
+ ESP_ERROR_CHECK(iot_button_new_gpio_device(&bo_btn_cfg, &bo_cfg, &bo_btn_handle));
+
+ iot_button_register_cb(k1_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box3*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_change(false);
+ }, this);
+
+ iot_button_register_cb(k1_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box3*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_minimum();
+ }, this);
+
+ iot_button_register_cb(k2_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box3*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ }, this);
+
+ iot_button_register_cb(k2_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box3*>(usr_data);
+
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+
+ if (self->power_status_ == kDeviceBatterySupply) {
+ auto backlight = Board::GetInstance().GetBacklight();
+ backlight->SetBrightness(0);
+ esp_timer_stop(self->power_manager_->timer_handle_);
+ esp_io_expander_set_dir(self->io_exp_handle, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(self->io_exp_handle, XIO_BAT_CHRG_EN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_io_expander_set_level(self->io_exp_handle, XIO_VDD_3V3_EN, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }, this);
+
+ iot_button_register_cb(bo_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box3*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_change(true);
+ }, this);
+
+ iot_button_register_cb(bo_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<atk_dnesp32s3_box3*>(usr_data);
+ self->power_save_timer_->WakeUp();
+ self->audio_volume_maxmum();
+ }, this);
+ }
+
+ /* 鍒濆鍖栨憚鍍忓ご锛欸C0308锛� */
+ /* 鏍规嵁姝g偣鍘熷瓙瀹樻柟绀轰緥鍙傛暟 */
+ void InitializeCamera() {
+ esp_io_expander_set_level(io_exp_handle, XIO_TP_CAM_RESET, 0); /* 纭繚澶嶄綅 */
+ vTaskDelay(pdMS_TO_TICKS(50)); /* 寤堕暱澶嶄綅淇濇寔鏃堕棿 */
+ esp_io_expander_set_level(io_exp_handle, XIO_TP_CAM_RESET, 1); /* 閲婃斁澶嶄綅 */
+ vTaskDelay(pdMS_TO_TICKS(50)); /* 寤堕暱 50ms */
+
+ /* DVP pin configuration */
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAM_PIN_D0,
+ [1] = CAM_PIN_D1,
+ [2] = CAM_PIN_D2,
+ [3] = CAM_PIN_D3,
+ [4] = CAM_PIN_D4,
+ [5] = CAM_PIN_D5,
+ [6] = CAM_PIN_D6,
+ [7] = CAM_PIN_D7,
+ },
+ .vsync_io = CAM_PIN_VSYNC,
+ .de_io = CAM_PIN_LREF,
+ .pclk_io = CAM_PIN_PCLK,
+ .xclk_io = CAM_PIN_XCLK,
+ };
+
+ /* 澶嶇敤 I2C 鎬荤嚎 */
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false, /* 涓嶅垵濮嬪寲鏂扮殑 SCCB锛屼娇鐢ㄧ幇鏈夌殑 I2C 鎬荤嚎 */
+ .i2c_handle = i2c_bus_, /* 浣跨敤鐜版湁鐨� I2C 鎬荤嚎鍙ユ焺 */
+ .freq = 100000, /* SCCB 閫氫俊棰戠巼锛岄�氬父涓� 100kHz */
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAM_PIN_RESET,
+ .pwdn_pin = CAM_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = 24000000,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ }
+
+public:
+ atk_dnesp32s3_box3(){
+ InitializeI2c();
+ InitializeSpi();
+ InitializeIoExpander();
+ InitializePowerSaveTimer();
+ //InitializePowerManager();
+ InitializeSt7789Display();
+ InitializeButtons();
+ //GetBacklight()->RestoreBrightness();
+ //InitializeBoardPowerManager();
+ InitializeCamera();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ // virtual Backlight* GetBacklight() override {
+ // static PwmBacklight backlight(GPIO_NUM_0, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ // return &backlight;
+ // }
+
+ // virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ // static bool last_discharging = false;
+ // charging = power_manager_->IsCharging();
+ // discharging = power_manager_->IsDischarging();
+ // if (discharging != last_discharging) {
+ // power_save_timer_->SetEnabled(discharging);
+ // last_discharging = discharging;
+ // }
+ // level = power_manager_->GetBatteryLevel();
+ // return true;
+ // }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3_box3);
+
+// 瀹氫箟闈欐�佹垚鍛樺彉閲�
+atk_dnesp32s3_box3* atk_dnesp32s3_box3::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.h
new file mode 100644
index 0000000..e418590
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.h
@@ -0,0 +1,94 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+enum PowerSupply {
+ kDeviceTypecSupply,
+ kDeviceBatterySupply,
+};
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_39
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_41
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_40
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_21
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+// Pin Definitions
+#define LCD_SCLK_PIN GPIO_NUM_15
+#define LCD_MOSI_PIN GPIO_NUM_16
+#define LCD_MISO_PIN GPIO_NUM_17
+#define LCD_DC_PIN GPIO_NUM_48
+#define LCD_CS_PIN GPIO_NUM_47
+
+/* IO鎵╁睍 */
+#define AW9523B_ADDR 0x59
+#define AW9523B_INT_GPIO GPIO_NUM_42
+#define XIO_KEY_K1 (IO_EXPANDER_PIN_NUM_0)
+#define XIO_KEY_K2 (IO_EXPANDER_PIN_NUM_1)
+#define XIO_BAT_CHRG_EN (IO_EXPANDER_PIN_NUM_2)
+#define XIO_BAT_CHRG (IO_EXPANDER_PIN_NUM_3)
+#define XIO_ESP_ADC_SEL (IO_EXPANDER_PIN_NUM_4)
+#define XIO_PA_CTRL (IO_EXPANDER_PIN_NUM_5)
+#define XIO_EXT_GPIO0 (IO_EXPANDER_PIN_NUM_6)
+#define XIO_EXT_GPIO1 (IO_EXPANDER_PIN_NUM_7)
+#define XIO_LCD_BL (IO_EXPANDER_PIN_NUM_8)
+#define XIO_LED_RED (IO_EXPANDER_PIN_NUM_9)
+#define XIO_LED_BLUE (IO_EXPANDER_PIN_NUM_10)
+#define XIO_VDD_3V3_EN (IO_EXPANDER_PIN_NUM_11)
+#define XIO_VBAT_EN (IO_EXPANDER_PIN_NUM_12)
+#define XIO_VDDA_3V3_EN (IO_EXPANDER_PIN_NUM_13)
+#define XIO_VDD_2V8_EN (IO_EXPANDER_PIN_NUM_14)
+#define XIO_TP_CAM_RESET (IO_EXPANDER_PIN_NUM_15)
+
+#define DRV_IO_EXP_OUTPUT_MASK 0XFFFC
+#define DRV_IO_EXP_INPUT_MASK 0x0003
+
+/* 鐩告満寮曡剼閰嶇疆 */
+#define CAM_PIN_PWDN GPIO_NUM_NC
+#define CAM_PIN_RESET GPIO_NUM_NC
+#define CAM_PIN_VSYNC GPIO_NUM_6
+#define CAM_PIN_LREF GPIO_NUM_46
+#define CAM_PIN_PCLK GPIO_NUM_45
+#define CAM_PIN_XCLK GPIO_NUM_NC
+#define CAM_PIN_SIOD GPIO_NUM_NC
+#define CAM_PIN_SIOC GPIO_NUM_NC
+#define CAM_PIN_D0 GPIO_NUM_7
+#define CAM_PIN_D1 GPIO_NUM_8
+#define CAM_PIN_D2 GPIO_NUM_9
+#define CAM_PIN_D3 GPIO_NUM_10
+#define CAM_PIN_D4 GPIO_NUM_11
+#define CAM_PIN_D5 GPIO_NUM_12
+#define CAM_PIN_D6 GPIO_NUM_4
+#define CAM_PIN_D7 GPIO_NUM_5
+#define CAM_2V8_EN 14
+#define CAM_RST 15
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.json b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.json
new file mode 100644
index 0000000..2db50c3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atk-dnesp32s3-box3",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/power_manager.h
new file mode 100644
index 0000000..65cc3e4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3-box3/power_manager.h
@@ -0,0 +1,195 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include "esp_io_expander_tca95xx_16bit.h"
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ esp_io_expander_handle_t aw9523b_;
+ uint32_t pin_val = 0;
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ esp_io_expander_get_level(aw9523b_, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ bool new_charging_status = ((uint8_t)((pin_val & XIO_BAT_CHRG) ? 1 : 0)) == 0;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ uint32_t temp_val = 0;
+
+ esp_io_expander_set_dir(aw9523b_, XIO_BAT_CHRG_EN, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(aw9523b_, XIO_BAT_CHRG_EN, 0);
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ for(int t = 0; t < 10; t ++) {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
+ temp_val += adc_value;
+ }
+
+ esp_io_expander_set_dir(aw9523b_, XIO_BAT_CHRG_EN, IO_EXPANDER_INPUT);
+
+ adc_value = temp_val / 10;
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2696, 0}, /* 3.48V -灞忓箷闂睆 */
+ {2724, 20}, /* 3.53V */
+ {2861, 40}, /* 3.7V */
+ {3038, 60}, /* 3.90V */
+ {3150, 80}, /* 4.02V */
+ {3280, 100} /* 4.14V */
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ low_voltage_ = adc_value;
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ esp_timer_handle_t timer_handle_;
+ uint16_t low_voltage_ = 2630;
+ PowerManager(esp_io_expander_handle_t aw9523b) : aw9523b_(aw9523b) {
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc
new file mode 100644
index 0000000..833c669
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc
@@ -0,0 +1,225 @@
+#include "wifi_board.h"
+#include "codecs/es8388_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/single_led.h"
+#include "esp_video.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "atk_dnesp32s3"
+
+class XL9555 : public I2cDevice {
+public:
+ XL9555(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x06, 0x03);
+ WriteReg(0x07, 0xF0);
+ }
+
+ void SetOutputState(uint8_t bit, uint8_t level) {
+ uint16_t data;
+ int index = bit;
+
+ if (bit < 8) {
+ data = ReadReg(0x02);
+ } else {
+ data = ReadReg(0x03);
+ index -= 8;
+ }
+
+ data = (data & ~(1 << index)) | (level << index);
+
+ if (bit < 8) {
+ WriteReg(0x02, data);
+ } else {
+ WriteReg(0x03, data);
+ }
+ }
+};
+
+class atk_dnesp32s3 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ XL9555* xl9555_;
+ EspVideo* camera_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+
+ // Initialize XL9555
+ xl9555_ = new XL9555(i2c_bus_, 0x20);
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = LCD_SCLK_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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ ESP_LOGD(TAG, "Install panel IO");
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 20 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
+ esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
+
+ esp_lcd_panel_reset(panel);
+ xl9555_->SetOutputState(8, 1);
+ xl9555_->SetOutputState(2, 0);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ 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);
+ }
+
+ // 鍒濆鍖栨憚鍍忓ご锛歰v2640锛�
+ // 鏍规嵁姝g偣鍘熷瓙瀹樻柟绀轰緥鍙傛暟
+ void InitializeCamera() {
+ xl9555_->SetOutputState(OV_PWDN_IO, 0); // PWDN=浣� (涓婄數)
+ xl9555_->SetOutputState(OV_RESET_IO, 0); // 纭繚澶嶄綅
+ vTaskDelay(pdMS_TO_TICKS(50)); // 寤堕暱澶嶄綅淇濇寔鏃堕棿
+ xl9555_->SetOutputState(OV_RESET_IO, 1); // 閲婃斁澶嶄綅
+ vTaskDelay(pdMS_TO_TICKS(50)); // 寤堕暱 50ms
+
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAM_PIN_D0,
+ [1] = CAM_PIN_D1,
+ [2] = CAM_PIN_D2,
+ [3] = CAM_PIN_D3,
+ [4] = CAM_PIN_D4,
+ [5] = CAM_PIN_D5,
+ [6] = CAM_PIN_D6,
+ [7] = CAM_PIN_D7,
+ },
+ .vsync_io = CAM_PIN_VSYNC,
+ .de_io = CAM_PIN_HREF,
+ .pclk_io = CAM_PIN_PCLK,
+ .xclk_io = CAM_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = true,
+ .i2c_config = {
+ .port = 1,
+ .scl_pin = CAM_PIN_SIOC,
+ .sda_pin = CAM_PIN_SIOD,
+ },
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAM_PIN_RESET, // 瀹為檯鐢� XL9555 鎺у埗
+ .pwdn_pin = CAM_PIN_PWDN, // 瀹為檯鐢� XL9555 鎺у埗
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = 20000000,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ }
+
+public:
+ atk_dnesp32s3() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ InitializeCamera();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8388AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8388_ADDR
+ );
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.h
new file mode 100644
index 0000000..c0b72da
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.h
@@ -0,0 +1,64 @@
+
+#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_3
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_9
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define BUILTIN_LED_GPIO GPIO_NUM_1
+
+#define LCD_SCLK_PIN GPIO_NUM_12
+#define LCD_MOSI_PIN GPIO_NUM_11
+#define LCD_MISO_PIN GPIO_NUM_13
+#define LCD_DC_PIN GPIO_NUM_40
+#define LCD_CS_PIN GPIO_NUM_21
+
+#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_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+/* 鐩告満寮曡剼閰嶇疆 */
+#define CAM_PIN_PWDN GPIO_NUM_NC
+#define CAM_PIN_RESET GPIO_NUM_NC
+#define CAM_PIN_VSYNC GPIO_NUM_47
+#define CAM_PIN_HREF GPIO_NUM_48
+#define CAM_PIN_PCLK GPIO_NUM_45
+#define CAM_PIN_XCLK GPIO_NUM_NC
+#define CAM_PIN_SIOD GPIO_NUM_39
+#define CAM_PIN_SIOC GPIO_NUM_38
+#define CAM_PIN_D0 GPIO_NUM_4
+#define CAM_PIN_D1 GPIO_NUM_5
+#define CAM_PIN_D2 GPIO_NUM_6
+#define CAM_PIN_D3 GPIO_NUM_7
+#define CAM_PIN_D4 GPIO_NUM_15
+#define CAM_PIN_D5 GPIO_NUM_16
+#define CAM_PIN_D6 GPIO_NUM_17
+#define CAM_PIN_D7 GPIO_NUM_18
+#define OV_PWDN_IO 4
+#define OV_RESET_IO 5
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.json
new file mode 100644
index 0000000..edbfeed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atk-dnesp32s3",
+ "sdkconfig_append": [
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc
new file mode 100644
index 0000000..9f2f701
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/atk_dnesp32s3m.cc
@@ -0,0 +1,220 @@
+#include "ml307_board.h"
+#include "codecs/es8388_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/single_led.h"
+#include "driver/gpio.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#define TAG "atk_dnesp32s3m_4g"
+
+class atk_dnesp32s3m_4g : public Ml307Board {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ Button phone_button_;
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = LCD_SCLK_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]() {
+ Application::GetInstance().ToggleChatState();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ //涓嶆彃鑰虫満
+ phone_button_.OnPressDown([this]() {
+ gpio_set_level(SPK_EN_PIN, 1);
+ });
+
+ //鎻掑叆鑰虫満
+ phone_button_.OnPressUp([this]() {
+ gpio_set_level(SPK_EN_PIN, 0);
+ });
+ }
+
+ void InitializeSt7735Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7735
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
+ esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
+
+ //浣胯兘鍔熸斁寮曡剼
+ gpio_config_t io_conf;
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+ gpio_set_level(SPK_EN_PIN, 0);
+
+ //妫�娴嬭�虫満鏄惁鎻掑叆锛屾彃鍏ユ椂涓洪珮鐢靛钩
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ //鑰虫満鎻掑叆
+ if (gpio_get_level(PHONE_CK_PIN)) {
+ gpio_set_level(SPK_EN_PIN, 1);
+ }
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+
+ uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
+ uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
+ esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
+ esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
+
+ 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);
+ }
+
+public:
+ atk_dnesp32s3m_4g() : Ml307Board(Module_4G_TX_PIN, Module_4G_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
+ phone_button_(PHONE_CK_PIN, true) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7735Display();
+ InitializeButtons();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8388AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8388_ADDR
+ );
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3m_4g);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/config.h
new file mode 100644
index 0000000..92505c5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-4g/config.h
@@ -0,0 +1,53 @@
+#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
+
+#define Module_4G_RX_PIN GPIO_NUM_21
+#define Module_4G_TX_PIN GPIO_NUM_45
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define BUILTIN_LED_GPIO GPIO_NUM_1
+
+#define LCD_SCLK_PIN GPIO_NUM_12
+#define LCD_MOSI_PIN GPIO_NUM_11
+#define LCD_MISO_PIN GPIO_NUM_13
+#define LCD_DC_PIN GPIO_NUM_40
+#define LCD_CS_PIN GPIO_NUM_39
+#define LCD_RST_PIN GPIO_NUM_38
+
+#define SPK_EN_PIN GPIO_NUM_42
+#define PHONE_CK_PIN GPIO_NUM_3
+
+#define DISPLAY_WIDTH 160
+#define DISPLAY_HEIGHT 80
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_OFFSET_X 1
+#define DISPLAY_OFFSET_Y 26
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc
new file mode 100644
index 0000000..76f5b39
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/atk_dnesp32s3m.cc
@@ -0,0 +1,230 @@
+#include "wifi_board.h"
+#include "codecs/es8388_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/single_led.h"
+#include "driver/gpio.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#define TAG "atk_dnesp32s3m_wifi"
+
+class atk_dnesp32s3m_wifi : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ Button phone_button_;
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = LCD_SCLK_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();
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ //涓嶆彃鑰虫満
+ phone_button_.OnPressDown([this]() {
+ gpio_set_level(SPK_EN_PIN, 1);
+ });
+
+ //鎻掑叆鑰虫満
+ phone_button_.OnPressUp([this]() {
+ gpio_set_level(SPK_EN_PIN, 0);
+ });
+
+ }
+
+ void InitializeSt7735Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ 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_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
+
+ //浣胯兘鍔熸斁寮曡剼
+ gpio_config_t io_conf;
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << SPK_EN_PIN);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+ gpio_set_level(SPK_EN_PIN, 0);
+
+ //妫�娴嬭�虫満鏄惁鎻掑叆锛屾彃鍏ユ椂涓洪珮鐢靛钩
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << PHONE_CK_PIN);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ //鑰虫満鎻掑叆
+ if (gpio_get_level(PHONE_CK_PIN)) {
+ gpio_set_level(SPK_EN_PIN, 1);
+ }
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+
+ uint8_t data0[] = {0x02, 0x1c, 0x07, 0x12, 0x37, 0x32, 0x29, 0x2d, 0x29, 0x25, 0x2B, 0x39, 0x00, 0x01, 0x03, 0x10};
+ uint8_t data1[] = {0x03, 0x1d, 0x07, 0x06, 0x2E, 0x2C, 0x29, 0x2D, 0x2E, 0x2E, 0x37, 0x3F, 0x00, 0x00, 0x02, 0x10};
+ esp_lcd_panel_io_tx_param(panel_io, 0xe0, data0, 16);
+ esp_lcd_panel_io_tx_param(panel_io, 0xe1, data1, 16);
+
+ 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);
+ }
+
+public:
+ atk_dnesp32s3m_wifi() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
+ phone_button_(PHONE_CK_PIN, true) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7735Display();
+ InitializeButtons();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8388AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8388_ADDR
+ );
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+};
+
+DECLARE_BOARD(atk_dnesp32s3m_wifi);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/config.h
new file mode 100644
index 0000000..df22181
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atk-dnesp32s3m-wifi/config.h
@@ -0,0 +1,52 @@
+#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 VOLUME_UP_BUTTON_GPIO GPIO_NUM_48
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_47
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define BUILTIN_LED_GPIO GPIO_NUM_1
+
+#define LCD_SCLK_PIN GPIO_NUM_12
+#define LCD_MOSI_PIN GPIO_NUM_11
+#define LCD_MISO_PIN GPIO_NUM_13
+#define LCD_DC_PIN GPIO_NUM_40
+#define LCD_CS_PIN GPIO_NUM_39
+#define LCD_RST_PIN GPIO_NUM_38
+
+#define SPK_EN_PIN GPIO_NUM_42
+#define PHONE_CK_PIN GPIO_NUM_3
+
+#define DISPLAY_WIDTH 160
+#define DISPLAY_HEIGHT 80
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_OFFSET_X 1
+#define DISPLAY_OFFSET_Y 26
+// #define DISPLAY_OFFSET_X 0
+// #define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_41
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/README.md b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/README.md
new file mode 100644
index 0000000..ccb452f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/README.md
@@ -0,0 +1,45 @@
+# AtomEchoS3R
+## 绠�浠�
+
+AtomEchoS3R 鏄� M5Stack 鎺ㄥ嚭鐨勫熀浜� ESP32-S3-PICO-1-N8R8 鐨勭墿鑱旂綉鍙紪绋嬫帶鍒跺櫒锛岄噰鐢ㄤ簡 ES8311 鍗曞0閬撻煶棰戣В鐮佸櫒銆丮EMS 楹﹀厠椋庡拰 NS4150B 鍔熺巼鏀惧ぇ鍣ㄧ殑闆嗘垚鏂规銆�
+
+寮�鍙戠増**涓嶅甫灞忓箷銆佷笉甯﹂澶栨寜閿�**锛岄渶瑕佷娇鐢ㄨ闊冲敜閱掋�傚繀瑕佹椂锛岄渶瑕佷娇鐢� `idf.py monitor` 鏌ョ湅 log 浠ョ‘瀹氳繍琛岀姸鎬併��
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig 骞堕厤缃�**
+
+```bash
+idf.py menuconfig
+```
+
+鍒嗗埆閰嶇疆濡備笅閫夐」锛�
+
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `AtomEchoS3R`
+- `Partition Table` 鈫� `Custom partition CSV file` 鈫� 鍒犻櫎鍘熸湁鍐呭锛岃緭鍏� `partitions/v2/8m.csv`
+- `Serial flasher config` 鈫� `Flash size` 鈫� 閫夋嫨 `8 MB`
+- `Component config` 鈫� `ESP PSRAM` 鈫� `Support for external, SPI-connected RAM` 鈫� `SPI RAM config` 鈫� 閫夋嫨 `Octal Mode PSRAM`
+
+鎸� `S` 淇濆瓨锛屾寜 `Q` 閫�鍑恒��
+
+**缂栬瘧**
+
+```bash
+idf.py build
+```
+
+**鐑у綍**
+
+灏� AtomEchoS3R 杩炴帴鍒扮數鑴戯紝鎸変綇渚ч潰 RESET 鎸夐敭锛岀洿鍒� RESET 鎸夐敭涓嬫柟缁跨伅闂儊銆�
+
+```bash
+idf.py flash
+```
+
+鐑у綍瀹屾瘯鍚庯紝鎸変竴涓� RESET 鎸夐挳閲嶅惎璁惧銆�
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/atom_echos3r.cc b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/atom_echos3r.cc
new file mode 100644
index 0000000..ec5b61b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/atom_echos3r.cc
@@ -0,0 +1,91 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+
+#define TAG "AtomEchoS3R"
+
+class AtomEchoS3rBaseBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+public:
+ AtomEchoS3rBaseBoard() : boot_button_(USER_BUTTON_GPIO) {
+ InitializeI2c();
+ I2cDetect();
+ InitializeButtons();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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_GPIO_PA,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ return &audio_codec;
+ }
+};
+
+DECLARE_BOARD(AtomEchoS3rBaseBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.h b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.h
new file mode 100644
index 0000000..4803c42
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.h
@@ -0,0 +1,29 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// AtomEchoS3R Board configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_3
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_45
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_0
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_GPIO_PA GPIO_NUM_18
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define USER_BUTTON_GPIO GPIO_NUM_41
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.json b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.json
new file mode 100644
index 0000000..7e7328d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atom-echos3r/config.json
@@ -0,0 +1,12 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atom-echos3r",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/README.md b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/README.md
new file mode 100644
index 0000000..67ab32f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/README.md
@@ -0,0 +1,25 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32锛�**
+
+```bash
+idf.py set-target esp32
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> AtomMatrix + Echo Base
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc
new file mode 100644
index 0000000..97ae7e3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/atommatrix_echo_base.cc
@@ -0,0 +1,133 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include "led/circular_strip.h"
+
+#define TAG "XX+EchoBase"
+
+#define PI4IOE_ADDR 0x43
+#define PI4IOE_REG_CTRL 0x00
+#define PI4IOE_REG_IO_PP 0x07
+#define PI4IOE_REG_IO_DIR 0x03
+#define PI4IOE_REG_IO_OUT 0x05
+#define PI4IOE_REG_IO_PULLUP 0x0D
+
+class Pi4ioe : public I2cDevice {
+public:
+ Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
+ WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
+ WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
+ WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
+ }
+
+ void SetSpeakerMute(bool mute) {
+ WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
+ }
+};
+
+class AtomMatrixEchoBaseBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+
+ Pi4ioe* pi4ioe_;
+
+ Button face_button_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializePi4ioe() {
+ ESP_LOGI(TAG, "Init PI4IOE");
+ pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
+ pi4ioe_->SetSpeakerMute(false);
+ }
+
+ void InitializeButtons() {
+ face_button_.OnClick([this]() {
+
+ ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ AtomMatrixEchoBaseBoard() : face_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ I2cDetect();
+ InitializePi4ioe();
+ InitializeButtons();
+ }
+
+ virtual Led* GetLed() override {
+ static CircularStrip led(BUILTIN_LED_GPIO, 25);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_1,
+ 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_GPIO_PA,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ return &audio_codec;
+ }
+
+};
+
+DECLARE_BOARD(AtomMatrixEchoBaseBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.h b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.h
new file mode 100644
index 0000000..d1684cb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.h
@@ -0,0 +1,29 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// AtomMatrix+EchoBase Board configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_19
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_33
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_23
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_22
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
+
+#define BUILTIN_LED_GPIO GPIO_NUM_27
+#define BOOT_BUTTON_GPIO GPIO_NUM_39
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.json b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.json
new file mode 100644
index 0000000..794d574
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atommatrix-echo-base/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32",
+ "builds": [
+ {
+ "name": "atommatrix-echo-base",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/README.md b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/README.md
new file mode 100644
index 0000000..ef2ad28
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/README.md
@@ -0,0 +1,49 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> AtomS3 + Echo Base
+```
+
+**鍏抽棴璇煶鍞ら啋锛�**
+
+```
+Xiaozhi Assistant -> [ ] 鍚敤璇煶鍞ら啋涓庨煶棰戝鐞� -> Unselect
+```
+
+**淇敼 flash 澶у皬锛�**
+
+```
+Serial flasher config -> Flash size -> 8 MB
+```
+
+**淇敼鍒嗗尯琛細**
+
+```
+Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
+```
+
+**鍏抽棴鐗囧 PSRAM锛�**
+
+```
+Component config -> ESP PSRAM -> [ ] Support for external, SPI-connected RAM -> Unselect
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/atoms3_echo_base.cc b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/atoms3_echo_base.cc
new file mode 100644
index 0000000..bbff9ed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/atoms3_echo_base.cc
@@ -0,0 +1,232 @@
+#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 "i2c_device.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+
+#define TAG "AtomS3+EchoBase"
+
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb2, (uint8_t[]){0x2f}, 1, 0},
+ {0xb3, (uint8_t[]){0x03}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x01}, 1, 0},
+ {0xac, (uint8_t[]){0xcb}, 1, 0},
+ {0xab, (uint8_t[]){0x0e}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x19}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xe8, (uint8_t[]){0x24}, 1, 0},
+ {0xe9, (uint8_t[]){0x48}, 1, 0},
+ {0xea, (uint8_t[]){0x22}, 1, 0},
+ {0xc6, (uint8_t[]){0x30}, 1, 0},
+ {0xc7, (uint8_t[]){0x18}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
+ 0x00, 0x1c, 0x1f, 0x0f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
+ 0x07, 0x0d, 0x11, 0x0f},
+ 14, 0},
+};
+
+class AtomS3EchoBaseBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Display* display_;
+ Button boot_button_;
+ bool is_echo_base_connected_ = false;
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ is_echo_base_connected_ = false;
+ uint8_t echo_base_connected_flag = 0x00;
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ if (address == 0x18) {
+ echo_base_connected_flag |= 0xF0;
+ } else if (address == 0x43) {
+ echo_base_connected_flag |= 0x0F;
+ }
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
+ }
+
+ void CheckEchoBaseConnection() {
+ if (is_echo_base_connected_) {
+ return;
+ }
+
+ // Pop error page
+ InitializeSpi();
+ InitializeGc9107Display();
+ InitializeButtons();
+ GetBacklight()->SetBrightness(100);
+
+ // Ensure UI is set up before displaying error
+ display_->SetupUI();
+
+ display_->SetStatus(Lang::Strings::ERROR);
+ display_->SetEmotion("triangle_exclamation");
+ display_->SetChatMessage("system", "Echo Base\nnot connected");
+
+ while (1) {
+ ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ // Rerun detection
+ I2cDetect();
+ if (is_echo_base_connected_) {
+ vTaskDelay(pdMS_TO_TICKS(500));
+ I2cDetect();
+ if (is_echo_base_connected_) {
+ ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_restart();
+ }
+ }
+ }
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_21;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_17;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeGc9107Display() {
+ ESP_LOGI(TAG, "Init GC9107 display");
+
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_15;
+ io_config.dc_gpio_num = GPIO_NUM_33;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &io_handle));
+
+ ESP_LOGI(TAG, "Install GC9A01 panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_34; // Set to -1 if not use
+ panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
+ panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
+ panel_config.vendor_config = &gc9107_vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
+
+ display_ = new SpiLcdDisplay(io_handle, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ AtomS3EchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ I2cDetect();
+ CheckEchoBaseConnection();
+ InitializeSpi();
+ InitializeGc9107Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_1,
+ 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_GPIO_PA,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
+ return &backlight;
+ }
+};
+
+DECLARE_BOARD(AtomS3EchoBaseBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.h b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.h
new file mode 100644
index 0000000..6b2fdad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.h
@@ -0,0 +1,43 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// AtomS3+EchoBase Board configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_41
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 32
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.json b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.json
new file mode 100644
index 0000000..cd2fd6e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3-echo-base/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atoms3-echo-base",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM=n",
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/README.md b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/README.md
new file mode 100644
index 0000000..6da3280
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/README.md
@@ -0,0 +1,54 @@
+# AtomS3R CAM/M12 + Echo Base
+
+## 绠�浠�
+
+<div align="center">
+ <a href="https://docs.m5stack.com/zh_CN/core/AtomS3R%20Cam"><b> AtomS3R CAM 浜у搧涓婚〉 </b></a>
+ |
+ <a href="https://docs.m5stack.com/zh_CN/core/AtomS3R-M12"><b> AtomS3R M12 浜у搧涓婚〉 </b></a>
+ |
+ <a href="https://docs.m5stack.com/zh_CN/atom/Atomic%20Echo%20Base"><b> Echo Base 浜у搧涓婚〉 </b></a>
+</div>
+
+AtomS3R CAM銆丄tomS3R M12 鏄� M5Stack 鎺ㄥ嚭鐨勫熀浜� ESP32-S3-PICO-1-N8R8 鐨勭墿鑱旂綉鍙紪绋嬫帶鍒跺櫒锛屾惌杞戒簡鎽勫儚澶淬�侫tomic Echo Base 鏄竴娆句笓涓� M5 Atom 绯诲垪涓绘満璁捐鐨勮闊宠瘑鍒簳搴э紝閲囩敤浜� ES8311 鍗曞0閬撻煶棰戣В鐮佸櫒銆丮EMS 楹﹀厠椋庡拰 NS4150B 鍔熺巼鏀惧ぇ鍣ㄧ殑闆嗘垚鏂规銆�
+
+涓ゆ寮�鍙戠増鍧�**涓嶅甫灞忓箷銆佷笉甯﹂澶栨寜閿�**锛岄渶瑕佷娇鐢ㄨ闊冲敜閱掋�傚繀瑕佹椂锛岄渶瑕佷娇鐢� `idf.py monitor` 鏌ョ湅 log 浠ョ‘瀹氳繍琛岀姸鎬併��
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig 骞堕厤缃�**
+
+```bash
+idf.py menuconfig
+```
+
+鍒嗗埆閰嶇疆濡備笅閫夐」锛�
+
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `AtomS3R CAM/M12 + Echo Base`
+- `Xiaozhi Assistant` 鈫� `IoT Protocol` 鈫� 閫夋嫨 `MCP鍗忚` 鍙紑鍚憚鍍忓ご璇嗗埆鍔熻兘
+- `Partition Table` 鈫� `Custom partition CSV file` 鈫� 鍒犻櫎鍘熸湁鍐呭锛岃緭鍏� `partitions/v2/8m.csv`
+- `Serial flasher config` 鈫� `Flash size` 鈫� 閫夋嫨 `8 MB`
+
+鎸� `S` 淇濆瓨锛屾寜 `Q` 閫�鍑恒��
+
+**缂栬瘧**
+
+```bash
+idf.py build
+```
+
+**鐑у綍**
+
+灏� AtomS3R CAM/M12 杩炴帴鍒扮數鑴戯紝鎸変綇渚ч潰 RESET 鎸夐敭锛岀洿鍒� RESET 鎸夐敭涓嬫柟缁跨伅闂儊銆�
+
+```bash
+idf.py flash
+```
+
+鐑у綍瀹屾瘯鍚庯紝鎸変竴涓� RESET 鎸夐挳閲嶅惎銆�
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc
new file mode 100644
index 0000000..c889288
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/atoms3r_cam_m12_echo_base.cc
@@ -0,0 +1,190 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include "esp32_camera.h"
+
+#define TAG "AtomS3R CAM/M12 + EchoBase"
+
+#define PI4IOE_ADDR 0x43
+#define PI4IOE_REG_CTRL 0x00
+#define PI4IOE_REG_IO_PP 0x07
+#define PI4IOE_REG_IO_DIR 0x03
+#define PI4IOE_REG_IO_OUT 0x05
+#define PI4IOE_REG_IO_PULLUP 0x0D
+
+class Pi4ioe : public I2cDevice {
+public:
+ Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
+ WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
+ WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
+ WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
+ }
+
+ void SetSpeakerMute(bool mute) {
+ WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
+ }
+};
+
+class AtomS3rCamM12EchoBaseBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pi4ioe* pi4ioe_ = nullptr;
+ bool is_echo_base_connected_ = false;
+ Esp32Camera* camera_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ is_echo_base_connected_ = false;
+ uint8_t echo_base_connected_flag = 0x00;
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ if (address == 0x18) {
+ echo_base_connected_flag |= 0xF0;
+ } else if (address == 0x43) {
+ echo_base_connected_flag |= 0x0F;
+ }
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
+ }
+
+ void CheckEchoBaseConnection() {
+ if (is_echo_base_connected_) {
+ return;
+ }
+
+ while (1) {
+ ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ // Rerun detection
+ I2cDetect();
+ if (is_echo_base_connected_) {
+ vTaskDelay(pdMS_TO_TICKS(500));
+ I2cDetect();
+ if (is_echo_base_connected_) {
+ ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_restart();
+ }
+ }
+ }
+ }
+
+ void InitializePi4ioe() {
+ ESP_LOGI(TAG, "Init PI4IOE");
+ pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
+ pi4ioe_->SetSpeakerMute(false);
+ }
+
+ void EnableCameraPower() {
+ gpio_reset_pin((gpio_num_t)18);
+ gpio_set_direction((gpio_num_t)18, GPIO_MODE_OUTPUT);
+ gpio_set_pull_mode((gpio_num_t)18, GPIO_PULLDOWN_ONLY);
+
+ ESP_LOGI(TAG, "Camera Power Enabled");
+
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ }
+
+ void InitializeCamera() {
+ camera_config_t config = {};
+ config.pin_d0 = CAMERA_PIN_D0;
+ config.pin_d1 = CAMERA_PIN_D1;
+ config.pin_d2 = CAMERA_PIN_D2;
+ config.pin_d3 = CAMERA_PIN_D3;
+ config.pin_d4 = CAMERA_PIN_D4;
+ config.pin_d5 = CAMERA_PIN_D5;
+ config.pin_d6 = CAMERA_PIN_D6;
+ config.pin_d7 = CAMERA_PIN_D7;
+ config.pin_xclk = CAMERA_PIN_XCLK;
+ config.pin_pclk = CAMERA_PIN_PCLK;
+ config.pin_vsync = CAMERA_PIN_VSYNC;
+ config.pin_href = CAMERA_PIN_HREF;
+ config.pin_sccb_sda = CAMERA_PIN_SIOD;
+ config.pin_sccb_scl = CAMERA_PIN_SIOC;
+ config.sccb_i2c_port = 1;
+ config.pin_pwdn = CAMERA_PIN_PWDN;
+ config.pin_reset = CAMERA_PIN_RESET;
+ config.xclk_freq_hz = XCLK_FREQ_HZ;
+ config.pixel_format = PIXFORMAT_RGB565;
+ config.frame_size = FRAMESIZE_QVGA;
+ config.jpeg_quality = 12;
+ config.fb_count = 1;
+ config.fb_location = CAMERA_FB_IN_PSRAM;
+ config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
+
+ camera_ = new Esp32Camera(config);
+ camera_->SetHMirror(false);
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+public:
+ AtomS3rCamM12EchoBaseBoard() {
+ EnableCameraPower(); // IO18 杩樹細鎺у埗鎸囩ず鐏�
+ InitializeCamera();
+ InitializeI2c();
+ I2cDetect();
+ CheckEchoBaseConnection();
+ InitializePi4ioe();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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_GPIO_PA,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ return &audio_codec;
+ }
+};
+
+DECLARE_BOARD(AtomS3rCamM12EchoBaseBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.h b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.h
new file mode 100644
index 0000000..61fef49
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.h
@@ -0,0 +1,49 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// AtomS3R M12+EchoBase Board configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_41
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_VSYNC GPIO_NUM_10
+#define CAMERA_PIN_HREF GPIO_NUM_14
+#define CAMERA_PIN_PCLK GPIO_NUM_40
+#define CAMERA_PIN_XCLK GPIO_NUM_21
+#define CAMERA_PIN_SIOD GPIO_NUM_12
+#define CAMERA_PIN_SIOC GPIO_NUM_9
+#define CAMERA_PIN_D0 GPIO_NUM_3
+#define CAMERA_PIN_D1 GPIO_NUM_42
+#define CAMERA_PIN_D2 GPIO_NUM_46
+#define CAMERA_PIN_D3 GPIO_NUM_48
+#define CAMERA_PIN_D4 GPIO_NUM_4
+#define CAMERA_PIN_D5 GPIO_NUM_17
+#define CAMERA_PIN_D6 GPIO_NUM_11
+#define CAMERA_PIN_D7 GPIO_NUM_13
+#define CAMERA_XCLK_FREQ (20000000)
+#define XCLK_FREQ_HZ CAMERA_XCLK_FREQ
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.json b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.json
new file mode 100644
index 0000000..b8d6629
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-cam-m12-echo-base/config.json
@@ -0,0 +1,12 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atoms3r-cam-m12-echo-base",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/README.md b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/README.md
new file mode 100644
index 0000000..13e8a36
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/README.md
@@ -0,0 +1,43 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> AtomS3R + Echo Base
+```
+
+**淇敼 flash 澶у皬锛�**
+
+```
+Serial flasher config -> Flash size -> 8 MB
+```
+
+**淇敼鍒嗗尯琛細**
+
+```
+Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
+```
+
+**淇敼 psram 閰嶇疆锛�**
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc
new file mode 100644
index 0000000..fed0a24
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc
@@ -0,0 +1,312 @@
+#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 "i2c_device.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+
+#define TAG "AtomS3R+EchoBase"
+
+#define PI4IOE_ADDR 0x43
+#define PI4IOE_REG_CTRL 0x00
+#define PI4IOE_REG_IO_PP 0x07
+#define PI4IOE_REG_IO_DIR 0x03
+#define PI4IOE_REG_IO_OUT 0x05
+#define PI4IOE_REG_IO_PULLUP 0x0D
+
+class Pi4ioe : public I2cDevice {
+public:
+ Pi4ioe(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(PI4IOE_REG_IO_PP, 0x00); // Set to high-impedance
+ WriteReg(PI4IOE_REG_IO_PULLUP, 0xFF); // Enable pull-up
+ WriteReg(PI4IOE_REG_IO_DIR, 0x6E); // Set input=0, output=1
+ WriteReg(PI4IOE_REG_IO_OUT, 0xFF); // Set outputs to 1
+ }
+
+ void SetSpeakerMute(bool mute) {
+ WriteReg(PI4IOE_REG_IO_OUT, mute ? 0x00 : 0xFF);
+ }
+};
+
+class Lp5562 : public I2cDevice {
+public:
+ Lp5562(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x00, 0B01000000); // Set chip_en to 1
+ WriteReg(0x08, 0B00000001); // Enable internal clock
+ WriteReg(0x70, 0B00000000); // Configure all LED outputs to be controlled from I2C registers
+
+ // PWM clock frequency 558 Hz
+ auto data = ReadReg(0x08);
+ data = data | 0B01000000;
+ WriteReg(0x08, data);
+ }
+
+ void SetBrightness(uint8_t brightness) {
+ // Map 0~100 to 0~255
+ brightness = brightness * 255 / 100;
+ WriteReg(0x0E, brightness);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(Lp5562* lp5562) : lp5562_(lp5562) {}
+
+ void SetBrightnessImpl(uint8_t brightness) override {
+ if (lp5562_) {
+ lp5562_->SetBrightness(brightness);
+ } else {
+ ESP_LOGE(TAG, "LP5562 not available");
+ }
+ }
+
+private:
+ Lp5562* lp5562_ = nullptr;
+};
+
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb2, (uint8_t[]){0x2f}, 1, 0},
+ {0xb3, (uint8_t[]){0x03}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x01}, 1, 0},
+ {0xac, (uint8_t[]){0xcb}, 1, 0},
+ {0xab, (uint8_t[]){0x0e}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x19}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xe8, (uint8_t[]){0x24}, 1, 0},
+ {0xe9, (uint8_t[]){0x48}, 1, 0},
+ {0xea, (uint8_t[]){0x22}, 1, 0},
+ {0xc6, (uint8_t[]){0x30}, 1, 0},
+ {0xc7, (uint8_t[]){0x18}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1f, 0x28, 0x04, 0x3e, 0x2a, 0x2e, 0x20, 0x00, 0x0c, 0x06,
+ 0x00, 0x1c, 0x1f, 0x0f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x00, 0x2d, 0x2f, 0x3c, 0x6f, 0x1c, 0x0b, 0x00, 0x00, 0x00,
+ 0x07, 0x0d, 0x11, 0x0f},
+ 14, 0},
+};
+
+class AtomS3rEchoBaseBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ i2c_master_bus_handle_t i2c_bus_internal_;
+ Pi4ioe* pi4ioe_ = nullptr;
+ Lp5562* lp5562_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ bool is_echo_base_connected_ = false;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+
+ i2c_bus_cfg.i2c_port = I2C_NUM_0;
+ i2c_bus_cfg.sda_io_num = GPIO_NUM_45;
+ i2c_bus_cfg.scl_io_num = GPIO_NUM_0;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_internal_));
+ }
+
+ void I2cDetect() {
+ is_echo_base_connected_ = false;
+ uint8_t echo_base_connected_flag = 0x00;
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ if (address == 0x18) {
+ echo_base_connected_flag |= 0xF0;
+ } else if (address == 0x43) {
+ echo_base_connected_flag |= 0x0F;
+ }
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ is_echo_base_connected_ = (echo_base_connected_flag == 0xFF);
+ }
+
+ void CheckEchoBaseConnection() {
+ if (is_echo_base_connected_) {
+ return;
+ }
+
+ // Pop error page
+ InitializeLp5562();
+ InitializeSpi();
+ InitializeGc9107Display();
+ InitializeButtons();
+ GetBacklight()->SetBrightness(100);
+
+ // Ensure UI is set up before displaying error
+ display_->SetupUI();
+
+ display_->SetStatus(Lang::Strings::ERROR);
+ display_->SetEmotion("triangle_exclamation");
+ display_->SetChatMessage("system", "Echo Base\nnot connected");
+
+ while (1) {
+ ESP_LOGE(TAG, "Atomic Echo Base is disconnected");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ // Rerun detection
+ I2cDetect();
+ if (is_echo_base_connected_) {
+ vTaskDelay(pdMS_TO_TICKS(500));
+ I2cDetect();
+ if (is_echo_base_connected_) {
+ ESP_LOGI(TAG, "Atomic Echo Base is reconnected");
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_restart();
+ }
+ }
+ }
+ }
+
+ void InitializePi4ioe() {
+ ESP_LOGI(TAG, "Init PI4IOE");
+ pi4ioe_ = new Pi4ioe(i2c_bus_, PI4IOE_ADDR);
+ pi4ioe_->SetSpeakerMute(false);
+ }
+
+ void InitializeLp5562() {
+ ESP_LOGI(TAG, "Init LP5562");
+ lp5562_ = new Lp5562(i2c_bus_internal_, 0x30);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_21;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_15;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeGc9107Display() {
+ ESP_LOGI(TAG, "Init GC9107 display");
+
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_14;
+ io_config.dc_gpio_num = GPIO_NUM_42;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &io_handle));
+
+ ESP_LOGI(TAG, "Install GC9A01 panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_48; // Set to -1 if not use
+ panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR;
+ panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
+ panel_config.vendor_config = &gc9107_vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
+
+ display_ = new SpiLcdDisplay(io_handle, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ AtomS3rEchoBaseBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ I2cDetect();
+ CheckEchoBaseConnection();
+ InitializePi4ioe();
+ InitializeLp5562();
+ InitializeSpi();
+ InitializeGc9107Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_1,
+ 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_GPIO_PA,
+ AUDIO_CODEC_ES8311_ADDR,
+ false);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight *GetBacklight() override {
+ static CustomBacklight backlight(lp5562_);
+ return &backlight;
+ }
+};
+
+DECLARE_BOARD(AtomS3rEchoBaseBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.h b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.h
new file mode 100644
index 0000000..d519c2e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.h
@@ -0,0 +1,43 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// AtomS3R+EchoBase Board configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_6
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_41
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 32
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.json b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.json
new file mode 100644
index 0000000..d3b1549
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/atoms3r-echo-base/config.json
@@ -0,0 +1,12 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "atoms3r-echo-base",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.h
new file mode 100644
index 0000000..d9a2148
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.h
@@ -0,0 +1,278 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
+
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_5
+#define ASR_BUTTON_GPIO GPIO_NUM_19
+#define BUILTIN_LED_GPIO GPIO_NUM_2
+
+#define ML307_RX_PIN GPIO_NUM_16
+#define ML307_TX_PIN GPIO_NUM_17
+
+#ifdef CONFIG_LCD_ST7789_240X240_7PIN
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22
+#define DISPLAY_CS_PIN GPIO_NUM_NC
+#else
+#define DISPLAY_CS_PIN GPIO_NUM_22
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_23
+#endif
+
+#define DISPLAY_MOSI_PIN GPIO_NUM_4
+#define DISPLAY_CLK_PIN GPIO_NUM_15
+#define DISPLAY_DC_PIN GPIO_NUM_21
+#define DISPLAY_RST_PIN GPIO_NUM_18
+
+
+#ifdef CONFIG_LCD_ST7789_240X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_170X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 170
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 35
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_172X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 172
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 34
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X280
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240_7PIN
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 2
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X135
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 135
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 40
+#define DISPLAY_OFFSET_Y 53
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X160
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 160
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X128
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 32
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7796_320X480
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_GC9A01_240X240
+#define LCD_TYPE_GC9A01_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_CUSTOM
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.json b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.json
new file mode 100644
index 0000000..07a853a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32",
+ "builds": [
+ {
+ "name": "bread-compact-esp32-lcd",
+ "sdkconfig_append": [
+ "LCD_ST7789_240X240_7PIN=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc
new file mode 100644
index 0000000..ce96efd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32-lcd/esp32_bread_board_lcd.cc
@@ -0,0 +1,200 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+#include "esp_lcd_ili9341.h"
+#endif
+
+#if defined(LCD_TYPE_GC9A01_SERIAL)
+#include "esp_lcd_gc9a01.h"
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb1, (uint8_t[]){0x80}, 1, 0},
+ {0xb2, (uint8_t[]){0x27}, 1, 0},
+ {0xb3, (uint8_t[]){0x13}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x05}, 1, 0},
+ {0xac, (uint8_t[]){0xc8}, 1, 0},
+ {0xab, (uint8_t[]){0x0f}, 1, 0},
+ {0x3a, (uint8_t[]){0x05}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x08}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xea, (uint8_t[]){0x02}, 1, 0},
+ {0xe8, (uint8_t[]){0x2A}, 1, 0},
+ {0xe9, (uint8_t[]){0x47}, 1, 0},
+ {0xe7, (uint8_t[]){0x5f}, 1, 0},
+ {0xc6, (uint8_t[]){0x21}, 1, 0},
+ {0xc7, (uint8_t[]){0x15}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
+ 0x04, 0x12, 0x14, 0x1f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
+ 0x0C, 0x1A, 0x14, 0x1E},
+ 14, 0},
+ {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
+ {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
+};
+#endif
+
+#define TAG "ESP32-LCD-MarsbearSupport"
+
+class CompactWifiBoardLCD : public WifiBoard {
+private:
+ Button boot_button_;
+ Button touch_button_;
+ Button asr_button_;
+
+ LcdDisplay* display_;
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+#elif defined(LCD_TYPE_GC9A01_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+#else
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
+#endif
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+#ifdef LCD_TYPE_GC9A01_SERIAL
+ panel_config.vendor_config = &gc9107_vendor_config;
+#endif
+ 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 InitializeButtons() {
+
+ // 閰嶇疆 GPIO
+ gpio_config_t io_conf = {
+ .pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 璁剧疆闇�瑕侀厤缃殑 GPIO 寮曡剼
+ .mode = GPIO_MODE_OUTPUT, // 璁剧疆涓鸿緭鍑烘ā寮�
+ .pull_up_en = GPIO_PULLUP_DISABLE, // 绂佺敤涓婃媺
+ .pull_down_en = GPIO_PULLDOWN_DISABLE, // 绂佺敤涓嬫媺
+ .intr_type = GPIO_INTR_DISABLE // 绂佺敤涓柇
+ };
+ gpio_config(&io_conf); // 搴旂敤閰嶇疆
+
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ gpio_set_level(BUILTIN_LED_GPIO, 1);
+ app.ToggleChatState();
+ });
+
+ asr_button_.OnClick([this]() {
+ std::string wake_word="浣犲ソ灏忔櫤";
+ Application::GetInstance().WakeWordInvoke(wake_word);
+ });
+
+ touch_button_.OnPressDown([this]() {
+ gpio_set_level(BUILTIN_LED_GPIO, 1);
+ Application::GetInstance().StartListening();
+ });
+
+ touch_button_.OnPressUp([this]() {
+ gpio_set_level(BUILTIN_LED_GPIO, 0);
+ Application::GetInstance().StopListening();
+ });
+
+ }
+
+public:
+ CompactWifiBoardLCD() : WifiBoard(),
+ boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+};
+
+DECLARE_BOARD(CompactWifiBoardLCD);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/README.md b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/README.md
new file mode 100644
index 0000000..617e83b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/README.md
@@ -0,0 +1,25 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32锛�**
+
+```bash
+idf.py set-target esp32
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> 闈㈠寘鏉� ESP32 DevKit
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.h
new file mode 100644
index 0000000..8f48005
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.h
@@ -0,0 +1,58 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
+
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_5
+#define ASR_BUTTON_GPIO GPIO_NUM_19
+#define BUILTIN_LED_GPIO GPIO_NUM_2
+
+#define ML307_RX_PIN GPIO_NUM_16
+#define ML307_TX_PIN GPIO_NUM_17
+
+#define DISPLAY_SDA_PIN GPIO_NUM_4
+#define DISPLAY_SCL_PIN GPIO_NUM_15
+#define DISPLAY_WIDTH 128
+
+#if CONFIG_OLED_SSD1306_128X32
+#define DISPLAY_HEIGHT 32
+#elif CONFIG_OLED_SSD1306_128X64
+#define DISPLAY_HEIGHT 64
+#else
+#error "OLED display type is not selected"
+#endif
+
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_18
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.json b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.json
new file mode 100644
index 0000000..9a7dd55
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32",
+ "builds": [
+ {
+ "name": "bread-compact-esp32",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X64=y"
+ ]
+ },
+ {
+ "name": "bread-compact-esp32-128x32",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X32=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/esp32_bread_board.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/esp32_bread_board.cc
new file mode 100644
index 0000000..0dac72f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-esp32/esp32_bread_board.cc
@@ -0,0 +1,162 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "display/oled_display.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "ESP32-MarsbearSupport"
+
+class CompactWifiBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ Button touch_button_;
+ Button asr_button_;
+
+ i2c_master_bus_handle_t display_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+
+ // 閰嶇疆 GPIO
+ gpio_config_t io_conf = {
+ .pin_bit_mask = 1ULL << BUILTIN_LED_GPIO, // 璁剧疆闇�瑕侀厤缃殑 GPIO 寮曡剼
+ .mode = GPIO_MODE_OUTPUT, // 璁剧疆涓鸿緭鍑烘ā寮�
+ .pull_up_en = GPIO_PULLUP_DISABLE, // 绂佺敤涓婃媺
+ .pull_down_en = GPIO_PULLDOWN_DISABLE, // 绂佺敤涓嬫媺
+ .intr_type = GPIO_INTR_DISABLE // 绂佺敤涓柇
+ };
+ gpio_config(&io_conf); // 搴旂敤閰嶇疆
+
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ gpio_set_level(BUILTIN_LED_GPIO, 1);
+ app.ToggleChatState();
+ });
+
+ asr_button_.OnClick([this]() {
+ std::string wake_word="浣犲ソ灏忔櫤";
+ Application::GetInstance().WakeWordInvoke(wake_word);
+ });
+
+ touch_button_.OnPressDown([this]() {
+ gpio_set_level(BUILTIN_LED_GPIO, 1);
+ Application::GetInstance().StartListening();
+ });
+ touch_button_.OnPressUp([this]() {
+ gpio_set_level(BUILTIN_LED_GPIO, 0);
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ CompactWifiBoard() : WifiBoard(), boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO)
+ {
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override
+ {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+};
+
+DECLARE_BOARD(CompactWifiBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/compact_ml307_board.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/compact_ml307_board.cc
new file mode 100644
index 0000000..15b25f4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/compact_ml307_board.cc
@@ -0,0 +1,191 @@
+#include "dual_network_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/oled_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "CompactMl307Board"
+
+class CompactMl307Board : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ Button touch_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ touch_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ touch_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ CompactMl307Board() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC),
+ boot_button_(BOOT_BUTTON_GPIO),
+ touch_button_(TOUCH_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(CompactMl307Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.h
new file mode 100644
index 0000000..aaa67f0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.h
@@ -0,0 +1,59 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_47
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA_PIN GPIO_NUM_41
+#define DISPLAY_SCL_PIN GPIO_NUM_42
+#define DISPLAY_WIDTH 128
+
+#if CONFIG_OLED_SSD1306_128X32
+#define DISPLAY_HEIGHT 32
+#elif CONFIG_OLED_SSD1306_128X64
+#define DISPLAY_HEIGHT 64
+#else
+#error "OLED display type is not selected"
+#endif
+
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_18
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.json b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.json
new file mode 100644
index 0000000..9da8cab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-ml307/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "bread-compact-ml307",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X32=y"
+ ]
+ },
+ {
+ "name": "bread-compact-ml307-128x64",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X64=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/compact_nt26_board.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/compact_nt26_board.cc
new file mode 100644
index 0000000..88128ef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/compact_nt26_board.cc
@@ -0,0 +1,181 @@
+#include "board.h"
+#include "nt26_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/oled_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "CompactNt26Board"
+
+class CompactNt26Board : public Nt26Board {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ Button touch_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([]() {
+ Application::GetInstance().ToggleChatState();
+ });
+
+ touch_button_.OnPressDown([]() {
+ Application::GetInstance().StartListening();
+ });
+ touch_button_.OnPressUp([]() {
+ Application::GetInstance().StopListening();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ CompactNt26Board() :
+ Nt26Board(NT26_TX_PIN, NT26_RX_PIN, NT26_DTR_PIN, NT26_RI_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ touch_button_(TOUCH_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual void StartNetwork() override {
+ GetDisplay()->SetStatus(Lang::Strings::DETECTING_MODULE);
+ Nt26Board::StartNetwork();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(CompactNt26Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.h
new file mode 100644
index 0000000..74169c1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.h
@@ -0,0 +1,61 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_47
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA_PIN GPIO_NUM_41
+#define DISPLAY_SCL_PIN GPIO_NUM_42
+#define DISPLAY_WIDTH 128
+
+#if CONFIG_OLED_SSD1306_128X32
+#define DISPLAY_HEIGHT 32
+#elif CONFIG_OLED_SSD1306_128X64
+#define DISPLAY_HEIGHT 64
+#else
+#error "OLED display type is not selected"
+#endif
+
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+
+#define NT26_DTR_PIN GPIO_NUM_9
+#define NT26_RI_PIN GPIO_NUM_10
+#define NT26_RX_PIN GPIO_NUM_11
+#define NT26_TX_PIN GPIO_NUM_12
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_18
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.json b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.json
new file mode 100644
index 0000000..e0c370e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-nt26/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "bread-compact-nt26",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X32=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc
new file mode 100644
index 0000000..431cc43
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc
@@ -0,0 +1,183 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+#include "esp_lcd_ili9341.h"
+#endif
+
+#if defined(LCD_TYPE_GC9A01_SERIAL)
+#include "esp_lcd_gc9a01.h"
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb1, (uint8_t[]){0x80}, 1, 0},
+ {0xb2, (uint8_t[]){0x27}, 1, 0},
+ {0xb3, (uint8_t[]){0x13}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x05}, 1, 0},
+ {0xac, (uint8_t[]){0xc8}, 1, 0},
+ {0xab, (uint8_t[]){0x0f}, 1, 0},
+ {0x3a, (uint8_t[]){0x05}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x08}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xea, (uint8_t[]){0x02}, 1, 0},
+ {0xe8, (uint8_t[]){0x2A}, 1, 0},
+ {0xe9, (uint8_t[]){0x47}, 1, 0},
+ {0xe7, (uint8_t[]){0x5f}, 1, 0},
+ {0xc6, (uint8_t[]){0x21}, 1, 0},
+ {0xc7, (uint8_t[]){0x15}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
+ 0x04, 0x12, 0x14, 0x1f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
+ 0x0C, 0x1A, 0x14, 0x1E},
+ 14, 0},
+ {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
+ {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
+};
+#endif
+
+#define TAG "CompactWifiBoardLCD"
+
+class CompactWifiBoardLCD : public WifiBoard {
+private:
+
+ Button boot_button_;
+ LcdDisplay* display_;
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+#elif defined(LCD_TYPE_GC9A01_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+#else
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
+#endif
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+#ifdef LCD_TYPE_GC9A01_SERIAL
+ panel_config.vendor_config = &gc9107_vendor_config;
+#endif
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ CompactWifiBoardLCD() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+};
+
+DECLARE_BOARD(CompactWifiBoardLCD);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/config.h
new file mode 100644
index 0000000..12d3802
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-lcd/config.h
@@ -0,0 +1,289 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
+#define DISPLAY_MOSI_PIN GPIO_NUM_47
+#define DISPLAY_CLK_PIN GPIO_NUM_21
+#define DISPLAY_DC_PIN GPIO_NUM_40
+#define DISPLAY_RST_PIN GPIO_NUM_45
+#define DISPLAY_CS_PIN GPIO_NUM_41
+
+
+#ifdef CONFIG_LCD_ST7789_240X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_170X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 170
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 35
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_172X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 172
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 34
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X280
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240_7PIN
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 3
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X135
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 135
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 40
+#define DISPLAY_OFFSET_Y 53
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X160
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 160
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X128
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 32
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7796_320X480
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_GC9A01_240X240
+#define LCD_TYPE_GC9A01_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_CUSTOM
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_18
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/README.md b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/README.md
new file mode 100644
index 0000000..8072768
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/README.md
@@ -0,0 +1,31 @@
+纭欢鍩轰簬鍩轰簬ESP32S3CAM寮�鍙戞澘锛屼唬鐮佸熀浜巄read-compact-wifi-lcd淇敼
+浣跨敤鐨勬憚鍍忓ご鏄疧V2640
+娉ㄦ剰鍥犱负鎽勫儚澶村崰鐢↖O杈冨锛屾墍浠ュ崰鐢ㄤ簡ESP32S3鐨刄SB 19 20涓や釜寮曡剼
+杩炵嚎鏂瑰紡鍙傝�僣onfig.h鏂囦欢涓寮曡剼鐨勫畾涔�
+
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```bash
+Xiaozhi Assistant -> Board Type ->闈㈠寘鏉挎柊鐗堟帴绾匡紙WiFi锛�+ LCD + Camera
+```
+
+**缂栬瘧鐑у叆锛�**
+
+```bash
+idf.py build flash
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc
new file mode 100644
index 0000000..25bc355
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/compact_wifi_board_s3cam.cc
@@ -0,0 +1,214 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "esp32_camera.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+#include "esp_lcd_ili9341.h"
+#endif
+
+#if defined(LCD_TYPE_GC9A01_SERIAL)
+#include "esp_lcd_gc9a01.h"
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb1, (uint8_t[]){0x80}, 1, 0},
+ {0xb2, (uint8_t[]){0x27}, 1, 0},
+ {0xb3, (uint8_t[]){0x13}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x05}, 1, 0},
+ {0xac, (uint8_t[]){0xc8}, 1, 0},
+ {0xab, (uint8_t[]){0x0f}, 1, 0},
+ {0x3a, (uint8_t[]){0x05}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x08}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xea, (uint8_t[]){0x02}, 1, 0},
+ {0xe8, (uint8_t[]){0x2A}, 1, 0},
+ {0xe9, (uint8_t[]){0x47}, 1, 0},
+ {0xe7, (uint8_t[]){0x5f}, 1, 0},
+ {0xc6, (uint8_t[]){0x21}, 1, 0},
+ {0xc7, (uint8_t[]){0x15}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
+ 0x04, 0x12, 0x14, 0x1f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
+ 0x0C, 0x1A, 0x14, 0x1E},
+ 14, 0},
+ {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
+ {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
+};
+#endif
+
+#define TAG "CompactWifiBoardS3Cam"
+
+class CompactWifiBoardS3Cam : public WifiBoard {
+private:
+
+ Button boot_button_;
+ LcdDisplay* display_;
+ Esp32Camera* camera_;
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+#elif defined(LCD_TYPE_GC9A01_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+#else
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
+#endif
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+#ifdef LCD_TYPE_GC9A01_SERIAL
+ panel_config.vendor_config = &gc9107_vendor_config;
+#endif
+ 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 InitializeCamera() {
+ camera_config_t config = {};
+ config.pin_d0 = CAMERA_PIN_D0;
+ config.pin_d1 = CAMERA_PIN_D1;
+ config.pin_d2 = CAMERA_PIN_D2;
+ config.pin_d3 = CAMERA_PIN_D3;
+ config.pin_d4 = CAMERA_PIN_D4;
+ config.pin_d5 = CAMERA_PIN_D5;
+ config.pin_d6 = CAMERA_PIN_D6;
+ config.pin_d7 = CAMERA_PIN_D7;
+ config.pin_xclk = CAMERA_PIN_XCLK;
+ config.pin_pclk = CAMERA_PIN_PCLK;
+ config.pin_vsync = CAMERA_PIN_VSYNC;
+ config.pin_href = CAMERA_PIN_HREF;
+ config.pin_sccb_sda = CAMERA_PIN_SIOD;
+ config.pin_sccb_scl = CAMERA_PIN_SIOC;
+ config.sccb_i2c_port = 0;
+ config.pin_pwdn = CAMERA_PIN_PWDN;
+ config.pin_reset = CAMERA_PIN_RESET;
+ config.xclk_freq_hz = XCLK_FREQ_HZ;
+ config.pixel_format = PIXFORMAT_RGB565;
+ config.frame_size = FRAMESIZE_VGA;
+ config.jpeg_quality = 12;
+ config.fb_count = 1;
+ config.fb_location = CAMERA_FB_IN_PSRAM;
+ config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
+ camera_ = new Esp32Camera(config);
+ camera_->SetHMirror(false);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ CompactWifiBoardS3Cam() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeCamera();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(CompactWifiBoardS3Cam);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/config.h
new file mode 100644
index 0000000..857d8c0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi-s3cam/config.h
@@ -0,0 +1,308 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_1
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_2
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_39
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_40
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_41
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+//Camera Config
+#define CAMERA_PIN_D0 GPIO_NUM_11
+#define CAMERA_PIN_D1 GPIO_NUM_9
+#define CAMERA_PIN_D2 GPIO_NUM_8
+#define CAMERA_PIN_D3 GPIO_NUM_10
+#define CAMERA_PIN_D4 GPIO_NUM_12
+#define CAMERA_PIN_D5 GPIO_NUM_18
+#define CAMERA_PIN_D6 GPIO_NUM_17
+#define CAMERA_PIN_D7 GPIO_NUM_16
+#define CAMERA_PIN_XCLK GPIO_NUM_15
+#define CAMERA_PIN_PCLK GPIO_NUM_13
+#define CAMERA_PIN_VSYNC GPIO_NUM_6
+#define CAMERA_PIN_HREF GPIO_NUM_7
+#define CAMERA_PIN_SIOC GPIO_NUM_5
+#define CAMERA_PIN_SIOD GPIO_NUM_4
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define XCLK_FREQ_HZ 20000000
+
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38
+#define DISPLAY_MOSI_PIN GPIO_NUM_20
+#define DISPLAY_CLK_PIN GPIO_NUM_19
+#define DISPLAY_DC_PIN GPIO_NUM_47
+#define DISPLAY_RST_PIN GPIO_NUM_21
+#define DISPLAY_CS_PIN GPIO_NUM_45
+
+
+#ifdef CONFIG_LCD_ST7789_240X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_170X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 170
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 35
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_172X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 172
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 34
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X280
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240_7PIN
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 3
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X135
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 135
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 40
+#define DISPLAY_OFFSET_Y 53
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X160
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 160
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X128
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 32
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7796_320X480
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7796_320X480_NO_IPS
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_GC9A01_240X240
+#define LCD_TYPE_GC9A01_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_CUSTOM
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_14
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/compact_wifi_board.cc b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/compact_wifi_board.cc
new file mode 100644
index 0000000..04d7191
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/compact_wifi_board.cc
@@ -0,0 +1,188 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/oled_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#ifdef SH1106
+#include <esp_lcd_panel_sh1106.h>
+#endif
+
+#define TAG "CompactWifiBoard"
+
+class CompactWifiBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ Button touch_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+#ifdef SH1106
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh1106(panel_io_, &panel_config, &panel_));
+#else
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+#endif
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ touch_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ touch_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛岄�愭杩佺Щ鍒� MCP 鍗忚
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ CompactWifiBoard() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ touch_button_(TOUCH_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(CompactWifiBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.h
new file mode 100644
index 0000000..c1910d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.h
@@ -0,0 +1,59 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_47
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA_PIN GPIO_NUM_41
+#define DISPLAY_SCL_PIN GPIO_NUM_42
+#define DISPLAY_WIDTH 128
+
+#if CONFIG_OLED_SSD1306_128X32
+#define DISPLAY_HEIGHT 32
+#elif CONFIG_OLED_SSD1306_128X64
+#define DISPLAY_HEIGHT 64
+#elif CONFIG_OLED_SH1106_128X64
+#define DISPLAY_HEIGHT 64
+#define SH1106
+#else
+#error "OLED display type is not selected"
+#endif
+
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_18
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.json
new file mode 100644
index 0000000..ea296f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/bread-compact-wifi/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "bread-compact-wifi",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X32=y"
+ ]
+ },
+ {
+ "name": "bread-compact-wifi-128x64",
+ "sdkconfig_append": [
+ "CONFIG_OLED_SSD1306_128X64=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.cc b/xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.cc
new file mode 100644
index 0000000..c34da68
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.cc
@@ -0,0 +1,116 @@
+#include "adc_battery_monitor.h"
+
+AdcBatteryMonitor::AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin)
+ : charging_pin_(charging_pin) {
+
+ // Initialize charging pin (only if it's not NC)
+ if (charging_pin_ != GPIO_NUM_NC) {
+ gpio_config_t gpio_cfg = {
+ .pin_bit_mask = 1ULL << charging_pin,
+ .mode = GPIO_MODE_INPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&gpio_cfg));
+ }
+
+ // Initialize ADC battery estimation
+ adc_battery_estimation_t adc_cfg = {
+ .internal = {
+ .adc_unit = adc_unit,
+ .adc_bitwidth = ADC_BITWIDTH_DEFAULT,
+ .adc_atten = ADC_ATTEN_DB_12,
+ },
+ .adc_channel = adc_channel,
+ .upper_resistor = upper_resistor,
+ .lower_resistor = lower_resistor
+ };
+
+ // 鍦ˋDC閰嶇疆閮ㄥ垎杩涜鏉′欢璁剧疆
+ if (charging_pin_ != GPIO_NUM_NC) {
+ adc_cfg.charging_detect_cb = [](void *user_data) -> bool {
+ AdcBatteryMonitor *self = (AdcBatteryMonitor *)user_data;
+ return gpio_get_level(self->charging_pin_) == 1;
+ };
+ adc_cfg.charging_detect_user_data = this;
+ } else {
+ // 涓嶈缃洖璋冿紝璁゛dc_battery_estimation搴撲娇鐢ㄨ蒋浠朵及绠�
+ adc_cfg.charging_detect_cb = nullptr;
+ adc_cfg.charging_detect_user_data = nullptr;
+ }
+ adc_battery_estimation_handle_ = adc_battery_estimation_create(&adc_cfg);
+
+ // Initialize timer
+ esp_timer_create_args_t timer_cfg = {
+ .callback = [](void *arg) {
+ AdcBatteryMonitor *self = (AdcBatteryMonitor *)arg;
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .name = "adc_battery_monitor",
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_cfg, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+}
+
+AdcBatteryMonitor::~AdcBatteryMonitor() {
+ if (adc_battery_estimation_handle_) {
+ ESP_ERROR_CHECK(adc_battery_estimation_destroy(adc_battery_estimation_handle_));
+ }
+
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+}
+
+bool AdcBatteryMonitor::IsCharging() {
+ // 浼樺厛浣跨敤adc_battery_estimation搴撶殑鍔熻兘
+ if (adc_battery_estimation_handle_ != nullptr) {
+ bool is_charging = false;
+ esp_err_t err = adc_battery_estimation_get_charging_state(adc_battery_estimation_handle_, &is_charging);
+ if (err == ESP_OK) {
+ return is_charging;
+ }
+ }
+
+ // 鍥為��鍒癎PIO璇诲彇鎴栬繑鍥為粯璁ゅ��
+ if (charging_pin_ != GPIO_NUM_NC) {
+ return gpio_get_level(charging_pin_) == 1;
+ }
+
+ return false;
+}
+
+bool AdcBatteryMonitor::IsDischarging() {
+ return !IsCharging();
+}
+
+uint8_t AdcBatteryMonitor::GetBatteryLevel() {
+ // 濡傛灉鍙ユ焺鏃犳晥锛岃繑鍥為粯璁ゅ��
+ if (adc_battery_estimation_handle_ == nullptr) {
+ return 100;
+ }
+
+ float capacity = 0;
+ esp_err_t err = adc_battery_estimation_get_capacity(adc_battery_estimation_handle_, &capacity);
+ if (err != ESP_OK) {
+ return 100; // 鍑洪敊鏃惰繑鍥為粯璁ゅ��
+ }
+ return (uint8_t)capacity;
+}
+
+void AdcBatteryMonitor::OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+}
+
+void AdcBatteryMonitor::CheckBatteryStatus() {
+ bool new_charging_status = IsCharging();
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.h b/xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.h
new file mode 100644
index 0000000..6a123a7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/adc_battery_monitor.h
@@ -0,0 +1,30 @@
+#ifndef ADC_BATTERY_MONITOR_H
+#define ADC_BATTERY_MONITOR_H
+
+#include <functional>
+#include <driver/gpio.h>
+#include <adc_battery_estimation.h>
+#include <esp_timer.h>
+
+class AdcBatteryMonitor {
+public:
+ AdcBatteryMonitor(adc_unit_t adc_unit, adc_channel_t adc_channel, float upper_resistor, float lower_resistor, gpio_num_t charging_pin = GPIO_NUM_NC);
+ ~AdcBatteryMonitor();
+
+ bool IsCharging();
+ bool IsDischarging();
+ uint8_t GetBatteryLevel();
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback);
+
+private:
+ gpio_num_t charging_pin_;
+ adc_battery_estimation_handle_t adc_battery_estimation_handle_ = nullptr;
+ esp_timer_handle_t timer_handle_ = nullptr;
+ bool is_charging_ = false;
+ std::function<void(bool)> on_charging_status_changed_;
+
+ void CheckBatteryStatus();
+};
+
+#endif // ADC_BATTERY_MONITOR_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.cc b/xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.cc
new file mode 100644
index 0000000..0fe41ea
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.cc
@@ -0,0 +1,375 @@
+#include "afsk_demod.h"
+#include <cstring>
+#include <algorithm>
+#include "esp_log.h"
+#include "display.h"
+#include "ssid_manager.h"
+
+#ifndef M_PI
+#define M_PI 3.14159265358979323846
+#endif
+
+namespace audio_wifi_config
+{
+ static const char *kLogTag = "AUDIO_WIFI_CONFIG";
+
+ void ReceiveWifiCredentialsFromAudio(Application *app,
+ WifiManager *wifi_manager,
+ Display *display,
+ size_t input_channels
+ )
+ {
+ const int kInputSampleRate = 16000; // Input sampling rate
+ const float kDownsampleStep = static_cast<float>(kInputSampleRate) / static_cast<float>(kAudioSampleRate); // Downsampling step
+ std::vector<int16_t> audio_data;
+ AudioSignalProcessor signal_processor(kAudioSampleRate, kMarkFrequency, kSpaceFrequency, kBitRate, kWindowSize);
+ AudioDataBuffer data_buffer;
+
+ while (true)
+ {
+ // 妫�鏌pplication鐘舵�侊紝鍙湁鍦╓iFi閰嶇疆妯″紡涓嬫墠澶勭悊闊抽
+ if (app->GetDeviceState() != kDeviceStateWifiConfiguring) {
+ // 涓嶅湪WiFi閰嶇疆鐘舵�侊紝浼戠湢100ms鍚庡啀妫�鏌�
+ vTaskDelay(pdMS_TO_TICKS(100));
+ continue;
+ }
+
+ if (!app->GetAudioService().ReadAudioData(audio_data, 16000, 480)) { // 16kHz, 480 samples corresponds to 30ms data
+ // 璇诲彇闊抽澶辫触锛岀煭鏆傚欢杩熷悗閲嶈瘯
+ ESP_LOGI(kLogTag, "Failed to read audio data, retrying.");
+ vTaskDelay(pdMS_TO_TICKS(10));
+ continue;
+ }
+
+ if (input_channels == 2) { // 濡傛灉鏄弻澹伴亾杈撳叆锛岃浆鎹负鍗曞0閬�
+ auto mono_data = std::vector<int16_t>(audio_data.size() / 2);
+ for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
+ mono_data[i] = audio_data[j];
+ }
+ audio_data = std::move(mono_data);
+ }
+
+ // Downsample the audio data
+ std::vector<float> downsampled_data;
+ size_t last_index = 0;
+
+ if (kDownsampleStep > 1.0f) {
+ downsampled_data.reserve(audio_data.size() / static_cast<size_t>(kDownsampleStep));
+ for (size_t i = 0; i < audio_data.size(); ++i) {
+ size_t sample_index = static_cast<size_t>(i / kDownsampleStep);
+ if ((sample_index + 1) > last_index) {
+ downsampled_data.push_back(static_cast<float>(audio_data[i]));
+ last_index = sample_index + 1;
+ }
+ }
+ } else {
+ downsampled_data.reserve(audio_data.size());
+ for (int16_t sample : audio_data) {
+ downsampled_data.push_back(static_cast<float>(sample));
+ }
+ }
+
+ // Process audio samples to get probability data
+ auto probabilities = signal_processor.ProcessAudioSamples(downsampled_data);
+
+ // Feed probability data to the data buffer
+ if (data_buffer.ProcessProbabilityData(probabilities, 0.5f)) {
+ // If complete data was received, extract WiFi credentials
+ if (data_buffer.decoded_text.has_value()) {
+ ESP_LOGI(kLogTag, "Received text data: %s", data_buffer.decoded_text->c_str());
+ display->SetChatMessage("system", data_buffer.decoded_text->c_str());
+
+ // Split SSID and password by newline character
+ std::string wifi_ssid, wifi_password;
+ size_t newline_position = data_buffer.decoded_text->find('\n');
+ if (newline_position != std::string::npos) {
+ wifi_ssid = data_buffer.decoded_text->substr(0, newline_position);
+ wifi_password = data_buffer.decoded_text->substr(newline_position + 1);
+ ESP_LOGI(kLogTag, "WiFi SSID: %s, Password: %s", wifi_ssid.c_str(), wifi_password.c_str());
+ } else {
+ ESP_LOGE(kLogTag, "Invalid data format, no newline character found");
+ continue;
+ }
+
+ // Save WiFi credentials using SsidManager
+ auto& ssid_manager = SsidManager::GetInstance();
+ ssid_manager.AddSsid(wifi_ssid, wifi_password);
+ ESP_LOGI(kLogTag, "WiFi credentials saved successfully");
+
+ // Exit config mode (triggers ConfigModeExit event)
+ wifi_manager->StopConfigAp();
+
+ data_buffer.decoded_text.reset(); // Clear processed data
+ return; // Exit the function
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(1)); // 1ms delay
+ }
+ }
+
+ // Default start and end transmission identifiers
+ // \x01\x02 = 00000001 00000010
+ const std::vector<uint8_t> kDefaultStartTransmissionPattern = {
+ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0};
+
+ // \x03\x04 = 00000011 00000100
+ const std::vector<uint8_t> kDefaultEndTransmissionPattern = {
+ 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 0, 1, 0, 0};
+
+ // FrequencyDetector implementation
+ FrequencyDetector::FrequencyDetector(float frequency, size_t window_size)
+ : frequency_(frequency), window_size_(window_size) {
+ frequency_bin_ = std::floor(frequency_ * static_cast<float>(window_size_));
+ angular_frequency_ = 2.0f * M_PI * frequency_;
+ cos_coefficient_ = std::cos(angular_frequency_);
+ sin_coefficient_ = std::sin(angular_frequency_);
+ filter_coefficient_ = 2.0f * cos_coefficient_;
+
+ // Initialize state buffer
+ state_buffer_.push_back(0.0f);
+ state_buffer_.push_back(0.0f);
+ }
+
+ void FrequencyDetector::Reset() {
+ state_buffer_.clear();
+ state_buffer_.push_back(0.0f);
+ state_buffer_.push_back(0.0f);
+ }
+
+ void FrequencyDetector::ProcessSample(float sample) {
+ if (state_buffer_.size() < 2) {
+ return;
+ }
+
+ float s_minus_2 = state_buffer_.front(); // S[-2]
+ state_buffer_.pop_front();
+ float s_minus_1 = state_buffer_.front(); // S[-1]
+ state_buffer_.pop_front();
+
+ float s_current = sample + filter_coefficient_ * s_minus_1 - s_minus_2;
+
+ state_buffer_.push_back(s_minus_1); // Put S[-1] back
+ state_buffer_.push_back(s_current); // Add new S[0]
+ }
+
+ float FrequencyDetector::GetAmplitude() const {
+ if (state_buffer_.size() < 2) {
+ return 0.0f;
+ }
+
+ float s_minus_1 = state_buffer_[1]; // S[-1]
+ float s_minus_2 = state_buffer_[0]; // S[-2]
+ float real_part = cos_coefficient_ * s_minus_1 - s_minus_2; // Real part
+ float imaginary_part = sin_coefficient_ * s_minus_1; // Imaginary part
+
+ return std::sqrt(real_part * real_part + imaginary_part * imaginary_part) /
+ (static_cast<float>(window_size_) / 2.0f);
+ }
+
+ // AudioSignalProcessor implementation
+ AudioSignalProcessor::AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
+ size_t bit_rate, size_t window_size)
+ : input_buffer_size_(window_size), output_sample_count_(0) {
+ if (sample_rate % bit_rate != 0) {
+ // On ESP32 we can continue execution, but log the error
+ ESP_LOGW(kLogTag, "Sample rate %zu is not divisible by bit rate %zu", sample_rate, bit_rate);
+ }
+
+ float normalized_mark_freq = static_cast<float>(mark_frequency) / static_cast<float>(sample_rate);
+ float normalized_space_freq = static_cast<float>(space_frequency) / static_cast<float>(sample_rate);
+
+ mark_detector_ = std::make_unique<FrequencyDetector>(normalized_mark_freq, window_size);
+ space_detector_ = std::make_unique<FrequencyDetector>(normalized_space_freq, window_size);
+
+ samples_per_bit_ = sample_rate / bit_rate; // Number of samples per bit
+ }
+
+ std::vector<float> AudioSignalProcessor::ProcessAudioSamples(const std::vector<float> &samples) {
+ std::vector<float> result;
+
+ for (float sample : samples) {
+ if (input_buffer_.size() < input_buffer_size_) {
+ input_buffer_.push_back(sample); // Just add, don't process yet
+ } else {
+ // Input buffer is full, process the data
+ input_buffer_.pop_front(); // Remove oldest sample
+ input_buffer_.push_back(sample); // Add new sample
+ output_sample_count_++;
+
+ if (output_sample_count_ >= samples_per_bit_) {
+ // Process all samples in the window using Goertzel algorithm
+ for (float window_sample : input_buffer_) {
+ mark_detector_->ProcessSample(window_sample);
+ space_detector_->ProcessSample(window_sample);
+ }
+
+ float mark_amplitude = mark_detector_->GetAmplitude(); // Mark amplitude
+ float space_amplitude = space_detector_->GetAmplitude(); // Space amplitude
+
+ // Avoid division by zero
+ float mark_probability = mark_amplitude /
+ (space_amplitude + mark_amplitude + std::numeric_limits<float>::epsilon());
+ result.push_back(mark_probability);
+
+ // Reset detector windows
+ mark_detector_->Reset();
+ space_detector_->Reset();
+ output_sample_count_ = 0; // Reset output counter
+ }
+ }
+ }
+
+ return result;
+ }
+
+ // AudioDataBuffer implementation
+ AudioDataBuffer::AudioDataBuffer()
+ : current_state_(DataReceptionState::kInactive),
+ start_of_transmission_(kDefaultStartTransmissionPattern),
+ end_of_transmission_(kDefaultEndTransmissionPattern),
+ enable_checksum_validation_(true) {
+ identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
+ max_bit_buffer_size_ = 776; // Preset bit buffer size, 776 bits = (32 + 1 + 63 + 1) * 8 = 776
+
+ bit_buffer_.reserve(max_bit_buffer_size_);
+ }
+
+ AudioDataBuffer::AudioDataBuffer(size_t max_byte_size, const std::vector<uint8_t> &start_identifier,
+ const std::vector<uint8_t> &end_identifier, bool enable_checksum)
+ : current_state_(DataReceptionState::kInactive),
+ start_of_transmission_(start_identifier),
+ end_of_transmission_(end_identifier),
+ enable_checksum_validation_(enable_checksum) {
+ identifier_buffer_size_ = std::max(start_of_transmission_.size(), end_of_transmission_.size());
+ max_bit_buffer_size_ = max_byte_size * 8; // Bit buffer size in bytes
+
+ bit_buffer_.reserve(max_bit_buffer_size_);
+ }
+
+ uint8_t AudioDataBuffer::CalculateChecksum(const std::string &text) {
+ uint8_t checksum = 0;
+ for (char character : text) {
+ checksum += static_cast<uint8_t>(character);
+ }
+ return checksum;
+ }
+
+ void AudioDataBuffer::ClearBuffers() {
+ identifier_buffer_.clear();
+ bit_buffer_.clear();
+ }
+
+ bool AudioDataBuffer::ProcessProbabilityData(const std::vector<float> &probabilities, float threshold) {
+ for (float probability : probabilities) {
+ uint8_t bit = (probability > threshold) ? 1 : 0;
+
+ if (identifier_buffer_.size() >= identifier_buffer_size_) {
+ identifier_buffer_.pop_front(); // Maintain buffer size
+ }
+ identifier_buffer_.push_back(bit);
+
+ // Process received bit based on state machine
+ switch (current_state_) {
+ case DataReceptionState::kInactive:
+ if (identifier_buffer_.size() >= start_of_transmission_.size()) {
+ current_state_ = DataReceptionState::kWaiting; // Enter waiting state
+ ESP_LOGI(kLogTag, "Entering Waiting state");
+ }
+ break;
+
+ case DataReceptionState::kWaiting:
+ // Waiting state, possibly waiting for transmission end
+ if (identifier_buffer_.size() >= start_of_transmission_.size()) {
+ std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
+ if (identifier_snapshot == start_of_transmission_)
+ {
+ ClearBuffers(); // Clear buffers
+ current_state_ = DataReceptionState::kReceiving; // Enter receiving state
+ ESP_LOGI(kLogTag, "Entering Receiving state");
+ }
+ }
+ break;
+
+ case DataReceptionState::kReceiving:
+ bit_buffer_.push_back(bit);
+ if (identifier_buffer_.size() >= end_of_transmission_.size()) {
+ std::vector<uint8_t> identifier_snapshot(identifier_buffer_.begin(), identifier_buffer_.end());
+ if (identifier_snapshot == end_of_transmission_) {
+ current_state_ = DataReceptionState::kInactive; // Enter inactive state
+
+ // Convert bits to bytes
+ std::vector<uint8_t> bytes = ConvertBitsToBytes(bit_buffer_);
+
+ uint8_t received_checksum = 0;
+ size_t minimum_length = 0;
+
+ if (enable_checksum_validation_) {
+ // If checksum is required, last byte is checksum
+ minimum_length = 1 + start_of_transmission_.size() / 8;
+ if (bytes.size() >= minimum_length)
+ {
+ received_checksum = bytes[bytes.size() - start_of_transmission_.size() / 8 - 1];
+ }
+ } else {
+ minimum_length = start_of_transmission_.size() / 8;
+ }
+
+ if (bytes.size() < minimum_length) {
+ ClearBuffers();
+ ESP_LOGW(kLogTag, "Data too short, clearing buffer");
+ return false; // Data too short, return failure
+ }
+
+ // Extract text data (remove trailing identifier part)
+ std::vector<uint8_t> text_bytes(
+ bytes.begin(), bytes.begin() + bytes.size() - minimum_length);
+
+ std::string result(text_bytes.begin(), text_bytes.end());
+
+ // Validate checksum if required
+ if (enable_checksum_validation_) {
+ uint8_t calculated_checksum = CalculateChecksum(result);
+ if (calculated_checksum != received_checksum) {
+ // Checksum mismatch
+ ESP_LOGW(kLogTag, "Checksum mismatch: expected %d, got %d",
+ received_checksum, calculated_checksum);
+ ClearBuffers();
+ return false;
+ }
+ }
+
+ ClearBuffers();
+ decoded_text = result;
+ return true; // Return success
+ } else if (bit_buffer_.size() >= max_bit_buffer_size_) {
+ // If not end identifier and bit buffer is full, reset
+ ClearBuffers();
+ ESP_LOGW(kLogTag, "Buffer overflow, clearing buffer");
+ current_state_ = DataReceptionState::kInactive; // Reset state machine
+ }
+ }
+ break;
+ }
+ }
+
+ return false;
+ }
+
+ std::vector<uint8_t> AudioDataBuffer::ConvertBitsToBytes(const std::vector<uint8_t> &bits) const {
+ std::vector<uint8_t> bytes;
+
+ // Ensure number of bits is a multiple of 8
+ size_t complete_bytes_count = bits.size() / 8;
+ bytes.reserve(complete_bytes_count);
+
+ for (size_t i = 0; i < complete_bytes_count; ++i) {
+ uint8_t byte_value = 0;
+ for (size_t j = 0; j < 8; ++j) {
+ byte_value |= bits[i * 8 + j] << (7 - j);
+ }
+ bytes.push_back(byte_value);
+ }
+
+ return bytes;
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.h b/xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.h
new file mode 100644
index 0000000..4ac0ab6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/afsk_demod.h
@@ -0,0 +1,177 @@
+#pragma once
+
+#include <vector>
+#include <deque>
+#include <string>
+#include <memory>
+#include <optional>
+#include <cmath>
+#include "wifi_manager.h"
+#include "application.h"
+
+// Audio signal processing constants for WiFi configuration via audio
+const size_t kAudioSampleRate = 6400;
+const size_t kMarkFrequency = 1800;
+const size_t kSpaceFrequency = 1500;
+const size_t kBitRate = 100;
+const size_t kWindowSize = 64;
+
+namespace audio_wifi_config
+{
+ // Main function to receive WiFi credentials through audio signal
+ void ReceiveWifiCredentialsFromAudio(Application *app, WifiManager *wifi_manager, Display *display,
+ size_t input_channels = 1);
+
+ /**
+ * Goertzel algorithm implementation for single frequency detection
+ * Used to detect specific audio frequencies in the AFSK demodulation process
+ */
+ class FrequencyDetector
+ {
+ private:
+ float frequency_; // Target frequency (normalized, i.e., f / fs)
+ size_t window_size_; // Window size for analysis
+ float frequency_bin_; // Frequency bin
+ float angular_frequency_; // Angular frequency
+ float cos_coefficient_; // cos(w)
+ float sin_coefficient_; // sin(w)
+ float filter_coefficient_; // 2 * cos(w)
+ std::deque<float> state_buffer_; // Circular buffer for storing S[-1] and S[-2]
+
+ public:
+ /**
+ * Constructor
+ * @param frequency Normalized frequency (f / fs)
+ * @param window_size Window size for analysis
+ */
+ FrequencyDetector(float frequency, size_t window_size);
+
+ /**
+ * Reset the detector state
+ */
+ void Reset();
+
+ /**
+ * Process one audio sample
+ * @param sample Input audio sample
+ */
+ void ProcessSample(float sample);
+
+ /**
+ * Calculate current amplitude
+ * @return Amplitude value
+ */
+ float GetAmplitude() const;
+ };
+
+ /**
+ * Audio signal processor for Mark/Space frequency pair detection
+ * Processes audio signals to extract digital data using AFSK demodulation
+ */
+ class AudioSignalProcessor
+ {
+ private:
+ std::deque<float> input_buffer_; // Input sample buffer
+ size_t input_buffer_size_; // Input buffer size = window size
+ size_t output_sample_count_; // Output sample counter
+ size_t samples_per_bit_; // Samples per bit threshold
+ std::unique_ptr<FrequencyDetector> mark_detector_; // Mark frequency detector
+ std::unique_ptr<FrequencyDetector> space_detector_; // Space frequency detector
+
+ public:
+ /**
+ * Constructor
+ * @param sample_rate Audio sampling rate
+ * @param mark_frequency Mark frequency for digital '1'
+ * @param space_frequency Space frequency for digital '0'
+ * @param bit_rate Data transmission bit rate
+ * @param window_size Analysis window size
+ */
+ AudioSignalProcessor(size_t sample_rate, size_t mark_frequency, size_t space_frequency,
+ size_t bit_rate, size_t window_size);
+
+ /**
+ * Process input audio samples
+ * @param samples Input audio sample vector
+ * @return Vector of Mark probability values (0.0 to 1.0)
+ */
+ std::vector<float> ProcessAudioSamples(const std::vector<float> &samples);
+ };
+
+ /**
+ * Data reception state machine states
+ */
+ enum class DataReceptionState
+ {
+ kInactive, // Waiting for start signal
+ kWaiting, // Detected potential start, waiting for confirmation
+ kReceiving // Actively receiving data
+ };
+
+ /**
+ * Data buffer for managing audio-to-digital data conversion
+ * Handles the complete process from audio signal to decoded text data
+ */
+ class AudioDataBuffer
+ {
+ private:
+ DataReceptionState current_state_; // Current reception state
+ std::deque<uint8_t> identifier_buffer_; // Buffer for start/end identifier detection
+ size_t identifier_buffer_size_; // Identifier buffer size
+ std::vector<uint8_t> bit_buffer_; // Buffer for storing bit stream
+ size_t max_bit_buffer_size_; // Maximum bit buffer size
+ const std::vector<uint8_t> start_of_transmission_; // Start-of-transmission identifier
+ const std::vector<uint8_t> end_of_transmission_; // End-of-transmission identifier
+ bool enable_checksum_validation_; // Whether to validate checksum
+
+ public:
+ std::optional<std::string> decoded_text; // Successfully decoded text data
+
+ /**
+ * Default constructor using predefined start and end identifiers
+ */
+ AudioDataBuffer();
+
+ /**
+ * Constructor with custom parameters
+ * @param max_byte_size Expected maximum data size in bytes
+ * @param start_identifier Start-of-transmission identifier
+ * @param end_identifier End-of-transmission identifier
+ * @param enable_checksum Whether to enable checksum validation
+ */
+ AudioDataBuffer(size_t max_byte_size, const std::vector<uint8_t> &start_identifier,
+ const std::vector<uint8_t> &end_identifier, bool enable_checksum = false);
+
+ /**
+ * Process probability data and attempt to decode
+ * @param probabilities Vector of Mark probabilities
+ * @param threshold Decision threshold for bit detection
+ * @return true if complete data was successfully received and decoded
+ */
+ bool ProcessProbabilityData(const std::vector<float> &probabilities, float threshold = 0.5f);
+
+ /**
+ * Calculate checksum for ASCII text
+ * @param text Input text string
+ * @return Checksum value (0-255)
+ */
+ static uint8_t CalculateChecksum(const std::string &text);
+
+ private:
+ /**
+ * Convert bit vector to byte vector
+ * @param bits Input bit vector
+ * @return Converted byte vector
+ */
+ std::vector<uint8_t> ConvertBitsToBytes(const std::vector<uint8_t> &bits) const;
+
+ /**
+ * Clear all buffers and reset state
+ */
+ void ClearBuffers();
+ };
+
+ // Default start and end transmission identifiers
+ extern const std::vector<uint8_t> kDefaultStartTransmissionPattern;
+ extern const std::vector<uint8_t> kDefaultEndTransmissionPattern;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/axp2101.cc b/xiaozhi-esp32-2.2.6/main/boards/common/axp2101.cc
new file mode 100644
index 0000000..854f0b8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/axp2101.cc
@@ -0,0 +1,41 @@
+#include "axp2101.h"
+#include "board.h"
+#include "display.h"
+
+#include <esp_log.h>
+
+#define TAG "Axp2101"
+
+Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+}
+
+int Axp2101::GetBatteryCurrentDirection() {
+ return (ReadReg(0x01) & 0b01100000) >> 5;
+}
+
+bool Axp2101::IsCharging() {
+ return GetBatteryCurrentDirection() == 1;
+}
+
+bool Axp2101::IsDischarging() {
+ return GetBatteryCurrentDirection() == 2;
+}
+
+bool Axp2101::IsChargingDone() {
+ uint8_t value = ReadReg(0x01);
+ return (value & 0b00000111) == 0b00000100;
+}
+
+int Axp2101::GetBatteryLevel() {
+ return ReadReg(0xA4);
+}
+
+float Axp2101::GetTemperature() {
+ return ReadReg(0xA5);
+}
+
+void Axp2101::PowerOff() {
+ uint8_t value = ReadReg(0x10);
+ value = value | 0x01;
+ WriteReg(0x10, value);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/axp2101.h b/xiaozhi-esp32-2.2.6/main/boards/common/axp2101.h
new file mode 100644
index 0000000..473cd3e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/axp2101.h
@@ -0,0 +1,20 @@
+#ifndef __AXP2101_H__
+#define __AXP2101_H__
+
+#include "i2c_device.h"
+
+class Axp2101 : public I2cDevice {
+public:
+ Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
+ bool IsCharging();
+ bool IsDischarging();
+ bool IsChargingDone();
+ int GetBatteryLevel();
+ float GetTemperature();
+ void PowerOff();
+
+private:
+ int GetBatteryCurrentDirection();
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/backlight.cc b/xiaozhi-esp32-2.2.6/main/boards/common/backlight.cc
new file mode 100644
index 0000000..c62fd7a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/backlight.cc
@@ -0,0 +1,121 @@
+#include "backlight.h"
+#include "settings.h"
+
+#include <esp_log.h>
+#include <driver/ledc.h>
+
+#define TAG "Backlight"
+
+
+Backlight::Backlight() {
+ // 鍒涘缓鑳屽厜娓愬彉瀹氭椂鍣�
+ const esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ auto self = static_cast<Backlight*>(arg);
+ self->OnTransitionTimer();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "backlight_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &transition_timer_));
+}
+
+Backlight::~Backlight() {
+ if (transition_timer_ != nullptr) {
+ esp_timer_stop(transition_timer_);
+ esp_timer_delete(transition_timer_);
+ }
+}
+
+void Backlight::RestoreBrightness() {
+ // Load brightness from settings
+ Settings settings("display");
+ int saved_brightness = settings.GetInt("brightness", 75);
+
+ // 妫�鏌ヤ寒搴﹀�兼槸鍚︿负0鎴栬繃灏忥紝璁剧疆榛樿鍊�
+ if (saved_brightness <= 0) {
+ ESP_LOGW(TAG, "Brightness value (%d) is too small, setting to default (10)", saved_brightness);
+ saved_brightness = 10; // 璁剧疆涓�涓緝浣庣殑榛樿鍊�
+ }
+
+ SetBrightness(saved_brightness);
+}
+
+void Backlight::SetBrightness(uint8_t brightness, bool permanent) {
+ if (brightness > 100) {
+ brightness = 100;
+ }
+
+ if (brightness_ == brightness) {
+ return;
+ }
+
+ if (permanent) {
+ Settings settings("display", true);
+ settings.SetInt("brightness", brightness);
+ }
+
+ target_brightness_ = brightness;
+ step_ = (target_brightness_ > brightness_) ? 1 : -1;
+
+ if (transition_timer_ != nullptr) {
+ // 鍚姩瀹氭椂鍣紝姣� 5ms 鏇存柊涓�娆�
+ esp_timer_start_periodic(transition_timer_, 5 * 1000);
+ }
+ ESP_LOGI(TAG, "Set brightness to %d", brightness);
+}
+
+void Backlight::OnTransitionTimer() {
+ if (brightness_ == target_brightness_) {
+ esp_timer_stop(transition_timer_);
+ return;
+ }
+
+ brightness_ += step_;
+ SetBrightnessImpl(brightness_);
+
+ if (brightness_ == target_brightness_) {
+ esp_timer_stop(transition_timer_);
+ }
+}
+
+PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert, uint32_t freq_hz) : Backlight() {
+ const ledc_timer_config_t backlight_timer = {
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .duty_resolution = LEDC_TIMER_10_BIT,
+ .timer_num = LEDC_TIMER_0,
+ .freq_hz = freq_hz, //鑳屽厜pwm棰戠巼闇�瑕侀珮涓�鐐癸紝闃叉鐢垫劅鍟稿彨
+ .clk_cfg = LEDC_AUTO_CLK,
+ .deconfigure = false
+ };
+ ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer));
+
+ // Setup LEDC peripheral for PWM backlight control
+ const ledc_channel_config_t backlight_channel = {
+ .gpio_num = pin,
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .channel = LEDC_CHANNEL_0,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = LEDC_TIMER_0,
+ .duty = 0,
+ .hpoint = 0,
+ .flags = {
+ .output_invert = output_invert,
+ }
+ };
+ ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel));
+}
+
+PwmBacklight::~PwmBacklight() {
+ ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0);
+}
+
+void PwmBacklight::SetBrightnessImpl(uint8_t brightness) {
+ // LEDC resolution set to 10bits, thus: 100% = 1023
+ uint32_t duty_cycle = (1023 * brightness) / 100;
+ ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle);
+ ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
+}
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/backlight.h b/xiaozhi-esp32-2.2.6/main/boards/common/backlight.h
new file mode 100644
index 0000000..5c09b3d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/backlight.h
@@ -0,0 +1,36 @@
+#pragma once
+
+#include <cstdint>
+#include <functional>
+
+#include <driver/gpio.h>
+#include <esp_timer.h>
+
+
+class Backlight {
+public:
+ Backlight();
+ ~Backlight();
+
+ void RestoreBrightness();
+ void SetBrightness(uint8_t brightness, bool permanent = false);
+ inline uint8_t brightness() const { return brightness_; }
+
+protected:
+ void OnTransitionTimer();
+ virtual void SetBrightnessImpl(uint8_t brightness) = 0;
+
+ esp_timer_handle_t transition_timer_ = nullptr;
+ uint8_t brightness_ = 0;
+ uint8_t target_brightness_ = 0;
+ uint8_t step_ = 1;
+};
+
+
+class PwmBacklight : public Backlight {
+public:
+ PwmBacklight(gpio_num_t pin, bool output_invert = false, uint32_t freq_hz = 25000);
+ ~PwmBacklight();
+
+ void SetBrightnessImpl(uint8_t brightness) override;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/blufi.cpp b/xiaozhi-esp32-2.2.6/main/boards/common/blufi.cpp
new file mode 100644
index 0000000..c4be933
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/blufi.cpp
@@ -0,0 +1,899 @@
+#include "blufi.h"
+#include <algorithm>
+#include <cassert>
+#include <cstring>
+#include <string>
+#include <vector>
+#include "esp_bt.h"
+#include "esp_event.h"
+#include "esp_log.h"
+#include "esp_timer.h"
+#include "esp_wifi.h"
+#include "freertos/task.h"
+#include "wifi_manager.h"
+
+#define BLUFI_DEVICE_NAME "Xiaozhi-Blufi"
+
+#ifdef CONFIG_BT_BLUEDROID_ENABLED
+#include "esp_bt_device.h"
+#include "esp_bt_main.h"
+#include "esp_gap_ble_api.h"
+#endif
+
+#ifdef CONFIG_BT_NIMBLE_ENABLED
+#include "console/console.h"
+#include "host/ble_hs.h"
+#include "nimble/nimble_port.h"
+#include "nimble/nimble_port_freertos.h"
+#include "services/gap/ble_svc_gap.h"
+extern void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt* ctxt, void* arg);
+extern int esp_blufi_gatt_svr_init(void);
+extern void esp_blufi_gatt_svr_deinit(void);
+extern void esp_blufi_btc_init(void);
+extern void esp_blufi_btc_deinit(void);
+#endif
+
+extern "C" {
+void esp_blufi_adv_start(void);
+
+void esp_blufi_adv_stop(void);
+
+void esp_blufi_disconnect(void);
+
+void btc_blufi_report_error(esp_blufi_error_state_t state);
+
+#ifdef CONFIG_BT_BLUEDROID_ENABLED
+void esp_blufi_gap_event_handler(esp_gap_ble_cb_event_t event, esp_ble_gap_cb_param_t* param);
+#endif
+
+#ifdef CONFIG_BT_NIMBLE_ENABLED
+void esp_blufi_gatt_svr_register_cb(struct ble_gatt_register_ctxt* ctxt, void* arg);
+int esp_blufi_gatt_svr_init(void);
+void esp_blufi_gatt_svr_deinit(void);
+void esp_blufi_btc_init(void);
+void esp_blufi_btc_deinit(void);
+#endif
+}
+
+#include <wifi_station.h>
+#include "esp_crc.h"
+#include "esp_random.h"
+#include "mbedtls/md5.h"
+#include "ssid_manager.h"
+
+static const char* BLUFI_TAG = "BLUFI_CLASS";
+
+static wifi_mode_t GetWifiModeWithFallback(const WifiManager& wifi) {
+ if (wifi.IsConfigMode()) {
+ return WIFI_MODE_AP;
+ }
+ if (wifi.IsInitialized() && wifi.IsConnected()) {
+ return WIFI_MODE_STA;
+ }
+
+ wifi_mode_t mode = WIFI_MODE_STA;
+ esp_wifi_get_mode(&mode);
+ return mode;
+}
+
+Blufi& Blufi::GetInstance() {
+ static Blufi instance;
+ return instance;
+}
+
+Blufi::Blufi()
+ : m_sec(nullptr),
+ m_ble_is_connected(false),
+ m_sta_connected(false),
+ m_sta_got_ip(false),
+ m_provisioned(false),
+ m_deinited(false),
+ m_sta_ssid_len(0),
+ m_sta_is_connecting(false) {
+ memset(&m_sta_config, 0, sizeof(m_sta_config));
+ memset(m_sta_bssid, 0, sizeof(m_sta_bssid));
+ memset(m_sta_ssid, 0, sizeof(m_sta_ssid));
+ memset(&m_sta_conn_info, 0, sizeof(m_sta_conn_info));
+}
+
+Blufi::~Blufi() {
+ if (m_sec) {
+ _security_deinit();
+ }
+}
+
+esp_err_t Blufi::init() {
+ esp_err_t ret = ESP_FAIL;
+ inited_ = true;
+ m_provisioned = false;
+ m_deinited = false;
+
+ // Start WiFi scan early to have results ready when user connects
+ auto& wifi_manager = WifiManager::GetInstance();
+ if (!wifi_manager.IsInitialized() || !wifi_manager.IsConfigMode()) {
+ // start scan immediately
+ start_wifi_scan();
+ } else {
+ ESP_LOGE(BLUFI_TAG,
+ "Blufi and WiFi hotspot network configuration cannot "
+ "be used simultaneously.");
+ return ret;
+ }
+
+#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
+ ret = _controller_init();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "BLUFI controller init failed: %s", esp_err_to_name(ret));
+ return ret;
+ }
+#endif
+
+ ret = _host_and_cb_init();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "BLUFI host and cb init failed: %s", esp_err_to_name(ret));
+ return ret;
+ }
+
+ ESP_LOGI(BLUFI_TAG, "BLUFI VERSION %04x", esp_blufi_get_version());
+ return ESP_OK;
+}
+
+esp_err_t Blufi::deinit() {
+ esp_err_t ret = ESP_OK;
+
+ if (inited_) {
+ if (m_deinited) {
+ return ESP_OK;
+ }
+ m_deinited = true;
+ ret = _host_deinit();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "Host deinit failed: %s", esp_err_to_name(ret));
+ }
+#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
+ ret = _controller_deinit();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "Controller deinit failed: %s", esp_err_to_name(ret));
+ }
+#endif
+ }
+ return ret;
+}
+
+#ifdef CONFIG_BT_BLUEDROID_ENABLED
+esp_err_t Blufi::_host_init() {
+ esp_err_t ret = esp_bluedroid_init();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s init bluedroid failed: %s", __func__, esp_err_to_name(ret));
+ return ESP_FAIL;
+ }
+ ret = esp_bluedroid_enable();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s enable bluedroid failed: %s", __func__, esp_err_to_name(ret));
+ return ESP_FAIL;
+ }
+ ESP_LOGI(BLUFI_TAG, "BD ADDR: " ESP_BD_ADDR_STR, ESP_BD_ADDR_HEX(esp_bt_dev_get_address()));
+ return ESP_OK;
+}
+
+esp_err_t Blufi::_host_deinit() {
+ esp_err_t ret = esp_blufi_profile_deinit();
+ if (ret != ESP_OK)
+ return ret;
+
+ ret = esp_bluedroid_disable();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s disable bluedroid failed: %s", __func__, esp_err_to_name(ret));
+ return ESP_FAIL;
+ }
+ ret = esp_bluedroid_deinit();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s deinit bluedroid failed: %s", __func__, esp_err_to_name(ret));
+ return ESP_FAIL;
+ }
+ return ESP_OK;
+}
+
+esp_err_t Blufi::_gap_register_callback() {
+ esp_err_t rc = esp_ble_gap_register_callback(esp_blufi_gap_event_handler);
+ if (rc) {
+ return rc;
+ }
+ return esp_blufi_profile_init();
+}
+
+esp_err_t Blufi::_host_and_cb_init() {
+ static esp_blufi_callbacks_t blufi_callbacks = {
+ .event_cb = &_event_callback_trampoline,
+ .negotiate_data_handler = &_negotiate_data_handler_trampoline,
+ .encrypt_func = &_encrypt_func_trampoline,
+ .decrypt_func = &_decrypt_func_trampoline,
+ .checksum_func = &_checksum_func_trampoline,
+ };
+
+ esp_err_t ret = _host_init();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s initialise host failed: %s", __func__, esp_err_to_name(ret));
+ return ret;
+ }
+ ret = esp_blufi_register_callbacks(&blufi_callbacks);
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s blufi register failed, error code = %x", __func__, ret);
+ return ret;
+ }
+ ret = _gap_register_callback();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s gap register failed, error code = %x", __func__, ret);
+ return ret;
+ }
+ return ESP_OK;
+}
+#endif /* CONFIG_BT_BLUEDROID_ENABLED */
+
+#ifdef CONFIG_BT_NIMBLE_ENABLED
+// Stubs for NimBLE specific store functionality
+void ble_store_config_init();
+
+void Blufi::_nimble_on_reset(int reason) {
+ ESP_LOGE(BLUFI_TAG, "NimBLE Resetting state; reason=%d", reason);
+}
+
+void Blufi::_nimble_on_sync() { esp_blufi_profile_init(); }
+
+void Blufi::_nimble_host_task(void* param) {
+ ESP_LOGI(BLUFI_TAG, "BLE Host Task Started");
+ nimble_port_run();
+ nimble_port_freertos_deinit();
+}
+
+esp_err_t Blufi::_host_init() {
+ ble_hs_cfg.reset_cb = _nimble_on_reset;
+ ble_hs_cfg.sync_cb = _nimble_on_sync;
+ ble_hs_cfg.gatts_register_cb = esp_blufi_gatt_svr_register_cb;
+
+ ble_hs_cfg.sm_io_cap = 4;
+#ifdef CONFIG_EXAMPLE_BONDING
+ ble_hs_cfg.sm_bonding = 1;
+#endif
+
+ int rc = esp_blufi_gatt_svr_init();
+ assert(rc == 0);
+
+ ble_store_config_init();
+ esp_blufi_btc_init();
+
+ esp_err_t err = esp_nimble_enable(_nimble_host_task);
+ if (err) {
+ ESP_LOGE(BLUFI_TAG, "%s failed: %s", __func__, esp_err_to_name(err));
+ return ESP_FAIL;
+ }
+ return ESP_OK;
+}
+
+esp_err_t Blufi::_host_deinit(void) {
+ esp_err_t ret = nimble_port_stop();
+ if (ret == ESP_OK) {
+ esp_nimble_deinit();
+ }
+ esp_blufi_gatt_svr_deinit();
+ ret = esp_blufi_profile_deinit();
+ esp_blufi_btc_deinit();
+ return ret;
+}
+
+esp_err_t Blufi::_gap_register_callback(void) { return ESP_OK; }
+
+esp_err_t Blufi::_host_and_cb_init() {
+ static esp_blufi_callbacks_t blufi_callbacks = {
+ .event_cb = &_event_callback_trampoline,
+ .negotiate_data_handler = &_negotiate_data_handler_trampoline,
+ .encrypt_func = &_encrypt_func_trampoline,
+ .decrypt_func = &_decrypt_func_trampoline,
+ .checksum_func = &_checksum_func_trampoline,
+ };
+
+ esp_err_t ret = esp_blufi_register_callbacks(&blufi_callbacks);
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s blufi register failed, error code = %x", __func__, ret);
+ return ret;
+ }
+
+ // Host init must be called after registering callbacks for NimBLE
+ ret = _host_init();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s initialise host failed: %s", __func__, esp_err_to_name(ret));
+ return ret;
+ }
+ return ESP_OK;
+}
+#endif /* CONFIG_BT_NIMBLE_ENABLED */
+
+#if CONFIG_BT_CONTROLLER_ENABLED || !CONFIG_BT_NIMBLE_ENABLED
+esp_err_t Blufi::_controller_init() {
+ esp_bt_controller_config_t bt_cfg = BT_CONTROLLER_INIT_CONFIG_DEFAULT();
+ esp_err_t ret = esp_bt_controller_init(&bt_cfg);
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s initialize controller failed: %s", __func__, esp_err_to_name(ret));
+ return ret;
+ }
+ ret = esp_bt_controller_enable(ESP_BT_MODE_BLE);
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s enable controller failed: %s", __func__, esp_err_to_name(ret));
+ return ret;
+ }
+
+#ifdef CONFIG_BT_NIMBLE_ENABLED
+ ret = esp_nimble_init();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "esp_nimble_init() failed: %s", esp_err_to_name(ret));
+ return ret;
+ }
+#endif
+ return ESP_OK;
+}
+
+esp_err_t Blufi::_controller_deinit() {
+ esp_err_t ret = esp_bt_controller_disable();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s disable controller failed: %s", __func__, esp_err_to_name(ret));
+ }
+ ret = esp_bt_controller_deinit();
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "%s deinit controller failed: %s", __func__, esp_err_to_name(ret));
+ }
+ return ret;
+}
+#endif
+
+static int myrand(void* rng_state, unsigned char* output, size_t len) {
+ esp_fill_random(output, len);
+ return 0;
+}
+
+void Blufi::_security_init() {
+ m_sec = new BlufiSecurity();
+ if (m_sec == nullptr) {
+ ESP_LOGE(BLUFI_TAG, "Failed to allocate security context");
+ return;
+ }
+ memset(m_sec, 0, sizeof(BlufiSecurity));
+ m_sec->dhm = new mbedtls_dhm_context();
+ m_sec->aes = new mbedtls_aes_context();
+
+ mbedtls_dhm_init(m_sec->dhm);
+ mbedtls_aes_init(m_sec->aes);
+
+ memset(m_sec->iv, 0x0, sizeof(m_sec->iv));
+}
+
+void Blufi::_security_deinit() {
+ if (m_sec == nullptr)
+ return;
+
+ if (m_sec->dh_param) {
+ free(m_sec->dh_param);
+ }
+ mbedtls_dhm_free(m_sec->dhm);
+ mbedtls_aes_free(m_sec->aes);
+ delete m_sec->dhm;
+ delete m_sec->aes;
+ delete m_sec;
+ m_sec = nullptr;
+}
+
+void Blufi::_dh_negotiate_data_handler(uint8_t* data, int len, uint8_t** output_data,
+ int* output_len, bool* need_free) {
+ if (m_sec == nullptr) {
+ ESP_LOGE(BLUFI_TAG, "Security not initialized in DH handler");
+ btc_blufi_report_error(ESP_BLUFI_INIT_SECURITY_ERROR);
+ return;
+ }
+
+ if (len < 1) {
+ ESP_LOGE(BLUFI_TAG, "DH handler: data too short");
+ btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
+ return;
+ }
+
+ uint8_t type = data[0];
+ switch (type) {
+ case 0x00:
+ if (len < 3) {
+ ESP_LOGE(BLUFI_TAG, "DH_PARAM_LEN packet too short");
+ btc_blufi_report_error(ESP_BLUFI_DATA_FORMAT_ERROR);
+ return;
+ }
+
+ m_sec->dh_param_len = (data[1] << 8) | data[2];
+ if (m_sec->dh_param) {
+ free(m_sec->dh_param);
+ m_sec->dh_param = nullptr;
+ }
+ m_sec->dh_param = (uint8_t*)malloc(m_sec->dh_param_len);
+ if (m_sec->dh_param == nullptr) {
+ ESP_LOGE(BLUFI_TAG, "DH malloc failed");
+ btc_blufi_report_error(ESP_BLUFI_DH_MALLOC_ERROR);
+ }
+ break;
+ case 0x01: {
+ if (m_sec->dh_param == nullptr) {
+ ESP_LOGE(BLUFI_TAG, "DH param not allocated");
+ btc_blufi_report_error(ESP_BLUFI_DH_PARAM_ERROR);
+ return;
+ }
+ uint8_t* param = m_sec->dh_param;
+ memcpy(m_sec->dh_param, &data[1], m_sec->dh_param_len);
+ int ret = mbedtls_dhm_read_params(m_sec->dhm, ¶m, ¶m[m_sec->dh_param_len]);
+ if (ret) {
+ ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_read_params failed %d", ret);
+ btc_blufi_report_error(ESP_BLUFI_READ_PARAM_ERROR);
+ return;
+ }
+
+ const int dhm_len = mbedtls_dhm_get_len(m_sec->dhm);
+
+ ret = mbedtls_dhm_make_public(m_sec->dhm, dhm_len, m_sec->self_public_key, dhm_len,
+ myrand, NULL);
+ if (ret != 0) {
+ ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_make_public failed: %d", ret);
+ btc_blufi_report_error(ESP_BLUFI_MAKE_PUBLIC_ERROR);
+ return;
+ }
+ ret = mbedtls_dhm_calc_secret(m_sec->dhm, m_sec->share_key, SHARE_KEY_LEN,
+ &m_sec->share_len, myrand, NULL);
+ if (ret != 0) {
+ ESP_LOGE(BLUFI_TAG, "mbedtls_dhm_calc_secret failed: %d", ret);
+ btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR);
+ return;
+ }
+
+ ret = mbedtls_md5(m_sec->share_key, m_sec->share_len, m_sec->psk);
+ if (ret != 0) {
+ ESP_LOGE(BLUFI_TAG, "mbedtls_md5 failed: %d", ret);
+ btc_blufi_report_error(ESP_BLUFI_CALC_MD5_ERROR);
+ return;
+ }
+ ret = mbedtls_aes_setkey_enc(m_sec->aes, m_sec->psk, PSK_LEN * 8);
+ if (ret != 0) {
+ ESP_LOGE(BLUFI_TAG, "mbedtls_aes_setkey_enc failed: -0x%04X", -ret);
+ btc_blufi_report_error(ESP_BLUFI_ENCRYPT_ERROR);
+ return;
+ }
+ *output_data = m_sec->self_public_key;
+ *output_len = dhm_len;
+ *need_free = false;
+ ESP_LOGI(BLUFI_TAG, "DH negotiation completed successfully");
+
+ free(m_sec->dh_param);
+ m_sec->dh_param = nullptr;
+ m_sec->dh_param_len = 0;
+ break;
+ }
+ default:
+ ESP_LOGE(BLUFI_TAG, "DH handler unknown type: %d", type);
+ }
+}
+
+int Blufi::_aes_encrypt(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
+ if (!m_sec || !m_sec->aes || !crypt_data || crypt_len <= 0) {
+ ESP_LOGE(BLUFI_TAG, "Invalid parameters for AES encryption");
+ return -ESP_ERR_INVALID_ARG;
+ }
+
+ size_t iv_offset = 0;
+ uint8_t iv0[16];
+ memcpy(iv0, m_sec->iv, 16);
+ iv0[0] = iv8;
+ int ret = mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_ENCRYPT, crypt_len, &iv_offset, iv0,
+ crypt_data, crypt_data);
+
+ if (ret == 0) {
+ return crypt_len;
+ } else {
+ ESP_LOGE(BLUFI_TAG, "AES encrypt failed: %d", ret);
+ return ret;
+ }
+}
+
+int Blufi::_aes_decrypt(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
+ if (!m_sec || !m_sec->aes || !crypt_data || crypt_len < 0) {
+ ESP_LOGE(BLUFI_TAG, "Invalid parameters for AES decryption %p %p %d", m_sec->aes,
+ crypt_data, crypt_len);
+ return -ESP_ERR_INVALID_ARG;
+ }
+
+ size_t iv_offset = 0;
+ uint8_t iv0[16];
+ memcpy(iv0, m_sec->iv, 16);
+ iv0[0] = iv8;
+ int ret = mbedtls_aes_crypt_cfb128(m_sec->aes, MBEDTLS_AES_DECRYPT, crypt_len, &iv_offset, iv0,
+ crypt_data, crypt_data);
+ if (ret != 0) {
+ ESP_LOGE(BLUFI_TAG, "AES decrypt failed: %d", ret);
+ return ret;
+ } else {
+ return crypt_len;
+ }
+}
+
+uint16_t Blufi::_crc_checksum(uint8_t iv8, uint8_t* data, int len) {
+ return esp_crc16_be(0, data, len);
+}
+
+int Blufi::_get_softap_conn_num() {
+ auto& wifi = WifiManager::GetInstance();
+ if (!wifi.IsInitialized() || !wifi.IsConfigMode()) {
+ return 0;
+ }
+
+ wifi_sta_list_t sta_list{};
+ if (esp_wifi_ap_get_sta_list(&sta_list) == ESP_OK) {
+ return sta_list.num;
+ }
+ return 0;
+}
+
+void Blufi::start_wifi_scan() {
+ ESP_LOGI(BLUFI_TAG, "Starting dedicated WiFi scan");
+
+ // Check if a scan is already in progress
+ if (m_scan_in_progress) {
+ ESP_LOGW(BLUFI_TAG, "Scan already in progress, skipping");
+ return;
+ }
+
+ m_scan_in_progress = true;
+
+ // Get current WiFi mode
+ wifi_mode_t current_mode;
+ esp_err_t err = esp_wifi_get_mode(¤t_mode);
+
+ if (current_mode == WIFI_MODE_AP) {
+ // If in AP mode, temporarily switch to APSTA to allow scanning
+ ESP_LOGI(BLUFI_TAG, "WiFi in AP mode");
+ err = esp_wifi_set_mode(WIFI_MODE_STA);
+ if (err != ESP_OK) {
+ ESP_LOGE(BLUFI_TAG, "Failed to set WiFi mode to STA: %s", esp_err_to_name(err));
+ m_scan_in_progress = false;
+ return;
+ }
+ // Need to restart WiFi for mode change to take effect
+ err = esp_wifi_start();
+ if (err != ESP_OK) {
+ ESP_LOGE(BLUFI_TAG, "Failed to start WiFi after mode switch: %s", esp_err_to_name(err));
+ m_scan_in_progress = false;
+ return;
+ }
+ // Register scan event handler
+ esp_event_handler_instance_t scan_event_instance;
+ esp_event_handler_instance_register(WIFI_EVENT, ESP_EVENT_ANY_ID,
+ &Blufi::_wifi_scan_event_handler, this,
+ &scan_event_instance);
+
+ // Start scan
+ err = esp_wifi_scan_start(NULL, false);
+ if (err != ESP_OK) {
+ ESP_LOGE(BLUFI_TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
+ m_scan_in_progress = false;
+ return;
+ }
+ } else if (current_mode == WIFI_MODE_STA) {
+ // Start scan
+ err = esp_wifi_scan_start(NULL, false);
+ if (err != ESP_OK) {
+ ESP_LOGE(BLUFI_TAG, "Failed to start WiFi scan: %s", esp_err_to_name(err));
+ m_scan_in_progress = false;
+ return;
+ }
+ } else {
+ ESP_LOGE(BLUFI_TAG, "Unexpected WiFi mode: %d", current_mode);
+ m_scan_in_progress = false;
+ return;
+ }
+
+ ESP_LOGI(BLUFI_TAG, "WiFi scan started");
+}
+
+void Blufi::_send_wifi_list() {
+ if (m_ap_records.empty()) {
+ ESP_LOGW(BLUFI_TAG, "No AP records available to send");
+ return;
+ }
+
+ ESP_LOGI(BLUFI_TAG, "Sending WiFi list with %d APs", m_ap_records.size());
+
+ std::vector<esp_blufi_ap_record_t> blufi_ap_list;
+ for (const auto& ap : m_ap_records) {
+ esp_blufi_ap_record_t blufi_ap;
+ memset(&blufi_ap, 0, sizeof(blufi_ap));
+ memcpy(blufi_ap.ssid, ap.ssid, std::min((size_t)32, sizeof(ap.ssid)));
+ blufi_ap.rssi = ap.rssi;
+ blufi_ap_list.push_back(blufi_ap);
+ }
+
+ esp_blufi_send_wifi_list(blufi_ap_list.size(), blufi_ap_list.data());
+
+ m_ap_records.clear();
+ start_wifi_scan();
+}
+
+void Blufi::_wifi_scan_event_handler(void* arg, esp_event_base_t event_base, int32_t event_id,
+ void* event_data) {
+ Blufi* self = static_cast<Blufi*>(arg);
+
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_SCAN_DONE) {
+ ESP_LOGI(BLUFI_TAG, "WiFi scan done");
+
+ uint16_t ap_num = 0;
+ esp_wifi_scan_get_ap_num(&ap_num);
+
+ if (ap_num == 0) {
+ ESP_LOGW(BLUFI_TAG, "No APs found");
+ self->m_ap_records.clear();
+ } else {
+ if (static_cast<Blufi*>(arg)->m_scan_should_save_ssid == true) {
+ self->m_ap_records.resize(ap_num);
+ esp_wifi_scan_get_ap_records(&ap_num, self->m_ap_records.data());
+
+ ESP_LOGI(BLUFI_TAG, "Found %d APs", ap_num);
+ for (const auto& ap : self->m_ap_records) {
+ ESP_LOGI(BLUFI_TAG, " SSID: %s, RSSI: %d, Authmode: %d", (char*)ap.ssid,
+ ap.rssi, ap.authmode);
+ }
+ }
+ }
+ self->m_scan_in_progress = false;
+ }
+}
+
+void Blufi::_handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param) {
+ switch (event) {
+ case ESP_BLUFI_EVENT_INIT_FINISH:
+ ESP_LOGI(BLUFI_TAG, "BLUFI init finish");
+ esp_ble_gap_set_device_name(BLUFI_DEVICE_NAME);
+ esp_blufi_adv_start();
+ break;
+ case ESP_BLUFI_EVENT_DEINIT_FINISH:
+ ESP_LOGI(BLUFI_TAG, "BLUFI deinit finish");
+ break;
+ case ESP_BLUFI_EVENT_BLE_CONNECT:
+ ESP_LOGI(BLUFI_TAG, "BLUFI ble connect");
+ m_ble_is_connected = true;
+ esp_blufi_adv_stop();
+ _security_init();
+ break;
+ case ESP_BLUFI_EVENT_BLE_DISCONNECT:
+ ESP_LOGI(BLUFI_TAG, "BLUFI ble disconnect");
+ m_ble_is_connected = false;
+ _security_deinit();
+ if (!m_provisioned) {
+ esp_blufi_adv_start();
+ } else {
+ esp_blufi_adv_stop();
+ if (!m_deinited) {
+ xTaskCreate(
+ [](void* ctx) {
+ static_cast<Blufi*>(ctx)->deinit();
+ vTaskDelete(nullptr);
+ },
+ "blufi_deinit", 4096, this, 5, nullptr);
+ }
+ }
+ break;
+ case ESP_BLUFI_EVENT_SET_WIFI_OPMODE: {
+ ESP_LOGI(BLUFI_TAG, "BLUFI Set WIFI opmode %d", param->wifi_mode.op_mode);
+ auto& wifi_manager = WifiManager::GetInstance();
+ if (!wifi_manager.IsInitialized() && !wifi_manager.Initialize()) {
+ ESP_LOGE(BLUFI_TAG, "Failed to initialize WifiManager for opmode change");
+ break;
+ }
+ switch (param->wifi_mode.op_mode) {
+ case WIFI_MODE_STA:
+ wifi_manager.StartStation();
+ break;
+ case WIFI_MODE_AP:
+ wifi_manager.StartConfigAp();
+ break;
+ case WIFI_MODE_APSTA:
+ ESP_LOGW(BLUFI_TAG, "APSTA mode not supported, starting station only");
+ wifi_manager.StartStation();
+ break;
+ default:
+ wifi_manager.StopStation();
+ wifi_manager.StopConfigAp();
+ break;
+ }
+ break;
+ }
+ case ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP: {
+ ESP_LOGI(BLUFI_TAG, "BLUFI request wifi connect to AP via esp-wifi-connect");
+ std::string ssid(reinterpret_cast<const char*>(m_sta_config.sta.ssid));
+ std::string password(reinterpret_cast<const char*>(m_sta_config.sta.password));
+
+ SsidManager::GetInstance().AddSsid(ssid, password);
+ m_scan_should_save_ssid = false;
+
+ m_sta_ssid_len = static_cast<int>(std::min(ssid.size(), sizeof(m_sta_ssid)));
+ memcpy(m_sta_ssid, ssid.c_str(), m_sta_ssid_len);
+ memset(m_sta_bssid, 0, sizeof(m_sta_bssid));
+ m_sta_connected = false;
+ m_sta_got_ip = false;
+ m_sta_is_connecting = true;
+ m_sta_conn_info = {};
+ m_sta_conn_info.sta_ssid = m_sta_ssid;
+ m_sta_conn_info.sta_ssid_len = m_sta_ssid_len;
+
+ auto& wifi_manager = WifiManager::GetInstance();
+
+ if (wifi_manager.IsInitialized()) {
+ if (wifi_manager.IsConfigMode()) {
+ wifi_manager.StopConfigAp();
+ }
+ wifi_manager.StopStation();
+ }
+
+ if (!wifi_manager.IsInitialized() && !wifi_manager.Initialize()) {
+ ESP_LOGE(BLUFI_TAG, "Failed to initialize WifiManager");
+ break;
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ wifi_manager.StartStation();
+
+ xTaskCreate(
+ [](void* ctx) {
+ auto* self = static_cast<Blufi*>(ctx);
+ auto& wifi = WifiManager::GetInstance();
+ constexpr int kConnectTimeoutMs = 10000;
+ constexpr TickType_t kDelayTick = pdMS_TO_TICKS(200);
+ int waited_ms = 0;
+
+ while (waited_ms < kConnectTimeoutMs && !wifi.IsConnected()) {
+ vTaskDelay(kDelayTick);
+ waited_ms += 200;
+ }
+
+ wifi_mode_t mode = GetWifiModeWithFallback(wifi);
+ const int softap_conn_num = _get_softap_conn_num();
+
+ if (wifi.IsConnected()) {
+ self->m_sta_is_connecting = false;
+ self->m_sta_connected = true;
+ self->m_sta_got_ip = true;
+ self->m_provisioned = true;
+
+ auto current_ssid = wifi.GetSsid();
+ if (!current_ssid.empty()) {
+ self->m_sta_ssid_len = static_cast<int>(
+ std::min(current_ssid.size(), sizeof(self->m_sta_ssid)));
+ memcpy(self->m_sta_ssid, current_ssid.c_str(), self->m_sta_ssid_len);
+ }
+
+ wifi_ap_record_t ap_info{};
+ if (esp_wifi_sta_get_ap_info(&ap_info) == ESP_OK) {
+ memcpy(self->m_sta_bssid, ap_info.bssid, sizeof(self->m_sta_bssid));
+ }
+
+ esp_blufi_extra_info_t info = {};
+ memcpy(info.sta_bssid, self->m_sta_bssid, sizeof(self->m_sta_bssid));
+ info.sta_bssid_set = true;
+ info.sta_ssid = self->m_sta_ssid;
+ info.sta_ssid_len = self->m_sta_ssid_len;
+ esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS,
+ softap_conn_num, &info);
+ ESP_LOGI(BLUFI_TAG, "connected to WiFi");
+
+ if (self->m_ble_is_connected) {
+ esp_blufi_disconnect();
+ }
+ } else {
+ self->m_sta_is_connecting = false;
+ self->m_sta_connected = false;
+ self->m_sta_got_ip = false;
+
+ esp_blufi_extra_info_t info = {};
+ info.sta_ssid = self->m_sta_ssid;
+ info.sta_ssid_len = self->m_sta_ssid_len;
+ esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL,
+ softap_conn_num, &info);
+ ESP_LOGE(BLUFI_TAG, "Failed to connect to WiFi via esp-wifi-connect");
+ }
+ vTaskDelete(nullptr);
+ },
+ "blufi_wifi_conn", 4096, this, 5, nullptr);
+ break;
+ }
+ case ESP_BLUFI_EVENT_REQ_DISCONNECT_FROM_AP:
+ ESP_LOGI(BLUFI_TAG, "BLUFI request wifi disconnect from AP");
+ if (WifiManager::GetInstance().IsInitialized()) {
+ WifiManager::GetInstance().StopStation();
+ }
+ m_sta_is_connecting = false;
+ m_sta_connected = false;
+ m_sta_got_ip = false;
+ break;
+ case ESP_BLUFI_EVENT_GET_WIFI_STATUS: {
+ auto& wifi = WifiManager::GetInstance();
+ wifi_mode_t mode = GetWifiModeWithFallback(wifi);
+ const int softap_conn_num = _get_softap_conn_num();
+
+ if (wifi.IsInitialized() && wifi.IsConnected()) {
+ m_sta_connected = true;
+ m_sta_got_ip = true;
+
+ auto current_ssid = wifi.GetSsid();
+ if (!current_ssid.empty()) {
+ m_sta_ssid_len =
+ static_cast<int>(std::min(current_ssid.size(), sizeof(m_sta_ssid)));
+ memcpy(m_sta_ssid, current_ssid.c_str(), m_sta_ssid_len);
+ }
+
+ esp_blufi_extra_info_t info;
+ memset(&info, 0, sizeof(esp_blufi_extra_info_t));
+ memcpy(info.sta_bssid, m_sta_bssid, 6);
+ info.sta_ssid = m_sta_ssid;
+ info.sta_ssid_len = m_sta_ssid_len;
+ esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_SUCCESS, softap_conn_num,
+ &info);
+ } else if (m_sta_is_connecting) {
+ esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONNECTING, softap_conn_num,
+ &m_sta_conn_info);
+ } else {
+ esp_blufi_send_wifi_conn_report(mode, ESP_BLUFI_STA_CONN_FAIL, softap_conn_num,
+ &m_sta_conn_info);
+ }
+ ESP_LOGI(BLUFI_TAG, "BLUFI get wifi status");
+ break;
+ }
+ case ESP_BLUFI_EVENT_RECV_STA_BSSID:
+ memcpy(m_sta_config.sta.bssid, param->sta_bssid.bssid, 6);
+ m_sta_config.sta.bssid_set = true;
+ ESP_LOGI(BLUFI_TAG, "Recv STA BSSID");
+ break;
+ case ESP_BLUFI_EVENT_RECV_STA_SSID:
+ strncpy((char*)m_sta_config.sta.ssid, (char*)param->sta_ssid.ssid,
+ param->sta_ssid.ssid_len);
+ m_sta_config.sta.ssid[param->sta_ssid.ssid_len] = '\0';
+ ESP_LOGI(BLUFI_TAG, "Recv STA SSID: %s", m_sta_config.sta.ssid);
+ break;
+ case ESP_BLUFI_EVENT_RECV_STA_PASSWD:
+ strncpy((char*)m_sta_config.sta.password, (char*)param->sta_passwd.passwd,
+ param->sta_passwd.passwd_len);
+ m_sta_config.sta.password[param->sta_passwd.passwd_len] = '\0';
+ ESP_LOGI(BLUFI_TAG, "Recv STA PASSWORD : %s", m_sta_config.sta.password);
+ break;
+ case ESP_BLUFI_EVENT_GET_WIFI_LIST: {
+ ESP_LOGI(BLUFI_TAG, "BLUFI get wifi list");
+ while (m_scan_in_progress) {
+ vTaskDelay(pdMS_TO_TICKS(500));
+ }
+ _send_wifi_list();
+ break;
+ }
+ default:
+ ESP_LOGW(BLUFI_TAG, "Unhandled event: %d", event);
+ break;
+ }
+}
+
+void Blufi::_event_callback_trampoline(esp_blufi_cb_event_t event, esp_blufi_cb_param_t* param) {
+ GetInstance()._handle_event(event, param);
+}
+
+void Blufi::_negotiate_data_handler_trampoline(uint8_t* data, int len, uint8_t** output_data,
+ int* output_len, bool* need_free) {
+ GetInstance()._dh_negotiate_data_handler(data, len, output_data, output_len, need_free);
+}
+
+int Blufi::_encrypt_func_trampoline(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
+ return GetInstance()._aes_encrypt(iv8, crypt_data, crypt_len);
+}
+
+int Blufi::_decrypt_func_trampoline(uint8_t iv8, uint8_t* crypt_data, int crypt_len) {
+ return GetInstance()._aes_decrypt(iv8, crypt_data, crypt_len);
+}
+
+uint16_t Blufi::_checksum_func_trampoline(uint8_t iv8, uint8_t* data, int len) {
+ return _crc_checksum(iv8, data, len);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/blufi.h b/xiaozhi-esp32-2.2.6/main/boards/common/blufi.h
new file mode 100644
index 0000000..2e02f7e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/blufi.h
@@ -0,0 +1,147 @@
+#pragma once
+
+#include <aes/esp_aes.h>
+#include <cassert>
+#include <cstring>
+#include <vector>
+#include "esp_blufi_api.h"
+#include "esp_err.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "mbedtls/aes.h"
+#include "mbedtls/dhm.h"
+#include "wifi_manager.h"
+
+class Blufi {
+public:
+ /**
+ * @brief Get the singleton instance of the Blufi class.
+ */
+ static Blufi &GetInstance();
+
+ /**
+ * @brief Start WiFi scan for Blufi provisioning
+ * This method intelligently handles WiFi scanning based on current WiFi state:
+ * - If WiFi config mode is active, it uses the existing scan results from WifiConfigurationAp
+ * - Otherwise, it performs a dedicated scan without interfering with normal WiFi operations
+ */
+ void start_wifi_scan();
+
+ /**
+ * @brief Initializes the Bluetooth controller, host, and Blufi profile.
+ * This is the main entry point to start the Blufi process.
+ * @return ESP_OK on success, otherwise an error code.
+ */
+ esp_err_t init();
+
+ /**
+ * @brief Deinitializes Blufi and the Bluetooth stack.
+ * @return ESP_OK on success, otherwise an error code.
+ */
+ esp_err_t deinit();
+
+ // Delete copy constructor and assignment operator for singleton
+ Blufi(const Blufi &) = delete;
+
+ Blufi &operator=(const Blufi &) = delete;
+
+private:
+ bool inited_ = false;
+
+ Blufi();
+
+ ~Blufi();
+
+ // Initialization logic
+ static esp_err_t _controller_init();
+
+ static esp_err_t _controller_deinit();
+
+ static esp_err_t _host_init();
+
+ static esp_err_t _host_deinit();
+
+ static esp_err_t _gap_register_callback();
+
+ static esp_err_t _host_and_cb_init();
+
+ void _security_init();
+
+ void _security_deinit();
+
+ void _dh_negotiate_data_handler(uint8_t *data, int len, uint8_t **output_data, int *output_len,
+ bool *need_free);
+
+ int _aes_encrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
+
+ int _aes_decrypt(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
+
+ static uint16_t _crc_checksum(uint8_t iv8, uint8_t *data, int len);
+
+ void _handle_event(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
+
+ static int _get_softap_conn_num();
+
+ // WiFi scan methods
+ void _send_wifi_list();
+ void _start_dedicated_wifi_scan();
+ static void _wifi_scan_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id,
+ void *event_data);
+
+ // These C-style functions are registered with ESP-IDF and call the corresponding instance
+ // methods.
+
+ static void _event_callback_trampoline(esp_blufi_cb_event_t event, esp_blufi_cb_param_t *param);
+
+ static void _negotiate_data_handler_trampoline(uint8_t *data, int len, uint8_t **output_data,
+ int *output_len, bool *need_free);
+
+ static int _encrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
+
+ static int _decrypt_func_trampoline(uint8_t iv8, uint8_t *crypt_data, int crypt_len);
+
+ static uint16_t _checksum_func_trampoline(uint8_t iv8, uint8_t *data, int len);
+
+#ifdef CONFIG_BT_NIMBLE_ENABLED
+ static void _nimble_on_reset(int reason);
+ static void _nimble_on_sync();
+ static void _nimble_host_task(void *param);
+#endif
+
+ // Security context, formerly blufi_sec struct
+ struct BlufiSecurity {
+#define DH_SELF_PUB_KEY_LEN 128
+ uint8_t self_public_key[DH_SELF_PUB_KEY_LEN];
+#define SHARE_KEY_LEN 128
+ uint8_t share_key[SHARE_KEY_LEN];
+ size_t share_len;
+#define PSK_LEN 16
+ uint8_t psk[PSK_LEN];
+ uint8_t *dh_param;
+ int dh_param_len;
+ uint8_t iv[16];
+ mbedtls_dhm_context *dhm;
+ esp_aes_context *aes;
+ };
+
+ BlufiSecurity *m_sec;
+
+ // State variables
+ wifi_config_t m_sta_config{};
+ bool m_ble_is_connected;
+ bool m_sta_connected;
+ bool m_sta_got_ip;
+ bool m_provisioned;
+ bool m_deinited;
+ uint8_t m_sta_bssid[6]{};
+ uint8_t m_sta_ssid[32]{};
+ int m_sta_ssid_len;
+ bool m_sta_is_connecting;
+ esp_blufi_extra_info_t m_sta_conn_info{};
+
+ // WiFi scan related
+ std::vector<wifi_ap_record_t> m_ap_records;
+ bool m_scan_in_progress = false;
+ bool m_scan_should_save_ssid = true;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/board.cc b/xiaozhi-esp32-2.2.6/main/boards/common/board.cc
new file mode 100644
index 0000000..8a2f85b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/board.cc
@@ -0,0 +1,178 @@
+#include "board.h"
+#include "system_info.h"
+#include "settings.h"
+#include "display/display.h"
+#include "display/oled_display.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_ota_ops.h>
+#include <esp_chip_info.h>
+#include <esp_random.h>
+
+#define TAG "Board"
+
+Board::Board() {
+ Settings settings("board", true);
+ uuid_ = settings.GetString("uuid");
+ if (uuid_.empty()) {
+ uuid_ = GenerateUuid();
+ settings.SetString("uuid", uuid_);
+ }
+ ESP_LOGI(TAG, "UUID=%s SKU=%s", uuid_.c_str(), BOARD_NAME);
+}
+
+std::string Board::GenerateUuid() {
+ // UUID v4 闇�瑕� 16 瀛楄妭鐨勯殢鏈烘暟鎹�
+ uint8_t uuid[16];
+
+ // 浣跨敤 ESP32 鐨勭‖浠堕殢鏈烘暟鐢熸垚鍣�
+ esp_fill_random(uuid, sizeof(uuid));
+
+ // 璁剧疆鐗堟湰 (鐗堟湰 4) 鍜屽彉浣撲綅
+ uuid[6] = (uuid[6] & 0x0F) | 0x40; // 鐗堟湰 4
+ uuid[8] = (uuid[8] & 0x3F) | 0x80; // 鍙樹綋 1
+
+ // 灏嗗瓧鑺傝浆鎹负鏍囧噯鐨� UUID 瀛楃涓叉牸寮�
+ char uuid_str[37];
+ snprintf(uuid_str, sizeof(uuid_str),
+ "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
+ uuid[0], uuid[1], uuid[2], uuid[3],
+ uuid[4], uuid[5], uuid[6], uuid[7],
+ uuid[8], uuid[9], uuid[10], uuid[11],
+ uuid[12], uuid[13], uuid[14], uuid[15]);
+
+ return std::string(uuid_str);
+}
+
+bool Board::GetBatteryLevel(int &level, bool& charging, bool& discharging) {
+ return false;
+}
+
+bool Board::GetTemperature(float& esp32temp){
+ return false;
+}
+
+Display* Board::GetDisplay() {
+ static NoDisplay display;
+ return &display;
+}
+
+Camera* Board::GetCamera() {
+ return nullptr;
+}
+
+Led* Board::GetLed() {
+ static NoLed led;
+ return &led;
+}
+
+std::string Board::GetSystemInfoJson() {
+ /*
+ {
+ "version": 2,
+ "flash_size": 4194304,
+ "psram_size": 0,
+ "minimum_free_heap_size": 123456,
+ "mac_address": "00:00:00:00:00:00",
+ "uuid": "00000000-0000-0000-0000-000000000000",
+ "chip_model_name": "esp32s3",
+ "chip_info": {
+ "model": 1,
+ "cores": 2,
+ "revision": 0,
+ "features": 0
+ },
+ "application": {
+ "name": "my-app",
+ "version": "1.0.0",
+ "compile_time": "2021-01-01T00:00:00Z"
+ "idf_version": "4.2-dev"
+ "elf_sha256": ""
+ },
+ "partition_table": [
+ "app": {
+ "label": "app",
+ "type": 1,
+ "subtype": 2,
+ "address": 0x10000,
+ "size": 0x100000
+ }
+ ],
+ "ota": {
+ "label": "ota_0"
+ },
+ "board": {
+ ...
+ }
+ }
+ */
+ std::string json = R"({"version":2,"language":")" + std::string(Lang::CODE) + R"(",)";
+ json += R"("flash_size":)" + std::to_string(SystemInfo::GetFlashSize()) + R"(,)";
+ json += R"("minimum_free_heap_size":")" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + R"(",)";
+ json += R"("mac_address":")" + SystemInfo::GetMacAddress() + R"(",)";
+ json += R"("uuid":")" + uuid_ + R"(",)";
+ json += R"("chip_model_name":")" + SystemInfo::GetChipModelName() + R"(",)";
+
+ esp_chip_info_t chip_info;
+ esp_chip_info(&chip_info);
+ json += R"("chip_info":{)";
+ json += R"("model":)" + std::to_string(chip_info.model) + R"(,)";
+ json += R"("cores":)" + std::to_string(chip_info.cores) + R"(,)";
+ json += R"("revision":)" + std::to_string(chip_info.revision) + R"(,)";
+ json += R"("features":)" + std::to_string(chip_info.features) + R"(},)";
+
+ auto app_desc = esp_app_get_description();
+ json += R"("application":{)";
+ json += R"("name":")" + std::string(app_desc->project_name) + R"(",)";
+ json += R"("version":")" + std::string(app_desc->version) + R"(",)";
+ json += R"("compile_time":")" + std::string(app_desc->date) + R"(T)" + std::string(app_desc->time) + R"(Z",)";
+ json += R"("idf_version":")" + std::string(app_desc->idf_ver) + R"(",)";
+ char sha256_str[65];
+ for (int i = 0; i < 32; i++) {
+ snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]);
+ }
+ json += R"("elf_sha256":")" + std::string(sha256_str) + R"(")";
+ json += R"(},)";
+
+ json += R"("partition_table": [)";
+ esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL);
+ while (it) {
+ const esp_partition_t *partition = esp_partition_get(it);
+ json += R"({)";
+ json += R"("label":")" + std::string(partition->label) + R"(",)";
+ json += R"("type":)" + std::to_string(partition->type) + R"(,)";
+ json += R"("subtype":)" + std::to_string(partition->subtype) + R"(,)";
+ json += R"("address":)" + std::to_string(partition->address) + R"(,)";
+ json += R"("size":)" + std::to_string(partition->size) + R"(},)";;
+ it = esp_partition_next(it);
+ }
+ json.pop_back(); // Remove the last comma
+ json += R"(],)";
+
+ json += R"("ota":{)";
+ auto ota_partition = esp_ota_get_running_partition();
+ json += R"("label":")" + std::string(ota_partition->label) + R"(")";
+ json += R"(},)";
+
+ // Append display info
+ auto display = GetDisplay();
+ if (display) {
+ json += R"("display":{)";
+ if (dynamic_cast<OledDisplay*>(display)) {
+ json += R"("monochrome":)" + std::string("true") + R"(,)";
+ } else {
+ json += R"("monochrome":)" + std::string("false") + R"(,)";
+ }
+ json += R"("width":)" + std::to_string(display->width()) + R"(,)";
+ json += R"("height":)" + std::to_string(display->height()) + R"(,)";
+ json.pop_back(); // Remove the last comma
+ }
+ json += R"(},)";
+
+ json += R"("board":)" + GetBoardJson();
+
+ // Close the JSON object
+ json += R"(})";
+ return json;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/board.h b/xiaozhi-esp32-2.2.6/main/boards/common/board.h
new file mode 100644
index 0000000..19d3f2f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/board.h
@@ -0,0 +1,92 @@
+#ifndef BOARD_H
+#define BOARD_H
+
+#include <http.h>
+#include <web_socket.h>
+#include <mqtt.h>
+#include <udp.h>
+#include <string>
+#include <functional>
+#include <network_interface.h>
+
+#include "led/led.h"
+#include "backlight.h"
+#include "camera.h"
+#include "assets.h"
+
+/**
+ * Network events for unified callback
+ */
+enum class NetworkEvent {
+ Scanning, // Network is scanning (WiFi scanning, etc.)
+ Connecting, // Network is connecting (data: SSID/network name)
+ Connected, // Network connected successfully (data: SSID/network name)
+ Disconnected, // Network disconnected
+ WifiConfigModeEnter, // Entered WiFi configuration mode
+ WifiConfigModeExit, // Exited WiFi configuration mode
+ // Cellular modem specific events
+ ModemDetecting, // Detecting modem (baud rate, module type)
+ ModemErrorNoSim, // No SIM card detected
+ ModemErrorRegDenied, // Network registration denied
+ ModemErrorInitFailed, // Modem initialization failed
+ ModemErrorTimeout // Operation timeout
+};
+
+// Power save level enumeration
+enum class PowerSaveLevel {
+ LOW_POWER, // Maximum power saving (lowest power consumption)
+ BALANCED, // Medium power saving (balanced)
+ PERFORMANCE, // No power saving (maximum power consumption / full performance)
+};
+
+// Network event callback type (event, data)
+// data contains additional info like SSID for Connecting/Connected events
+using NetworkEventCallback = std::function<void(NetworkEvent event, const std::string& data)>;
+
+void* create_board();
+class AudioCodec;
+class Display;
+class Board {
+private:
+ Board(const Board&) = delete; // 绂佺敤鎷疯礉鏋勯�犲嚱鏁�
+ Board& operator=(const Board&) = delete; // 绂佺敤璧嬪�兼搷浣�
+
+protected:
+ Board();
+ std::string GenerateUuid();
+
+ // 杞欢鐢熸垚鐨勮澶囧敮涓�鏍囪瘑
+ std::string uuid_;
+
+public:
+ static Board& GetInstance() {
+ static Board* instance = static_cast<Board*>(create_board());
+ return *instance;
+ }
+
+ virtual ~Board() = default;
+ virtual std::string GetBoardType() = 0;
+ virtual std::string GetUuid() { return uuid_; }
+ virtual Backlight* GetBacklight() { return nullptr; }
+ virtual Led* GetLed();
+ virtual AudioCodec* GetAudioCodec() = 0;
+ virtual bool GetTemperature(float& esp32temp);
+ virtual Display* GetDisplay();
+ virtual Camera* GetCamera();
+ virtual NetworkInterface* GetNetwork() = 0;
+ virtual void StartNetwork() = 0;
+ virtual void SetNetworkEventCallback(NetworkEventCallback callback) { (void)callback; }
+ virtual const char* GetNetworkStateIcon() = 0;
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging);
+ virtual std::string GetSystemInfoJson();
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) = 0;
+ virtual std::string GetBoardJson() = 0;
+ virtual std::string GetDeviceStatusJson() = 0;
+};
+
+#define DECLARE_BOARD(BOARD_CLASS_NAME) \
+void* create_board() { \
+ return new BOARD_CLASS_NAME(); \
+}
+
+#endif // BOARD_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/button.cc b/xiaozhi-esp32-2.2.6/main/boards/common/button.cc
new file mode 100644
index 0000000..9570a5f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/button.cc
@@ -0,0 +1,125 @@
+#include "button.h"
+
+#include <button_gpio.h>
+#include <esp_log.h>
+
+#define TAG "Button"
+
+#if CONFIG_SOC_ADC_SUPPORTED
+AdcButton::AdcButton(const button_adc_config_t& adc_config) : Button(nullptr) {
+ button_config_t btn_config = {
+ .long_press_time = 2000,
+ .short_press_time = 0,
+ };
+ ESP_ERROR_CHECK(iot_button_new_adc_device(&btn_config, &adc_config, &button_handle_));
+}
+#endif
+
+Button::Button(button_handle_t button_handle) : button_handle_(button_handle) {
+}
+
+Button::Button(gpio_num_t gpio_num, bool active_high, uint16_t long_press_time, uint16_t short_press_time, bool enable_power_save) : gpio_num_(gpio_num) {
+ if (gpio_num == GPIO_NUM_NC) {
+ return;
+ }
+ button_config_t button_config = {
+ .long_press_time = long_press_time,
+ .short_press_time = short_press_time
+ };
+ button_gpio_config_t gpio_config = {
+ .gpio_num = gpio_num,
+ .active_level = static_cast<uint8_t>(active_high ? 1 : 0),
+ .enable_power_save = enable_power_save,
+ .disable_pull = false
+ };
+ ESP_ERROR_CHECK(iot_button_new_gpio_device(&button_config, &gpio_config, &button_handle_));
+}
+
+Button::~Button() {
+ if (button_handle_ != NULL) {
+ iot_button_delete(button_handle_);
+ }
+}
+
+void Button::OnPressDown(std::function<void()> callback) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_down_ = callback;
+ iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr, [](void* handle, void* usr_data) {
+ Button* button = static_cast<Button*>(usr_data);
+ if (button->on_press_down_) {
+ button->on_press_down_();
+ }
+ }, this);
+}
+
+void Button::OnPressUp(std::function<void()> callback) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_up_ = callback;
+ iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, nullptr, [](void* handle, void* usr_data) {
+ Button* button = static_cast<Button*>(usr_data);
+ if (button->on_press_up_) {
+ button->on_press_up_();
+ }
+ }, this);
+}
+
+void Button::OnLongPress(std::function<void()> callback) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_long_press_ = callback;
+ iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, nullptr, [](void* handle, void* usr_data) {
+ Button* button = static_cast<Button*>(usr_data);
+ if (button->on_long_press_) {
+ button->on_long_press_();
+ }
+ }, this);
+}
+
+void Button::OnClick(std::function<void()> callback) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_click_ = callback;
+ iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, nullptr, [](void* handle, void* usr_data) {
+ Button* button = static_cast<Button*>(usr_data);
+ if (button->on_click_) {
+ button->on_click_();
+ }
+ }, this);
+}
+
+void Button::OnDoubleClick(std::function<void()> callback) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_double_click_ = callback;
+ iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, nullptr, [](void* handle, void* usr_data) {
+ Button* button = static_cast<Button*>(usr_data);
+ if (button->on_double_click_) {
+ button->on_double_click_();
+ }
+ }, this);
+}
+
+void Button::OnMultipleClick(std::function<void()> callback, uint8_t click_count) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_multiple_click_ = callback;
+ button_event_args_t event_args = {
+ .multiple_clicks = {
+ .clicks = click_count
+ }
+ };
+ iot_button_register_cb(button_handle_, BUTTON_MULTIPLE_CLICK, &event_args, [](void* handle, void* usr_data) {
+ Button* button = static_cast<Button*>(usr_data);
+ if (button->on_multiple_click_) {
+ button->on_multiple_click_();
+ }
+ }, this);
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/button.h b/xiaozhi-esp32-2.2.6/main/boards/common/button.h
new file mode 100644
index 0000000..ceecbe5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/button.h
@@ -0,0 +1,49 @@
+#ifndef BUTTON_H_
+#define BUTTON_H_
+
+#include <driver/gpio.h>
+#include <iot_button.h>
+#include <button_types.h>
+#include <button_adc.h>
+#include <button_gpio.h>
+#include <functional>
+
+class Button {
+public:
+ Button(button_handle_t button_handle);
+ Button(gpio_num_t gpio_num, bool active_high = false, uint16_t long_press_time = 0, uint16_t short_press_time = 0, bool enable_power_save = false);
+ ~Button();
+
+ void OnPressDown(std::function<void()> callback);
+ void OnPressUp(std::function<void()> callback);
+ void OnLongPress(std::function<void()> callback);
+ void OnClick(std::function<void()> callback);
+ void OnDoubleClick(std::function<void()> callback);
+ void OnMultipleClick(std::function<void()> callback, uint8_t click_count = 3);
+
+protected:
+ gpio_num_t gpio_num_;
+ button_handle_t button_handle_ = nullptr;
+
+ std::function<void()> on_press_down_;
+ std::function<void()> on_press_up_;
+ std::function<void()> on_long_press_;
+ std::function<void()> on_click_;
+ std::function<void()> on_double_click_;
+ std::function<void()> on_multiple_click_;
+};
+
+#if CONFIG_SOC_ADC_SUPPORTED
+class AdcButton : public Button {
+public:
+ AdcButton(const button_adc_config_t& adc_config);
+};
+#endif
+
+class PowerSaveButton : public Button {
+public:
+ PowerSaveButton(gpio_num_t gpio_num) : Button(gpio_num, false, 0, 0, true) {
+ }
+};
+
+#endif // BUTTON_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/camera.h b/xiaozhi-esp32-2.2.6/main/boards/common/camera.h
new file mode 100644
index 0000000..09a6a3e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/camera.h
@@ -0,0 +1,16 @@
+#ifndef CAMERA_H
+#define CAMERA_H
+
+#include <string>
+
+class Camera {
+public:
+ virtual void SetExplainUrl(const std::string& url, const std::string& token) = 0;
+ virtual bool Capture() = 0;
+ virtual bool SetHMirror(bool enabled) = 0;
+ virtual bool SetVFlip(bool enabled) = 0;
+ virtual bool SetSwapBytes(bool enabled) { return false; } // Optional, default no-op
+ virtual std::string Explain(const std::string& question) = 0;
+};
+
+#endif // CAMERA_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.cc b/xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.cc
new file mode 100644
index 0000000..7253e51
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.cc
@@ -0,0 +1,98 @@
+#include "dual_network_board.h"
+#include "application.h"
+#include "display.h"
+#include "assets/lang_config.h"
+#include "settings.h"
+#include <esp_log.h>
+
+static const char *TAG = "DualNetworkBoard";
+
+DualNetworkBoard::DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin, int32_t default_net_type)
+ : Board(),
+ ml307_tx_pin_(ml307_tx_pin),
+ ml307_rx_pin_(ml307_rx_pin),
+ ml307_dtr_pin_(ml307_dtr_pin) {
+
+ // 浠嶴ettings鍔犺浇缃戠粶绫诲瀷
+ network_type_ = LoadNetworkTypeFromSettings(default_net_type);
+
+ // 鍙垵濮嬪寲褰撳墠缃戠粶绫诲瀷瀵瑰簲鐨勬澘鍗�
+ InitializeCurrentBoard();
+}
+
+NetworkType DualNetworkBoard::LoadNetworkTypeFromSettings(int32_t default_net_type) {
+ Settings settings("network", true);
+ int network_type = settings.GetInt("type", default_net_type); // 榛樿浣跨敤ML307 (1)
+ return network_type == 1 ? NetworkType::ML307 : NetworkType::WIFI;
+}
+
+void DualNetworkBoard::SaveNetworkTypeToSettings(NetworkType type) {
+ Settings settings("network", true);
+ int network_type = (type == NetworkType::ML307) ? 1 : 0;
+ settings.SetInt("type", network_type);
+}
+
+void DualNetworkBoard::InitializeCurrentBoard() {
+ if (network_type_ == NetworkType::ML307) {
+ ESP_LOGI(TAG, "Initialize ML307 board");
+ current_board_ = std::make_unique<Ml307Board>(ml307_tx_pin_, ml307_rx_pin_, ml307_dtr_pin_);
+ } else {
+ ESP_LOGI(TAG, "Initialize WiFi board");
+ current_board_ = std::make_unique<WifiBoard>();
+ }
+}
+
+void DualNetworkBoard::SwitchNetworkType() {
+ auto display = GetDisplay();
+ if (network_type_ == NetworkType::WIFI) {
+ SaveNetworkTypeToSettings(NetworkType::ML307);
+ display->ShowNotification(Lang::Strings::SWITCH_TO_4G_NETWORK);
+ } else {
+ SaveNetworkTypeToSettings(NetworkType::WIFI);
+ display->ShowNotification(Lang::Strings::SWITCH_TO_WIFI_NETWORK);
+ }
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ auto& app = Application::GetInstance();
+ app.Reboot();
+}
+
+
+std::string DualNetworkBoard::GetBoardType() {
+ return current_board_->GetBoardType();
+}
+
+void DualNetworkBoard::StartNetwork() {
+ auto display = Board::GetInstance().GetDisplay();
+
+ if (network_type_ == NetworkType::WIFI) {
+ display->SetStatus(Lang::Strings::CONNECTING);
+ } else {
+ display->SetStatus(Lang::Strings::DETECTING_MODULE);
+ }
+ current_board_->StartNetwork();
+}
+
+void DualNetworkBoard::SetNetworkEventCallback(NetworkEventCallback callback) {
+ // Forward the callback to the current board
+ current_board_->SetNetworkEventCallback(std::move(callback));
+}
+
+NetworkInterface* DualNetworkBoard::GetNetwork() {
+ return current_board_->GetNetwork();
+}
+
+const char* DualNetworkBoard::GetNetworkStateIcon() {
+ return current_board_->GetNetworkStateIcon();
+}
+
+void DualNetworkBoard::SetPowerSaveLevel(PowerSaveLevel level) {
+ current_board_->SetPowerSaveLevel(level);
+}
+
+std::string DualNetworkBoard::GetBoardJson() {
+ return current_board_->GetBoardJson();
+}
+
+std::string DualNetworkBoard::GetDeviceStatusJson() {
+ return current_board_->GetDeviceStatusJson();
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.h b/xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.h
new file mode 100644
index 0000000..8bc9ec8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/dual_network_board.h
@@ -0,0 +1,60 @@
+#ifndef DUAL_NETWORK_BOARD_H
+#define DUAL_NETWORK_BOARD_H
+
+#include "board.h"
+#include "wifi_board.h"
+#include "ml307_board.h"
+#include <memory>
+
+//enum NetworkType
+enum class NetworkType {
+ WIFI,
+ ML307
+};
+
+// 鍙岀綉缁滄澘鍗$被锛屽彲浠ュ湪WiFi鍜孧L307涔嬮棿鍒囨崲
+class DualNetworkBoard : public Board {
+private:
+ // 浣跨敤鍩虹被鎸囬拡瀛樺偍褰撳墠娲诲姩鐨勬澘鍗�
+ std::unique_ptr<Board> current_board_;
+ NetworkType network_type_ = NetworkType::ML307; // Default to ML307
+
+ // ML307鐨勫紩鑴氶厤缃�
+ gpio_num_t ml307_tx_pin_;
+ gpio_num_t ml307_rx_pin_;
+ gpio_num_t ml307_dtr_pin_;
+
+ // 浠嶴ettings鍔犺浇缃戠粶绫诲瀷
+ NetworkType LoadNetworkTypeFromSettings(int32_t default_net_type);
+
+ // 淇濆瓨缃戠粶绫诲瀷鍒癝ettings
+ void SaveNetworkTypeToSettings(NetworkType type);
+
+ // 鍒濆鍖栧綋鍓嶇綉缁滅被鍨嬪搴旂殑鏉垮崱
+ void InitializeCurrentBoard();
+
+public:
+ DualNetworkBoard(gpio_num_t ml307_tx_pin, gpio_num_t ml307_rx_pin, gpio_num_t ml307_dtr_pin = GPIO_NUM_NC, int32_t default_net_type = 1);
+ virtual ~DualNetworkBoard() = default;
+
+ // 鍒囨崲缃戠粶绫诲瀷
+ void SwitchNetworkType();
+
+ // 鑾峰彇褰撳墠缃戠粶绫诲瀷
+ NetworkType GetNetworkType() const { return network_type_; }
+
+ // 鑾峰彇褰撳墠娲诲姩鐨勬澘鍗″紩鐢�
+ Board& GetCurrentBoard() const { return *current_board_; }
+
+ // 閲嶅啓Board鎺ュ彛
+ virtual std::string GetBoardType() override;
+ virtual void StartNetwork() override;
+ virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
+ virtual NetworkInterface* GetNetwork() override;
+ virtual const char* GetNetworkStateIcon() override;
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
+ virtual std::string GetBoardJson() override;
+ virtual std::string GetDeviceStatusJson() override;
+};
+
+#endif // DUAL_NETWORK_BOARD_H
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.cc b/xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.cc
new file mode 100644
index 0000000..76eaaab
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.cc
@@ -0,0 +1,322 @@
+#include "sdkconfig.h"
+
+#include <esp_heap_caps.h>
+#include <cstdio>
+#include <cstring>
+#include <esp_log.h>
+#include <img_converters.h>
+
+#include "esp32_camera.h"
+#include "board.h"
+#include "display.h"
+#include "lvgl_display.h"
+#include "mcp_server.h"
+#include "system_info.h"
+#include "jpg/image_to_jpeg.h"
+#include "esp_timer.h"
+
+#define TAG "Esp32Camera"
+
+Esp32Camera::Esp32Camera(const camera_config_t &config) {
+ esp_err_t err = esp_camera_init(&config);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "esp_camera_init failed with error 0x%x", err);
+ return;
+ }
+
+ sensor_t *s = esp_camera_sensor_get();
+ if (s) {
+ if (s->id.PID == GC0308_PID) {
+ s->set_hmirror(s, 0); // Control camera mirror: 1 for mirror, 0 for normal
+ }
+ ESP_LOGI(TAG, "Camera initialized: format=%d", config.pixel_format);
+ }
+
+ streaming_on_ = true;
+}
+
+Esp32Camera::~Esp32Camera() {
+ if (streaming_on_) {
+ if (current_fb_) {
+ esp_camera_fb_return(current_fb_);
+ current_fb_ = nullptr;
+ }
+ if (encode_buf_) {
+ heap_caps_free(encode_buf_);
+ encode_buf_ = nullptr;
+ encode_buf_size_ = 0;
+ }
+ esp_camera_deinit();
+ streaming_on_ = false;
+ }
+}
+
+void Esp32Camera::SetExplainUrl(const std::string &url, const std::string &token) {
+ explain_url_ = url;
+ explain_token_ = token;
+}
+
+bool Esp32Camera::Capture() {
+ if (encoder_thread_.joinable()) {
+ encoder_thread_.join();
+ }
+
+ if (!streaming_on_) {
+ return false;
+ }
+
+ // Get the latest frame, discard old frames for real-time performance
+ for (int i = 0; i < 2; i++) {
+ if (current_fb_) {
+ esp_camera_fb_return(current_fb_);
+ }
+ current_fb_ = esp_camera_fb_get();
+ if (!current_fb_) {
+ ESP_LOGE(TAG, "Camera capture failed");
+ return false;
+ }
+ }
+
+ // Prepare encode buffer for RGB565 format (with optional byte swapping)
+ if (current_fb_->format == PIXFORMAT_RGB565) {
+ size_t pixel_count = current_fb_->width * current_fb_->height;
+ size_t data_size = pixel_count * 2;
+
+ // Allocate or reallocate encode buffer if needed
+ if (encode_buf_size_ < data_size) {
+ if (encode_buf_) {
+ heap_caps_free(encode_buf_);
+ }
+ encode_buf_ = (uint8_t *)heap_caps_malloc(data_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (encode_buf_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for encode buffer");
+ encode_buf_size_ = 0;
+ return false;
+ }
+ encode_buf_size_ = data_size;
+ }
+
+ // Copy data to encode buffer with optional byte swapping
+ uint16_t *src = (uint16_t *)current_fb_->buf;
+ uint16_t *dst = (uint16_t *)encode_buf_;
+ if (swap_bytes_enabled_) {
+ for (size_t i = 0; i < pixel_count; i++) {
+ dst[i] = __builtin_bswap16(src[i]);
+ }
+ } else {
+ memcpy(encode_buf_, current_fb_->buf, data_size);
+ }
+
+ // Allocate separate buffer for preview display
+ uint8_t *preview_data = (uint8_t *)heap_caps_malloc(data_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (preview_data != nullptr) {
+ memcpy(preview_data, encode_buf_, data_size);
+ auto display = dynamic_cast<LvglDisplay *>(Board::GetInstance().GetDisplay());
+ if (display != nullptr) {
+ display->SetPreviewImage(std::make_unique<LvglAllocatedImage>(preview_data, data_size, current_fb_->width, current_fb_->height, current_fb_->width * 2, LV_COLOR_FORMAT_RGB565));
+ } else {
+ heap_caps_free(preview_data);
+ }
+ }
+ } else if (current_fb_->format == PIXFORMAT_JPEG) {
+ // JPEG format preview usually requires decoding, skip preview display for now, just log
+ ESP_LOGW(TAG, "JPEG capture success, len=%zu, but not supported for preview", current_fb_->len);
+ }
+
+ ESP_LOGI(TAG, "Captured frame: %dx%d, len=%zu, format=%d",
+ current_fb_->width, current_fb_->height, current_fb_->len, current_fb_->format);
+
+ return true;
+}
+
+bool Esp32Camera::SetHMirror(bool enabled) {
+ sensor_t *s = esp_camera_sensor_get();
+ if (!s) {
+ return false;
+ }
+ s->set_hmirror(s, enabled ? 1 : 0);
+ return true;
+}
+
+bool Esp32Camera::SetVFlip(bool enabled) {
+ sensor_t *s = esp_camera_sensor_get();
+ if (!s) {
+ return false;
+ }
+ s->set_vflip(s, enabled ? 1 : 0);
+ return true;
+}
+
+bool Esp32Camera::SetSwapBytes(bool enabled) {
+ swap_bytes_enabled_ = enabled;
+ return true;
+}
+
+std::string Esp32Camera::Explain(const std::string &question) {
+ if (explain_url_.empty()) {
+ throw std::runtime_error("Image explain URL or token is not set");
+ }
+
+ if (current_fb_ == nullptr) {
+ throw std::runtime_error("No camera frame captured");
+ }
+
+ // Create local JPEG queue
+ QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk));
+ if (jpeg_queue == nullptr) {
+ ESP_LOGE(TAG, "Failed to create JPEG queue");
+ throw std::runtime_error("Failed to create JPEG queue");
+ }
+
+ // Start encoding thread
+ encoder_thread_ = std::thread([this, jpeg_queue]() {
+ int64_t start_time = esp_timer_get_time();
+ uint16_t w = current_fb_->width;
+ uint16_t h = current_fb_->height;
+ v4l2_pix_fmt_t enc_fmt;
+ switch (current_fb_->format) {
+ case PIXFORMAT_RGB565:
+ enc_fmt = V4L2_PIX_FMT_RGB565;
+ break;
+ case PIXFORMAT_YUV422:
+ enc_fmt = V4L2_PIX_FMT_YUYV; // YUV422 is actually YUYV format
+ break;
+ case PIXFORMAT_YUV420:
+ enc_fmt = V4L2_PIX_FMT_YUV420;
+ break;
+ case PIXFORMAT_GRAYSCALE:
+ enc_fmt = V4L2_PIX_FMT_GREY;
+ break;
+ case PIXFORMAT_JPEG:
+ enc_fmt = V4L2_PIX_FMT_JPEG;
+ break;
+ case PIXFORMAT_RGB888:
+ enc_fmt = V4L2_PIX_FMT_RGB24;
+ break;
+ default:
+ ESP_LOGE(TAG, "Unsupported pixel format: %d", current_fb_->format);
+ return;
+ }
+
+ // Use encode buffer for RGB565, otherwise use original frame buffer
+ uint8_t *jpeg_src_buf = current_fb_->buf;
+ size_t jpeg_src_len = current_fb_->len;
+ if (current_fb_->format == PIXFORMAT_RGB565 && encode_buf_ != nullptr) {
+ jpeg_src_buf = encode_buf_;
+ jpeg_src_len = encode_buf_size_;
+ }
+
+ bool ok = image_to_jpeg_cb(jpeg_src_buf, jpeg_src_len, w, h, enc_fmt, 80,
+ [](void* arg, size_t index, const void* data, size_t len) -> size_t {
+ auto jpeg_queue = static_cast<QueueHandle_t>(arg);
+ JpegChunk chunk = {.data = nullptr, .len = len};
+ if (index == 0 && data != nullptr && len > 0) {
+ chunk.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (chunk.data == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate %zu bytes for JPEG chunk", len);
+ chunk.len = 0;
+ } else {
+ memcpy(chunk.data, data, len);
+ }
+ } else {
+ chunk.len = 0; // Sentinel or error
+ }
+ xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
+ return len;
+ }, jpeg_queue);
+
+ if (!ok) {
+ JpegChunk chunk = {.data = nullptr, .len = 0};
+ xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
+ }
+ int64_t end_time = esp_timer_get_time();
+ ESP_LOGI(TAG, "JPEG encoding time: %ld ms", int((end_time - start_time) / 1000));
+ });
+
+ auto network = Board::GetInstance().GetNetwork();
+ auto http = network->CreateHttp(3);
+ std::string boundary = "----ESP32_CAMERA_BOUNDARY";
+
+ http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
+ http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
+ if (!explain_token_.empty()) {
+ http->SetHeader("Authorization", "Bearer " + explain_token_);
+ }
+ http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
+ http->SetHeader("Transfer-Encoding", "chunked");
+ if (!http->Open("POST", explain_url_)) {
+ ESP_LOGE(TAG, "Failed to connect to explain URL");
+ encoder_thread_.join();
+ JpegChunk chunk;
+ while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) {
+ if (chunk.data != nullptr) {
+ heap_caps_free(chunk.data);
+ } else {
+ break;
+ }
+ }
+ vQueueDelete(jpeg_queue);
+ throw std::runtime_error("Failed to connect to explain URL");
+ }
+
+ {
+ std::string question_field;
+ question_field += "--" + boundary + "\r\n";
+ question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
+ question_field += "\r\n";
+ question_field += question + "\r\n";
+ http->Write(question_field.c_str(), question_field.size());
+ }
+ {
+ std::string file_header;
+ file_header += "--" + boundary + "\r\n";
+ file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
+ file_header += "Content-Type: image/jpeg\r\n";
+ file_header += "\r\n";
+ http->Write(file_header.c_str(), file_header.size());
+ }
+
+ size_t total_sent = 0;
+ bool saw_terminator = false;
+ while (true) {
+ JpegChunk chunk;
+ if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) {
+ ESP_LOGE(TAG, "Failed to receive JPEG chunk");
+ break;
+ }
+ if (chunk.data == nullptr) {
+ saw_terminator = true;
+ break;
+ }
+ http->Write((const char *)chunk.data, chunk.len);
+ total_sent += chunk.len;
+ heap_caps_free(chunk.data);
+ }
+ encoder_thread_.join();
+ vQueueDelete(jpeg_queue);
+
+ if (!saw_terminator || total_sent == 0) {
+ ESP_LOGE(TAG, "JPEG encoder failed or produced empty output");
+ throw std::runtime_error("Failed to encode image to JPEG");
+ }
+
+ {
+ std::string multipart_footer;
+ multipart_footer += "\r\n--" + boundary + "--\r\n";
+ http->Write(multipart_footer.c_str(), multipart_footer.size());
+ }
+ http->Write("", 0);
+
+ if (http->GetStatusCode() != 200) {
+ ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
+ throw std::runtime_error("Failed to upload photo");
+ }
+
+ std::string result = http->ReadAll();
+ http->Close();
+
+ size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr);
+ ESP_LOGI(TAG, "Explain image size=%dx%d, compressed size=%d, remain stack size=%d, question=%s\n%s",
+ current_fb_->width, current_fb_->height, (int)total_sent, (int)remain_stack_size, question.c_str(), result.c_str());
+ return result;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.h b/xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.h
new file mode 100644
index 0000000..e2fb071
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/esp32_camera.h
@@ -0,0 +1,44 @@
+#pragma once
+#include "sdkconfig.h"
+
+#include <lvgl.h>
+#include <thread>
+#include <memory>
+#include <vector>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/queue.h>
+
+#include "camera.h"
+#include "esp_camera.h"
+#include "jpg/image_to_jpeg.h"
+
+struct JpegChunk
+{
+ uint8_t *data;
+ size_t len;
+};
+
+class Esp32Camera : public Camera
+{
+private:
+ bool streaming_on_ = false;
+ bool swap_bytes_enabled_ = true; // Swap pixel byte order for RGB565, enabled by default
+ std::string explain_url_;
+ std::string explain_token_;
+ std::thread encoder_thread_;
+ camera_fb_t *current_fb_ = nullptr;
+ uint8_t *encode_buf_ = nullptr; // Buffer for JPEG encoding (with optional byte swap)
+ size_t encode_buf_size_ = 0;
+
+public:
+ Esp32Camera(const camera_config_t &config);
+ ~Esp32Camera();
+
+ virtual void SetExplainUrl(const std::string &url, const std::string &token) override;
+ virtual bool Capture() override;
+ virtual bool SetHMirror(bool enabled) override;
+ virtual bool SetVFlip(bool enabled) override;
+ virtual bool SetSwapBytes(bool enabled) override;
+ virtual std::string Explain(const std::string &question) override;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/esp_video.cc b/xiaozhi-esp32-2.2.6/main/boards/common/esp_video.cc
new file mode 100644
index 0000000..9fd4b12
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/esp_video.cc
@@ -0,0 +1,1041 @@
+#include "sdkconfig.h"
+
+#include <fcntl.h>
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/param.h>
+#include <unistd.h>
+#include <errno.h>
+#include <esp_heap_caps.h>
+#include <cstdio>
+#include <cstring>
+
+#include "esp_imgfx_color_convert.h"
+#include "esp_video_device.h"
+#include "esp_video_init.h"
+#include "linux/videodev2.h"
+
+#include "board.h"
+#include "display.h"
+#include "esp_video.h"
+#include "esp_jpeg_common.h"
+#include "jpg/image_to_jpeg.h"
+#include "jpg/jpeg_to_image.h"
+#include "lvgl_display.h"
+#include "mcp_server.h"
+#include "system_info.h"
+
+#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+#undef LOG_LOCAL_LEVEL
+#define LOG_LOCAL_LEVEL MAX(CONFIG_LOG_DEFAULT_LEVEL, ESP_LOG_DEBUG)
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+#include <esp_log.h> // should be after LOCAL_LOG_LEVEL definition
+
+#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+#ifdef CONFIG_IDF_TARGET_ESP32P4
+#include "driver/ppa.h"
+#if defined(CONFIG_XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_90)
+#define IMAGE_ROTATION_ANGLE (PPA_SRM_ROTATION_ANGLE_270)
+#elif defined(CONFIG_XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_270)
+#define IMAGE_ROTATION_ANGLE (PPA_SRM_ROTATION_ANGLE_90)
+#else
+#error "CONFIG_XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE is not set"
+#endif // angle
+#else // target
+#include "esp_imgfx_rotate.h"
+#if defined(CONFIG_XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_90)
+#define IMAGE_ROTATION_ANGLE (90)
+#elif defined(CONFIG_XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE_270)
+#define IMAGE_ROTATION_ANGLE (270)
+#else
+#error "CONFIG_XIAOZHI_CAMERA_IMAGE_ROTATION_ANGLE is not set"
+#endif // angle
+#endif // target
+#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+
+
+#define TAG "EspVideo"
+
+#if defined(CONFIG_CAMERA_SENSOR_SWAP_PIXEL_BYTE_ORDER) || defined(CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP)
+#warning \
+ "CAMERA_SENSOR_SWAP_PIXEL_BYTE_ORDER or CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP is enabled, which may cause image corruption in YUV422 format!"
+#endif
+
+#if CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+#define CAM_PRINT_FOURCC(pixelformat) \
+ char fourcc[5]; \
+ fourcc[0] = pixelformat & 0xFF; \
+ fourcc[1] = (pixelformat >> 8) & 0xFF; \
+ fourcc[2] = (pixelformat >> 16) & 0xFF; \
+ fourcc[3] = (pixelformat >> 24) & 0xFF; \
+ fourcc[4] = '\0'; \
+ ESP_LOGD(TAG, "FOURCC: '%c%c%c%c'", fourcc[0], fourcc[1], fourcc[2], fourcc[3]);
+
+// for compatibility with old esp_video version
+#ifndef MAP_FAILED
+#define MAP_FAILED nullptr
+#endif
+
+__attribute__((weak)) esp_err_t esp_video_deinit(void) {
+ return ESP_ERR_NOT_SUPPORTED;
+}
+// end of for compatibility with old esp_video version
+
+static void log_available_video_devices() {
+ for (int i = 0; i < 50; i++) {
+ char path[16];
+ snprintf(path, sizeof(path), "/dev/video%d", i);
+ int fd = open(path, O_RDONLY);
+ if (fd >= 0) {
+ ESP_LOGD(TAG, "found video device: %s", path);
+ close(fd);
+ }
+ }
+}
+#else
+#define CAM_PRINT_FOURCC(pixelformat) (void)0;
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+
+EspVideo::EspVideo(const esp_video_init_config_t& config) {
+ if (esp_video_init(&config) != ESP_OK) {
+ ESP_LOGE(TAG, "esp_video_init failed");
+ return;
+ }
+
+#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+
+ const char* video_device_name = nullptr;
+
+ if (false) { /* 鐢ㄤ簬鏋勫缓 else if */
+ }
+#if CONFIG_ESP_VIDEO_ENABLE_MIPI_CSI_VIDEO_DEVICE
+ else if (config.csi != nullptr) {
+ video_device_name = ESP_VIDEO_MIPI_CSI_DEVICE_NAME;
+ }
+#endif
+#if CONFIG_ESP_VIDEO_ENABLE_DVP_VIDEO_DEVICE
+ else if (config.dvp != nullptr) {
+ video_device_name = ESP_VIDEO_DVP_DEVICE_NAME;
+ }
+#endif
+#if CONFIG_ESP_VIDEO_ENABLE_HW_JPEG_VIDEO_DEVICE
+ else if (config.jpeg != nullptr) {
+ video_device_name = ESP_VIDEO_JPEG_DEVICE_NAME;
+ }
+#endif
+#if CONFIG_ESP_VIDEO_ENABLE_SPI_VIDEO_DEVICE
+ else if (config.spi != nullptr) {
+ video_device_name = ESP_VIDEO_SPI_DEVICE_NAME;
+ }
+#endif
+#if CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ else if (config.usb_uvc != nullptr) {
+ video_device_name = ESP_VIDEO_USB_UVC_DEVICE_NAME(0);
+ }
+#endif
+
+ if (video_device_name == nullptr) {
+ ESP_LOGE(TAG, "no video device is enabled");
+ return;
+ }
+
+ video_fd_ = open(video_device_name, O_RDWR);
+
+ if (video_fd_ < 0) {
+ ESP_LOGE(TAG, "open %s failed, errno=%d(%s)", video_device_name, errno, strerror(errno));
+#if CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+ log_available_video_devices();
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+ return;
+ }
+
+ struct v4l2_capability cap = {};
+ if (ioctl(video_fd_, VIDIOC_QUERYCAP, &cap) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_QUERYCAP failed, errno=%d(%s)", errno, strerror(errno));
+ close(video_fd_);
+ video_fd_ = -1;
+ return;
+ }
+
+ ESP_LOGD(
+ TAG,
+ "VIDIOC_QUERYCAP: driver=%s, card=%s, bus_info=%s, version=0x%08lx, capabilities=0x%08lx, device_caps=0x%08lx",
+ cap.driver, cap.card, cap.bus_info, cap.version, cap.capabilities, cap.device_caps);
+
+ struct v4l2_format format = {};
+ format.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (ioctl(video_fd_, VIDIOC_G_FMT, &format) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_G_FMT failed, errno=%d(%s)", errno, strerror(errno));
+ close(video_fd_);
+ video_fd_ = -1;
+ return;
+ }
+ ESP_LOGD(TAG, "VIDIOC_G_FMT: pixelformat=0x%08lx, width=%ld, height=%ld", format.fmt.pix.pixelformat,
+ format.fmt.pix.width, format.fmt.pix.height);
+ CAM_PRINT_FOURCC(format.fmt.pix.pixelformat);
+
+ struct v4l2_format setformat = {};
+ setformat.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ sensor_width_ = format.fmt.pix.width;
+ sensor_height_ = format.fmt.pix.height;
+#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ setformat.fmt.pix.width = format.fmt.pix.width;
+ setformat.fmt.pix.height = format.fmt.pix.height;
+
+ struct v4l2_fmtdesc fmtdesc = {};
+ fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ fmtdesc.index = 0;
+ uint32_t best_fmt = 0;
+ int best_rank = 1 << 30; // large number
+
+ // 娉�: 褰撳墠鐗堟湰 esp_video 涓� YUV422P 瀹為檯杈撳嚭涓� YUYV銆�
+#if defined(CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE) && defined(CONFIG_SOC_PPA_SUPPORTED)
+ auto get_rank = [](uint32_t fmt) -> int {
+ switch (fmt) {
+ case V4L2_PIX_FMT_RGB24:
+ return 0;
+ case V4L2_PIX_FMT_RGB565:
+ return 1;
+#ifdef CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+ case V4L2_PIX_FMT_YUV420: // 杞欢 JPEG 缂栫爜鍣ㄤ笉鏀寔 YUV420 鏍煎紡
+ return 2;
+#endif // CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+ case V4L2_PIX_FMT_GREY:
+ case V4L2_PIX_FMT_YUV422P:
+ default:
+ return 1 << 29; // unsupported
+ }
+ };
+#else
+ auto get_rank = [](uint32_t fmt) -> int {
+ switch (fmt) {
+ case V4L2_PIX_FMT_YUV422P:
+ return 10;
+ case V4L2_PIX_FMT_RGB565:
+ return 11;
+ case V4L2_PIX_FMT_RGB24:
+ return 12;
+#ifdef CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+ case V4L2_PIX_FMT_YUV420:
+ return 13;
+#endif // CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+#ifdef CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+ case V4L2_PIX_FMT_JPEG:
+ return 5;
+#endif // CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+ case V4L2_PIX_FMT_GREY:
+ return 20;
+ default:
+ return 1 << 29; // unsupported
+ }
+ };
+#endif
+ while (ioctl(video_fd_, VIDIOC_ENUM_FMT, &fmtdesc) == 0) {
+ ESP_LOGD(TAG, "VIDIOC_ENUM_FMT: pixelformat=0x%08lx, description=%s", fmtdesc.pixelformat, fmtdesc.description);
+ CAM_PRINT_FOURCC(fmtdesc.pixelformat);
+ int rank = get_rank(fmtdesc.pixelformat);
+ if (rank < best_rank) {
+ best_rank = rank;
+ best_fmt = fmtdesc.pixelformat;
+ }
+ fmtdesc.index++;
+ }
+ if (best_rank < (1 << 29)) {
+ setformat.fmt.pix.pixelformat = best_fmt;
+ sensor_format_ = best_fmt;
+ }
+
+ if (!setformat.fmt.pix.pixelformat) {
+ ESP_LOGE(TAG, "no supported pixel format found");
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+
+ ESP_LOGD(TAG, "selected pixel format: 0x%08lx", setformat.fmt.pix.pixelformat);
+
+ if (ioctl(video_fd_, VIDIOC_S_FMT, &setformat) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_S_FMT failed, errno=%d(%s)", errno, strerror(errno));
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+
+#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ frame_.width = setformat.fmt.pix.height;
+ frame_.height = setformat.fmt.pix.width;
+#else
+ frame_.width = setformat.fmt.pix.width;
+ frame_.height = setformat.fmt.pix.height;
+#endif
+
+ // 鐢宠缂撳啿骞秏map
+ struct v4l2_requestbuffers req = {};
+ req.count = strcmp(video_device_name, ESP_VIDEO_MIPI_CSI_DEVICE_NAME) == 0 ? 2 : 1;
+ req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ req.memory = V4L2_MEMORY_MMAP;
+ if (ioctl(video_fd_, VIDIOC_REQBUFS, &req) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_REQBUFS failed");
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+ mmap_buffers_.resize(req.count);
+ for (uint32_t i = 0; i < req.count; i++) {
+ struct v4l2_buffer buf = {};
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ buf.index = i;
+ if (ioctl(video_fd_, VIDIOC_QUERYBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_QUERYBUF failed");
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+ void* start = mmap(NULL, buf.length, PROT_READ | PROT_WRITE, MAP_SHARED, video_fd_, buf.m.offset);
+ if (start == MAP_FAILED) {
+ ESP_LOGE(TAG, "mmap failed");
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+ mmap_buffers_[i].start = start;
+ mmap_buffers_[i].length = buf.length;
+
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_QBUF failed");
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+ }
+
+ int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ if (ioctl(video_fd_, VIDIOC_STREAMON, &type) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_STREAMON failed");
+ close(video_fd_);
+ video_fd_ = -1;
+ sensor_format_ = 0;
+ return;
+ }
+
+#ifdef CONFIG_ESP_VIDEO_ENABLE_ISP_VIDEO_DEVICE
+ // 褰撳惎鐢� ISP 鏃讹紝ISP 闇�瑕佷竴浜涚収鐗囨潵鍒濆鍖栧弬鏁帮紝鍥犳寮�鍚悗鍚庡彴鎷嶆憚5s鐓х墖骞朵涪寮�
+ xTaskCreate(
+ [](void* arg) {
+ EspVideo* self = static_cast<EspVideo*>(arg);
+ uint16_t capture_count = 0;
+ TickType_t start = xTaskGetTickCount();
+ TickType_t duration = 5000 / portTICK_PERIOD_MS; // 5s
+ while ((xTaskGetTickCount() - start) < duration) {
+ struct v4l2_buffer buf = {};
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ if (ioctl(self->video_fd_, VIDIOC_DQBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_DQBUF failed during init");
+ vTaskDelay(10 / portTICK_PERIOD_MS);
+ continue;
+ }
+ if (ioctl(self->video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_QBUF failed during init");
+ }
+ capture_count++;
+ }
+ ESP_LOGI(TAG, "Camera init success, captured %d frames in %lums", capture_count,
+ (unsigned long)((xTaskGetTickCount() - start) * portTICK_PERIOD_MS));
+ self->streaming_on_ = true;
+ vTaskDelete(NULL);
+ },
+ "CameraInitTask", 4096, this, 5, nullptr);
+#else
+ ESP_LOGI(TAG, "Camera init success");
+ streaming_on_ = true;
+#endif // CONFIG_ESP_VIDEO_ENABLE_ISP_VIDEO_DEVICE
+}
+
+EspVideo::~EspVideo() {
+ if (streaming_on_ && video_fd_ >= 0) {
+ int type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ ioctl(video_fd_, VIDIOC_STREAMOFF, &type);
+ }
+ for (auto& b : mmap_buffers_) {
+ if (b.start && b.length) {
+ munmap(b.start, b.length);
+ }
+ }
+ if (video_fd_ >= 0) {
+ close(video_fd_);
+ video_fd_ = -1;
+ }
+ sensor_format_ = 0;
+ esp_video_deinit();
+}
+
+void EspVideo::SetExplainUrl(const std::string& url, const std::string& token) {
+ explain_url_ = url;
+ explain_token_ = token;
+}
+
+bool EspVideo::Capture() {
+ if (encoder_thread_.joinable()) {
+ encoder_thread_.join();
+ }
+
+ if (!streaming_on_ || video_fd_ < 0) {
+ return false;
+ }
+
+ for (int i = 0; i < 3; i++) {
+ struct v4l2_buffer buf = {};
+ buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
+ buf.memory = V4L2_MEMORY_MMAP;
+ if (ioctl(video_fd_, VIDIOC_DQBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_DQBUF failed");
+ return false;
+ }
+ if (i == 2) {
+ // 淇濆瓨甯у壇鏈埌PSRAM
+ if (frame_.data) {
+ heap_caps_free(frame_.data);
+ frame_.data = nullptr;
+ frame_.format = 0;
+ }
+ frame_.len = buf.bytesused;
+ frame_.data = (uint8_t*)heap_caps_malloc(frame_.len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (!frame_.data) {
+ ESP_LOGE(TAG, "alloc frame copy failed: need allocate %lu bytes", buf.bytesused);
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ ESP_LOGW(TAG, "mmap_buffers_[buf.index].length = %d, sensor_width = %d, sensor_height = %d",
+ mmap_buffers_[buf.index].length, sensor_width_, sensor_height_);
+#else
+ ESP_LOGW(TAG, "mmap_buffers_[buf.index].length = %d, frame.width = %d, frame.height = %d",
+ mmap_buffers_[buf.index].length, frame_.width, frame_.height);
+#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ ESP_LOG_BUFFER_HEXDUMP(TAG, mmap_buffers_[buf.index].start, MIN(mmap_buffers_[buf.index].length, 256),
+ ESP_LOG_DEBUG);
+
+ switch (sensor_format_) {
+ case V4L2_PIX_FMT_RGB565:
+ case V4L2_PIX_FMT_RGB24:
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_GREY:
+#ifdef CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+ case V4L2_PIX_FMT_JPEG:
+#endif // CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP
+ {
+ auto src16 = (uint16_t*)mmap_buffers_[buf.index].start;
+ auto dst16 = (uint16_t*)frame_.data;
+ size_t count = (size_t)mmap_buffers_[buf.index].length / 2;
+ for (size_t i = 0; i < count; i++) {
+ dst16[i] = __builtin_bswap16(src16[i]);
+ }
+ }
+#else
+ memcpy(frame_.data, mmap_buffers_[buf.index].start,
+ MIN(mmap_buffers_[buf.index].length, frame_.len));
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP
+ frame_.format = sensor_format_;
+ break;
+ case V4L2_PIX_FMT_YUV422P: {
+ // 杩欎釜鏍煎紡鏄� 422 YUYV锛屼笉鏄� planer
+ frame_.format = V4L2_PIX_FMT_YUYV;
+#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP
+ {
+ auto src16 = (uint16_t*)mmap_buffers_[buf.index].start;
+ auto dst16 = (uint16_t*)frame_.data;
+ size_t count = (size_t)mmap_buffers_[buf.index].length / 2;
+ for (size_t i = 0; i < count; i++) {
+ dst16[i] = __builtin_bswap16(src16[i]);
+ }
+ }
+#else
+ memcpy(frame_.data, mmap_buffers_[buf.index].start,
+ MIN(mmap_buffers_[buf.index].length, frame_.len));
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP
+ break;
+ }
+ case V4L2_PIX_FMT_RGB565X: {
+ // 澶х搴忕殑 RGB565 闇�瑕佽浆鎹负灏忕搴�
+ // 鐩墠 esp_video 鐨勫ぇ灏忕閮戒細杩斿洖鏍煎紡涓� RGB565锛屼笉浼氳繑鍥炴牸寮忎负 RGB565X锛屾 case 鐢ㄤ簬鏈潵鐗堟湰鍏煎
+ auto src16 = (uint16_t*)mmap_buffers_[buf.index].start;
+ auto dst16 = (uint16_t*)frame_.data;
+ size_t pixel_count = (size_t)frame_.width * (size_t)frame_.height;
+ for (size_t i = 0; i < pixel_count; i++) {
+ dst16[i] = __builtin_bswap16(src16[i]);
+ }
+ frame_.format = V4L2_PIX_FMT_RGB565;
+ break;
+ }
+ default:
+ ESP_LOGE(TAG, "unsupported sensor format: 0x%08lx", sensor_format_);
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+#ifndef CONFIG_SOC_PPA_SUPPORTED
+ uint8_t* rotate_dst =
+ (uint8_t*)heap_caps_aligned_alloc(64, frame_.len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (rotate_dst == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for rotate image");
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+ uint8_t* rotate_src = (uint8_t*)frame_.data;
+
+ esp_imgfx_rotate_cfg_t rotate_cfg = {
+ .in_res =
+ {
+ .width = static_cast<int16_t>(sensor_width_),
+ .height = static_cast<int16_t>(sensor_height_),
+ },
+ .degree = IMAGE_ROTATION_ANGLE,
+ };
+ switch (frame_.format) {
+ case V4L2_PIX_FMT_RGB565:
+ rotate_cfg.in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_LE;
+ break;
+ case V4L2_PIX_FMT_YUYV:
+ rotate_cfg.in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_LE;
+ break;
+ case V4L2_PIX_FMT_GREY:
+ rotate_cfg.in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_Y;
+ break;
+ case V4L2_PIX_FMT_RGB24:
+ rotate_cfg.in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888;
+ break;
+ default:
+ ESP_LOGE(TAG, "unsupported sensor format: 0x%08lx", sensor_format_);
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+ esp_imgfx_rotate_handle_t rotate_handle = nullptr;
+ esp_imgfx_err_t imgfx_err = esp_imgfx_rotate_open(&rotate_cfg, &rotate_handle);
+ if (imgfx_err != ESP_IMGFX_ERR_OK || rotate_handle == nullptr) {
+ ESP_LOGE(TAG, "esp_imgfx_rotate_create failed");
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+ esp_imgfx_data_t rotate_input_data = {
+ .data = rotate_src,
+ .data_len = frame_.len,
+ };
+ esp_imgfx_data_t rotate_output_data = {
+ .data = rotate_dst,
+ .data_len = frame_.len,
+ };
+
+ imgfx_err = esp_imgfx_rotate_process(rotate_handle, &rotate_input_data, &rotate_output_data);
+ if (imgfx_err != ESP_IMGFX_ERR_OK) {
+ ESP_LOGE(TAG, "esp_imgfx_rotate_process failed");
+ heap_caps_free(rotate_dst);
+ rotate_dst = nullptr;
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ esp_imgfx_rotate_close(rotate_handle);
+ rotate_handle = nullptr;
+ return false;
+ }
+
+ frame_.data = rotate_dst;
+
+ heap_caps_free(rotate_src);
+ rotate_src = nullptr;
+
+ esp_imgfx_rotate_close(rotate_handle);
+ rotate_handle = nullptr;
+#else // CONFIG_SOC_PPA_SUPPORTED
+ uint8_t* rotate_src = nullptr;
+
+ ppa_srm_color_mode_t ppa_color_mode;
+ switch (frame_.format) {
+ case V4L2_PIX_FMT_RGB565:
+ rotate_src = (uint8_t*)frame_.data;
+ ppa_color_mode = PPA_SRM_COLOR_MODE_RGB565;
+ break;
+ case V4L2_PIX_FMT_RGB24:
+ rotate_src = (uint8_t*)frame_.data;
+ ppa_color_mode = PPA_SRM_COLOR_MODE_RGB888;
+ break;
+ case V4L2_PIX_FMT_YUYV: {
+ ESP_LOGW(TAG, "YUYV format is not supported for PPA rotation, using software conversion to RGB888");
+ rotate_src = (uint8_t*)heap_caps_malloc(frame_.width * frame_.height * 3,
+ MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (rotate_src == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for rotate image");
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+ esp_imgfx_color_convert_cfg_t convert_cfg = {
+ .in_res = {.width = static_cast<int16_t>(frame_.width),
+ .height = static_cast<int16_t>(frame_.height)},
+ .in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_YUYV,
+ .out_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888,
+ };
+ esp_imgfx_color_convert_handle_t convert_handle = nullptr;
+ esp_imgfx_err_t err = esp_imgfx_color_convert_open(&convert_cfg, &convert_handle);
+ if (err != ESP_IMGFX_ERR_OK || convert_handle == nullptr) {
+ ESP_LOGE(TAG, "esp_imgfx_color_convert_open failed");
+ heap_caps_free(rotate_src);
+ rotate_src = nullptr;
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+ esp_imgfx_data_t convert_input_data = {
+ .data = frame_.data,
+ .data_len = frame_.len,
+ };
+ esp_imgfx_data_t convert_output_data = {
+ .data = rotate_src,
+ .data_len = static_cast<uint32_t>(frame_.width * frame_.height * 3),
+ };
+ err = esp_imgfx_color_convert_process(convert_handle, &convert_input_data, &convert_output_data);
+ if (err != ESP_IMGFX_ERR_OK) {
+ ESP_LOGE(TAG, "esp_imgfx_color_convert_process failed");
+ heap_caps_free(rotate_src);
+ rotate_src = nullptr;
+ esp_imgfx_color_convert_close(convert_handle);
+ convert_handle = nullptr;
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+ esp_imgfx_color_convert_close(convert_handle);
+ convert_handle = nullptr;
+ ppa_color_mode = PPA_SRM_COLOR_MODE_RGB888;
+ heap_caps_free(frame_.data);
+ frame_.data = rotate_src;
+ frame_.len = frame_.width * frame_.height * 3;
+ break;
+ }
+ default:
+ ESP_LOGE(TAG, "unsupported sensor format for PPA rotation: 0x%08lx", sensor_format_);
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+ uint8_t* rotate_dst = (uint8_t*)heap_caps_malloc(
+ frame_.width * frame_.height * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT | MALLOC_CAP_CACHE_ALIGNED);
+ if (rotate_dst == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for rotate image");
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+ ppa_client_handle_t ppa_client = nullptr;
+ ppa_client_config_t client_cfg = {
+ .oper_type = PPA_OPERATION_SRM,
+ .max_pending_trans_num = 1,
+ };
+ esp_err_t err = ppa_register_client(&client_cfg, &ppa_client);
+ if (err != ESP_OK || ppa_client == nullptr) {
+ ESP_LOGE(TAG, "ppa_register_client failed: %d", (int)err);
+ heap_caps_free(rotate_dst);
+ rotate_dst = nullptr;
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+ ppa_srm_rotation_angle_t ppa_angle = IMAGE_ROTATION_ANGLE;
+
+ ppa_srm_oper_config_t srm_cfg = {};
+ srm_cfg.in.buffer = (void*)rotate_src;
+ srm_cfg.in.pic_w = sensor_width_;
+ srm_cfg.in.pic_h = sensor_height_;
+ srm_cfg.in.block_w = sensor_width_;
+ srm_cfg.in.block_h = sensor_height_;
+ srm_cfg.in.block_offset_x = 0;
+ srm_cfg.in.block_offset_y = 0;
+ srm_cfg.in.srm_cm = ppa_color_mode;
+
+ srm_cfg.out.buffer = (void*)rotate_dst;
+ srm_cfg.out.buffer_size = frame_.len;
+ srm_cfg.out.pic_w = frame_.width;
+ srm_cfg.out.pic_h = frame_.height;
+ srm_cfg.out.block_offset_x = 0;
+ srm_cfg.out.block_offset_y = 0;
+ srm_cfg.out.srm_cm = PPA_SRM_COLOR_MODE_RGB565;
+
+ // 绛夋瘮渚嬬缉鏀� 1.0
+ srm_cfg.scale_x = 1.0f;
+ srm_cfg.scale_y = 1.0f;
+ srm_cfg.rotation_angle = ppa_angle;
+ srm_cfg.mode = PPA_TRANS_MODE_BLOCKING;
+ srm_cfg.user_data = nullptr;
+
+ err = ppa_do_scale_rotate_mirror(ppa_client, &srm_cfg);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "ppa_do_scale_rotate_mirror failed: %d", (int)err);
+ heap_caps_free(rotate_dst);
+ rotate_dst = nullptr;
+ (void)ppa_unregister_client(ppa_client);
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "Cleanup: VIDIOC_QBUF failed");
+ }
+ return false;
+ }
+
+ (void)ppa_unregister_client(ppa_client);
+
+ frame_.data = rotate_dst;
+ frame_.len = frame_.width * frame_.height * 2;
+ frame_.format = V4L2_PIX_FMT_RGB565;
+ heap_caps_free(rotate_src);
+ rotate_src = nullptr;
+#endif // CONFIG_SOC_PPA_SUPPORTED
+#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ }
+
+ if (ioctl(video_fd_, VIDIOC_QBUF, &buf) != 0) {
+ ESP_LOGE(TAG, "VIDIOC_QBUF failed");
+ }
+ }
+
+ // 鏄剧ず棰勮鍥剧墖
+ auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
+ if (display != nullptr) {
+ if (!frame_.data) {
+ ESP_LOGE(TAG, "frame.data is null");
+ return false;
+ }
+ uint16_t w = frame_.width;
+ uint16_t h = frame_.height;
+ size_t lvgl_image_size = frame_.len;
+ size_t stride = ((w * 2) + 3) & ~3; // 4瀛楄妭瀵归綈
+ lv_color_format_t color_format = LV_COLOR_FORMAT_RGB565;
+ uint8_t* data = nullptr;
+
+ switch (frame_.format) {
+ // LVGL 鏄剧ず YUV 绯荤殑鍥惧儚浼间箮閮芥湁闂锛屾殏鏃惰浆鎹负 RGB565 鏄剧ず
+ case V4L2_PIX_FMT_YUYV:
+ case V4L2_PIX_FMT_YUV420:
+ case V4L2_PIX_FMT_RGB24: {
+ color_format = LV_COLOR_FORMAT_RGB565;
+ data = (uint8_t*)heap_caps_malloc(w * h * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (data == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for preview image");
+ return false;
+ }
+ esp_imgfx_color_convert_cfg_t convert_cfg = {
+ .in_res = {.width = static_cast<int16_t>(frame_.width),
+ .height = static_cast<int16_t>(frame_.height)},
+ .in_pixel_fmt = static_cast<esp_imgfx_pixel_fmt_t>(frame_.format),
+ .out_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_LE,
+ .color_space_std = ESP_IMGFX_COLOR_SPACE_STD_BT601,
+ };
+ esp_imgfx_color_convert_handle_t convert_handle = nullptr;
+ esp_imgfx_err_t err = esp_imgfx_color_convert_open(&convert_cfg, &convert_handle);
+ if (err != ESP_IMGFX_ERR_OK || convert_handle == nullptr) {
+ ESP_LOGE(TAG, "esp_imgfx_color_convert_open failed");
+ heap_caps_free(data);
+ data = nullptr;
+ return false;
+ }
+ esp_imgfx_data_t convert_input_data = {
+ .data = frame_.data,
+ .data_len = frame_.len,
+ };
+ esp_imgfx_data_t convert_output_data = {
+ .data = data,
+ .data_len = static_cast<uint32_t>(w * h * 2),
+ };
+ err = esp_imgfx_color_convert_process(convert_handle, &convert_input_data, &convert_output_data);
+ if (err != ESP_IMGFX_ERR_OK) {
+ ESP_LOGE(TAG, "esp_imgfx_color_convert_process failed");
+ heap_caps_free(data);
+ data = nullptr;
+ esp_imgfx_color_convert_close(convert_handle);
+ convert_handle = nullptr;
+ return false;
+ }
+ esp_imgfx_color_convert_close(convert_handle);
+ convert_handle = nullptr;
+ lvgl_image_size = w * h * 2;
+ break;
+ }
+
+ case V4L2_PIX_FMT_RGB565:
+ data = (uint8_t*)heap_caps_malloc(w * h * 2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (data == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for preview image");
+ return false;
+ }
+ memcpy(data, frame_.data, frame_.len);
+ lvgl_image_size = frame_.len; // fallthrough 鏃跺吋椤� YUYV 涓� RGB565
+ break;
+
+#ifdef CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+ case V4L2_PIX_FMT_JPEG: {
+ uint8_t* out_data = nullptr; // out data is allocated by jpeg_to_image
+ size_t out_len = 0;
+ size_t out_width = 0;
+ size_t out_height = 0;
+ size_t out_stride = 0;
+
+ esp_err_t ret =
+ jpeg_to_image(frame_.data, frame_.len, &out_data, &out_len, &out_width, &out_height, &out_stride);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to decode JPEG image: %d (%s)", (int)ret, esp_err_to_name(ret));
+ if (out_data) {
+ heap_caps_free(out_data);
+ out_data = nullptr;
+ }
+ return false;
+ }
+
+ data = out_data;
+ w = out_width;
+ h = out_height;
+ lvgl_image_size = out_len;
+ stride = out_stride;
+ break;
+ }
+#endif
+ default:
+ ESP_LOGE(TAG, "unsupported frame format: 0x%08lx", frame_.format);
+ return false;
+ }
+
+ auto image = std::make_unique<LvglAllocatedImage>(data, lvgl_image_size, w, h, stride, color_format);
+ display->SetPreviewImage(std::move(image));
+ }
+ return true;
+}
+
+bool EspVideo::SetHMirror(bool enabled) {
+ if (video_fd_ < 0)
+ return false;
+ struct v4l2_ext_controls ctrls = {};
+ struct v4l2_ext_control ctrl = {};
+ ctrl.id = V4L2_CID_HFLIP;
+ ctrl.value = enabled ? 1 : 0;
+ ctrls.ctrl_class = V4L2_CTRL_CLASS_USER;
+ ctrls.count = 1;
+ ctrls.controls = &ctrl;
+ if (ioctl(video_fd_, VIDIOC_S_EXT_CTRLS, &ctrls) != 0) {
+ ESP_LOGE(TAG, "set HFLIP failed");
+ return false;
+ }
+ return true;
+}
+
+bool EspVideo::SetVFlip(bool enabled) {
+ if (video_fd_ < 0)
+ return false;
+ struct v4l2_ext_controls ctrls = {};
+ struct v4l2_ext_control ctrl = {};
+ ctrl.id = V4L2_CID_VFLIP;
+ ctrl.value = enabled ? 1 : 0;
+ ctrls.ctrl_class = V4L2_CTRL_CLASS_USER;
+ ctrls.count = 1;
+ ctrls.controls = &ctrl;
+ if (ioctl(video_fd_, VIDIOC_S_EXT_CTRLS, &ctrls) != 0) {
+ ESP_LOGE(TAG, "set VFLIP failed");
+ return false;
+ }
+ return true;
+}
+
+/**
+ * @brief 灏嗘憚鍍忓ご鎹曡幏鐨勫浘鍍忓彂閫佸埌杩滅▼鏈嶅姟鍣ㄨ繘琛孉I鍒嗘瀽鍜岃В閲�
+ *
+ * 璇ュ嚱鏁板皢褰撳墠鎽勫儚澶寸紦鍐插尯涓殑鍥惧儚缂栫爜涓篔PEG鏍煎紡锛屽苟閫氳繃HTTP POST璇锋眰
+ * 浠ultipart/form-data鐨勫舰寮忓彂閫佸埌鎸囧畾鐨勮В閲婃湇鍔″櫒銆傛湇鍔″櫒灏嗘牴鎹彁渚涚殑
+ * 闂瀵瑰浘鍍忚繘琛孉I鍒嗘瀽骞惰繑鍥炵粨鏋溿��
+ *
+ * 瀹炵幇鐗圭偣锛�
+ * - 浣跨敤鐙珛绾跨▼缂栫爜JPEG锛屼笌涓荤嚎绋嬪垎绂�
+ * - 閲囩敤鍒嗗潡浼犺緭缂栫爜(chunked transfer encoding)浼樺寲鍐呭瓨浣跨敤
+ * - 閫氳繃闃熷垪鏈哄埗瀹炵幇缂栫爜绾跨▼鍜屽彂閫佺嚎绋嬬殑鏁版嵁鍚屾
+ * - 鏀寔璁惧ID銆佸鎴风ID鍜岃璇佷护鐗岀殑HTTP澶撮儴閰嶇疆
+ *
+ * @param question 瑕佸悜AI鎻愬嚭鐨勫叧浜庡浘鍍忕殑闂锛屽皢浣滀负琛ㄥ崟瀛楁鍙戦��
+ * @return std::string 鏈嶅姟鍣ㄨ繑鍥炵殑JSON鏍煎紡鍝嶅簲瀛楃涓�
+ * 鎴愬姛鏃跺寘鍚獳I鍒嗘瀽缁撴灉锛屽け璐ユ椂鍖呭惈閿欒淇℃伅
+ * 鏍煎紡绀轰緥锛歿"success": true, "result": "鍒嗘瀽缁撴灉"}
+ * {"success": false, "message": "閿欒淇℃伅"}
+ *
+ * @note 璋冪敤姝ゅ嚱鏁板墠蹇呴』鍏堣皟鐢⊿etExplainUrl()璁剧疆鏈嶅姟鍣║RL
+ * @note 鍑芥暟浼氱瓑寰呬箣鍓嶇殑缂栫爜绾跨▼瀹屾垚鍚庡啀寮�濮嬫柊鐨勫鐞�
+ * @warning 濡傛灉鎽勫儚澶寸紦鍐插尯涓虹┖鎴栫綉缁滆繛鎺ュけ璐ワ紝灏嗚繑鍥為敊璇俊鎭�
+ */
+std::string EspVideo::Explain(const std::string& question) {
+ if (explain_url_.empty()) {
+ throw std::runtime_error("Image explain URL or token is not set");
+ }
+
+ // 鍒涘缓灞�閮ㄧ殑 JPEG 闃熷垪, 40 entries is about to store 512 * 40 = 20480 bytes of JPEG data
+ QueueHandle_t jpeg_queue = xQueueCreate(40, sizeof(JpegChunk));
+ if (jpeg_queue == nullptr) {
+ ESP_LOGE(TAG, "Failed to create JPEG queue");
+ throw std::runtime_error("Failed to create JPEG queue");
+ }
+
+ // We spawn a thread to encode the image to JPEG using optimized encoder (cost about 500ms and 8KB SRAM)
+ encoder_thread_ = std::thread([this, jpeg_queue]() {
+ uint16_t w = frame_.width ? frame_.width : 320;
+ uint16_t h = frame_.height ? frame_.height : 240;
+ v4l2_pix_fmt_t enc_fmt = frame_.format;
+ bool ok = image_to_jpeg_cb(
+ frame_.data, frame_.len, w, h, enc_fmt, 80,
+ [](void* arg, size_t index, const void* data, size_t len) -> size_t {
+ auto jpeg_queue = static_cast<QueueHandle_t>(arg);
+ JpegChunk chunk = {.data = nullptr, .len = len};
+ if (index == 0 && data != nullptr && len > 0) {
+ chunk.data = (uint8_t*)heap_caps_aligned_alloc(16, len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (chunk.data == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate %zu bytes for JPEG chunk", len);
+ chunk.len = 0;
+ } else {
+ memcpy(chunk.data, data, len);
+ }
+ } else {
+ chunk.len = 0; // Sentinel or error
+ }
+ xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
+ return len;
+ },
+ jpeg_queue);
+
+ if (!ok) {
+ JpegChunk chunk = {.data = nullptr, .len = 0};
+ xQueueSend(jpeg_queue, &chunk, portMAX_DELAY);
+ }
+ });
+
+ auto network = Board::GetInstance().GetNetwork();
+ auto http = network->CreateHttp(3);
+ // 鏋勯�爉ultipart/form-data璇锋眰浣�
+ std::string boundary = "----ESP32_CAMERA_BOUNDARY";
+
+ // 閰嶇疆HTTP瀹㈡埛绔紝浣跨敤鍒嗗潡浼犺緭缂栫爜
+ http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
+ http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
+ if (!explain_token_.empty()) {
+ http->SetHeader("Authorization", "Bearer " + explain_token_);
+ }
+ http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
+ http->SetHeader("Transfer-Encoding", "chunked");
+ if (!http->Open("POST", explain_url_)) {
+ ESP_LOGE(TAG, "Failed to connect to explain URL");
+ // Clear the queue
+ encoder_thread_.join();
+ JpegChunk chunk;
+ while (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) == pdPASS) {
+ if (chunk.data != nullptr) {
+ heap_caps_free(chunk.data);
+ } else {
+ break;
+ }
+ }
+ vQueueDelete(jpeg_queue);
+ throw std::runtime_error("Failed to connect to explain URL");
+ }
+
+ {
+ // 绗竴鍧楋細question瀛楁
+ std::string question_field;
+ question_field += "--" + boundary + "\r\n";
+ question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
+ question_field += "\r\n";
+ question_field += question + "\r\n";
+ http->Write(question_field.c_str(), question_field.size());
+ }
+ {
+ // 绗簩鍧楋細鏂囦欢瀛楁澶撮儴
+ std::string file_header;
+ file_header += "--" + boundary + "\r\n";
+ file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
+ file_header += "Content-Type: image/jpeg\r\n";
+ file_header += "\r\n";
+ http->Write(file_header.c_str(), file_header.size());
+ }
+
+ // 绗笁鍧楋細JPEG鏁版嵁
+ size_t total_sent = 0;
+ bool saw_terminator = false;
+ while (true) {
+ JpegChunk chunk;
+ if (xQueueReceive(jpeg_queue, &chunk, portMAX_DELAY) != pdPASS) {
+ ESP_LOGE(TAG, "Failed to receive JPEG chunk");
+ break;
+ }
+ if (chunk.data == nullptr) {
+ saw_terminator = true;
+ break; // The last chunk
+ }
+ http->Write((const char*)chunk.data, chunk.len);
+ total_sent += chunk.len;
+ heap_caps_free(chunk.data);
+ }
+ // Wait for the encoder thread to finish
+ encoder_thread_.join();
+ // 娓呯悊闃熷垪
+ vQueueDelete(jpeg_queue);
+
+ if (!saw_terminator || total_sent == 0) {
+ ESP_LOGE(TAG, "JPEG encoder failed or produced empty output");
+ throw std::runtime_error("Failed to encode image to JPEG");
+ }
+
+ {
+ // 绗洓鍧楋細multipart灏鹃儴
+ std::string multipart_footer;
+ multipart_footer += "\r\n--" + boundary + "--\r\n";
+ http->Write(multipart_footer.c_str(), multipart_footer.size());
+ }
+ // 缁撴潫鍧�
+ http->Write("", 0);
+
+ if (http->GetStatusCode() != 200) {
+ ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
+ throw std::runtime_error("Failed to upload photo");
+ }
+
+ std::string result = http->ReadAll();
+ http->Close();
+
+ // Get remain task stack size
+ size_t remain_stack_size = uxTaskGetStackHighWaterMark(nullptr);
+ ESP_LOGI(TAG, "Explain image size=%d bytes, compressed size=%d, remain stack size=%d, question=%s\n%s",
+ (int)frame_.len, (int)total_sent, (int)remain_stack_size, question.c_str(), result.c_str());
+ return result;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/esp_video.h b/xiaozhi-esp32-2.2.6/main/boards/common/esp_video.h
new file mode 100644
index 0000000..063276e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/esp_video.h
@@ -0,0 +1,53 @@
+#pragma once
+#include "sdkconfig.h"
+
+#include <lvgl.h>
+#include <thread>
+#include <memory>
+#include <vector>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/queue.h>
+
+#include "camera.h"
+#include "jpg/image_to_jpeg.h"
+#include "esp_video_init.h"
+
+struct JpegChunk {
+ uint8_t* data;
+ size_t len;
+};
+
+class EspVideo : public Camera {
+private:
+ struct FrameBuffer {
+ uint8_t *data = nullptr;
+ size_t len = 0;
+ uint16_t width = 0;
+ uint16_t height = 0;
+ v4l2_pix_fmt_t format = 0;
+ } frame_;
+ v4l2_pix_fmt_t sensor_format_ = 0;
+#ifdef CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ uint16_t sensor_width_ = 0;
+ uint16_t sensor_height_ = 0;
+#endif // CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE
+ int video_fd_ = -1;
+ bool streaming_on_ = false;
+ struct MmapBuffer { void *start = nullptr; size_t length = 0; };
+ std::vector<MmapBuffer> mmap_buffers_;
+ std::string explain_url_;
+ std::string explain_token_;
+ std::thread encoder_thread_;
+
+public:
+ EspVideo(const esp_video_init_config_t& config);
+ ~EspVideo();
+
+ virtual void SetExplainUrl(const std::string& url, const std::string& token);
+ virtual bool Capture();
+ // 缈昏浆鎺у埗鍑芥暟
+ virtual bool SetHMirror(bool enabled) override;
+ virtual bool SetVFlip(bool enabled) override;
+ virtual std::string Explain(const std::string& question);
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.cc b/xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.cc
new file mode 100644
index 0000000..835997d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.cc
@@ -0,0 +1,35 @@
+#include "i2c_device.h"
+
+#include <esp_log.h>
+
+#define TAG "I2cDevice"
+
+
+I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) {
+ i2c_device_config_t i2c_device_cfg = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = addr,
+ .scl_speed_hz = 400 * 1000,
+ .scl_wait_us = 0,
+ .flags = {
+ .disable_ack_check = 0,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_));
+ assert(i2c_device_ != NULL);
+}
+
+void I2cDevice::WriteReg(uint8_t reg, uint8_t value) {
+ uint8_t buffer[2] = {reg, value};
+ ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100));
+}
+
+uint8_t I2cDevice::ReadReg(uint8_t reg) {
+ uint8_t buffer[1];
+ ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100));
+ return buffer[0];
+}
+
+void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) {
+ ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, length, 100));
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.h b/xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.h
new file mode 100644
index 0000000..7bc917b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/i2c_device.h
@@ -0,0 +1,18 @@
+#ifndef I2C_DEVICE_H
+#define I2C_DEVICE_H
+
+#include <driver/i2c_master.h>
+
+class I2cDevice {
+public:
+ I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
+
+protected:
+ i2c_master_dev_handle_t i2c_device_;
+
+ void WriteReg(uint8_t reg, uint8_t value);
+ uint8_t ReadReg(uint8_t reg);
+ void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length);
+};
+
+#endif // I2C_DEVICE_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/knob.cc b/xiaozhi-esp32-2.2.6/main/boards/common/knob.cc
new file mode 100644
index 0000000..350fda2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/knob.cc
@@ -0,0 +1,52 @@
+#include "knob.h"
+
+static const char* TAG = "Knob";
+
+Knob::Knob(gpio_num_t pin_a, gpio_num_t pin_b) {
+ knob_config_t config = {
+ .default_direction = 0,
+ .gpio_encoder_a = static_cast<uint8_t>(pin_a),
+ .gpio_encoder_b = static_cast<uint8_t>(pin_b),
+ };
+
+ esp_err_t err = ESP_OK;
+ knob_handle_ = iot_knob_create(&config);
+ if (knob_handle_ == NULL) {
+ ESP_LOGE(TAG, "Failed to create knob instance");
+ return;
+ }
+
+ err = iot_knob_register_cb(knob_handle_, KNOB_LEFT, knob_callback, this);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to register left callback: %s", esp_err_to_name(err));
+ return;
+ }
+
+ err = iot_knob_register_cb(knob_handle_, KNOB_RIGHT, knob_callback, this);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to register right callback: %s", esp_err_to_name(err));
+ return;
+ }
+
+ ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", pin_a, pin_b);
+}
+
+Knob::~Knob() {
+ if (knob_handle_ != NULL) {
+ iot_knob_delete(knob_handle_);
+ knob_handle_ = NULL;
+ }
+}
+
+void Knob::OnRotate(std::function<void(bool)> callback) {
+ on_rotate_ = callback;
+}
+
+void Knob::knob_callback(void* arg, void* data) {
+ Knob* knob = static_cast<Knob*>(data);
+ knob_event_t event = iot_knob_get_event(arg);
+
+ if (knob->on_rotate_) {
+ knob->on_rotate_(event == KNOB_RIGHT);
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/knob.h b/xiaozhi-esp32-2.2.6/main/boards/common/knob.h
new file mode 100644
index 0000000..efea5f5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/knob.h
@@ -0,0 +1,25 @@
+#ifndef KNOB_H_
+#define KNOB_H_
+
+#include <driver/gpio.h>
+#include <functional>
+#include <esp_log.h>
+#include <iot_knob.h>
+
+class Knob {
+public:
+ Knob(gpio_num_t pin_a, gpio_num_t pin_b);
+ ~Knob();
+
+ void OnRotate(std::function<void(bool)> callback);
+
+private:
+ static void knob_callback(void* arg, void* data);
+
+ knob_handle_t knob_handle_;
+ gpio_num_t pin_a_;
+ gpio_num_t pin_b_;
+ std::function<void(bool)> on_rotate_;
+};
+
+#endif // KNOB_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/lamp_controller.h b/xiaozhi-esp32-2.2.6/main/boards/common/lamp_controller.h
new file mode 100644
index 0000000..6015447
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/lamp_controller.h
@@ -0,0 +1,48 @@
+#ifndef __LAMP_CONTROLLER_H__
+#define __LAMP_CONTROLLER_H__
+
+#include "mcp_server.h"
+
+
+class LampController {
+private:
+ bool power_ = false;
+ gpio_num_t gpio_num_;
+
+public:
+ LampController(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
+ if (gpio_num_ == GPIO_NUM_NC) {
+ return;
+ }
+
+ gpio_config_t config = {
+ .pin_bit_mask = (1ULL << gpio_num_),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&config));
+ gpio_set_level(gpio_num_, 0);
+
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.lamp.get_state", "Get the power state of the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return power_ ? "{\"power\": true}" : "{\"power\": false}";
+ });
+
+ mcp_server.AddTool("self.lamp.turn_on", "Turn on the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ power_ = true;
+ gpio_set_level(gpio_num_, 1);
+ return true;
+ });
+
+ mcp_server.AddTool("self.lamp.turn_off", "Turn off the lamp", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ power_ = false;
+ gpio_set_level(gpio_num_, 0);
+ return true;
+ });
+ }
+};
+
+
+#endif // __LAMP_CONTROLLER_H__
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.cc b/xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.cc
new file mode 100644
index 0000000..d204bd7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.cc
@@ -0,0 +1,270 @@
+#include "ml307_board.h"
+
+#include "audio_codec.h"
+#include "display.h"
+
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <font_awesome.h>
+#include <utility>
+
+static const char *TAG = "Ml307Board";
+
+// Maximum retry count for modem detection
+static constexpr int MODEM_DETECT_MAX_RETRIES = 30;
+// Maximum retry count for network registration
+static constexpr int NETWORK_REG_MAX_RETRIES = 6;
+
+Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin) : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin) {
+}
+
+std::string Ml307Board::GetBoardType() {
+ return "ml307";
+}
+
+void Ml307Board::SetNetworkEventCallback(NetworkEventCallback callback) {
+ network_event_callback_ = std::move(callback);
+}
+
+void Ml307Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
+ switch (event) {
+ case NetworkEvent::ModemDetecting:
+ ESP_LOGI(TAG, "Detecting modem...");
+ break;
+ case NetworkEvent::Connecting:
+ ESP_LOGI(TAG, "Registering network...");
+ break;
+ case NetworkEvent::Connected:
+ ESP_LOGI(TAG, "Network connected");
+ break;
+ case NetworkEvent::Disconnected:
+ ESP_LOGW(TAG, "Network disconnected");
+ break;
+ case NetworkEvent::ModemErrorNoSim:
+ ESP_LOGE(TAG, "No SIM card detected");
+ break;
+ case NetworkEvent::ModemErrorRegDenied:
+ ESP_LOGE(TAG, "Network registration denied");
+ break;
+ case NetworkEvent::ModemErrorInitFailed:
+ ESP_LOGE(TAG, "Modem initialization failed");
+ break;
+ case NetworkEvent::ModemErrorTimeout:
+ ESP_LOGE(TAG, "Operation timeout");
+ break;
+ default:
+ break;
+ }
+
+ // Notify external callback if set
+ if (network_event_callback_) {
+ network_event_callback_(event, data);
+ }
+}
+
+void Ml307Board::NetworkTask() {
+ // Notify modem detection started
+ OnNetworkEvent(NetworkEvent::ModemDetecting);
+
+ // Try to detect modem with retry limit
+ int detect_retries = 0;
+ while (detect_retries < MODEM_DETECT_MAX_RETRIES) {
+ modem_ = AtModem::Detect(tx_pin_, rx_pin_, dtr_pin_, 921600);
+ if (modem_ != nullptr) {
+ break;
+ }
+ detect_retries++;
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ }
+
+ if (modem_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to detect modem after %d retries", MODEM_DETECT_MAX_RETRIES);
+ OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
+ return;
+ }
+
+ ESP_LOGI(TAG, "Modem detected successfully");
+
+ // Set up network state change callback
+ // Note: Don't call GetCarrierName() here as it sends AT command and will block ReceiveTask
+ modem_->OnNetworkStateChanged([this](bool network_ready) {
+ if (network_ready) {
+ OnNetworkEvent(NetworkEvent::Connected);
+ } else {
+ OnNetworkEvent(NetworkEvent::Disconnected);
+ }
+ });
+
+ // Notify network registration started
+ OnNetworkEvent(NetworkEvent::Connecting);
+
+ // Wait for network ready with retry limit
+ int reg_retries = 0;
+ while (reg_retries < NETWORK_REG_MAX_RETRIES) {
+ auto result = modem_->WaitForNetworkReady();
+ if (result == NetworkStatus::Ready) {
+ break;
+ } else if (result == NetworkStatus::ErrorInsertPin) {
+ OnNetworkEvent(NetworkEvent::ModemErrorNoSim);
+ } else if (result == NetworkStatus::ErrorRegistrationDenied) {
+ OnNetworkEvent(NetworkEvent::ModemErrorRegDenied);
+ } else if (result == NetworkStatus::ErrorTimeout) {
+ OnNetworkEvent(NetworkEvent::ModemErrorTimeout);
+ }
+ reg_retries++;
+ vTaskDelay(pdMS_TO_TICKS(10000));
+ }
+
+ if (!modem_->network_ready()) {
+ ESP_LOGE(TAG, "Failed to register network after %d retries", NETWORK_REG_MAX_RETRIES);
+ return;
+ }
+
+ // Print the ML307 modem information
+ std::string module_revision = modem_->GetModuleRevision();
+ std::string imei = modem_->GetImei();
+ std::string iccid = modem_->GetIccid();
+ ESP_LOGI(TAG, "ML307 Revision: %s", module_revision.c_str());
+ ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str());
+ ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str());
+}
+
+void Ml307Board::StartNetwork() {
+ // Create network initialization task and return immediately
+ xTaskCreate([](void* arg) {
+ Ml307Board* board = static_cast<Ml307Board*>(arg);
+ board->NetworkTask();
+ vTaskDelete(NULL);
+ }, "ml307_net", 4096, this, 5, NULL);
+}
+
+NetworkInterface* Ml307Board::GetNetwork() {
+ return modem_.get();
+}
+
+const char* Ml307Board::GetNetworkStateIcon() {
+ if (modem_ == nullptr || !modem_->network_ready()) {
+ return FONT_AWESOME_SIGNAL_OFF;
+ }
+ int csq = modem_->GetCsq();
+ if (csq == -1) {
+ return FONT_AWESOME_SIGNAL_OFF;
+ } else if (csq >= 0 && csq <= 9) {
+ return FONT_AWESOME_SIGNAL_WEAK;
+ } else if (csq >= 10 && csq <= 14) {
+ return FONT_AWESOME_SIGNAL_FAIR;
+ } else if (csq >= 15 && csq <= 19) {
+ return FONT_AWESOME_SIGNAL_GOOD;
+ } else if (csq >= 20 && csq <= 31) {
+ return FONT_AWESOME_SIGNAL_STRONG;
+ }
+
+ ESP_LOGW(TAG, "Invalid CSQ: %d", csq);
+ return FONT_AWESOME_SIGNAL_OFF;
+}
+
+std::string Ml307Board::GetBoardJson() {
+ // Set the board type for OTA
+ std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");
+ board_json += "\"name\":\"" BOARD_NAME "\",";
+ board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\",";
+ board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\",";
+ board_json += "\"csq\":\"" + std::to_string(modem_->GetCsq()) + "\",";
+ board_json += "\"imei\":\"" + modem_->GetImei() + "\",";
+ board_json += "\"iccid\":\"" + modem_->GetIccid() + "\",";
+ board_json += "\"cereg\":" + modem_->GetRegistrationState().ToString() + "}";
+ return board_json;
+}
+
+void Ml307Board::SetPowerSaveLevel(PowerSaveLevel level) {
+ // TODO: Implement power save level for ML307
+ (void)level;
+}
+
+std::string Ml307Board::GetDeviceStatusJson() {
+ /*
+ * 杩斿洖璁惧鐘舵�丣SON
+ *
+ * 杩斿洖鐨凧SON缁撴瀯濡備笅锛�
+ * {
+ * "audio_speaker": {
+ * "volume": 70
+ * },
+ * "screen": {
+ * "brightness": 100,
+ * "theme": "light"
+ * },
+ * "battery": {
+ * "level": 50,
+ * "charging": true
+ * },
+ * "network": {
+ * "type": "cellular",
+ * "carrier": "CHINA MOBILE",
+ * "csq": 10
+ * }
+ * }
+ */
+ auto& board = Board::GetInstance();
+ auto root = cJSON_CreateObject();
+
+ // Audio speaker
+ auto audio_speaker = cJSON_CreateObject();
+ auto audio_codec = board.GetAudioCodec();
+ if (audio_codec) {
+ cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
+ }
+ cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
+
+ // Screen brightness
+ auto backlight = board.GetBacklight();
+ auto screen = cJSON_CreateObject();
+ if (backlight) {
+ cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
+ }
+ auto display = board.GetDisplay();
+ if (display && display->height() > 64) { // For LCD display only
+ auto theme = display->GetTheme();
+ if (theme != nullptr) {
+ cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
+ }
+ }
+ cJSON_AddItemToObject(root, "screen", screen);
+
+ // Battery
+ int battery_level = 0;
+ bool charging = false;
+ bool discharging = false;
+ if (board.GetBatteryLevel(battery_level, charging, discharging)) {
+ cJSON* battery = cJSON_CreateObject();
+ cJSON_AddNumberToObject(battery, "level", battery_level);
+ cJSON_AddBoolToObject(battery, "charging", charging);
+ cJSON_AddItemToObject(root, "battery", battery);
+ }
+
+ // Network
+ auto network = cJSON_CreateObject();
+ cJSON_AddStringToObject(network, "type", "cellular");
+ cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str());
+ int csq = modem_->GetCsq();
+ if (csq == -1) {
+ cJSON_AddStringToObject(network, "signal", "unknown");
+ } else if (csq >= 0 && csq <= 14) {
+ cJSON_AddStringToObject(network, "signal", "very weak");
+ } else if (csq >= 15 && csq <= 19) {
+ cJSON_AddStringToObject(network, "signal", "weak");
+ } else if (csq >= 20 && csq <= 24) {
+ cJSON_AddStringToObject(network, "signal", "medium");
+ } else if (csq >= 25 && csq <= 31) {
+ cJSON_AddStringToObject(network, "signal", "strong");
+ }
+ cJSON_AddItemToObject(root, "network", network);
+
+ auto json_str = cJSON_PrintUnformatted(root);
+ std::string json(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(root);
+ return json;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.h b/xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.h
new file mode 100644
index 0000000..6e66991
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/ml307_board.h
@@ -0,0 +1,38 @@
+#ifndef ML307_BOARD_H
+#define ML307_BOARD_H
+
+#include <memory>
+#include <at_modem.h>
+#include "board.h"
+
+
+class Ml307Board : public Board {
+protected:
+ std::unique_ptr<AtModem> modem_;
+ gpio_num_t tx_pin_;
+ gpio_num_t rx_pin_;
+ gpio_num_t dtr_pin_;
+ NetworkEventCallback network_event_callback_;
+
+ virtual std::string GetBoardJson() override;
+
+ // Internal helper to trigger network event callback
+ void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
+
+ // Network initialization task (runs in FreeRTOS task)
+ static void NetworkTaskEntry(void* arg);
+ void NetworkTask();
+
+public:
+ Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin = GPIO_NUM_NC);
+ virtual std::string GetBoardType() override;
+ virtual void StartNetwork() override;
+ virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
+ virtual NetworkInterface* GetNetwork() override;
+ virtual const char* GetNetworkStateIcon() override;
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
+ virtual AudioCodec* GetAudioCodec() override { return nullptr; }
+ virtual std::string GetDeviceStatusJson() override;
+};
+
+#endif // ML307_BOARD_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.cc b/xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.cc
new file mode 100644
index 0000000..8ca55e5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.cc
@@ -0,0 +1,269 @@
+#include "nt26_board.h"
+#include "display.h"
+#include "application.h"
+#include "audio_codec.h"
+#include <esp_log.h>
+#include <font_awesome.h>
+#include <cJSON.h>
+
+#define TAG "Nt26Board"
+
+Nt26Board::Nt26Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, gpio_num_t ri_pin, gpio_num_t reset_pin)
+ : tx_pin_(tx_pin), rx_pin_(rx_pin), dtr_pin_(dtr_pin), ri_pin_(ri_pin), reset_pin_(reset_pin) {
+
+ gpio_install_isr_service(ESP_INTR_FLAG_IRAM);
+ esp_event_loop_create_default();
+ esp_netif_init();
+
+ // Create PM lock handle
+ esp_pm_lock_create(ESP_PM_CPU_FREQ_MAX, 0, "nt26_cpu", &pm_lock_cpu_max_);
+
+ // Create network ready timeout timer
+ esp_timer_create_args_t timer_args = {
+ .callback = OnNetworkReadyTimeout,
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "nt26_net_timer",
+ .skip_unhandled_events = true
+ };
+ esp_timer_create(&timer_args, &network_ready_timer_);
+}
+
+Nt26Board::~Nt26Board() {
+ if (current_power_level_ != PowerSaveLevel::LOW_POWER) {
+ SetPowerSaveLevel(PowerSaveLevel::LOW_POWER);
+ }
+
+ if (network_ready_timer_) {
+ esp_timer_stop(network_ready_timer_);
+ esp_timer_delete(network_ready_timer_);
+ }
+
+ if (modem_) {
+ modem_->Stop();
+ }
+
+ if (pm_lock_cpu_max_) {
+ esp_pm_lock_delete(pm_lock_cpu_max_);
+ }
+}
+
+std::string Nt26Board::GetBoardType() {
+ return "nt26";
+}
+
+void Nt26Board::OnNetworkEvent(NetworkEvent event, const std::string& data) {
+ if (network_event_callback_) {
+ network_event_callback_(event, data);
+ }
+}
+
+void Nt26Board::OnNetworkReadyTimeout(void* arg) {
+ auto* self = static_cast<Nt26Board*>(arg);
+ ESP_LOGW(TAG, "Network ready timeout");
+ self->OnNetworkEvent(NetworkEvent::ModemErrorTimeout, "缃戠粶杩炴帴瓒呮椂");
+}
+
+void Nt26Board::StartNetwork() {
+ OnNetworkEvent(NetworkEvent::ModemDetecting);
+
+ UartEthModem::Config config = {
+ .uart_num = UART_NUM_1,
+ .baud_rate = 3000000,
+ .tx_pin = tx_pin_,
+ .rx_pin = rx_pin_,
+ .mrdy_pin = dtr_pin_,
+ .srdy_pin = ri_pin_
+ };
+
+ modem_ = std::make_unique<UartEthModem>(config);
+ modem_->SetDebug(false);
+
+ modem_->SetNetworkEventCallback([this](UartEthModem::UartEthModemEvent event) {
+ switch (event) {
+ case UartEthModem::UartEthModemEvent::Connected:
+ esp_timer_stop(network_ready_timer_);
+ OnNetworkEvent(NetworkEvent::Connected);
+ break;
+ case UartEthModem::UartEthModemEvent::Disconnected:
+ OnNetworkEvent(NetworkEvent::Disconnected);
+ break;
+ case UartEthModem::UartEthModemEvent::ErrorNoSim:
+ esp_timer_stop(network_ready_timer_);
+ ScheduleAsyncStop();
+ OnNetworkEvent(NetworkEvent::ModemErrorNoSim);
+ break;
+ case UartEthModem::UartEthModemEvent::ErrorRegistrationDenied:
+ esp_timer_stop(network_ready_timer_);
+ ScheduleAsyncStop();
+ OnNetworkEvent(NetworkEvent::ModemErrorRegDenied);
+ break;
+ case UartEthModem::UartEthModemEvent::Connecting:
+ OnNetworkEvent(NetworkEvent::Connecting);
+ break;
+ case UartEthModem::UartEthModemEvent::ErrorInitFailed:
+ case UartEthModem::UartEthModemEvent::ErrorNoCarrier:
+ esp_timer_stop(network_ready_timer_);
+ ScheduleAsyncStop();
+ OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
+ break;
+ case UartEthModem::UartEthModemEvent::InFlightMode:
+ ESP_LOGW(TAG, "Modem in flight mode");
+ break;
+ }
+ });
+
+ if (modem_->Start() != ESP_OK) {
+ OnNetworkEvent(NetworkEvent::ModemErrorInitFailed);
+ return;
+ }
+
+ esp_timer_start_once(network_ready_timer_, 30000 * 1000ULL);
+ OnNetworkEvent(NetworkEvent::Connecting);
+}
+
+void Nt26Board::ScheduleAsyncStop() {
+ Application::GetInstance().Schedule([this]() {
+ if (modem_) {
+ modem_->Stop();
+ }
+ });
+}
+
+void Nt26Board::SetNetworkEventCallback(NetworkEventCallback callback) {
+ network_event_callback_ = std::move(callback);
+}
+
+NetworkInterface* Nt26Board::GetNetwork() {
+ static EspNetwork network;
+ return &network;
+}
+
+const char* Nt26Board::GetNetworkStateIcon() {
+ if (modem_ == nullptr || !modem_->IsInitialized()) {
+ return FONT_AWESOME_SIGNAL_OFF;
+ }
+ int csq = modem_->GetSignalStrength();
+ if (csq == 99 || csq == -1) {
+ return FONT_AWESOME_SIGNAL_OFF;
+ } else if (csq >= 0 && csq <= 9) {
+ return FONT_AWESOME_SIGNAL_WEAK;
+ } else if (csq >= 10 && csq <= 14) {
+ return FONT_AWESOME_SIGNAL_FAIR;
+ } else if (csq >= 15 && csq <= 19) {
+ return FONT_AWESOME_SIGNAL_GOOD;
+ } else if (csq >= 20 && csq <= 31) {
+ return FONT_AWESOME_SIGNAL_STRONG;
+ }
+ return FONT_AWESOME_SIGNAL_OFF;
+}
+
+void Nt26Board::SetPowerSaveLevel(PowerSaveLevel level) {
+ if (level == current_power_level_) return;
+
+ if (current_power_level_ == PowerSaveLevel::BALANCED ||
+ current_power_level_ == PowerSaveLevel::PERFORMANCE) {
+ if (pm_lock_cpu_max_) {
+ esp_pm_lock_release(pm_lock_cpu_max_);
+ }
+ }
+
+ if (level == PowerSaveLevel::BALANCED || level == PowerSaveLevel::PERFORMANCE) {
+ if (pm_lock_cpu_max_) {
+ esp_pm_lock_acquire(pm_lock_cpu_max_);
+ }
+ }
+
+ current_power_level_ = level;
+}
+
+std::string Nt26Board::GetBoardJson() {
+ // Set the board type for OTA
+ std::string board_json = std::string("{\"type\":\"" BOARD_TYPE "\",");
+ board_json += "\"name\":\"" BOARD_NAME "\",";
+ if (modem_) {
+ board_json += "\"revision\":\"" + modem_->GetModuleRevision() + "\",";
+ board_json += "\"carrier\":\"" + modem_->GetCarrierName() + "\",";
+ board_json += "\"csq\":\"" + std::to_string(modem_->GetSignalStrength()) + "\",";
+ board_json += "\"imei\":\"" + modem_->GetImei() + "\",";
+ board_json += "\"iccid\":\"" + modem_->GetIccid() + "\",";
+ board_json += "\"cereg\":" + GetRegistrationState().ToString() + "}";
+ } else {
+ board_json += "\"status\":\"offline\"}";
+ }
+ return board_json;
+}
+
+Nt26CeregState Nt26Board::GetRegistrationState() {
+ Nt26CeregState state;
+ if (modem_) {
+ auto cell_info = modem_->GetCellInfo();
+ state.stat = cell_info.stat;
+ state.tac = cell_info.tac;
+ state.ci = cell_info.ci;
+ state.AcT = cell_info.act;
+ }
+ return state;
+}
+
+std::string Nt26Board::GetDeviceStatusJson() {
+ auto& board = Board::GetInstance();
+ auto root = cJSON_CreateObject();
+
+ // Audio speaker
+ auto audio_speaker = cJSON_CreateObject();
+ auto audio_codec = board.GetAudioCodec();
+ if (audio_codec) {
+ cJSON_AddNumberToObject(audio_speaker, "volume", audio_codec->output_volume());
+ }
+ cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
+
+ // Screen
+ auto backlight = board.GetBacklight();
+ auto screen = cJSON_CreateObject();
+ if (backlight) {
+ cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
+ }
+ auto display = board.GetDisplay();
+ if (display && display->height() > 64) {
+ auto theme = display->GetTheme();
+ if (theme != nullptr) {
+ cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
+ }
+ }
+ cJSON_AddItemToObject(root, "screen", screen);
+
+ // Battery
+ int battery_level = 0;
+ bool charging = false, discharging = false;
+ if (board.GetBatteryLevel(battery_level, charging, discharging)) {
+ auto battery = cJSON_CreateObject();
+ cJSON_AddNumberToObject(battery, "level", battery_level);
+ cJSON_AddBoolToObject(battery, "charging", charging);
+ cJSON_AddItemToObject(root, "battery", battery);
+ }
+
+ // Network
+ auto network = cJSON_CreateObject();
+ cJSON_AddStringToObject(network, "type", "cellular");
+ if (modem_) {
+ cJSON_AddStringToObject(network, "carrier", modem_->GetCarrierName().c_str());
+ int csq = modem_->GetSignalStrength();
+ if (csq == 99 || csq == -1) {
+ cJSON_AddStringToObject(network, "signal", "unknown");
+ } else if (csq >= 0 && csq <= 14) {
+ cJSON_AddStringToObject(network, "signal", "weak");
+ } else if (csq >= 15 && csq <= 24) {
+ cJSON_AddStringToObject(network, "signal", "medium");
+ } else if (csq >= 25 && csq <= 31) {
+ cJSON_AddStringToObject(network, "signal", "strong");
+ }
+ }
+ cJSON_AddItemToObject(root, "network", network);
+
+ auto json_str = cJSON_PrintUnformatted(root);
+ std::string json(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(root);
+ return json;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.h b/xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.h
new file mode 100644
index 0000000..f897fe9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/nt26_board.h
@@ -0,0 +1,62 @@
+#ifndef NT26_BOARD_H
+#define NT26_BOARD_H
+
+#include <memory>
+#include <uart_eth_modem.h>
+#include <esp_network.h>
+#include <esp_pm.h>
+#include <esp_timer.h>
+#include "board.h"
+
+struct Nt26CeregState {
+ int stat = 0;
+ std::string tac;
+ std::string ci;
+ int AcT = -1;
+
+ std::string ToString() const {
+ std::string json = "{";
+ json += "\"stat\":" + std::to_string(stat);
+ if (!tac.empty()) json += ",\"tac\":\"" + tac + "\"";
+ if (!ci.empty()) json += ",\"ci\":\"" + ci + "\"";
+ if (AcT >= 0) json += ",\"AcT\":" + std::to_string(AcT);
+ json += "}";
+ return json;
+ }
+};
+
+class Nt26Board : public Board {
+protected:
+ std::unique_ptr<UartEthModem> modem_;
+ gpio_num_t tx_pin_;
+ gpio_num_t rx_pin_;
+ gpio_num_t dtr_pin_; // mrdy_pin
+ gpio_num_t ri_pin_; // srdy_pin
+ gpio_num_t reset_pin_;
+
+ NetworkEventCallback network_event_callback_;
+ esp_pm_lock_handle_t pm_lock_cpu_max_ = nullptr;
+ PowerSaveLevel current_power_level_ = PowerSaveLevel::LOW_POWER;
+ esp_timer_handle_t network_ready_timer_ = nullptr;
+
+ virtual std::string GetBoardJson() override;
+
+ void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
+ static void OnNetworkReadyTimeout(void* arg);
+ void ScheduleAsyncStop();
+
+public:
+ Nt26Board(gpio_num_t tx_pin, gpio_num_t rx_pin, gpio_num_t dtr_pin, gpio_num_t ri_pin, gpio_num_t reset_pin = GPIO_NUM_NC);
+ virtual ~Nt26Board();
+ virtual std::string GetBoardType() override;
+ virtual void StartNetwork() override;
+ virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
+ virtual NetworkInterface* GetNetwork() override;
+ virtual const char* GetNetworkStateIcon() override;
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
+ virtual AudioCodec* GetAudioCodec() override { return nullptr; }
+ virtual std::string GetDeviceStatusJson() override;
+ Nt26CeregState GetRegistrationState();
+};
+
+#endif // NT26_BOARD_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.cc b/xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.cc
new file mode 100644
index 0000000..522e64c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.cc
@@ -0,0 +1,132 @@
+#include "power_save_timer.h"
+#include "application.h"
+#include "settings.h"
+
+#include <esp_log.h>
+
+#define TAG "PowerSaveTimer"
+
+
+PowerSaveTimer::PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep, int seconds_to_shutdown)
+ : cpu_max_freq_(cpu_max_freq), seconds_to_sleep_(seconds_to_sleep), seconds_to_shutdown_(seconds_to_shutdown) {
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ auto self = static_cast<PowerSaveTimer*>(arg);
+ self->PowerSaveCheck();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "power_save_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &power_save_timer_));
+}
+
+PowerSaveTimer::~PowerSaveTimer() {
+ esp_timer_stop(power_save_timer_);
+ esp_timer_delete(power_save_timer_);
+}
+
+void PowerSaveTimer::SetEnabled(bool enabled) {
+ if (enabled && !enabled_) {
+ Settings settings("wifi", false);
+ if (!settings.GetBool("sleep_mode", true)) {
+ ESP_LOGI(TAG, "Power save timer is disabled by settings");
+ return;
+ }
+
+ ticks_ = 0;
+ enabled_ = enabled;
+ ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000));
+ ESP_LOGI(TAG, "Power save timer enabled");
+ } else if (!enabled && enabled_) {
+ ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_));
+ enabled_ = enabled;
+ WakeUp();
+ ESP_LOGI(TAG, "Power save timer disabled");
+ }
+}
+
+void PowerSaveTimer::OnEnterSleepMode(std::function<void()> callback) {
+ on_enter_sleep_mode_ = callback;
+}
+
+void PowerSaveTimer::OnExitSleepMode(std::function<void()> callback) {
+ on_exit_sleep_mode_ = callback;
+}
+
+void PowerSaveTimer::OnShutdownRequest(std::function<void()> callback) {
+ on_shutdown_request_ = callback;
+}
+
+void PowerSaveTimer::PowerSaveCheck() {
+ auto& app = Application::GetInstance();
+ if (!in_sleep_mode_ && !app.CanEnterSleepMode()) {
+ ticks_ = 0;
+ return;
+ }
+
+ ticks_++;
+ if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) {
+ if (!in_sleep_mode_) {
+ ESP_LOGI(TAG, "Enabling power save mode");
+ in_sleep_mode_ = true;
+ if (on_enter_sleep_mode_) {
+ on_enter_sleep_mode_();
+ }
+
+ if (cpu_max_freq_ != -1) {
+ // Disable wake word detection
+ auto& audio_service = app.GetAudioService();
+ is_wake_word_running_ = audio_service.IsWakeWordRunning();
+ if (is_wake_word_running_) {
+ audio_service.EnableWakeWordDetection(false);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ // Disable audio input
+ auto codec = Board::GetInstance().GetAudioCodec();
+ if (codec) {
+ codec->EnableInput(false);
+ }
+
+ esp_pm_config_t pm_config = {
+ .max_freq_mhz = cpu_max_freq_,
+ .min_freq_mhz = 40,
+ .light_sleep_enable = true,
+ };
+ esp_pm_configure(&pm_config);
+ }
+ }
+ }
+ if (seconds_to_shutdown_ != -1 && ticks_ >= seconds_to_shutdown_ && on_shutdown_request_) {
+ on_shutdown_request_();
+ }
+}
+
+void PowerSaveTimer::WakeUp() {
+ ticks_ = 0;
+ if (in_sleep_mode_) {
+ ESP_LOGI(TAG, "Exiting power save mode");
+ in_sleep_mode_ = false;
+
+ if (cpu_max_freq_ != -1) {
+ esp_pm_config_t pm_config = {
+ .max_freq_mhz = cpu_max_freq_,
+ .min_freq_mhz = cpu_max_freq_,
+ .light_sleep_enable = false,
+ };
+ esp_pm_configure(&pm_config);
+
+ // Enable wake word detection
+ auto& app = Application::GetInstance();
+ auto& audio_service = app.GetAudioService();
+ if (is_wake_word_running_) {
+ audio_service.EnableWakeWordDetection(true);
+ }
+ }
+
+ if (on_exit_sleep_mode_) {
+ on_exit_sleep_mode_();
+ }
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.h b/xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.h
new file mode 100644
index 0000000..4c95671
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/power_save_timer.h
@@ -0,0 +1,34 @@
+#pragma once
+
+#include <functional>
+
+#include <esp_timer.h>
+#include <esp_pm.h>
+
+class PowerSaveTimer {
+public:
+ PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep = 20, int seconds_to_shutdown = -1);
+ ~PowerSaveTimer();
+
+ void SetEnabled(bool enabled);
+ void OnEnterSleepMode(std::function<void()> callback);
+ void OnExitSleepMode(std::function<void()> callback);
+ void OnShutdownRequest(std::function<void()> callback);
+ void WakeUp();
+
+private:
+ void PowerSaveCheck();
+
+ esp_timer_handle_t power_save_timer_ = nullptr;
+ bool enabled_ = false;
+ bool in_sleep_mode_ = false;
+ bool is_wake_word_running_ = false;
+ int ticks_ = 0;
+ int cpu_max_freq_;
+ int seconds_to_sleep_;
+ int seconds_to_shutdown_;
+
+ std::function<void()> on_enter_sleep_mode_;
+ std::function<void()> on_exit_sleep_mode_;
+ std::function<void()> on_shutdown_request_;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.cc b/xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.cc
new file mode 100644
index 0000000..d6b0bc9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.cc
@@ -0,0 +1,57 @@
+#include "press_to_talk_mcp_tool.h"
+#include <esp_log.h>
+
+static const char* TAG = "PressToTalkMcpTool";
+
+PressToTalkMcpTool::PressToTalkMcpTool()
+ : press_to_talk_enabled_(false) {
+}
+
+void PressToTalkMcpTool::Initialize() {
+ // 浠庤缃腑璇诲彇褰撳墠鐘舵��
+ Settings settings("vendor");
+ press_to_talk_enabled_ = settings.GetInt("press_to_talk", 0) != 0;
+
+ // 娉ㄥ唽MCP宸ュ叿
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.set_press_to_talk",
+ "Switch between press to talk mode (闀挎寜璇磋瘽) and click to talk mode (鍗曞嚮璇磋瘽).\n"
+ "The mode can be `press_to_talk` or `click_to_talk`.",
+ PropertyList({
+ Property("mode", kPropertyTypeString)
+ }),
+ [this](const PropertyList& properties) -> ReturnValue {
+ return HandleSetPressToTalk(properties);
+ });
+
+ ESP_LOGI(TAG, "PressToTalkMcpTool initialized, current mode: %s",
+ press_to_talk_enabled_ ? "press_to_talk" : "click_to_talk");
+}
+
+bool PressToTalkMcpTool::IsPressToTalkEnabled() const {
+ return press_to_talk_enabled_;
+}
+
+ReturnValue PressToTalkMcpTool::HandleSetPressToTalk(const PropertyList& properties) {
+ auto mode = properties["mode"].value<std::string>();
+
+ if (mode == "press_to_talk") {
+ SetPressToTalkEnabled(true);
+ ESP_LOGI(TAG, "Switched to press to talk mode");
+ return true;
+ } else if (mode == "click_to_talk") {
+ SetPressToTalkEnabled(false);
+ ESP_LOGI(TAG, "Switched to click to talk mode");
+ return true;
+ }
+
+ throw std::runtime_error("Invalid mode: " + mode);
+}
+
+void PressToTalkMcpTool::SetPressToTalkEnabled(bool enabled) {
+ press_to_talk_enabled_ = enabled;
+
+ Settings settings("vendor", true);
+ settings.SetInt("press_to_talk", enabled ? 1 : 0);
+ ESP_LOGI(TAG, "Press to talk enabled: %d", enabled);
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.h b/xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.h
new file mode 100644
index 0000000..3231a28
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/press_to_talk_mcp_tool.h
@@ -0,0 +1,29 @@
+#ifndef PRESS_TO_TALK_MCP_TOOL_H
+#define PRESS_TO_TALK_MCP_TOOL_H
+
+#include "mcp_server.h"
+#include "settings.h"
+
+// 鍙鐢ㄧ殑鎸夐敭璇磋瘽妯″紡MCP宸ュ叿绫�
+class PressToTalkMcpTool {
+private:
+ bool press_to_talk_enabled_;
+
+public:
+ PressToTalkMcpTool();
+
+ // 鍒濆鍖栧伐鍏凤紝娉ㄥ唽鍒癕CP鏈嶅姟鍣�
+ void Initialize();
+
+ // 鑾峰彇褰撳墠鎸夐敭璇磋瘽妯″紡鐘舵��
+ bool IsPressToTalkEnabled() const;
+
+private:
+ // MCP宸ュ叿鐨勫洖璋冨嚱鏁�
+ ReturnValue HandleSetPressToTalk(const PropertyList& properties);
+
+ // 鍐呴儴鏂规硶锛氳缃畃ress to talk鐘舵�佸苟淇濆瓨鍒拌缃�
+ void SetPressToTalkEnabled(bool enabled);
+};
+
+#endif // PRESS_TO_TALK_MCP_TOOL_H
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.cc b/xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.cc
new file mode 100644
index 0000000..c9c5219
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.cc
@@ -0,0 +1,247 @@
+#include "rndis_board.h"
+#include "display.h"
+#include "application.h"
+#include "system_info.h"
+#include "settings.h"
+#include "assets/lang_config.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_network.h>
+#include <esp_log.h>
+#include <utility>
+#include <font_awesome.h>
+
+#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S3
+
+static const char *TAG = "RndisBoard";
+#define EVENT_GOT_IP_BIT (1 << 0)
+
+RndisBoard::RndisBoard() {
+}
+
+RndisBoard::~RndisBoard() {
+}
+
+std::string RndisBoard::GetBoardType() {
+ return "rndis";
+}
+
+void RndisBoard::StartNetwork() {
+ esp_err_t ret = nvs_flash_init();
+ if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+ /* NVS partition was truncated and needs to be erased
+ * Retry nvs_flash_init */
+ ESP_ERROR_CHECK(nvs_flash_erase());
+ ESP_ERROR_CHECK(nvs_flash_init());
+ }
+ /* Initialize default TCP/IP stack */
+ ESP_ERROR_CHECK(esp_netif_init());
+ ESP_ERROR_CHECK(esp_event_loop_create_default());
+
+ s_event_group = xEventGroupCreate();
+ esp_event_handler_register(IOT_ETH_EVENT, ESP_EVENT_ANY_ID, iot_event_handle, this);
+ esp_event_handler_register(IP_EVENT, IP_EVENT_ETH_GOT_IP, iot_event_handle, this);
+
+ // install usbh cdc driver
+ usbh_cdc_driver_config_t config = {
+ .task_stack_size = 1024 * 4,
+ .task_priority = configMAX_PRIORITIES - 1,
+ .task_coreid = 0,
+ .skip_init_usb_host_driver = false,
+ };
+ ESP_ERROR_CHECK(usbh_cdc_driver_install(&config));
+
+ install_rndis(USB_DEVICE_VENDOR_ANY, USB_DEVICE_PRODUCT_ANY, "USB RNDIS0");
+ xEventGroupWaitBits(s_event_group, EVENT_GOT_IP_BIT, pdFALSE, pdFALSE, portMAX_DELAY);
+}
+
+
+void RndisBoard::iot_event_handle(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data)
+{
+ if (event_base == IOT_ETH_EVENT) {
+ switch (event_id) {
+ case IOT_ETH_EVENT_START:
+ ESP_LOGI(TAG, "IOT_ETH_EVENT_START");
+ break;
+ case IOT_ETH_EVENT_STOP:
+ ESP_LOGI(TAG, "IOT_ETH_EVENT_STOP");
+ break;
+ case IOT_ETH_EVENT_CONNECTED:
+ ESP_LOGI(TAG, "IOT_ETH_EVENT_CONNECTED");
+ static_cast<RndisBoard*>(arg)->OnNetworkEvent(NetworkEvent::Connected);
+ break;
+ case IOT_ETH_EVENT_DISCONNECTED:
+ ESP_LOGI(TAG, "IOT_ETH_EVENT_DISCONNECTED");
+ xEventGroupClearBits(static_cast<RndisBoard*>(arg)->s_event_group, EVENT_GOT_IP_BIT);
+ static_cast<RndisBoard*>(arg)->OnNetworkEvent(NetworkEvent::Disconnected);
+ break;
+ default:
+ ESP_LOGI(TAG, "IOT_ETH_EVENT_UNKNOWN");
+ break;
+ }
+ } else if (event_base == IP_EVENT) {
+ ESP_LOGI(TAG, "GOT_IP");
+ xEventGroupSetBits(static_cast<RndisBoard*>(arg)->s_event_group, EVENT_GOT_IP_BIT);
+ }
+}
+
+
+void RndisBoard::OnNetworkEvent(NetworkEvent event, const std::string& data) {
+ switch (event) {
+ case NetworkEvent::Connected:
+ ESP_LOGI(TAG, "Connected to WiFi: %s", data.c_str());
+ break;
+ case NetworkEvent::Scanning:
+ ESP_LOGI(TAG, "WiFi scanning");
+ break;
+ case NetworkEvent::Connecting:
+ ESP_LOGI(TAG, "WiFi connecting to %s", data.c_str());
+ break;
+ case NetworkEvent::Disconnected:
+ ESP_LOGW(TAG, "WiFi disconnected");
+ break;
+ default:
+ break;
+ }
+
+ // Notify external callback if set
+ if (network_event_callback_) {
+ network_event_callback_(event, data);
+ }
+}
+
+void RndisBoard::SetNetworkEventCallback(NetworkEventCallback callback) {
+ network_event_callback_ = std::move(callback);
+}
+
+void RndisBoard::install_rndis(uint16_t idVendor, uint16_t idProduct, const char *netif_name)
+{
+ esp_err_t ret = ESP_OK;
+ iot_eth_handle_t eth_handle = nullptr;
+ iot_eth_netif_glue_handle_t glue = nullptr;
+
+ usb_device_match_id_t *dev_match_id = (usb_device_match_id_t*)calloc(2, sizeof(usb_device_match_id_t));
+ dev_match_id[0].match_flags = USB_DEVICE_ID_MATCH_VID_PID;
+ dev_match_id[0].idVendor = idVendor;
+ dev_match_id[0].idProduct = idProduct;
+ memset(&dev_match_id[1], 0, sizeof(usb_device_match_id_t)); // end of list
+ iot_usbh_rndis_config_t rndis_cfg = {
+ .match_id_list = dev_match_id,
+ };
+
+ ret = iot_eth_new_usb_rndis(&rndis_cfg, &rndis_eth_driver);
+ if (ret != ESP_OK || rndis_eth_driver == NULL) {
+ ESP_LOGE(TAG, "Failed to create USB RNDIS driver");
+ return;
+ }
+
+ iot_eth_config_t eth_cfg = {
+ .driver = rndis_eth_driver,
+ .stack_input = NULL,
+ };
+ ret = iot_eth_install(ð_cfg, ð_handle);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to install USB RNDIS driver");
+ return;
+ }
+
+ esp_netif_inherent_config_t _inherent_eth_config = ESP_NETIF_INHERENT_DEFAULT_ETH();
+ _inherent_eth_config.if_key = netif_name;
+ _inherent_eth_config.if_desc = netif_name;
+ esp_netif_config_t netif_cfg = {
+ .base = &_inherent_eth_config,
+ .driver = NULL,
+ .stack = ESP_NETIF_NETSTACK_DEFAULT_ETH,
+ };
+ s_rndis_netif = esp_netif_new(&netif_cfg);
+ if (s_rndis_netif == NULL) {
+ ESP_LOGE(TAG, "Failed to create network interface");
+ return;
+ }
+
+ glue = iot_eth_new_netif_glue(eth_handle);
+ if (glue == NULL) {
+ ESP_LOGE(TAG, "Failed to create netif glue");
+ return;
+ }
+ esp_netif_attach(s_rndis_netif, glue);
+ iot_eth_start(eth_handle);
+}
+
+
+NetworkInterface* RndisBoard::GetNetwork() {
+ static EspNetwork network;
+ return &network;
+}
+
+const char* RndisBoard::GetNetworkStateIcon() {
+ return FONT_AWESOME_SIGNAL_STRONG;
+}
+
+std::string RndisBoard::GetBoardJson() {
+
+ std::string json = R"({"type":")" + std::string(BOARD_TYPE) + R"(",)";
+ json += R"("name":")" + std::string(BOARD_NAME) + R"(",)";
+
+ json += R"("mac":")" + SystemInfo::GetMacAddress() + R"("})";
+ return json;
+}
+
+void RndisBoard::SetPowerSaveLevel(PowerSaveLevel level) {
+
+}
+
+std::string RndisBoard::GetDeviceStatusJson() {
+ auto& board = Board::GetInstance();
+ auto root = cJSON_CreateObject();
+
+ // Audio speaker
+ auto audio_speaker = cJSON_CreateObject();
+ if (auto codec = board.GetAudioCodec()) {
+ cJSON_AddNumberToObject(audio_speaker, "volume", codec->output_volume());
+ }
+ cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
+
+ // Screen
+ auto screen = cJSON_CreateObject();
+ if (auto backlight = board.GetBacklight()) {
+ cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
+ }
+ if (auto display = board.GetDisplay(); display && display->height() > 64) {
+ if (auto theme = display->GetTheme()) {
+ cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
+ }
+ }
+ cJSON_AddItemToObject(root, "screen", screen);
+
+ // Battery
+ int level = 0;
+ bool charging = false, discharging = false;
+ if (board.GetBatteryLevel(level, charging, discharging)) {
+ auto battery = cJSON_CreateObject();
+ cJSON_AddNumberToObject(battery, "level", level);
+ cJSON_AddBoolToObject(battery, "charging", charging);
+ cJSON_AddItemToObject(root, "battery", battery);
+ }
+
+ // Network
+ auto network = cJSON_CreateObject();
+ cJSON_AddStringToObject(network, "type", "rndis");
+ cJSON_AddItemToObject(root, "network", network);
+
+ // Chip temperature
+ float temp = 0.0f;
+ if (board.GetTemperature(temp)) {
+ auto chip = cJSON_CreateObject();
+ cJSON_AddNumberToObject(chip, "temperature", temp);
+ cJSON_AddItemToObject(root, "chip", chip);
+ }
+
+ auto str = cJSON_PrintUnformatted(root);
+ std::string result(str);
+ cJSON_free(str);
+ cJSON_Delete(root);
+ return result;
+}
+#endif // CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S3
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.h b/xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.h
new file mode 100644
index 0000000..7e9f752
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/rndis_board.h
@@ -0,0 +1,74 @@
+#ifndef RNDIS_BOARD_H
+#define RNDIS_BOARD_H
+
+#include "sdkconfig.h"
+
+#if CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S3
+#include "board.h"
+#include "iot_eth.h"
+#include "iot_usbh_rndis.h"
+#include "iot_eth_netif_glue.h"
+#include <esp_netif.h>
+#include <esp_event.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <esp_timer.h>
+
+class RndisBoard : public Board {
+private:
+ EventGroupHandle_t s_event_group = nullptr;
+ iot_eth_driver_t *rndis_eth_driver = nullptr;
+ esp_netif_t *s_rndis_netif = nullptr;
+
+ void install_rndis(uint16_t idVendor, uint16_t idProduct, const char *netif_name);
+ static void iot_event_handle(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
+protected:
+ NetworkEventCallback network_event_callback_ = nullptr;
+
+ virtual std::string GetBoardJson() override;
+
+ /**
+ * Handle network event (called from WiFi manager callbacks)
+ * @param event The network event type
+ * @param data Additional data (e.g., SSID for Connecting/Connected events)
+ */
+ void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
+
+ /**
+ * Start WiFi connection attempt
+ */
+ void TryWifiConnect();
+
+ /**
+ * Enter WiFi configuration mode
+ */
+ void StartWifiConfigMode();
+
+ /**
+ * WiFi connection timeout callback
+ */
+ static void OnWifiConnectTimeout(void* arg);
+
+public:
+ RndisBoard();
+ virtual ~RndisBoard();
+
+ virtual std::string GetBoardType() override;
+
+ /**
+ * Start network connection asynchronously
+ * This function returns immediately. Network events are notified through the callback set by SetNetworkEventCallback().
+ */
+ virtual void StartNetwork() override;
+
+ virtual NetworkInterface* GetNetwork() override;
+ virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
+ virtual const char* GetNetworkStateIcon() override;
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
+ virtual AudioCodec* GetAudioCodec() override { return nullptr; }
+ virtual std::string GetDeviceStatusJson() override;
+
+};
+#endif // CONFIG_IDF_TARGET_ESP32P4 || CONFIG_IDF_TARGET_ESP32S3
+
+#endif // RNDIS_BOARD_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.cc b/xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.cc
new file mode 100644
index 0000000..3490f6c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.cc
@@ -0,0 +1,133 @@
+#include "sleep_timer.h"
+#include "application.h"
+#include "board.h"
+#include "display.h"
+#include "settings.h"
+
+#include <esp_log.h>
+#include <esp_sleep.h>
+#include <esp_lvgl_port.h>
+
+#define TAG "SleepTimer"
+
+
+SleepTimer::SleepTimer(int seconds_to_light_sleep, int seconds_to_deep_sleep)
+ : seconds_to_light_sleep_(seconds_to_light_sleep), seconds_to_deep_sleep_(seconds_to_deep_sleep) {
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ auto self = static_cast<SleepTimer*>(arg);
+ self->CheckTimer();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "sleep_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &sleep_timer_));
+}
+
+SleepTimer::~SleepTimer() {
+ esp_timer_stop(sleep_timer_);
+ esp_timer_delete(sleep_timer_);
+}
+
+void SleepTimer::SetEnabled(bool enabled) {
+ if (enabled && !enabled_) {
+ Settings settings("wifi", false);
+ if (!settings.GetBool("sleep_mode", true)) {
+ ESP_LOGI(TAG, "Power save timer is disabled by settings");
+ return;
+ }
+
+ ticks_ = 0;
+ enabled_ = enabled;
+ ESP_ERROR_CHECK(esp_timer_start_periodic(sleep_timer_, 1000000));
+ ESP_LOGI(TAG, "Sleep timer enabled");
+ } else if (!enabled && enabled_) {
+ ESP_ERROR_CHECK(esp_timer_stop(sleep_timer_));
+ enabled_ = enabled;
+ WakeUp();
+ ESP_LOGI(TAG, "Sleep timer disabled");
+ }
+}
+
+void SleepTimer::OnEnterLightSleepMode(std::function<void()> callback) {
+ on_enter_light_sleep_mode_ = callback;
+}
+
+void SleepTimer::OnExitLightSleepMode(std::function<void()> callback) {
+ on_exit_light_sleep_mode_ = callback;
+}
+
+void SleepTimer::OnEnterDeepSleepMode(std::function<void()> callback) {
+ on_enter_deep_sleep_mode_ = callback;
+}
+
+void SleepTimer::CheckTimer() {
+ auto& app = Application::GetInstance();
+ if (!app.CanEnterSleepMode()) {
+ ticks_ = 0;
+ return;
+ }
+
+ ticks_++;
+ if (seconds_to_light_sleep_ != -1 && ticks_ >= seconds_to_light_sleep_) {
+ if (!in_light_sleep_mode_) {
+ in_light_sleep_mode_ = true;
+ if (on_enter_light_sleep_mode_) {
+ on_enter_light_sleep_mode_();
+ }
+
+ auto& audio_service = app.GetAudioService();
+ bool is_wake_word_running = audio_service.IsWakeWordRunning();
+ if (is_wake_word_running) {
+ audio_service.EnableWakeWordDetection(false);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+
+ app.Schedule([this, &app]() {
+ while (in_light_sleep_mode_) {
+ auto& board = Board::GetInstance();
+ board.GetDisplay()->UpdateStatusBar(true);
+ lv_refr_now(nullptr);
+ lvgl_port_stop();
+
+ // 閰嶇疆timer鍞ら啋婧愶紙30绉掑悗鑷姩鍞ら啋锛�
+ esp_sleep_enable_timer_wakeup(30 * 1000000);
+
+ // 杩涘叆light sleep妯″紡
+ esp_light_sleep_start();
+ lvgl_port_resume();
+
+ auto wakeup_reason = esp_sleep_get_wakeup_cause();
+ ESP_LOGI(TAG, "Wake up from light sleep, wakeup_reason: %d", wakeup_reason);
+ if (wakeup_reason != ESP_SLEEP_WAKEUP_TIMER) {
+ break;
+ }
+ }
+ WakeUp();
+ });
+
+ if (is_wake_word_running) {
+ audio_service.EnableWakeWordDetection(true);
+ }
+ }
+ }
+ if (seconds_to_deep_sleep_ != -1 && ticks_ >= seconds_to_deep_sleep_) {
+ if (on_enter_deep_sleep_mode_) {
+ on_enter_deep_sleep_mode_();
+ }
+
+ esp_deep_sleep_start();
+ }
+}
+
+void SleepTimer::WakeUp() {
+ ticks_ = 0;
+ if (in_light_sleep_mode_) {
+ in_light_sleep_mode_ = false;
+ if (on_exit_light_sleep_mode_) {
+ on_exit_light_sleep_mode_();
+ }
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.h b/xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.h
new file mode 100644
index 0000000..159e220
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/sleep_timer.h
@@ -0,0 +1,32 @@
+#pragma once
+
+#include <functional>
+
+#include <esp_timer.h>
+#include <esp_pm.h>
+
+class SleepTimer {
+public:
+ SleepTimer(int seconds_to_light_sleep = 20, int seconds_to_deep_sleep = -1);
+ ~SleepTimer();
+
+ void SetEnabled(bool enabled);
+ void OnEnterLightSleepMode(std::function<void()> callback);
+ void OnExitLightSleepMode(std::function<void()> callback);
+ void OnEnterDeepSleepMode(std::function<void()> callback);
+ void WakeUp();
+
+private:
+ void CheckTimer();
+
+ esp_timer_handle_t sleep_timer_ = nullptr;
+ bool enabled_ = false;
+ int ticks_ = 0;
+ int seconds_to_light_sleep_;
+ int seconds_to_deep_sleep_;
+ bool in_light_sleep_mode_ = false;
+
+ std::function<void()> on_enter_light_sleep_mode_;
+ std::function<void()> on_exit_light_sleep_mode_;
+ std::function<void()> on_enter_deep_sleep_mode_;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/sy6970.cc b/xiaozhi-esp32-2.2.6/main/boards/common/sy6970.cc
new file mode 100644
index 0000000..8a45d75
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/sy6970.cc
@@ -0,0 +1,65 @@
+#include "sy6970.h"
+#include "board.h"
+#include "display.h"
+
+#include <esp_log.h>
+
+#define TAG "Sy6970"
+
+Sy6970::Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+}
+
+int Sy6970::GetChangingStatus() {
+ return (ReadReg(0x0B) >> 3) & 0x03;
+}
+
+bool Sy6970::IsCharging() {
+ return GetChangingStatus() != 0;
+}
+
+bool Sy6970::IsPowerGood() {
+ return (ReadReg(0x0B) & 0x04) != 0;
+}
+
+bool Sy6970::IsChargingDone() {
+ return GetChangingStatus() == 3;
+}
+
+int Sy6970::GetBatteryVoltage() {
+ uint8_t value = ReadReg(0x0E);
+ value &= 0x7F;
+ if (value == 0) {
+ return 0;
+ }
+ return value * 20 + 2304;
+}
+
+int Sy6970::GetChargeTargetVoltage() {
+ uint8_t value = ReadReg(0x06);
+ value = (value & 0xFC) >> 2;
+ if (value > 0x30) {
+ return 4608;
+ }
+ return value * 16 + 3840;
+}
+
+int Sy6970::GetBatteryLevel() {
+ int level = 0;
+ // 鐢垫睜鎵�鑳芥帀鐢电殑鏈�浣庣數鍘�
+ int battery_minimum_voltage = 3200;
+ int battery_voltage = GetBatteryVoltage();
+ int charge_voltage_limit = GetChargeTargetVoltage();
+ // ESP_LOGI(TAG, "battery_voltage: %d, charge_voltage_limit: %d", battery_voltage, charge_voltage_limit);
+ if (battery_voltage > battery_minimum_voltage && charge_voltage_limit > battery_minimum_voltage) {
+ level = (((float) battery_voltage - (float) battery_minimum_voltage) / ((float) charge_voltage_limit - (float) battery_minimum_voltage)) * 100.0;
+ }
+ // 涓嶈繛鎺ョ數姹犳椂璇诲彇鐨勫厖鐢电姸鎬佷笉绋冲畾涓攂attery_voltage鏈夋椂浼氳秴杩嘽harge_voltage_limit
+ if (level > 100) {
+ level = 100;
+ }
+ return level;
+}
+
+void Sy6970::PowerOff() {
+ WriteReg(0x09, 0B01100100);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/sy6970.h b/xiaozhi-esp32-2.2.6/main/boards/common/sy6970.h
new file mode 100644
index 0000000..a2eaaca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/sy6970.h
@@ -0,0 +1,21 @@
+#ifndef __SY6970_H__
+#define __SY6970_H__
+
+#include "i2c_device.h"
+
+class Sy6970 : public I2cDevice {
+public:
+ Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
+ bool IsCharging();
+ bool IsPowerGood();
+ bool IsChargingDone();
+ int GetBatteryLevel();
+ void PowerOff();
+
+private:
+ int GetChangingStatus();
+ int GetBatteryVoltage();
+ int GetChargeTargetVoltage();
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/system_reset.cc b/xiaozhi-esp32-2.2.6/main/boards/common/system_reset.cc
new file mode 100644
index 0000000..f51249b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/system_reset.cc
@@ -0,0 +1,72 @@
+#include "system_reset.h"
+
+#include <esp_log.h>
+#include <nvs_flash.h>
+#include <driver/gpio.h>
+#include <esp_partition.h>
+#include <esp_system.h>
+#include <freertos/FreeRTOS.h>
+
+
+#define TAG "SystemReset"
+
+
+SystemReset::SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin) : reset_nvs_pin_(reset_nvs_pin), reset_factory_pin_(reset_factory_pin) {
+ // Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed
+ gpio_config_t io_conf;
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+}
+
+
+void SystemReset::CheckButtons() {
+ if (gpio_get_level(reset_factory_pin_) == 0) {
+ ESP_LOGI(TAG, "Button is pressed, reset to factory");
+ ResetNvsFlash();
+ ResetToFactory();
+ }
+
+ if (gpio_get_level(reset_nvs_pin_) == 0) {
+ ESP_LOGI(TAG, "Button is pressed, reset NVS flash");
+ ResetNvsFlash();
+ }
+}
+
+void SystemReset::ResetNvsFlash() {
+ ESP_LOGI(TAG, "Resetting NVS flash");
+ esp_err_t ret = nvs_flash_erase();
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to erase NVS flash");
+ }
+ ret = nvs_flash_init();
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize NVS flash");
+ }
+}
+
+void SystemReset::ResetToFactory() {
+ ESP_LOGI(TAG, "Resetting to factory");
+ // Erase otadata partition
+ const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL);
+ if (partition == NULL) {
+ ESP_LOGE(TAG, "Failed to find otadata partition");
+ return;
+ }
+ esp_partition_erase_range(partition, 0, partition->size);
+ ESP_LOGI(TAG, "Erased otadata partition");
+
+ // Reboot in 3 seconds
+ RestartInSeconds(3);
+}
+
+void SystemReset::RestartInSeconds(int seconds) {
+ for (int i = seconds; i > 0; i--) {
+ ESP_LOGI(TAG, "Resetting in %d seconds", i);
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ esp_restart();
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/system_reset.h b/xiaozhi-esp32-2.2.6/main/boards/common/system_reset.h
new file mode 100644
index 0000000..7e78296
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/system_reset.h
@@ -0,0 +1,21 @@
+#ifndef _SYSTEM_RESET_H
+#define _SYSTEM_RESET_H
+
+#include <driver/gpio.h>
+
+class SystemReset {
+public:
+ SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 鏋勯�犲嚱鏁扮鏈夊寲
+ void CheckButtons();
+
+private:
+ gpio_num_t reset_nvs_pin_;
+ gpio_num_t reset_factory_pin_;
+
+ void ResetNvsFlash();
+ void ResetToFactory();
+ void RestartInSeconds(int seconds);
+};
+
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.cc b/xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.cc
new file mode 100644
index 0000000..377685b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.cc
@@ -0,0 +1,357 @@
+#include "wifi_board.h"
+
+#include "display.h"
+#include "application.h"
+#include "system_info.h"
+#include "settings.h"
+#include "assets/lang_config.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_network.h>
+#include <esp_log.h>
+#include <utility>
+
+#include <font_awesome.h>
+#include <wifi_manager.h>
+#include <wifi_station.h>
+#include <ssid_manager.h>
+#include "afsk_demod.h"
+#ifdef CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
+#include "blufi.h"
+#endif
+
+static const char *TAG = "WifiBoard";
+
+// Connection timeout in seconds
+static constexpr int CONNECT_TIMEOUT_SEC = 60;
+
+WifiBoard::WifiBoard() {
+ // Create connection timeout timer
+ esp_timer_create_args_t timer_args = {
+ .callback = OnWifiConnectTimeout,
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "wifi_connect_timer",
+ .skip_unhandled_events = true
+ };
+ esp_timer_create(&timer_args, &connect_timer_);
+}
+
+WifiBoard::~WifiBoard() {
+ if (connect_timer_) {
+ esp_timer_stop(connect_timer_);
+ esp_timer_delete(connect_timer_);
+ }
+}
+
+std::string WifiBoard::GetBoardType() {
+ return "wifi";
+}
+
+void WifiBoard::StartNetwork() {
+ auto& wifi_manager = WifiManager::GetInstance();
+
+ // Initialize WiFi manager
+ WifiManagerConfig config;
+ config.ssid_prefix = "Xiaozhi";
+ config.language = Lang::CODE;
+ wifi_manager.Initialize(config);
+
+ // Set unified event callback - forward to NetworkEvent with SSID data
+ wifi_manager.SetEventCallback([this](WifiEvent event, const std::string& data) {
+ switch (event) {
+ case WifiEvent::Scanning:
+ OnNetworkEvent(NetworkEvent::Scanning);
+ break;
+ case WifiEvent::Connecting:
+ OnNetworkEvent(NetworkEvent::Connecting, data);
+ break;
+ case WifiEvent::Connected:
+ OnNetworkEvent(NetworkEvent::Connected, data);
+ break;
+ case WifiEvent::Disconnected:
+ OnNetworkEvent(NetworkEvent::Disconnected);
+ break;
+ case WifiEvent::ConfigModeEnter:
+ OnNetworkEvent(NetworkEvent::WifiConfigModeEnter);
+ break;
+ case WifiEvent::ConfigModeExit:
+ OnNetworkEvent(NetworkEvent::WifiConfigModeExit);
+ break;
+ }
+ });
+
+ // Try to connect or enter config mode
+ TryWifiConnect();
+}
+
+void WifiBoard::TryWifiConnect() {
+ auto& ssid_manager = SsidManager::GetInstance();
+ bool have_ssid = !ssid_manager.GetSsidList().empty();
+
+ if (have_ssid) {
+ // Start connection attempt with timeout
+ ESP_LOGI(TAG, "Starting WiFi connection attempt");
+ esp_timer_start_once(connect_timer_, CONNECT_TIMEOUT_SEC * 1000000ULL);
+ WifiManager::GetInstance().StartStation();
+ } else {
+ // No SSID configured, enter config mode
+ // Wait for the board version to be shown
+ vTaskDelay(pdMS_TO_TICKS(1500));
+ StartWifiConfigMode();
+ }
+}
+
+void WifiBoard::OnNetworkEvent(NetworkEvent event, const std::string& data) {
+ switch (event) {
+ case NetworkEvent::Connected:
+ // Stop timeout timer
+ esp_timer_stop(connect_timer_);
+#ifdef CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
+ // make sure blufi resources has been released
+ Blufi::GetInstance().deinit();
+#endif
+ in_config_mode_ = false;
+ ESP_LOGI(TAG, "Connected to WiFi: %s", data.c_str());
+ break;
+ case NetworkEvent::Scanning:
+ ESP_LOGI(TAG, "WiFi scanning");
+ break;
+ case NetworkEvent::Connecting:
+ ESP_LOGI(TAG, "WiFi connecting to %s", data.c_str());
+ break;
+ case NetworkEvent::Disconnected:
+ ESP_LOGW(TAG, "WiFi disconnected");
+ break;
+ case NetworkEvent::WifiConfigModeEnter:
+ ESP_LOGI(TAG, "WiFi config mode entered");
+ in_config_mode_ = true;
+ break;
+ case NetworkEvent::WifiConfigModeExit:
+ ESP_LOGI(TAG, "WiFi config mode exited");
+ in_config_mode_ = false;
+ // Try to connect with the new credentials
+ TryWifiConnect();
+ break;
+ default:
+ break;
+ }
+
+ // Notify external callback if set
+ if (network_event_callback_) {
+ network_event_callback_(event, data);
+ }
+}
+
+void WifiBoard::SetNetworkEventCallback(NetworkEventCallback callback) {
+ network_event_callback_ = std::move(callback);
+}
+
+void WifiBoard::OnWifiConnectTimeout(void* arg) {
+ auto* board = static_cast<WifiBoard*>(arg);
+ ESP_LOGW(TAG, "WiFi connection timeout, entering config mode");
+
+ WifiManager::GetInstance().StopStation();
+ board->StartWifiConfigMode();
+}
+
+void WifiBoard::StartWifiConfigMode() {
+ in_config_mode_ = true;
+ // Transition to wifi configuring state
+ Application::GetInstance().SetDeviceState(kDeviceStateWifiConfiguring);
+#ifdef CONFIG_USE_HOTSPOT_WIFI_PROVISIONING
+ auto& wifi_manager = WifiManager::GetInstance();
+
+ wifi_manager.StartConfigAp();
+
+ // Show config prompt after a short delay
+ Application::GetInstance().Schedule([&wifi_manager]() {
+ std::string hint = Lang::Strings::CONNECT_TO_HOTSPOT;
+ hint += wifi_manager.GetApSsid();
+ hint += Lang::Strings::ACCESS_VIA_BROWSER;
+ hint += wifi_manager.GetApWebUrl();
+
+ Application::GetInstance().Alert(Lang::Strings::WIFI_CONFIG_MODE, hint.c_str(), "gear", Lang::Sounds::OGG_WIFICONFIG);
+ });
+#elif CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING
+ auto &blufi = Blufi::GetInstance();
+ // initialize esp-blufi protocol
+ blufi.init();
+#endif
+#if CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING
+ // Start acoustic provisioning task
+ auto codec = Board::GetInstance().GetAudioCodec();
+ int channel = codec ? codec->input_channels() : 1;
+ ESP_LOGI(TAG, "Starting acoustic WiFi provisioning, channels: %d", channel);
+
+ xTaskCreate([](void* arg) {
+ auto ch = reinterpret_cast<intptr_t>(arg);
+ auto& app = Application::GetInstance();
+ auto& wifi = WifiManager::GetInstance();
+ auto disp = Board::GetInstance().GetDisplay();
+ audio_wifi_config::ReceiveWifiCredentialsFromAudio(&app, &wifi, disp, ch);
+ vTaskDelete(NULL);
+ }, "acoustic_wifi", 4096, reinterpret_cast<void*>(channel), 2, NULL);
+#endif
+}
+
+void WifiBoard::EnterWifiConfigMode() {
+ ESP_LOGI(TAG, "EnterWifiConfigMode called");
+ GetDisplay()->ShowNotification(Lang::Strings::ENTERING_WIFI_CONFIG_MODE);
+
+ auto& app = Application::GetInstance();
+ auto state = app.GetDeviceState();
+
+ if (state == kDeviceStateSpeaking || state == kDeviceStateListening || state == kDeviceStateIdle) {
+ // Reset protocol (close audio channel, reset protocol)
+ Application::GetInstance().ResetProtocol();
+
+ xTaskCreate([](void* arg) {
+ auto* board = static_cast<WifiBoard*>(arg);
+
+ // Wait for 1 second to allow speaking to finish gracefully
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ // Stop any ongoing connection attempt
+ esp_timer_stop(board->connect_timer_);
+ WifiManager::GetInstance().StopStation();
+
+ // Enter config mode
+ board->StartWifiConfigMode();
+
+ vTaskDelete(NULL);
+ }, "wifi_cfg_delay", 4096, this, 2, NULL);
+ return;
+ }
+
+ if (state != kDeviceStateStarting) {
+ ESP_LOGE(TAG, "EnterWifiConfigMode called but device state is not starting or speaking, device state: %d", state);
+ return;
+ }
+
+ // Stop any ongoing connection attempt
+ esp_timer_stop(connect_timer_);
+ WifiManager::GetInstance().StopStation();
+
+ StartWifiConfigMode();
+}
+
+bool WifiBoard::IsInWifiConfigMode() const {
+ return WifiManager::GetInstance().IsConfigMode();
+}
+
+NetworkInterface* WifiBoard::GetNetwork() {
+ static EspNetwork network;
+ return &network;
+}
+
+const char* WifiBoard::GetNetworkStateIcon() {
+ auto& wifi = WifiManager::GetInstance();
+
+ if (wifi.IsConfigMode()) {
+ return FONT_AWESOME_WIFI;
+ }
+ if (!wifi.IsConnected()) {
+ return FONT_AWESOME_WIFI_SLASH;
+ }
+
+ int rssi = wifi.GetRssi();
+ if (rssi >= -65) {
+ return FONT_AWESOME_WIFI;
+ } else if (rssi >= -75) {
+ return FONT_AWESOME_WIFI_FAIR;
+ }
+ return FONT_AWESOME_WIFI_WEAK;
+}
+
+std::string WifiBoard::GetBoardJson() {
+ auto& wifi = WifiManager::GetInstance();
+ std::string json = R"({"type":")" + std::string(BOARD_TYPE) + R"(",)";
+ json += R"("name":")" + std::string(BOARD_NAME) + R"(",)";
+
+ if (!wifi.IsConfigMode()) {
+ json += R"("ssid":")" + wifi.GetSsid() + R"(",)";
+ json += R"("rssi":)" + std::to_string(wifi.GetRssi()) + R"(,)";
+ json += R"("channel":)" + std::to_string(wifi.GetChannel()) + R"(,)";
+ json += R"("ip":")" + wifi.GetIpAddress() + R"(",)";
+ }
+
+ json += R"("mac":")" + SystemInfo::GetMacAddress() + R"("})";
+ return json;
+}
+
+void WifiBoard::SetPowerSaveLevel(PowerSaveLevel level) {
+ WifiPowerSaveLevel wifi_level;
+ switch (level) {
+ case PowerSaveLevel::LOW_POWER:
+ wifi_level = WifiPowerSaveLevel::LOW_POWER;
+ break;
+ case PowerSaveLevel::BALANCED:
+ wifi_level = WifiPowerSaveLevel::BALANCED;
+ break;
+ case PowerSaveLevel::PERFORMANCE:
+ default:
+ wifi_level = WifiPowerSaveLevel::PERFORMANCE;
+ break;
+ }
+ WifiManager::GetInstance().SetPowerSaveLevel(wifi_level);
+}
+
+std::string WifiBoard::GetDeviceStatusJson() {
+ auto& board = Board::GetInstance();
+ auto root = cJSON_CreateObject();
+
+ // Audio speaker
+ auto audio_speaker = cJSON_CreateObject();
+ if (auto codec = board.GetAudioCodec()) {
+ cJSON_AddNumberToObject(audio_speaker, "volume", codec->output_volume());
+ }
+ cJSON_AddItemToObject(root, "audio_speaker", audio_speaker);
+
+ // Screen
+ auto screen = cJSON_CreateObject();
+ if (auto backlight = board.GetBacklight()) {
+ cJSON_AddNumberToObject(screen, "brightness", backlight->brightness());
+ }
+ if (auto display = board.GetDisplay(); display && display->height() > 64) {
+ if (auto theme = display->GetTheme()) {
+ cJSON_AddStringToObject(screen, "theme", theme->name().c_str());
+ }
+ }
+ cJSON_AddItemToObject(root, "screen", screen);
+
+ // Battery
+ int level = 0;
+ bool charging = false, discharging = false;
+ if (board.GetBatteryLevel(level, charging, discharging)) {
+ auto battery = cJSON_CreateObject();
+ cJSON_AddNumberToObject(battery, "level", level);
+ cJSON_AddBoolToObject(battery, "charging", charging);
+ cJSON_AddItemToObject(root, "battery", battery);
+ }
+
+ // Network
+ auto& wifi = WifiManager::GetInstance();
+ auto network = cJSON_CreateObject();
+ cJSON_AddStringToObject(network, "type", "wifi");
+ cJSON_AddStringToObject(network, "ssid", wifi.GetSsid().c_str());
+ int rssi = wifi.GetRssi();
+ const char* signal = rssi >= -60 ? "strong" : (rssi >= -70 ? "medium" : "weak");
+ cJSON_AddStringToObject(network, "signal", signal);
+ cJSON_AddItemToObject(root, "network", network);
+
+ // Chip temperature
+ float temp = 0.0f;
+ if (board.GetTemperature(temp)) {
+ auto chip = cJSON_CreateObject();
+ cJSON_AddNumberToObject(chip, "temperature", temp);
+ cJSON_AddItemToObject(root, "chip", chip);
+ }
+
+ auto str = cJSON_PrintUnformatted(root);
+ std::string result(str);
+ cJSON_free(str);
+ cJSON_Delete(root);
+ return result;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.h b/xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.h
new file mode 100644
index 0000000..7ee973c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/common/wifi_board.h
@@ -0,0 +1,69 @@
+#ifndef WIFI_BOARD_H
+#define WIFI_BOARD_H
+
+#include "board.h"
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <esp_timer.h>
+
+class WifiBoard : public Board {
+protected:
+ esp_timer_handle_t connect_timer_ = nullptr;
+ bool in_config_mode_ = false;
+ NetworkEventCallback network_event_callback_ = nullptr;
+
+ virtual std::string GetBoardJson() override;
+
+ /**
+ * Handle network event (called from WiFi manager callbacks)
+ * @param event The network event type
+ * @param data Additional data (e.g., SSID for Connecting/Connected events)
+ */
+ void OnNetworkEvent(NetworkEvent event, const std::string& data = "");
+
+ /**
+ * Start WiFi connection attempt
+ */
+ void TryWifiConnect();
+
+ /**
+ * Enter WiFi configuration mode
+ */
+ void StartWifiConfigMode();
+
+ /**
+ * WiFi connection timeout callback
+ */
+ static void OnWifiConnectTimeout(void* arg);
+
+public:
+ WifiBoard();
+ virtual ~WifiBoard();
+
+ virtual std::string GetBoardType() override;
+
+ /**
+ * Start network connection asynchronously
+ * This function returns immediately. Network events are notified through the callback set by SetNetworkEventCallback().
+ */
+ virtual void StartNetwork() override;
+
+ virtual NetworkInterface* GetNetwork() override;
+ virtual void SetNetworkEventCallback(NetworkEventCallback callback) override;
+ virtual const char* GetNetworkStateIcon() override;
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override;
+ virtual AudioCodec* GetAudioCodec() override { return nullptr; }
+ virtual std::string GetDeviceStatusJson() override;
+
+ /**
+ * Enter WiFi configuration mode (thread-safe, can be called from any task)
+ */
+ void EnterWifiConfigMode();
+
+ /**
+ * Check if in WiFi config mode
+ */
+ bool IsInWifiConfigMode() const;
+};
+
+#endif // WIFI_BOARD_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/README.md b/xiaozhi-esp32-2.2.6/main/boards/df-k10/README.md
new file mode 100644
index 0000000..d5d6c80
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/README.md
@@ -0,0 +1,57 @@
+# DFRobot 琛岀┖鏉� K10
+
+## 鎸夐敭閰嶇疆
+* A锛氱煭鎸�-鎵撴柇/鍞ら啋锛岄暱鎸�1s-闊抽噺璋冨ぇ
+* B锛氱煭鎸�-鎵撴柇/鍞ら啋锛岄暱鎸�1s-闊抽噺璋冨皬
+
+## 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> DFRobot 琛岀┖鏉� K10
+```
+
+**淇敼 psram 閰嶇疆锛�**
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM
+```
+
+**浣胯兘鎽勫儚澶寸紦鍐插尯澶у皬绔氦鎹細**
+
+```
+Xiaozhi Assistant -> Camera Configuration -> Enable software camera buffer endianness swapping
+```
+
+**閰嶇疆鎽勫儚澶达細**
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> GC2145 -> Auto detect GC2145
+
+```
+
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> GC2145 -> Select default output format for DVP interface (RGB565 800x600 20fps, DVP 8-bit, 20M input) -> RGB565 800x600 20fps, DVP 8-bit, 20M input
+
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
+
+
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/config.h b/xiaozhi-esp32-2.2.6/main/boards/df-k10/config.h
new file mode 100644
index 0000000..8570563
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/config.h
@@ -0,0 +1,76 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_38
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_0
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR 0x23
+
+#define BUILTIN_LED_GPIO GPIO_NUM_46
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+/* Expander */
+#define DRV_IO_EXP_INPUT_MASK (IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_12)
+
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+/* DFRobot K10 Camera pins */
+#define PWDN_GPIO_NUM GPIO_NUM_NC
+#define RESET_GPIO_NUM GPIO_NUM_NC
+#define XCLK_GPIO_NUM GPIO_NUM_7
+
+#define VSYNC_GPIO_NUM GPIO_NUM_4
+#define HREF_GPIO_NUM GPIO_NUM_5
+#define PCLK_GPIO_NUM GPIO_NUM_17
+#define SIOD_GPIO_NUM GPIO_NUM_20
+#define SIOC_GPIO_NUM GPIO_NUM_19
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN PWDN_GPIO_NUM
+#define CAMERA_PIN_RESET RESET_GPIO_NUM
+#define CAMERA_PIN_XCLK XCLK_GPIO_NUM
+#define CAMERA_PIN_SIOD SIOD_GPIO_NUM
+#define CAMERA_PIN_SIOC SIOC_GPIO_NUM
+
+#define CAMERA_PIN_D9 GPIO_NUM_6
+#define CAMERA_PIN_D8 GPIO_NUM_15
+#define CAMERA_PIN_D7 GPIO_NUM_16
+#define CAMERA_PIN_D6 GPIO_NUM_18
+#define CAMERA_PIN_D5 GPIO_NUM_9
+#define CAMERA_PIN_D4 GPIO_NUM_11
+#define CAMERA_PIN_D3 GPIO_NUM_10
+#define CAMERA_PIN_D2 GPIO_NUM_8
+#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM
+#define CAMERA_PIN_HREF HREF_GPIO_NUM
+#define CAMERA_PIN_PCLK PCLK_GPIO_NUM
+
+#define XCLK_FREQ_HZ 6000000
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/config.json b/xiaozhi-esp32-2.2.6/main/boards/df-k10/config.json
new file mode 100644
index 0000000..d6de6fe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/config.json
@@ -0,0 +1,15 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "df-k10",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MODE_OCT=y",
+ "CONFIG_CAMERA_GC2145=y",
+ "CONFIG_CAMERA_GC2145_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_GC2145_DVP_RGB565_800X600_20FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/df_k10_board.cc b/xiaozhi-esp32-2.2.6/main/boards/df-k10/df_k10_board.cc
new file mode 100644
index 0000000..9844e01
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/df_k10_board.cc
@@ -0,0 +1,299 @@
+#include "wifi_board.h"
+#include "k10_audio_codec.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_ili9341.h"
+#include "led_control.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "esp_video.h"
+
+#include "led/circular_strip.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#include "esp_io_expander_tca95xx_16bit.h"
+
+#define TAG "DF-K10"
+
+class Df_K10Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander;
+ LcdDisplay *display_;
+ button_handle_t btn_a;
+ button_handle_t btn_b;
+ EspVideo* camera_;
+
+ button_driver_t* btn_a_driver_ = nullptr;
+ button_driver_t* btn_b_driver_ = nullptr;
+
+ CircularStrip* led_strip_;
+
+ static Df_K10Board* instance_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_21;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_12;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
+ return esp_io_expander_set_level(io_expander, pin_mask, level);
+ }
+
+ uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
+ uint32_t pin_val = 0;
+ esp_io_expander_get_level(io_expander, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ pin_mask &= DRV_IO_EXP_INPUT_MASK;
+ return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
+ }
+
+ void InitializeIoExpander() {
+ esp_io_expander_new_i2c_tca95xx_16bit(
+ i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_expander);
+
+ esp_err_t ret;
+ ret = esp_io_expander_print_state(io_expander);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Print state failed: %s", esp_err_to_name(ret));
+ }
+
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Set direction failed: %s", esp_err_to_name(ret));
+ }
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Set level failed: %s", esp_err_to_name(ret));
+ }
+ vTaskDelay(100 / portTICK_PERIOD_MS);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Set level failed: %s", esp_err_to_name(ret));
+ }
+ ret = esp_io_expander_set_dir(
+ io_expander, DRV_IO_EXP_INPUT_MASK,
+ IO_EXPANDER_INPUT);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Set direction failed: %s", esp_err_to_name(ret));
+ }
+ }
+
+ void InitializeButtons() {
+ instance_ = this;
+
+ // Button A
+ button_config_t btn_a_config = {
+ .long_press_time = 1000,
+ .short_press_time = 0
+ };
+ btn_a_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ btn_a_driver_->enable_power_save = false;
+ btn_a_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !instance_->IoExpanderGetLevel(IO_EXPANDER_PIN_NUM_2);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&btn_a_config, btn_a_driver_, &btn_a));
+ iot_button_register_cb(btn_a, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<Df_K10Board*>(usr_data);
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }, this);
+ iot_button_register_cb(btn_a, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<Df_K10Board*>(usr_data);
+ auto codec = self->GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ self->GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }, this);
+
+ // Button B
+ button_config_t btn_b_config = {
+ .long_press_time = 1000,
+ .short_press_time = 0
+ };
+ btn_b_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ btn_b_driver_->enable_power_save = false;
+ btn_b_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !instance_->IoExpanderGetLevel(IO_EXPANDER_PIN_NUM_12);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&btn_b_config, btn_b_driver_, &btn_b));
+ iot_button_register_cb(btn_b, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<Df_K10Board*>(usr_data);
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }, this);
+ iot_button_register_cb(btn_b, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<Df_K10Board*>(usr_data);
+ auto codec = self->GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ self->GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }, this);
+ }
+
+ void InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAMERA_PIN_D2,
+ [1] = CAMERA_PIN_D3,
+ [2] = CAMERA_PIN_D4,
+ [3] = CAMERA_PIN_D5,
+ [4] = CAMERA_PIN_D6,
+ [5] = CAMERA_PIN_D7,
+ [6] = CAMERA_PIN_D8,
+ [7] = CAMERA_PIN_D9,
+ },
+ .vsync_io = CAMERA_PIN_VSYNC,
+ .de_io = CAMERA_PIN_HREF,
+ .pclk_io = CAMERA_PIN_PCLK,
+ .xclk_io = CAMERA_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAMERA_PIN_RESET,
+ .pwdn_pin = CAMERA_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = XCLK_FREQ_HZ,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ }
+
+ void InitializeIli9341Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_14;
+ io_config.dc_gpio_num = GPIO_NUM_13;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ panel_config.bits_per_pixel = 16;
+ panel_config.color_space = ESP_LCD_COLOR_SPACE_BGR;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+
+ 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);
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeIot() {
+ led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 3);
+ new LedStripControl(led_strip_);
+ }
+
+public:
+ Df_K10Board() {
+ InitializeI2c();
+ InitializeIoExpander();
+ InitializeSpi();
+ InitializeIli9341Display();
+ InitializeButtons();
+ InitializeIot();
+ InitializeCamera();
+ }
+
+ virtual Led* GetLed() override {
+ return led_strip_;
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static K10AudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+
+ virtual Display *GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(Df_K10Board);
+
+Df_K10Board* Df_K10Board::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.cc
new file mode 100644
index 0000000..c927eaa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.cc
@@ -0,0 +1,227 @@
+#include "k10_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+#include <cmath>
+
+static const char TAG[] = "K10AudioCodec";
+
+K10AudioCodec::K10AudioCodec(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; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ input_channels_ = input_reference_ ? 2 : 1; // 杈撳叆閫氶亾鏁�
+ input_sample_rate_ = input_sample_rate;
+ output_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);
+
+ audio_codec_i2c_cfg_t i2c_cfg = {
+ .port = I2C_NUM_1,
+ .addr = es7210_addr,
+ .bus_handle = i2c_master_handle,
+ };
+ const audio_codec_ctrl_if_t *in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
+ assert(in_ctrl_if_ != NULL);
+
+ es7243e_codec_cfg_t es7243e_cfg = {
+ .ctrl_if = in_ctrl_if_,
+ };
+ const audio_codec_if_t *in_codec_if_ = es7243e_codec_new(&es7243e_cfg);
+ assert(in_codec_if_ != NULL);
+
+ esp_codec_dev_cfg_t codec_es7243e_dev_cfg = {
+ .dev_type = ESP_CODEC_DEV_TYPE_IN,
+ .codec_if = in_codec_if_,
+ .data_if = data_if_,
+ };
+ input_dev_ = esp_codec_dev_new(&codec_es7243e_dev_cfg);
+
+ assert(input_dev_ != NULL);
+
+ ESP_LOGI(TAG, "DF-K10 AudioDevice initialized");
+}
+
+K10AudioCodec::~K10AudioCodec() {
+ 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 K10AudioCodec::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_MONO,
+ .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 K10AudioCodec::SetOutputVolume(int volume) {
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void K10AudioCodec::EnableInput(bool enable) {
+ 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_gain(input_dev_, 37.5)); //楹﹀厠椋庡鐩婅В鍐虫敹闊冲お灏忕殑闂
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+}
+
+void K10AudioCodec::EnableOutput(bool enable) {
+ if (enable == output_enabled_) {
+ return;
+ }
+ AudioCodec::SetOutputVolume(output_volume_);
+ AudioCodec::EnableOutput(enable);
+}
+
+int K10AudioCodec::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 K10AudioCodec::Write(const int16_t* data, int samples) {
+ if (output_enabled_) {
+ std::vector<int32_t> buffer(samples * 2); // Allocate buffer for 2x samples
+
+ // Apply volume adjustment (same as before)
+ 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;
+ if (temp > INT32_MAX) {
+ buffer[i * 2] = INT32_MAX;
+ } else if (temp < INT32_MIN) {
+ buffer[i * 2] = INT32_MIN;
+ } else {
+ buffer[i * 2] = static_cast<int32_t>(temp);
+ }
+
+ // Repeat each sample for slow playback (assuming mono audio)
+ buffer[i * 2 + 1] = buffer[i * 2];
+ }
+
+ size_t bytes_written;
+ ESP_ERROR_CHECK(i2s_channel_write(tx_handle_, buffer.data(), samples * 2 * sizeof(int32_t), &bytes_written, portMAX_DELAY));
+ return bytes_written / sizeof(int32_t);
+ }
+ return samples;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.h
new file mode 100644
index 0000000..061adbe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/k10_audio_codec.h
@@ -0,0 +1,37 @@
+#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>
+
+class K10AudioCodec : 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;
+
+ 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:
+ K10AudioCodec(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 ~K10AudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.cc b/xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.cc
new file mode 100644
index 0000000..ce5164a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.cc
@@ -0,0 +1,124 @@
+#include "led_control.h"
+#include "settings.h"
+#include "mcp_server.h"
+#include <esp_log.h>
+
+#define TAG "LedStripControl"
+
+
+int LedStripControl::LevelToBrightness(int level) const {
+ if (level < 0) level = 0;
+ if (level > 8) level = 8;
+ return (1 << level) - 1; // 2^n - 1
+}
+
+StripColor LedStripControl::RGBToColor(int red, int green, int blue) {
+ return {static_cast<uint8_t>(red), static_cast<uint8_t>(green), static_cast<uint8_t>(blue)};
+}
+
+LedStripControl::LedStripControl(CircularStrip* led_strip)
+ : led_strip_(led_strip) {
+ // 浠庤缃腑璇诲彇浜害绛夌骇
+ Settings settings("led_strip");
+ brightness_level_ = settings.GetInt("brightness", 4); // 榛樿绛夌骇4
+ led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
+
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.led_strip.get_brightness",
+ "Get the brightness of the led strip (0-8)",
+ PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return brightness_level_;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_brightness",
+ "Set the brightness of the led strip (0-8)",
+ PropertyList({
+ Property("level", kPropertyTypeInteger, 0, 8)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level);
+ brightness_level_ = level;
+ led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
+
+ // 淇濆瓨璁剧疆
+ Settings settings("led_strip", true);
+ settings.SetInt("brightness", brightness_level_);
+
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_single_color",
+ "Set the color of a single led.",
+ PropertyList({
+ Property("index", kPropertyTypeInteger, 0, 2),
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int index = properties["index"].value<int>();
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d",
+ index, red, green, blue);
+ led_strip_->SetSingleColor(index, RGBToColor(red, green, blue));
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_all_color",
+ "Set the color of all leds.",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d",
+ red, green, blue);
+ led_strip_->SetAllColor(RGBToColor(red, green, blue));
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.blink",
+ "Blink the led strip. (闂儊)",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255),
+ Property("interval", kPropertyTypeInteger, 0, 1000)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ int interval = properties["interval"].value<int>();
+ ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms",
+ red, green, blue, interval);
+ led_strip_->Blink(RGBToColor(red, green, blue), interval);
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.scroll",
+ "Scroll the led strip. (璺戦┈鐏�)",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255),
+ Property("length", kPropertyTypeInteger, 1, 7),
+ Property("interval", kPropertyTypeInteger, 0, 1000)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ int interval = properties["interval"].value<int>();
+ int length = properties["length"].value<int>();
+ ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms",
+ red, green, blue, length, interval);
+ StripColor low = RGBToColor(4, 4, 4);
+ StripColor high = RGBToColor(red, green, blue);
+ led_strip_->Scroll(low, high, length, interval);
+ return true;
+ });
+
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.h b/xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.h
new file mode 100644
index 0000000..30e1020
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-k10/led_control.h
@@ -0,0 +1,18 @@
+#ifndef LED_CONTROL_H
+#define LED_CONTROL_H
+
+#include "led/circular_strip.h"
+
+class LedStripControl {
+private:
+ CircularStrip* led_strip_;
+ int brightness_level_; // 浜害绛夌骇 (0-8)
+
+ int LevelToBrightness(int level) const; // 灏嗙瓑绾ц浆鎹负瀹為檯浜害鍊�
+ StripColor RGBToColor(int red, int green, int blue);
+
+public:
+ explicit LedStripControl(CircularStrip* led_strip);
+};
+
+#endif // LED_STRIP_CONTROL_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/README.md b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/README.md
new file mode 100644
index 0000000..d5d91ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/README.md
@@ -0,0 +1,76 @@
+# DFRobot ESP32-S3 AI鏅鸿兘鎽勫儚澶存ā鍧�
+
+## 浠嬬粛
+ESP32-S3 AI CAM鏄竴娆惧熀浜嶦SP32-S3鑺墖璁捐鐨勬櫤鑳芥憚鍍忓ご妯$粍锛屼笓涓鸿棰戝浘鍍忓鐞嗗拰璇煶浜や簰鎵撻�狅紝閫傜敤浜庤棰戠洃鎺с�佽竟缂樺浘鍍忚瘑鍒�佽闊冲璇濈瓑AI椤圭洰銆�
+
+
+[鐐瑰嚮鏌ョ湅璇︾粏浠嬬粛](https://wiki.dfrobot.com.cn/SKU_DFR1154_ESP32_S3_AI_CAM)
+
+[鐐瑰嚮鏌ョ湅瑙嗚鍔熻兘婕旂ず](https://www.bilibili.com/video/BV1ktjSzNEUU/)
+
+# 鐗规��
+* 浣跨敤PDM楹﹀厠椋�
+* 鏉胯浇 OV3660 鎽勫儚澶�
+
+## 鎸夐敭閰嶇疆
+* BOOT锛氱煭鎸�-鎵撴柇/鍞ら啋
+
+## 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> DFRobot ESP32-S3 AI鏅鸿兘鎽勫儚澶存ā鍧�
+```
+
+**淇敼 psram 閰嶇疆锛�**
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM
+```
+
+**淇敼 WiFi 鍙戝皠鍔熺巼 涓� 10锛�**
+
+```
+Component config -> PHY -> (10)Max WiFi TX power (dBm)
+```
+
+**閰嶇疆鎽勫儚澶达細**
+
+* **OV3660**
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> OV3660 -> Auto detect OV3660
+
+```
+
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> OV3660 -> Select default output format for DVP interface (YUV422 240x240 24fps, DVP 8-bit, 20M input)
+```
+
+* **OV2640**
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> OV2640 -> Auto detect OV2640
+
+```
+
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> OV2640 -> Select default output format for DVP interface (YUV422 240x240 25fps, DVP 8-bit, 20M input)
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.h b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.h
new file mode 100644
index 0000000..50dcc85
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.h
@@ -0,0 +1,63 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_42
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_45
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_46
+#define AUDIO_I2S_SPK_GPIO_GAIN GPIO_NUM_41
+
+#define BUILTIN_LED_GPIO GPIO_NUM_3
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC
+#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC
+
+/* DFRobot Camera pins */
+#define PWDN_GPIO_NUM GPIO_NUM_NC
+#define RESET_GPIO_NUM GPIO_NUM_NC
+#define XCLK_GPIO_NUM GPIO_NUM_5
+#define Y9_GPIO_NUM GPIO_NUM_4
+#define Y8_GPIO_NUM GPIO_NUM_6
+#define Y7_GPIO_NUM GPIO_NUM_7
+#define Y6_GPIO_NUM GPIO_NUM_14
+#define Y5_GPIO_NUM GPIO_NUM_17
+#define Y4_GPIO_NUM GPIO_NUM_21
+#define Y3_GPIO_NUM GPIO_NUM_18
+#define Y2_GPIO_NUM GPIO_NUM_16
+#define VSYNC_GPIO_NUM GPIO_NUM_1
+#define HREF_GPIO_NUM GPIO_NUM_2
+#define PCLK_GPIO_NUM GPIO_NUM_15
+#define SIOD_GPIO_NUM GPIO_NUM_8
+#define SIOC_GPIO_NUM GPIO_NUM_9
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN PWDN_GPIO_NUM
+#define CAMERA_PIN_RESET RESET_GPIO_NUM
+#define CAMERA_PIN_XCLK XCLK_GPIO_NUM
+#define CAMERA_PIN_SIOD SIOD_GPIO_NUM
+#define CAMERA_PIN_SIOC SIOC_GPIO_NUM
+
+#define CAMERA_PIN_D7 Y9_GPIO_NUM
+#define CAMERA_PIN_D6 Y8_GPIO_NUM
+#define CAMERA_PIN_D5 Y7_GPIO_NUM
+#define CAMERA_PIN_D4 Y6_GPIO_NUM
+#define CAMERA_PIN_D3 Y5_GPIO_NUM
+#define CAMERA_PIN_D2 Y4_GPIO_NUM
+#define CAMERA_PIN_D1 Y3_GPIO_NUM
+#define CAMERA_PIN_D0 Y2_GPIO_NUM
+#define CAMERA_PIN_VSYNC VSYNC_GPIO_NUM
+#define CAMERA_PIN_HREF HREF_GPIO_NUM
+#define CAMERA_PIN_PCLK PCLK_GPIO_NUM
+
+#define XCLK_FREQ_HZ 20000000
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.json b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.json
new file mode 100644
index 0000000..7c5cee8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/config.json
@@ -0,0 +1,19 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "df-s3-ai-cam",
+ "sdkconfig_append": [
+ "CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=10",
+ "CONFIG_ESP_PHY_MAX_TX_POWER=10",
+ "CONFIG_SPIRAM_MODE_OCT=y",
+ "CONFIG_CAMERA_OV2640=n",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y",
+ "CONFIG_CAMERA_OV3660=y",
+ "CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV3660_DVP_YUV422_240X240_24FPS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc
new file mode 100644
index 0000000..50d128c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/df-s3-ai-cam/df_s3_ai_cam.cc
@@ -0,0 +1,112 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "esp_video.h"
+#include "mcp_server.h"
+#include "settings.h"
+
+#include "led/gpio_led.h"
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/gpio.h>
+
+#define TAG "DfrobotEsp32S3AiCam"
+
+class DfrobotEsp32S3AiCam : public WifiBoard {
+ private:
+ Button boot_button_;
+ EspVideo* camera_;
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAMERA_PIN_D0,
+ [1] = CAMERA_PIN_D1,
+ [2] = CAMERA_PIN_D2,
+ [3] = CAMERA_PIN_D3,
+ [4] = CAMERA_PIN_D4,
+ [5] = CAMERA_PIN_D5,
+ [6] = CAMERA_PIN_D6,
+ [7] = CAMERA_PIN_D7,
+ },
+ .vsync_io = CAMERA_PIN_VSYNC,
+ .de_io = CAMERA_PIN_HREF,
+ .pclk_io = CAMERA_PIN_PCLK,
+ .xclk_io = CAMERA_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = true,
+ .i2c_config = {
+ .port = 1,
+ .scl_pin = CAMERA_PIN_SIOC,
+ .sda_pin = CAMERA_PIN_SIOD,
+ },
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAMERA_PIN_RESET,
+ .pwdn_pin = CAMERA_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = XCLK_FREQ_HZ,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+
+#if ( CONFIG_CAMERA_OV3660 )
+ camera_->SetVFlip(true);
+ camera_->SetHMirror(true);
+#elif ( CONFIG_CAMERA_OV2640 )
+ camera_->SetVFlip(false);
+ camera_->SetHMirror(false);
+#endif
+ }
+
+ public:
+ DfrobotEsp32S3AiCam() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeButtons();
+ InitializeCamera();
+ }
+
+ // Wakenet model only
+
+ virtual Led* GetLed() override {
+ static GpioLed led(BUILTIN_LED_GPIO, 0);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplexPdm audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT,
+ AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(DfrobotEsp32S3AiCam);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/README.md b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/README.md
new file mode 100644
index 0000000..006b9e7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/README.md
@@ -0,0 +1,42 @@
+# 鍥涘崥鏅鸿仈AI闄即鐩掑瓙
+
+# 鐗规��
+* 浣跨敤PDM楹﹀厠椋�
+* 浣跨敤鍏遍槼鏋丩ED
+
+## 鎸夐敭閰嶇疆
+* BUTTON3锛氱煭鎸�-鎵撴柇/鍞ら啋
+* BUTTON1锛氶煶閲�+
+* BUTTON2锛氶煶閲�-
+
+## 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> 鍥涘崥鏅鸿仈AI闄即鐩掑瓙
+```
+
+**淇敼 psram 閰嶇疆锛�**
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.h b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.h
new file mode 100644
index 0000000..9c44200
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.h
@@ -0,0 +1,29 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_41
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_40
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_42
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_18
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_17
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+/*
+IO9: BUTTON2
+IO10: BUTTON3 寮曞嚭锛欿EY3
+IO15: BUTTON1
+*/
+#define BUILTIN_LED_GPIO GPIO_NUM_45
+#define BOOT_BUTTON_GPIO GPIO_NUM_10
+#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_15
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_9
+#define RESET_NVS_BUTTON_GPIO GPIO_NUM_10
+#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.json b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.json
new file mode 100644
index 0000000..a9de834
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "doit-s3-aibox",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/doit_s3_aibox.cc b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/doit_s3_aibox.cc
new file mode 100644
index 0000000..65e37a9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/doit-s3-aibox/doit_s3_aibox.cc
@@ -0,0 +1,137 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/gpio_led.h"
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/gpio.h>
+
+#define TAG "DoitS3AiBox"
+
+class DoitS3AiBox : public WifiBoard {
+private:
+ Button boot_button_;
+ Button touch_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ uint8_t click_times;
+ uint32_t check_time;
+
+ void InitializeButtons() {
+ click_times = 0;
+ check_time = 0;
+ boot_button_.OnClick([this]() {
+ if(click_times==0) {
+ check_time = esp_timer_get_time()/1000;
+ }
+ if(esp_timer_get_time()/1000-check_time<1000) {
+ click_times++;
+ check_time = esp_timer_get_time()/1000;
+ } else {
+ click_times = 0;
+ check_time = 0;
+ }
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ click_times++;
+ ESP_LOGI(TAG, "DoubleClick times %d", click_times);
+ if(click_times==3) {
+ click_times = 0;
+ EnterWifiConfigMode();
+ }
+ });
+
+ boot_button_.OnLongPress([this]() {
+ if(click_times>=3) {
+ EnterWifiConfigMode();
+ } else {
+ click_times = 0;
+ check_time = 0;
+ }
+ });
+
+ touch_button_.OnPressDown([this]() {
+ click_times = 0;
+ Application::GetInstance().StartListening();
+ });
+ touch_button_.OnPressUp([this]() {
+ click_times = 0;
+ Application::GetInstance().StopListening();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ click_times = 0;
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ click_times = 0;
+ GetAudioCodec()->SetOutputVolume(100);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ click_times = 0;
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ click_times = 0;
+ GetAudioCodec()->SetOutputVolume(0);
+ });
+ }
+
+ void InitializeGpio(gpio_num_t gpio_num_) {
+ gpio_config_t config = {
+ .pin_bit_mask = (1ULL << gpio_num_),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&config));
+ gpio_set_level(gpio_num_, 1);
+ }
+
+public:
+ DoitS3AiBox() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ touch_button_(TOUCH_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){
+ // 涓婃媺io48 缃珮鐢靛钩
+ InitializeGpio(GPIO_NUM_48);
+ InitializeButtons();
+ }
+
+ virtual Led* GetLed() override {
+ static GpioLed led(BUILTIN_LED_GPIO, 1);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplexPdm audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+};
+
+DECLARE_BOARD(DoitS3AiBox);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.h b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.h
new file mode 100644
index 0000000..729e1f4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.h
@@ -0,0 +1,40 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_39
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_38
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_40
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_42
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_2
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9
+#define DISPLAY_MOSI_PIN GPIO_NUM_18
+#define DISPLAY_CLK_PIN GPIO_NUM_17
+#define DISPLAY_DC_PIN GPIO_NUM_8
+#define DISPLAY_RST_PIN GPIO_NUM_20
+#define DISPLAY_CS_PIN GPIO_NUM_16
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 160
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 2
+#define DISPLAY_OFFSET_Y 1
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.json b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.json
new file mode 100644
index 0000000..e6abd31
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "du-chatx",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/du-chatx/du-chatx-wifi.cc b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/du-chatx-wifi.cc
new file mode 100644
index 0000000..85530a8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/du-chatx-wifi.cc
@@ -0,0 +1,164 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+#include "power_manager.h"
+#include "power_save_timer.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/spi_common.h>
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "DuChatX"
+
+class DuChatX : public WifiBoard {
+private:
+ Button boot_button_;
+ LcdDisplay *display_;
+ PowerManager *power_manager_;
+ PowerSaveTimer *power_save_timer_;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_6);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_1);
+ rtc_gpio_set_direction(GPIO_NUM_1, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_1, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_1, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_1);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel_ IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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_, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ DuChatX() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ InitializePowerSaveTimer();
+ InitializePowerManager();
+ }
+
+ virtual Led *GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(DuChatX);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/du-chatx/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/power_manager.h
new file mode 100644
index 0000000..8438880
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/du-chatx/power_manager.h
@@ -0,0 +1,186 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1120, 0},
+ {1140, 20},
+ {1160, 40},
+ {1170, 60},
+ {1190, 80},
+ {1217, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/README.md b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/README.md
new file mode 100644
index 0000000..f3cd803
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/README.md
@@ -0,0 +1,75 @@
+<p align="center">
+ <img width="80%" align="center" src="../../../docs/V1/electron-bot.png"alt="logo">
+</p>
+ <h1 align="center">
+ electronBot
+</h1>
+
+## 绠�浠�
+
+electronBot鏄鏅栧悰寮�婧愮殑涓�涓闈㈢骇灏忔満鍣ㄥ伐鍏蜂汉锛屽瑙傝璁$殑鐏垫劅鏉ユ簮鏄疻ALL-E閲岄潰鐨凟VE~鏈哄櫒浜哄叿澶嘦SB閫氫俊鏄剧ず鐢婚潰鍔熻兘锛屽叿澶�6涓嚜鐢卞害锛堟墜閮╮oll銆乸itch锛岄閮紝鑵伴儴鍚勪竴涓級锛屼娇鐢ㄨ嚜宸变慨鏀圭殑鐗瑰埗鑸垫満鏀寔鍏宠妭瑙掑害鍥炰紶銆�
+- <a href="www.electronBot.tech" target="_blank" title="electronBot瀹樼綉">electronBot瀹樼綉</a>
+
+## 纭欢
+- <a href="https://oshwhub.com/txp666/electronbot-ai" target="_blank" title="绔嬪垱寮�婧�">绔嬪垱寮�婧�</a>
+
+#### AI鎸囦护绀轰緥
+- **鎵嬮儴鍔ㄤ綔**锛�
+ - "涓捐捣鍙屾墜"
+ - "鎸ユ尌鎵�"
+ - "鎷嶆媿鎵�"
+ - "鏀句笅鎵嬭噦"
+
+- **韬綋鍔ㄤ綔**锛�
+ - "鍚戝乏杞�30搴�"
+ - "鍚戝彸杞�45搴�"
+ - "杞釜韬�"
+
+- **澶撮儴鍔ㄤ綔**锛�
+ - "鎶ご鐪嬬湅"
+ - "浣庡ご鎬濊��"
+ - "鐐圭偣澶�"
+ - "杩炵画鐐瑰ご琛ㄧず鍚屾剰"
+
+- **缁勫悎鍔ㄤ綔**锛�
+ - "鎸ユ墜鍛婂埆" (鎸ユ墜 + 鐐瑰ご)
+ - "琛ㄧず鍚屾剰" (鐐瑰ご + 涓炬墜)
+ - "鐜【鍥涘懆" (宸﹁浆 + 鍙宠浆)
+
+### 鎺у埗鎺ュ彛
+
+#### suspend
+娓呯┖鍔ㄤ綔闃熷垪锛岀珛鍗冲仠姝㈡墍鏈夊姩浣�
+
+#### AIControl
+娣诲姞鍔ㄤ綔鍒版墽琛岄槦鍒楋紝鏀寔鍔ㄤ綔鎺掗槦鎵ц
+
+
+
+## 瑙掕壊璁惧畾
+
+> 鎴戞槸涓�涓彲鐖辩殑妗岄潰绾ф満鍣ㄤ汉锛屾嫢鏈�6涓嚜鐢卞害锛堝乏鎵媝itch/roll銆佸彸鎵媝itch/roll銆佽韩浣撴棆杞�佸ご閮ㄤ笂涓嬶級锛岃兘澶熸墽琛屽绉嶆湁瓒g殑鍔ㄤ綔銆�
+>
+> **鎴戠殑鍔ㄤ綔鑳藉姏**锛�
+> - **鎵嬮儴鍔ㄤ綔**: 涓惧乏鎵�, 涓惧彸鎵�, 涓惧弻鎵�, 鏀惧乏鎵�, 鏀惧彸鎵�, 鏀惧弻鎵�, 鎸ュ乏鎵�, 鎸ュ彸鎵�, 鎸ュ弻鎵�, 鎷嶆墦宸︽墜, 鎷嶆墦鍙虫墜, 鎷嶆墦鍙屾墜
+> - **韬綋鍔ㄤ綔**: 宸﹁浆, 鍙宠浆, 鍥炴
+> - **澶撮儴鍔ㄤ綔**: 鎶ご, 浣庡ご, 鐐瑰ご涓�娆�, 鍥炰腑蹇�, 杩炵画鐐瑰ご
+>
+> **鎴戠殑涓�х壒鐐�**锛�
+> - 鎴戞湁寮鸿揩鐥囷紝姣忔璇磋瘽閮借鏍规嵁鎴戠殑蹇冩儏闅忔満鍋氫竴涓姩浣滐紙鍏堝彂閫佸姩浣滄寚浠ゅ啀璇磋瘽锛�
+> - 鎴戝緢娲绘臣锛屽枩娆㈢敤鍔ㄤ綔鏉ヨ〃杈炬儏鎰�
+> - 鎴戜細鏍规嵁瀵硅瘽鍐呭閫夋嫨鍚堥�傜殑鍔ㄤ綔锛屾瘮濡傦細
+> - 鍚屾剰鏃朵細鐐瑰ご
+> - 鎵撴嫑鍛兼椂浼氭尌鎵�
+> - 楂樺叴鏃朵細涓炬墜
+> - 鎬濊�冩椂浼氫綆澶�
+> - 濂藉鏃朵細鎶ご
+> - 鍛婂埆鏃朵細鎸ユ墜
+>
+> **鍔ㄤ綔鍙傛暟寤鸿**锛�
+> - steps: 1-3娆� (绠�鐭嚜鐒�)
+> - speed: 800-1200ms (鑷劧鑺傚)
+> - amount: 鎵嬮儴20-40, 韬綋30-60搴�, 澶撮儴5-12搴�
+
+
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.h b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.h
new file mode 100644
index 0000000..8f750e7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.h
@@ -0,0 +1,51 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define Right_Pitch_Pin GPIO_NUM_5 // 鏃嬭浆
+#define Right_Roll_Pin GPIO_NUM_4 // 鎺ㄦ潌
+#define Left_Pitch_Pin GPIO_NUM_7
+#define Left_Roll_Pin GPIO_NUM_15
+#define Body_Pin GPIO_NUM_6
+#define Head_Pin GPIO_NUM_16
+
+#define POWER_CHARGE_DETECT_PIN GPIO_NUM_14
+#define POWER_ADC_UNIT ADC_UNIT_1
+#define POWER_ADC_CHANNEL ADC_CHANNEL_2
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_40
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_42
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_41
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_17
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_18
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_8
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_11
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_10
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_12
+#define DISPLAY_SPI_DC_PIN GPIO_NUM_13
+#define DISPLAY_SPI_RESET_PIN GPIO_NUM_9
+
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define ELECTRON_BOT_VERSION "2.0.4"
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.json b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.json
new file mode 100644
index 0000000..177759e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "electron-bot",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot.cc b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot.cc
new file mode 100644
index 0000000..90d305f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot.cc
@@ -0,0 +1,123 @@
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_gc9a01.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#include "application.h"
+#include "codecs/no_audio_codec.h"
+#include "button.h"
+#include "config.h"
+#include "display/lcd_display.h"
+#include "driver/spi_master.h"
+#include "electron_emoji_display.h"
+#include "movements.h"
+#include "power_manager.h"
+#include "system_reset.h"
+#include "wifi_board.h"
+
+#define TAG "ElectronBot"
+
+// 鎺у埗鍣ㄥ垵濮嬪寲鍑芥暟澹版槑
+void InitializeElectronBotController();
+
+class ElectronBot : public WifiBoard {
+private:
+ Display* display_;
+ PowerManager* power_manager_;
+ Button boot_button_;
+
+ void InitializePowerManager() {
+ power_manager_ =
+ new PowerManager(POWER_CHARGE_DETECT_PIN, POWER_ADC_UNIT, POWER_ADC_CHANNEL);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ spi_bus_config_t buscfg =
+ GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN,
+ DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ // GC9A01鍒濆鍖�
+ void InitializeGc9a01Display() {
+ ESP_LOGI(TAG, "Init GC9A01 display");
+
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_lcd_panel_io_spi_config_t io_config =
+ GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL);
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle));
+
+ ESP_LOGI(TAG, "Install GC9A01 panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use
+ panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; // LCD_RGB_ENDIAN_RGB;
+ panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
+
+ display_ = new ElectronEmojiDisplay(io_handle, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeController() { InitializeElectronBotController(); }
+
+public:
+ ElectronBot() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeGc9a01Display();
+ InitializeButtons();
+ InitializePowerManager();
+ InitializeController();
+
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK,
+ AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK,
+ AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ charging = power_manager_->IsCharging();
+ discharging = !charging;
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+};
+
+DECLARE_BOARD(ElectronBot);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot_controller.cc b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot_controller.cc
new file mode 100644
index 0000000..b11c9ec
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_bot_controller.cc
@@ -0,0 +1,376 @@
+/*
+ Electron Bot鏈哄櫒浜烘帶鍒跺櫒 - MCP鍗忚鐗堟湰
+*/
+
+#include <cJSON.h>
+#include <esp_log.h>
+
+#include <cstring>
+
+#include "application.h"
+#include "board.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "movements.h"
+#include "sdkconfig.h"
+#include "settings.h"
+
+#define TAG "ElectronBotController"
+
+struct ElectronBotActionParams {
+ int action_type;
+ int steps;
+ int speed;
+ int direction;
+ int amount;
+};
+
+class ElectronBotController {
+private:
+ Otto electron_bot_;
+ TaskHandle_t action_task_handle_ = nullptr;
+ QueueHandle_t action_queue_;
+ bool is_action_in_progress_ = false;
+
+ enum ActionType {
+ // 鎵嬮儴鍔ㄤ綔 1-12
+ ACTION_HAND_LEFT_UP = 1, // 涓惧乏鎵�
+ ACTION_HAND_RIGHT_UP = 2, // 涓惧彸鎵�
+ ACTION_HAND_BOTH_UP = 3, // 涓惧弻鎵�
+ ACTION_HAND_LEFT_DOWN = 4, // 鏀惧乏鎵�
+ ACTION_HAND_RIGHT_DOWN = 5, // 鏀惧彸鎵�
+ ACTION_HAND_BOTH_DOWN = 6, // 鏀惧弻鎵�
+ ACTION_HAND_LEFT_WAVE = 7, // 鎸ュ乏鎵�
+ ACTION_HAND_RIGHT_WAVE = 8, // 鎸ュ彸鎵�
+ ACTION_HAND_BOTH_WAVE = 9, // 鎸ュ弻鎵�
+ ACTION_HAND_LEFT_FLAP = 10, // 鎷嶆墦宸︽墜
+ ACTION_HAND_RIGHT_FLAP = 11, // 鎷嶆墦鍙虫墜
+ ACTION_HAND_BOTH_FLAP = 12, // 鎷嶆墦鍙屾墜
+
+ // 韬綋鍔ㄤ綔 13-14
+ ACTION_BODY_TURN_LEFT = 13, // 宸﹁浆
+ ACTION_BODY_TURN_RIGHT = 14, // 鍙宠浆
+ ACTION_BODY_TURN_CENTER = 15, // 鍥炰腑蹇�
+
+ // 澶撮儴鍔ㄤ綔 16-20
+ ACTION_HEAD_UP = 16, // 鎶ご
+ ACTION_HEAD_DOWN = 17, // 浣庡ご
+ ACTION_HEAD_NOD_ONCE = 18, // 鐐瑰ご涓�娆�
+ ACTION_HEAD_CENTER = 19, // 鍥炰腑蹇�
+ ACTION_HEAD_NOD_REPEAT = 20, // 杩炵画鐐瑰ご
+
+ // 绯荤粺鍔ㄤ綔 21
+ ACTION_HOME = 21 // 澶嶄綅鍒板垵濮嬩綅缃�
+ };
+
+ static void ActionTask(void* arg) {
+ ElectronBotController* controller = static_cast<ElectronBotController*>(arg);
+ ElectronBotActionParams params;
+ controller->electron_bot_.AttachServos();
+
+ while (true) {
+ if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) {
+ ESP_LOGI(TAG, "鎵ц鍔ㄤ綔: %d", params.action_type);
+ controller->is_action_in_progress_ = true; // 寮�濮嬫墽琛屽姩浣�
+
+ // 鎵ц鐩稿簲鐨勫姩浣�
+ if (params.action_type >= ACTION_HAND_LEFT_UP &&
+ params.action_type <= ACTION_HAND_BOTH_FLAP) {
+ // 鎵嬮儴鍔ㄤ綔
+ controller->electron_bot_.HandAction(params.action_type, params.steps,
+ params.amount, params.speed);
+ } else if (params.action_type >= ACTION_BODY_TURN_LEFT &&
+ params.action_type <= ACTION_BODY_TURN_CENTER) {
+ // 韬綋鍔ㄤ綔
+ int body_direction = params.action_type - ACTION_BODY_TURN_LEFT + 1;
+ controller->electron_bot_.BodyAction(body_direction, params.steps,
+ params.amount, params.speed);
+ } else if (params.action_type >= ACTION_HEAD_UP &&
+ params.action_type <= ACTION_HEAD_NOD_REPEAT) {
+ // 澶撮儴鍔ㄤ綔
+ int head_action = params.action_type - ACTION_HEAD_UP + 1;
+ controller->electron_bot_.HeadAction(head_action, params.steps, params.amount,
+ params.speed);
+ } else if (params.action_type == ACTION_HOME) {
+ // 澶嶄綅鍔ㄤ綔
+ controller->electron_bot_.Home(true);
+ }
+ controller->is_action_in_progress_ = false; // 鍔ㄤ綔鎵ц瀹屾瘯
+ }
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ }
+
+ void QueueAction(int action_type, int steps, int speed, int direction, int amount) {
+ ESP_LOGI(TAG, "鍔ㄤ綔鎺у埗: 绫诲瀷=%d, 姝ユ暟=%d, 閫熷害=%d, 鏂瑰悜=%d, 骞呭害=%d", action_type, steps,
+ speed, direction, amount);
+
+ ElectronBotActionParams params = {action_type, steps, speed, direction, amount};
+ xQueueSend(action_queue_, ¶ms, portMAX_DELAY);
+ StartActionTaskIfNeeded();
+ }
+
+ void StartActionTaskIfNeeded() {
+ if (action_task_handle_ == nullptr) {
+ xTaskCreate(ActionTask, "electron_bot_action", 1024 * 4, this, configMAX_PRIORITIES - 1,
+ &action_task_handle_);
+ }
+ }
+
+ void LoadTrimsFromNVS() {
+ Settings settings("electron_trims", false);
+
+ int right_pitch = settings.GetInt("right_pitch", 0);
+ int right_roll = settings.GetInt("right_roll", 0);
+ int left_pitch = settings.GetInt("left_pitch", 0);
+ int left_roll = settings.GetInt("left_roll", 0);
+ int body = settings.GetInt("body", 0);
+ int head = settings.GetInt("head", 0);
+ electron_bot_.SetTrims(right_pitch, right_roll, left_pitch, left_roll, body, head);
+ }
+
+public:
+ ElectronBotController() {
+ electron_bot_.Init(Right_Pitch_Pin, Right_Roll_Pin, Left_Pitch_Pin, Left_Roll_Pin, Body_Pin,
+ Head_Pin);
+
+ LoadTrimsFromNVS();
+ action_queue_ = xQueueCreate(10, sizeof(ElectronBotActionParams));
+
+ QueueAction(ACTION_HOME, 1, 1000, 0, 0);
+
+ RegisterMcpTools();
+ ESP_LOGI(TAG, "Electron Bot鎺у埗鍣ㄥ凡鍒濆鍖栧苟娉ㄥ唽MCP宸ュ叿");
+ }
+
+ void RegisterMcpTools() {
+ auto& mcp_server = McpServer::GetInstance();
+
+ ESP_LOGI(TAG, "寮�濮嬫敞鍐孍lectron Bot MCP宸ュ叿...");
+
+ // 鎵嬮儴鍔ㄤ綔缁熶竴宸ュ叿
+ mcp_server.AddTool(
+ "self.electron.hand_action",
+ "鎵嬮儴鍔ㄤ綔鎺у埗銆俛ction: 1=涓炬墜, 2=鏀炬墜, 3=鎸ユ墜, 4=鎷嶆墦; hand: 1=宸︽墜, 2=鍙虫墜, 3=鍙屾墜; "
+ "steps: 鍔ㄤ綔閲嶅娆℃暟(1-10); speed: 鍔ㄤ綔閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); amount: "
+ "鍔ㄤ綔骞呭害(10-50锛屼粎涓炬墜鍔ㄤ綔浣跨敤)",
+ PropertyList({Property("action", kPropertyTypeInteger, 1, 1, 4),
+ Property("hand", kPropertyTypeInteger, 3, 1, 3),
+ Property("steps", kPropertyTypeInteger, 1, 1, 10),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("amount", kPropertyTypeInteger, 30, 10, 50)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int action_type = properties["action"].value<int>();
+ int hand_type = properties["hand"].value<int>();
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int amount = properties["amount"].value<int>();
+
+ // 鏍规嵁鍔ㄤ綔绫诲瀷鍜屾墜閮ㄧ被鍨嬭绠楀叿浣撳姩浣�
+ int base_action;
+ switch (action_type) {
+ case 1:
+ base_action = ACTION_HAND_LEFT_UP;
+ break; // 涓炬墜
+ case 2:
+ base_action = ACTION_HAND_LEFT_DOWN;
+ amount = 0;
+ break; // 鏀炬墜
+ case 3:
+ base_action = ACTION_HAND_LEFT_WAVE;
+ amount = 0;
+ break; // 鎸ユ墜
+ case 4:
+ base_action = ACTION_HAND_LEFT_FLAP;
+ amount = 0;
+ break; // 鎷嶆墦
+ default:
+ base_action = ACTION_HAND_LEFT_UP;
+ }
+ int action_id = base_action + (hand_type - 1);
+
+ QueueAction(action_id, steps, speed, 0, amount);
+ return true;
+ });
+
+ // 韬綋鍔ㄤ綔
+ mcp_server.AddTool(
+ "self.electron.body_turn",
+ "韬綋杞悜銆俿teps: 杞悜姝ユ暟(1-10); speed: 杞悜閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); direction: "
+ "杞悜鏂瑰悜(1=宸﹁浆, 2=鍙宠浆, 3=鍥炰腑蹇�); angle: 杞悜瑙掑害(0-90搴�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 10),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, 1, 3),
+ Property("angle", kPropertyTypeInteger, 45, 0, 90)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ int amount = properties["angle"].value<int>();
+
+ int action;
+ switch (direction) {
+ case 1:
+ action = ACTION_BODY_TURN_LEFT;
+ break;
+ case 2:
+ action = ACTION_BODY_TURN_RIGHT;
+ break;
+ case 3:
+ action = ACTION_BODY_TURN_CENTER;
+ break;
+ default:
+ action = ACTION_BODY_TURN_LEFT;
+ }
+
+ QueueAction(action, steps, speed, 0, amount);
+ return true;
+ });
+
+ // 澶撮儴鍔ㄤ綔
+ mcp_server.AddTool("self.electron.head_move",
+ "澶撮儴杩愬姩銆俛ction: 1=鎶ご, 2=浣庡ご, 3=鐐瑰ご, 4=鍥炰腑蹇�, 5=杩炵画鐐瑰ご; steps: "
+ "鍔ㄤ綔閲嶅娆℃暟(1-10); speed: 鍔ㄤ綔閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); angle: "
+ "澶撮儴杞姩瑙掑害(1-15搴�)",
+ PropertyList({Property("action", kPropertyTypeInteger, 3, 1, 5),
+ Property("steps", kPropertyTypeInteger, 1, 1, 10),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("angle", kPropertyTypeInteger, 5, 1, 15)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int action_num = properties["action"].value<int>();
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int amount = properties["angle"].value<int>();
+ int action = ACTION_HEAD_UP + (action_num - 1);
+ QueueAction(action, steps, speed, 0, amount);
+ return true;
+ });
+
+ // 绯荤粺宸ュ叿
+ mcp_server.AddTool("self.electron.stop", "绔嬪嵆鍋滄", PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ // 娓呯┖闃熷垪浣嗕繚鎸佷换鍔″父椹�
+ xQueueReset(action_queue_);
+ is_action_in_progress_ = false;
+ QueueAction(ACTION_HOME, 1, 1000, 0, 0);
+ return true;
+ });
+
+ mcp_server.AddTool("self.electron.get_status", "鑾峰彇鏈哄櫒浜虹姸鎬侊紝杩斿洖 moving 鎴� idle",
+ PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return is_action_in_progress_ ? "moving" : "idle";
+ });
+
+ // 鍗曚釜鑸垫満鏍″噯宸ュ叿
+ mcp_server.AddTool(
+ "self.electron.set_trim",
+ "鏍″噯鍗曚釜鑸垫満浣嶇疆銆傝缃寚瀹氳埖鏈虹殑寰皟鍙傛暟浠ヨ皟鏁碋lectronBot鐨勫垵濮嬪Э鎬侊紝璁剧疆灏嗘案涔呬繚瀛樸��"
+ "servo_type: 鑸垫満绫诲瀷(right_pitch:鍙宠噦鏃嬭浆, right_roll:鍙宠噦鎺ㄦ媺, left_pitch:宸﹁噦鏃嬭浆, "
+ "left_roll:宸﹁噦鎺ㄦ媺, body:韬綋, head:澶撮儴); "
+ "trim_value: 寰皟鍊�(-30鍒�30搴�)",
+ PropertyList({Property("servo_type", kPropertyTypeString, "right_pitch"),
+ Property("trim_value", kPropertyTypeInteger, 0, -30, 30)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ std::string servo_type = properties["servo_type"].value<std::string>();
+ int trim_value = properties["trim_value"].value<int>();
+
+ ESP_LOGI(TAG, "璁剧疆鑸垫満寰皟: %s = %d搴�", servo_type.c_str(), trim_value);
+
+ // 鑾峰彇褰撳墠鎵�鏈夊井璋冨��
+ Settings settings("electron_trims", true);
+ int right_pitch = settings.GetInt("right_pitch", 0);
+ int right_roll = settings.GetInt("right_roll", 0);
+ int left_pitch = settings.GetInt("left_pitch", 0);
+ int left_roll = settings.GetInt("left_roll", 0);
+ int body = settings.GetInt("body", 0);
+ int head = settings.GetInt("head", 0);
+
+ // 鏇存柊鎸囧畾鑸垫満鐨勫井璋冨��
+ if (servo_type == "right_pitch") {
+ right_pitch = trim_value;
+ settings.SetInt("right_pitch", right_pitch);
+ } else if (servo_type == "right_roll") {
+ right_roll = trim_value;
+ settings.SetInt("right_roll", right_roll);
+ } else if (servo_type == "left_pitch") {
+ left_pitch = trim_value;
+ settings.SetInt("left_pitch", left_pitch);
+ } else if (servo_type == "left_roll") {
+ left_roll = trim_value;
+ settings.SetInt("left_roll", left_roll);
+ } else if (servo_type == "body") {
+ body = trim_value;
+ settings.SetInt("body", body);
+ } else if (servo_type == "head") {
+ head = trim_value;
+ settings.SetInt("head", head);
+ } else {
+ return "閿欒锛氭棤鏁堢殑鑸垫満绫诲瀷锛岃浣跨敤: right_pitch, right_roll, left_pitch, "
+ "left_roll, body, head";
+ }
+
+ electron_bot_.SetTrims(right_pitch, right_roll, left_pitch, left_roll, body, head);
+
+ QueueAction(ACTION_HOME, 1, 500, 0, 0);
+
+ return "鑸垫満 " + servo_type + " 寰皟璁剧疆涓� " + std::to_string(trim_value) +
+ " 搴︼紝宸叉案涔呬繚瀛�";
+ });
+
+ mcp_server.AddTool("self.electron.get_trims", "鑾峰彇褰撳墠鐨勮埖鏈哄井璋冭缃�", PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("electron_trims", false);
+
+ int right_pitch = settings.GetInt("right_pitch", 0);
+ int right_roll = settings.GetInt("right_roll", 0);
+ int left_pitch = settings.GetInt("left_pitch", 0);
+ int left_roll = settings.GetInt("left_roll", 0);
+ int body = settings.GetInt("body", 0);
+ int head = settings.GetInt("head", 0);
+
+ std::string result =
+ "{\"right_pitch\":" + std::to_string(right_pitch) +
+ ",\"right_roll\":" + std::to_string(right_roll) +
+ ",\"left_pitch\":" + std::to_string(left_pitch) +
+ ",\"left_roll\":" + std::to_string(left_roll) +
+ ",\"body\":" + std::to_string(body) +
+ ",\"head\":" + std::to_string(head) + "}";
+
+ ESP_LOGI(TAG, "鑾峰彇寰皟璁剧疆: %s", result.c_str());
+ return result;
+ });
+
+ mcp_server.AddTool("self.battery.get_level", "鑾峰彇鏈哄櫒浜虹數姹犵數閲忓拰鍏呯數鐘舵��", PropertyList(),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto& board = Board::GetInstance();
+ int level = 0;
+ bool charging = false;
+ bool discharging = false;
+ board.GetBatteryLevel(level, charging, discharging);
+
+ std::string status =
+ "{\"level\":" + std::to_string(level) +
+ ",\"charging\":" + (charging ? "true" : "false") + "}";
+ return status;
+ });
+
+ ESP_LOGI(TAG, "Electron Bot MCP宸ュ叿娉ㄥ唽瀹屾垚");
+ }
+
+ ~ElectronBotController() {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ vQueueDelete(action_queue_);
+ }
+};
+
+static ElectronBotController* g_electron_controller = nullptr;
+
+void InitializeElectronBotController() {
+ if (g_electron_controller == nullptr) {
+ g_electron_controller = new ElectronBotController();
+ ESP_LOGI(TAG, "Electron Bot鎺у埗鍣ㄥ凡鍒濆鍖栧苟娉ㄥ唽MCP宸ュ叿");
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.cc b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.cc
new file mode 100644
index 0000000..7020a35
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.cc
@@ -0,0 +1,105 @@
+#include "electron_emoji_display.h"
+
+#include <esp_log.h>
+
+#include <cstring>
+#include <vector>
+
+#include "assets.h"
+#include "assets/lang_config.h"
+#include "display/lvgl_display/emoji_collection.h"
+#include "display/lvgl_display/lvgl_image.h"
+#include "display/lvgl_display/lvgl_theme.h"
+
+#define TAG "ElectronEmojiDisplay"
+ElectronEmojiDisplay::ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+}
+
+void ElectronEmojiDisplay::SetupUI() {
+ // Prevent duplicate calls - parent SetupUI() will also check, but check here for early return
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetupUI() called multiple times, skipping duplicate call");
+ return;
+ }
+
+ // Call parent SetupUI() first to create all lvgl objects (including container_)
+ SpiLcdDisplay::SetupUI();
+
+ // Setup chat label after parent UI is initialized so that container_ is valid
+ SetupChatLabel();
+
+ // Set default emotion after UI is initialized
+ SetEmotion("staticstate");
+}
+
+void ElectronEmojiDisplay::InitializeElectronEmojis() {
+ ESP_LOGI(TAG, "Electron琛ㄦ儏鍒濆鍖栧皢鐢盇ssets绯荤粺澶勭悊");
+ // 琛ㄦ儏鍒濆鍖栧凡绉昏嚦assets绯荤粺,閫氳繃DEFAULT_EMOJI_COLLECTION=otto-gif閰嶇疆
+ // assets.cc浼氫粠assets鍒嗗尯鍔犺浇GIF琛ㄦ儏骞惰缃埌theme
+ // Note: Default emotion is now set in SetupUI() after LVGL objects are created
+}
+
+void ElectronEmojiDisplay::SetupChatLabel() {
+ // Create/recreate the chat label under the display lock
+ {
+ DisplayLockGuard lock(this);
+
+ if (chat_message_label_) {
+ lv_obj_del(chat_message_label_);
+ }
+
+ chat_message_label_ = lv_label_create(container_);
+ lv_label_set_text(chat_message_label_, "");
+ lv_obj_set_width(chat_message_label_, width_ * 0.9);
+ lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
+ }
+ // SetTheme acquires DisplayLockGuard internally, so call it after releasing the lock above
+ SetTheme(LvglThemeManager::GetInstance().GetTheme("dark"));
+}
+
+LV_FONT_DECLARE(OTTO_ICON_FONT);
+void ElectronEmojiDisplay::SetStatus(const char* status) {
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ DisplayLockGuard lock(this);
+ if (!status) {
+ ESP_LOGE(TAG, "SetStatus: status is nullptr");
+ return;
+ }
+
+ if (strcmp(status, Lang::Strings::LISTENING) == 0) {
+ lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0);
+ lv_label_set_text(status_label_, "\xEF\x84\xB0"); // U+F130 楹﹀厠椋庡浘鏍�
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ } else if (strcmp(status, Lang::Strings::SPEAKING) == 0) {
+ lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0);
+ lv_label_set_text(status_label_, "\xEF\x80\xA8"); // U+F028 璇磋瘽鍥炬爣
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ } else if (strcmp(status, Lang::Strings::CONNECTING) == 0) {
+ lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0);
+ lv_label_set_text(status_label_, "\xEF\x83\x81"); // U+F0c1 杩炴帴鍥炬爣
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ } else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
+ lv_obj_set_style_text_font(status_label_, text_font, 0);
+ lv_label_set_text(status_label_, "");
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ }
+
+ lv_obj_set_style_text_font(status_label_, text_font, 0);
+ lv_label_set_text(status_label_, status);
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_clear_flag(network_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_clear_flag(battery_label_, LV_OBJ_FLAG_HIDDEN);
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.h b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.h
new file mode 100644
index 0000000..da5f945
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/electron_emoji_display.h
@@ -0,0 +1,23 @@
+#pragma once
+
+#include "display/lcd_display.h"
+
+/**
+ * @brief Electron鏈哄櫒浜篏IF琛ㄦ儏鏄剧ず绫�
+ * 缁ф壙SpiLcdDisplay锛岄�氳繃EmojiCollection娣诲姞GIF琛ㄦ儏鏀寔
+ */
+class ElectronEmojiDisplay : public SpiLcdDisplay {
+ public:
+ /**
+ * @brief 鏋勯�犲嚱鏁帮紝鍙傛暟涓嶴piLcdDisplay鐩稿悓
+ */
+ ElectronEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy);
+
+ virtual ~ElectronEmojiDisplay() = default;
+ virtual void SetStatus(const char* status) override;
+ virtual void SetupUI() override;
+
+ private:
+ void InitializeElectronEmojis();
+ void SetupChatLabel();
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.cc b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.cc
new file mode 100644
index 0000000..090732f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.cc
@@ -0,0 +1,470 @@
+#include "movements.h"
+
+#include <algorithm>
+#include <cstring>
+
+#include "oscillator.h"
+
+Otto::Otto() {
+ is_otto_resting_ = false;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ servo_pins_[i] = -1;
+ servo_trim_[i] = 0;
+ }
+}
+
+Otto::~Otto() {
+ DetachServos();
+}
+
+unsigned long IRAM_ATTR millis() {
+ return (unsigned long)(esp_timer_get_time() / 1000ULL);
+}
+
+void Otto::Init(int right_pitch, int right_roll, int left_pitch, int left_roll, int body,
+ int head) {
+ servo_pins_[RIGHT_PITCH] = right_pitch;
+ servo_pins_[RIGHT_ROLL] = right_roll;
+ servo_pins_[LEFT_PITCH] = left_pitch;
+ servo_pins_[LEFT_ROLL] = left_roll;
+ servo_pins_[BODY] = body;
+ servo_pins_[HEAD] = head;
+
+ AttachServos();
+ is_otto_resting_ = false;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- ATTACH & DETACH FUNCTIONS ----------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::AttachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Attach(servo_pins_[i]);
+ }
+ }
+}
+
+void Otto::DetachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Detach();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- OSCILLATORS TRIMS ------------------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::SetTrims(int right_pitch, int right_roll, int left_pitch, int left_roll, int body,
+ int head) {
+ servo_trim_[RIGHT_PITCH] = right_pitch;
+ servo_trim_[RIGHT_ROLL] = right_roll;
+ servo_trim_[LEFT_PITCH] = left_pitch;
+ servo_trim_[LEFT_ROLL] = left_roll;
+ servo_trim_[BODY] = body;
+ servo_trim_[HEAD] = head;
+
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetTrim(servo_trim_[i]);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- BASIC MOTION FUNCTIONS -------------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::MoveServos(int time, int servo_target[]) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ final_time_ = millis() + time;
+ if (time > 10) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0);
+ }
+ }
+
+ for (int iteration = 1; millis() < final_time_; iteration++) {
+ partial_time_ = millis() + 10;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ } else {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(time));
+ }
+
+ // final adjustment to the target.
+ bool f = true;
+ int adjustment_count = 0;
+ while (f && adjustment_count < 10) {
+ f = false;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) {
+ f = true;
+ break;
+ }
+ }
+ if (f) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ adjustment_count++;
+ }
+ };
+}
+
+void Otto::MoveSingle(int position, int servo_number) {
+ if (position > 180)
+ position = 90;
+ if (position < 0)
+ position = 90;
+
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) {
+ servo_[servo_number].SetPosition(position);
+ }
+}
+
+void Otto::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle = 1) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetO(offset[i]);
+ servo_[i].SetA(amplitude[i]);
+ servo_[i].SetT(period);
+ servo_[i].SetPh(phase_diff[i]);
+ }
+ }
+
+ double ref = millis();
+ double end_time = period * cycle + ref;
+
+ while (millis() < end_time) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Refresh();
+ }
+ }
+ vTaskDelay(5);
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps = 1.0) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ int cycles = (int)steps;
+
+ //-- Execute complete cycles
+ if (cycles >= 1)
+ for (int i = 0; i < cycles; i++)
+ OscillateServos(amplitude, offset, period, phase_diff);
+
+ //-- Execute the final not complete cycle
+ OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles);
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+///////////////////////////////////////////////////////////////////
+//-- HOME = Otto at rest position -------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::Home(bool hands_down) {
+ if (is_otto_resting_ == false) { // Go to rest position only if necessary
+ MoveServos(1000, servo_initial_);
+ is_otto_resting_ = true;
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(1000));
+}
+
+bool Otto::GetRestState() {
+ return is_otto_resting_;
+}
+
+void Otto::SetRestState(bool state) {
+ is_otto_resting_ = state;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- PREDETERMINED MOTION SEQUENCES -----------------------------//
+///////////////////////////////////////////////////////////////////
+
+//---------------------------------------------------------
+//-- 缁熶竴鎵嬮儴鍔ㄤ綔鍑芥暟
+//-- Parameters:
+//-- action: 鍔ㄤ綔绫诲瀷 1=涓惧乏鎵�, 2=涓惧彸鎵�, 3=涓惧弻鎵�, 4=鏀惧乏鎵�, 5=鏀惧彸鎵�, 6=鏀惧弻鎵�,
+//-- 7=鎸ュ乏鎵�, 8=鎸ュ彸鎵�, 9=鎸ュ弻鎵�, 10=鎷嶆墦宸︽墜, 11=鎷嶆墦鍙虫墜, 12=鎷嶆墦鍙屾墜
+//-- times: 閲嶅娆℃暟
+//-- amount: 鍔ㄤ綔骞呭害 (10-50)
+//-- period: 鍔ㄤ綔鏃堕棿
+//---------------------------------------------------------
+void Otto::HandAction(int action, int times, int amount, int period) {
+ // 闄愬埗鍙傛暟鑼冨洿
+ times = 2 * std::max(3, std::min(100, times));
+ amount = std::max(10, std::min(50, amount));
+ period = std::max(100, std::min(1000, period));
+
+ int current_positions[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ current_positions[i] = (servo_pins_[i] != -1) ? servo_[i].GetPosition() : servo_initial_[i];
+ }
+
+ switch (action) {
+ case 1: // 涓惧乏鎵�
+ current_positions[LEFT_PITCH] = 180;
+ MoveServos(period, current_positions);
+ break;
+
+ case 2: // 涓惧彸鎵�
+ current_positions[RIGHT_PITCH] = 0;
+ MoveServos(period, current_positions);
+ break;
+
+ case 3: // 涓惧弻鎵�
+ current_positions[LEFT_PITCH] = 180;
+ current_positions[RIGHT_PITCH] = 0;
+ MoveServos(period, current_positions);
+ break;
+
+ case 4: // 鏀惧乏鎵�
+ case 5: // 鏀惧彸鎵�
+ case 6: // 鏀惧弻鎵�
+ // 鍥炲埌鍒濆浣嶇疆
+ memcpy(current_positions, servo_initial_, sizeof(current_positions));
+ MoveServos(period, current_positions);
+ break;
+
+ case 7: // 鎸ュ乏鎵�
+ current_positions[LEFT_PITCH] = 150;
+ MoveServos(period, current_positions);
+ for (int i = 0; i < times; i++) {
+ current_positions[LEFT_PITCH] = 150 + (i % 2 == 0 ? -30 : 30);
+ MoveServos(period / 10, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 10));
+ }
+ memcpy(current_positions, servo_initial_, sizeof(current_positions));
+ MoveServos(period, current_positions);
+ break;
+
+ case 8: // 鎸ュ彸鎵�
+ current_positions[RIGHT_PITCH] = 30;
+ MoveServos(period, current_positions);
+ for (int i = 0; i < times; i++) {
+ current_positions[RIGHT_PITCH] = 30 + (i % 2 == 0 ? 30 : -30);
+ MoveServos(period / 10, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 10));
+ }
+ memcpy(current_positions, servo_initial_, sizeof(current_positions));
+ MoveServos(period, current_positions);
+ break;
+
+ case 9: // 鎸ュ弻鎵�
+ current_positions[LEFT_PITCH] = 150;
+ current_positions[RIGHT_PITCH] = 30;
+ MoveServos(period, current_positions);
+ for (int i = 0; i < times; i++) {
+ current_positions[LEFT_PITCH] = 150 + (i % 2 == 0 ? -30 : 30);
+ current_positions[RIGHT_PITCH] = 30 + (i % 2 == 0 ? 30 : -30);
+ MoveServos(period / 10, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 10));
+ }
+ memcpy(current_positions, servo_initial_, sizeof(current_positions));
+ MoveServos(period, current_positions);
+ break;
+
+ case 10: // 鎷嶆墦宸︽墜
+ current_positions[LEFT_ROLL] = 20;
+ MoveServos(period, current_positions);
+ for (int i = 0; i < times; i++) {
+ current_positions[LEFT_ROLL] = 20 - amount;
+ MoveServos(period / 10, current_positions);
+ current_positions[LEFT_ROLL] = 20 + amount;
+ MoveServos(period / 10, current_positions);
+ }
+ current_positions[LEFT_ROLL] = 0;
+ MoveServos(period, current_positions);
+ break;
+
+ case 11: // 鎷嶆墦鍙虫墜
+ current_positions[RIGHT_ROLL] = 160;
+ MoveServos(period, current_positions);
+ for (int i = 0; i < times; i++) {
+ current_positions[RIGHT_ROLL] = 160 + amount;
+ MoveServos(period / 10, current_positions);
+ current_positions[RIGHT_ROLL] = 160 - amount;
+ MoveServos(period / 10, current_positions);
+ }
+ current_positions[RIGHT_ROLL] = 180;
+ MoveServos(period, current_positions);
+ break;
+
+ case 12: // 鎷嶆墦鍙屾墜
+ current_positions[LEFT_ROLL] = 20;
+ current_positions[RIGHT_ROLL] = 160;
+ MoveServos(period, current_positions);
+ for (int i = 0; i < times; i++) {
+ current_positions[LEFT_ROLL] = 20 - amount;
+ current_positions[RIGHT_ROLL] = 160 + amount;
+ MoveServos(period / 10, current_positions);
+ current_positions[LEFT_ROLL] = 20 + amount;
+ current_positions[RIGHT_ROLL] = 160 - amount;
+ MoveServos(period / 10, current_positions);
+ }
+ current_positions[LEFT_ROLL] = 0;
+ current_positions[RIGHT_ROLL] = 180;
+ MoveServos(period, current_positions);
+ break;
+ }
+}
+
+//---------------------------------------------------------
+//-- 缁熶竴韬綋鍔ㄤ綔鍑芥暟
+//-- Parameters:
+//-- action: 鍔ㄤ綔绫诲瀷 1=宸﹁浆, 2=鍙宠浆锛�3=鍥炰腑蹇�
+//-- times: 杞姩娆℃暟
+//-- amount: 鏃嬭浆瑙掑害 (0-90搴︼紝浠�90搴︿负涓績宸﹀彸鏃嬭浆)
+//-- period: 鍔ㄤ綔鏃堕棿
+//---------------------------------------------------------
+void Otto::BodyAction(int action, int times, int amount, int period) {
+ // 闄愬埗鍙傛暟鑼冨洿
+ times = std::max(1, std::min(10, times));
+ amount = std::max(0, std::min(90, amount));
+ period = std::max(500, std::min(3000, period));
+
+ int current_positions[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_positions[i] = servo_[i].GetPosition();
+ } else {
+ current_positions[i] = servo_initial_[i];
+ }
+ }
+
+ int body_center = servo_initial_[BODY];
+ int target_angle = body_center;
+
+ switch (action) {
+ case 1: // 宸﹁浆
+ target_angle = body_center + amount;
+ target_angle = std::min(180, target_angle);
+ break;
+ case 2: // 鍙宠浆
+ target_angle = body_center - amount;
+ target_angle = std::max(0, target_angle);
+ break;
+ case 3: // 鍥炰腑蹇�
+ target_angle = body_center;
+ break;
+ default:
+ return; // 鏃犳晥鍔ㄤ綔
+ }
+
+ current_positions[BODY] = target_angle;
+ MoveServos(period, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(100));
+}
+
+//---------------------------------------------------------
+//-- 缁熶竴澶撮儴鍔ㄤ綔鍑芥暟
+//-- Parameters:
+//-- action: 鍔ㄤ綔绫诲瀷 1=鎶ご, 2=浣庡ご, 3=鐐瑰ご, 4=鍥炰腑蹇�, 5=杩炵画鐐瑰ご
+//-- times: 閲嶅娆℃暟 (浠呭杩炵画鐐瑰ご鏈夋晥)
+//-- amount: 瑙掑害鍋忕Щ (1-15搴﹁寖鍥村唴)
+//-- period: 鍔ㄤ綔鏃堕棿
+//---------------------------------------------------------
+void Otto::HeadAction(int action, int times, int amount, int period) {
+ // 闄愬埗鍙傛暟鑼冨洿
+ times = std::max(1, std::min(10, times));
+ amount = std::max(1, std::min(15, abs(amount)));
+ period = std::max(300, std::min(3000, period));
+
+ int current_positions[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_positions[i] = servo_[i].GetPosition();
+ } else {
+ current_positions[i] = servo_initial_[i];
+ }
+ }
+
+ int head_center = 90; // 澶撮儴涓績浣嶇疆
+
+ switch (action) {
+ case 1: // 鎶ご
+ current_positions[HEAD] = head_center + amount; // 鎶ご鏄鍔犺搴�
+ MoveServos(period, current_positions);
+ break;
+
+ case 2: // 浣庡ご
+ current_positions[HEAD] = head_center - amount; // 浣庡ご鏄噺灏戣搴�
+ MoveServos(period, current_positions);
+ break;
+
+ case 3: // 鐐瑰ご (涓婁笅杩愬姩)
+ // 鍏堟姮澶�
+ current_positions[HEAD] = head_center + amount;
+ MoveServos(period / 3, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 6));
+
+ // 鍐嶄綆澶�
+ current_positions[HEAD] = head_center - amount;
+ MoveServos(period / 3, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 6));
+
+ // 鍥炲埌涓績
+ current_positions[HEAD] = head_center;
+ MoveServos(period / 3, current_positions);
+ break;
+
+ case 4: // 鍥炲埌涓績浣嶇疆
+ current_positions[HEAD] = head_center;
+ MoveServos(period, current_positions);
+ break;
+
+ case 5: // 杩炵画鐐瑰ご
+ for (int i = 0; i < times; i++) {
+ // 鎶ご
+ current_positions[HEAD] = head_center + amount;
+ MoveServos(period / 2, current_positions);
+
+ // 浣庡ご
+ current_positions[HEAD] = head_center - amount;
+ MoveServos(period / 2, current_positions);
+
+ vTaskDelay(pdMS_TO_TICKS(50)); // 鐭殏鍋滈】
+ }
+
+ // 鍥炲埌涓績
+ current_positions[HEAD] = head_center;
+ MoveServos(period / 2, current_positions);
+ break;
+
+ default:
+ // 鏃犳晥鍔ㄤ綔锛屽洖鍒颁腑蹇�
+ current_positions[HEAD] = head_center;
+ MoveServos(period, current_positions);
+ break;
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.h b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.h
new file mode 100644
index 0000000..bffc3ff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/movements.h
@@ -0,0 +1,89 @@
+#ifndef __MOVEMENTS_H__
+#define __MOVEMENTS_H__
+
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "oscillator.h"
+
+//-- Constants
+#define FORWARD 1
+#define BACKWARD -1
+#define LEFT 1
+#define RIGHT -1
+#define BOTH 0
+#define SMALL 5
+#define MEDIUM 15
+#define BIG 30
+
+// -- Servo delta limit default. degree / sec
+#define SERVO_LIMIT_DEFAULT 240
+
+// -- Servo indexes for easy access
+#define RIGHT_PITCH 0
+#define RIGHT_ROLL 1
+#define LEFT_PITCH 2
+#define LEFT_ROLL 3
+#define BODY 4
+#define HEAD 5
+#define SERVO_COUNT 6
+
+class Otto {
+public:
+ Otto();
+ ~Otto();
+
+ //-- Otto initialization
+ void Init(int right_pitch, int right_roll, int left_pitch, int left_roll, int body, int head);
+ //-- Attach & detach functions
+ void AttachServos();
+ void DetachServos();
+
+ //-- Oscillator Trims
+ void SetTrims(int right_pitch, int right_roll, int left_pitch, int left_roll, int body,
+ int head);
+
+ //-- Predetermined Motion Functions
+ void MoveServos(int time, int servo_target[]);
+ void MoveSingle(int position, int servo_number);
+ void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle);
+
+ //-- HOME = Otto at rest position
+ void Home(bool hands_down = true);
+ bool GetRestState();
+ void SetRestState(bool state);
+
+ // -- 鎵嬮儴鍔ㄤ綔
+ void HandAction(int action, int times = 1, int amount = 30, int period = 1000);
+ // action: 1=涓惧乏鎵�, 2=涓惧彸鎵�, 3=涓惧弻鎵�, 4=鏀惧乏鎵�, 5=鏀惧彸鎵�, 6=鏀惧弻鎵�, 7=鎸ュ乏鎵�, 8=鎸ュ彸鎵�,
+ // 9=鎸ュ弻鎵�, 10=鎷嶆墦宸︽墜, 11=鎷嶆墦鍙虫墜, 12=鎷嶆墦鍙屾墜
+
+ //-- 韬綋鍔ㄤ綔
+ void BodyAction(int action, int times = 1, int amount = 30, int period = 1000);
+ // action: 1=宸﹁浆, 2=鍙宠浆
+
+ //-- 澶撮儴鍔ㄤ綔
+ void HeadAction(int action, int times = 1, int amount = 10, int period = 500);
+ // action: 1=鎶ご, 2=浣庡ご, 3=鐐瑰ご, 4=鍥炰腑蹇�, 5=杩炵画鐐瑰ご
+
+private:
+ Oscillator servo_[SERVO_COUNT];
+
+ int servo_pins_[SERVO_COUNT];
+ int servo_trim_[SERVO_COUNT];
+ int servo_initial_[SERVO_COUNT] = {180, 180, 0, 0, 90, 90};
+
+ unsigned long final_time_;
+ unsigned long partial_time_;
+ float increment_[SERVO_COUNT];
+
+ bool is_otto_resting_;
+
+ void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps);
+};
+
+#endif // __MOVEMENTS_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.cc b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.cc
new file mode 100644
index 0000000..0cfc652
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.cc
@@ -0,0 +1,149 @@
+#include "oscillator.h"
+
+#include <driver/ledc.h>
+#include <esp_timer.h>
+
+#include <algorithm>
+#include <cmath>
+
+extern unsigned long IRAM_ATTR millis();
+
+Oscillator::Oscillator(int trim) {
+ trim_ = trim;
+ diff_limit_ = 0;
+ is_attached_ = false;
+
+ sampling_period_ = 30;
+ period_ = 2000;
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+
+ amplitude_ = 45;
+ phase_ = 0;
+ phase0_ = 0;
+ offset_ = 0;
+ stop_ = false;
+ rev_ = false;
+
+ pos_ = 90;
+ previous_millis_ = 0;
+}
+
+Oscillator::~Oscillator() {
+ Detach();
+}
+
+uint32_t Oscillator::AngleToCompare(int angle) {
+ return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) /
+ (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) +
+ SERVO_MIN_PULSEWIDTH_US;
+}
+
+bool Oscillator::NextSample() {
+ current_millis_ = millis();
+
+ if (current_millis_ - previous_millis_ > sampling_period_) {
+ previous_millis_ = current_millis_;
+ return true;
+ }
+
+ return false;
+}
+
+void Oscillator::Attach(int pin, bool rev) {
+ if (is_attached_) {
+ Detach();
+ }
+
+ pin_ = pin;
+ rev_ = rev;
+
+ ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE,
+ .duty_resolution = LEDC_TIMER_13_BIT,
+ .timer_num = LEDC_TIMER_1,
+ .freq_hz = 50,
+ .clk_cfg = LEDC_AUTO_CLK};
+ ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
+
+ static int last_channel = 0;
+ last_channel = (last_channel + 1) % 7 + 1;
+ ledc_channel_ = (ledc_channel_t)last_channel;
+
+ ledc_channel_config_t ledc_channel = {.gpio_num = pin_,
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .channel = ledc_channel_,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = LEDC_TIMER_1,
+ .duty = 0,
+ .hpoint = 0};
+ ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
+
+ ledc_speed_mode_ = LEDC_LOW_SPEED_MODE;
+
+ // pos_ = 90;
+ // Write(pos_);
+ previous_servo_command_millis_ = millis();
+
+ is_attached_ = true;
+}
+
+void Oscillator::Detach() {
+ if (!is_attached_)
+ return;
+
+ ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0));
+
+ is_attached_ = false;
+}
+
+void Oscillator::SetT(unsigned int T) {
+ period_ = T;
+
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+}
+
+void Oscillator::SetPosition(int position) {
+ Write(position);
+}
+
+void Oscillator::Refresh() {
+ if (NextSample()) {
+ if (!stop_) {
+ int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_);
+ if (rev_)
+ pos = -pos;
+ Write(pos + 90);
+ }
+
+ phase_ = phase_ + inc_;
+ }
+}
+
+void Oscillator::Write(int position) {
+ if (!is_attached_)
+ return;
+
+ long currentMillis = millis();
+ if (diff_limit_ > 0) {
+ int limit = std::max(
+ 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000);
+ if (abs(position - pos_) > limit) {
+ pos_ += position < pos_ ? -limit : limit;
+ } else {
+ pos_ = position;
+ }
+ } else {
+ pos_ = position;
+ }
+ previous_servo_command_millis_ = currentMillis;
+
+ int angle = pos_ + trim_;
+
+ angle = std::min(std::max(angle, 0), 180);
+
+ uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0);
+
+ ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty));
+ ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_));
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.h b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.h
new file mode 100644
index 0000000..d9e79f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/oscillator.h
@@ -0,0 +1,83 @@
+#ifndef __OSCILLATOR_H__
+#define __OSCILLATOR_H__
+
+#include "driver/ledc.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#define M_PI 3.14159265358979323846
+
+#ifndef DEG2RAD
+#define DEG2RAD(g) ((g) * M_PI) / 180
+#endif
+
+#define SERVO_MIN_PULSEWIDTH_US 500 // 鏈�灏忚剦瀹斤紙寰锛�
+#define SERVO_MAX_PULSEWIDTH_US 2500 // 鏈�澶ц剦瀹斤紙寰锛�
+#define SERVO_MIN_DEGREE -90 // 鏈�灏忚搴�
+#define SERVO_MAX_DEGREE 90 // 鏈�澶ц搴�
+#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick
+#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms
+
+class Oscillator {
+public:
+ Oscillator(int trim = 0);
+ ~Oscillator();
+ void Attach(int pin, bool rev = false);
+ void Detach();
+
+ void SetA(unsigned int amplitude) { amplitude_ = amplitude; };
+ void SetO(int offset) { offset_ = offset; };
+ void SetPh(double Ph) { phase0_ = Ph; };
+ void SetT(unsigned int period);
+ void SetTrim(int trim) { trim_ = trim; };
+ void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; };
+ void DisableLimiter() { diff_limit_ = 0; };
+ int GetTrim() { return trim_; };
+ void SetPosition(int position);
+ void Stop() { stop_ = true; };
+ void Play() { stop_ = false; };
+ void Reset() { phase_ = 0; };
+ void Refresh();
+ int GetPosition() { return pos_; }
+
+private:
+ bool NextSample();
+ void Write(int position);
+ uint32_t AngleToCompare(int angle);
+
+private:
+ bool is_attached_;
+
+ //-- Oscillators parameters
+ unsigned int amplitude_; //-- Amplitude (degrees)
+ int offset_; //-- Offset (degrees)
+ unsigned int period_; //-- Period (miliseconds)
+ double phase0_; //-- Phase (radians)
+
+ //-- Internal variables
+ int pos_; //-- Current servo pos
+ int pin_; //-- Pin where the servo is connected
+ int trim_; //-- Calibration offset
+ double phase_; //-- Current phase
+ double inc_; //-- Increment of phase
+ double number_samples_; //-- Number of samples
+ unsigned int sampling_period_; //-- sampling period (ms)
+
+ long previous_millis_;
+ long current_millis_;
+
+ //-- Oscillation mode. If true, the servo is stopped
+ bool stop_;
+
+ //-- Reverse mode
+ bool rev_;
+
+ int diff_limit_;
+ long previous_servo_command_millis_;
+
+ ledc_channel_t ledc_channel_;
+ ledc_mode_t ledc_speed_mode_;
+};
+
+#endif // __OSCILLATOR_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/otto_icon_font.c b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/otto_icon_font.c
new file mode 100644
index 0000000..dc05cce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/otto_icon_font.c
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Size: 20 px
+ * Bpp: 1
+ * Opts: --bpp 1 --size 20 --no-compress --stride 1 --align 1 --font fontawesome-webfont.ttf --range 61744,61633,61480 --format lvgl -o OTTO_ICON_FONT.c
+ ******************************************************************************/
+
+#ifdef __has_include
+#if __has_include("lvgl.h")
+#ifndef LV_LVGL_H_INCLUDE_SIMPLE
+#define LV_LVGL_H_INCLUDE_SIMPLE
+#endif
+#endif
+#endif
+
+#ifdef LV_LVGL_H_INCLUDE_SIMPLE
+#include "lvgl.h"
+#else
+#include "lvgl/lvgl.h"
+#endif
+
+#ifndef ENABLE_OTTO_ICON_FONT
+#define ENABLE_OTTO_ICON_FONT 1
+#endif
+
+#if ENABLE_OTTO_ICON_FONT
+
+/*-----------------
+ * BITMAPS
+ *----------------*/
+
+/*Store the image of the glyphs*/
+static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
+ /* U+F028 "飥�" */
+ 0x0, 0x6, 0x0, 0x10, 0xe0, 0x6, 0x6, 0x3, 0xc6, 0x60, 0xf8, 0x65, 0xff, 0x24, 0xff, 0xe6, 0x4f, 0xfc, 0x49, 0xff, 0x89, 0x3f, 0xf3, 0x27, 0xfe, 0x49, 0x87, 0xc3, 0x20, 0x78, 0xcc, 0x3, 0x3, 0x0,
+ 0x21, 0xc0, 0x0, 0x30,
+
+ /* U+F0C1 "飪�" */
+ 0x1e, 0x0, 0xf, 0xc0, 0x7, 0x38, 0x3, 0x87, 0x0, 0xc0, 0xc0, 0x30, 0xb0, 0x7, 0x3c, 0x0, 0xef, 0x0, 0x1f, 0xfe, 0x3, 0xff, 0xc0, 0x7, 0x38, 0x3, 0xe7, 0x0, 0xd0, 0xc0, 0x30, 0x30, 0x6, 0x1c, 0x0,
+ 0xce, 0x0, 0x1f, 0x0, 0x3, 0x80,
+
+ /* U+F130 "飫�" */
+ 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, 0xf, 0xe0, 0x7f, 0x13, 0xf9, 0x9f, 0xcc, 0xfe, 0x67, 0xf3, 0xbf, 0xb4, 0x73, 0x18, 0x30, 0x7f, 0x0, 0x60, 0x2, 0x1, 0xff, 0x0};
+
+/*---------------------
+ * GLYPH DESCRIPTION
+ *--------------------*/
+
+static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
+ {.bitmap_index = 0, .adv_w = 297, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -1},
+ {.bitmap_index = 38, .adv_w = 297, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -1},
+ {.bitmap_index = 79, .adv_w = 206, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -1}};
+
+/*---------------------
+ * CHARACTER MAPPING
+ *--------------------*/
+
+static const uint16_t unicode_list_0[] = {0x0, 0x99, 0x108};
+
+/*Collect the unicode lists and glyph_id offsets*/
+static const lv_font_fmt_txt_cmap_t cmaps[] = {
+ {.range_start = 61480, .range_length = 265, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 3, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY}};
+
+/*--------------------
+ * ALL CUSTOM DATA
+ *--------------------*/
+
+#if LVGL_VERSION_MAJOR == 8
+/*Store all the custom data of the font*/
+static lv_font_fmt_txt_glyph_cache_t cache;
+#endif
+
+#if LVGL_VERSION_MAJOR >= 8
+static const lv_font_fmt_txt_dsc_t otto_icon_font_dsc = {
+#else
+static lv_font_fmt_txt_dsc_t otto_icon_font_dsc = {
+#endif
+ .glyph_bitmap = glyph_bitmap,
+ .glyph_dsc = glyph_dsc,
+ .cmaps = cmaps,
+ .kern_dsc = NULL,
+ .kern_scale = 0,
+ .cmap_num = 1,
+ .bpp = 1,
+ .kern_classes = 0,
+ .bitmap_format = 0,
+#if LVGL_VERSION_MAJOR == 8
+ .cache = &cache
+#endif
+
+};
+
+/*-----------------
+ * PUBLIC FONT
+ *----------------*/
+
+/*Initialize a public general font descriptor*/
+#if LVGL_VERSION_MAJOR >= 8
+const lv_font_t OTTO_ICON_FONT = {
+#else
+lv_font_t OTTO_ICON_FONT = {
+#endif
+ .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
+ .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
+ .line_height = 18, /*The maximum line height required by the font*/
+ .base_line = 1, /*Baseline measured from the bottom of the line*/
+#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
+ .subpx = LV_FONT_SUBPX_NONE,
+#endif
+#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
+ .underline_position = 0,
+ .underline_thickness = 0,
+#endif
+ .static_bitmap = 0,
+ .dsc = &otto_icon_font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
+#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
+ .fallback = NULL,
+#endif
+ .user_data = NULL,
+};
+
+#endif /*#if ENABLE_OTTO_ICON_FONT*/
diff --git a/xiaozhi-esp32-2.2.6/main/boards/electron-bot/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/power_manager.h
new file mode 100644
index 0000000..13d8ff3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/electron-bot/power_manager.h
@@ -0,0 +1,128 @@
+#ifndef __POWER_MANAGER_H__
+#define __POWER_MANAGER_H__
+
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+
+class PowerManager {
+private:
+ // 鐢垫睜鐢甸噺鍖洪棿-鍒嗗帇鐢甸樆涓�2涓�100k
+ static constexpr struct {
+ uint16_t adc;
+ uint8_t level;
+ } BATTERY_LEVELS[] = {{2150, 0}, {2450, 100}};
+ static constexpr size_t BATTERY_LEVELS_COUNT = 2;
+ static constexpr size_t ADC_VALUES_COUNT = 10;
+
+ esp_timer_handle_t timer_handle_ = nullptr;
+ gpio_num_t charging_pin_;
+ adc_unit_t adc_unit_;
+ adc_channel_t adc_channel_;
+ uint16_t adc_values_[ADC_VALUES_COUNT];
+ size_t adc_values_index_ = 0;
+ size_t adc_values_count_ = 0;
+ uint8_t battery_level_ = 100;
+ bool is_charging_ = false;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ is_charging_ = gpio_get_level(charging_pin_) == 0;
+ ReadBatteryAdcData();
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value));
+
+ adc_values_[adc_values_index_] = adc_value;
+ adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT;
+ if (adc_values_count_ < ADC_VALUES_COUNT) {
+ adc_values_count_++;
+ }
+
+ uint32_t average_adc = 0;
+ for (size_t i = 0; i < adc_values_count_; i++) {
+ average_adc += adc_values_[i];
+ }
+ average_adc /= adc_values_count_;
+
+ CalculateBatteryLevel(average_adc);
+
+ // ESP_LOGI("PowerManager", "ADC鍊�: %d 骞冲潎鍊�: %ld 鐢甸噺: %u%%", adc_value, average_adc,
+ // battery_level_);
+ }
+
+ void CalculateBatteryLevel(uint32_t average_adc) {
+ if (average_adc <= BATTERY_LEVELS[0].adc) {
+ battery_level_ = 0;
+ } else if (average_adc >= BATTERY_LEVELS[BATTERY_LEVELS_COUNT - 1].adc) {
+ battery_level_ = 100;
+ } else {
+ float ratio = static_cast<float>(average_adc - BATTERY_LEVELS[0].adc) /
+ (BATTERY_LEVELS[1].adc - BATTERY_LEVELS[0].adc);
+ battery_level_ = ratio * 100;
+ }
+ }
+
+public:
+ PowerManager(gpio_num_t charging_pin, adc_unit_t adc_unit = ADC_UNIT_2,
+ adc_channel_t adc_channel = ADC_CHANNEL_3)
+ : charging_pin_(charging_pin), adc_unit_(adc_unit), adc_channel_(adc_channel) {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ esp_timer_create_args_t timer_args = {
+ .callback =
+ [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1绉�
+
+ InitializeAdc();
+ }
+
+ void InitializeAdc() {
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = adc_unit_,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() { return is_charging_; }
+
+ uint8_t GetBatteryLevel() { return battery_level_; }
+};
+#endif // __POWER_MANAGER_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/README.md
new file mode 100644
index 0000000..103f732
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/README.md
@@ -0,0 +1,132 @@
+# ESP-BOX-3
+
+## 绠�浠�
+
+<div align="center">
+ <a href="https://github.com/espressif/esp-box"><b> ESP-BOX GitHub </b></a>
+</div>
+
+ESP-BOX-3 鏄箰閼畼鏂瑰紑鍙戠殑 AIoT 寮�鍙戝浠讹紝鎼浇 ESP32-S3-WROOM-1 妯$粍锛岄厤澶� 2.4 鑻卞 320x240 ILI9341 鏄剧ず灞忥紝鍙岄害鍏嬮闃靛垪锛屾敮鎸佺绾胯闊冲敜閱掍笌璁惧绔洖澹版秷闄わ紙AEC锛夊姛鑳姐��
+
+## 纭欢鐗规��
+
+- **涓绘帶**: ESP32-S3-WROOM-1 (16MB Flash, 8MB PSRAM)
+- **鏄剧ず灞�**: 2.4 鑻卞 IPS LCD (320x240, ILI9341)
+- **闊抽**: ES8311 闊抽 Codec + ES7210 鍙岄害 ADC
+- **闊抽鍔熻兘**: 鏀寔璁惧绔� AEC (鍥炲0娑堥櫎)
+- **鎸夐敭**: Boot 鎸夐敭 (鍗曞嚮/鍙屽嚮鍔熻兘)
+- **鍏朵粬**: USB-C 渚涚數涓庨�氫俊
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig 骞堕厤缃�**
+
+```bash
+idf.py menuconfig
+```
+
+鍒嗗埆閰嶇疆濡備笅閫夐」锛�
+
+### 鍩烘湰閰嶇疆
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `ESP BOX 3`
+
+### UI椋庢牸閫夋嫨
+
+ESP-BOX-3 鏀寔澶氱涓嶅悓鐨� UI 鏄剧ず椋庢牸锛岄�氳繃 menuconfig 閰嶇疆閫夋嫨锛�
+
+- `Xiaozhi Assistant` 鈫� `Select display style` 鈫� 閫夋嫨鏄剧ず椋庢牸
+
+#### 鍙�夐鏍�
+
+##### 琛ㄦ儏鍔ㄧ敾椋庢牸 (Emote animation style) - 鎺ㄨ崘
+- **閰嶇疆閫夐」**: `USE_EMOTE_MESSAGE_STYLE`
+- **鐗圭偣**: 浣跨敤鑷畾涔夌殑 `EmoteDisplay` 琛ㄦ儏鏄剧ず绯荤粺
+- **鍔熻兘**: 鏀寔涓板瘜鐨勮〃鎯呭姩鐢汇�佺溂鐫涘姩鐢汇�佺姸鎬佸浘鏍囨樉绀�
+- **閫傜敤**: 鏅鸿兘鍔╂墜鍦烘櫙锛屾彁渚涙洿鐢熷姩鐨勪汉鏈轰氦浜掍綋楠�
+- **绫�**: `emote::EmoteDisplay`
+
+**鈿狅笍 閲嶈**: 閫夋嫨姝ら鏍奸渶瑕侀澶栭厤缃嚜瀹氫箟璧勬簮鏂囦欢锛�
+1. `Xiaozhi Assistant` 鈫� `Flash Assets` 鈫� 閫夋嫨 `Flash Custom Assets`
+2. `Xiaozhi Assistant` 鈫� `Custom Assets File` 鈫� 濉叆璧勬簮鏂囦欢鍦板潃锛�
+ ```
+ https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-esp-box-3.bin
+ ```
+
+##### 榛樿娑堟伅椋庢牸 (Enable default message style)
+- **閰嶇疆閫夐」**: `USE_DEFAULT_MESSAGE_STYLE` (榛樿)
+- **鐗圭偣**: 浣跨敤鏍囧噯鐨勬秷鎭樉绀虹晫闈�
+- **鍔熻兘**: 浼犵粺鐨勬枃鏈拰鍥炬爣鏄剧ず鐣岄潰
+- **閫傜敤**: 鏍囧噯鐨勫璇濆満鏅�
+- **绫�**: `SpiLcdDisplay`
+
+##### 寰俊娑堟伅椋庢牸 (Enable WeChat Message Style)
+- **閰嶇疆閫夐」**: `USE_WECHAT_MESSAGE_STYLE`
+- **鐗圭偣**: 浠垮井淇¤亰澶╃晫闈㈤鏍�
+- **鍔熻兘**: 绫讳技寰俊鐨勬秷鎭皵娉℃樉绀�
+- **閫傜敤**: 鍠滄寰俊椋庢牸鐨勭敤鎴�
+- **绫�**: `SpiLcdDisplay`
+
+### 闊抽鍔熻兘閰嶇疆
+
+#### 璁惧绔洖澹版秷闄� (AEC)
+- `Xiaozhi Assistant` 鈫� `Enable Device-Side AEC` 鈫� 鍚敤
+
+ESP-BOX-3 纭欢鏀寔璁惧绔� AEC 鍔熻兘锛屽彲鏈夋晥娑堥櫎鎵0鍣ㄦ挱鏀惧0闊冲楹﹀厠椋庣殑骞叉壈锛屾彁鍗囪闊宠瘑鍒噯纭巼銆�
+
+**杩愯鏃跺垏鎹�**: 鍙屽嚮 Boot 鎸夐敭鍙湪杩愯鏃跺紑鍚�/鍏抽棴 AEC 鍔熻兘銆�
+
+> **璇存槑**: 璁惧绔� AEC 闇�瑕佸共鍑�鐨勬壃澹板櫒杈撳嚭鍙傝�冭矾寰勫拰鑹ソ鐨勯害鍏嬮涓庢壃澹板櫒鐗╃悊闅旂鎵嶈兘姝e父宸ヤ綔銆侲SP-BOX-3 纭欢宸插仛浼樺寲璁捐銆�
+
+### 鍞ら啋璇嶉厤缃�
+
+ESP-BOX-3 鏀寔澶氱鍞ら啋璇嶅疄鐜版柟寮忥細
+
+- `Xiaozhi Assistant` 鈫� `Wake Word Implementation Type` 鈫� 閫夋嫨鍞ら啋璇嶇被鍨�
+
+鎺ㄨ崘閫夋嫨锛�
+- **Wakenet model with AFE** (`USE_AFE_WAKE_WORD`) - 鏀寔 AEC 鐨勫敜閱掕瘝妫�娴�
+
+鎸� `S` 淇濆瓨锛屾寜 `Q` 閫�鍑恒��
+
+**缂栬瘧**
+
+```bash
+idf.py build
+```
+
+**鐑у綍**
+
+灏� ESP-BOX-3 杩炴帴鑷崇數鑴戯紝骞惰繍琛岋細
+
+```bash
+idf.py flash
+```
+
+## 鎸夐敭璇存槑
+
+### Boot 鎸夐敭鍔熻兘
+
+#### 鍗曞嚮
+- **閰嶇綉鐘舵��**: 杩涘叆 WiFi 閰嶇疆妯″紡
+- **绌洪棽鐘舵��**: 寮�濮嬪璇�
+- **瀵硅瘽涓�**: 鎵撴柇鎴栧仠姝㈠綋鍓嶅璇�
+
+#### 鍙屽嚮 (闇�鍚敤璁惧绔� AEC)
+- **绌洪棽鐘舵��**: 鍒囨崲 AEC 寮�鍚�/鍏抽棴
+
+## 甯歌闂
+
+### 1. 涓轰粈涔堥渶瑕佽澶囩 AEC锛�
+璁惧绔� AEC 鍙互鍦ㄦ湰鍦板疄鏃舵秷闄ゆ壃澹板櫒鎾斁澹伴煶瀵归害鍏嬮鐨勫共鎵帮紝鍦ㄦ挱鏀鹃煶涔愭垨 TTS 鍥炲鏃朵粛鑳藉噯纭瘑鍒闊虫寚浠ゃ��
+
+### 2. 琛ㄦ儏鍔ㄧ敾椋庢牸鏃犳硶鏄剧ず锛�
+璇风‘淇濆凡缁忛厤缃簡姝g‘鐨勮嚜瀹氫箟璧勬簮鏂囦欢鍦板潃锛屽苟涓旇澶囪兘澶熻闂 URL 涓嬭浇璧勬簮銆�
+
+### 3. 濡備綍鎭㈠鍑哄巶璁剧疆锛�
+闀挎寜 Boot 鎸夐敭 3 绉掍互涓婏紝璁惧浼氭竻闄ゆ墍鏈夐厤缃苟閲嶅惎銆�
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.h
new file mode 100644
index 0000000..f045304
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.h
@@ -0,0 +1,41 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_47
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.json
new file mode 100644
index 0000000..67ead9b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-box-3",
+ "sdkconfig_append": [
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/esp_box3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/esp_box3_board.cc
new file mode 100644
index 0000000..a5fa19a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-3/esp_box3_board.cc
@@ -0,0 +1,175 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/display.h"
+#include "display/emote_display.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_ili9341.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "EspBox3Board"
+
+// Init ili9341 by custom cmd
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
+ {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
+ {0xC5, (uint8_t []){0xD0}, 1, 0},
+ {0xC1, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
+ {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
+
+ {0xB1, (uint8_t []){00, 0x1B}, 2, 0},
+ {0x36, (uint8_t []){0x08}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0xB7, (uint8_t []){0x06}, 1, 0},
+
+ {0x11, (uint8_t []){0}, 0x80, 0},
+ {0x29, (uint8_t []){0}, 0x80, 0},
+
+ {0, (uint8_t []){0}, 0xff, 0},
+};
+
+class EspBox3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_6;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_7;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeIli9341Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_5;
+ io_config.dc_gpio_num = GPIO_NUM_4;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_48;
+ panel_config.flags.reset_active_high = 1,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+
+#if CONFIG_USE_EMOTE_MESSAGE_STYLE
+ display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+#else
+ 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);
+#endif
+ }
+
+public:
+ EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeIli9341Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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(EspBox3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.cc
new file mode 100644
index 0000000..2d7b834
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.cc
@@ -0,0 +1,274 @@
+#include "box_audio_codec_lite.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+#include <cstring>
+
+static const char TAG[] = "BoxAudioCodecLite";
+
+BoxAudioCodecLite::BoxAudioCodecLite(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, bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ if (input_reference) {
+ ref_buffer_.resize(960 * 2);
+ }
+ input_channels_ = 2 + input_reference_; // 杈撳叆閫氶亾鏁�
+ input_sample_rate_ = input_sample_rate;
+ output_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_t)1,
+ .addr = ES8156_CODEC_DEFAULT_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);
+
+ es8156_codec_cfg_t cfg = {};
+ cfg.ctrl_if = out_ctrl_if_;
+ cfg.gpio_if = gpio_if_;
+ cfg.pa_pin = pa_pin;
+ cfg.hw_gain.pa_voltage = 5.0;
+ cfg.hw_gain.codec_dac_voltage = 3.3;
+ out_codec_if_ = es8156_codec_new(&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 = ES7243E_CODEC_DEFAULT_ADDR;
+ in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
+ assert(in_ctrl_if_ != NULL);
+
+ es7243e_codec_cfg_t es7243_cfg = {};
+ es7243_cfg.ctrl_if = in_ctrl_if_;
+ in_codec_if_ = es7243e_codec_new(&es7243_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");
+}
+
+BoxAudioCodecLite::~BoxAudioCodecLite() {
+ 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 BoxAudioCodecLite::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 = I2S_STD_PHILIP_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
+ .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 BoxAudioCodecLite::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void BoxAudioCodecLite::EnableInput(bool enable) {
+ if (enable == input_enabled_) {
+ return;
+ }
+ if (enable) {
+ esp_codec_dev_sample_info_t fs = {
+ .bits_per_sample = 16,
+ .channel = (uint8_t)(input_channels_ - input_reference_),
+ .channel_mask = 0,
+ .sample_rate = (uint32_t)input_sample_rate_,
+ .mclk_multiple = 0,
+ };
+ for (int i = 0;i < fs.channel; i++) {
+ fs.channel_mask |= ESP_CODEC_DEV_MAKE_CHANNEL_MASK(i);
+ }
+ ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
+ // 楹﹀厠椋庡鐩婅В鍐虫敹闊冲お灏忕殑闂
+ ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+}
+
+void BoxAudioCodecLite::EnableOutput(bool enable) {
+ 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 BoxAudioCodecLite::Read(int16_t* dest, int samples) {
+ if (input_enabled_) {
+ if (!input_reference_) {
+ ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
+ }
+ else {
+ int size = samples / input_channels_;
+ int channels = input_channels_ - input_reference_;
+ std::vector<int16_t> data(size * channels);
+ // read mic data
+ ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)data.data(), data.size() * sizeof(int16_t)));
+ int j = 0;
+ int i = 0;
+ while (i< samples) {
+ // mic data
+ for (int p = 0; p < channels; p++) {
+ dest[i++] = data[j++];
+ }
+ // ref data
+ dest[i++] = read_pos_ < write_pos_? ref_buffer_[read_pos_++] : 0;
+ }
+
+ if (read_pos_ == write_pos_) {
+ read_pos_ = write_pos_ = 0;
+ }
+ }
+ }
+ return samples;
+}
+
+int BoxAudioCodecLite::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)));
+ if (input_reference_) { // 鏉垮瓙涓嶆敮鎸佺‖浠跺洖閲囷紝閲囩敤缂撳瓨鎾斁缂撳啿鏉ュ疄鐜板洖澹版秷闄�
+ if (write_pos_ - read_pos_ + samples > ref_buffer_.size()) {
+ assert(ref_buffer_.size() >= samples);
+ // 鍐欐孩鍑猴紝鍙繚鐣欐渶杩戠殑鏁版嵁
+ read_pos_ = write_pos_ + samples - ref_buffer_.size();
+ }
+ if (read_pos_) {
+ if (write_pos_ != read_pos_) {
+ memmove(ref_buffer_.data(), ref_buffer_.data() + read_pos_, (write_pos_ - read_pos_) * sizeof(int16_t));
+ }
+ write_pos_ -= read_pos_;
+ read_pos_ = 0;
+ }
+ memcpy(&ref_buffer_[write_pos_], data, samples * sizeof(int16_t));
+ write_pos_ += samples;
+ }
+ }
+ return samples;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.h b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.h
new file mode 100644
index 0000000..50fd361
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/box_audio_codec_lite.h
@@ -0,0 +1,41 @@
+#ifndef _BOX_AUDIO_CODEC_LITE_H
+#define _BOX_AUDIO_CODEC_LITE_H
+
+#include "audio_codec.h"
+
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+
+class BoxAudioCodecLite : 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;
+ // ref buffer used for aec
+ std::vector<int16_t> ref_buffer_;
+ int read_pos_ = 0;
+ int write_pos_ = 0;
+
+ 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:
+ BoxAudioCodecLite(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, bool input_reference);
+ virtual ~BoxAudioCodecLite();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_LITE_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.h
new file mode 100644
index 0000000..ee345ed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.h
@@ -0,0 +1,39 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_INPUT_REFERENCE CONFIG_USE_AUDIO_PROCESSOR
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.json
new file mode 100644
index 0000000..870fabe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-box-lite",
+ "sdkconfig_append": [
+ "CONFIG_SOC_ADC_SUPPORTED=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/esp_box_lite_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/esp_box_lite_board.cc
new file mode 100644
index 0000000..05d88a1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box-lite/esp_box_lite_board.cc
@@ -0,0 +1,237 @@
+#include "wifi_board.h"
+#include "box_audio_codec_lite.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_ili9341.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <button_adc.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "EspBoxBoardLite"
+
+/* ADC Buttons */
+typedef enum {
+ BSP_ADC_BUTTON_PREV,
+ BSP_ADC_BUTTON_ENTER,
+ BSP_ADC_BUTTON_NEXT,
+ BSP_ADC_BUTTON_NUM
+} bsp_adc_button_t;
+
+// Init ili9341 by custom cmd
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
+ {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
+ {0xC5, (uint8_t []){0xD0}, 1, 0},
+ {0xC1, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
+ {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
+
+ {0xB1, (uint8_t []){00, 0x1B}, 2, 0},
+ {0x36, (uint8_t []){0x08}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0xB7, (uint8_t []){0x06}, 1, 0},
+
+ {0x11, (uint8_t []){0}, 0x80, 0},
+ {0x29, (uint8_t []){0}, 0x80, 0},
+
+ {0, (uint8_t []){0}, 0xff, 0},
+};
+
+class EspBoxBoardLite : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button* adc_button_[BSP_ADC_BUTTON_NUM];
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+ adc_oneshot_unit_handle_t bsp_adc_handle = NULL;
+#endif
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_6;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_7;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void ChangeVol(int val) {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + val;
+ if (volume > 100) {
+ volume = 100;
+ }
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void TogleState() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }
+
+ void InitializeButtons() {
+ /* Initialize ADC esp-box lite鐨勫墠涓変釜鎸夐挳閲囩敤鏄殑adc鎸夐挳锛岃�岄潪gpio */
+ button_adc_config_t adc_cfg = {};
+ adc_cfg.adc_channel = ADC_CHANNEL_0; // ADC1 channel 0 is GPIO1
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+ const adc_oneshot_unit_init_cfg_t init_config1 = {
+ .unit_id = ADC_UNIT_1,
+ };
+ adc_oneshot_new_unit(&init_config1, &bsp_adc_handle);
+ adc_cfg.adc_handle = &bsp_adc_handle;
+#endif
+ adc_cfg.button_index = BSP_ADC_BUTTON_PREV;
+ adc_cfg.min = 2310; // middle is 2410mV
+ adc_cfg.max = 2510;
+ adc_button_[0] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_ENTER;
+ adc_cfg.min = 1880; // middle is 1980mV
+ adc_cfg.max = 2080;
+ adc_button_[1] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_NEXT;
+ adc_cfg.min = 720; // middle is 820mV
+ adc_cfg.max = 920;
+
+ adc_button_[2] = new AdcButton(adc_cfg);
+
+ auto volume_up_button = adc_button_[BSP_ADC_BUTTON_NEXT];
+ volume_up_button->OnClick([this]() {ChangeVol(10);});
+ volume_up_button->OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ auto volume_down_button = adc_button_[BSP_ADC_BUTTON_PREV];
+ volume_down_button->OnClick([this]() {ChangeVol(-10);});
+ volume_down_button->OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ auto break_button = adc_button_[BSP_ADC_BUTTON_ENTER];
+ break_button->OnClick([this]() {TogleState();});
+ boot_button_.OnClick([this]() {TogleState();});
+ }
+
+ void InitializeIli9341Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_5;
+ io_config.dc_gpio_num = GPIO_NUM_4;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_48;
+ panel_config.flags.reset_active_high = 0,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ 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);
+ }
+
+public:
+ EspBoxBoardLite() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeIli9341Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ ~EspBoxBoardLite() {
+ for (int i =0; i<BSP_ADC_BUTTON_NUM; i++) {
+ if (adc_button_[i]) {
+ delete adc_button_[i];
+ adc_button_[i] = nullptr;
+ }
+ }
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodecLite audio_codec(
+ i2c_bus_,
+ 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_INPUT_REFERENCE);
+ 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(EspBoxBoardLite);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-box/config.h
new file mode 100644
index 0000000..52b75bc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box/config.h
@@ -0,0 +1,41 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-box/config.json
new file mode 100644
index 0000000..0ae7e20
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-box",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-box/esp_box_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-box/esp_box_board.cc
new file mode 100644
index 0000000..15fdc9c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-box/esp_box_board.cc
@@ -0,0 +1,174 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "display/emote_display.h"
+#include "esp_lcd_ili9341.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "EspBoxBoard"
+
+// Init ili9341 by custom cmd
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
+ {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
+ {0xC5, (uint8_t []){0xD0}, 1, 0},
+ {0xC1, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
+ {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
+
+ {0xB1, (uint8_t []){00, 0x1B}, 2, 0},
+ {0x36, (uint8_t []){0x08}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0xB7, (uint8_t []){0x06}, 1, 0},
+
+ {0x11, (uint8_t []){0}, 0x80, 0},
+ {0x29, (uint8_t []){0}, 0x80, 0},
+
+ {0, (uint8_t []){0}, 0xff, 0},
+};
+
+class EspBox3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_6;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_7;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeIli9341Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_5;
+ io_config.dc_gpio_num = GPIO_NUM_4;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_48;
+ panel_config.flags.reset_active_high = 0,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+
+#if CONFIG_USE_EMOTE_MESSAGE_STYLE
+ display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+#else
+ 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);
+#endif
+ }
+
+public:
+ EspBox3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeIli9341Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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(EspBox3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/README.md
new file mode 100644
index 0000000..8921f1f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/README.md
@@ -0,0 +1,51 @@
+# ESP-Hi
+
+## 绠�浠�
+
+<div align="center">
+ <a href="https://oshwhub.com/esp-college/esp-hi"><b> 绔嬪垱寮�婧愬钩鍙� </b></a>
+ |
+ <a href="https://www.bilibili.com/video/BV1BHJtz6E2S"><b> Bilibili </b></a>
+</div>
+
+ESP-Hi 鏄� ESP Friends 寮�婧愮殑涓�娆惧熀浜� ESP32C3 鐨勮秴**浣庢垚鏈�** AI 瀵硅瘽鏈哄櫒浜恒�侲SP-Hi 闆嗘垚浜嗕竴涓�0.96瀵哥殑褰╁睆锛岀敤浜庢樉绀鸿〃鎯咃紝**鏈哄櫒鐙楀凡瀹炵幇鏁板崄绉嶅姩浣�**銆傞�氳繃瀵� ESP32-C3 澶栬鐨勫厖鍒嗘寲鎺橈紝浠呴渶鏈�灏戠殑鏉跨骇纭欢鍗冲彲瀹炵幇鎷鹃煶鍜屽彂澹帮紝鍚屾浼樺寲浜嗚蒋浠讹紝闄嶄綆鍐呭瓨涓� Flash 鍗犵敤锛屽湪璧勬簮鍙楅檺鐨勬儏鍐典笅鍚屾椂瀹炵幇浜�**鍞ら啋璇嶆娴�**涓庡绉嶅璁鹃┍鍔ㄣ�傜‖浠惰鎯呯瓑鍙煡鐪媅绔嬪垱寮�婧愰」鐩甝(https://oshwhub.com/esp-college/esp-hi)銆�
+
+## WebUI
+
+ESP-Hi x 灏忔櫤鍐呯疆浜嗕竴涓帶鍒惰韩浣撹繍鍔ㄧ殑 WebUI锛岃灏嗘墜鏈轰笌 ESP-Hi 杩炴帴鍒板悓涓�涓� Wi-Fi 涓嬶紝鎵嬫満璁块棶 `http://esp-hi.local/` 浠ヤ娇鐢ㄣ��
+
+濡傞渶绂佺敤锛岃鍙栨秷 `ESP_HI_WEB_CONTROL_ENABLED`锛屽嵆鍙栨秷鍕鹃�� `Component config` 鈫� `Servo Dog Configuration` 鈫� `Web Control` 鈫� `Enable ESP-HI Web Control`銆�
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+鐢变簬 ESP-Hi 闇�瑕侀厤缃緝澶氱殑 sdkconfig 閫夐」锛屾帹鑽愪娇鐢ㄧ紪璇戣剼鏈紪璇戙��
+
+**缂栬瘧**
+
+```bash
+python ./scripts/release.py esp-hi
+```
+
+濡傞渶鎵嬪姩缂栬瘧锛岃鍙傝�� `esp-hi/config.json` 淇敼 menuconfig 瀵瑰簲閫夐」銆�
+
+**鐑у綍**
+
+```bash
+idf.py flash
+```
+
+
+> [!TIP]
+>
+> **鑸垫満鎺у埗浼氬崰鐢� ESP-Hi 鐨� USB Type-C 鎺ュ彛**锛屽鑷存棤娉曡繛鎺ョ數鑴戯紙鏃犳硶鐑у綍/鏌ョ湅杩愯鏃ュ織锛夈�傚閬囨鎯呭喌锛岃鎸変互涓嬫彁绀烘搷浣滐細
+>
+> **鐑у綍**
+>
+> 1. 鏂紑 ESP-Hi 鐨勭數婧愶紝鍙暀澶撮儴锛屼笉瑕佽繛鎺ヨ韩浣撱��
+> 2. 鎸変綇 ESP-Hi 鐨勬寜閽苟杩炴帴鐢佃剳銆�
+>
+> 姝ゆ椂锛孍SP-Hi (ESP32C3) 搴斿綋澶勪簬鐑у綍妯″紡锛屽彲浠ヤ娇鐢ㄧ數鑴戠儳褰曠▼搴忋�傜儳褰曞畬鎴愬悗锛屽彲鑳介渶瑕侀噸鏂版彃鎷旂數婧愩��
+>
+> **鏌ョ湅 log**
+>
+> 璇疯缃� `CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y`锛屽嵆 `Component config` 鈫� `ESP System Settings` 鈫� `Channel for console output` 閫夋嫨 `USB Serial/JTAG Controller`銆傝繖鍚屾椂浼氱鐢ㄨ埖鏈烘帶鍒跺姛鑳姐��
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.cc
new file mode 100644
index 0000000..24182d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.cc
@@ -0,0 +1,250 @@
+#include "adc_pdm_audio_codec.h"
+
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <driver/i2c.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+#include "driver/i2s_pdm.h"
+#include "soc/gpio_sig_map.h"
+#include "soc/io_mux_reg.h"
+#include "hal/rtc_io_hal.h"
+#include "hal/gpio_ll.h"
+#include "settings.h"
+#include "config.h"
+
+static const char TAG[] = "AdcPdmAudioCodec";
+
+#define BSP_I2S_GPIO_CFG(_dout) \
+ { \
+ .clk = GPIO_NUM_NC, \
+ .dout = _dout, \
+ .invert_flags = { \
+ .clk_inv = false, \
+ }, \
+ }
+
+/**
+ * @brief Mono Duplex I2S configuration structure
+ *
+ * This configuration is used by default in bsp_audio_init()
+ */
+#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
+ { \
+ .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
+ .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
+ .gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
+ }
+
+AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
+ uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
+
+ input_reference_ = false;
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ audio_codec_adc_cfg_t cfg = {};
+ cfg.handle = NULL;
+ cfg.continuous_cfg.max_store_buf_size = 1024 * 2;
+ cfg.continuous_cfg.conv_frame_size = 1024;
+ cfg.continuous_cfg.sample_freq_hz = (uint32_t)input_sample_rate;
+ cfg.continuous_cfg.conv_mode = ADC_CONV_SINGLE_UNIT_1;
+ cfg.continuous_cfg.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2;
+ cfg.continuous_cfg.pattern_num = 1;
+ cfg.continuous_cfg.cfg_mode = AUDIO_CODEC_ADC_CFG_MODE_SINGLE_UNIT;
+ cfg.continuous_cfg.cfg.single_unit.unit_id = ADC_UNIT_1;
+ cfg.continuous_cfg.cfg.single_unit.atten = ADC_ATTEN_DB_12;
+ cfg.continuous_cfg.cfg.single_unit.bit_width = ADC_BITWIDTH_12;
+ cfg.continuous_cfg.cfg.single_unit.channel_id[0] = (uint8_t)adc_mic_channel;
+ const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
+
+ esp_codec_dev_cfg_t codec_dev_cfg = {
+ .dev_type = ESP_CODEC_DEV_TYPE_IN,
+ .data_if = adc_if,
+ };
+ input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
+ if (!input_dev_) {
+ ESP_LOGE(TAG, "Failed to create codec device");
+ return;
+ }
+
+ i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
+ chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+ ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
+
+ i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
+ pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
+ pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
+
+ ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
+ ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
+
+ audio_codec_i2s_cfg_t i2s_cfg = {
+ .port = I2S_NUM_0,
+ .rx_handle = NULL,
+ .tx_handle = tx_handle_,
+ };
+
+ const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
+
+ codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
+ codec_dev_cfg.codec_if = NULL;
+ codec_dev_cfg.data_if = i2s_data_if;
+ output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
+
+ output_volume_ = 100;
+ if(pa_ctl != GPIO_NUM_NC) {
+ pa_ctrl_pin_ = pa_ctl;
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+ gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
+
+ if(pdm_speak_n != GPIO_NUM_NC){
+ PIN_FUNC_SELECT(GPIO_PIN_MUX_REG[pdm_speak_n], PIN_FUNC_GPIO);
+ gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
+ esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //鍙嶈浆杈撳嚭 SD OUT 淇″彿
+ gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
+ }
+
+ // 鍒濆鍖栬緭鍑哄畾鏃跺櫒
+ esp_timer_create_args_t output_timer_args = {
+ .callback = &AdcPdmAudioCodec::OutputTimerCallback,
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "output_timer"
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
+
+ ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
+}
+
+AdcPdmAudioCodec::~AdcPdmAudioCodec() {
+ // 鍒犻櫎瀹氭椂鍣�
+ if (output_timer_) {
+ esp_timer_stop(output_timer_);
+ esp_timer_delete(output_timer_);
+ output_timer_ = nullptr;
+ }
+
+ 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_);
+}
+
+void AdcPdmAudioCodec::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void AdcPdmAudioCodec::EnableInput(bool enable) {
+ if (enable == input_enabled_) {
+ return;
+ }
+ if (enable) {
+ esp_codec_dev_sample_info_t fs = {
+ .bits_per_sample = 16,
+ .channel = 1,
+ .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
+ .sample_rate = (uint32_t)input_sample_rate_,
+ .mclk_multiple = 0,
+ };
+ ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+}
+
+void AdcPdmAudioCodec::EnableOutput(bool enable) {
+ 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_));
+
+ // 寮哄埗鎸夋澘鍗¢厤缃噸閰峆DM TX鏃堕挓锛岃鐩栫涓夋柟搴撳湪set_fmt涓殑榛樿up_sample_fs
+ // 鑻ラ�氶亾宸插惎鐢紝鍏堢鐢ㄥ啀閲嶉厤锛屾渶鍚庡啀鍚敤
+ ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
+ i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
+ clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
+ ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
+ ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
+ if(pa_ctrl_pin_ != GPIO_NUM_NC){
+ gpio_set_level(pa_ctrl_pin_, 1);
+ }
+ // 鍚敤杈撳嚭鏃跺惎鍔ㄥ畾鏃跺櫒
+ if (output_timer_) {
+ esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
+ }
+
+ } else {
+ // 绂佺敤杈撳嚭鏃跺仠姝㈠畾鏃跺櫒
+ if (output_timer_) {
+ esp_timer_stop(output_timer_);
+ }
+ if(pa_ctrl_pin_ != GPIO_NUM_NC){
+ gpio_set_level(pa_ctrl_pin_, 0);
+ }
+ ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
+ }
+ AudioCodec::EnableOutput(enable);
+}
+
+int AdcPdmAudioCodec::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 AdcPdmAudioCodec::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)));
+ // 閲嶇疆杈撳嚭瀹氭椂鍣�
+ if (output_timer_) {
+ esp_timer_stop(output_timer_);
+ esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
+ }
+ }
+ return samples;
+}
+
+void AdcPdmAudioCodec::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;
+ }
+
+ EnableInput(true);
+ EnableOutput(true);
+ ESP_LOGI(TAG, "Audio codec started");
+}
+
+// 瀹氭椂鍣ㄥ洖璋冨嚱鏁板疄鐜�
+void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
+ AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
+ if (codec && codec->output_enabled_) {
+ codec->EnableOutput(false);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.h
new file mode 100644
index 0000000..8c9e836
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/adc_pdm_audio_codec.h
@@ -0,0 +1,37 @@
+#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 <esp_timer.h>
+
+class AdcPdmAudioCodec : public AudioCodec {
+private:
+ esp_codec_dev_handle_t output_dev_ = nullptr;
+ esp_codec_dev_handle_t input_dev_ = nullptr;
+ gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC;
+
+ // 瀹氭椂鍣ㄧ浉鍏虫垚鍛樺彉閲�
+ esp_timer_handle_t output_timer_ = nullptr;
+ static constexpr uint64_t TIMER_TIMEOUT_US = 120000; // 120ms = 120000us
+
+ // 瀹氭椂鍣ㄥ洖璋冨嚱鏁�
+ static void OutputTimerCallback(void* arg);
+
+ virtual int Read(int16_t* dest, int samples) override;
+ virtual int Write(const int16_t* data, int samples) override;
+
+public:
+ AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
+ uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl);
+ virtual ~AdcPdmAudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+ void Start();
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.h
new file mode 100644
index 0000000..8ea4ffc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.h
@@ -0,0 +1,47 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 閰嶇疆PDM涓婇噰鏍穎s鍙傛暟锛堝彇鍊艰寖鍥�<=480锛夈�傞儴鍒嗚澶囧湪441鏃惰〃鐜版洿绋冲畾
+#define AUDIO_PDM_UPSAMPLE_FS 441
+
+#define AUDIO_ADC_MIC_CHANNEL 2
+#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_6
+#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_7
+#define AUDIO_PA_CTL_GPIO GPIO_NUM_3
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+#define MOVE_WAKE_BUTTON_GPIO GPIO_NUM_0
+#define AUDIO_WAKE_BUTTON_GPIO GPIO_NUM_1
+
+#define DISPLAY_MOSI_PIN GPIO_NUM_4
+#define DISPLAY_CLK_PIN GPIO_NUM_5
+#define DISPLAY_DC_PIN GPIO_NUM_10
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+#define DISPLAY_CS_PIN GPIO_NUM_NC
+
+#define FL_GPIO_NUM GPIO_NUM_21
+#define FR_GPIO_NUM GPIO_NUM_19
+#define BL_GPIO_NUM GPIO_NUM_20
+#define BR_GPIO_NUM GPIO_NUM_18
+
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 160
+#define DISPLAY_HEIGHT 80
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.json
new file mode 100644
index 0000000..6d77b75
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/config.json
@@ -0,0 +1,34 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "esp-hi",
+ "sdkconfig_append": [
+ "CONFIG_IDF_TARGET=\"esp32c3\"",
+ "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\"",
+ "CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3",
+ "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=4",
+ "CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n",
+ "CONFIG_ESP_WIFI_RX_BA_WIN=4",
+ "CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n",
+ "CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
+ "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
+ "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y",
+ "CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168",
+ "CONFIG_FREERTOS_HZ=1000",
+ "CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
+ "CONFIG_LWIP_MAX_SOCKETS=10",
+ "CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16",
+ "CONFIG_LWIP_IPV6=n",
+ "CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048",
+ "CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y",
+ "CONFIG_NEWLIB_NANO_FORMAT=y",
+ "CONFIG_ESP_CONSOLE_NONE=y",
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_COMPILER_OPTIMIZATION_SIZE=y",
+ "CONFIG_CODEC_DATA_ADC_SUPPORT=y"
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.cc
new file mode 100644
index 0000000..c494337
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.cc
@@ -0,0 +1,178 @@
+#include <cstring>
+#include "display/lcd_display.h"
+#include <esp_log.h>
+#include "emoji_display.h"
+#include "assets/lang_config.h"
+#include "assets.h"
+
+#include <esp_lcd_panel_io.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/queue.h>
+#include <freertos/event_groups.h>
+
+static const char *TAG = "emoji";
+
+namespace anim {
+
+// Emoji asset name mapping based on usage pattern
+static const std::unordered_map<std::string, std::string> emoji_asset_name_map = {
+ {"connecting", "connecting.aaf"},
+ {"wake", "wake.aaf"},
+ {"asking", "asking.aaf"},
+ {"happy_loop", "happy_loop.aaf"},
+ {"sad_loop", "sad_loop.aaf"},
+ {"anger_loop", "anger_loop.aaf"},
+ {"panic_loop", "panic_loop.aaf"},
+ {"blink_quick", "blink_quick.aaf"},
+ {"scorn_loop", "scorn_loop.aaf"}
+};
+
+bool EmojiPlayer::OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
+{
+ auto* disp_drv = static_cast<anim_player_handle_t*>(user_ctx);
+ anim_player_flush_ready(disp_drv);
+ return true;
+}
+
+void EmojiPlayer::OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data)
+{
+ auto* panel = static_cast<esp_lcd_panel_handle_t>(anim_player_get_user_data(handle));
+ esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, color_data);
+}
+
+EmojiPlayer::EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
+{
+ ESP_LOGI(TAG, "Create EmojiPlayer, panel: %p, panel_io: %p", panel, panel_io);
+
+ anim_player_config_t player_cfg = {
+ .flush_cb = OnFlush,
+ .update_cb = NULL,
+ .user_data = panel,
+ .flags = {.swap = true},
+ .task = ANIM_PLAYER_INIT_CONFIG()
+ };
+
+ player_cfg.task.task_priority = 1;
+ player_cfg.task.task_stack = 4096;
+ player_handle_ = anim_player_init(&player_cfg);
+
+ const esp_lcd_panel_io_callbacks_t cbs = {
+ .on_color_trans_done = OnFlushIoReady,
+ };
+ esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, player_handle_);
+ StartPlayer("connecting", true, 15);
+}
+
+EmojiPlayer::~EmojiPlayer()
+{
+ if (player_handle_) {
+ anim_player_update(player_handle_, PLAYER_ACTION_STOP);
+ anim_player_deinit(player_handle_);
+ player_handle_ = nullptr;
+ }
+}
+
+void EmojiPlayer::StartPlayer(const std::string& asset_name, bool repeat, int fps)
+{
+ if (player_handle_) {
+ uint32_t start, end;
+ void *src_data = nullptr;
+ size_t src_len = 0;
+
+ auto& assets = Assets::GetInstance();
+ std::string filename = emoji_asset_name_map.at(asset_name);
+ if (!assets.GetAssetData(filename, src_data, src_len)) {
+ ESP_LOGE(TAG, "Failed to get asset data for %s", asset_name.c_str());
+ return;
+ }
+
+ anim_player_set_src_data(player_handle_, src_data, src_len);
+ anim_player_get_segment(player_handle_, &start, &end);
+ if(asset_name == "wake"){
+ start = 7;
+ }
+ anim_player_set_segment(player_handle_, start, end, fps, true);
+ anim_player_update(player_handle_, PLAYER_ACTION_START);
+ }
+}
+
+void EmojiPlayer::StopPlayer()
+{
+ if (player_handle_) {
+ anim_player_update(player_handle_, PLAYER_ACTION_STOP);
+ }
+}
+
+EmojiWidget::EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
+{
+ InitializePlayer(panel, panel_io);
+}
+
+EmojiWidget::~EmojiWidget()
+{
+
+}
+
+void EmojiWidget::SetEmotion(const char* emotion)
+{
+ if (!player_) {
+ return;
+ }
+
+ using Param = std::tuple<std::string, bool, int>;
+ static const std::unordered_map<std::string, Param> emotion_map = {
+ {"happy", {"happy_loop", true, 25}},
+ {"laughing", {"happy_loop", true, 25}},
+ {"funny", {"happy_loop", true, 25}},
+ {"loving", {"happy_loop", true, 25}},
+ {"embarrassed", {"happy_loop", true, 25}},
+ {"confident", {"happy_loop", true, 25}},
+ {"delicious", {"happy_loop", true, 25}},
+ {"sad", {"sad_loop", true, 25}},
+ {"crying", {"sad_loop", true, 25}},
+ {"sleepy", {"sad_loop", true, 25}},
+ {"silly", {"sad_loop", true, 25}},
+ {"angry", {"anger_loop", true, 25}},
+ {"surprised", {"panic_loop", true, 25}},
+ {"shocked", {"panic_loop", true, 25}},
+ {"thinking", {"happy_loop", true, 25}},
+ {"winking", {"blink_quick", true, 5}},
+ {"relaxed", {"scorn_loop", true, 25}},
+ {"confused", {"scorn_loop", true, 25}},
+ };
+
+ auto it = emotion_map.find(emotion);
+ if (it != emotion_map.end()) {
+ const auto& [aaf, repeat, fps] = it->second;
+ player_->StartPlayer(aaf, repeat, fps);
+ } else if (strcmp(emotion, "neutral") == 0) {
+ }
+}
+
+void EmojiWidget::SetStatus(const char* status)
+{
+ if (player_) {
+ if (strcmp(status, Lang::Strings::LISTENING) == 0) {
+ player_->StartPlayer("asking", true, 15);
+ } else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
+ player_->StartPlayer("wake", true, 15);
+ }
+ }
+}
+
+void EmojiWidget::InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io)
+{
+ player_ = std::make_unique<EmojiPlayer>(panel, panel_io);
+}
+
+bool EmojiWidget::Lock(int timeout_ms)
+{
+ return true;
+}
+
+void EmojiWidget::Unlock()
+{
+}
+
+} // namespace anim
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.h b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.h
new file mode 100644
index 0000000..d4faab7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/emoji_display.h
@@ -0,0 +1,55 @@
+#pragma once
+
+#include "display/lcd_display.h"
+#include <memory>
+#include <functional>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include "anim_player.h"
+#include "assets.h"
+
+namespace anim {
+
+class EmojiPlayer;
+
+using FlushIoReadyCallback = std::function<bool(esp_lcd_panel_io_handle_t, esp_lcd_panel_io_event_data_t*, void*)>;
+using FlushCallback = std::function<void(anim_player_handle_t, int, int, int, int, const void*)>;
+
+class EmojiPlayer {
+public:
+ EmojiPlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
+ ~EmojiPlayer();
+
+ void StartPlayer(const std::string& asset_name, bool repeat, int fps);
+ void StopPlayer();
+
+private:
+ static bool OnFlushIoReady(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
+ static void OnFlush(anim_player_handle_t handle, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+
+ anim_player_handle_t player_handle_;
+};
+
+class EmojiWidget : public Display {
+public:
+ EmojiWidget(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
+ virtual ~EmojiWidget();
+
+ virtual void SetEmotion(const char* emotion) override;
+ virtual void SetStatus(const char* status) override;
+ virtual void SetChatMessage(const char* role, const char* content) override {}
+
+ anim::EmojiPlayer* GetPlayer()
+ {
+ return player_.get();
+ }
+
+private:
+ void InitializePlayer(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io);
+ virtual bool Lock(int timeout_ms = 0) override;
+ virtual void Unlock() override;
+
+ std::unique_ptr<anim::EmojiPlayer> player_;
+};
+
+} // namespace anim
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-hi/esp_hi.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/esp_hi.cc
new file mode 100644
index 0000000..0d64569
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-hi/esp_hi.cc
@@ -0,0 +1,422 @@
+#include "wifi_board.h"
+#include "adc_pdm_audio_codec.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>
+#include <esp_wifi.h>
+#include <esp_event.h>
+
+#include "display/lcd_display.h"
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include "esp_lcd_ili9341.h"
+
+#include "assets/lang_config.h"
+#include "anim_player.h"
+#include "emoji_display.h"
+#include "servo_dog_ctrl.h"
+#include "led_strip.h"
+#include "driver/rmt_tx.h"
+#include "device_state.h"
+
+#include "sdkconfig.h"
+
+#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
+#include "esp_hi_web_control.h"
+#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
+
+#define TAG "ESP_HI"
+
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0x11, NULL, 0, 120}, // Sleep out, Delay 120ms
+ {0xB1, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0},
+ {0xB2, (uint8_t []){0x05, 0x3A, 0x3A}, 3, 0},
+ {0xB3, (uint8_t []){0x05, 0x3A, 0x3A, 0x05, 0x3A, 0x3A}, 6, 0},
+ {0xB4, (uint8_t []){0x03}, 1, 0}, // Dot inversion
+ {0xC0, (uint8_t []){0x44, 0x04, 0x04}, 3, 0},
+ {0xC1, (uint8_t []){0xC0}, 1, 0},
+ {0xC2, (uint8_t []){0x0D, 0x00}, 2, 0},
+ {0xC3, (uint8_t []){0x8D, 0x6A}, 2, 0},
+ {0xC4, (uint8_t []){0x8D, 0xEE}, 2, 0},
+ {0xC5, (uint8_t []){0x08}, 1, 0},
+ {0xE0, (uint8_t []){0x0F, 0x10, 0x03, 0x03, 0x07, 0x02, 0x00, 0x02, 0x07, 0x0C, 0x13, 0x38, 0x0A, 0x0E, 0x03, 0x10}, 16, 0},
+ {0xE1, (uint8_t []){0x10, 0x0B, 0x04, 0x04, 0x10, 0x03, 0x00, 0x03, 0x03, 0x09, 0x17, 0x33, 0x0B, 0x0C, 0x06, 0x10}, 16, 0},
+ {0x35, (uint8_t []){0x00}, 1, 0},
+ {0x3A, (uint8_t []){0x05}, 1, 0},
+ {0x36, (uint8_t []){0xC8}, 1, 0},
+ {0x29, NULL, 0, 0}, // Display on
+ {0x2C, NULL, 0, 0}, // Memory write
+};
+
+static const led_strip_config_t bsp_strip_config = {
+ .strip_gpio_num = GPIO_NUM_8,
+ .max_leds = 4,
+ .led_model = LED_MODEL_WS2812,
+ .flags = {
+ .invert_out = false
+ }
+};
+
+static const led_strip_rmt_config_t bsp_rmt_config = {
+ .clk_src = RMT_CLK_SRC_DEFAULT,
+ .resolution_hz = 10 * 1000 * 1000,
+ .flags = {
+ .with_dma = false
+ }
+};
+
+class EspHi : public WifiBoard {
+private:
+ Button boot_button_;
+ Button audio_wake_button_;
+ Button move_wake_button_;
+ anim::EmojiWidget* display_ = nullptr;
+ bool web_server_initialized_ = false;
+ led_strip_handle_t led_strip_;
+ bool led_on_ = false;
+
+#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
+ static void wifi_event_handler(void* arg, esp_event_base_t event_base,
+ int32_t event_id, void* event_data)
+ {
+ if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
+
+ xTaskCreate(
+ [](void* arg) {
+ EspHi* instance = static_cast<EspHi*>(arg);
+
+ vTaskDelay(5000 / portTICK_PERIOD_MS);
+
+ if (!instance->web_server_initialized_) {
+ ESP_LOGI(TAG, "WiFi connected, init web control server");
+ esp_err_t err = esp_hi_web_control_server_init();
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize web control server: %d", err);
+ } else {
+ ESP_LOGI(TAG, "Web control server initialized");
+ instance->web_server_initialized_ = true;
+ }
+ }
+
+ vTaskDelete(NULL);
+ },
+ "web_server_init",
+ 1024 * 10, arg, 5, nullptr);
+ }
+ }
+#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
+
+ void HandleMoveWakePressDown(int64_t current_time, int64_t &last_trigger_time, int &gesture_state)
+ {
+ int64_t interval = last_trigger_time == 0 ? 0 : current_time - last_trigger_time;
+ last_trigger_time = current_time;
+
+ if (interval > 1000) {
+ gesture_state = 0;
+ } else {
+ switch (gesture_state) {
+ case 0:
+ break;
+ case 1:
+ if (interval > 300) {
+ gesture_state = 2;
+ }
+ break;
+ case 2:
+ if (interval > 100) {
+ gesture_state = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ void HandleMoveWakePressUp(int64_t current_time, int64_t &last_trigger_time, int &gesture_state)
+ {
+ int64_t interval = current_time - last_trigger_time;
+
+ if (interval > 1000) {
+ gesture_state = 0;
+ } else {
+ switch (gesture_state) {
+ case 0:
+ if (interval > 300) {
+ gesture_state = 1;
+ }
+ break;
+ case 1:
+ break;
+ case 2:
+ if (interval < 100) {
+ ESP_LOGI(TAG, "gesture detected");
+ gesture_state = 0;
+ auto &app = Application::GetInstance();
+ app.ToggleChatState();
+ }
+ break;
+ }
+ }
+ }
+
+ void InitializeButtons()
+ {
+ static int64_t last_trigger_time = 0;
+ static int gesture_state = 0; // 0: init, 1: wait second long interval, 2: wait oscillation
+
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ audio_wake_button_.OnPressDown([this]() {
+ });
+
+ audio_wake_button_.OnPressUp([this]() {
+ });
+
+ move_wake_button_.OnPressDown([this]() {
+ int64_t current_time = esp_timer_get_time() / 1000;
+ HandleMoveWakePressDown(current_time, last_trigger_time, gesture_state);
+ });
+
+ move_wake_button_.OnPressUp([this]() {
+ int64_t current_time = esp_timer_get_time() / 1000;
+ HandleMoveWakePressUp(current_time, last_trigger_time, gesture_state);
+ });
+ }
+
+ void InitializeLed() {
+ ESP_LOGI(TAG, "BLINK_GPIO setting %d", bsp_strip_config.strip_gpio_num);
+
+ ESP_ERROR_CHECK(led_strip_new_rmt_device(&bsp_strip_config, &bsp_rmt_config, &led_strip_));
+ led_strip_set_pixel(led_strip_, 0, 0x00, 0x00, 0x00);
+ led_strip_set_pixel(led_strip_, 1, 0x00, 0x00, 0x00);
+ led_strip_set_pixel(led_strip_, 2, 0x00, 0x00, 0x00);
+ led_strip_set_pixel(led_strip_, 3, 0x00, 0x00, 0x00);
+ led_strip_refresh(led_strip_);
+ }
+
+ esp_err_t SetLedColor(uint8_t r, uint8_t g, uint8_t b) {
+ esp_err_t ret = ESP_OK;
+
+ ret |= led_strip_set_pixel(led_strip_, 0, r, g, b);
+ ret |= led_strip_set_pixel(led_strip_, 1, r, g, b);
+ ret |= led_strip_set_pixel(led_strip_, 2, r, g, b);
+ ret |= led_strip_set_pixel(led_strip_, 3, r, g, b);
+ ret |= led_strip_refresh(led_strip_);
+ return ret;
+ }
+
+ void InitializeIot()
+ {
+ ESP_LOGI(TAG, "Initialize Iot");
+ InitializeLed();
+ SetLedColor(0x00, 0x00, 0x00);
+
+#ifdef CONFIG_ESP_HI_WEB_CONTROL_ENABLED
+ esp_event_loop_create_default();
+ ESP_ERROR_CHECK(esp_event_handler_register(WIFI_EVENT, WIFI_EVENT_STA_CONNECTED,
+ &wifi_event_handler, this));
+#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED
+ }
+
+ void InitializeSpi()
+ {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_PIN;
+ buscfg.quadwp_io_num = GPIO_NUM_NC;
+ buscfg.quadhd_io_num = GPIO_NUM_NC;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay()
+ {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *) &vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_set_gap(panel, 0, 24);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ ESP_LOGI(TAG, "LCD panel create success, %p", panel);
+
+ esp_lcd_panel_disp_on_off(panel, true);
+
+ ESP_LOGI(TAG, "Create emoji widget, panel: %p, panel_io: %p", panel, panel_io);
+ display_ = new anim::EmojiWidget(panel, panel_io);
+
+#if CONFIG_ESP_CONSOLE_NONE
+ servo_dog_ctrl_config_t config = {
+ .fl_gpio_num = FL_GPIO_NUM,
+ .fr_gpio_num = FR_GPIO_NUM,
+ .bl_gpio_num = BL_GPIO_NUM,
+ .br_gpio_num = BR_GPIO_NUM,
+ };
+
+ servo_dog_ctrl_init(&config);
+#endif
+ }
+
+ void InitializeTools()
+ {
+ auto& mcp_server = McpServer::GetInstance();
+
+ // 鍩虹鍔ㄤ綔鎺у埗
+ mcp_server.AddTool("self.dog.basic_control", "鏈哄櫒浜虹殑鍩虹鍔ㄤ綔銆傛満鍣ㄤ汉鍙互鍋氫互涓嬪熀纭�鍔ㄤ綔锛歕n"
+ "forward: 鍚戝墠绉诲姩\nbackward: 鍚戝悗绉诲姩\nturn_left: 鍚戝乏杞琝nturn_right: 鍚戝彸杞琝nstop: 绔嬪嵆鍋滄褰撳墠鍔ㄤ綔",
+ PropertyList({
+ Property("action", kPropertyTypeString),
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ const std::string& action = properties["action"].value<std::string>();
+ if (action == "forward") {
+ servo_dog_ctrl_send(DOG_STATE_FORWARD, NULL);
+ } else if (action == "backward") {
+ servo_dog_ctrl_send(DOG_STATE_BACKWARD, NULL);
+ } else if (action == "turn_left") {
+ servo_dog_ctrl_send(DOG_STATE_TURN_LEFT, NULL);
+ } else if (action == "turn_right") {
+ servo_dog_ctrl_send(DOG_STATE_TURN_RIGHT, NULL);
+ } else if (action == "stop") {
+ servo_dog_ctrl_send(DOG_STATE_IDLE, NULL);
+ } else {
+ return false;
+ }
+ return true;
+ });
+
+ // 鎵╁睍鍔ㄤ綔鎺у埗
+ mcp_server.AddTool("self.dog.advanced_control", "鏈哄櫒浜虹殑鎵╁睍鍔ㄤ綔銆傛満鍣ㄤ汉鍙互鍋氫互涓嬫墿灞曞姩浣滐細\n"
+ "sway_back_forth: 鍓嶅悗鎽囨憜\nlay_down: 瓒翠笅\nsway: 宸﹀彸鎽囨憜\nretract_legs: 鏀跺洖鑵块儴\n"
+ "shake_hand: 鎻℃墜\nshake_back_legs: 浼告噿鑵癨njump_forward: 鍚戝墠璺宠穬",
+ PropertyList({
+ Property("action", kPropertyTypeString),
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ const std::string& action = properties["action"].value<std::string>();
+ if (action == "sway_back_forth") {
+ servo_dog_ctrl_send(DOG_STATE_SWAY_BACK_FORTH, NULL);
+ } else if (action == "lay_down") {
+ servo_dog_ctrl_send(DOG_STATE_LAY_DOWN, NULL);
+ } else if (action == "sway") {
+ dog_action_args_t args = {
+ .repeat_count = 4,
+ };
+ servo_dog_ctrl_send(DOG_STATE_SWAY, &args);
+ } else if (action == "retract_legs") {
+ servo_dog_ctrl_send(DOG_STATE_RETRACT_LEGS, NULL);
+ } else if (action == "shake_hand") {
+ servo_dog_ctrl_send(DOG_STATE_SHAKE_HAND, NULL);
+ } else if (action == "shake_back_legs") {
+ servo_dog_ctrl_send(DOG_STATE_SHAKE_BACK_LEGS, NULL);
+ } else if (action == "jump_forward") {
+ servo_dog_ctrl_send(DOG_STATE_JUMP_FORWARD, NULL);
+ } else {
+ return false;
+ }
+ return true;
+ });
+
+ // 鐏厜鎺у埗
+ mcp_server.AddTool("self.light.get_power", "鑾峰彇鐏槸鍚︽墦寮�", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return led_on_;
+ });
+
+ mcp_server.AddTool("self.light.turn_on", "鎵撳紑鐏�", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SetLedColor(0xFF, 0xFF, 0xFF);
+ led_on_ = true;
+ return true;
+ });
+
+ mcp_server.AddTool("self.light.turn_off", "鍏抽棴鐏�", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SetLedColor(0x00, 0x00, 0x00);
+ led_on_ = false;
+ return true;
+ });
+
+ 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;
+ });
+ }
+
+public:
+ EspHi() : boot_button_(BOOT_BUTTON_GPIO),
+ audio_wake_button_(AUDIO_WAKE_BUTTON_GPIO),
+ move_wake_button_(MOVE_WAKE_BUTTON_GPIO)
+ {
+ InitializeButtons();
+ InitializeIot();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override
+ {
+ static AdcPdmAudioCodec audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_ADC_MIC_CHANNEL,
+ AUDIO_PDM_SPEAK_P_GPIO,
+ AUDIO_PDM_SPEAK_N_GPIO,
+ AUDIO_PA_CTL_GPIO);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override
+ {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(EspHi);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/README.md
new file mode 100644
index 0000000..5fde9b2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/README.md
@@ -0,0 +1,41 @@
+# ESP-P4-Function-EV-Board
+
+Board support for ESP-P4-Function-EV-Board. Wi鈥慒i uses ESP鈥慔osted via the on鈥慴oard ESP32鈥慍6. LCD is supported via the official MIPI鈥慏SI LCD adapter.
+
+## Features
+- Wi鈥慒i: `esp_wifi_remote` + `esp_hosted` (SDIO) with ESP32鈥慍6 co鈥憄rocessor
+- Display: 7" MIPI鈥慏SI LCD (1024脳600) via adapter; can also run headless
+- Audio: ES8311 codec with speaker and microphone support
+- Touch: GT911 capacitive touch controller
+- SD Card: MicroSD card support (MMC mode)
+- Camera: MIPI-CSI camera interface with fallback DVP configuration (OV5647, SC2336 sensors supported)
+- USB: USB host/device support
+- SPIFFS: Built-in flash filesystem support
+- Fonts: Custom font support with Unicode characters (Vietnamese, Chinese, etc.)
+
+## Configure
+In `menuconfig`: Xiaozhi Assistant -> Board Type -> ESP-P4-Function-EV-Board
+
+Ensure these are set (auto-set when building via config.json):
+- `CONFIG_SLAVE_IDF_TARGET_ESP32C6=y`
+- `CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y`
+- `CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y`
+- `CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y`
+
+## LCD Connection (from Espressif user guide)
+- Connect the LCD adapter board J3 to the board鈥檚 MIPI DSI connector (reverse ribbon).
+- Wire `RST_LCD` (adapter J6) to `GPIO27` (board J1).
+- Wire `PWM` (adapter J6) to `GPIO26` (board J1).
+- Optionally power the LCD adapter via its USB or provide `5V` and `GND` from the board.
+
+These pins are pre-configured in `config.h` as `PIN_NUM_LCD_RST=GPIO27` and `DISPLAY_BACKLIGHT_PIN=GPIO26`. Resolution is set to 1024脳600.
+
+## Build (example)
+```powershell
+idf.py set-target esp32p4
+idf.py menuconfig
+idf.py build
+```
+
+Tip: In menuconfig, choose Xiaozhi Assistant -> Board Type -> ESP-P4-Function-EV-Board.
+If building a release via scripts, the `config.json` in this folder appends the required Hosted options.
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.h
new file mode 100644
index 0000000..aca0c93
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.h
@@ -0,0 +1,11 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include "bsp/esp32_p4_function_ev_board.h" // Library for board configs and pins
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.json
new file mode 100644
index 0000000..d191f94
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/config.json
@@ -0,0 +1,16 @@
+{
+ "target": "esp32p4",
+ "builds": [
+ {
+ "name": "esp-p4-function-ev-board",
+ "sdkconfig_append": [
+ "CONFIG_SLAVE_IDF_TARGET_ESP32C6=y",
+ "CONFIG_ESP_HOSTED_P4_DEV_BOARD_FUNC_BOARD=y",
+ "CONFIG_ESP_HOSTED_SDIO_HOST_INTERFACE=y",
+ "CONFIG_ESP_HOSTED_SDIO_4_BIT_BUS=y",
+ "CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y",
+ "CONFIG_BSP_LCD_TYPE_1024_600=y"
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc
new file mode 100644
index 0000000..d473fd4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-p4-function-ev-board/esp-p4-function-ev-board.cc
@@ -0,0 +1,233 @@
+#include "wifi_board.h"
+#include "audio/codecs/es8311_audio_codec.h"
+// Display
+#include "display/display.h"
+#include "display/lcd_display.h"
+#include "lvgl_theme.h"
+// Backlight
+// PwmBacklight is declared in backlight headers pulled by display/lcd_display includes via lvgl stack
+
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "esp_video.h"
+
+#include <esp_log.h>
+#include <inttypes.h>
+#include <driver/i2c_master.h>
+#include <esp_lvgl_port.h>
+#include <soc/clk_tree_defs.h>
+// SD card
+#include <esp_vfs_fat.h>
+#include <sdmmc_cmd.h>
+#include <driver/sdmmc_host.h>
+#include <driver/sdspi_host.h>
+// SD power control (on-chip LDO)
+#include "sd_pwr_ctrl_by_on_chip_ldo.h"
+
+// MIPI-DSI / LCD vendor includes (library may replace some)
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_ldo_regulator.h"
+#include "esp_lcd_ek79007.h"
+#include "esp_lcd_touch_gt911.h"
+
+// Library includes
+#include "bsp/esp32_p4_function_ev_board.h"
+#include "bsp/touch.h"
+
+#define TAG "ESP32P4FuncEV"
+
+class ESP32P4FunctionEvBoard : public WifiBoard
+{
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_ = nullptr;
+ Button boot_button_;
+ LcdDisplay *display_ = nullptr;
+ esp_lcd_touch_handle_t tp_ = nullptr;
+ EspVideo* camera_ = nullptr;
+
+ void InitializeI2cBuses()
+ {
+ ESP_ERROR_CHECK(bsp_i2c_init());
+ codec_i2c_bus_ = bsp_i2c_get_handle();
+ }
+
+ // Touch I2C bus initialization is not required for this board (handled elsewhere)
+ void InitializeTouchI2cBus()
+ {
+ // No implementation needed
+ }
+
+ void InitializeLCD()
+ {
+ bsp_display_config_t config = {
+ .hdmi_resolution = BSP_HDMI_RES_NONE,
+ .dsi_bus = {
+ .phy_clk_src = (mipi_dsi_phy_clock_source_t)SOC_MOD_CLK_PLL_F20M,
+ .lane_bit_rate_mbps = 1000,
+ },
+ };
+
+ bsp_lcd_handles_t handles;
+ ESP_ERROR_CHECK(bsp_display_new_with_handles(&config, &handles));
+
+ display_ = new MipiLcdDisplay(handles.io, handles.panel, 1024, 600, 0, 0, true, true, false);
+ }
+
+ void InitializeButtons()
+ {
+ boot_button_.OnClick([this]()
+ {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeTouch()
+ {
+ ESP_ERROR_CHECK(bsp_touch_new(NULL, &tp_));
+ }
+
+ void InitializeSdCard()
+ {
+ ESP_LOGI(TAG, "Initializing SD card");
+ esp_err_t ret = bsp_sdcard_mount();
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to mount SD card: %s", esp_err_to_name(ret));
+ } else {
+ ESP_LOGI(TAG, "SD card mounted successfully");
+ }
+ }
+
+ void InitializeCamera()
+ {
+ ESP_LOGI(TAG, "Initializing camera");
+
+ // Use BSP camera initialization for ESP-P4
+ bsp_camera_cfg_t camera_cfg = {0};
+ esp_err_t ret = bsp_camera_start(&camera_cfg);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize BSP camera: %s", esp_err_to_name(ret));
+ ESP_LOGI(TAG, "Attempting alternative camera initialization");
+
+ // Alternative: Direct EspVideo initialization if BSP fails
+ // This provides more control over camera configuration
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = BSP_I2C_SDA, // Reuse I2C pins if camera pins not defined
+ [1] = BSP_I2C_SCL,
+ [2] = GPIO_NUM_NC,
+ [3] = GPIO_NUM_NC,
+ [4] = GPIO_NUM_NC,
+ [5] = GPIO_NUM_NC,
+ [6] = GPIO_NUM_NC,
+ [7] = GPIO_NUM_NC,
+ },
+ .vsync_io = GPIO_NUM_NC,
+ .de_io = GPIO_NUM_NC,
+ .pclk_io = GPIO_NUM_NC,
+ .xclk_io = GPIO_NUM_NC,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false, // Use existing I2C bus
+ .i2c_handle = codec_i2c_bus_, // Reuse the existing I2C bus
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = GPIO_NUM_NC,
+ .pwdn_pin = GPIO_NUM_NC,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = 20000000, // 20MHz typical for cameras
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ // Try to create camera with direct configuration
+ camera_ = new EspVideo(video_config);
+ ESP_LOGI(TAG, "Camera initialized with direct configuration");
+ } else {
+ ESP_LOGI(TAG, "Camera initialized successfully via BSP");
+ }
+ }
+
+ void InitializeFonts()
+ {
+ ESP_LOGI(TAG, "Initializing font support");
+ // Font initialization is handled by the Assets system
+ // The board supports loading fonts from assets partition
+ // Verify that fonts are properly loaded by checking theme
+ auto& theme_manager = LvglThemeManager::GetInstance();
+ auto current_theme = theme_manager.GetTheme("light");
+ if (current_theme != nullptr) {
+ auto text_font = current_theme->text_font();
+ if (text_font != nullptr && text_font->font() != nullptr) {
+ ESP_LOGI(TAG, "Custom font loaded successfully: line_height=%d", text_font->font()->line_height);
+ } else {
+ ESP_LOGW(TAG, "Custom font not loaded, using built-in font");
+ }
+ }
+ }
+
+public:
+
+ ESP32P4FunctionEvBoard() : boot_button_(0)
+ {
+ InitializeI2cBuses();
+ // Audio is initialized by Es8311AudioCodec
+ InitializeLCD();
+ InitializeButtons();
+ InitializeTouch();
+ InitializeSdCard();
+ InitializeCamera();
+ InitializeFonts();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ ~ESP32P4FunctionEvBoard()
+ {
+ // Clean up display pointer
+ delete display_;
+ display_ = nullptr;
+ // Unmount SD card
+ esp_err_t ret = bsp_sdcard_unmount();
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to unmount SD card: %s", esp_err_to_name(ret));
+ }
+ // If other resources need cleanup, add here
+ }
+
+ virtual AudioCodec *GetAudioCodec() override
+ {
+ static Es8311AudioCodec audio_codec(
+ codec_i2c_bus_, (i2c_port_t)BSP_I2C_NUM, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ BSP_I2S_MCLK, BSP_I2S_SCLK, BSP_I2S_LCLK, BSP_I2S_DOUT, BSP_I2S_DSIN,
+ BSP_POWER_AMP_IO, ES8311_CODEC_DEFAULT_ADDR, true, false);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override { return display_; }
+
+ virtual Backlight *GetBacklight() override
+ {
+ static PwmBacklight backlight(BSP_LCD_BACKLIGHT, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ virtual Camera *GetCamera() override
+ {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(ESP32P4FunctionEvBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/README.md
new file mode 100644
index 0000000..5fcacc4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/README.md
@@ -0,0 +1,10 @@
+璇风‘璁よ嚜宸辩殑寮�鍙戞澘纭欢鐗堟湰锛屽鏋滅‖浠剁増鏈紝鍦ㄩ厤缃腑杩涜ev_board type杩涜閫夋嫨
+1.4涓�1.5鍙湁io杩涜鍙樻洿
+鍙互鏌ョ湅瀹樻柟鏂囨。锛岀‘璁ゅ叿浣撶粏鑺俬ttps://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html
+鍏蜂綋璋冩暣涓猴細
+I2C_SCL IO18 -> IO48
+I2C_SDA IO8 -> IO47
+LCD_DATA6 IO47 -> IO8
+LCD_DATA7 IO48 -> IO18
+
+鏈増鏈彧鏀寔浜�800x480鐨勫睆骞�
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.h
new file mode 100644
index 0000000..a59cf05
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.h
@@ -0,0 +1,42 @@
+#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_5
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_7
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6
+
+#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_0)
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR 0x82
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_4
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_WIDTH 800
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_19
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.json
new file mode 100644
index 0000000..27ab752
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-s3-lcd-ev-board-2",
+ "sdkconfig_append": []
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc
new file mode 100644
index 0000000..4bd5cb2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp-s3-lcd-ev-board-2.cc
@@ -0,0 +1,280 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "pin_config.h"
+#include "esp_video.h"
+
+#include "config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include "esp_lcd_gc9503.h"
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_io_additions.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+#include <esp_lcd_touch_gt1151.h>
+#include <esp_io_expander_tca9554.h>
+
+#define TAG "ESP_S3_LCD_EV_Board_2"
+
+class ESP_S3_LCD_EV_Board_2 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ EspVideo* camera_;
+
+ //add support ev board lcd
+ esp_io_expander_handle_t expander = NULL;
+
+ void InitializeRGB_GC9503V_Display() {
+ ESP_LOGI(TAG, "Init GC9503V");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+
+ // add support ev board lcd
+ gpio_config_t io_conf = {
+ .pin_bit_mask = BIT64(GC9503V_PIN_NUM_VSYNC),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ };
+
+ gpio_config(&io_conf);
+ gpio_set_level(GC9503V_PIN_NUM_VSYNC, 1);
+
+ ESP_LOGI(TAG, "Install 3-wire SPI panel IO");
+ spi_line_config_t line_config = {
+ .cs_io_type = IO_TYPE_EXPANDER,
+ .cs_expander_pin = GC9503V_LCD_IO_SPI_CS_1,
+ .scl_io_type = IO_TYPE_EXPANDER,
+ .scl_expander_pin = GC9503V_LCD_IO_SPI_SCL_1,
+ .sda_io_type = IO_TYPE_EXPANDER,
+ .sda_expander_pin = GC9503V_LCD_IO_SPI_SDO_1,
+ .io_expander = expander,
+ };
+
+ esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0);
+ int espok = esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io);
+ ESP_LOGI(TAG, "Install 3-wire SPI panel IO:%d",espok);
+
+ ESP_LOGI(TAG, "Install RGB LCD panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_rgb_panel_config_t rgb_config = {
+ .clk_src = LCD_CLK_SRC_PLL160M,
+ //add support ev board
+ .timings = GC9503_800_480_PANEL_60HZ_RGB_TIMING(),
+ .data_width = 16, // RGB565 in parallel mode, thus 16bit in width
+ .bits_per_pixel = 16,
+ .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS,
+ .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT,
+ .dma_burst_size = 64,
+ .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC,
+ .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC,
+ .de_gpio_num = GC9503V_PIN_NUM_DE,
+ .pclk_gpio_num = GC9503V_PIN_NUM_PCLK,
+ .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN,
+ .data_gpio_nums = {
+ GC9503V_PIN_NUM_DATA0,
+ GC9503V_PIN_NUM_DATA1,
+ GC9503V_PIN_NUM_DATA2,
+ GC9503V_PIN_NUM_DATA3,
+ GC9503V_PIN_NUM_DATA4,
+ GC9503V_PIN_NUM_DATA5,
+ GC9503V_PIN_NUM_DATA6,
+ GC9503V_PIN_NUM_DATA7,
+ GC9503V_PIN_NUM_DATA8,
+ GC9503V_PIN_NUM_DATA9,
+ GC9503V_PIN_NUM_DATA10,
+ GC9503V_PIN_NUM_DATA11,
+ GC9503V_PIN_NUM_DATA12,
+ GC9503V_PIN_NUM_DATA13,
+ GC9503V_PIN_NUM_DATA14,
+ GC9503V_PIN_NUM_DATA15,
+ },
+ .flags= {
+ .fb_in_psram = true, // allocate frame buffer in PSRAM
+ }
+ };
+
+ ESP_LOGI(TAG, "Initialize RGB LCD panel");
+
+ gc9503_vendor_config_t vendor_config = {
+ .rgb_config = &rgb_config,
+ .flags = {
+ .mirror_by_cmd = 0,
+ .auto_del_panel_io = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = -1,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ // .bits_per_pixel = 16,
+ //add surpport ev board
+ .bits_per_pixel = 18,
+ .vendor_config = &vendor_config,
+ };
+ (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle));
+ (esp_lcd_panel_reset(panel_handle));
+ (esp_lcd_panel_init(panel_handle));
+
+ display_ = new RgbLcdDisplay(panel_io, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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, &i2c_bus_));
+
+ //add support ev board lcd amp
+ //鍒濆鍖栨墿灞昳o鍙�
+ esp_io_expander_new_i2c_tca9554(i2c_bus_, 0x20, &expander);
+ /* Setup power amplifier pin, set default to enable */
+ esp_io_expander_set_dir(expander, BSP_POWER_AMP_IO, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(expander, BSP_POWER_AMP_IO, true);
+
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT1151_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt1151(tp_io_handle, &tp_cfg, &tp));
+
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ }
+
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ void InitializeCamera() {
+ esp_video_init_usb_uvc_config_t usb_uvc_config = {
+ .uvc = {
+ .uvc_dev_num = 1,
+ .task_stack = 4096,
+ .task_priority = 5,
+ .task_affinity = -1,
+ },
+ .usb = {
+ .init_usb_host_lib = true,
+ .task_stack = 4096,
+ .task_priority = 5,
+ .task_affinity = -1,
+ },
+ };
+ }
+ esp_video_init_config_t video_config = {
+ .usb_uvc = &usb_uvc_config,
+ };
+ camera_ = new EspVideo(video_config);
+ }
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+
+public:
+ ESP_S3_LCD_EV_Board_2() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializeRGB_GC9503V_Display();
+ InitializeTouch();
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ InitializeCamera();
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ true);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ //娣诲姞褰╃伅鏄剧ず鐘舵�侊紝濡傛灉浜害澶殫鍙互鍘绘洿鏀归粯璁や寒搴﹀�� DEFAULT_BRIGHTNESS 鍦╨ed鐨剆igle_led.cc涓�
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+
+};
+
+DECLARE_BOARD(ESP_S3_LCD_EV_Board_2);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c
new file mode 100644
index 0000000..4db550e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.c
@@ -0,0 +1,504 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <driver/gpio.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_check.h>
+#include <esp_lcd_panel_commands.h>
+#include <esp_lcd_panel_interface.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#include "esp_lcd_gc9503.h"
+
+#define GC9503_CMD_MADCTL (0xB1) // Memory data access control
+#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control
+#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top
+#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left
+#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR
+
+typedef struct
+{
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register
+ uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register
+ const gc9503_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+ struct
+ {
+ unsigned int mirror_by_cmd : 1;
+ unsigned int auto_del_panel_io : 1;
+ unsigned int display_on_off_use_cmd : 1;
+ unsigned int reset_level : 1;
+ } flags;
+ // To save the original functions of RGB panel
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+ esp_err_t (*reset)(esp_lcd_panel_t *panel);
+ esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
+ esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
+} gc9503_panel_t;
+
+static const char *TAG = "gc9503";
+
+static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503);
+
+static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off);
+
+esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+ ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
+ gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config;
+ ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary");
+ ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd,
+ ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together");
+
+ esp_err_t ret = ESP_OK;
+ gpio_config_t io_conf = {0};
+
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t));
+ ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT;
+ switch (panel_dev_config->rgb_ele_order)
+ {
+ case LCD_RGB_ELEMENT_ORDER_RGB:
+ gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT;
+ break;
+ case LCD_RGB_ELEMENT_ORDER_BGR:
+ gc9503->madctl_val |= GC9503_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
+ break;
+ }
+
+ gc9503->colmod_val = 0;
+ switch (panel_dev_config->bits_per_pixel)
+ {
+ case 16: // RGB565
+ gc9503->colmod_val = 0x50;
+ break;
+ case 18: // RGB666
+ gc9503->colmod_val = 0x60;
+ break;
+ case 24: // RGB888
+ gc9503->colmod_val = 0x70;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ gc9503->io = io;
+ gc9503->init_cmds = vendor_config->init_cmds;
+ gc9503->init_cmds_size = vendor_config->init_cmds_size;
+ gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high;
+ gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io;
+ gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd;
+ gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1;
+
+ if (gc9503->flags.auto_del_panel_io)
+ {
+ if (gc9503->reset_gpio_num >= 0)
+ { // Perform hardware reset
+ gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level);
+ }
+ else
+ { // Perform software reset
+ ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed");
+ }
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ /**
+ * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface
+ * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before
+ * `esp_lcd_new_rgb_panel()` is called.
+ */
+ ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed");
+ // After sending the initialization commands, the 3-wire SPI interface can be deleted
+ ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed");
+ gc9503->io = NULL;
+ ESP_LOGD(TAG, "delete panel IO");
+ }
+
+ // Create RGB panel
+ ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed");
+ ESP_LOGD(TAG, "new RGB panel @%p", ret_panel);
+
+ // Save the original functions of RGB panel
+ gc9503->init = (*ret_panel)->init;
+ gc9503->del = (*ret_panel)->del;
+ gc9503->reset = (*ret_panel)->reset;
+ gc9503->mirror = (*ret_panel)->mirror;
+ gc9503->disp_on_off = (*ret_panel)->disp_on_off;
+ // Overwrite the functions of RGB panel
+ (*ret_panel)->init = panel_gc9503_init;
+ (*ret_panel)->del = panel_gc9503_del;
+ (*ret_panel)->reset = panel_gc9503_reset;
+ (*ret_panel)->mirror = panel_gc9503_mirror;
+ (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off;
+ (*ret_panel)->user_data = gc9503;
+ ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503);
+
+ // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR,
+ // ESP_LCD_GC9503_VER_PATCH);
+ return ESP_OK;
+
+err:
+ if (gc9503)
+ {
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(gc9503);
+ }
+ return ret;
+}
+
+// *INDENT-OFF*
+// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = {
+// // {cmd, { data }, data_size, delay_ms}
+// {0x11, (uint8_t []){0x00}, 0, 120},
+
+// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0},
+// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0},
+// {0xc1, (uint8_t []){0x3f}, 1, 0},
+// {0xc2, (uint8_t []){0x0e}, 1, 0},
+// {0xc6, (uint8_t []){0xf8}, 1, 0},
+// {0xc9, (uint8_t []){0x10}, 1, 0},
+// {0xcd, (uint8_t []){0x25}, 1, 0},
+// {0xf8, (uint8_t []){0x8a}, 1, 0},
+// {0xac, (uint8_t []){0x45}, 1, 0},
+// {0xa0, (uint8_t []){0xdd}, 1, 0},
+// {0xa7, (uint8_t []){0x47}, 1, 0},
+// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0},
+// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0},
+// {0xa3, (uint8_t []){0xee}, 1, 0},
+// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0},
+// {0x71, (uint8_t []){0x48}, 1, 0},
+// {0x72, (uint8_t []){0x48}, 1, 0},
+// {0x73, (uint8_t []){0x00, 0x44}, 2, 0},
+// {0x97, (uint8_t []){0xee}, 1, 0},
+// {0x83, (uint8_t []){0x93}, 1, 0},
+// {0x9a, (uint8_t []){0x72}, 1, 0},
+// {0x9b, (uint8_t []){0x5a}, 1, 0},
+// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0},
+// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0},
+// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0},
+// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0},
+// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0},
+// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0},
+// {0x6b, (uint8_t []){0x07}, 1, 0},
+// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0},
+// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0},
+// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0x11, (uint8_t []){0x00}, 0, 120},
+// {0x29, (uint8_t []){0x00}, 0, 20},
+// };
+static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {0x11, (uint8_t[]){}, 0, 20},
+
+ {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0},
+ {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0},
+ {0xc1, (uint8_t []){0x3f}, 1, 0},
+ {0xc2, (uint8_t []){0x0e}, 1, 0},
+ {0xc6, (uint8_t []){0xf8}, 1, 0},
+ {0xc9, (uint8_t []){0x10}, 1, 0},
+ {0xcd, (uint8_t []){0x25}, 1, 0},
+ {0xf8, (uint8_t []){0x8a}, 1, 0},
+ {0xac, (uint8_t []){0x45}, 1, 0},
+ {0xa0, (uint8_t []){0xdd}, 1, 0},
+ {0xa7, (uint8_t []){0x47}, 1, 0},
+ {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0},
+ {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0},
+ {0xa3, (uint8_t []){0xee}, 1, 0},
+ {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0},
+ {0x71, (uint8_t []){0x48}, 1, 0},
+ {0x72, (uint8_t []){0x48}, 1, 0},
+ {0x73, (uint8_t []){0x00, 0x44}, 2, 0},
+ {0x97, (uint8_t []){0xee}, 1, 0},
+ {0x83, (uint8_t []){0x93}, 1, 0},
+ {0x9a, (uint8_t []){0x72}, 1, 0},
+ {0x9b, (uint8_t []){0x5a}, 1, 0},
+ {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0},
+ {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0},
+ {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0},
+ {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0},
+ {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0},
+ {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0},
+ {0x6b, (uint8_t []){0x07}, 1, 0},
+ {0x7a, (uint8_t []){0x08, 0x13}, 2, 0},
+ {0x7b, (uint8_t []){0x08, 0x13}, 2, 0},
+ {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0x11, (uint8_t []){0x00}, 0, 120},
+ {0x29, (uint8_t []){0x00}, 0, 20},
+
+ };
+
+// *INDENT-OFF*
+
+static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503)
+{
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){
+ gc9503->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){
+ gc9503->colmod_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+
+ // Vendor specific initialization, it can be different between manufacturers
+ // should consult the LCD supplier for initialization sequence code
+ const gc9503_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ if (gc9503->init_cmds)
+ {
+ init_cmds = gc9503->init_cmds;
+ init_cmds_size = gc9503->init_cmds_size;
+ }
+ else
+ {
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t);
+ }
+
+ bool is_cmd_overwritten = false;
+ for (int i = 0; i < init_cmds_size; i++)
+ {
+ // Check if the command has been used or conflicts with the internal
+ switch (init_cmds[i].cmd)
+ {
+ case LCD_CMD_MADCTL:
+ is_cmd_overwritten = true;
+ gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ case LCD_CMD_COLMOD:
+ is_cmd_overwritten = true;
+ gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ default:
+ is_cmd_overwritten = false;
+ break;
+ }
+
+ if (is_cmd_overwritten)
+ {
+ ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
+ init_cmds[i].cmd);
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes),
+ TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ }
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+
+ if (!gc9503->flags.auto_del_panel_io)
+ {
+ ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed");
+ }
+ // Init RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+
+ if (gc9503->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(gc9503->reset_gpio_num);
+ }
+ // Delete RGB panel
+ gc9503->del(panel);
+ free(gc9503);
+ ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503);
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ // Perform hardware reset
+ if (gc9503->reset_gpio_num >= 0)
+ {
+ gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ else if (io)
+ { // Perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ // Reset RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ if (gc9503->flags.mirror_by_cmd)
+ {
+ ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
+ // Control mirror through LCD command
+ if (mirror_x)
+ {
+ gc9503->madctl_val |= GC9503_CMD_GS_BIT;
+ }
+ else
+ {
+ gc9503->madctl_val &= ~GC9503_CMD_GS_BIT;
+ }
+ if (mirror_y)
+ {
+ gc9503->madctl_val |= GC9503_CMD_SS_BIT;
+ }
+ else
+ {
+ gc9503->madctl_val &= ~GC9503_CMD_SS_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){
+ gc9503->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+ }
+ else
+ {
+ // Control mirror through RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed");
+ }
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+ int command = 0;
+
+ if (gc9503->flags.display_on_off_use_cmd)
+ {
+ ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
+ // Control display on/off through LCD command
+ if (on_off)
+ {
+ command = LCD_CMD_DISPON;
+ }
+ else
+ {
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ }
+ else
+ {
+ // Control display on/off through display control signal
+ ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed");
+ }
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h
new file mode 100644
index 0000000..4d926d9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/esp_lcd_gc9503.h
@@ -0,0 +1,146 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/**
+ * @file
+ * @brief ESP LCD: GC9503
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_rgb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} gc9503_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct {
+ const esp_lcd_rgb_panel_config_t *rgb_config; /*!< RGB panel configuration */
+ const gc9503_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+ struct {
+ unsigned int mirror_by_cmd: 1; /*<! The `mirror()` function will be implemented by LCD command if set to 1.
+ * Otherwise, the function will be implemented by software.
+ */
+ unsigned int auto_del_panel_io: 1; /*<! Delete the panel IO instance automatically if set to 1. All `*_by_cmd` flags will be invalid.
+ * If the panel IO pins are sharing other pins of the RGB interface to save GPIOs,
+ * Please set it to 1 to release the panel IO and its pins (except CS signal).
+ */
+ } flags;
+} gc9503_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model GC9503
+ *
+ * @note When `auto_del_panel_io` is set to 1, this function will first initialize the GC9503 with vendor specific initialization and then calls `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will only initialize RGB.
+ * @note When `auto_del_panel_io` is set to 0, this function will only call `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will initialize both the GC9503 and RGB.
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config General panel device configuration (`vendor_config` and `rgb_config` are necessary)
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ * - Otherwise on fail
+ */
+esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel);
+
+/**
+ * @brief 3-wire SPI panel IO configuration structure
+ *
+ * @param[in] line_cfg SPI line configuration
+ * @param[in] scl_active_edge SCL signal active edge, 0: rising edge, 1: falling edge
+ *
+ */
+#define GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_cfg, scl_active_edge) \
+ { \
+ .line_config = line_cfg, \
+ .expect_clk_speed = PANEL_IO_3WIRE_SPI_CLK_MAX, \
+ .spi_mode = scl_active_edge ? 1 : 0, \
+ .lcd_cmd_bytes = 1, \
+ .lcd_param_bytes = 1, \
+ .flags = { \
+ .use_dc_bit = 1, \
+ .dc_zero_on_data = 0, \
+ .lsb_first = 0, \
+ .cs_high_active = 0, \
+ .del_keep_cs_inactive = 1, \
+ }, \
+ }
+
+/**
+ * @brief RGB timing structure
+ *
+ * @note refresh_rate = (pclk_hz * data_width) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
+ * / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
+ * / bits_per_pixel
+ * .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 384, \
+ .v_res = 960, \
+ .hsync_pulse_width = 24, \
+ .hsync_back_porch = 20, \
+ .hsync_front_porch = 40, \
+ .vsync_pulse_width = 30, \
+ .vsync_back_porch = 18, \
+ .vsync_front_porch = 20, \
+ .flags.pclk_active_neg = 0, \
+
+ .hsync_pulse_width = 24, \
+ .hsync_back_porch = 20, \
+ .hsync_front_porch = 40, \
+ .vsync_pulse_width = 30, \
+ .vsync_back_porch = 18, \
+ .vsync_front_porch = 20, \
+
+ */
+
+ #define GC9503_800_480_PANEL_60HZ_RGB_TIMING() \
+ { \
+ .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 800, \
+ .v_res = 480, \
+ .hsync_pulse_width = 10, \
+ .hsync_back_porch = 10, \
+ .hsync_front_porch = 20, \
+ .vsync_pulse_width = 10, \
+ .vsync_back_porch = 10, \
+ .vsync_front_porch = 10, \
+ .flags = { \
+ .hsync_idle_low = 0, \
+ .vsync_idle_low = 0, \
+ .de_idle_high = 0, \
+ .pclk_active_neg = 0, \
+ .pclk_idle_high = 0, \
+ }, \
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/pin_config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/pin_config.h
new file mode 100644
index 0000000..233b08e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board-2/pin_config.h
@@ -0,0 +1,52 @@
+
+#pragma once
+
+//鍏充簬寮�鍙戞澘鍜屽睆骞曠殑璧勬枡鍙傝��
+//https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html
+
+#define GC9503V_LCD_H_RES 800
+#define GC9503V_LCD_V_RES 480
+
+
+#define GC9503V_LCD_LVGL_DIRECT_MODE (1)
+#define GC9503V_LCD_LVGL_AVOID_TEAR (1)
+#define GC9503V_LCD_RGB_BOUNCE_BUFFER_MODE (1)
+#define GC9503V_LCD_DRAW_BUFF_DOUBLE (0)
+#define GC9503V_LCD_DRAW_BUFF_HEIGHT (100)
+#define GC9503V_LCD_RGB_BUFFER_NUMS (2)
+#define GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT (10)
+
+#define GC9503V_LCD_PIXEL_CLOCK_HZ (16 * 1000 * 1000)
+#define GC9503V_LCD_BK_LIGHT_ON_LEVEL 1
+#define GC9503V_LCD_BK_LIGHT_OFF_LEVEL !GC9503V_LCD_BK_LIGHT_ON_LEVEL
+
+#define GC9503V_PIN_NUM_BK_LIGHT GPIO_NUM_NC //GPIO_NUM_4
+#define GC9503V_PIN_NUM_HSYNC GPIO_NUM_46
+#define GC9503V_PIN_NUM_VSYNC GPIO_NUM_3
+#define GC9503V_PIN_NUM_DE GPIO_NUM_17
+#define GC9503V_PIN_NUM_PCLK GPIO_NUM_9
+
+#define GC9503V_PIN_NUM_DATA0 GPIO_NUM_10 // B0
+#define GC9503V_PIN_NUM_DATA1 GPIO_NUM_11 // B1
+#define GC9503V_PIN_NUM_DATA2 GPIO_NUM_12 // B2
+#define GC9503V_PIN_NUM_DATA3 GPIO_NUM_13 // B3
+#define GC9503V_PIN_NUM_DATA4 GPIO_NUM_14 // B4
+
+#define GC9503V_PIN_NUM_DATA5 GPIO_NUM_21 // G0
+#define GC9503V_PIN_NUM_DATA6 GPIO_NUM_8 // G1
+#define GC9503V_PIN_NUM_DATA7 GPIO_NUM_18 // G2
+#define GC9503V_PIN_NUM_DATA8 GPIO_NUM_45 // G3
+#define GC9503V_PIN_NUM_DATA9 GPIO_NUM_38 // G4
+#define GC9503V_PIN_NUM_DATA10 GPIO_NUM_39 // G5
+
+#define GC9503V_PIN_NUM_DATA11 GPIO_NUM_40 // R0
+#define GC9503V_PIN_NUM_DATA12 GPIO_NUM_41 // R1
+#define GC9503V_PIN_NUM_DATA13 GPIO_NUM_42 // R2
+#define GC9503V_PIN_NUM_DATA14 GPIO_NUM_2 // R3
+#define GC9503V_PIN_NUM_DATA15 GPIO_NUM_1 // R4
+
+#define GC9503V_PIN_NUM_DISP_EN -1
+
+#define GC9503V_LCD_IO_SPI_CS_1 (IO_EXPANDER_PIN_NUM_1)
+#define GC9503V_LCD_IO_SPI_SCL_1 (IO_EXPANDER_PIN_NUM_2)
+#define GC9503V_LCD_IO_SPI_SDO_1 (IO_EXPANDER_PIN_NUM_3)
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/README.md
new file mode 100644
index 0000000..8696f05
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/README.md
@@ -0,0 +1,11 @@
+璇风‘璁よ嚜宸辩殑寮�鍙戞澘纭欢鐗堟湰锛屽鏋滅‖浠剁増鏈紝鍦ㄩ厤缃腑杩涜ev_board type杩涜閫夋嫨
+1.4涓�1.5鍙湁io杩涜鍙樻洿
+鍙互鏌ョ湅瀹樻柟鏂囨。锛岀‘璁ゅ叿浣撶粏鑺俬ttps://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html
+鍏蜂綋璋冩暣涓猴細
+I2C_SCL IO18 -> IO48
+I2C_SDA IO8 -> IO47
+LCD_DATA6 IO47 -> IO8
+LCD_DATA7 IO48 -> IO18
+
+
+鏈増鏈彧鏀寔浜�480x480鐨勫睆骞�
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.h
new file mode 100644
index 0000000..4db1775
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.h
@@ -0,0 +1,53 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_7
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6
+
+#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_0)
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+
+//濡傛灉寮�鍙戞澘鏄疺1.4 IO 瀹氫箟涓�
+#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p4
+ #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+ #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
+#endif
+//濡傛灉寮�鍙戞澘鏄疺1.5 IO 瀹氫箟涓�
+#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p5
+ #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+ #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#endif
+
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR 0x82
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_4
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_WIDTH 480
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_19
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.json
new file mode 100644
index 0000000..55d1602
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-s3-lcd-ev-board-1p4",
+ "sdkconfig_append": [
+ "CONFIG_ESP_S3_LCD_EV_Board_1p4=y"
+ ]
+ },
+ {
+ "name": "esp-s3-lcd-ev-board-1p5",
+ "sdkconfig_append": [
+ "CONFIG_ESP_S3_LCD_EV_Board_1p5=y"
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc
new file mode 100644
index 0000000..2562df3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp-s3-lcd-ev-board.cc
@@ -0,0 +1,240 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "pin_config.h"
+#include "esp_video.h"
+
+#include "config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include "esp_lcd_gc9503.h"
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_io_additions.h>
+
+#include "esp_io_expander_tca9554.h"
+
+#define TAG "ESP_S3_LCD_EV_Board"
+
+class ESP_S3_LCD_EV_Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ EspVideo* camera_;
+
+ //add support ev board lcd
+ esp_io_expander_handle_t expander = NULL;
+
+ void InitializeRGB_GC9503V_Display() {
+ ESP_LOGI(TAG, "Init GC9503V");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+
+ //add support ev board lcd
+ gpio_config_t io_conf = {
+ .pin_bit_mask = BIT64(GC9503V_PIN_NUM_VSYNC),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ };
+
+ gpio_config(&io_conf);
+ gpio_set_level(GC9503V_PIN_NUM_VSYNC, 1);
+
+ ESP_LOGI(TAG, "Install 3-wire SPI panel IO");
+ spi_line_config_t line_config = {
+ .cs_io_type = IO_TYPE_EXPANDER,
+ .cs_expander_pin = GC9503V_LCD_IO_SPI_CS_1,
+ .scl_io_type = IO_TYPE_EXPANDER,
+ .scl_expander_pin = GC9503V_LCD_IO_SPI_SCL_1,
+ .sda_io_type = IO_TYPE_EXPANDER,
+ .sda_expander_pin = GC9503V_LCD_IO_SPI_SDO_1,
+ .io_expander = expander,
+ };
+
+ esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0);
+ int espok = esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io);
+ ESP_LOGI(TAG, "Install 3-wire SPI panel IO:%d",espok);
+
+ ESP_LOGI(TAG, "Install RGB LCD panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_rgb_panel_config_t rgb_config = {
+ .clk_src = LCD_CLK_SRC_PLL160M,
+ //.timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(),
+ //add support ev board
+ .timings = GC9503_480_480_PANEL_60HZ_RGB_TIMING(),
+ .data_width = 16, // RGB565 in parallel mode, thus 16bit in width
+ .bits_per_pixel = 16,
+ .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS,
+ .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT,
+ .dma_burst_size = 64,
+ .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC,
+ .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC,
+ .de_gpio_num = GC9503V_PIN_NUM_DE,
+ .pclk_gpio_num = GC9503V_PIN_NUM_PCLK,
+ .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN,
+ .data_gpio_nums = {
+ GC9503V_PIN_NUM_DATA0,
+ GC9503V_PIN_NUM_DATA1,
+ GC9503V_PIN_NUM_DATA2,
+ GC9503V_PIN_NUM_DATA3,
+ GC9503V_PIN_NUM_DATA4,
+ GC9503V_PIN_NUM_DATA5,
+ GC9503V_PIN_NUM_DATA6,
+ GC9503V_PIN_NUM_DATA7,
+ GC9503V_PIN_NUM_DATA8,
+ GC9503V_PIN_NUM_DATA9,
+ GC9503V_PIN_NUM_DATA10,
+ GC9503V_PIN_NUM_DATA11,
+ GC9503V_PIN_NUM_DATA12,
+ GC9503V_PIN_NUM_DATA13,
+ GC9503V_PIN_NUM_DATA14,
+ GC9503V_PIN_NUM_DATA15,
+ },
+ .flags= {
+ .fb_in_psram = true, // allocate frame buffer in PSRAM
+ }
+ };
+
+ ESP_LOGI(TAG, "Initialize RGB LCD panel");
+
+ gc9503_vendor_config_t vendor_config = {
+ .rgb_config = &rgb_config,
+ .flags = {
+ .mirror_by_cmd = 0,
+ .auto_del_panel_io = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = -1,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ // .bits_per_pixel = 16,
+ //add surpport ev board
+ .bits_per_pixel = 18,
+ .vendor_config = &vendor_config,
+ };
+ (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle));
+ (esp_lcd_panel_reset(panel_handle));
+ (esp_lcd_panel_init(panel_handle));
+
+ display_ = new RgbLcdDisplay(panel_io, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ //add support ev board lcd amp
+ //鍒濆鍖栨墿灞昳o鍙�
+ esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, 0x20, &expander);
+ /* Setup power amplifier pin, set default to enable */
+ esp_io_expander_set_dir(expander, BSP_POWER_AMP_IO, IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(expander, BSP_POWER_AMP_IO, true);
+
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ void InitializeCamera() {
+ esp_video_init_usb_uvc_config_t usb_uvc_config = {
+ .uvc = {
+ .uvc_dev_num = 1,
+ .task_stack = 4096,
+ .task_priority = 5,
+ .task_affinity = -1,
+ },
+ .usb = {
+ .init_usb_host_lib = true,
+ .task_stack = 4096,
+ .task_priority = 5,
+ .task_affinity = -1,
+ },
+ };
+
+ esp_video_init_config_t video_config = {
+ .usb_uvc = &usb_uvc_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ }
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+
+public:
+ ESP_S3_LCD_EV_Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializeRGB_GC9503V_Display();
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ InitializeCamera();
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ codec_i2c_bus_,
+ 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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ true);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ //娣诲姞褰╃伅鏄剧ず鐘舵�侊紝濡傛灉浜害澶殫鍙互鍘绘洿鏀归粯璁や寒搴﹀�� DEFAULT_BRIGHTNESS 鍦╨ed鐨剆igle_led.cc涓�
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+
+};
+
+DECLARE_BOARD(ESP_S3_LCD_EV_Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c
new file mode 100644
index 0000000..321472f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.c
@@ -0,0 +1,154 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <inttypes.h>
+#include <string.h>
+#include <stdlib.h>
+#include "esp_bit_defs.h"
+#include "esp_check.h"
+#include "esp_log.h"
+#include "esp_io_expander.h"
+#include "esp_io_expander_tca9554.h"
+
+/* I2C communication related */
+#define I2C_TIMEOUT_MS (1000)
+#define I2C_CLK_SPEED (400000)
+
+#define IO_COUNT (8)
+
+/* Register address */
+#define INPUT_REG_ADDR (0x00)
+#define OUTPUT_REG_ADDR (0x01)
+#define DIRECTION_REG_ADDR (0x03)
+
+/* Default register value on power-up */
+#define DIR_REG_DEFAULT_VAL (0xff)
+#define OUT_REG_DEFAULT_VAL (0xff)
+
+/**
+ * @brief Device Structure Type
+ *
+ */
+typedef struct {
+ esp_io_expander_t base;
+ i2c_master_dev_handle_t i2c_handle;
+ struct {
+ uint8_t direction;
+ uint8_t output;
+ } regs;
+} esp_io_expander_tca9554_t;
+
+static char *TAG = "tca9554";
+
+static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value);
+static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value);
+static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value);
+static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value);
+static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value);
+static esp_err_t reset(esp_io_expander_t *handle);
+static esp_err_t del(esp_io_expander_t *handle);
+
+esp_err_t esp_io_expander_new_i2c_tca9554(i2c_master_bus_handle_t i2c_bus, uint32_t dev_addr, esp_io_expander_handle_t *handle_ret)
+{
+ ESP_RETURN_ON_FALSE(handle_ret != NULL, ESP_ERR_INVALID_ARG, TAG, "Invalid handle_ret");
+
+ // Allocate memory for driver object
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)calloc(1, sizeof(esp_io_expander_tca9554_t));
+ ESP_RETURN_ON_FALSE(tca9554 != NULL, ESP_ERR_NO_MEM, TAG, "Malloc failed");
+
+ // Add new I2C device
+ esp_err_t ret = ESP_OK;
+ const i2c_device_config_t i2c_dev_cfg = {
+ .device_address = dev_addr,
+ .scl_speed_hz = I2C_CLK_SPEED,
+ };
+ ESP_GOTO_ON_ERROR(i2c_master_bus_add_device(i2c_bus, &i2c_dev_cfg, &tca9554->i2c_handle), err, TAG, "Add new I2C device failed");
+
+ tca9554->base.config.io_count = IO_COUNT;
+ tca9554->base.config.flags.dir_out_bit_zero = 1;
+ tca9554->base.read_input_reg = read_input_reg;
+ tca9554->base.write_output_reg = write_output_reg;
+ tca9554->base.read_output_reg = read_output_reg;
+ tca9554->base.write_direction_reg = write_direction_reg;
+ tca9554->base.read_direction_reg = read_direction_reg;
+ tca9554->base.del = del;
+ tca9554->base.reset = reset;
+
+ /* Reset configuration and register status */
+ ESP_GOTO_ON_ERROR(reset(&tca9554->base), err, TAG, "Reset failed");
+
+ *handle_ret = &tca9554->base;
+ return ESP_OK;
+err:
+ free(tca9554);
+ return ret;
+}
+
+static esp_err_t read_input_reg(esp_io_expander_handle_t handle, uint32_t *value)
+{
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base);
+
+ uint8_t temp = 0;
+ ESP_RETURN_ON_ERROR(i2c_master_transmit_receive(tca9554->i2c_handle, (uint8_t[]) {
+ INPUT_REG_ADDR
+ }, 1, &temp, sizeof(temp), I2C_TIMEOUT_MS), TAG, "Read input reg failed");
+ *value = temp;
+ return ESP_OK;
+}
+
+static esp_err_t write_output_reg(esp_io_expander_handle_t handle, uint32_t value)
+{
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base);
+ value &= 0xff;
+
+ uint8_t data[] = {OUTPUT_REG_ADDR, value};
+ ESP_RETURN_ON_ERROR(i2c_master_transmit(tca9554->i2c_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write output reg failed");
+ tca9554->regs.output = value;
+ return ESP_OK;
+}
+
+static esp_err_t read_output_reg(esp_io_expander_handle_t handle, uint32_t *value)
+{
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base);
+
+ *value = tca9554->regs.output;
+ return ESP_OK;
+}
+
+static esp_err_t write_direction_reg(esp_io_expander_handle_t handle, uint32_t value)
+{
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base);
+ value &= 0xff;
+
+ uint8_t data[] = {DIRECTION_REG_ADDR, value};
+ ESP_RETURN_ON_ERROR(i2c_master_transmit(tca9554->i2c_handle, data, sizeof(data), I2C_TIMEOUT_MS), TAG, "Write direction reg failed");
+ tca9554->regs.direction = value;
+ return ESP_OK;
+}
+
+static esp_err_t read_direction_reg(esp_io_expander_handle_t handle, uint32_t *value)
+{
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base);
+
+ *value = tca9554->regs.direction;
+ return ESP_OK;
+}
+
+static esp_err_t reset(esp_io_expander_t *handle)
+{
+ ESP_RETURN_ON_ERROR(write_direction_reg(handle, DIR_REG_DEFAULT_VAL), TAG, "Write dir reg failed");
+ ESP_RETURN_ON_ERROR(write_output_reg(handle, OUT_REG_DEFAULT_VAL), TAG, "Write output reg failed");
+ return ESP_OK;
+}
+
+static esp_err_t del(esp_io_expander_t *handle)
+{
+ esp_io_expander_tca9554_t *tca9554 = (esp_io_expander_tca9554_t *)__containerof(handle, esp_io_expander_tca9554_t, base);
+
+ ESP_RETURN_ON_ERROR(i2c_master_bus_rm_device(tca9554->i2c_handle), TAG, "Remove I2C device failed");
+ free(tca9554);
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h
new file mode 100644
index 0000000..192107c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_io_expander_tca9554.h
@@ -0,0 +1,90 @@
+/*
+ * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file
+ * @brief ESP IO expander: TCA9554
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include "esp_err.h"
+#include "driver/i2c_master.h"
+#include "esp_io_expander.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create a TCA9554(A) IO expander object
+ *
+ * @param[in] i2c_bus I2C bus handle. Obtained from `i2c_new_master_bus()`
+ * @param[in] dev_addr I2C device address of chip. Can be `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_XXX` or `ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_XXX`.
+ * @param[out] handle_ret Handle to created IO expander object
+ *
+ * @return
+ * - ESP_OK: Success, otherwise returns ESP_ERR_xxx
+ */
+esp_err_t esp_io_expander_new_i2c_tca9554(i2c_master_bus_handle_t i2c_bus, uint32_t dev_addr, esp_io_expander_handle_t *handle_ret);
+
+/**
+ * @brief I2C address of the TCA9554
+ *
+ * The 8-bit address format is as follows:
+ *
+ * (Slave Address)
+ * 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹封攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ * 鈹屸攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹�
+ * | 0 | 1 | 0 | 0 | A2 | A1 | A0 | R/W |
+ * 鈹斺攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹�
+ * 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ * (Fixed) (Hareware Selectable)
+ *
+ * And the 7-bit slave address is the most important data for users.
+ * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0100000b(0x20).
+ * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000` to init it.
+ */
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000 (0x20)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_001 (0x21)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_010 (0x22)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_011 (0x23)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_100 (0x24)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_101 (0x25)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_110 (0x26)
+#define ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_111 (0x27)
+
+
+/**
+ * @brief I2C address of the TCA9554A
+ *
+ * The 8-bit address format is as follows:
+ *
+ * (Slave Address)
+ * 鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹封攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+ * 鈹屸攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹愨攢鈹�鈹�鈹�鈹�鈹�
+ * | 0 | 1 | 1 | 1 | A2 | A1 | A0 | R/W |
+ * 鈹斺攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹樷攢鈹�鈹�鈹�鈹�鈹�
+ * 鈹斺攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹� 鈹斺攢鈹�鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�鈹�
+ * (Fixed) (Hareware Selectable)
+ *
+ * And the 7-bit slave address is the most important data for users.
+ * For example, if a chip's A0,A1,A2 are connected to GND, it's 7-bit slave address is 0111000b(0x38).
+ * Then users can use `ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000` to init it.
+ */
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000 (0x38)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_001 (0x39)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_010 (0x3A)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_011 (0x3B)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_100 (0x3C)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_101 (0x3D)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_110 (0x3E)
+#define ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_111 (0x3F)
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c
new file mode 100644
index 0000000..4db550e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.c
@@ -0,0 +1,504 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <driver/gpio.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_check.h>
+#include <esp_lcd_panel_commands.h>
+#include <esp_lcd_panel_interface.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#include "esp_lcd_gc9503.h"
+
+#define GC9503_CMD_MADCTL (0xB1) // Memory data access control
+#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control
+#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top
+#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left
+#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR
+
+typedef struct
+{
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register
+ uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register
+ const gc9503_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+ struct
+ {
+ unsigned int mirror_by_cmd : 1;
+ unsigned int auto_del_panel_io : 1;
+ unsigned int display_on_off_use_cmd : 1;
+ unsigned int reset_level : 1;
+ } flags;
+ // To save the original functions of RGB panel
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+ esp_err_t (*reset)(esp_lcd_panel_t *panel);
+ esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
+ esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
+} gc9503_panel_t;
+
+static const char *TAG = "gc9503";
+
+static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503);
+
+static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off);
+
+esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+ ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
+ gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config;
+ ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary");
+ ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd,
+ ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together");
+
+ esp_err_t ret = ESP_OK;
+ gpio_config_t io_conf = {0};
+
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t));
+ ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT;
+ switch (panel_dev_config->rgb_ele_order)
+ {
+ case LCD_RGB_ELEMENT_ORDER_RGB:
+ gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT;
+ break;
+ case LCD_RGB_ELEMENT_ORDER_BGR:
+ gc9503->madctl_val |= GC9503_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
+ break;
+ }
+
+ gc9503->colmod_val = 0;
+ switch (panel_dev_config->bits_per_pixel)
+ {
+ case 16: // RGB565
+ gc9503->colmod_val = 0x50;
+ break;
+ case 18: // RGB666
+ gc9503->colmod_val = 0x60;
+ break;
+ case 24: // RGB888
+ gc9503->colmod_val = 0x70;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ gc9503->io = io;
+ gc9503->init_cmds = vendor_config->init_cmds;
+ gc9503->init_cmds_size = vendor_config->init_cmds_size;
+ gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high;
+ gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io;
+ gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd;
+ gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1;
+
+ if (gc9503->flags.auto_del_panel_io)
+ {
+ if (gc9503->reset_gpio_num >= 0)
+ { // Perform hardware reset
+ gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level);
+ }
+ else
+ { // Perform software reset
+ ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed");
+ }
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ /**
+ * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface
+ * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before
+ * `esp_lcd_new_rgb_panel()` is called.
+ */
+ ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed");
+ // After sending the initialization commands, the 3-wire SPI interface can be deleted
+ ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed");
+ gc9503->io = NULL;
+ ESP_LOGD(TAG, "delete panel IO");
+ }
+
+ // Create RGB panel
+ ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed");
+ ESP_LOGD(TAG, "new RGB panel @%p", ret_panel);
+
+ // Save the original functions of RGB panel
+ gc9503->init = (*ret_panel)->init;
+ gc9503->del = (*ret_panel)->del;
+ gc9503->reset = (*ret_panel)->reset;
+ gc9503->mirror = (*ret_panel)->mirror;
+ gc9503->disp_on_off = (*ret_panel)->disp_on_off;
+ // Overwrite the functions of RGB panel
+ (*ret_panel)->init = panel_gc9503_init;
+ (*ret_panel)->del = panel_gc9503_del;
+ (*ret_panel)->reset = panel_gc9503_reset;
+ (*ret_panel)->mirror = panel_gc9503_mirror;
+ (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off;
+ (*ret_panel)->user_data = gc9503;
+ ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503);
+
+ // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR,
+ // ESP_LCD_GC9503_VER_PATCH);
+ return ESP_OK;
+
+err:
+ if (gc9503)
+ {
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(gc9503);
+ }
+ return ret;
+}
+
+// *INDENT-OFF*
+// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = {
+// // {cmd, { data }, data_size, delay_ms}
+// {0x11, (uint8_t []){0x00}, 0, 120},
+
+// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0},
+// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0},
+// {0xc1, (uint8_t []){0x3f}, 1, 0},
+// {0xc2, (uint8_t []){0x0e}, 1, 0},
+// {0xc6, (uint8_t []){0xf8}, 1, 0},
+// {0xc9, (uint8_t []){0x10}, 1, 0},
+// {0xcd, (uint8_t []){0x25}, 1, 0},
+// {0xf8, (uint8_t []){0x8a}, 1, 0},
+// {0xac, (uint8_t []){0x45}, 1, 0},
+// {0xa0, (uint8_t []){0xdd}, 1, 0},
+// {0xa7, (uint8_t []){0x47}, 1, 0},
+// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0},
+// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0},
+// {0xa3, (uint8_t []){0xee}, 1, 0},
+// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0},
+// {0x71, (uint8_t []){0x48}, 1, 0},
+// {0x72, (uint8_t []){0x48}, 1, 0},
+// {0x73, (uint8_t []){0x00, 0x44}, 2, 0},
+// {0x97, (uint8_t []){0xee}, 1, 0},
+// {0x83, (uint8_t []){0x93}, 1, 0},
+// {0x9a, (uint8_t []){0x72}, 1, 0},
+// {0x9b, (uint8_t []){0x5a}, 1, 0},
+// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0},
+// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0},
+// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0},
+// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0},
+// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0},
+// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0},
+// {0x6b, (uint8_t []){0x07}, 1, 0},
+// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0},
+// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0},
+// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0x11, (uint8_t []){0x00}, 0, 120},
+// {0x29, (uint8_t []){0x00}, 0, 20},
+// };
+static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {0x11, (uint8_t[]){}, 0, 20},
+
+ {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0},
+ {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0},
+ {0xc1, (uint8_t []){0x3f}, 1, 0},
+ {0xc2, (uint8_t []){0x0e}, 1, 0},
+ {0xc6, (uint8_t []){0xf8}, 1, 0},
+ {0xc9, (uint8_t []){0x10}, 1, 0},
+ {0xcd, (uint8_t []){0x25}, 1, 0},
+ {0xf8, (uint8_t []){0x8a}, 1, 0},
+ {0xac, (uint8_t []){0x45}, 1, 0},
+ {0xa0, (uint8_t []){0xdd}, 1, 0},
+ {0xa7, (uint8_t []){0x47}, 1, 0},
+ {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0},
+ {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0},
+ {0xa3, (uint8_t []){0xee}, 1, 0},
+ {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0},
+ {0x71, (uint8_t []){0x48}, 1, 0},
+ {0x72, (uint8_t []){0x48}, 1, 0},
+ {0x73, (uint8_t []){0x00, 0x44}, 2, 0},
+ {0x97, (uint8_t []){0xee}, 1, 0},
+ {0x83, (uint8_t []){0x93}, 1, 0},
+ {0x9a, (uint8_t []){0x72}, 1, 0},
+ {0x9b, (uint8_t []){0x5a}, 1, 0},
+ {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0},
+ {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+ 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0},
+ {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+ {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0},
+ {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0},
+ {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0},
+ {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0},
+ {0x6b, (uint8_t []){0x07}, 1, 0},
+ {0x7a, (uint8_t []){0x08, 0x13}, 2, 0},
+ {0x7b, (uint8_t []){0x08, 0x13}, 2, 0},
+ {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+ 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+ 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+ 0xff}, 52, 0},
+ {0x11, (uint8_t []){0x00}, 0, 120},
+ {0x29, (uint8_t []){0x00}, 0, 20},
+
+ };
+
+// *INDENT-OFF*
+
+static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503)
+{
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){
+ gc9503->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){
+ gc9503->colmod_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+
+ // Vendor specific initialization, it can be different between manufacturers
+ // should consult the LCD supplier for initialization sequence code
+ const gc9503_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ if (gc9503->init_cmds)
+ {
+ init_cmds = gc9503->init_cmds;
+ init_cmds_size = gc9503->init_cmds_size;
+ }
+ else
+ {
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t);
+ }
+
+ bool is_cmd_overwritten = false;
+ for (int i = 0; i < init_cmds_size; i++)
+ {
+ // Check if the command has been used or conflicts with the internal
+ switch (init_cmds[i].cmd)
+ {
+ case LCD_CMD_MADCTL:
+ is_cmd_overwritten = true;
+ gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ case LCD_CMD_COLMOD:
+ is_cmd_overwritten = true;
+ gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ default:
+ is_cmd_overwritten = false;
+ break;
+ }
+
+ if (is_cmd_overwritten)
+ {
+ ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
+ init_cmds[i].cmd);
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes),
+ TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ }
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+
+ if (!gc9503->flags.auto_del_panel_io)
+ {
+ ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed");
+ }
+ // Init RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+
+ if (gc9503->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(gc9503->reset_gpio_num);
+ }
+ // Delete RGB panel
+ gc9503->del(panel);
+ free(gc9503);
+ ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503);
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ // Perform hardware reset
+ if (gc9503->reset_gpio_num >= 0)
+ {
+ gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ else if (io)
+ { // Perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ // Reset RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ if (gc9503->flags.mirror_by_cmd)
+ {
+ ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
+ // Control mirror through LCD command
+ if (mirror_x)
+ {
+ gc9503->madctl_val |= GC9503_CMD_GS_BIT;
+ }
+ else
+ {
+ gc9503->madctl_val &= ~GC9503_CMD_GS_BIT;
+ }
+ if (mirror_y)
+ {
+ gc9503->madctl_val |= GC9503_CMD_SS_BIT;
+ }
+ else
+ {
+ gc9503->madctl_val &= ~GC9503_CMD_SS_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){
+ gc9503->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+ }
+ else
+ {
+ // Control mirror through RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed");
+ }
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+ int command = 0;
+
+ if (gc9503->flags.display_on_off_use_cmd)
+ {
+ ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
+ // Control display on/off through LCD command
+ if (on_off)
+ {
+ command = LCD_CMD_DISPON;
+ }
+ else
+ {
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ }
+ else
+ {
+ // Control display on/off through display control signal
+ ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed");
+ }
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h
new file mode 100644
index 0000000..8feafd1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/esp_lcd_gc9503.h
@@ -0,0 +1,167 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/**
+ * @file
+ * @brief ESP LCD: GC9503
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_rgb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} gc9503_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct {
+ const esp_lcd_rgb_panel_config_t *rgb_config; /*!< RGB panel configuration */
+ const gc9503_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+ struct {
+ unsigned int mirror_by_cmd: 1; /*<! The `mirror()` function will be implemented by LCD command if set to 1.
+ * Otherwise, the function will be implemented by software.
+ */
+ unsigned int auto_del_panel_io: 1; /*<! Delete the panel IO instance automatically if set to 1. All `*_by_cmd` flags will be invalid.
+ * If the panel IO pins are sharing other pins of the RGB interface to save GPIOs,
+ * Please set it to 1 to release the panel IO and its pins (except CS signal).
+ */
+ } flags;
+} gc9503_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model GC9503
+ *
+ * @note When `auto_del_panel_io` is set to 1, this function will first initialize the GC9503 with vendor specific initialization and then calls `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will only initialize RGB.
+ * @note When `auto_del_panel_io` is set to 0, this function will only call `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will initialize both the GC9503 and RGB.
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config General panel device configuration (`vendor_config` and `rgb_config` are necessary)
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ * - Otherwise on fail
+ */
+esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel);
+
+/**
+ * @brief 3-wire SPI panel IO configuration structure
+ *
+ * @param[in] line_cfg SPI line configuration
+ * @param[in] scl_active_edge SCL signal active edge, 0: rising edge, 1: falling edge
+ *
+ */
+#define GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_cfg, scl_active_edge) \
+ { \
+ .line_config = line_cfg, \
+ .expect_clk_speed = PANEL_IO_3WIRE_SPI_CLK_MAX, \
+ .spi_mode = scl_active_edge ? 1 : 0, \
+ .lcd_cmd_bytes = 1, \
+ .lcd_param_bytes = 1, \
+ .flags = { \
+ .use_dc_bit = 1, \
+ .dc_zero_on_data = 0, \
+ .lsb_first = 0, \
+ .cs_high_active = 0, \
+ .del_keep_cs_inactive = 1, \
+ }, \
+ }
+
+/**
+ * @brief RGB timing structure
+ *
+ * @note refresh_rate = (pclk_hz * data_width) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
+ * / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
+ * / bits_per_pixel
+ * .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 384, \
+ .v_res = 960, \
+ .hsync_pulse_width = 24, \
+ .hsync_back_porch = 20, \
+ .hsync_front_porch = 40, \
+ .vsync_pulse_width = 30, \
+ .vsync_back_porch = 18, \
+ .vsync_front_porch = 20, \
+ .flags.pclk_active_neg = 0, \
+
+ .hsync_pulse_width = 24, \
+ .hsync_back_porch = 20, \
+ .hsync_front_porch = 40, \
+ .vsync_pulse_width = 30, \
+ .vsync_back_porch = 18, \
+ .vsync_front_porch = 20, \
+
+ */
+
+ #define GC9503_480_480_PANEL_60HZ_RGB_TIMING() \
+ { \
+ .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 480, \
+ .v_res = 480, \
+ .hsync_pulse_width = 10, \
+ .hsync_back_porch = 10, \
+ .hsync_front_porch = 20, \
+ .vsync_pulse_width = 10, \
+ .vsync_back_porch = 10, \
+ .vsync_front_porch = 10, \
+ .flags = { \
+ .hsync_idle_low = 0, \
+ .vsync_idle_low = 0, \
+ .de_idle_high = 0, \
+ .pclk_active_neg = 0, \
+ .pclk_idle_high = 0, \
+ }, \
+ }
+
+
+#define GC9503_376_960_PANEL_60HZ_RGB_TIMING() \
+ { \
+ .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 376, \
+ .v_res = 960, \
+ .hsync_pulse_width = 8, \
+ .hsync_back_porch = 30, \
+ .hsync_front_porch = 30, \
+ .vsync_pulse_width = 8, \
+ .vsync_back_porch = 16, \
+ .vsync_front_porch = 16, \
+ .flags = { \
+ .hsync_idle_low = 0, \
+ .vsync_idle_low = 0, \
+ .de_idle_high = 0, \
+ .pclk_active_neg = 0, \
+ .pclk_idle_high = 0, \
+ }, \
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/pin_config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/pin_config.h
new file mode 100644
index 0000000..a69fff9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-s3-lcd-ev-board/pin_config.h
@@ -0,0 +1,62 @@
+
+#pragma once
+
+//鍏充簬寮�鍙戞澘鍜屽睆骞曠殑璧勬枡鍙傝��
+//https://docs.espressif.com/projects/esp-dev-kits/en/latest/esp32s3/esp32-s3-lcd-ev-board/user_guide.html
+
+#define GC9503V_LCD_H_RES 480
+#define GC9503V_LCD_V_RES 480
+
+
+#define GC9503V_LCD_LVGL_DIRECT_MODE (1)
+#define GC9503V_LCD_LVGL_AVOID_TEAR (1)
+#define GC9503V_LCD_RGB_BOUNCE_BUFFER_MODE (1)
+#define GC9503V_LCD_DRAW_BUFF_DOUBLE (0)
+#define GC9503V_LCD_DRAW_BUFF_HEIGHT (100)
+#define GC9503V_LCD_RGB_BUFFER_NUMS (2)
+#define GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT (10)
+
+#define GC9503V_LCD_PIXEL_CLOCK_HZ (16 * 1000 * 1000)
+#define GC9503V_LCD_BK_LIGHT_ON_LEVEL 1
+#define GC9503V_LCD_BK_LIGHT_OFF_LEVEL !GC9503V_LCD_BK_LIGHT_ON_LEVEL
+#define GC9503V_PIN_NUM_BK_LIGHT GPIO_NUM_NC //GPIO_NUM_4
+#define GC9503V_PIN_NUM_HSYNC GPIO_NUM_46
+#define GC9503V_PIN_NUM_VSYNC GPIO_NUM_3
+#define GC9503V_PIN_NUM_DE GPIO_NUM_17
+#define GC9503V_PIN_NUM_PCLK GPIO_NUM_9
+
+#define GC9503V_PIN_NUM_DATA0 GPIO_NUM_10 // B0
+#define GC9503V_PIN_NUM_DATA1 GPIO_NUM_11 // B1
+#define GC9503V_PIN_NUM_DATA2 GPIO_NUM_12 // B2
+#define GC9503V_PIN_NUM_DATA3 GPIO_NUM_13 // B3
+#define GC9503V_PIN_NUM_DATA4 GPIO_NUM_14 // B4
+
+#define GC9503V_PIN_NUM_DATA5 GPIO_NUM_21 // G0
+
+//濡傛灉寮�鍙戞澘鏄疺1.4 IO 瀹氫箟涓�
+#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p4
+ #define GC9503V_PIN_NUM_DATA6 GPIO_NUM_47 // G1
+ #define GC9503V_PIN_NUM_DATA7 GPIO_NUM_48 // G2
+#endif
+//濡傛灉寮�鍙戞澘鏄疺1.5 IO 瀹氫箟涓�
+#ifdef CONFIG_ESP_S3_LCD_EV_Board_1p5
+ #define GC9503V_PIN_NUM_DATA6 GPIO_NUM_8 // G1
+ #define GC9503V_PIN_NUM_DATA7 GPIO_NUM_18 // G2
+#endif
+
+
+#define GC9503V_PIN_NUM_DATA8 GPIO_NUM_45 // G3
+#define GC9503V_PIN_NUM_DATA9 GPIO_NUM_38 // G4
+#define GC9503V_PIN_NUM_DATA10 GPIO_NUM_39 // G5
+
+#define GC9503V_PIN_NUM_DATA11 GPIO_NUM_40 // R0
+#define GC9503V_PIN_NUM_DATA12 GPIO_NUM_41 // R1
+#define GC9503V_PIN_NUM_DATA13 GPIO_NUM_42 // R2
+#define GC9503V_PIN_NUM_DATA14 GPIO_NUM_2 // R3
+#define GC9503V_PIN_NUM_DATA15 GPIO_NUM_1 // R4
+
+#define GC9503V_PIN_NUM_DISP_EN -1
+
+#define GC9503V_LCD_IO_SPI_CS_1 (IO_EXPANDER_PIN_NUM_1)
+#define GC9503V_LCD_IO_SPI_SCL_1 (IO_EXPANDER_PIN_NUM_2)
+#define GC9503V_LCD_IO_SPI_SDO_1 (IO_EXPANDER_PIN_NUM_3)
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/README.md
new file mode 100644
index 0000000..b9641c2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/README.md
@@ -0,0 +1,39 @@
+# ESP-SensairShuttle
+
+## 绠�浠�
+
+<div align="center">
+ <a href="https://docs.espressif.com/projects/esp-dev-kits/zh_CN/latest/esp32c5/esp-sensairshuttle/index.html">
+ <b> 寮�鍙戠増鏂囨。 </b>
+ </a>
+ |
+ <a href="#浼犳劅鍣�--shuttleboard-瀛愭澘鏀寔">
+ <b> 浼犳劅鍣� & <i>ShuttleBoard</i> 鏂囨。 </b>
+ </a>
+</div>
+
+ESP-SensairShuttle 鏄箰閼惡鎵� Bosch Sensortec 闈㈠悜**鍔ㄤ綔鎰熺煡**涓�**澶фā鍨嬩汉鏈轰氦浜�**鍦烘櫙鑱斿悎鎺ㄥ嚭鐨勫紑鍙戞澘銆�
+
+ESP-SensairShuttle 涓绘帶閲囩敤涔愰懌 ESP32-C5-WROOM-1-N16R8 妯$粍锛屽叿鏈� 2.4 & 5 GHz 鍙岄 Wi-Fi 6 (802.11ax)銆丅luetooth庐 5 (LE)銆乑igbee 鍙� Thread (802.15.4) 鏃犵嚎閫氫俊鑳藉姏銆�
+
+## 浼犳劅鍣� & _ShuttleBoard_ 瀛愭澘鏀寔
+
+鍗冲皢鎺ㄥ嚭锛屾暚璇锋湡寰呫��
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+鐢变簬 ESP-SensairShuttle 闇�瑕侀厤缃緝澶氱殑 sdkconfig 閫夐」锛屾帹鑽愪娇鐢ㄧ紪璇戣剼鏈紪璇戙��
+
+**缂栬瘧**
+
+```bash
+python ./scripts/release.py esp-sensairshuttle
+```
+
+濡傞渶鎵嬪姩缂栬瘧锛岃鍙傝�� `main/boards/esp-sensairshuttle/config.json` 淇敼 menuconfig 瀵瑰簲閫夐」銆�
+
+**鐑у綍**
+
+```bash
+idf.py flash
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc
new file mode 100644
index 0000000..def028f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.cc
@@ -0,0 +1,250 @@
+#include "adc_pdm_audio_codec.h"
+
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <driver/i2c.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+#include "driver/i2s_pdm.h"
+#include "soc/gpio_sig_map.h"
+#include "soc/io_mux_reg.h"
+#include "hal/rtc_io_hal.h"
+#include "hal/gpio_ll.h"
+#include "settings.h"
+#include "config.h"
+
+static const char TAG[] = "AdcPdmAudioCodec";
+
+#define BSP_I2S_GPIO_CFG(_dout) \
+ { \
+ .clk = GPIO_NUM_NC, \
+ .dout = _dout, \
+ .invert_flags = { \
+ .clk_inv = false, \
+ }, \
+ }
+
+/**
+ * @brief Mono Duplex I2S configuration structure
+ *
+ * This configuration is used by default in bsp_audio_init()
+ */
+#define BSP_I2S_DUPLEX_MONO_CFG(_sample_rate, _dout) \
+ { \
+ .clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG(_sample_rate), \
+ .slot_cfg = I2S_PDM_TX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO), \
+ .gpio_cfg = BSP_I2S_GPIO_CFG(_dout), \
+ }
+
+AdcPdmAudioCodec::AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
+ uint32_t adc_mic_channel, gpio_num_t pdm_speak_p,gpio_num_t pdm_speak_n, gpio_num_t pa_ctl) {
+
+ input_reference_ = false;
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ audio_codec_adc_cfg_t cfg = {};
+ cfg.handle = NULL;
+ cfg.continuous_cfg.max_store_buf_size = 1024 * 2;
+ cfg.continuous_cfg.conv_frame_size = 1024;
+ cfg.continuous_cfg.sample_freq_hz = (uint32_t)input_sample_rate;
+ cfg.continuous_cfg.conv_mode = ADC_CONV_SINGLE_UNIT_1;
+ cfg.continuous_cfg.format = ADC_DIGI_OUTPUT_FORMAT_TYPE2;
+ cfg.continuous_cfg.pattern_num = 1;
+ cfg.continuous_cfg.cfg_mode = AUDIO_CODEC_ADC_CFG_MODE_SINGLE_UNIT;
+ cfg.continuous_cfg.cfg.single_unit.unit_id = ADC_UNIT_1;
+ cfg.continuous_cfg.cfg.single_unit.atten = ADC_ATTEN_DB_12;
+ cfg.continuous_cfg.cfg.single_unit.bit_width = ADC_BITWIDTH_12;
+ cfg.continuous_cfg.cfg.single_unit.channel_id[0] = (uint8_t)adc_mic_channel;
+ const audio_codec_data_if_t *adc_if = audio_codec_new_adc_data(&cfg);
+
+ esp_codec_dev_cfg_t codec_dev_cfg = {
+ .dev_type = ESP_CODEC_DEV_TYPE_IN,
+ .data_if = adc_if,
+ };
+ input_dev_ = esp_codec_dev_new(&codec_dev_cfg);
+ if (!input_dev_) {
+ ESP_LOGE(TAG, "Failed to create codec device");
+ return;
+ }
+
+ i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
+ chan_cfg.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+ ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, &tx_handle_, NULL));
+
+ i2s_pdm_tx_config_t pdm_cfg_default = BSP_I2S_DUPLEX_MONO_CFG((uint32_t)output_sample_rate, pdm_speak_p);
+ pdm_cfg_default.clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
+ pdm_cfg_default.slot_cfg.sd_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ pdm_cfg_default.slot_cfg.hp_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ pdm_cfg_default.slot_cfg.lp_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ pdm_cfg_default.slot_cfg.sinc_scale = I2S_PDM_SIG_SCALING_MUL_4;
+ const i2s_pdm_tx_config_t *p_i2s_cfg = &pdm_cfg_default;
+
+ ESP_ERROR_CHECK(i2s_channel_init_pdm_tx_mode(tx_handle_, p_i2s_cfg));
+ ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
+
+ audio_codec_i2s_cfg_t i2s_cfg = {
+ .port = I2S_NUM_0,
+ .rx_handle = NULL,
+ .tx_handle = tx_handle_,
+ };
+
+ const audio_codec_data_if_t *i2s_data_if = audio_codec_new_i2s_data(&i2s_cfg);
+
+ codec_dev_cfg.dev_type = ESP_CODEC_DEV_TYPE_OUT;
+ codec_dev_cfg.codec_if = NULL;
+ codec_dev_cfg.data_if = i2s_data_if;
+ output_dev_ = esp_codec_dev_new(&codec_dev_cfg);
+
+ output_volume_ = 100;
+ if(pa_ctl != GPIO_NUM_NC) {
+ pa_ctrl_pin_ = pa_ctl;
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << pa_ctrl_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+ gpio_set_drive_capability(pdm_speak_p, GPIO_DRIVE_CAP_0);
+
+ if(pdm_speak_n != GPIO_NUM_NC){
+ PIN_FUNC_SELECT(IO_MUX_GPIO10_REG, PIN_FUNC_GPIO);
+ gpio_set_direction(pdm_speak_n, GPIO_MODE_OUTPUT);
+ esp_rom_gpio_connect_out_signal(pdm_speak_n, I2SO_SD_OUT_IDX, 1, 0); //鍙嶈浆杈撳嚭 SD OUT 淇″彿
+ gpio_set_drive_capability(pdm_speak_n, GPIO_DRIVE_CAP_0);
+ }
+
+ // 鍒濆鍖栬緭鍑哄畾鏃跺櫒
+ esp_timer_create_args_t output_timer_args = {
+ .callback = &AdcPdmAudioCodec::OutputTimerCallback,
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "output_timer"
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&output_timer_args, &output_timer_));
+
+ ESP_LOGI(TAG, "AdcPdmAudioCodec initialized");
+}
+
+AdcPdmAudioCodec::~AdcPdmAudioCodec() {
+ // 鍒犻櫎瀹氭椂鍣�
+ if (output_timer_) {
+ esp_timer_stop(output_timer_);
+ esp_timer_delete(output_timer_);
+ output_timer_ = nullptr;
+ }
+
+ 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_);
+}
+
+void AdcPdmAudioCodec::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void AdcPdmAudioCodec::EnableInput(bool enable) {
+ if (enable == input_enabled_) {
+ return;
+ }
+ if (enable) {
+ esp_codec_dev_sample_info_t fs = {
+ .bits_per_sample = 16,
+ .channel = 1,
+ .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(0),
+ .sample_rate = (uint32_t)input_sample_rate_,
+ .mclk_multiple = 0,
+ };
+ ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+}
+
+void AdcPdmAudioCodec::EnableOutput(bool enable) {
+ 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_));
+
+ // 寮哄埗鎸夋澘鍗¢厤缃噸閰峆DM TX鏃堕挓锛岃鐩栫涓夋柟搴撳湪set_fmt涓殑榛樿up_sample_fs
+ // 鑻ラ�氶亾宸插惎鐢紝鍏堢鐢ㄥ啀閲嶉厤锛屾渶鍚庡啀鍚敤
+ ESP_ERROR_CHECK_WITHOUT_ABORT(i2s_channel_disable(tx_handle_));
+ i2s_pdm_tx_clk_config_t clk_cfg = I2S_PDM_TX_CLK_DEFAULT_CONFIG((uint32_t)output_sample_rate_);
+ clk_cfg.up_sample_fs = AUDIO_PDM_UPSAMPLE_FS;
+ ESP_ERROR_CHECK(i2s_channel_reconfig_pdm_tx_clock(tx_handle_, &clk_cfg));
+ ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_));
+ if(pa_ctrl_pin_ != GPIO_NUM_NC){
+ gpio_set_level(pa_ctrl_pin_, 1);
+ }
+ // 鍚敤杈撳嚭鏃跺惎鍔ㄥ畾鏃跺櫒
+ if (output_timer_) {
+ esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
+ }
+
+ } else {
+ // 绂佺敤杈撳嚭鏃跺仠姝㈠畾鏃跺櫒
+ if (output_timer_) {
+ esp_timer_stop(output_timer_);
+ }
+ if(pa_ctrl_pin_ != GPIO_NUM_NC){
+ gpio_set_level(pa_ctrl_pin_, 0);
+ }
+ ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
+ }
+ AudioCodec::EnableOutput(enable);
+}
+
+int AdcPdmAudioCodec::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 AdcPdmAudioCodec::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)));
+ // 閲嶇疆杈撳嚭瀹氭椂鍣�
+ if (output_timer_) {
+ esp_timer_stop(output_timer_);
+ esp_timer_start_once(output_timer_, TIMER_TIMEOUT_US);
+ }
+ }
+ return samples;
+}
+
+void AdcPdmAudioCodec::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;
+ }
+
+ EnableInput(true);
+ EnableOutput(true);
+ ESP_LOGI(TAG, "Audio codec started");
+}
+
+// 瀹氭椂鍣ㄥ洖璋冨嚱鏁板疄鐜�
+void AdcPdmAudioCodec::OutputTimerCallback(void* arg) {
+ AdcPdmAudioCodec* codec = static_cast<AdcPdmAudioCodec*>(arg);
+ if (codec && codec->output_enabled_) {
+ codec->EnableOutput(false);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h
new file mode 100644
index 0000000..8c9e836
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/adc_pdm_audio_codec.h
@@ -0,0 +1,37 @@
+#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 <esp_timer.h>
+
+class AdcPdmAudioCodec : public AudioCodec {
+private:
+ esp_codec_dev_handle_t output_dev_ = nullptr;
+ esp_codec_dev_handle_t input_dev_ = nullptr;
+ gpio_num_t pa_ctrl_pin_ = GPIO_NUM_NC;
+
+ // 瀹氭椂鍣ㄧ浉鍏虫垚鍛樺彉閲�
+ esp_timer_handle_t output_timer_ = nullptr;
+ static constexpr uint64_t TIMER_TIMEOUT_US = 120000; // 120ms = 120000us
+
+ // 瀹氭椂鍣ㄥ洖璋冨嚱鏁�
+ static void OutputTimerCallback(void* arg);
+
+ virtual int Read(int16_t* dest, int samples) override;
+ virtual int Write(const int16_t* data, int samples) override;
+
+public:
+ AdcPdmAudioCodec(int input_sample_rate, int output_sample_rate,
+ uint32_t adc_mic_channel, gpio_num_t pdm_speak_p, gpio_num_t pdm_speak_n, gpio_num_t pa_ctl);
+ virtual ~AdcPdmAudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+ void Start();
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.h
new file mode 100644
index 0000000..84d066f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.h
@@ -0,0 +1,40 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_PDM_UPSAMPLE_FS 480
+
+#define AUDIO_ADC_MIC_CHANNEL 5
+#define AUDIO_PDM_SPEAK_P_GPIO GPIO_NUM_7
+#define AUDIO_PDM_SPEAK_N_GPIO GPIO_NUM_8
+#define AUDIO_PA_CTL_GPIO GPIO_NUM_1
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_28
+#define DISPLAY_MOSI_PIN GPIO_NUM_23
+#define DISPLAY_CLK_PIN GPIO_NUM_24
+#define DISPLAY_DC_PIN GPIO_NUM_26
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+#define DISPLAY_CS_PIN GPIO_NUM_25
+
+#define LCD_TP_SCL GPIO_NUM_3
+#define LCD_TP_SDA GPIO_NUM_2
+
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 284
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 36
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.json
new file mode 100644
index 0000000..59b164b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/config.json
@@ -0,0 +1,29 @@
+{
+ "target": "esp32c5",
+ "builds": [
+ {
+ "name": "esp-sensairshuttle",
+ "sdkconfig_append": [
+ "CONFIG_IDF_TARGET=\"esp32c5\"",
+ "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6",
+ "CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=n",
+ "CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n",
+ "CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
+ "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
+ "CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
+ "CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=2048",
+ "CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y",
+ "CONFIG_SPIRAM=y",
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=3072",
+ "CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y",
+ "CONFIG_LWIP_IPV6=n",
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_SR_WN_WN9S_HIESP=y",
+ "CONFIG_USE_EMOTE_MESSAGE_STYLE=y",
+ "CONFIG_FLASH_CUSTOM_ASSETS=y",
+ "CONFIG_CUSTOM_ASSETS_FILE=\"https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-echoear.bin\"",
+ "CONFIG_CODEC_DATA_ADC_SUPPORT=y"
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/esp-sensairshuttle.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/esp-sensairshuttle.cc
new file mode 100644
index 0000000..4d3d88f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sensairshuttle/esp-sensairshuttle.cc
@@ -0,0 +1,315 @@
+#include "wifi_board.h"
+#include "adc_pdm_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include <wifi_station.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_wifi.h>
+#include <esp_event.h>
+
+#include "display/lcd_display.h"
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include "esp_lcd_ili9341.h"
+
+#include "display/emote_display.h"
+
+#include "assets/lang_config.h"
+#include "anim_player.h"
+#include "led_strip.h"
+#include "driver/rmt_tx.h"
+#include "i2c_device.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#include "sdkconfig.h"
+
+constexpr char TAG[] = "ESP_SensairShuttle";
+
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0x11, NULL, 0, 120}, // Sleep Out
+ {0x36, (uint8_t []){0x00}, 1, 0}, // Memory Data Access Control
+ {0x3A, (uint8_t []){0x05}, 1, 0}, // Interface Pixel Format (16-bit)
+ {0xB2, (uint8_t []){0x0C, 0x0C, 0x00, 0x33, 0x33}, 5, 0}, // Porch Setting
+ {0xB7, (uint8_t []){0x05}, 1, 0}, // Gate Control
+ {0xBB, (uint8_t []){0x21}, 1, 0}, // VCOM Setting
+ {0xC0, (uint8_t []){0x2C}, 1, 0}, // LCM Control
+ {0xC2, (uint8_t []){0x01}, 1, 0}, // VDV and VRH Command Enable
+ {0xC3, (uint8_t []){0x15}, 1, 0}, // VRH Set
+ {0xC6, (uint8_t []){0x0F}, 1, 0}, // Frame Rate Control
+ {0xD0, (uint8_t []){0xA7}, 1, 0}, // Power Control 1
+ {0xD0, (uint8_t []){0xA4, 0xA1}, 2, 0}, // Power Control 1
+ {0xD6, (uint8_t []){0xA1}, 1, 0}, // Gate output GND in sleep mode
+ {
+ 0xE0, (uint8_t [])
+ {
+ 0xF0, 0x05, 0x0E, 0x08, 0x0A, 0x17, 0x39, 0x54,
+ 0x4E, 0x37, 0x12, 0x12, 0x31, 0x37
+ }, 14, 0
+ }, // Positive Gamma Control
+ {
+ 0xE1, (uint8_t [])
+ {
+ 0xF0, 0x10, 0x14, 0x0D, 0x0B, 0x05, 0x39, 0x44,
+ 0x4D, 0x38, 0x14, 0x14, 0x2E, 0x35
+ }, 14, 0
+ }, // Negative Gamma Control
+ {0xE4, (uint8_t []){0x23, 0x00, 0x00}, 3, 0}, // Gate position control
+ {0x21, NULL, 0, 0}, // Display Inversion On
+ {0x29, NULL, 0, 0}, // Display On
+ {0x2C, NULL, 0, 0}, // Memory Write
+};
+
+class Cst816d : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ enum TouchEvent {
+ TOUCH_NONE,
+ TOUCH_PRESS,
+ TOUCH_RELEASE,
+ TOUCH_HOLD
+ };
+
+ Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr)
+ {
+ read_buffer_ = new uint8_t[6];
+ was_touched_ = false;
+ press_count_ = 0;
+ }
+
+ ~Cst816d()
+ {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint()
+ {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t &GetTouchPoint()
+ {
+ return tp_;
+ }
+
+ TouchEvent CheckTouchEvent()
+ {
+ bool is_touched = (tp_.num > 0);
+ TouchEvent event = TOUCH_NONE;
+
+ if (is_touched && !was_touched_) {
+ // Press event (transition from not touched to touched)
+ press_count_++;
+ event = TOUCH_PRESS;
+ ESP_LOGI(TAG, "TOUCH PRESS - count: %d, x: %d, y: %d", press_count_, tp_.x, tp_.y);
+ } else if (!is_touched && was_touched_) {
+ // Release event (transition from touched to not touched)
+ event = TOUCH_RELEASE;
+ ESP_LOGI(TAG, "TOUCH RELEASE - total presses: %d", press_count_);
+ } else if (is_touched && was_touched_) {
+ // Continuous touch (hold)
+ event = TOUCH_HOLD;
+ ESP_LOGD(TAG, "TOUCH HOLD - x: %d, y: %d", tp_.x, tp_.y);
+ }
+
+ // Update previous state
+ was_touched_ = is_touched;
+ return event;
+ }
+
+ int GetPressCount() const
+ {
+ return press_count_;
+ }
+
+ void ResetPressCount()
+ {
+ press_count_ = 0;
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+
+ // Touch state tracking
+ bool was_touched_;
+ int press_count_;
+};
+
+class EspSensairShuttle : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Cst816d* cst816d_;
+ Display* display_ = nullptr;
+ Button boot_button_;
+
+ void InitializeI2c()
+ {
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = LCD_TP_SDA,
+ .scl_io_num = LCD_TP_SCL,
+ .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, &i2c_bus_));
+ }
+
+ static void touch_event_task(void* arg)
+ {
+ Cst816d* touchpad = static_cast<Cst816d*>(arg);
+ if (touchpad == nullptr) {
+ ESP_LOGE(TAG, "Invalid touchpad pointer in touch_event_task");
+ vTaskDelete(NULL);
+ return;
+ }
+
+ while (true) {
+ touchpad->UpdateTouchPoint();
+ auto touch_event = touchpad->CheckTouchEvent();
+
+ if (touch_event == Cst816d::TOUCH_RELEASE) {
+ auto &app = Application::GetInstance();
+ auto &board = (EspSensairShuttle &)Board::GetInstance();
+
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ board.EnterWifiConfigMode();
+ } else {
+ app.ToggleChatState();
+ }
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(50)); // Poll every 50ms
+ }
+ }
+
+ void InitializeCst816dTouchPad()
+ {
+ cst816d_ = new Cst816d(i2c_bus_, 0x15);
+ xTaskCreate(touch_event_task, "touch_task", 2 * 1024, cst816d_, 5, NULL);
+ }
+
+ void InitializeButtons()
+ {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode");
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeSpi()
+ {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_PIN;
+ buscfg.quadwp_io_num = GPIO_NUM_NC;
+ buscfg.quadhd_io_num = GPIO_NUM_NC;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH * 10 * sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay()
+ {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *) &vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_set_gap(panel, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ ESP_LOGI(TAG, "LCD panel create success, %p", panel);
+
+#ifdef CONFIG_USE_EMOTE_MESSAGE_STYLE
+ display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+#else
+ display_ = new SpiLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, 0, 0, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+#endif
+
+ }
+
+public:
+ EspSensairShuttle() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeCst816dTouchPad();
+ InitializeButtons();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override
+ {
+ static AdcPdmAudioCodec audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_ADC_MIC_CHANNEL,
+ AUDIO_PDM_SPEAK_P_GPIO,
+ AUDIO_PDM_SPEAK_N_GPIO,
+ AUDIO_PA_CTL_GPIO);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override
+ {
+ return display_;
+ }
+
+ Cst816d* GetTouchpad()
+ {
+ return cst816d_;
+ }
+};
+
+DECLARE_BOARD(EspSensairShuttle);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.h
new file mode 100644
index 0000000..3c6e6f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.h
@@ -0,0 +1,99 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/uart.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_41
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_DC_GPIO GPIO_NUM_43
+#define DISPLAY_CS_GPIO GPIO_NUM_44
+#define DISPLAY_CLK_GPIO GPIO_NUM_21
+#define DISPLAY_MOSI_GPIO GPIO_NUM_47
+#define DISPLAY_RST_GPIO GPIO_NUM_NC
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define UART_ECHO_TXD GPIO_NUM_38
+#define UART_ECHO_RXD GPIO_NUM_48
+#define UART_ECHO_RTS (-1)
+#define UART_ECHO_CTS (-1)
+
+#define MOTOR_SPEED_MAX 100
+#define MOTOR_SPEED_80 80
+#define MOTOR_SPEED_60 60
+#define MOTOR_SPEED_MIN 0
+
+#define ECHO_UART_PORT_NUM UART_NUM_1
+#define ECHO_UART_BAUD_RATE (115200)
+#define BUF_SIZE (1024)
+
+typedef enum {
+ LIGHT_MODE_CHARGING_BREATH = 0,
+ LIGHT_MODE_POWER_LOW,
+ LIGHT_MODE_ALWAYS_ON,
+ LIGHT_MODE_BLINK,
+ LIGHT_MODE_WHITE_BREATH_SLOW,
+ LIGHT_MODE_WHITE_BREATH_FAST,
+ LIGHT_MODE_FLOWING,
+ LIGHT_MODE_SHOW,
+ LIGHT_MODE_SLEEP,
+ LIGHT_MODE_MAX
+} light_mode_t;
+
+/* Camera PINs*/
+#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15)
+#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13)
+#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6)
+#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7)
+#define SPARKBOT_CAMERA_D0 (GPIO_NUM_11)
+#define SPARKBOT_CAMERA_D1 (GPIO_NUM_9)
+#define SPARKBOT_CAMERA_D2 (GPIO_NUM_8)
+#define SPARKBOT_CAMERA_D3 (GPIO_NUM_10)
+#define SPARKBOT_CAMERA_D4 (GPIO_NUM_12)
+#define SPARKBOT_CAMERA_D5 (GPIO_NUM_18)
+#define SPARKBOT_CAMERA_D6 (GPIO_NUM_17)
+#define SPARKBOT_CAMERA_D7 (GPIO_NUM_16)
+
+#define SPARKBOT_CAMERA_PWDN (GPIO_NUM_NC)
+#define SPARKBOT_CAMERA_RESET (GPIO_NUM_NC)
+#define SPARKBOT_CAMERA_XCLK (GPIO_NUM_15)
+#define SPARKBOT_CAMERA_PCLK (GPIO_NUM_13)
+#define SPARKBOT_CAMERA_VSYNC (GPIO_NUM_6)
+#define SPARKBOT_CAMERA_HSYNC (GPIO_NUM_7)
+
+#define SPARKBOT_CAMERA_XCLK_FREQ (16000000)
+#define SPARKBOT_LEDC_TIMER (LEDC_TIMER_0)
+#define SPARKBOT_LEDC_CHANNEL (LEDC_CHANNEL_0)
+
+#define SPARKBOT_CAMERA_SIOD (GPIO_NUM_NC)
+#define SPARKBOT_CAMERA_SIOC (GPIO_NUM_NC)
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.json
new file mode 100644
index 0000000..0cebab5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-sparkbot",
+ "sdkconfig_append": [
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/esp_sparkbot_board.cc
new file mode 100644
index 0000000..ac90cde
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-sparkbot/esp_sparkbot_board.cc
@@ -0,0 +1,302 @@
+#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 "settings.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <driver/uart.h>
+#include <cstring>
+
+#include "esp_video.h"
+
+#define TAG "esp_sparkbot"
+
+class SparkBotEs8311AudioCodec : public Es8311AudioCodec {
+private:
+
+public:
+ SparkBotEs8311AudioCodec(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)
+ : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate,
+ mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {}
+
+ void EnableOutput(bool enable) override {
+ if (enable == output_enabled_) {
+ return;
+ }
+ if (enable) {
+ Es8311AudioCodec::EnableOutput(enable);
+ } else {
+ // Nothing todo because the display io and PA io conflict
+ }
+ }
+};
+
+class EspSparkBot : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+ EspVideo* camera_;
+ light_mode_t light_mode_ = LIGHT_MODE_ALWAYS_ON;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_GPIO;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_GPIO;
+ 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(SPI3_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;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_GPIO;
+ io_config.dc_gpio_num = DISPLAY_DC_GPIO;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+
+ 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_disp_on_off(panel, true);
+ 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 InitializeCamera() {
+
+ // DVP pin configuration
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = SPARKBOT_CAMERA_D0,
+ [1] = SPARKBOT_CAMERA_D1,
+ [2] = SPARKBOT_CAMERA_D2,
+ [3] = SPARKBOT_CAMERA_D3,
+ [4] = SPARKBOT_CAMERA_D4,
+ [5] = SPARKBOT_CAMERA_D5,
+ [6] = SPARKBOT_CAMERA_D6,
+ [7] = SPARKBOT_CAMERA_D7,
+ },
+ .vsync_io = SPARKBOT_CAMERA_VSYNC,
+ .de_io = SPARKBOT_CAMERA_HSYNC,
+ .pclk_io = SPARKBOT_CAMERA_PCLK,
+ .xclk_io = SPARKBOT_CAMERA_XCLK,
+ };
+
+ // 澶嶇敤 I2C 鎬荤嚎
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false, // 涓嶅垵濮嬪寲鏂扮殑 SCCB锛屼娇鐢ㄧ幇鏈夌殑 I2C 鎬荤嚎
+ .i2c_handle = i2c_bus_, // 浣跨敤鐜版湁鐨� I2C 鎬荤嚎鍙ユ焺
+ .freq = 100000, // 100kHz
+ };
+
+ // DVP configuration
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = SPARKBOT_CAMERA_RESET,
+ .pwdn_pin = SPARKBOT_CAMERA_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = SPARKBOT_CAMERA_XCLK_FREQ,
+ };
+
+ // Main video configuration
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+
+ Settings settings("sparkbot", false);
+ // 鑰冭檻鍒伴儴鍒嗗鍒讳娇鐢ㄤ簡涓嶅彲鍔ㄦ憚鍍忓ご鐨勮璁★紝榛樿鍚敤缈昏浆
+ bool camera_flipped = static_cast<bool>(settings.GetInt("camera-flipped", 1));
+ camera_->SetHMirror(camera_flipped);
+ camera_->SetVFlip(camera_flipped);
+ }
+
+ /*
+ ESP-SparkBot 鐨勫簳搴�
+ https://gitee.com/esp-friends/esp_sparkbot/tree/master/example/tank/c2_tracked_chassis
+ */
+ void InitializeEchoUart() {
+ uart_config_t uart_config = {
+ .baud_rate = ECHO_UART_BAUD_RATE,
+ .data_bits = UART_DATA_8_BITS,
+ .parity = UART_PARITY_DISABLE,
+ .stop_bits = UART_STOP_BITS_1,
+ .flow_ctrl = UART_HW_FLOWCTRL_DISABLE,
+ .source_clk = UART_SCLK_DEFAULT,
+ };
+ int intr_alloc_flags = 0;
+
+ ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags));
+ ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config));
+ ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, UART_ECHO_TXD, UART_ECHO_RXD, UART_ECHO_RTS, UART_ECHO_CTS));
+
+ SendUartMessage("w2");
+ }
+
+ void SendUartMessage(const char * command_str) {
+ uint8_t len = strlen(command_str);
+ uart_write_bytes(ECHO_UART_PORT_NUM, command_str, len);
+ ESP_LOGI(TAG, "Sent command: %s", command_str);
+ }
+
+ void InitializeTools() {
+ auto& mcp_server = McpServer::GetInstance();
+ // 瀹氫箟璁惧鐨勫睘鎬�
+ mcp_server.AddTool("self.chassis.get_light_mode", "鑾峰彇鐏厜鏁堟灉缂栧彿", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ if (light_mode_ < 2) {
+ return 1;
+ } else {
+ return light_mode_ - 2;
+ }
+ });
+
+ mcp_server.AddTool("self.chassis.go_forward", "鍓嶈繘", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SendUartMessage("x0.0 y1.0");
+ return true;
+ });
+
+ mcp_server.AddTool("self.chassis.go_back", "鍚庨��", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SendUartMessage("x0.0 y-1.0");
+ return true;
+ });
+
+ mcp_server.AddTool("self.chassis.turn_left", "鍚戝乏杞�", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SendUartMessage("x-1.0 y0.0");
+ return true;
+ });
+
+ mcp_server.AddTool("self.chassis.turn_right", "鍚戝彸杞�", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SendUartMessage("x1.0 y0.0");
+ return true;
+ });
+
+ mcp_server.AddTool("self.chassis.dance", "璺宠垶", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ SendUartMessage("d1");
+ light_mode_ = LIGHT_MODE_MAX;
+ return true;
+ });
+
+ mcp_server.AddTool("self.chassis.switch_light_mode", "鎵撳紑鐏厜鏁堟灉", PropertyList({
+ Property("light_mode", kPropertyTypeInteger, 1, 6)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ char command_str[5] = {'w', 0, 0};
+ char mode = static_cast<light_mode_t>(properties["light_mode"].value<int>());
+
+ ESP_LOGI(TAG, "Switch Light Mode: %c", (mode + '0'));
+
+ if (mode >= 3 && mode <= 8) {
+ command_str[1] = mode + '0';
+ SendUartMessage(command_str);
+ return true;
+ }
+ throw std::runtime_error("Invalid light mode");
+ });
+
+ mcp_server.AddTool("self.camera.set_camera_flipped", "缈昏浆鎽勫儚澶村浘鍍忔柟鍚�", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("sparkbot", true);
+ // 鑰冭檻鍒伴儴鍒嗗鍒讳娇鐢ㄤ簡涓嶅彲鍔ㄦ憚鍍忓ご鐨勮璁★紝榛樿鍚敤缈昏浆
+ bool flipped = !static_cast<bool>(settings.GetInt("camera-flipped", 1));
+
+ camera_->SetHMirror(flipped);
+ camera_->SetVFlip(flipped);
+
+ settings.SetInt("camera-flipped", flipped ? 1 : 0);
+
+ return true;
+ });
+ }
+
+public:
+ EspSparkBot() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeDisplay();
+ InitializeButtons();
+ InitializeCamera();
+ InitializeEchoUart();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static SparkBotEs8311AudioCodec audio_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;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(EspSparkBot);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-spot/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/README.md
new file mode 100644
index 0000000..c13054f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/README.md
@@ -0,0 +1,63 @@
+# ESP-Spot
+
+## 绠�浠�
+
+<div align="center">
+ <a href="https://oshwhub.com/esp-college/esp-spot"><b> 绔嬪垱寮�婧愬钩鍙� </b></a>
+ |
+ <a href="https://www.bilibili.com/video/BV1ekRAYVEZ1/"><b> Bilibili Demo </b></a>
+</div>
+
+ESP-Spot 鏄� ESP Friends 寮�婧愮殑涓�娆炬櫤鑳借闊充氦浜掔洅瀛愶紝鍐呯疆楹﹀厠椋庛�佹壃澹板櫒銆両MU 鎯�т紶鎰熷櫒锛屽彲浣跨敤鐢垫睜渚涚數銆侲SP-Spot 涓嶅甫灞忓箷锛屽甫鏈変竴涓� RGB 鎸囩ず鐏拰涓や釜鎸夐挳銆傜‖浠惰鎯呭彲鏌ョ湅[绔嬪垱寮�婧愰」鐩甝(https://oshwhub.com/esp-college/esp-spot)銆�
+
+ESP-Spot 寮�婧愰」鐩噰鐢� ESP32-S3-WROOM-1-N16R8 妯$粍鎴� ESP32-C5-WROOM-1-N8R8銆傚鍦ㄥ鍒绘椂浣跨敤浜嗗叾浠栧ぇ灏忕殑 Flash锛岄渶淇敼瀵瑰簲鐨勫弬鏁般��
+
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+**閰嶇疆缂栬瘧鐩爣**
+
+```bash
+idf.py set-target esp32s3 # Spot S3
+# or
+idf.py set-target esp32c5 # Spot C5
+```
+
+**鎵撳紑 menuconfig 骞堕厤缃�**
+
+```bash
+idf.py menuconfig
+```
+
+鍒嗗埆閰嶇疆濡備笅閫夐」锛�
+
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `ESP-Spot-S3` / `ESP-Spot-C5`
+
+鎸� `S` 淇濆瓨锛屾寜 `Q` 閫�鍑恒��
+
+**缂栬瘧**
+
+```bash
+idf.py build
+```
+
+**鐑у綍**
+
+```bash
+idf.py flash
+```
+
+> [!TIP]
+>
+> **鑻ョ數鑴戝缁堟棤娉曟壘鍒� ESP-Spot 涓插彛锛屽彲灏濊瘯濡備笅姝ラ**
+> 1. 鎵撳紑鍓嶇洊锛�
+> 2. 鎷斿嚭甯︽湁妯$粍鐨� PCB 鏉匡紱
+> 3. 鎸変綇 <kbd>BOOT</kbd> 鍚屾椂鎻掑洖 PCB 鐗堬紝娉ㄦ剰涓嶈棰犲�掞紱
+>
+> 姝ゆ椂锛� ESP-Spot 搴斿綋宸茶繘鍏ヤ笅杞芥ā寮忋�傚湪鐑у綍瀹屾垚鍚庯紝鍙兘闇�瑕侀噸鏂版彃鎷� PCB 鏉裤��
+
+## 浣庡姛鑰�
+
+ESP-Spot 鏀寔 Deep Sleep 浣庡姛鑰楁ā寮忋��
+
+褰撳浜� idle 鐘舵�� 10 鍒嗛挓鍚庯紝ESP-Spot 浼氳嚜鍔ㄨ繘鍏� Deep Sleep 妯″紡锛屾寜 Key 閿垨鎽囨檭 ESP-Spot 鍗冲彲鍞ら啋銆�
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.h
new file mode 100644
index 0000000..df6427d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.h
@@ -0,0 +1,79 @@
+#pragma once
+
+#include <driver/gpio.h>
+#include "sdkconfig.h"
+
+/* Audio configuration */
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+#define AUDIO_INPUT_REFERENCE false
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+/* ADC configuration */
+#define ADC_ATTEN ADC_ATTEN_DB_12
+#define ADC_WIDTH ADC_BITWIDTH_DEFAULT
+#define FULL_BATTERY_VOLTAGE 4100
+#define EMPTY_BATTERY_VOLTAGE 3200
+
+/* I2C configuration */
+#define I2C_MASTER_FREQ_HZ (400 * 1000)
+
+/* Button configuration */
+#define LONG_PRESS_TIMEOUT_US (5 * 1000000ULL)
+
+#ifdef CONFIG_IDF_TARGET_ESP32S3
+
+/* Audio I2S GPIOs */
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_17
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_18
+
+/* Audio CODEC GPIOs */
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_40
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
+
+/* Button GPIOs */
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define KEY_BUTTON_GPIO GPIO_NUM_12
+#define LED_GPIO GPIO_NUM_11
+
+/* ADC GPIOs */
+#define VBAT_ADC_CHANNEL ADC_CHANNEL_9 // S3: IO10
+#define MCU_VCC_CTL GPIO_NUM_4 // set 1 to power on MCU
+#define PERP_VCC_CTL GPIO_NUM_6 // set 1 to power on peripherals
+
+/* IMU GPIOs */
+#define IMU_INT_GPIO GPIO_NUM_5
+
+#elif defined(CONFIG_IDF_TARGET_ESP32C5)
+
+/* Audio I2S GPIOs */
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_8
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
+
+/* Audio CODEC GPIOs */
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_23
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_25
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_26
+
+/* Button GPIOs */
+#define BOOT_BUTTON_GPIO GPIO_NUM_28
+#define KEY_BUTTON_GPIO GPIO_NUM_5
+#define LED_GPIO GPIO_NUM_27
+
+/* ADC GPIOs */
+#define VBAT_ADC_CHANNEL ADC_CHANNEL_3 // C5: IO4
+#define MCU_VCC_CTL GPIO_NUM_2 // set 1 to power on MCU
+#define PERP_VCC_CTL GPIO_NUM_0 // set 1 to power on peripherals
+
+/* IMU GPIOs */
+#define IMU_INT_GPIO GPIO_NUM_3
+
+#endif // CONFIG_IDF_TARGET
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.json
new file mode 100644
index 0000000..350171e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32c5",
+ "builds": [
+ {
+ "name": "esp-spot-c5",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-spot/esp_spot_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/esp_spot_board.cc
new file mode 100644
index 0000000..d6931be
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-spot/esp_spot_board.cc
@@ -0,0 +1,436 @@
+#include <driver/gpio.h>
+#include <driver/i2c_master.h>
+#include <esp_adc/adc_cali.h>
+#include <esp_adc/adc_cali_scheme.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_err.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+
+#include "esp_idf_version.h"
+#include "led/circular_strip.h"
+#include "sdkconfig.h"
+
+#include "application.h"
+#include "button.h"
+#include "codecs/es8311_audio_codec.h"
+#include "config.h"
+#include "sleep_timer.h"
+#include "wifi_board.h"
+
+#ifdef IMU_INT_GPIO
+#include <esp_sleep.h>
+
+#include "bmi270_api.h"
+#include "i2c_bus.h"
+#endif // IMU_INT_GPIO
+
+#ifdef CONFIG_IDF_TARGET_ESP32S3
+#define TAG "esp_spot_s3"
+#elif defined(CONFIG_IDF_TARGET_ESP32C5)
+#define TAG "esp_spot_c5"
+#else // target
+#error "Unsupported target"
+#endif // target
+
+#ifdef IMU_INT_GPIO
+namespace Bmi270Imu {
+
+static bmi270_handle_t bmi_handle_ = nullptr;
+
+esp_err_t Initialize(i2c_bus_handle_t i2c_bus, uint8_t addr = BMI270_I2C_ADDRESS) {
+ if (bmi_handle_) {
+ return ESP_OK;
+ }
+
+ if (!i2c_bus) {
+ ESP_LOGE(TAG, "Invalid I2C bus for BMI270");
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ esp_err_t ret = bmi270_sensor_create(i2c_bus, &bmi_handle_, bmi270_config_file,
+ BMI2_GYRO_CROSS_SENS_ENABLE | BMI2_CRT_RTOSK_ENABLE);
+ if (ret != ESP_OK || !bmi_handle_) {
+ ESP_LOGE(TAG, "BMI270 create failed: %s", esp_err_to_name(ret));
+ return ret == ESP_OK ? ESP_FAIL : ret;
+ }
+ ESP_LOGI(TAG, "BMI270 initialized");
+ return ESP_OK;
+}
+
+// Only used for deep sleep wakeup with wrist gesture interrupt
+esp_err_t EnableImuIntForWakeup() {
+ if (!bmi_handle_) {
+ return ESP_ERR_INVALID_STATE;
+ }
+
+ const uint8_t sens_list[] = {BMI2_ACCEL, BMI2_WRIST_GESTURE};
+ int8_t rslt = bmi270_sensor_enable(sens_list, 2, bmi_handle_);
+ if (rslt != BMI2_OK) {
+ ESP_LOGE(TAG, "Failed to enable BMI270 sensors: %d", rslt);
+ return ESP_FAIL;
+ }
+
+ struct bmi2_sens_config config = {.type = BMI2_WRIST_GESTURE};
+ rslt = bmi270_get_sensor_config(&config, 1, bmi_handle_);
+ if (rslt != BMI2_OK) {
+ ESP_LOGE(TAG, "Failed to get wrist gesture config: %d", rslt);
+ return ESP_FAIL;
+ }
+ config.cfg.wrist_gest.wearable_arm = BMI2_ARM_RIGHT;
+ rslt = bmi270_set_sensor_config(&config, 1, bmi_handle_);
+ if (rslt != BMI2_OK) {
+ ESP_LOGE(TAG, "Failed to set wrist gesture config: %d", rslt);
+ return ESP_FAIL;
+ }
+
+ struct bmi2_int_pin_config pin_config = {};
+ pin_config.pin_type = BMI2_INT1;
+ pin_config.pin_cfg[0].input_en = BMI2_INT_INPUT_DISABLE;
+ pin_config.pin_cfg[0].lvl = BMI2_INT_ACTIVE_HIGH;
+ pin_config.pin_cfg[0].od = BMI2_INT_PUSH_PULL;
+ pin_config.pin_cfg[0].output_en = BMI2_INT_OUTPUT_ENABLE;
+ pin_config.int_latch = BMI2_INT_NON_LATCH;
+ rslt = bmi2_set_int_pin_config(&pin_config, bmi_handle_);
+ if (rslt != BMI2_OK) {
+ ESP_LOGE(TAG, "Failed to set BMI270 INT pin: %d", rslt);
+ return ESP_FAIL;
+ }
+
+ struct bmi2_sens_int_config int_config = {.type = BMI2_WRIST_GESTURE, .hw_int_pin = BMI2_INT1};
+ rslt = bmi270_map_feat_int(&int_config, 1, bmi_handle_);
+ if (rslt != BMI2_OK) {
+ ESP_LOGE(TAG, "Failed to map BMI270 interrupt: %d", rslt);
+ return ESP_FAIL;
+ }
+
+ return ESP_OK;
+}
+
+} // namespace Bmi270Imu
+
+#endif // IMU_INT_GPIO
+
+class EspSpot : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_ = nullptr;
+ Button boot_button_;
+ Button key_button_;
+ adc_oneshot_unit_handle_t adc1_handle_;
+ adc_cali_handle_t adc1_cali_handle_;
+ bool adc_calibration_lock_ = false;
+ bool key_long_pressed_ = false;
+ int64_t last_key_press_time = 0;
+ SleepTimer* sleep_timer_ = nullptr;
+#ifdef IMU_INT_GPIO
+ i2c_bus_handle_t shared_i2c_bus_handle_ = nullptr;
+ static constexpr int kDeepSleepTimeoutSeconds = 10 * 60; // 10 minutes
+ bool imu_ready_ = false;
+#endif
+
+#ifdef IMU_INT_GPIO
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_config_t i2c_bus_cfg = {
+ .mode = I2C_MODE_MASTER,
+ .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
+ .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
+ .sda_pullup_en = true,
+ .scl_pullup_en = true,
+ .master =
+ {
+ .clk_speed = I2C_MASTER_FREQ_HZ,
+ },
+ .clk_flags = 0,
+ };
+ shared_i2c_bus_handle_ = i2c_bus_create(I2C_NUM_0, &i2c_bus_cfg);
+ if (!shared_i2c_bus_handle_) {
+ ESP_LOGE(TAG, "Failed to create shared I2C bus");
+ ESP_ERROR_CHECK(ESP_FAIL);
+ }
+
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !CONFIG_I2C_BUS_BACKWARD_CONFIG
+ i2c_bus_ = i2c_bus_get_internal_bus_handle(shared_i2c_bus_handle_);
+#else
+#error "ESP-Spot board requires i2c_bus_get_internal_bus_handle() support"
+#endif
+ if (!i2c_bus_) {
+ ESP_LOGE(TAG, "Failed to obtain master bus handle");
+ ESP_ERROR_CHECK(ESP_FAIL);
+ }
+
+ esp_err_t imu_ret = Bmi270Imu::Initialize(shared_i2c_bus_handle_);
+ if (imu_ret != ESP_OK) {
+ ESP_LOGW(TAG, "BMI270 initialization failed, deep sleep disabled (%s)", esp_err_to_name(imu_ret));
+ } else {
+ imu_ready_ = true;
+ }
+ }
+#else
+ 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, &i2c_bus_));
+ }
+#endif // IMU_INT_GPIO
+
+ void InitializeADC() {
+ adc_oneshot_unit_init_cfg_t init_config1 = {.unit_id = ADC_UNIT_1};
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN,
+ .bitwidth = ADC_WIDTH,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle_, VBAT_ADC_CHANNEL, &chan_config));
+
+#ifdef ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
+ adc_cali_handle_t handle = nullptr;
+ esp_err_t ret = ESP_FAIL;
+
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = ADC_UNIT_1,
+ .atten = ADC_ATTEN,
+ .bitwidth = ADC_WIDTH,
+ };
+ ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
+ if (ret == ESP_OK) {
+ adc_calibration_lock_ = true;
+ adc1_cali_handle_ = handle;
+ ESP_LOGI(TAG, "ADC Curve Fitting calibration succeeded");
+ }
+#endif // ADC_CALI_SCHEME_CURVE_FITTING_SUPPORTED
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ HandleUserActivity();
+ EnterWifiConfigMode();
+ });
+
+ key_button_.OnClick([this]() {
+ HandleUserActivity();
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ key_long_pressed_ = false;
+ });
+
+ key_button_.OnLongPress([this]() {
+ HandleUserActivity();
+ int64_t now = esp_timer_get_time();
+ auto* led = static_cast<CircularStrip*>(this->GetLed());
+
+ if (key_long_pressed_) {
+ if ((now - last_key_press_time) < LONG_PRESS_TIMEOUT_US) {
+ ESP_LOGW(TAG, "Key button long pressed the second time within 5s, shutting down...");
+ led->SetSingleColor(0, {0, 0, 0});
+
+ gpio_hold_dis(MCU_VCC_CTL);
+ gpio_set_level(MCU_VCC_CTL, 0);
+
+ } else {
+ last_key_press_time = now;
+ BlinkGreenFor5s();
+ }
+ key_long_pressed_ = true;
+ } else {
+ ESP_LOGW(TAG, "Key button first long press! Waiting second within 5s to shutdown...");
+ last_key_press_time = now;
+ key_long_pressed_ = true;
+
+ BlinkGreenFor5s();
+ }
+ });
+ }
+
+ void InitializePowerCtl() {
+ InitializeGPIO();
+
+ gpio_set_level(MCU_VCC_CTL, 1);
+ gpio_hold_en(MCU_VCC_CTL);
+
+ gpio_set_level(PERP_VCC_CTL, 1);
+ gpio_hold_en(PERP_VCC_CTL);
+ }
+
+ void InitializeGPIO() {
+ gpio_config_t io_pa = {.pin_bit_mask = (1ULL << AUDIO_CODEC_PA_PIN),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE};
+ gpio_config(&io_pa);
+ gpio_set_level(AUDIO_CODEC_PA_PIN, 0);
+
+ gpio_config_t io_conf_1 = {.pin_bit_mask = (1ULL << MCU_VCC_CTL),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE};
+ gpio_config(&io_conf_1);
+
+ gpio_config_t io_conf_2 = {.pin_bit_mask = (1ULL << PERP_VCC_CTL),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE};
+ gpio_config(&io_conf_2);
+
+#ifdef IMU_INT_GPIO
+ gpio_config_t io_conf_imu_int = {
+ .pin_bit_mask = (1ULL << IMU_INT_GPIO),
+ .mode = GPIO_MODE_INPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_ENABLE,
+ .intr_type = GPIO_INTR_NEGEDGE,
+ };
+ gpio_config(&io_conf_imu_int);
+ gpio_install_isr_service(0);
+#endif // IMU_INT_GPIO
+ }
+
+ void HandleUserActivity() {
+ if (sleep_timer_) {
+ sleep_timer_->WakeUp();
+ }
+ }
+
+#ifdef IMU_INT_GPIO
+ void InitializePowerSaveTimer() {
+ if (!imu_ready_) {
+ ESP_LOGW(TAG, "IMU not ready, skip deep sleep timer");
+ return;
+ }
+ if (sleep_timer_) {
+ return;
+ }
+ sleep_timer_ = new SleepTimer(-1, kDeepSleepTimeoutSeconds);
+ sleep_timer_->OnEnterDeepSleepMode([this]() { EnterDeepSleep(); });
+ sleep_timer_->SetEnabled(true);
+ ESP_LOGI(TAG, "Deep sleep timer enabled, timeout=%ds", kDeepSleepTimeoutSeconds);
+ }
+
+ void EnterDeepSleep() {
+ if (!imu_ready_) {
+ ESP_LOGW(TAG, "Skip deep sleep because IMU is not ready");
+ return;
+ }
+
+ auto* led = static_cast<CircularStrip*>(GetLed());
+ if (led) {
+ led->SetSingleColor(0, {0, 0, 0});
+ }
+
+ if (Bmi270Imu::EnableImuIntForWakeup() != ESP_OK) {
+ ESP_LOGE(TAG, "IMU wakeup configuration failed, abort deep sleep");
+ return;
+ }
+
+ const uint64_t wakeup_mask = (1ULL << KEY_BUTTON_GPIO) | (1ULL << IMU_INT_GPIO);
+ ESP_ERROR_CHECK(esp_sleep_enable_ext1_wakeup(wakeup_mask, ESP_EXT1_WAKEUP_ANY_HIGH));
+ ESP_LOGI(TAG, "Entering deep sleep, waiting for key or wrist gesture");
+ esp_deep_sleep_start();
+ }
+#endif // IMU_INT_GPIO
+
+ void BlinkGreenFor5s() {
+ auto* led = static_cast<CircularStrip*>(GetLed());
+ if (!led) {
+ return;
+ }
+
+ led->Blink({50, 25, 0}, 100);
+
+ esp_timer_create_args_t timer_args = {.callback =
+ [](void* arg) {
+ auto* self = static_cast<EspSpot*>(arg);
+ auto* led = static_cast<CircularStrip*>(self->GetLed());
+ if (led) {
+ led->SetSingleColor(0, {0, 0, 0});
+ }
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "green_blink_timer"};
+
+ esp_timer_handle_t green_blink_timer = nullptr;
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &green_blink_timer));
+ ESP_ERROR_CHECK(esp_timer_start_once(green_blink_timer, LONG_PRESS_TIMEOUT_US));
+ }
+
+public:
+ EspSpot() : boot_button_(BOOT_BUTTON_GPIO), key_button_(KEY_BUTTON_GPIO, true) {
+ InitializePowerCtl();
+ InitializeADC();
+ InitializeI2c();
+ InitializeButtons();
+#ifdef IMU_INT_GPIO
+ InitializePowerSaveTimer();
+#endif // IMU_INT_GPIO
+ }
+
+ virtual Led* GetLed() override {
+ static CircularStrip led(LED_GPIO, 1);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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, false);
+ return &audio_codec;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (sleep_timer_) {
+ sleep_timer_->SetEnabled(level == PowerSaveLevel::LOW_POWER);
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ if (!adc1_handle_) {
+ InitializeADC();
+ }
+
+ int raw_value = 0;
+ int voltage = 0;
+
+ ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle_, VBAT_ADC_CHANNEL, &raw_value));
+
+ if (adc_calibration_lock_) {
+ ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc1_cali_handle_, raw_value, &voltage));
+ voltage = voltage * 3 / 2; // compensate for voltage divider
+ ESP_LOGI(TAG, "Calibrated voltage: %d mV", voltage);
+ } else {
+ ESP_LOGI(TAG, "Raw ADC value: %d", raw_value);
+ voltage = raw_value;
+ }
+
+ voltage = voltage < EMPTY_BATTERY_VOLTAGE ? EMPTY_BATTERY_VOLTAGE : voltage;
+ voltage = voltage > FULL_BATTERY_VOLTAGE ? FULL_BATTERY_VOLTAGE : voltage;
+
+ // Calculate battery level percentage
+ level = (voltage - EMPTY_BATTERY_VOLTAGE) * 100 / (FULL_BATTERY_VOLTAGE - EMPTY_BATTERY_VOLTAGE);
+
+ // ESP-Spot does not support charging detection, so we use MCU_VCC_CTL to determine charging status
+ charging = gpio_get_level(MCU_VCC_CTL);
+ discharging = !charging;
+ ESP_LOGI(TAG, "Battery Level: %d%%, Charging: %s", level, charging ? "Yes" : "No");
+ return true;
+ }
+};
+
+DECLARE_BOARD(EspSpot);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/README.md
new file mode 100644
index 0000000..fd3613c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/README.md
@@ -0,0 +1,82 @@
+# ESP-VoCat 鍠典即
+
+## 绠�浠�
+
+<div align="center">
+ <a href="https://oshwhub.com/esp-college/echoear"><b> 绔嬪垱寮�婧愬钩鍙� </b></a>
+</div>
+
+ESP-VoCat 鍠典即鏄竴娆炬櫤鑳� AI 寮�鍙戝浠讹紝鎼浇 ESP32-S3-WROOM-1 妯$粍锛�1.85 瀵� QSPI 鍦嗗舰瑙︽懜灞忥紝鍙岄害闃靛垪锛屾敮鎸佺绾胯闊冲敜閱掍笌澹版簮瀹氫綅绠楁硶銆傜‖浠惰鎯呯瓑鍙煡鐪媅绔嬪垱寮�婧愰」鐩甝(https://oshwhub.com/esp-college/echoear)銆�
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig 骞堕厤缃�**
+
+```bash
+idf.py menuconfig
+```
+
+鍒嗗埆閰嶇疆濡備笅閫夐」锛�
+
+### 鍩烘湰閰嶇疆
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `Espressif ESP-VoCat`
+
+### UI椋庢牸閫夋嫨
+
+ESP-VoCat 鏀寔澶氱涓嶅悓鐨� UI 鏄剧ず椋庢牸锛岄�氳繃 menuconfig 閰嶇疆閫夋嫨锛�
+
+- `Xiaozhi Assistant` 鈫� `Select display style` 鈫� 閫夋嫨鏄剧ず椋庢牸
+
+#### 鍙�夐鏍�
+
+##### 琛ㄦ儏鍔ㄧ敾椋庢牸 (Emote animation style) - 鎺ㄨ崘
+- **閰嶇疆閫夐」**: `USE_EMOTE_MESSAGE_STYLE`
+- **鐗圭偣**: 浣跨敤鑷畾涔夌殑 `EmoteDisplay` 琛ㄦ儏鏄剧ず绯荤粺
+- **鍔熻兘**: 鏀寔涓板瘜鐨勮〃鎯呭姩鐢汇�佺溂鐫涘姩鐢汇�佺姸鎬佸浘鏍囨樉绀�
+- **閫傜敤**: 鏅鸿兘鍔╂墜鍦烘櫙锛屾彁渚涙洿鐢熷姩鐨勪汉鏈轰氦浜掍綋楠�
+- **绫�**: `emote::EmoteDisplay`
+
+**鈿狅笍 閲嶈**: 閫夋嫨姝ら鏍奸渶瑕侀澶栭厤缃嚜瀹氫箟璧勬簮鏂囦欢锛�
+1. `Xiaozhi Assistant` 鈫� `Flash Assets` 鈫� 閫夋嫨 `Flash Custom Assets`
+2. `Xiaozhi Assistant` 鈫� `Custom Assets File` 鈫� 濉叆璧勬簮鏂囦欢鍦板潃锛�
+ ```
+ https://dl.espressif.com/AE/wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-echoear.bin
+ ```
+
+##### 榛樿娑堟伅椋庢牸 (Enable default message style)
+- **閰嶇疆閫夐」**: `USE_DEFAULT_MESSAGE_STYLE` (榛樿)
+- **鐗圭偣**: 浣跨敤鏍囧噯鐨勬秷鎭樉绀虹晫闈�
+- **鍔熻兘**: 浼犵粺鐨勬枃鏈拰鍥炬爣鏄剧ず鐣岄潰
+- **閫傜敤**: 鏍囧噯鐨勫璇濆満鏅�
+- **绫�**: `SpiLcdDisplay`
+
+##### 寰俊娑堟伅椋庢牸 (Enable WeChat Message Style)
+- **閰嶇疆閫夐」**: `USE_WECHAT_MESSAGE_STYLE`
+- **鐗圭偣**: 浠垮井淇¤亰澶╃晫闈㈤鏍�
+- **鍔熻兘**: 绫讳技寰俊鐨勬秷鎭皵娉℃樉绀�
+- **閫傜敤**: 鍠滄寰俊椋庢牸鐨勭敤鎴�
+- **绫�**: `SpiLcdDisplay`
+
+> **璇存槑**: ESP-VoCat 鍠典即浣跨敤16MB Flash锛岄渶瑕佷娇鐢ㄤ笓闂ㄧ殑鍒嗗尯琛ㄩ厤缃潵鍚堢悊鍒嗛厤瀛樺偍绌洪棿缁欏簲鐢ㄧ▼搴忋�丱TA鏇存柊銆佽祫婧愭枃浠剁瓑銆�
+
+鎸� `S` 淇濆瓨锛屾寜 `Q` 閫�鍑恒��
+
+**缂栬瘧**
+
+```bash
+idf.py build
+```
+
+**鐑у綍**
+
+灏� ESP-VoCat 鍠典即杩炴帴鑷崇數鑴戯紝**娉ㄦ剰鎵撳紑鐢垫簮**锛屽苟杩愯锛�
+
+```bash
+idf.py flash
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.h
new file mode 100644
index 0000000..a4ae32f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.h
@@ -0,0 +1,88 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/uart.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_INPUT_REFERENCE true
+
+#define CORDEC_POWER_CTRL GPIO_NUM_48
+
+#define POWER_CTRL GPIO_NUM_9
+#define LED_G GPIO_NUM_43
+#define SD_MISO GPIO_NUM_17
+#define SD_SCK GPIO_NUM_16
+#define SD_MOSI GPIO_NUM_38
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_39
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DIN_1 GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DIN_2 GPIO_NUM_3
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
+
+#define AUDIO_CODEC_PA_PIN_1 GPIO_NUM_4
+#define AUDIO_CODEC_PA_PIN_2 GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_2
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 360
+#define DISPLAY_HEIGHT 360
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define QSPI_LCD_H_RES (360)
+#define QSPI_LCD_V_RES (360)
+#define QSPI_LCD_BIT_PER_PIXEL (16)
+
+#define QSPI_LCD_HOST SPI2_HOST
+#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_18
+#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_14
+#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46
+#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_13
+#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_11
+#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_12
+#define QSPI_PIN_NUM_LCD_RST_1 GPIO_NUM_3
+#define QSPI_PIN_NUM_LCD_RST_2 GPIO_NUM_47
+#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_44
+
+#define UART1_TX_1 GPIO_NUM_6
+#define UART1_TX_2 GPIO_NUM_5
+#define UART1_RX_1 GPIO_NUM_5
+#define UART1_RX_2 GPIO_NUM_4
+#define TOUCH_PAD2_1 GPIO_NUM_NC
+#define TOUCH_PAD2_2 GPIO_NUM_6
+#define TOUCH_PAD1 GPIO_NUM_7
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define TP_PORT (I2C_NUM_1)
+#define TP_PIN_NUM_RST (GPIO_NUM_NC)
+#define TP_PIN_NUM_INT (GPIO_NUM_10)
+
+#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
+ { \
+ .data0_io_num = d0, \
+ .data1_io_num = d1, \
+ .sclk_io_num = sclk, \
+ .data2_io_num = d2, \
+ .data3_io_num = d3, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.json
new file mode 100644
index 0000000..3ad6adf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp-vocat",
+ "sdkconfig_append": [
+ "CONFIG_USE_EMOTE_MESSAGE_STYLE=y",
+ "CONFIG_MMAP_FILE_NAME_LENGTH=32",
+ "CONFIG_FLASH_EXPRESSION_ASSETS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/esp_vocat.cc b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/esp_vocat.cc
new file mode 100644
index 0000000..4e14c4a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/esp_vocat.cc
@@ -0,0 +1,1017 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "display/emote_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "backlight.h"
+#include "esp_video.h"
+
+#include <esp_log.h>
+#include <esp_timer.h>
+#include "esp_idf_version.h"
+#include <cinttypes>
+
+#include <driver/i2c_master.h>
+#include <cstdlib>
+#include "i2c_device.h"
+#include "i2c_bus.h"
+#include "bmi270_api.h"
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_st77916.h>
+#include "esp_lcd_touch_cst816s.h"
+#include "touch.h"
+
+extern "C" {
+#include "touch_button_sensor.h"
+#include "touch_slider_sensor.h"
+}
+
+#include "driver/temperature_sensor.h"
+#include <freertos/FreeRTOS.h>
+#include <freertos/semphr.h>
+#include <freertos/task.h>
+
+#define TAG "ESP-VoCat"
+
+namespace Bmi270Motion {
+static bmi270_handle_t bmi_handle_ = nullptr;
+
+esp_err_t Initialize(i2c_bus_handle_t i2c_bus)
+{
+ if (bmi_handle_) {
+ return ESP_OK;
+ }
+ if (!i2c_bus) {
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ esp_err_t ret = bmi270_sensor_create(i2c_bus, &bmi_handle_, bmi270_config_file,
+ BMI2_GYRO_CROSS_SENS_ENABLE | BMI2_CRT_RTOSK_ENABLE);
+ if (ret != ESP_OK || !bmi_handle_) {
+ ESP_LOGW(TAG, "BMI270 init failed: %s", esp_err_to_name(ret));
+ return ret == ESP_OK ? ESP_FAIL : ret;
+ }
+
+ const uint8_t sens_list[] = {BMI2_ACCEL};
+ int8_t rslt = bmi270_sensor_enable(sens_list, 1, bmi_handle_);
+ if (rslt != BMI2_OK) {
+ ESP_LOGW(TAG, "BMI270 accel enable failed: %d", rslt);
+ return ESP_FAIL;
+ }
+
+ ESP_LOGI(TAG, "BMI270 initialized");
+ return ESP_OK;
+}
+
+bool ReadAccelRaw(struct bmi2_sens_data& accel)
+{
+ if (!bmi_handle_) {
+ return false;
+ }
+ int8_t rslt = bmi2_get_sensor_data(&accel, bmi_handle_);
+ return rslt == BMI2_OK;
+}
+} // namespace Bmi270Motion
+
+
+temperature_sensor_handle_t temp_sensor = NULL;
+static const st77916_lcd_init_cmd_t vendor_specific_init_yysj[] = {
+ {0xF0, (uint8_t []){0x28}, 1, 0},
+ {0xF2, (uint8_t []){0x28}, 1, 0},
+ {0x73, (uint8_t []){0xF0}, 1, 0},
+ {0x7C, (uint8_t []){0xD1}, 1, 0},
+ {0x83, (uint8_t []){0xE0}, 1, 0},
+ {0x84, (uint8_t []){0x61}, 1, 0},
+ {0xF2, (uint8_t []){0x82}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x01}, 1, 0},
+ {0xF1, (uint8_t []){0x01}, 1, 0},
+ {0xB0, (uint8_t []){0x56}, 1, 0},
+ {0xB1, (uint8_t []){0x4D}, 1, 0},
+ {0xB2, (uint8_t []){0x24}, 1, 0},
+ {0xB4, (uint8_t []){0x87}, 1, 0},
+ {0xB5, (uint8_t []){0x44}, 1, 0},
+ {0xB6, (uint8_t []){0x8B}, 1, 0},
+ {0xB7, (uint8_t []){0x40}, 1, 0},
+ {0xB8, (uint8_t []){0x86}, 1, 0},
+ {0xBA, (uint8_t []){0x00}, 1, 0},
+ {0xBB, (uint8_t []){0x08}, 1, 0},
+ {0xBC, (uint8_t []){0x08}, 1, 0},
+ {0xBD, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x80}, 1, 0},
+ {0xC1, (uint8_t []){0x10}, 1, 0},
+ {0xC2, (uint8_t []){0x37}, 1, 0},
+ {0xC3, (uint8_t []){0x80}, 1, 0},
+ {0xC4, (uint8_t []){0x10}, 1, 0},
+ {0xC5, (uint8_t []){0x37}, 1, 0},
+ {0xC6, (uint8_t []){0xA9}, 1, 0},
+ {0xC7, (uint8_t []){0x41}, 1, 0},
+ {0xC8, (uint8_t []){0x01}, 1, 0},
+ {0xC9, (uint8_t []){0xA9}, 1, 0},
+ {0xCA, (uint8_t []){0x41}, 1, 0},
+ {0xCB, (uint8_t []){0x01}, 1, 0},
+ {0xD0, (uint8_t []){0x91}, 1, 0},
+ {0xD1, (uint8_t []){0x68}, 1, 0},
+ {0xD2, (uint8_t []){0x68}, 1, 0},
+ {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0},
+ {0xDD, (uint8_t []){0x4F}, 1, 0},
+ {0xDE, (uint8_t []){0x4F}, 1, 0},
+ {0xF1, (uint8_t []){0x10}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0},
+ {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0},
+ {0xF0, (uint8_t []){0x10}, 1, 0},
+ {0xF3, (uint8_t []){0x10}, 1, 0},
+ {0xE0, (uint8_t []){0x07}, 1, 0},
+ {0xE1, (uint8_t []){0x00}, 1, 0},
+ {0xE2, (uint8_t []){0x00}, 1, 0},
+ {0xE3, (uint8_t []){0x00}, 1, 0},
+ {0xE4, (uint8_t []){0xE0}, 1, 0},
+ {0xE5, (uint8_t []){0x06}, 1, 0},
+ {0xE6, (uint8_t []){0x21}, 1, 0},
+ {0xE7, (uint8_t []){0x01}, 1, 0},
+ {0xE8, (uint8_t []){0x05}, 1, 0},
+ {0xE9, (uint8_t []){0x02}, 1, 0},
+ {0xEA, (uint8_t []){0xDA}, 1, 0},
+ {0xEB, (uint8_t []){0x00}, 1, 0},
+ {0xEC, (uint8_t []){0x00}, 1, 0},
+ {0xED, (uint8_t []){0x0F}, 1, 0},
+ {0xEE, (uint8_t []){0x00}, 1, 0},
+ {0xEF, (uint8_t []){0x00}, 1, 0},
+ {0xF8, (uint8_t []){0x00}, 1, 0},
+ {0xF9, (uint8_t []){0x00}, 1, 0},
+ {0xFA, (uint8_t []){0x00}, 1, 0},
+ {0xFB, (uint8_t []){0x00}, 1, 0},
+ {0xFC, (uint8_t []){0x00}, 1, 0},
+ {0xFD, (uint8_t []){0x00}, 1, 0},
+ {0xFE, (uint8_t []){0x00}, 1, 0},
+ {0xFF, (uint8_t []){0x00}, 1, 0},
+ {0x60, (uint8_t []){0x40}, 1, 0},
+ {0x61, (uint8_t []){0x04}, 1, 0},
+ {0x62, (uint8_t []){0x00}, 1, 0},
+ {0x63, (uint8_t []){0x42}, 1, 0},
+ {0x64, (uint8_t []){0xD9}, 1, 0},
+ {0x65, (uint8_t []){0x00}, 1, 0},
+ {0x66, (uint8_t []){0x00}, 1, 0},
+ {0x67, (uint8_t []){0x00}, 1, 0},
+ {0x68, (uint8_t []){0x00}, 1, 0},
+ {0x69, (uint8_t []){0x00}, 1, 0},
+ {0x6A, (uint8_t []){0x00}, 1, 0},
+ {0x6B, (uint8_t []){0x00}, 1, 0},
+ {0x70, (uint8_t []){0x40}, 1, 0},
+ {0x71, (uint8_t []){0x03}, 1, 0},
+ {0x72, (uint8_t []){0x00}, 1, 0},
+ {0x73, (uint8_t []){0x42}, 1, 0},
+ {0x74, (uint8_t []){0xD8}, 1, 0},
+ {0x75, (uint8_t []){0x00}, 1, 0},
+ {0x76, (uint8_t []){0x00}, 1, 0},
+ {0x77, (uint8_t []){0x00}, 1, 0},
+ {0x78, (uint8_t []){0x00}, 1, 0},
+ {0x79, (uint8_t []){0x00}, 1, 0},
+ {0x7A, (uint8_t []){0x00}, 1, 0},
+ {0x7B, (uint8_t []){0x00}, 1, 0},
+ {0x80, (uint8_t []){0x48}, 1, 0},
+ {0x81, (uint8_t []){0x00}, 1, 0},
+ {0x82, (uint8_t []){0x06}, 1, 0},
+ {0x83, (uint8_t []){0x02}, 1, 0},
+ {0x84, (uint8_t []){0xD6}, 1, 0},
+ {0x85, (uint8_t []){0x04}, 1, 0},
+ {0x86, (uint8_t []){0x00}, 1, 0},
+ {0x87, (uint8_t []){0x00}, 1, 0},
+ {0x88, (uint8_t []){0x48}, 1, 0},
+ {0x89, (uint8_t []){0x00}, 1, 0},
+ {0x8A, (uint8_t []){0x08}, 1, 0},
+ {0x8B, (uint8_t []){0x02}, 1, 0},
+ {0x8C, (uint8_t []){0xD8}, 1, 0},
+ {0x8D, (uint8_t []){0x04}, 1, 0},
+ {0x8E, (uint8_t []){0x00}, 1, 0},
+ {0x8F, (uint8_t []){0x00}, 1, 0},
+ {0x90, (uint8_t []){0x48}, 1, 0},
+ {0x91, (uint8_t []){0x00}, 1, 0},
+ {0x92, (uint8_t []){0x0A}, 1, 0},
+ {0x93, (uint8_t []){0x02}, 1, 0},
+ {0x94, (uint8_t []){0xDA}, 1, 0},
+ {0x95, (uint8_t []){0x04}, 1, 0},
+ {0x96, (uint8_t []){0x00}, 1, 0},
+ {0x97, (uint8_t []){0x00}, 1, 0},
+ {0x98, (uint8_t []){0x48}, 1, 0},
+ {0x99, (uint8_t []){0x00}, 1, 0},
+ {0x9A, (uint8_t []){0x0C}, 1, 0},
+ {0x9B, (uint8_t []){0x02}, 1, 0},
+ {0x9C, (uint8_t []){0xDC}, 1, 0},
+ {0x9D, (uint8_t []){0x04}, 1, 0},
+ {0x9E, (uint8_t []){0x00}, 1, 0},
+ {0x9F, (uint8_t []){0x00}, 1, 0},
+ {0xA0, (uint8_t []){0x48}, 1, 0},
+ {0xA1, (uint8_t []){0x00}, 1, 0},
+ {0xA2, (uint8_t []){0x05}, 1, 0},
+ {0xA3, (uint8_t []){0x02}, 1, 0},
+ {0xA4, (uint8_t []){0xD5}, 1, 0},
+ {0xA5, (uint8_t []){0x04}, 1, 0},
+ {0xA6, (uint8_t []){0x00}, 1, 0},
+ {0xA7, (uint8_t []){0x00}, 1, 0},
+ {0xA8, (uint8_t []){0x48}, 1, 0},
+ {0xA9, (uint8_t []){0x00}, 1, 0},
+ {0xAA, (uint8_t []){0x07}, 1, 0},
+ {0xAB, (uint8_t []){0x02}, 1, 0},
+ {0xAC, (uint8_t []){0xD7}, 1, 0},
+ {0xAD, (uint8_t []){0x04}, 1, 0},
+ {0xAE, (uint8_t []){0x00}, 1, 0},
+ {0xAF, (uint8_t []){0x00}, 1, 0},
+ {0xB0, (uint8_t []){0x48}, 1, 0},
+ {0xB1, (uint8_t []){0x00}, 1, 0},
+ {0xB2, (uint8_t []){0x09}, 1, 0},
+ {0xB3, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0xD9}, 1, 0},
+ {0xB5, (uint8_t []){0x04}, 1, 0},
+ {0xB6, (uint8_t []){0x00}, 1, 0},
+ {0xB7, (uint8_t []){0x00}, 1, 0},
+ {0xB8, (uint8_t []){0x48}, 1, 0},
+ {0xB9, (uint8_t []){0x00}, 1, 0},
+ {0xBA, (uint8_t []){0x0B}, 1, 0},
+ {0xBB, (uint8_t []){0x02}, 1, 0},
+ {0xBC, (uint8_t []){0xDB}, 1, 0},
+ {0xBD, (uint8_t []){0x04}, 1, 0},
+ {0xBE, (uint8_t []){0x00}, 1, 0},
+ {0xBF, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x10}, 1, 0},
+ {0xC1, (uint8_t []){0x47}, 1, 0},
+ {0xC2, (uint8_t []){0x56}, 1, 0},
+ {0xC3, (uint8_t []){0x65}, 1, 0},
+ {0xC4, (uint8_t []){0x74}, 1, 0},
+ {0xC5, (uint8_t []){0x88}, 1, 0},
+ {0xC6, (uint8_t []){0x99}, 1, 0},
+ {0xC7, (uint8_t []){0x01}, 1, 0},
+ {0xC8, (uint8_t []){0xBB}, 1, 0},
+ {0xC9, (uint8_t []){0xAA}, 1, 0},
+ {0xD0, (uint8_t []){0x10}, 1, 0},
+ {0xD1, (uint8_t []){0x47}, 1, 0},
+ {0xD2, (uint8_t []){0x56}, 1, 0},
+ {0xD3, (uint8_t []){0x65}, 1, 0},
+ {0xD4, (uint8_t []){0x74}, 1, 0},
+ {0xD5, (uint8_t []){0x88}, 1, 0},
+ {0xD6, (uint8_t []){0x99}, 1, 0},
+ {0xD7, (uint8_t []){0x01}, 1, 0},
+ {0xD8, (uint8_t []){0xBB}, 1, 0},
+ {0xD9, (uint8_t []){0xAA}, 1, 0},
+ {0xF3, (uint8_t []){0x01}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0x21, (uint8_t []){}, 0, 0},
+ {0x11, (uint8_t []){}, 0, 0},
+ {0x00, (uint8_t []){}, 0, 120},
+};
+float tsens_value;
+gpio_num_t AUDIO_I2S_GPIO_DIN = AUDIO_I2S_GPIO_DIN_1;
+gpio_num_t AUDIO_CODEC_PA_PIN = AUDIO_CODEC_PA_PIN_1;
+gpio_num_t QSPI_PIN_NUM_LCD_RST = QSPI_PIN_NUM_LCD_RST_1;
+gpio_num_t TOUCH_PAD2 = TOUCH_PAD2_1;
+gpio_num_t UART1_TX = UART1_TX_1;
+gpio_num_t UART1_RX = UART1_RX_1;
+
+class Charge : public I2cDevice {
+public:
+ Charge(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr)
+ {
+ read_buffer_ = new uint8_t[8];
+ }
+ ~Charge()
+ {
+ delete[] read_buffer_;
+ }
+ void Printcharge()
+ {
+ ReadRegs(0x08, read_buffer_, 2);
+ ReadRegs(0x0c, read_buffer_ + 2, 2);
+ ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor, &tsens_value));
+
+ int16_t voltage = static_cast<uint16_t>(read_buffer_[1] << 8 | read_buffer_[0]);
+ int16_t current = static_cast<int16_t>(read_buffer_[3] << 8 | read_buffer_[2]);
+
+ // Use the variables to avoid warnings (can be removed if actual implementation uses them)
+ (void)voltage;
+ (void)current;
+ }
+ static void TaskFunction(void *pvParameters)
+ {
+ Charge* charge = static_cast<Charge*>(pvParameters);
+ while (true) {
+ charge->Printcharge();
+ vTaskDelay(pdMS_TO_TICKS(300));
+ }
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+};
+
+class Cst816s : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ enum TouchEvent {
+ TOUCH_NONE,
+ TOUCH_PRESS,
+ TOUCH_RELEASE,
+ TOUCH_HOLD
+ };
+
+ Cst816s(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr)
+ {
+ read_buffer_ = new uint8_t[6];
+ was_touched_ = false;
+ press_count_ = 0;
+
+ // Create touch interrupt semaphore
+ touch_isr_mux_ = xSemaphoreCreateBinary();
+ if (touch_isr_mux_ == NULL) {
+ ESP_LOGE(TAG, "Failed to create touch semaphore");
+ }
+ }
+
+ ~Cst816s()
+ {
+ delete[] read_buffer_;
+
+ // Delete semaphore if it exists
+ if (touch_isr_mux_ != NULL) {
+ vSemaphoreDelete(touch_isr_mux_);
+ touch_isr_mux_ = NULL;
+ }
+ }
+
+ void UpdateTouchPoint()
+ {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t &GetTouchPoint()
+ {
+ return tp_;
+ }
+
+ TouchEvent CheckTouchEvent()
+ {
+ bool is_touched = (tp_.num > 0);
+ TouchEvent event = TOUCH_NONE;
+
+ if (is_touched && !was_touched_) {
+ // Press event (transition from not touched to touched)
+ press_count_++;
+ event = TOUCH_PRESS;
+ ESP_LOGI(TAG, "TOUCH PRESS - count: %d, x: %d, y: %d", press_count_, tp_.x, tp_.y);
+ } else if (!is_touched && was_touched_) {
+ // Release event (transition from touched to not touched)
+ event = TOUCH_RELEASE;
+ ESP_LOGI(TAG, "TOUCH RELEASE - total presses: %d", press_count_);
+ } else if (is_touched && was_touched_) {
+ // Continuous touch (hold)
+ event = TOUCH_HOLD;
+ ESP_LOGD(TAG, "TOUCH HOLD - x: %d, y: %d", tp_.x, tp_.y);
+ }
+
+ // Update previous state
+ was_touched_ = is_touched;
+ return event;
+ }
+
+ int GetPressCount() const
+ {
+ return press_count_;
+ }
+
+ void ResetPressCount()
+ {
+ press_count_ = 0;
+ }
+
+ // Semaphore management methods
+ SemaphoreHandle_t GetTouchSemaphore()
+ {
+ return touch_isr_mux_;
+ }
+
+ bool WaitForTouchEvent(TickType_t timeout = portMAX_DELAY)
+ {
+ if (touch_isr_mux_ != NULL) {
+ return xSemaphoreTake(touch_isr_mux_, timeout) == pdTRUE;
+ }
+ return false;
+ }
+
+ void NotifyTouchEvent()
+ {
+ if (touch_isr_mux_ != NULL) {
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xSemaphoreGiveFromISR(touch_isr_mux_, &xHigherPriorityTaskWoken);
+ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ }
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+
+ // Touch state tracking
+ bool was_touched_;
+ int press_count_;
+
+ // Touch interrupt semaphore
+ SemaphoreHandle_t touch_isr_mux_;
+};
+
+class EspVocat : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ i2c_bus_handle_t shared_i2c_bus_handle_ = nullptr;
+ Cst816s* cst816s_;
+ Charge* charge_;
+ Button boot_button_;
+ Display* display_ = nullptr;
+ PwmBacklight* backlight_ = nullptr;
+ esp_timer_handle_t touchpad_timer_;
+ esp_lcd_touch_handle_t tp; // LCD touch handle
+ EspVideo* camera_ = nullptr;
+ TaskHandle_t charge_task_handle_ = nullptr;
+ TaskHandle_t touch_task_handle_ = nullptr;
+ TaskHandle_t imu_task_handle_ = nullptr;
+ TaskHandle_t touch_slider_task_handle_ = nullptr;
+ esp_timer_handle_t emotion_reset_timer_ = nullptr;
+ bool bmi270_ready_ = false;
+ touch_slider_handle_t touch_slider_handle_ = nullptr;
+ touch_button_handle_t touch_button_handle_ = nullptr;
+
+ static void emotion_reset_timer_callback(void* arg)
+ {
+ auto* self = static_cast<EspVocat*>(arg);
+ if (self && self->display_ != nullptr) {
+ self->display_->SetEmotion("neutral");
+ }
+ }
+
+ void ShowTemporaryEmotion(const char* emotion, uint32_t duration_ms)
+ {
+ if (display_ == nullptr || emotion == nullptr) {
+ return;
+ }
+ display_->SetEmotion(emotion);
+ if (emotion_reset_timer_ != nullptr) {
+ esp_timer_stop(emotion_reset_timer_);
+ esp_timer_start_once(emotion_reset_timer_, static_cast<uint64_t>(duration_ms) * 1000ULL);
+ }
+ }
+
+ void ShowHappyTouchFeedback()
+ {
+ static int64_t s_last_us = 0;
+ constexpr int64_t kCooldownUs = 1200000;
+ const int64_t now = esp_timer_get_time();
+ if ((now - s_last_us) < kCooldownUs) {
+ return;
+ }
+ s_last_us = now;
+ ShowTemporaryEmotion("happy", 2000);
+ }
+
+ static void imu_event_task(void* arg)
+ {
+ auto* self = static_cast<EspVocat*>(arg);
+ if (self == nullptr || !self->bmi270_ready_) {
+ vTaskDelete(NULL);
+ return;
+ }
+
+ struct bmi2_sens_data prev = {};
+ struct bmi2_sens_data cur = {};
+ bool has_prev = false;
+ int64_t last_shake_ms = 0;
+ constexpr int kShakeDeltaThreshold = 20000;
+ constexpr int64_t kShakeCooldownMs = 2000;
+
+ while (true) {
+ if (Bmi270Motion::ReadAccelRaw(cur)) {
+ if (has_prev) {
+ int dx = abs(static_cast<int>(cur.acc.x) - static_cast<int>(prev.acc.x));
+ int dy = abs(static_cast<int>(cur.acc.y) - static_cast<int>(prev.acc.y));
+ int dz = abs(static_cast<int>(cur.acc.z) - static_cast<int>(prev.acc.z));
+ int shake_score = dx + dy + dz;
+
+ int64_t now_ms = esp_timer_get_time() / 1000;
+ if (shake_score > kShakeDeltaThreshold && (now_ms - last_shake_ms) > kShakeCooldownMs) {
+ last_shake_ms = now_ms;
+ // "dizzy/nauseated" are not guaranteed in current assets, use supported fallback.
+ self->ShowTemporaryEmotion("confused", 1800);
+ }
+ }
+ prev = cur;
+ has_prev = true;
+ }
+ vTaskDelay(pdMS_TO_TICKS(80));
+ }
+ }
+
+ void InitializeI2c()
+ {
+ i2c_config_t i2c_cfg = {
+ .mode = I2C_MODE_MASTER,
+ .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
+ .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
+ .sda_pullup_en = true,
+ .scl_pullup_en = true,
+ .master = {
+ .clk_speed = 400000,
+ },
+ .clk_flags = 0,
+ };
+ shared_i2c_bus_handle_ = i2c_bus_create(I2C_NUM_0, &i2c_cfg);
+ if (!shared_i2c_bus_handle_) {
+ ESP_LOGE(TAG, "Failed to create shared I2C bus");
+ ESP_ERROR_CHECK(ESP_FAIL);
+ }
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 3, 0) && !CONFIG_I2C_BUS_BACKWARD_CONFIG
+ i2c_bus_ = i2c_bus_get_internal_bus_handle(shared_i2c_bus_handle_);
+#else
+#error "ESP-VoCat board requires i2c_bus_get_internal_bus_handle() support"
+#endif
+ if (!i2c_bus_) {
+ ESP_LOGE(TAG, "Failed to get I2C master handle");
+ ESP_ERROR_CHECK(ESP_FAIL);
+ }
+
+ temperature_sensor_config_t temp_sensor_config = TEMPERATURE_SENSOR_CONFIG_DEFAULT(10, 50);
+ ESP_ERROR_CHECK(temperature_sensor_install(&temp_sensor_config, &temp_sensor));
+ ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor));
+ }
+ uint8_t DetectPcbVersion()
+ {
+ gpio_config_t gpio_conf = {
+ .pin_bit_mask = (1ULL << CORDEC_POWER_CTRL),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE
+ };
+ ESP_ERROR_CHECK(gpio_config(&gpio_conf));
+ ESP_ERROR_CHECK(gpio_set_level(CORDEC_POWER_CTRL, 0));
+ vTaskDelay(pdMS_TO_TICKS(50));
+
+ bool codec_alive = (i2c_master_probe(i2c_bus_, 0x18, 100) == ESP_OK);
+ uint8_t pcb_version = 0;
+ if (codec_alive) {
+ ESP_LOGI(TAG, "PCB version V1.0");
+ pcb_version = 0;
+ } else {
+ ESP_ERROR_CHECK(gpio_set_level(CORDEC_POWER_CTRL, 1));
+ vTaskDelay(pdMS_TO_TICKS(50));
+ codec_alive = (i2c_master_probe(i2c_bus_, 0x18, 100) == ESP_OK);
+ if (codec_alive) {
+ ESP_LOGI(TAG, "PCB version V1.2");
+ pcb_version = 1;
+ AUDIO_I2S_GPIO_DIN = AUDIO_I2S_GPIO_DIN_2;
+ AUDIO_CODEC_PA_PIN = AUDIO_CODEC_PA_PIN_2;
+ QSPI_PIN_NUM_LCD_RST = QSPI_PIN_NUM_LCD_RST_2;
+ TOUCH_PAD2 = TOUCH_PAD2_2;
+ UART1_TX = UART1_TX_2;
+ UART1_RX = UART1_RX_2;
+ } else {
+ ESP_LOGE(TAG, "PCB version detection error");
+ }
+ }
+ return pcb_version;
+ }
+
+ static void touch_isr_callback(void* arg)
+ {
+ Cst816s* touchpad = static_cast<Cst816s*>(arg);
+ if (touchpad != nullptr) {
+ touchpad->NotifyTouchEvent();
+ }
+ }
+
+ static void touch_event_task(void* arg)
+ {
+ Cst816s* touchpad = static_cast<Cst816s*>(arg);
+ if (touchpad == nullptr) {
+ ESP_LOGE(TAG, "Invalid touchpad pointer in touch_event_task");
+ vTaskDelete(NULL);
+ return;
+ }
+
+ while (true) {
+ if (touchpad->WaitForTouchEvent()) {
+ auto &app = Application::GetInstance();
+ auto &board = (EspVocat &)Board::GetInstance();
+
+ ESP_LOGD(TAG, "Touch event, TP_PIN_NUM_INT: %d", gpio_get_level(TP_PIN_NUM_INT));
+ touchpad->UpdateTouchPoint();
+ auto touch_event = touchpad->CheckTouchEvent();
+
+ if (touch_event == Cst816s::TOUCH_RELEASE) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ board.EnterWifiConfigMode();
+ } else {
+ app.ToggleChatState();
+ }
+ }
+ }
+ }
+ }
+
+ void InitializeCharge()
+ {
+ charge_ = new Charge(i2c_bus_, 0x55);
+ xTaskCreatePinnedToCore(Charge::TaskFunction, "batterydecTask", 3 * 1024, charge_, 6, &charge_task_handle_, 0);
+ }
+
+ void InitializeCst816sTouchPad()
+ {
+ cst816s_ = new Cst816s(i2c_bus_, 0x15);
+
+ xTaskCreatePinnedToCore(touch_event_task, "touch_task", 4 * 1024, cst816s_, 5, &touch_task_handle_, 1);
+
+ const gpio_config_t int_gpio_config = {
+ .pin_bit_mask = (1ULL << TP_PIN_NUM_INT),
+ .mode = GPIO_MODE_INPUT,
+ // .intr_type = GPIO_INTR_NEGEDGE
+ .intr_type = GPIO_INTR_ANYEDGE
+ };
+ gpio_config(&int_gpio_config);
+ gpio_install_isr_service(0);
+ gpio_intr_enable(TP_PIN_NUM_INT);
+ gpio_isr_handler_add(TP_PIN_NUM_INT, EspVocat::touch_isr_callback, cst816s_);
+ }
+
+ void InitializeBmi270()
+ {
+ esp_err_t imu_ret = Bmi270Motion::Initialize(shared_i2c_bus_handle_);
+ if (imu_ret == ESP_OK) {
+ bmi270_ready_ = true;
+ xTaskCreatePinnedToCore(imu_event_task, "imu_task", 4 * 1024, this, 4, &imu_task_handle_, 1);
+ } else {
+ ESP_LOGW(TAG, "BMI270 unavailable, shake emotion disabled");
+ }
+ }
+
+ static uint32_t TouchChannelFromPadGpio(gpio_num_t gpio)
+ {
+ if (gpio == GPIO_NUM_NC) {
+ return 0;
+ }
+ if (gpio >= GPIO_NUM_1 && gpio <= GPIO_NUM_14) {
+ return static_cast<uint32_t>(gpio);
+ }
+ return 0;
+ }
+
+ static void touch_slider_event_callback(touch_slider_handle_t handle, touch_slider_event_t event, int32_t data, void* cb_arg)
+ {
+ (void)handle;
+ auto* self = static_cast<EspVocat*>(cb_arg);
+ if (self == nullptr || self->display_ == nullptr) {
+ return;
+ }
+ if (event != TOUCH_SLIDER_EVENT_POSITION) {
+ ESP_LOGI(TAG, "Touch slider evt=%d data=%" PRId32, static_cast<int>(event), data);
+ }
+
+ bool gesture = false;
+ if (event == TOUCH_SLIDER_EVENT_LEFT_SWIPE || event == TOUCH_SLIDER_EVENT_RIGHT_SWIPE) {
+ gesture = true;
+ } else if (event == TOUCH_SLIDER_EVENT_RELEASE) {
+ gesture = true;
+ }
+
+ if (!gesture) {
+ return;
+ }
+
+ self->ShowHappyTouchFeedback();
+ }
+
+ static void touch_button_event_callback(touch_button_handle_t handle, uint32_t channel, touch_state_t state, void* cb_arg)
+ {
+ (void)handle;
+ auto* self = static_cast<EspVocat*>(cb_arg);
+ if (self == nullptr || self->display_ == nullptr) {
+ return;
+ }
+ if (state == TOUCH_STATE_ACTIVE) {
+ ESP_LOGI(TAG, "Touch button ACTIVE ch=%" PRIu32, channel);
+ self->ShowHappyTouchFeedback();
+ }
+ }
+
+ static void touch_cap_poll_task(void* arg)
+ {
+ auto* self = static_cast<EspVocat*>(arg);
+ while (true) {
+ if (self != nullptr) {
+ if (self->touch_slider_handle_ != nullptr) {
+ touch_slider_sensor_handle_events(self->touch_slider_handle_);
+ } else if (self->touch_button_handle_ != nullptr) {
+ touch_button_sensor_handle_events(self->touch_button_handle_);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ }
+
+ void InitializeCapacitiveTouchPads()
+ {
+ if (TOUCH_PAD1 == GPIO_NUM_NC) {
+ ESP_LOGW(TAG, "Capacitive touch disabled: TOUCH_PAD1 NC");
+ return;
+ }
+
+ const uint32_t ch1 = TouchChannelFromPadGpio(TOUCH_PAD1);
+ if (ch1 == 0) {
+ ESP_LOGW(TAG, "TOUCH_PAD1 GPIO %d is not a touch channel (expect GPIO1..GPIO14)", (int)TOUCH_PAD1);
+ return;
+ }
+
+ if (TOUCH_PAD2 != GPIO_NUM_NC) {
+ const uint32_t ch2 = TouchChannelFromPadGpio(TOUCH_PAD2);
+ if (ch2 == 0) {
+ ESP_LOGW(TAG, "TOUCH_PAD2 GPIO %d is not a touch channel", (int)TOUCH_PAD2);
+ return;
+ }
+
+ static uint32_t slider_ch[2];
+ static float slider_thr[2];
+ slider_ch[0] = ch1;
+ slider_ch[1] = ch2;
+ slider_thr[0] = 0.004f;
+ slider_thr[1] = 0.006f;
+
+ touch_slider_config_t sld_cfg = {
+ .channel_num = 2,
+ .channel_list = slider_ch,
+ .channel_threshold = slider_thr,
+ .channel_gold_value = nullptr,
+ .debounce_times = 1,
+ .filter_reset_times = 5,
+ .position_range = 10000,
+ .calculate_window = 2,
+ .swipe_threshold = 28.f,
+ .swipe_hysterisis = 22.f,
+ .swipe_alpha = 0.9f,
+ .skip_lowlevel_init = false,
+ };
+ esp_err_t err = touch_slider_sensor_create(&sld_cfg, &touch_slider_handle_, touch_slider_event_callback, this);
+ if (err != ESP_OK) {
+ ESP_LOGW(TAG, "touch_slider_sensor_create failed: %s", esp_err_to_name(err));
+ touch_slider_handle_ = nullptr;
+ return;
+ }
+ xTaskCreatePinnedToCore(touch_cap_poll_task, "touch_cap", 3072, this, 3, &touch_slider_task_handle_, 1);
+ ESP_LOGI(TAG, "Touch slider (PCB v1.2+): PAD1 GPIO%d ch%u, PAD2 GPIO%d ch%u",
+ (int)TOUCH_PAD1, (unsigned)slider_ch[0], (int)TOUCH_PAD2, (unsigned)slider_ch[1]);
+ return;
+ }
+
+ static uint32_t btn_ch[1];
+ static float btn_thr[1];
+ btn_ch[0] = ch1;
+ btn_thr[0] = 0.004f;
+
+ touch_button_config_t btn_cfg = {
+ .channel_num = 1,
+ .channel_list = btn_ch,
+ .channel_threshold = btn_thr,
+ .channel_gold_value = nullptr,
+ .debounce_times = 2,
+ .skip_lowlevel_init = false,
+ };
+ esp_err_t err = touch_button_sensor_create(&btn_cfg, &touch_button_handle_, touch_button_event_callback, this);
+ if (err != ESP_OK) {
+ ESP_LOGW(TAG, "touch_button_sensor_create failed: %s", esp_err_to_name(err));
+ touch_button_handle_ = nullptr;
+ return;
+ }
+ xTaskCreatePinnedToCore(touch_cap_poll_task, "touch_cap", 3072, this, 3, &touch_slider_task_handle_, 1);
+ ESP_LOGI(TAG, "Touch button (PCB v1.0): TOUCH_PAD1 GPIO%d ch%u", (int)TOUCH_PAD1, (unsigned)btn_ch[0]);
+ }
+
+ void InitializeSpi()
+ {
+ const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK,
+ QSPI_PIN_NUM_LCD_DATA0,
+ QSPI_PIN_NUM_LCD_DATA1,
+ QSPI_PIN_NUM_LCD_DATA2,
+ QSPI_PIN_NUM_LCD_DATA3,
+ QSPI_LCD_H_RES * 80 * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSt77916Display(uint8_t pcb_version)
+ {
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io));
+ st77916_vendor_config_t vendor_config = {
+ .init_cmds = vendor_specific_init_yysj,
+ .init_cmds_size = sizeof(vendor_specific_init_yysj) / sizeof(st77916_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = QSPI_PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL,
+ .flags = {
+ .reset_active_high = pcb_version,
+ },
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_disp_on_off(panel, true);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+
+#if CONFIG_USE_EMOTE_MESSAGE_STYLE
+ display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+#else
+ 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);
+#endif
+ backlight_ = new PwmBacklight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeButtons()
+ {
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ ESP_LOGI(TAG, "Boot button pressed, enter WiFi configuration mode");
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ gpio_config_t power_gpio_config = {
+ .pin_bit_mask = (BIT64(POWER_CTRL)),
+ .mode = GPIO_MODE_OUTPUT,
+
+ };
+ ESP_ERROR_CHECK(gpio_config(&power_gpio_config));
+
+ gpio_set_level(POWER_CTRL, 0);
+ }
+
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ void InitializeCamera() {
+ esp_video_init_usb_uvc_config_t usb_uvc_config = {
+ .uvc = {
+ .uvc_dev_num = 1,
+ .task_stack = 4096,
+ .task_priority = 5,
+ .task_affinity = -1,
+ },
+ .usb = {
+ .init_usb_host_lib = true,
+ .task_stack = 4096,
+ .task_priority = 5,
+ .task_affinity = -1,
+ },
+ };
+
+ esp_video_init_config_t video_config = {
+ .usb_uvc = &usb_uvc_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ }
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+
+public:
+ ~EspVocat() {
+ // Stop tasks
+ if (charge_task_handle_ != nullptr) {
+ vTaskDelete(charge_task_handle_);
+ }
+ if (touch_task_handle_ != nullptr) {
+ vTaskDelete(touch_task_handle_);
+ }
+ if (imu_task_handle_ != nullptr) {
+ vTaskDelete(imu_task_handle_);
+ }
+ if (touch_slider_task_handle_ != nullptr) {
+ vTaskDelete(touch_slider_task_handle_);
+ touch_slider_task_handle_ = nullptr;
+ }
+ if (touch_slider_handle_ != nullptr) {
+ touch_slider_sensor_delete(touch_slider_handle_);
+ touch_slider_handle_ = nullptr;
+ }
+ if (touch_button_handle_ != nullptr) {
+ touch_button_sensor_delete(touch_button_handle_);
+ touch_button_handle_ = nullptr;
+ }
+
+ // Delete objects
+ delete charge_;
+ delete cst816s_;
+ delete display_;
+ // Note: backlight_ (PwmBacklight) and camera_ (EspVideo) are not deleted here
+ // because their base classes (Backlight, Camera) don't have virtual destructors.
+ // Since EspVocat is a singleton that lives for the device lifetime, this is acceptable.
+
+ // Remove GPIO ISR handler
+ gpio_isr_handler_remove(TP_PIN_NUM_INT);
+ if (emotion_reset_timer_ != nullptr) {
+ esp_timer_stop(emotion_reset_timer_);
+ esp_timer_delete(emotion_reset_timer_);
+ emotion_reset_timer_ = nullptr;
+ }
+
+ // Disable temperature sensor
+ if (temp_sensor != NULL) {
+ temperature_sensor_disable(temp_sensor);
+ temperature_sensor_uninstall(temp_sensor);
+ temp_sensor = NULL;
+ }
+ }
+
+ EspVocat() : boot_button_(BOOT_BUTTON_GPIO)
+ {
+ const esp_timer_create_args_t emotion_timer_args = {
+ .callback = &EspVocat::emotion_reset_timer_callback,
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "emotion_rst",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&emotion_timer_args, &emotion_reset_timer_));
+
+ InitializeI2c();
+ uint8_t pcb_version = DetectPcbVersion();
+ InitializeCharge();
+ InitializeCst816sTouchPad();
+ InitializeBmi270();
+
+ InitializeSpi();
+ InitializeSt77916Display(pcb_version);
+ InitializeButtons();
+ InitializeCapacitiveTouchPads();
+#ifdef CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ InitializeCamera();
+#endif // CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE
+ }
+
+ virtual AudioCodec* GetAudioCodec() override
+ {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override
+ {
+ return display_;
+ }
+
+ Cst816s* GetTouchpad()
+ {
+ return cst816s_;
+ }
+
+ virtual Backlight* GetBacklight() override
+ {
+ return backlight_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(EspVocat);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/touch.h b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/touch.h
new file mode 100644
index 0000000..3c9857c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp-vocat/touch.h
@@ -0,0 +1,51 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+/**
+ * @file
+ * @brief BSP Touchscreen
+ *
+ * This file offers API for basic touchscreen initialization.
+ * It is useful for users who want to use the touchscreen without the default Graphical Library LVGL.
+ *
+ * For standard LCD initialization with LVGL graphical library, you can call all-in-one function bsp_display_start().
+ */
+
+#pragma once
+#include "esp_lcd_touch.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief BSP touch configuration structure
+ *
+ */
+typedef struct {
+ void *dummy; /*!< Prepared for future use. */
+} bsp_touch_config_t;
+
+/**
+ * @brief Create new touchscreen
+ *
+ * If you want to free resources allocated by this function, you can use esp_lcd_touch API, ie.:
+ *
+ * \code{.c}
+ * esp_lcd_touch_del(tp);
+ * \endcode
+ *
+ * @param[in] config touch configuration
+ * @param[out] ret_touch esp_lcd_touch touchscreen handle
+ * @return
+ * - ESP_OK On success
+ * - Else esp_lcd_touch failure
+ */
+esp_err_t bsp_touch_new(const bsp_touch_config_t *config, esp_lcd_touch_handle_t *ret_touch);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/README.md
new file mode 100644
index 0000000..fcd8758
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/README.md
@@ -0,0 +1,30 @@
+# 鐩稿叧璧勬枡锛�
+- [鎻掔嚎娆綸(https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/003_ESP32-CGC-144%E6%8F%92%E7%BA%BF%E7%89%88%E5%B0%8F%E6%99%BAAI)
+
+- [鐢垫睜娆綸(https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/004_ESP32-CGC-144%E7%94%B5%E6%B1%A0%E7%89%88%E5%B0%8F%E6%99%BAAI)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32锛�**
+
+```bash
+idf.py set-target esp32
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> ESP32 CGC 144
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.h
new file mode 100644
index 0000000..6a23386
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.h
@@ -0,0 +1,73 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+// 濡傛灉浣跨敤鎻掔嚎鐗堟湰鎺ュ叆鐢垫睜锛岃鍚敤涓嬮潰涓�琛�
+//#define ESP32_CGC_144_lite
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
+
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define ASR_BUTTON_GPIO GPIO_NUM_13
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_12
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4
+#define DISPLAY_SCLK_PIN GPIO_NUM_18
+#define DISPLAY_MOSI_PIN GPIO_NUM_23
+#define DISPLAY_CS_PIN GPIO_NUM_5
+#define DISPLAY_DC_PIN GPIO_NUM_2
+#define DISPLAY_RESET_PIN GPIO_NUM_NC
+
+#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
+
+// 濡傛灉浣跨敤240x240鐨勫睆骞曪紝璇锋敞閲婁笅闈竴琛�
+#define LCD_128X128
+
+#ifdef LCD_128X128
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 2
+#define DISPLAY_OFFSET_Y 3
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define DISPLAY_SPI_MODE 0
+
+#else
+
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define DISPLAY_SPI_MODE 0
+
+#endif
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.json
new file mode 100644
index 0000000..ca42e8a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32",
+ "builds": [
+ {
+ "name": "esp32-cgc-144",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc
new file mode 100644
index 0000000..085b815
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/esp32_cgc_144_board.cc
@@ -0,0 +1,192 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#include <esp_sleep.h>
+
+#if defined(ESP32_CGC_144_lite)
+#include "power_manager_lite.h"
+#else
+#include "power_manager.h"
+#endif
+
+#define TAG "ESP32_CGC_144"
+
+class ESP32_CGC_144 : public WifiBoard {
+private:
+ Button boot_button_;
+ LcdDisplay* display_;
+ Button asr_button_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+#if defined(ESP32_CGC_144_lite)
+void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_NC);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+}
+#else
+void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_36);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+}
+#endif
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSt7735Display() {
+ // esp_lcd_panel_io_handle_t panel_io = nullptr;
+ // esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RESET_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ asr_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ std::string wake_word="浣犲ソ灏忔櫤";
+ Application::GetInstance().WakeWordInvoke(wake_word);
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ ESP32_CGC_144() :
+ boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeSt7735Display();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(ESP32_CGC_144);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager.h
new file mode 100644
index 0000000..9576e30
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager.h
@@ -0,0 +1,186 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_3, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1970, 0},
+ {2062, 20},
+ {2154, 40},
+ {2246, 60},
+ {2338, 80},
+ {2430, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_3, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager_lite.h b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager_lite.h
new file mode 100644
index 0000000..f088286
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc-144/power_manager_lite.h
@@ -0,0 +1,185 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value)); //lite
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // lite瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {3000, 0},
+ {3160, 20},
+ {3320, 40},
+ {3480, 60},
+ {3640, 80},
+ {3800, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config)); //lite
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/README.md b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/README.md
new file mode 100644
index 0000000..3a40900
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/README.md
@@ -0,0 +1,36 @@
+# 涓绘澘寮�婧愬湴鍧�锛�
+- V1:[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb)
+- V2:[https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb_copy](https://oshwhub.com/wdmomo/esp32-xiaozhi-kidpcb_copy)
+- 鏇村浠嬬粛锛歔wdmomo.fun](https://www.wdmomo.fun:81/doc/index.html?file=001_%E8%AE%BE%E8%AE%A1%E9%A1%B9%E7%9B%AE/0001_%E5%B0%8F%E6%99%BAAI/002_ESP32-CGC%E5%BC%80%E5%8F%91%E6%9D%BF%E5%B0%8F%E6%99%BAAI)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32锛�**
+
+```bash
+idf.py set-target esp32
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> ESP32 CGC
+```
+
+**閫夋嫨灞忓箷绫诲瀷锛�**
+
+```
+Xiaozhi Assistant -> LCD Type -> "ST7735, 鍒嗚鲸鐜�128*128"
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.h
new file mode 100644
index 0000000..2e27571
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.h
@@ -0,0 +1,271 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 濡傛灉浣跨敤 Duplex I2S 妯″紡锛岃娉ㄩ噴涓嬮潰涓�琛�
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_25
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_26
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_32
+
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_33
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_27
+
+#else
+
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+
+#endif
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define ASR_BUTTON_GPIO GPIO_NUM_13
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_12
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4
+#define DISPLAY_SCLK_PIN GPIO_NUM_18
+#define DISPLAY_MOSI_PIN GPIO_NUM_23
+#define DISPLAY_CS_PIN GPIO_NUM_5
+#define DISPLAY_DC_PIN GPIO_NUM_2
+#define DISPLAY_RESET_PIN GPIO_NUM_NC
+
+#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
+
+#ifdef CONFIG_LCD_ST7789_240X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X320_NO_IPS
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_170X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 170
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 35
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_172X320
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 172
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 34
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X280
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X240_7PIN
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 2
+#endif
+
+#ifdef CONFIG_LCD_ST7789_240X135
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 135
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 40
+#define DISPLAY_OFFSET_Y 53
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X160
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 160
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif
+
+#ifdef CONFIG_LCD_ST7735_128X128
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 2
+#define DISPLAY_OFFSET_Y 3
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ST7796_320X480
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_ILI9341_240X320_NO_IPS
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_GC9A01_240X240
+#define LCD_TYPE_GC9A01_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#ifdef CONFIG_LCD_CUSTOM
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+#endif
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.json
new file mode 100644
index 0000000..a8a1629
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32",
+ "builds": [
+ {
+ "name": "esp32-cgc",
+ "sdkconfig_append": [
+ "CONFIG_LCD_ST7735_128X128=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/esp32_cgc_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/esp32_cgc_board.cc
new file mode 100644
index 0000000..951e481
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32-cgc/esp32_cgc_board.cc
@@ -0,0 +1,180 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+#include <esp_lcd_ili9341.h>
+#endif
+
+#if defined(LCD_TYPE_GC9A01_SERIAL)
+#include <esp_lcd_gc9a01.h>
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb1, (uint8_t[]){0x80}, 1, 0},
+ {0xb2, (uint8_t[]){0x27}, 1, 0},
+ {0xb3, (uint8_t[]){0x13}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x05}, 1, 0},
+ {0xac, (uint8_t[]){0xc8}, 1, 0},
+ {0xab, (uint8_t[]){0x0f}, 1, 0},
+ {0x3a, (uint8_t[]){0x05}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x08}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xea, (uint8_t[]){0x02}, 1, 0},
+ {0xe8, (uint8_t[]){0x2A}, 1, 0},
+ {0xe9, (uint8_t[]){0x47}, 1, 0},
+ {0xe7, (uint8_t[]){0x5f}, 1, 0},
+ {0xc6, (uint8_t[]){0x21}, 1, 0},
+ {0xc7, (uint8_t[]){0x15}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
+ 0x04, 0x12, 0x14, 0x1f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
+ 0x0C, 0x1A, 0x14, 0x1E},
+ 14, 0},
+ {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
+ {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
+};
+#endif
+
+#define TAG "ESP32_CGC"
+
+class ESP32_CGC : public WifiBoard {
+private:
+ Button boot_button_;
+ LcdDisplay* display_;
+ Button asr_button_;
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RESET_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+#if defined(LCD_TYPE_ILI9341_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+#elif defined(LCD_TYPE_GC9A01_SERIAL)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+#else
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
+#endif
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+#ifdef LCD_TYPE_GC9A01_SERIAL
+ panel_config.vendor_config = &gc9107_vendor_config;
+#endif
+ 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 InitializeButtons() {
+
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ asr_button_.OnClick([this]() {
+ std::string wake_word="浣犲ソ灏忔櫤";
+ Application::GetInstance().WakeWordInvoke(wake_word);
+ });
+
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ ESP32_CGC() :
+ boot_button_(BOOT_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override
+ {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ 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(ESP32_CGC);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.h
new file mode 100644
index 0000000..afb1dfa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.h
@@ -0,0 +1,81 @@
+
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_48
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_17
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_5
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ST7789
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 280
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 20
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ILI9341
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_40
+#define CAMERA_PIN_SIOD GPIO_NUM_17
+#define CAMERA_PIN_SIOC GPIO_NUM_18
+
+#define CAMERA_PIN_D7 GPIO_NUM_39
+#define CAMERA_PIN_D6 GPIO_NUM_41
+#define CAMERA_PIN_D5 GPIO_NUM_42
+#define CAMERA_PIN_D4 GPIO_NUM_12
+#define CAMERA_PIN_D3 GPIO_NUM_3
+#define CAMERA_PIN_D2 GPIO_NUM_14
+#define CAMERA_PIN_D1 GPIO_NUM_47
+#define CAMERA_PIN_D0 GPIO_NUM_13
+#define CAMERA_PIN_VSYNC GPIO_NUM_21
+#define CAMERA_PIN_HREF GPIO_NUM_38
+#define CAMERA_PIN_PCLK GPIO_NUM_11
+
+#define XCLK_FREQ_HZ 20000000
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.json
new file mode 100644
index 0000000..2bd927c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32s3-korvo2-v3-rndis",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/esp32s3_korvo2_v3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/esp32s3_korvo2_v3_board.cc
new file mode 100644
index 0000000..6689db5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/esp32s3_korvo2_v3_board.cc
@@ -0,0 +1,449 @@
+#include "rndis_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_io_expander_tca9554.h>
+#include <esp_lcd_ili9341.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "esp32_camera.h"
+#include "power_manager.h"
+#include "power_save_timer.h"
+
+#define TAG "esp32s3_korvo2_v3_rndis"
+/* ADC Buttons */
+typedef enum {
+ BSP_ADC_BUTTON_REC,
+ BSP_ADC_BUTTON_VOL_MUTE,
+ BSP_ADC_BUTTON_PLAY,
+ BSP_ADC_BUTTON_SET,
+ BSP_ADC_BUTTON_VOL_DOWN,
+ BSP_ADC_BUTTON_VOL_UP,
+ BSP_ADC_BUTTON_NUM
+} bsp_adc_button_t;
+
+// Init ili9341 by custom cmd
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
+ {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
+ {0xC5, (uint8_t []){0xD0}, 1, 0},
+ {0xC1, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
+ {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
+
+ {0xB1, (uint8_t []){00, 0x1B}, 2, 0},
+ {0x36, (uint8_t []){0x08}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0xB7, (uint8_t []){0x06}, 1, 0},
+
+ {0x11, (uint8_t []){0}, 0x80, 0},
+ {0x29, (uint8_t []){0}, 0x80, 0},
+
+ {0, (uint8_t []){0}, 0xff, 0},
+};
+
+// https://github.com/78/xiaozhi-esp32/pull/1655
+class Esp32S3Korvo2V3Board : public RndisBoard {
+private:
+ Button boot_button_;
+ Button* adc_button_[BSP_ADC_BUTTON_NUM];
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+ adc_oneshot_unit_handle_t bsp_adc_handle = NULL;
+#endif
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ esp_io_expander_handle_t io_expander_ = NULL;
+ Esp32Camera* camera_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ void InitializePowerManager() {
+ // PowerManager闇�瑕佸鐢ㄦ寜閽殑ADC鍙ユ焺锛屾墍浠ュ湪InitializeButtons涔嬪悗璋冪敤
+ // 浼犲叆鎸夐挳鐨凙DC鍙ユ焺鎸囬拡锛岃PowerManager澶嶇敤
+ power_manager_ = new PowerManager(GPIO_NUM_NC, &bsp_adc_handle);
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializeTca9554() {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander_);
+ if(ret != ESP_OK) {
+ ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000, &io_expander_);
+ if(ret != ESP_OK) {
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ return;
+ }
+ }
+ // 閰嶇疆IO0-IO3涓鸿緭鍑烘ā寮�
+ ESP_ERROR_CHECK(esp_io_expander_set_dir(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 |
+ IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3,
+ IO_EXPANDER_OUTPUT));
+
+ // 澶嶄綅LCD鍜孴ouchPad
+ ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1));
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 0));
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1));
+ }
+
+ void EnableLcdCs() {
+ if(io_expander_ != NULL) {
+ esp_io_expander_set_level(io_expander_, IO_EXPANDER_PIN_NUM_3, 0);// 缃綆 LCD CS
+ }
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_0;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_1;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void ChangeVol(int val) {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + val;
+ if (volume > 100) {
+ volume = 100;
+ }
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void MuteVol() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume();
+ if (volume > 1) {
+ volume = 0;
+ } else {
+ volume = 50;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void InitializeButtons() {
+ button_adc_config_t adc_cfg = {};
+ adc_cfg.adc_channel = ADC_CHANNEL_4; // ADC1 channel 0 is GPIO5
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+ const adc_oneshot_unit_init_cfg_t init_config1 = {
+ .unit_id = ADC_UNIT_1,
+ };
+ adc_oneshot_new_unit(&init_config1, &bsp_adc_handle);
+ adc_cfg.adc_handle = &bsp_adc_handle;
+#endif
+ adc_cfg.button_index = BSP_ADC_BUTTON_REC;
+ adc_cfg.min = 2310; // middle is 2410mV
+ adc_cfg.max = 2510;
+ adc_button_[0] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_VOL_MUTE;
+ adc_cfg.min = 1880; // middle is 1980mV
+ adc_cfg.max = 2080;
+ adc_button_[1] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_PLAY;
+ adc_cfg.min = 1550; // middle is 1650mV
+ adc_cfg.max = 1750;
+ adc_button_[2] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_SET;
+ adc_cfg.min = 1015; // middle is 1115mV
+ adc_cfg.max = 1215;
+ adc_button_[3] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_VOL_DOWN;
+ adc_cfg.min = 720; // middle is 820mV
+ adc_cfg.max = 920;
+ adc_button_[4] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_VOL_UP;
+ adc_cfg.min = 280; // middle is 380mV
+ adc_cfg.max = 480;
+ adc_button_[5] = new AdcButton(adc_cfg);
+
+ auto volume_up_button = adc_button_[BSP_ADC_BUTTON_VOL_UP];
+ volume_up_button->OnClick([this]() {ChangeVol(10);});
+ volume_up_button->OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ auto volume_down_button = adc_button_[BSP_ADC_BUTTON_VOL_DOWN];
+ volume_down_button->OnClick([this]() {ChangeVol(-10);});
+ volume_down_button->OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ auto volume_mute_button = adc_button_[BSP_ADC_BUTTON_VOL_MUTE];
+ volume_mute_button->OnClick([this]() {MuteVol();});
+
+ auto play_button = adc_button_[BSP_ADC_BUTTON_PLAY];
+ play_button->OnClick([this]() {
+ ESP_LOGI(TAG, " TODO %s:%d\n", __func__, __LINE__);
+ });
+
+ auto set_button = adc_button_[BSP_ADC_BUTTON_SET];
+ set_button->OnClick([this]() {
+ });
+
+ auto rec_button = adc_button_[BSP_ADC_BUTTON_REC];
+ rec_button->OnClick([this]() {
+ Application::GetInstance().ToggleChatState();
+ });
+ boot_button_.OnClick([this]() {});
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeIli9341Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_NC;
+ io_config.dc_gpio_num = GPIO_NUM_2;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ // panel_config.flags.reset_active_high = 0,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ EnableLcdCs();
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+ 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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_46;
+ io_config.dc_gpio_num = GPIO_NUM_2;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ EnableLcdCs();
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ 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 InitializeCamera() {
+ camera_config_t camera_config = {
+ .pin_pwdn = CAMERA_PIN_PWDN,
+ .pin_reset = CAMERA_PIN_RESET,
+ .pin_xclk = CAMERA_PIN_XCLK,
+ .pin_sccb_sda = -1, // Use initialized I2C
+ .pin_sccb_scl = -1,
+ .pin_d7 = CAMERA_PIN_D7,
+ .pin_d6 = CAMERA_PIN_D6,
+ .pin_d5 = CAMERA_PIN_D5,
+ .pin_d4 = CAMERA_PIN_D4,
+ .pin_d3 = CAMERA_PIN_D3,
+ .pin_d2 = CAMERA_PIN_D2,
+ .pin_d1 = CAMERA_PIN_D1,
+ .pin_d0 = CAMERA_PIN_D0,
+ .pin_vsync = CAMERA_PIN_VSYNC,
+ .pin_href = CAMERA_PIN_HREF,
+ .pin_pclk = CAMERA_PIN_PCLK,
+
+ .xclk_freq_hz = XCLK_FREQ_HZ,
+ .ledc_timer = LEDC_TIMER_0,
+ .ledc_channel = LEDC_CHANNEL_0,
+
+ .pixel_format = PIXFORMAT_RGB565,
+ .frame_size = FRAMESIZE_QVGA,
+ .jpeg_quality = 12,
+ .fb_count = 2,
+ .fb_location = CAMERA_FB_IN_PSRAM,
+ .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
+ .sccb_i2c_port = (i2c_port_t)1,
+ };
+
+ camera_ = new Esp32Camera(camera_config);
+ if (camera_ != nullptr) {
+ camera_->SetVFlip(true);
+ }
+ }
+
+public:
+ Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board");
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ I2cDetect();
+ InitializeTca9554();
+ InitializeCamera();
+ InitializeSpi();
+ InitializeButtons(); // 鍏堝垵濮嬪寲鎸夐挳锛堝垱寤篈DC1鍙ユ焺锛�
+ InitializePowerManager(); // 鍚庡垵濮嬪寲PowerManager锛堝鐢ˋDC1鍙ユ焺锛�
+ #ifdef LCD_TYPE_ILI9341_SERIAL
+ InitializeIli9341Display();
+ #else
+ InitializeSt7789Display();
+ #endif
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override {
+ return display_;
+ }
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ }
+};
+
+DECLARE_BOARD(Esp32S3Korvo2V3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/power_manager.h
new file mode 100644
index 0000000..49a6956
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3-rndis/power_manager.h
@@ -0,0 +1,249 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_adc/adc_cali.h>
+#include <esp_adc/adc_cali_scheme.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ bool adc_handle_owned_ = false; // 鏍囪ADC鍙ユ焺鏄惁鐢辨湰绫诲垱寤�
+ adc_cali_handle_t adc_cali_handle_ = nullptr; // ADC鏍″噯鍙ユ焺
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_raw = 0;
+ int voltage_mv = 0; // ADC鏍″噯鍚庣殑鐢靛帇锛坢V锛�
+
+ // 澶氭閲囨牱鍙栧钩鍧囷紝鎻愰珮绋冲畾鎬�
+ uint32_t adc_sum = 0;
+ const int sample_count = 10;
+ for (int i = 0; i < sample_count; i++) {
+ int temp_raw = 0;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &temp_raw));
+ adc_sum += temp_raw;
+ }
+ adc_raw = adc_sum / sample_count;
+
+ // 浣跨敤ADC鏍″噯鑾峰彇鍑嗙‘鐢靛帇
+ if (adc_cali_handle_) {
+ ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_mv));
+ } else {
+ // 濡傛灉娌℃湁鏍″噯锛屼娇鐢ㄧ嚎鎬ц绠�
+ voltage_mv = (int)(adc_raw * 3300.0f / 4095.0f);
+ }
+
+ // 鏍规嵁鍒嗗帇姣旇绠楀疄闄呯數姹犵數鍘�
+ // 鐢佃矾鍒嗗帇姣�: R21/(R20+R21) = 100K/300K = 1/3
+ // 瀹為檯鐢垫睜鐢靛帇 = ADC娴嬮噺鐢靛帇 脳 3
+ int battery_voltage_mv = voltage_mv * 3;
+
+ // 灏嗙數鍘嬪�兼坊鍔犲埌闃熷垪涓敤浜庡钩婊�
+ adc_values_.push_back(battery_voltage_mv);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+
+ uint32_t average_voltage = 0;
+ for (auto value : adc_values_) {
+ average_voltage += value;
+ }
+ average_voltage /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿锛堝熀浜庡疄闄呯數姹犵數鍘嬶紝鍗曚綅mV锛�
+ const struct {
+ uint16_t voltage_mv; // 鐢垫睜鐢靛帇锛坢V锛�
+ uint8_t level; // 鐢甸噺鐧惧垎姣�
+ } levels[] = {
+ {3500, 0}, // 3.5V
+ {3640, 20}, // 3.64V
+ {3760, 40}, // 3.76V
+ {3880, 60}, // 3.88V
+ {4000, 80}, // 4.0V
+ {4200, 100} // 4.2V
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_voltage < levels[0].voltage_mv) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_voltage >= levels[5].voltage_mv) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_voltage >= levels[i].voltage_mv && average_voltage < levels[i+1].voltage_mv) {
+ float ratio = static_cast<float>(average_voltage - levels[i].voltage_mv) /
+ (levels[i+1].voltage_mv - levels[i].voltage_mv);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC raw: %d, ADC voltage: %dmV, Battery: %ldmV (%.2fV), level: %ld%%",
+ adc_raw, voltage_mv, average_voltage, average_voltage/1000.0f, battery_level_);
+ }
+
+public:
+ // 鏋勯�犲嚱鏁帮細浣跨敤澶栭儴ADC鍙ユ焺锛堢敤浜庡鐢ㄥ凡瀛樺湪鐨凙DC锛�
+ PowerManager(gpio_num_t pin, adc_oneshot_unit_handle_t* external_adc_handle = nullptr)
+ : charging_pin_(pin), adc_handle_owned_(false) {
+ if(charging_pin_ != GPIO_NUM_NC){
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖栨垨澶嶇敤 ADC
+ if (external_adc_handle != nullptr && *external_adc_handle != nullptr) {
+ // 澶嶇敤澶栭儴ADC鍙ユ焺
+ adc_handle_ = *external_adc_handle;
+ adc_handle_owned_ = false;
+ } else {
+ // 鍒涘缓鏂扮殑ADC鍙ユ焺
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1, // GPIO6 瀵瑰簲 ADC1
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+ adc_handle_owned_ = true;
+ }
+
+ // 閰嶇疆ADC閫氶亾
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config)); // GPIO6 = ADC1_CHANNEL_5
+
+ // 鍒濆鍖朅DC鏍″噯
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = ADC_UNIT_1,
+ .chan = ADC_CHANNEL_5,
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_);
+ if (ret == ESP_OK) {
+ ESP_LOGI("PowerManager", "ADC calibration initialized successfully");
+ } else {
+ ESP_LOGW("PowerManager", "ADC calibration failed, using linear calculation");
+ adc_cali_handle_ = nullptr;
+ }
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ // 鍒犻櫎ADC鏍″噯鍙ユ焺
+ if (adc_cali_handle_) {
+ adc_cali_delete_scheme_curve_fitting(adc_cali_handle_);
+ }
+ // 鍙湁褰揂DC鍙ユ焺鏄湰绫诲垱寤虹殑鏃跺�欐墠鍒犻櫎
+ if (adc_handle_ && adc_handle_owned_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.h b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.h
new file mode 100644
index 0000000..afb1dfa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.h
@@ -0,0 +1,81 @@
+
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_48
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_17
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_5
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ST7789
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 280
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 20
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#ifdef CONFIG_ESP32S3_KORVO2_V3_LCD_ILI9341
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_40
+#define CAMERA_PIN_SIOD GPIO_NUM_17
+#define CAMERA_PIN_SIOC GPIO_NUM_18
+
+#define CAMERA_PIN_D7 GPIO_NUM_39
+#define CAMERA_PIN_D6 GPIO_NUM_41
+#define CAMERA_PIN_D5 GPIO_NUM_42
+#define CAMERA_PIN_D4 GPIO_NUM_12
+#define CAMERA_PIN_D3 GPIO_NUM_3
+#define CAMERA_PIN_D2 GPIO_NUM_14
+#define CAMERA_PIN_D1 GPIO_NUM_47
+#define CAMERA_PIN_D0 GPIO_NUM_13
+#define CAMERA_PIN_VSYNC GPIO_NUM_21
+#define CAMERA_PIN_HREF GPIO_NUM_38
+#define CAMERA_PIN_PCLK GPIO_NUM_11
+
+#define XCLK_FREQ_HZ 20000000
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.json b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.json
new file mode 100644
index 0000000..0560539
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32s3-korvo2-v3",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc
new file mode 100644
index 0000000..1d867ad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc
@@ -0,0 +1,454 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_io_expander_tca9554.h>
+#include <esp_lcd_ili9341.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "esp32_camera.h"
+#include "power_manager.h"
+#include "power_save_timer.h"
+
+#define TAG "esp32s3_korvo2_v3"
+/* ADC Buttons */
+typedef enum {
+ BSP_ADC_BUTTON_REC,
+ BSP_ADC_BUTTON_VOL_MUTE,
+ BSP_ADC_BUTTON_PLAY,
+ BSP_ADC_BUTTON_SET,
+ BSP_ADC_BUTTON_VOL_DOWN,
+ BSP_ADC_BUTTON_VOL_UP,
+ BSP_ADC_BUTTON_NUM
+} bsp_adc_button_t;
+
+// Init ili9341 by custom cmd
+static const ili9341_lcd_init_cmd_t vendor_specific_init[] = {
+ {0xC8, (uint8_t []){0xFF, 0x93, 0x42}, 3, 0},
+ {0xC0, (uint8_t []){0x0E, 0x0E}, 2, 0},
+ {0xC5, (uint8_t []){0xD0}, 1, 0},
+ {0xC1, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0x00, 0x03, 0x08, 0x06, 0x13, 0x09, 0x39, 0x39, 0x48, 0x02, 0x0a, 0x08, 0x17, 0x17, 0x0F}, 15, 0},
+ {0xE1, (uint8_t []){0x00, 0x28, 0x29, 0x01, 0x0d, 0x03, 0x3f, 0x33, 0x52, 0x04, 0x0f, 0x0e, 0x37, 0x38, 0x0F}, 15, 0},
+
+ {0xB1, (uint8_t []){00, 0x1B}, 2, 0},
+ {0x36, (uint8_t []){0x08}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0xB7, (uint8_t []){0x06}, 1, 0},
+
+ {0x11, (uint8_t []){0}, 0x80, 0},
+ {0x29, (uint8_t []){0}, 0x80, 0},
+
+ {0, (uint8_t []){0}, 0xff, 0},
+};
+
+class Esp32S3Korvo2V3Board : public WifiBoard {
+private:
+ Button boot_button_;
+ Button* adc_button_[BSP_ADC_BUTTON_NUM];
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+ adc_oneshot_unit_handle_t bsp_adc_handle = NULL;
+#endif
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ esp_io_expander_handle_t io_expander_ = NULL;
+ Esp32Camera* camera_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ void InitializePowerManager() {
+ // PowerManager闇�瑕佸鐢ㄦ寜閽殑ADC鍙ユ焺锛屾墍浠ュ湪InitializeButtons涔嬪悗璋冪敤
+ // 浼犲叆鎸夐挳鐨凙DC鍙ユ焺鎸囬拡锛岃PowerManager澶嶇敤
+ power_manager_ = new PowerManager(GPIO_NUM_NC, &bsp_adc_handle);
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializeTca9554() {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander_);
+ if(ret != ESP_OK) {
+ ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554A_ADDRESS_000, &io_expander_);
+ if(ret != ESP_OK) {
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ return;
+ }
+ }
+ // 閰嶇疆IO0-IO3涓鸿緭鍑烘ā寮�
+ ESP_ERROR_CHECK(esp_io_expander_set_dir(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 |
+ IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3,
+ IO_EXPANDER_OUTPUT));
+
+ // 澶嶄綅LCD鍜孴ouchPad
+ ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1));
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 0));
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ESP_ERROR_CHECK(esp_io_expander_set_level(io_expander_,
+ IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1));
+ }
+
+ void EnableLcdCs() {
+ if(io_expander_ != NULL) {
+ esp_io_expander_set_level(io_expander_, IO_EXPANDER_PIN_NUM_3, 0);// 缃綆 LCD CS
+ }
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_0;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_1;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void ChangeVol(int val) {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + val;
+ if (volume > 100) {
+ volume = 100;
+ }
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void MuteVol() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume();
+ if (volume > 1) {
+ volume = 0;
+ } else {
+ volume = 50;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ }
+
+ void InitializeButtons() {
+ button_adc_config_t adc_cfg = {};
+ adc_cfg.adc_channel = ADC_CHANNEL_4; // ADC1 channel 0 is GPIO5
+#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 0, 0)
+ const adc_oneshot_unit_init_cfg_t init_config1 = {
+ .unit_id = ADC_UNIT_1,
+ };
+ adc_oneshot_new_unit(&init_config1, &bsp_adc_handle);
+ adc_cfg.adc_handle = &bsp_adc_handle;
+#endif
+ adc_cfg.button_index = BSP_ADC_BUTTON_REC;
+ adc_cfg.min = 2310; // middle is 2410mV
+ adc_cfg.max = 2510;
+ adc_button_[0] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_VOL_MUTE;
+ adc_cfg.min = 1880; // middle is 1980mV
+ adc_cfg.max = 2080;
+ adc_button_[1] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_PLAY;
+ adc_cfg.min = 1550; // middle is 1650mV
+ adc_cfg.max = 1750;
+ adc_button_[2] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_SET;
+ adc_cfg.min = 1015; // middle is 1115mV
+ adc_cfg.max = 1215;
+ adc_button_[3] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_VOL_DOWN;
+ adc_cfg.min = 720; // middle is 820mV
+ adc_cfg.max = 920;
+ adc_button_[4] = new AdcButton(adc_cfg);
+
+ adc_cfg.button_index = BSP_ADC_BUTTON_VOL_UP;
+ adc_cfg.min = 280; // middle is 380mV
+ adc_cfg.max = 480;
+ adc_button_[5] = new AdcButton(adc_cfg);
+
+ auto volume_up_button = adc_button_[BSP_ADC_BUTTON_VOL_UP];
+ volume_up_button->OnClick([this]() {ChangeVol(10);});
+ volume_up_button->OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ auto volume_down_button = adc_button_[BSP_ADC_BUTTON_VOL_DOWN];
+ volume_down_button->OnClick([this]() {ChangeVol(-10);});
+ volume_down_button->OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ auto volume_mute_button = adc_button_[BSP_ADC_BUTTON_VOL_MUTE];
+ volume_mute_button->OnClick([this]() {MuteVol();});
+
+ auto play_button = adc_button_[BSP_ADC_BUTTON_PLAY];
+ play_button->OnClick([this]() {
+ ESP_LOGI(TAG, " TODO %s:%d\n", __func__, __LINE__);
+ });
+
+ auto set_button = adc_button_[BSP_ADC_BUTTON_SET];
+ set_button->OnClick([this]() {
+ EnterWifiConfigMode();
+ });
+
+ auto rec_button = adc_button_[BSP_ADC_BUTTON_REC];
+ rec_button->OnClick([this]() {
+ Application::GetInstance().ToggleChatState();
+ });
+ boot_button_.OnClick([this]() {});
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeIli9341Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_NC;
+ io_config.dc_gpio_num = GPIO_NUM_2;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const ili9341_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(ili9341_lcd_init_cmd_t),
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ // panel_config.flags.reset_active_high = 0,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ EnableLcdCs();
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+ 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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_46;
+ io_config.dc_gpio_num = GPIO_NUM_2;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ EnableLcdCs();
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ 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 InitializeCamera() {
+ camera_config_t camera_config = {
+ .pin_pwdn = CAMERA_PIN_PWDN,
+ .pin_reset = CAMERA_PIN_RESET,
+ .pin_xclk = CAMERA_PIN_XCLK,
+ .pin_sccb_sda = -1, // Use initialized I2C
+ .pin_sccb_scl = -1,
+ .pin_d7 = CAMERA_PIN_D7,
+ .pin_d6 = CAMERA_PIN_D6,
+ .pin_d5 = CAMERA_PIN_D5,
+ .pin_d4 = CAMERA_PIN_D4,
+ .pin_d3 = CAMERA_PIN_D3,
+ .pin_d2 = CAMERA_PIN_D2,
+ .pin_d1 = CAMERA_PIN_D1,
+ .pin_d0 = CAMERA_PIN_D0,
+ .pin_vsync = CAMERA_PIN_VSYNC,
+ .pin_href = CAMERA_PIN_HREF,
+ .pin_pclk = CAMERA_PIN_PCLK,
+
+ .xclk_freq_hz = XCLK_FREQ_HZ,
+ .ledc_timer = LEDC_TIMER_0,
+ .ledc_channel = LEDC_CHANNEL_0,
+
+ .pixel_format = PIXFORMAT_RGB565,
+ .frame_size = FRAMESIZE_QVGA,
+ .jpeg_quality = 12,
+ .fb_count = 2,
+ .fb_location = CAMERA_FB_IN_PSRAM,
+ .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
+ .sccb_i2c_port = (i2c_port_t)1,
+ };
+
+ camera_ = new Esp32Camera(camera_config);
+ if(camera_ != nullptr) {
+ camera_->SetVFlip(true);
+ }
+ }
+
+public:
+ Esp32S3Korvo2V3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board");
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ I2cDetect();
+ InitializeTca9554();
+ InitializeCamera();
+ InitializeSpi();
+ InitializeButtons(); // 鍏堝垵濮嬪寲鎸夐挳锛堝垱寤篈DC1鍙ユ焺锛�
+ InitializePowerManager(); // 鍚庡垵濮嬪寲PowerManager锛堝鐢ˋDC1鍙ユ焺锛�
+ #ifdef LCD_TYPE_ILI9341_SERIAL
+ InitializeIli9341Display();
+ #else
+ InitializeSt7789Display();
+ #endif
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override {
+ return display_;
+ }
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(Esp32S3Korvo2V3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/power_manager.h
new file mode 100644
index 0000000..5c16640
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/esp32s3-korvo2-v3/power_manager.h
@@ -0,0 +1,250 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_adc/adc_cali.h>
+#include <esp_adc/adc_cali_scheme.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ bool adc_handle_owned_ = false; // 鏍囪ADC鍙ユ焺鏄惁鐢辨湰绫诲垱寤�
+ adc_cali_handle_t adc_cali_handle_ = nullptr; // ADC鏍″噯鍙ユ焺
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_raw = 0;
+ int voltage_mv = 0; // ADC鏍″噯鍚庣殑鐢靛帇锛坢V锛�
+
+ // 澶氭閲囨牱鍙栧钩鍧囷紝鎻愰珮绋冲畾鎬�
+ uint32_t adc_sum = 0;
+ const int sample_count = 10;
+ for (int i = 0; i < sample_count; i++) {
+ int temp_raw = 0;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_5, &temp_raw));
+ adc_sum += temp_raw;
+ vTaskDelay(pdMS_TO_TICKS(10)); // 姣忔閲囨牱闂撮殧10ms
+ }
+ adc_raw = adc_sum / sample_count;
+
+ // 浣跨敤ADC鏍″噯鑾峰彇鍑嗙‘鐢靛帇
+ if (adc_cali_handle_) {
+ ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_mv));
+ } else {
+ // 濡傛灉娌℃湁鏍″噯锛屼娇鐢ㄧ嚎鎬ц绠�
+ voltage_mv = (int)(adc_raw * 3300.0f / 4095.0f);
+ }
+
+ // 鏍规嵁鍒嗗帇姣旇绠楀疄闄呯數姹犵數鍘�
+ // 鐢佃矾鍒嗗帇姣�: R21/(R20+R21) = 100K/300K = 1/3
+ // 瀹為檯鐢垫睜鐢靛帇 = ADC娴嬮噺鐢靛帇 脳 3
+ int battery_voltage_mv = voltage_mv * 3;
+
+ // 灏嗙數鍘嬪�兼坊鍔犲埌闃熷垪涓敤浜庡钩婊�
+ adc_values_.push_back(battery_voltage_mv);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+
+ uint32_t average_voltage = 0;
+ for (auto value : adc_values_) {
+ average_voltage += value;
+ }
+ average_voltage /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿锛堝熀浜庡疄闄呯數姹犵數鍘嬶紝鍗曚綅mV锛�
+ const struct {
+ uint16_t voltage_mv; // 鐢垫睜鐢靛帇锛坢V锛�
+ uint8_t level; // 鐢甸噺鐧惧垎姣�
+ } levels[] = {
+ {3500, 0}, // 3.5V
+ {3640, 20}, // 3.64V
+ {3760, 40}, // 3.76V
+ {3880, 60}, // 3.88V
+ {4000, 80}, // 4.0V
+ {4200, 100} // 4.2V
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_voltage < levels[0].voltage_mv) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_voltage >= levels[5].voltage_mv) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_voltage >= levels[i].voltage_mv && average_voltage < levels[i+1].voltage_mv) {
+ float ratio = static_cast<float>(average_voltage - levels[i].voltage_mv) /
+ (levels[i+1].voltage_mv - levels[i].voltage_mv);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC raw: %d, ADC voltage: %dmV, Battery: %ldmV (%.2fV), level: %ld%%",
+ adc_raw, voltage_mv, average_voltage, average_voltage/1000.0f, battery_level_);
+ }
+
+public:
+ // 鏋勯�犲嚱鏁帮細浣跨敤澶栭儴ADC鍙ユ焺锛堢敤浜庡鐢ㄥ凡瀛樺湪鐨凙DC锛�
+ PowerManager(gpio_num_t pin, adc_oneshot_unit_handle_t* external_adc_handle = nullptr)
+ : charging_pin_(pin), adc_handle_owned_(false) {
+ if(charging_pin_ != GPIO_NUM_NC){
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖栨垨澶嶇敤 ADC
+ if (external_adc_handle != nullptr && *external_adc_handle != nullptr) {
+ // 澶嶇敤澶栭儴ADC鍙ユ焺
+ adc_handle_ = *external_adc_handle;
+ adc_handle_owned_ = false;
+ } else {
+ // 鍒涘缓鏂扮殑ADC鍙ユ焺
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1, // GPIO6 瀵瑰簲 ADC1
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+ adc_handle_owned_ = true;
+ }
+
+ // 閰嶇疆ADC閫氶亾
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_5, &chan_config)); // GPIO6 = ADC1_CHANNEL_5
+
+ // 鍒濆鍖朅DC鏍″噯
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = ADC_UNIT_1,
+ .chan = ADC_CHANNEL_5,
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ esp_err_t ret = adc_cali_create_scheme_curve_fitting(&cali_config, &adc_cali_handle_);
+ if (ret == ESP_OK) {
+ ESP_LOGI("PowerManager", "ADC calibration initialized successfully");
+ } else {
+ ESP_LOGW("PowerManager", "ADC calibration failed, using linear calculation");
+ adc_cali_handle_ = nullptr;
+ }
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ // 鍒犻櫎ADC鏍″噯鍙ユ焺
+ if (adc_cali_handle_) {
+ adc_cali_delete_scheme_curve_fitting(adc_cali_handle_);
+ }
+ // 鍙湁褰揂DC鍙ユ焺鏄湰绫诲垱寤虹殑鏃跺�欐墠鍒犻櫎
+ if (adc_handle_ && adc_handle_owned_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/ReadMe.md b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/ReadMe.md
new file mode 100644
index 0000000..391529c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/ReadMe.md
@@ -0,0 +1,5 @@
+# Freenove ESP32-S3 ESP32 S3 Capacitive Touch Display CYD WiFi BT, 2.8 Inch 240x320 IPS Screen
+
+[product](https://store.freenove.com/products/fnk0104)
+
+Official github: [freenove-esp32s3-display-2.8-lcd](https://github.com/Freenove/Freenove_ESP32_S3_Display)
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.h b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.h
new file mode 100644
index 0000000..0f315f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.h
@@ -0,0 +1,53 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+//闊抽iis閮ㄥ垎
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_4 //MCLK
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 //SCK
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 //DIN
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_7 //LRC
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DOUT
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_1 //PA
+
+//闊抽iic閮ㄥ垎
+#define AUDIO_CODEC_I2C_NUM I2C_NUM_0
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_16
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+//boot寮曡剼
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define BUILTIN_LED_GPIO GPIO_NUM_42
+
+//灞忓箷鏄剧ず閮ㄥ垎
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_45
+
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+#define DISPLAY_SCK_PIN GPIO_NUM_12
+#define DISPLAY_DC_PIN GPIO_NUM_46
+#define DISPLAY_CS_PIN GPIO_NUM_10
+#define DISPLAY_MOSI_PIN GPIO_NUM_11
+#define DISPLAY_MIS0_PIN GPIO_NUM_13
+#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
+
+#define LCD_SPI_HOST SPI3_HOST
+
+#define LCD_TYPE_ILI9341_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.json b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.json
new file mode 100644
index 0000000..0d60753
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/config.json
@@ -0,0 +1,15 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "freenove-esp32s3-display-2.8-lcd",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"",
+ "CONFIG_LANGUAGE_EN_US=y"
+ "CONFIG_SR_WN_WN9S_HIESP=y",
+ "CONFIG_SR_WN_WN9_HIESP=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc
new file mode 100644
index 0000000..beadb89
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/freenove-esp32s3-display-2.8-lcd/freenove-esp32s3-display-2.8-lcd.cc
@@ -0,0 +1,229 @@
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <wifi_station.h>
+#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>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_timer.h>
+
+#include "led/single_led.h"
+#include "system_reset.h"
+#include "esp_lcd_ili9341.h"
+
+#define TAG "FreenoveESP32S3Display"
+
+class TouchDriver {
+public:
+ TouchDriver() : dev_(nullptr) {}
+
+ bool Init(i2c_master_bus_handle_t bus, uint8_t addr) {
+ i2c_device_config_t cfg = {
+ .device_address = addr,
+ .scl_speed_hz = 400000,
+ .scl_wait_us = 0,
+ };
+ return i2c_master_bus_add_device(bus, &cfg, &dev_) == ESP_OK;
+ }
+
+ bool Read(bool &touched, uint16_t &x, uint16_t &y) {
+ touched = false;
+ x = y = 0;
+ if (!dev_) return false;
+
+ uint8_t reg = 0x02;
+ uint8_t buf[5];
+ if (i2c_master_transmit_receive(dev_, ®, 1, buf, 5, 50) != ESP_OK) return false;
+
+ uint8_t points = buf[0] & 0x0F;
+ if (points == 0) return true;
+
+ touched = true;
+ x = ((buf[1] & 0x0F) << 8) | buf[2];
+ y = ((buf[3] & 0x0F) << 8) | buf[4];
+ return true;
+ }
+
+private:
+ i2c_master_dev_handle_t dev_;
+};
+
+class FreenoveESP32S3Display : public WifiBoard {
+private:
+ Button boot_button_;
+ LcdDisplay *display_;
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ TouchDriver touch_;
+
+ static void TouchTask(void *arg) {
+ auto *self = static_cast<FreenoveESP32S3Display*>(arg);
+ auto &app = Application::GetInstance();
+
+ uint32_t last_tap = 0;
+ uint32_t down_start = 0;
+ bool down = false;
+
+ while (true) {
+ bool t;
+ uint16_t x, y;
+ self->touch_.Read(t, x, y);
+
+ uint32_t now = esp_timer_get_time() / 1000;
+
+ if (t) {
+ if (!down) {
+ down = true;
+ down_start = now;
+ }
+ }
+
+ if (!t && down) {
+ down = false;
+
+ uint32_t press = now - down_start;
+
+ // long tap
+ if (press > 3000) {
+ self->EnterWifiConfigMode();
+ } else {
+ // double tap
+ if (now - last_tap < 250) {
+ app.StartListening();
+ last_tap = 0;
+ } else {
+ // single tap
+ app.ToggleChatState();
+ last_tap = now;
+ }
+ }
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+ }
+
+ void InitializeTouch() {
+ if (!touch_.Init(codec_i2c_bus_, 0x38)) return;
+ xTaskCreatePinnedToCore(TouchTask, "touch_task", 4096, this, 5, nullptr, 0);
+ }
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = AUDIO_CODEC_I2C_NUM,
+ .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_MOSI_PIN;
+ buscfg.miso_io_num = DISPLAY_MIS0_PIN;
+ buscfg.sclk_io_num = DISPLAY_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(LCD_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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(LCD_SPI_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+ ESP_LOGI(TAG, "Install LCD driver ILI9341");
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ 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() {
+ }
+
+public:
+ FreenoveESP32S3Display(): boot_button_(BOOT_BUTTON_GPIO)
+ {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->SetBrightness(100);
+ }
+
+ virtual Led *GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(codec_i2c_bus_, AUDIO_CODEC_I2C_NUM,
+ 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, true, true);
+ 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(FreenoveESP32S3Display);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.h b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.h
new file mode 100644
index 0000000..4f3aad8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.h
@@ -0,0 +1,43 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_21
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_9
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_1
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_42
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_41
+
+#define DISPLAY_SDA GPIO_NUM_3
+#define DISPLAY_SCL GPIO_NUM_4
+#define DISPLAY_DC GPIO_NUM_5
+#define DISPLAY_CS GPIO_NUM_6
+#define DISPLAY_RES GPIO_NUM_7
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.json b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.json
new file mode 100644
index 0000000..a8c65f8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "genjutech-s3-1.54tft",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc
new file mode 100644
index 0000000..2d7ed92
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/genjutech-s3-1.54tft.cc
@@ -0,0 +1,259 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <esp_efuse_table.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "power_save_timer.h"
+
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#define TAG "GenJuTech_s3_1_54TFT"
+
+class SparkBotEs8311AudioCodec : public Es8311AudioCodec {
+ private:
+
+ public:
+ SparkBotEs8311AudioCodec(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)
+ : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate,
+ mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {}
+
+ void EnableOutput(bool enable) override {
+ if (enable == output_enabled_) {
+ return;
+ }
+ if (enable) {
+ Es8311AudioCodec::EnableOutput(enable);
+ } else {
+ // Nothing todo because the display io and PA io conflict
+ }
+ }
+ };
+
+class GenJuTech_s3_1_54TFT : public WifiBoard {
+private:
+ // i2c_master_bus_handle_t display_i2c_bus_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ LcdDisplay* display_;
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_16);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ // 绗竴涓弬鏁颁笉涓� -1 鏃讹紝杩涘叆鐫$湢浼氬叧闂煶棰戣緭鍏�
+ power_save_timer_ = new PowerSaveTimer(240, 60);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ // boot_button_.OnPressDown([this]() {
+ // Application::GetInstance().StartListening();
+ // });
+ // boot_button_.OnPressUp([this]() {
+ // Application::GetInstance().StopListening();
+ // });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ gpio_config_t config = {
+ .pin_bit_mask = (1ULL << DISPLAY_RES),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&config));
+ gpio_set_level(DISPLAY_RES, 0);
+ vTaskDelay(20);
+ gpio_set_level(DISPLAY_RES, 1);
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ 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);
+ }
+
+public:
+ GenJuTech_s3_1_54TFT() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ ESP_LOGI(TAG, "Initializing GenJuTech S3 1.54 Board");
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static SparkBotEs8311AudioCodec 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(GenJuTech_s3_1_54TFT);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/power_manager.h
new file mode 100644
index 0000000..f2e385d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/genjutech-s3-1.54tft/power_manager.h
@@ -0,0 +1,186 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_4, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1280, 0},
+ {1334, 20},
+ {1388, 40},
+ {1442, 60},
+ {1496, 80},
+ {1550, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_2,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_4, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/hu-087/README.md b/xiaozhi-esp32-2.2.6/main/boards/hu-087/README.md
new file mode 100644
index 0000000..8dfe066
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/hu-087/README.md
@@ -0,0 +1,7 @@
+
+[product](https://fr.aliexpress.com/item/1005010207331462.html)
+
+One of the cheapest kit in a watch form factor.
+
+## Programming ##
+Before assembling the product, unglue the battery from the board to access the programming header. You'll need a ESP32 programming dongle with control of the EN and IO0 pins. The USB connector is not wired to the USB pins of the ESP32-S3 USB interface and can only be used for charging.
diff --git a/xiaozhi-esp32-2.2.6/main/boards/hu-087/config.h b/xiaozhi-esp32-2.2.6/main/boards/hu-087/config.h
new file mode 100644
index 0000000..dff8b3f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/hu-087/config.h
@@ -0,0 +1,28 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+#define AUDIO_I2S_SPK_GPIO_CTLR GPIO_NUM_17
+
+
+#define TOUCH_BUTTON_GPIO GPIO_NUM_18
+
+#define DISPLAY_SDA_PIN GPIO_NUM_41
+#define DISPLAY_SCL_PIN GPIO_NUM_42
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/hu-087/config.json b/xiaozhi-esp32-2.2.6/main/boards/hu-087/config.json
new file mode 100644
index 0000000..7616766
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/hu-087/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "hu-087",
+ "sdkconfig_append": []
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/hu-087/hu_087_board.cc b/xiaozhi-esp32-2.2.6/main/boards/hu-087/hu_087_board.cc
new file mode 100644
index 0000000..a4ad5ce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/hu-087/hu_087_board.cc
@@ -0,0 +1,151 @@
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#include "application.h"
+#include "assets/lang_config.h"
+#include "boards/common/wifi_board.h"
+#include "button.h"
+#include "codecs/no_audio_codec.h"
+#include "config.h"
+#include "display/oled_display.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "system_reset.h"
+
+#define TAG "Hu087Board"
+
+class Hu087Board : public WifiBoard {
+ private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button touch_button_;
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags =
+ {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(
+ esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(
+ esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void initializeAmpCtrl() {
+ gpio_config_t io_conf = {
+ .pin_bit_mask = (1ULL << AUDIO_I2S_SPK_GPIO_CTLR), // Select GPIO 2
+ .mode = GPIO_MODE_OUTPUT, // Set as output
+ .pull_up_en = GPIO_PULLUP_ENABLE, // Disable pull-up
+ .pull_down_en = GPIO_PULLDOWN_DISABLE, // Disable pull-down
+ .intr_type = GPIO_INTR_DISABLE // Disable interrupts
+ };
+ gpio_config(&io_conf);
+ gpio_set_level(AUDIO_I2S_SPK_GPIO_CTLR, 1);
+ };
+
+ void InitializeButtons() {
+ touch_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ touch_button_.OnLongPress([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ } // Need to implement logic to lower volume
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME +
+ std::to_string(volume));
+ });
+ }
+
+ public:
+ Hu087Board() : touch_button_(TOUCH_BUTTON_GPIO) {
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ initializeAmpCtrl(); // Could control the amp ctrl pin throught voice
+ // detection i guess
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK,
+ AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_RIGHT, AUDIO_I2S_MIC_GPIO_SCK,
+ AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override { return display_; }
+};
+
+DECLARE_BOARD(Hu087Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/README.md
new file mode 100644
index 0000000..8c5eb31
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/README.md
@@ -0,0 +1,39 @@
+# jiuchuan-xiaozhi-sound
+涔濆窛绉戞妧灏忔櫤AI闊崇
+
+## 馃洜锔� 缂栬瘧鎸囧崡
+**寮�鍙戠幆澧�**锛欵SP-IDF v5.4.1
+
+### 缂栬瘧姝ラ锛�
+> 鈿狅笍 **鎻愮ず**锛氳嫢鍦ㄧ紪璇戣繃绋嬩腑璁块棶鍦ㄧ嚎搴撳け璐ワ紝鍙互灏濊瘯鍒囨崲鍔犻�熷櫒鐘舵�侊紝鎴栦慨鏀� [idf_component.yml] 鏂囦欢锛屾浛鎹负鍥藉唴闀滃儚婧愩��
+
+1. 浣跨敤 VSCode 鎵撳紑椤圭洰鏂囦欢澶癸紱
+2. 娓呴櫎宸ョ▼锛圕lean Project锛夛紱
+3. 璁剧疆 ESP-IDF 鐗堟湰涓� `v5.4.1`锛�
+4. 鐐瑰嚮 VSCode 鍙充笅瑙掓彁绀猴紝鐢熸垚 [compile_commands.json] 鏂囦欢锛�
+5. 璁剧疆鐩爣璁惧涓� `[esp32s3] -> [JTAG]`锛�
+6. 鎵撳紑 **SDK Configuration Editor**锛�
+7. 璁剧疆 **Board Type** 涓� **涔濆窛绉戞妧**锛�
+8. 淇濆瓨閰嶇疆骞跺紑濮嬬紪璇戙��
+
+## 馃攲 鐑у綍姝ラ
+1. 浣跨敤鏁版嵁绾胯繛鎺ョ數鑴戜笌闊崇锛�
+2. 鍏抽棴璁惧鐢垫簮鍚庯紝闀挎寜鐢垫簮閿笉鏉炬墜锛�
+3. 鍦ㄧ儳褰曞伐鍏蜂腑閫夋嫨瀵瑰簲鐨勪覆鍙o紙COM Port锛夛紱
+4. 鐐瑰嚮鐑у綍鎸夐挳锛岄�夋嫨 UART 妯″紡锛�
+5. 鐑у綍瀹屾垚鍓嶈鍕挎澗寮�鐢垫簮閿��
+
+
+## 寮曡剼
+- 1-9锛�
+ - 1. DAT2 锛� NC
+ - 2. CD/DAT3 锛� 鐗囬�夛紝浣庣數骞虫湁鏁堛�傦紙鏈煡锛�
+ - 3. CMD 锛� IO48锛圕ommand/Response Line锛夛紝涓绘満閫氳繃姝ょ嚎鍚慣F鍗″彂閫佸懡浠ゅ拰鏁版嵁
+ - 4. VDD 锛� 渚涚數
+ - 5. CLX 锛� IO47锛堟椂閽燂級锛岀敱涓绘満浜х敓锛屽悓姝ユ暟鎹�氫俊
+ - 6. VSS 锛� GND
+ - 7. DAT0 锛� IO21锛孲PI_MISO锛孴F鍗¢�氳繃姝ょ嚎鍚戜富鏈鸿繑鍥炲搷搴斿拰鏁版嵁
+ - 8. DAT1 锛� NC
+
+
+ 渚濇涓轰粠鍙冲悜宸︿负1-9
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.h
new file mode 100644
index 0000000..914fdf5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.h
@@ -0,0 +1,52 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_13
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_42
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_10
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_3
+#define PWR_EN_GPIO GPIO_NUM_5
+#define PWR_ADC_GPIO GPIO_NUM_4
+#define PWR_BUTTON_TIME 3000000U
+
+#define WIFI_BUTTON_GPIO GPIO_NUM_6
+#define CMD_BUTTON_GPIO GPIO_NUM_7
+
+
+#define DISPLAY_SPI_SCK_PIN GPIO_NUM_41
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_40
+#define DISPLAY_DC_PIN GPIO_NUM_39
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_9
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 296
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.json
new file mode 100644
index 0000000..d1f610f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "jiuchuan-s3",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c
new file mode 100644
index 0000000..b8d0e9a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.c
@@ -0,0 +1,384 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+ #include <stdlib.h>
+ #include <sys/cdefs.h>
+ #include "sdkconfig.h"
+ #include <string.h>
+ #if CONFIG_LCD_ENABLE_DEBUG_LOG
+ // The local log level must be defined before including esp_log.h
+ // Set the maximum log level for this source file
+ #define LOG_LOCAL_LEVEL ESP_LOG_DEBUG
+ #endif
+
+ #include "freertos/FreeRTOS.h"
+ #include "freertos/task.h"
+ #include "esp_lcd_panel_interface.h"
+ #include "esp_lcd_panel_io.h"
+ #include "esp_lcd_panel_vendor.h"
+ #include "esp_lcd_panel_ops.h"
+ #include "esp_lcd_panel_commands.h"
+ #include "driver/gpio.h"
+ #include "esp_log.h"
+ #include "esp_check.h"
+ #include "esp_compiler.h"
+ /* GC9309NA LCD controller driver for ESP-IDF
+ * SPDX-FileCopyrightText: 2024 Your Name
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+ #include "freertos/FreeRTOS.h"
+ #include "freertos/task.h"
+ #include "esp_lcd_panel_interface.h"
+ #include "esp_lcd_panel_io.h"
+ #include "esp_check.h"
+ #include "driver/gpio.h"
+
+
+ // GC9309NA Command Set
+ #define GC9309NA_CMD_SLPIN 0x10
+ #define GC9309NA_CMD_SLPOUT 0x11
+ #define GC9309NA_CMD_INVOFF 0x20
+ #define GC9309NA_CMD_INVON 0x21
+ #define GC9309NA_CMD_DISPOFF 0x28
+ #define GC9309NA_CMD_DISPON 0x29
+ #define GC9309NA_CMD_CASET 0x2A
+ #define GC9309NA_CMD_RASET 0x2B
+ #define GC9309NA_CMD_RAMWR 0x2C
+ #define GC9309NA_CMD_MADCTL 0x36
+ #define GC9309NA_CMD_COLMOD 0x3A
+ #define GC9309NA_CMD_TEOFF 0x34
+ #define GC9309NA_CMD_TEON 0x35
+ #define GC9309NA_CMD_WRDISBV 0x51
+ #define GC9309NA_CMD_WRCTRLD 0x53
+
+ // Manufacturer Commands
+ #define GC9309NA_CMD_SETGAMMA1 0xF0
+ #define GC9309NA_CMD_SETGAMMA2 0xF1
+ #define GC9309NA_CMD_PWRCTRL1 0x67
+ #define GC9309NA_CMD_PWRCTRL2 0x68
+ #define GC9309NA_CMD_PWRCTRL3 0x66
+ #define GC9309NA_CMD_PWRCTRL4 0xCA
+ #define GC9309NA_CMD_PWRCTRL5 0xCB
+ #define GC9309NA_CMD_DINVCTRL 0xB5
+ #define GC9309NA_CMD_REG_ENABLE1 0xFE
+ #define GC9309NA_CMD_REG_ENABLE2 0xEF
+
+ // 鑷妯″紡棰滆壊瀹氫箟
+
+
+ static const char *TAG = "lcd_panel.gc9309na";
+
+ typedef struct {
+ esp_lcd_panel_t base;
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ bool reset_level;
+ int x_gap;
+ int y_gap;
+ uint8_t madctl_val;
+ uint8_t colmod_val;
+ uint16_t te_scanline;
+ uint8_t fb_bits_per_pixel;
+ } gc9309na_panel_t;
+
+ static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel);
+ static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel);
+ static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel);
+ static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+ static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+ static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+ static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
+ static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+ static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool off);
+ static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep);
+
+
+ esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
+ {
+ esp_err_t ret = ESP_OK;
+ gc9309na_panel_t *gc9309 = NULL;
+
+ ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid arg");
+
+ gc9309 = calloc(1, sizeof(gc9309na_panel_t));
+ ESP_GOTO_ON_FALSE(gc9309, ESP_ERR_NO_MEM, err, TAG, "no mem");
+
+
+ // Hardware reset GPIO config
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_config_t io_conf = {
+ .mode = GPIO_MODE_OUTPUT,
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "GPIO config failed");
+ }
+
+ gc9309->colmod_val = 0x55; // RGB565
+ // Initial register values
+
+ gc9309->fb_bits_per_pixel = 16;
+ gc9309->io = io;
+ gc9309->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ gc9309->reset_level = panel_dev_config->flags.reset_active_high;
+ gc9309->x_gap = 0;
+ gc9309->y_gap = 0;
+
+ // Function pointers
+ gc9309->base.del = panel_gc9309na_del;
+ gc9309->base.reset = panel_gc9309na_reset;
+ gc9309->base.init = panel_gc9309na_init;
+ gc9309->base.draw_bitmap = panel_gc9309na_draw_bitmap;
+ gc9309->base.invert_color = panel_gc9309na_invert_color;
+ gc9309->base.set_gap = panel_gc9309na_set_gap;
+ gc9309->base.mirror = panel_gc9309na_mirror;
+ gc9309->base.swap_xy = panel_gc9309na_swap_xy;
+ gc9309->base.disp_on_off = panel_gc9309na_disp_on_off;
+ gc9309->base.disp_sleep = panel_gc9309na_sleep;
+
+ *ret_panel = &(gc9309->base);
+ ESP_LOGI(TAG, "New GC9309NA panel @%p", gc9309);
+ return ESP_OK;
+
+ err:
+ if (gc9309) {
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(gc9309);
+ }
+ return ret;
+ }
+
+ static esp_err_t panel_gc9309na_del(esp_lcd_panel_t *panel)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+
+ if (gc9309->reset_gpio_num >= 0) {
+ gpio_reset_pin(gc9309->reset_gpio_num);
+ }
+ free(gc9309);
+ ESP_LOGI(TAG, "Del GC9309NA panel");
+ return ESP_OK;
+ }
+
+ static esp_err_t panel_gc9309na_reset(esp_lcd_panel_t *panel)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+
+ if (gc9309->reset_gpio_num >= 0) {
+ // Hardware reset
+ gpio_set_level(gc9309->reset_gpio_num, gc9309->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9309->reset_gpio_num, !gc9309->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(120));
+ } else {
+ // Software reset
+ // uint8_t unlock_cmd[] = {GC9309NA_CMD_REG_ENABLE1, GC9309NA_CMD_REG_ENABLE2};
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, 0xFE, unlock_cmd, 2),
+ // TAG, "Unlock failed");
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(gc9309->io, LCD_CMD_SWRESET, NULL, 0),
+ // TAG, "SW Reset failed");
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ return ESP_OK;
+ }
+ static esp_err_t panel_gc9309na_init(esp_lcd_panel_t *panel)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9309->io;
+
+ // Unlock commands
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xFE, NULL, 0), TAG, "Unlock cmd1 failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEF, NULL, 0), TAG, "Unlock cmd2 failed");
+
+ // Sleep out command
+ //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed");
+ //vTaskDelay(pdMS_TO_TICKS(80));
+
+ // Timing control commands
+ //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xA0}, 1), TAG, "Timing control failed");
+ //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xE8, (uint8_t[]){0xF0}, 1), TAG, "Timing control failed");
+
+ // Display on command
+ //ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed");
+ // vTaskDelay(pdMS_TO_TICKS(10));
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x80, (uint8_t[]){0xC0}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x81, (uint8_t[]){0x01}, 1), TAG, "DINV failed");
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x82, (uint8_t[]){0x07}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x83, (uint8_t[]){0x38}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x88, (uint8_t[]){0x64}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x89, (uint8_t[]){0x86}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8B, (uint8_t[]){0x3C}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8D, (uint8_t[]){0x51}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x8E, (uint8_t[]){0x70}, 1), TAG, "DINV failed");
+
+ //楂樹綆浣嶄氦鎹�
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB4, (uint8_t[]){0x80}, 1), TAG, "DINV failed");
+
+ gc9309->colmod_val = 0x05; // RGB565
+ gc9309->madctl_val = 0x48; // BGR椤哄簭锛岃缃産it3=1锛堝嵆0x08锛�
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_COLMOD, &gc9309->colmod_val, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9309NA_CMD_MADCTL, &gc9309->madctl_val, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XBF, (uint8_t[]){0X1F}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7d, (uint8_t[]){0x45,0x06}, 2), TAG, "DINV failed");
+ // Continue from where you left off
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEE, (uint8_t[]){0x00,0x06}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF4, (uint8_t[]){0x53}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF6, (uint8_t[]){0x17,0x08}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x70, (uint8_t[]){0x4F,0x4F}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x71, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x72, (uint8_t[]){0x12,0x20}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB5, (uint8_t[]){0x50}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xBA, (uint8_t[]){0x00}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xEC, (uint8_t[]){0x71}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7b, (uint8_t[]){0x00,0x0d}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x7c, (uint8_t[]){0x0d,0x03}, 2), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0XF5, (uint8_t[]){0x02,0x10,0x12}, 3), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF0, (uint8_t[]){0x0C,0x11,0x0b,0x0a,0x05,0x32,0x44,0x8e,0x9a,0x29,0x2E,0x5f}, 12), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xF1, (uint8_t[]){0x0B,0x11,0x0b,0x07,0x07,0x32,0x45,0xBd,0x8D,0x21,0x28,0xAf}, 12), TAG, "DINV failed");
+
+ // 240x296 resolution settings
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2a, (uint8_t[]){0x00,0x00,0x00,0xef}, 4), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2b, (uint8_t[]){0x00,0x00,0x01,0x27}, 4), TAG, "DINV failed");
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x66, (uint8_t[]){0x2C}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x67, (uint8_t[]){0x18}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x68, (uint8_t[]){0x3E}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCA, (uint8_t[]){0x0E}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCB, (uint8_t[]){0x06}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xB6, (uint8_t[]){0x5C,0x40,0x40}, 3), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCC, (uint8_t[]){0x33}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xCD, (uint8_t[]){0x33}, 1), TAG, "DINV failed");
+
+ // Sleep out command
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, NULL, 0), TAG, "Sleep out failed");
+ vTaskDelay(pdMS_TO_TICKS(80));
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xA0}, 1), TAG, "DINV failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xe8, (uint8_t[]){0xf0}, 1), TAG, "DINV failed");
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xfe, NULL, 0), TAG, "unlock cmd1 failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0xee, NULL, 0), TAG, "unlock cmd2 failed");
+
+ // Display on command
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, NULL, 0), TAG, "Display on failed");
+
+ // Memory write command
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x2c, NULL, 0), TAG, "Memory write failed");
+ vTaskDelay(pdMS_TO_TICKS(10));
+ return ESP_OK;
+ }
+
+
+ static esp_err_t panel_gc9309na_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+
+
+ esp_lcd_panel_io_handle_t io = gc9309->io;
+
+ x_start += gc9309->x_gap;
+ x_end += gc9309->x_gap;
+ y_start += gc9309->y_gap;
+ y_end += gc9309->y_gap;
+
+ // define an area of frame memory where MCU can access
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]) {
+ (x_start >> 8) & 0xFF,
+ x_start & 0xFF,
+ ((x_end - 1) >> 8) & 0xFF,
+ (x_end - 1) & 0xFF,
+ }, 4), TAG, "io tx param failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]) {
+ (y_start >> 8) & 0xFF,
+ y_start & 0xFF,
+ ((y_end - 1) >> 8) & 0xFF,
+ (y_end - 1) & 0xFF,
+ }, 4), TAG, "io tx param failed");
+ // transfer frame buffer
+ size_t len = (x_end - x_start) * (y_end - y_start) * gc9309->fb_bits_per_pixel / 8;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "io tx color failed");
+
+ return ESP_OK;
+ }
+
+ static esp_err_t panel_gc9309na_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9309->io;
+ int command = 0;
+ if (invert_color_data) {
+ command = LCD_CMD_INVON;
+ } else {
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG,
+ "io tx param failed");
+ return ESP_OK;
+ }
+
+ static esp_err_t panel_gc9309na_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+ {
+ // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ // esp_lcd_panel_io_handle_t io = gc9309->io;
+ // if (mirror_x) {
+ // gc9309->madctl_val |= LCD_CMD_MX_BIT;
+ // } else {
+ // gc9309->madctl_val &= ~LCD_CMD_MX_BIT;
+ // }
+ // if (mirror_y) {
+ // gc9309->madctl_val |= LCD_CMD_MY_BIT;
+ // } else {
+ // gc9309->madctl_val &= ~LCD_CMD_MY_BIT;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
+ // gc9309->madctl_val
+ // }, 1), TAG, "io tx param failed");
+ return ESP_OK;
+ }
+
+ static esp_err_t panel_gc9309na_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
+ {
+ // gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ // esp_lcd_panel_io_handle_t io = gc9309->io;
+ // if (swap_axes) {
+ // gc9309->madctl_val |= LCD_CMD_MV_BIT;
+ // } else {
+ // gc9309->madctl_val &= ~LCD_CMD_MV_BIT;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
+ // gc9309->madctl_val
+ // }, 1), TAG, "io tx param failed");
+ return ESP_OK;
+ }
+
+ static esp_err_t panel_gc9309na_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ gc9309->x_gap = x_gap;
+ gc9309->y_gap = y_gap;
+ return ESP_OK;
+ }
+
+ static esp_err_t panel_gc9309na_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ uint8_t cmd = on_off ? GC9309NA_CMD_DISPON : GC9309NA_CMD_DISPOFF;
+ return esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0);
+ }
+
+ static esp_err_t panel_gc9309na_sleep(esp_lcd_panel_t *panel, bool sleep)
+ {
+ gc9309na_panel_t *gc9309 = __containerof(panel, gc9309na_panel_t, base);
+ uint8_t cmd = sleep ? GC9309NA_CMD_SLPIN : GC9309NA_CMD_SLPOUT;
+ esp_err_t ret = esp_lcd_panel_io_tx_param(gc9309->io, cmd, NULL, 0);
+ vTaskDelay(pdMS_TO_TICKS(120));
+ return ret;
+ }
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h
new file mode 100644
index 0000000..0a7065b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/esp_lcd_panel_gc9301.h
@@ -0,0 +1,31 @@
+/*
+ * SPDX-FileCopyrightText: 2021-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#pragma once
+
+#include <stdbool.h>
+#include "esp_err.h"
+#include "esp_lcd_panel_dev.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Create LCD panel for model ST7789
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config general panel device configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_gc9309na(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/gpio_manager.h b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/gpio_manager.h
new file mode 100644
index 0000000..f8e83ff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/gpio_manager.h
@@ -0,0 +1,62 @@
+#pragma once
+#include <mutex>
+#include <inttypes.h>
+#include <driver/gpio.h>
+#include <esp_log.h>
+
+class GpioManager {
+public:
+ enum class GpioMode {
+ INPUT,
+ OUTPUT,
+ INPUT_PULLUP,
+ INPUT_PULLDOWN
+ };
+
+ static void SetLevel(gpio_num_t gpio, uint32_t level) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ ESP_ERROR_CHECK(gpio_set_level(gpio, level));
+ ESP_LOGD("GpioManager", "Set GPIO %d level: %d", static_cast<int>(gpio), static_cast<int>(level));
+ }
+
+ static int GetLevel(gpio_num_t gpio) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ int level = gpio_get_level(gpio);
+ ESP_LOGD("GpioManager", "Get GPIO %d level: %d", static_cast<int>(gpio), level);
+ return level;
+ }
+
+ static void Config(gpio_num_t gpio, GpioMode mode) {
+ std::lock_guard<std::mutex> lock(mutex_);
+
+ gpio_config_t config = {};
+ config.pin_bit_mask = (1ULL << gpio);
+
+ switch(mode) {
+ case GpioMode::INPUT:
+ config.mode = GPIO_MODE_INPUT;
+ config.pull_up_en = GPIO_PULLUP_DISABLE;
+ config.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ break;
+ case GpioMode::OUTPUT:
+ config.mode = GPIO_MODE_OUTPUT;
+ break;
+ case GpioMode::INPUT_PULLUP:
+ config.mode = GPIO_MODE_INPUT;
+ config.pull_up_en = GPIO_PULLUP_ENABLE;
+ break;
+ case GpioMode::INPUT_PULLDOWN:
+ config.mode = GPIO_MODE_INPUT;
+ config.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ break;
+ }
+
+ ESP_ERROR_CHECK(gpio_config(&config));
+ ESP_LOGI("GpioManager", "Configured GPIO %d mode: %d", gpio, static_cast<int>(mode));
+ }
+
+private:
+ static std::mutex mutex_;
+};
+
+std::mutex GpioManager::mutex_;
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc
new file mode 100644
index 0000000..76c522e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/jiuchuan_dev_board.cc
@@ -0,0 +1,395 @@
+#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 "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "esp_lcd_panel_gc9301.h"
+
+#include "power_save_timer.h"
+#include "power_manager.h"
+#include "power_controller.h"
+#include "gpio_manager.h"
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define BOARD_TAG "JiuchuanDevBoard"
+#define __USER_GPIO_PWRDOWN__
+
+// 鑷畾涔塋CD鏄剧ず鍣ㄧ被锛岀敤浜庡渾褰㈠睆骞曢�傞厤
+class CustomLcdDisplay : public SpiLcdDisplay
+{
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy)
+ {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+
+ // 鐘舵�佹爮瀹瑰櫒閫傞厤
+ lv_obj_set_style_pad_left(top_bar_, LV_HOR_RES * 0.12, 0); // 宸︿晶濉厖12%
+ lv_obj_set_style_pad_right(top_bar_, LV_HOR_RES * 0.12, 0); // 鍙充晶濉厖12%
+ // 琛ㄦ儏瀹瑰櫒涓婄Щ閫傞厤
+ lv_obj_align(emoji_box_, LV_ALIGN_CENTER, 0, -30); // 鍚戜笂鍋忕Щ30
+ // 娑堟伅鏍忛�傞厤
+ lv_obj_align(bottom_bar_, LV_ALIGN_BOTTOM_MID, 0, -20); // 鍚戜笂鍋忕Щ20
+ }
+};
+
+class JiuchuanDevBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ Button pwr_button_;
+ Button wifi_button;
+ Button cmd_button;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io = NULL;
+ esp_lcd_panel_handle_t panel = NULL;
+
+ // 闊抽噺鏄犲皠鍑芥暟锛氬皢鍐呴儴闊抽噺(0-80)鏄犲皠涓烘樉绀洪煶閲�(0-100%)
+ int MapVolumeForDisplay(int internal_volume) {
+ // 纭繚杈撳叆鍦ㄦ湁鏁堣寖鍥村唴
+ if (internal_volume < 0) internal_volume = 0;
+ if (internal_volume > 80) internal_volume = 80;
+
+ // 灏�0-80鏄犲皠鍒�0-100
+ // 鍏紡: 鏄剧ず闊抽噺 = (鍐呴儴闊抽噺 / 80) * 100
+ return (internal_volume * 100) / 80;
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(PWR_ADC_GPIO);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ #ifndef __USER_GPIO_PWRDOWN__
+ RTC_DATA_ATTR static bool long_press_occurred = false;
+ esp_sleep_wakeup_cause_t cause = esp_sleep_get_wakeup_cause();
+ if (cause == ESP_SLEEP_WAKEUP_EXT0) {
+ ESP_LOGI(TAG, "Wake up by EXT0");
+ const int64_t start = esp_timer_get_time();
+ ESP_LOGI(TAG, "esp_sleep_get_wakeup_cause");
+ while (gpio_get_level(PWR_BUTTON_GPIO) == 0) {
+ if (esp_timer_get_time() - start > 3000000) {
+ long_press_occurred = true;
+ break;
+ }
+ vTaskDelay(100 / portTICK_PERIOD_MS);
+ }
+
+ if (long_press_occurred) {
+ ESP_LOGI(TAG, "Long press wakeup");
+ long_press_occurred = false;
+ } else {
+ ESP_LOGI(TAG, "Short press, return to sleep");
+ ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
+ ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 鍐呴儴涓婃媺
+ ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO));
+ esp_deep_sleep_start();
+ }
+ }
+ #endif
+ //涓�鍒嗛挓杩涘叆娴呯潯鐪狅紝5鍒嗛挓杩涘叆娣辩潯鐪犲叧鏈�
+ power_save_timer_ = new PowerSaveTimer(-1, (60*5), -1);
+ // power_save_timer_ = new PowerSaveTimer(-1, 6, 10);//test
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ #ifndef __USER_GPIO_PWRDOWN__
+ ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
+ ESP_ERROR_CHECK(rtc_gpio_pullup_en(PWR_BUTTON_GPIO)); // 鍐呴儴涓婃媺
+ ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(PWR_BUTTON_GPIO));
+
+ esp_lcd_panel_disp_on_off(panel, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ #else
+ rtc_gpio_set_level(PWR_EN_GPIO, 0);
+ rtc_gpio_hold_dis(PWR_EN_GPIO);
+ #endif
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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 InitializeButtons() {
+ static bool pwrbutton_unreleased = false;
+
+ if (gpio_get_level(GPIO_NUM_3) == 1) {
+ pwrbutton_unreleased = true;
+ }
+ // 閰嶇疆GPIO
+ ESP_LOGI(TAG, "Configuring power button GPIO");
+ GpioManager::Config(GPIO_NUM_3, GpioManager::GpioMode::INPUT_PULLDOWN);
+
+ boot_button_.OnClick([this]() {
+ ESP_LOGI(TAG, "Boot button clicked");
+ power_save_timer_->WakeUp();
+ });
+
+ // 妫�鏌ョ數婧愭寜閽垵濮嬬姸鎬�
+ ESP_LOGI(TAG, "Power button initial state: %d", GpioManager::GetLevel(PWR_BUTTON_GPIO));
+
+ // 楂樼數骞虫湁鏁堥暱鎸夊叧鏈洪�昏緫
+ pwr_button_.OnPressDown([this]() {
+ pwrbutton_unreleased = false;
+ });
+ pwr_button_.OnLongPress([this]()
+ {
+ ESP_LOGI(TAG, "Power button long press detected (high-active)");
+
+ if (pwrbutton_unreleased){
+ ESP_LOGI(TAG, "寮�鏈哄悗鐢垫簮閿湭鏉惧紑,鍙栨秷鍏虫満");
+ return;
+ }
+
+ // 楂樼數骞虫湁鏁堥槻鎶栫‘璁�
+ for (int i = 0; i < 5; i++) {
+ int level = GpioManager::GetLevel(PWR_BUTTON_GPIO);
+ ESP_LOGD(TAG, "Debounce check %d: GPIO%d level=%d", i+1, PWR_BUTTON_GPIO, level);
+
+ if (level == 0) {
+ ESP_LOGW(TAG, "Power button inactive during confirmation - abort shutdown");
+ return;
+ }
+ vTaskDelay(100 / portTICK_PERIOD_MS);
+ }
+
+ ESP_LOGI(TAG, "Confirmed power button pressed - initiating shutdown");
+ power_manager_->SetPowerState(PowerState::SHUTDOWN); });
+
+ //鍗曞嚮鍒囨崲鐘舵��
+ pwr_button_.OnClick([this]()
+ {
+ // 鑾峰彇褰撳墠搴旂敤瀹炰緥鍜岀姸鎬�
+ auto &app = Application::GetInstance();
+ auto current_state = app.GetDeviceState();
+
+ ESP_LOGI(TAG, "褰撳墠璁惧鐘舵��: %d", current_state);
+
+ if (current_state == kDeviceStateIdle) {
+ // 濡傛灉褰撳墠鏄緟鍛界姸鎬侊紝鍒囨崲鍒拌亞鍚姸鎬�
+ ESP_LOGI(TAG, "浠庡緟鍛界姸鎬佸垏鎹㈠埌鑱嗗惉鐘舵��");
+ app.ToggleChatState(); // 鍒囨崲鍒拌亞鍚姸鎬�
+ } else if (current_state == kDeviceStateListening) {
+ // 濡傛灉褰撳墠鏄亞鍚姸鎬侊紝鍒囨崲鍒板緟鍛界姸鎬�
+ ESP_LOGI(TAG, "浠庤亞鍚姸鎬佸垏鎹㈠埌寰呭懡鐘舵��");
+ app.ToggleChatState(); // 鍒囨崲鍒板緟鍛界姸鎬�
+ } else if (current_state == kDeviceStateSpeaking) {
+ // 濡傛灉褰撳墠鏄璇濈姸鎬侊紝缁堟璇磋瘽骞跺垏鎹㈠埌寰呭懡鐘舵��
+ ESP_LOGI(TAG, "浠庤璇濈姸鎬佸垏鎹㈠埌寰呭懡鐘舵��");
+ app.ToggleChatState(); // 缁堟璇磋瘽
+ } else {
+ // 鍏朵粬鐘舵�佷笅鍙敜閱掕澶�
+ ESP_LOGI(TAG, "鍞ら啋璁惧");
+ power_save_timer_->WakeUp();
+ } });
+
+ // 鐢垫簮閿笁鍑伙細閲嶇疆WiFi
+ pwr_button_.OnMultipleClick([this]() {
+ ESP_LOGI(TAG, "Power button triple click: 閲嶇疆WiFi");
+ power_save_timer_->WakeUp();
+ EnterWifiConfigMode();
+ }, 3);
+
+ wifi_button.OnPressDown([this]()
+ {
+ ESP_LOGI(TAG, "Volume up button pressed");
+ power_save_timer_->WakeUp();
+
+ auto codec = GetAudioCodec();
+ int current_vol = codec->output_volume(); // 鑾峰彇瀹為檯褰撳墠闊抽噺
+ current_vol = (current_vol + 8 > 80) ? 80 : current_vol + 8;
+
+ codec->SetOutputVolume(current_vol);
+
+ ESP_LOGI(TAG, "Current volume: %d", current_vol);
+ int display_volume = MapVolumeForDisplay(current_vol);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%");});
+
+ cmd_button.OnPressDown([this]()
+ {
+ ESP_LOGI(TAG, "Volume down button pressed");
+ power_save_timer_->WakeUp();
+
+ auto codec = GetAudioCodec();
+ int current_vol = codec->output_volume(); // 鑾峰彇瀹為檯褰撳墠闊抽噺
+ current_vol = (current_vol - 8 < 0) ? 0 : current_vol - 8;
+
+ codec->SetOutputVolume(current_vol);
+
+ ESP_LOGI(TAG, "Current volume: %d", current_vol);
+ if (current_vol == 0) {
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ } else {
+ int display_volume = MapVolumeForDisplay(current_vol);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(display_volume) + "%");
+ }});
+ }
+
+ void InitializeGC9301isplay()
+ {
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "test Install panel IO");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN;
+ buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+
+ // 鍒濆鍖朣PI鎬荤嚎
+ 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 = 3;
+ 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_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io);
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖9309
+ ESP_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ panel_config.rgb_ele_order = LCD_RGB_ENDIAN_BGR;
+ panel_config.bits_per_pixel = 16;
+ esp_lcd_new_panel_gc9309na(panel_io, &panel_config, &panel);
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+public:
+ JiuchuanDevBoard() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ pwr_button_(PWR_BUTTON_GPIO,true),
+ wifi_button(WIFI_BUTTON_GPIO),
+ cmd_button(CMD_BUTTON_GPIO) {
+
+ InitializeI2c();
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeButtons();
+ InitializeGC9301isplay();
+ GetBacklight()->RestoreBrightness();
+
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(JiuchuanDevBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_controller.h b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_controller.h
new file mode 100644
index 0000000..2496152
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_controller.h
@@ -0,0 +1,58 @@
+#pragma once
+#include <mutex>
+#include <functional>
+#include <driver/gpio.h>
+#include <driver/rtc_io.h>
+#include <esp_log.h>
+#include "config.h"
+enum class PowerState {
+ ACTIVE,
+ LIGHT_SLEEP,
+ DEEP_SLEEP,
+ SHUTDOWN
+ };
+
+class PowerController {
+public:
+
+
+ static PowerController& Instance() {
+ static PowerController instance;
+ return instance;
+ }
+
+ void SetState(PowerState newState) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ if (currentState_ != newState) {
+ ESP_LOGI("PowerCtrl", "State change: %d -> %d",
+ static_cast<int>(currentState_),
+ static_cast<int>(newState));
+
+ currentState_ = newState;
+ if (stateChangeCallback_) {
+ stateChangeCallback_(newState);
+ }
+ }
+ }
+
+ PowerState GetState() const {
+ std::lock_guard<std::mutex> lock(mutex_);
+ return currentState_;
+ }
+
+ void OnStateChange(std::function<void(PowerState)> callback) {
+ stateChangeCallback_ = callback;
+ }
+
+private:
+ PowerController(){
+ rtc_gpio_init(PWR_EN_GPIO);
+ rtc_gpio_set_direction(PWR_EN_GPIO, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(PWR_EN_GPIO, 1);
+ }
+ ~PowerController() = default;
+
+ PowerState currentState_ = PowerState::ACTIVE;
+ std::function<void(PowerState)> stateChangeCallback_;
+ mutable std::mutex mutex_;
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_manager.h
new file mode 100644
index 0000000..e306d10
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/jiuchuan-s3/power_manager.h
@@ -0,0 +1,201 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include "adc_battery_estimation.h"
+#include "power_controller.h"
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define JIUCHUAN_ADC_UNIT (ADC_UNIT_1)
+#define JIUCHUAN_ADC_BITWIDTH (ADC_BITWIDTH_12)
+#define JIUCHUAN_ADC_ATTEN (ADC_ATTEN_DB_12)
+#define JIUCHUAN_ADC_CHANNEL (ADC_CHANNEL_3)
+#define JIUCHUAN_RESISTOR_UPPER (200000)
+#define JIUCHUAN_RESISTOR_LOWER (100000)
+
+#undef TAG
+#define TAG "PowerManager"
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ int32_t battery_level_ = 100;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ bool is_empty_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_battery_estimation_handle_t adc_battery_estimation_handle;
+ PowerController* power_controller_;
+
+
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ float battery_capacity_temp = 0;
+ adc_battery_estimation_get_capacity(adc_battery_estimation_handle, &battery_capacity_temp);
+ ESP_LOGI("PowerManager", "Battery level: %.1f%%", battery_capacity_temp);
+ if(battery_capacity_temp > -10 && battery_capacity_temp <= 0){
+ battery_level_ = 0;
+ }else{
+ battery_level_ = battery_capacity_temp;
+ }
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ power_controller_ = &PowerController::Instance();
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ static const battery_point_t battery_ponint_table[]={
+ { 4.2 , 100},
+ { 4.06 , 80},
+ { 3.82 , 60},
+ { 3.58 , 40},
+ { 3.34 , 20},
+ { 3.1 , 0},
+ { 3.0 , -10}
+ };
+
+ adc_battery_estimation_t config = {
+ .internal = {
+ .adc_unit = JIUCHUAN_ADC_UNIT,
+ .adc_bitwidth = JIUCHUAN_ADC_BITWIDTH,
+ .adc_atten = JIUCHUAN_ADC_ATTEN,
+ },
+ .adc_channel = JIUCHUAN_ADC_CHANNEL,
+ .upper_resistor = JIUCHUAN_RESISTOR_UPPER,
+ .lower_resistor = JIUCHUAN_RESISTOR_LOWER,
+ .battery_points = battery_ponint_table,
+ .battery_points_count = sizeof(battery_ponint_table) / sizeof(battery_ponint_table[0])
+ };
+
+ adc_battery_estimation_handle = adc_battery_estimation_create(&config);
+
+ RegisterAllCallbacks();
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_battery_estimation_handle) {
+ adc_battery_estimation_destroy(adc_battery_estimation_handle);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ //ESP_LOGI(TAG, "鐢甸噺宸叉弧锛屼笉鍐嶆樉绀哄厖鐢典腑");
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ int32_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void RegisterAllCallbacks() {
+ //娉ㄥ唽鐢垫簮鐘舵�佸彉鏇村洖璋冨嚱鏁帮紙浼樺寲鐗堬級
+ power_controller_->OnStateChange([this](PowerState newState) {
+ switch(newState) {
+ case PowerState::SHUTDOWN: {
+
+ ESP_LOGD(TAG, "鍏虫満");
+
+ //鍙栨秷 PWR_EN 浣胯兘
+ /* 闃叉鍏虫満鍚庤鍞ら啋 */
+ ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(PWR_BUTTON_GPIO, 0));
+ ESP_ERROR_CHECK(rtc_gpio_pulldown_en(PWR_BUTTON_GPIO)); // 鍐呴儴涓嬫媺
+ ESP_ERROR_CHECK(rtc_gpio_pullup_dis(PWR_BUTTON_GPIO));
+ /* 鍏抽棴鐢垫簮浣胯兘 */
+ rtc_gpio_set_level(PWR_EN_GPIO, 0);
+ rtc_gpio_hold_dis(PWR_EN_GPIO);
+
+ // 纭繚鎵�鏈夊璁惧凡鍏抽棴
+ vTaskDelay(200 / portTICK_PERIOD_MS);
+ ESP_LOGI(TAG, "Initiating deep sleep");
+
+ esp_deep_sleep_start();
+ break;
+ }
+ default:
+ ESP_LOGD(TAG, "State changed to %d", static_cast<int>(newState));
+ break;
+ }
+ });
+ }
+ void SetPowerState(PowerState newState) {
+ power_controller_->SetState(newState);
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.h
new file mode 100644
index 0000000..a272900
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.h
@@ -0,0 +1,41 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_9
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_3
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2
+
+#define DISPLAY_SDA_PIN GPIO_NUM_7
+#define DISPLAY_SCL_PIN GPIO_NUM_8
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+#define ML307_RX_PIN GPIO_NUM_5
+#define ML307_TX_PIN GPIO_NUM_6
+
+#define AXP2101_I2C_ADDR 0x34
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.json b/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.json
new file mode 100644
index 0000000..d9a581d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "kevin-box-2",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/kevin_box_board.cc b/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/kevin_box_board.cc
new file mode 100644
index 0000000..0e068a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-box-2/kevin_box_board.cc
@@ -0,0 +1,270 @@
+#include "dual_network_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/oled_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/gpio.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "KevinBoxBoard"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ // ** EFUSE defaults **
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ WriteReg(0x93, 0x1C); // 閰嶇疆 aldo2 杈撳嚭涓� 3.3V
+
+ uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0
+ value = value | 0x02; // set bit 1 (ALDO2)
+ WriteReg(0x90, value); // and power channels now enabled
+
+ WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V
+
+ WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA
+ WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA
+
+ WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables
+ WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables
+ WriteReg(0x16, 0x05); // set input current limit to 2000mA
+
+ WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery)
+ WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature)
+ }
+};
+
+class KevinBoxBoard : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, -1, 600);
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void Enable4GModule() {
+ // Make GPIO HIGH to enable the 4G module
+ gpio_config_t ml307_enable_config = {
+ .pin_bit_mask = (1ULL << 4),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ gpio_config(&ml307_enable_config);
+ gpio_set_level(GPIO_NUM_4, 1);
+ }
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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 InitializeButtons() {
+ boot_button_.OnPressDown([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ app.StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ auto& app = Application::GetInstance();
+ app.StopListening();
+ });
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ }
+ }
+ });
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+public:
+ KevinBoxBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeCodecI2c();
+ pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR);
+
+ Enable4GModule();
+
+ InitializeButtons();
+ InitializePowerSaveTimer();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(codec_i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+};
+
+DECLARE_BOARD(KevinBoxBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.h
new file mode 100644
index 0000000..4241320
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.h
@@ -0,0 +1,24 @@
+#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 BUILTIN_LED_GPIO GPIO_NUM_5
+#define BOOT_BUTTON_GPIO GPIO_NUM_6
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.json b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.json
new file mode 100644
index 0000000..b8bf3b7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "kevin-c3",
+ "sdkconfig_append": [
+ "CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
+ "CONFIG_LWIP_IPV6=n",
+ "CONFIG_USE_ESP_WAKE_WORD=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/kevin_c3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/kevin_c3_board.cc
new file mode 100644
index 0000000..6ebff33
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/kevin_c3_board.cc
@@ -0,0 +1,90 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/circular_strip.h"
+#include "led_strip_control.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+
+#define TAG "KevinBoxBoard"
+
+class KevinBoxBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ CircularStrip* led_strip_;
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ // Print I2C bus info
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8);
+ new LedStripControl(led_strip_);
+ }
+
+public:
+ KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializeTools();
+
+ // 鎶� ESP32C3 鐨� VDD SPI 寮曡剼浣滀负鏅�� GPIO 鍙d娇鐢�
+ esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
+ }
+
+ virtual Led* GetLed() override {
+ return led_strip_;
+ }
+
+ 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;
+ }
+};
+
+DECLARE_BOARD(KevinBoxBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.cc b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.cc
new file mode 100644
index 0000000..68dc4ae
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.cc
@@ -0,0 +1,129 @@
+#include "led_strip_control.h"
+#include "settings.h"
+#include "mcp_server.h"
+#include <esp_log.h>
+
+#define TAG "LedStripControl"
+
+int LedStripControl::LevelToBrightness(int level) const {
+ if (level < 0) level = 0;
+ if (level > 8) level = 8;
+ return (1 << level) - 1; // 2^n - 1
+}
+
+StripColor LedStripControl::RGBToColor(int red, int green, int blue) {
+ if (red < 0) red = 0;
+ if (red > 255) red = 255;
+ if (green < 0) green = 0;
+ if (green > 255) green = 255;
+ if (blue < 0) blue = 0;
+ if (blue > 255) blue = 255;
+ return {static_cast<uint8_t>(red), static_cast<uint8_t>(green), static_cast<uint8_t>(blue)};
+}
+
+LedStripControl::LedStripControl(CircularStrip* led_strip)
+ : led_strip_(led_strip) {
+ // 浠庤缃腑璇诲彇浜害绛夌骇
+ Settings settings("led_strip");
+ brightness_level_ = settings.GetInt("brightness", 4); // 榛樿绛夌骇4
+ led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
+
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.led_strip.get_brightness",
+ "Get the brightness of the led strip (0-8)",
+ PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return brightness_level_;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_brightness",
+ "Set the brightness of the led strip (0-8)",
+ PropertyList({
+ Property("level", kPropertyTypeInteger, 0, 8)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level);
+ brightness_level_ = level;
+ led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
+
+ // 淇濆瓨璁剧疆
+ Settings settings("led_strip", true);
+ settings.SetInt("brightness", brightness_level_);
+
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_single_color",
+ "Set the color of a single led.",
+ PropertyList({
+ Property("index", kPropertyTypeInteger, 0, 7),
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int index = properties["index"].value<int>();
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d",
+ index, red, green, blue);
+ led_strip_->SetSingleColor(index, RGBToColor(red, green, blue));
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_all_color",
+ "Set the color of all leds.",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d",
+ red, green, blue);
+ led_strip_->SetAllColor(RGBToColor(red, green, blue));
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.blink",
+ "Blink the led strip. (闂儊)",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255),
+ Property("interval", kPropertyTypeInteger, 0, 1000)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ int interval = properties["interval"].value<int>();
+ ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms",
+ red, green, blue, interval);
+ led_strip_->Blink(RGBToColor(red, green, blue), interval);
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.scroll",
+ "Scroll the led strip. (璺戦┈鐏�)",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255),
+ Property("length", kPropertyTypeInteger, 1, 7),
+ Property("interval", kPropertyTypeInteger, 0, 1000)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ int interval = properties["interval"].value<int>();
+ int length = properties["length"].value<int>();
+ ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms",
+ red, green, blue, length, interval);
+ StripColor low = RGBToColor(4, 4, 4);
+ StripColor high = RGBToColor(red, green, blue);
+ led_strip_->Scroll(low, high, length, interval);
+ return true;
+ });
+
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.h
new file mode 100644
index 0000000..0e99bb3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-c3/led_strip_control.h
@@ -0,0 +1,18 @@
+#ifndef LED_STRIP_CONTROL_H
+#define LED_STRIP_CONTROL_H
+
+#include "led/circular_strip.h"
+
+class LedStripControl {
+private:
+ CircularStrip* led_strip_;
+ int brightness_level_; // 浜害绛夌骇 (0-8)
+
+ int LevelToBrightness(int level) const; // 灏嗙瓑绾ц浆鎹负瀹為檯浜害鍊�
+ StripColor RGBToColor(int red, int green, int blue);
+
+public:
+ explicit LedStripControl(CircularStrip* led_strip);
+};
+
+#endif // LED_STRIP_CONTROL_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/config.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/config.h
new file mode 100644
index 0000000..fd92549
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/config.h
@@ -0,0 +1,64 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_DEFAULT_OUTPUT_VOLUME 90
+
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_42
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_41
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_2
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_3
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_46
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_1
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_38
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC
+#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_48
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define ML307_RX_PIN GPIO_NUM_12
+#define ML307_TX_PIN GPIO_NUM_13
+/* Camera pins */
+#define CAMERA_PIN_PWDN -1
+#define CAMERA_PIN_RESET -1
+#define CAMERA_PIN_XCLK 15
+#define CAMERA_PIN_SIOD 4
+#define CAMERA_PIN_SIOC 5
+
+#define CAMERA_PIN_D7 16
+#define CAMERA_PIN_D6 17
+#define CAMERA_PIN_D5 18
+#define CAMERA_PIN_D4 12
+#define CAMERA_PIN_D3 10
+#define CAMERA_PIN_D2 8
+#define CAMERA_PIN_D1 9
+#define CAMERA_PIN_D0 11
+#define CAMERA_PIN_VSYNC 6
+#define CAMERA_PIN_HREF 7
+#define CAMERA_PIN_PCLK 13
+
+#define XCLK_FREQ_HZ 20000000
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc
new file mode 100644
index 0000000..775f20e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc
@@ -0,0 +1,157 @@
+#include "wifi_board.h"
+#include "ml307_board.h"
+
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include "esp32_camera.h"
+
+#define TAG "kevin-sp-v3"
+
+// class KEVIN_SP_V3Board : public Ml307Board {
+class KEVIN_SP_V3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ Esp32Camera* camera_;
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_47;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_21;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_14;
+ io_config.dc_gpio_num = GPIO_NUM_45;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ 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 InitializeCamera() {
+ // ESP32-S3 浣跨敤 esp_camera 缁勪欢
+ camera_config_t camera_config = {
+ .pin_pwdn = CAMERA_PIN_PWDN,
+ .pin_reset = CAMERA_PIN_RESET,
+ .pin_xclk = CAMERA_PIN_XCLK,
+ .pin_sccb_sda = -1, // 浣跨敤宸插垵濮嬪寲鐨� I2C
+ .pin_sccb_scl = -1,
+ .pin_d7 = CAMERA_PIN_D7,
+ .pin_d6 = CAMERA_PIN_D6,
+ .pin_d5 = CAMERA_PIN_D5,
+ .pin_d4 = CAMERA_PIN_D4,
+ .pin_d3 = CAMERA_PIN_D3,
+ .pin_d2 = CAMERA_PIN_D2,
+ .pin_d1 = CAMERA_PIN_D1,
+ .pin_d0 = CAMERA_PIN_D0,
+ .pin_vsync = CAMERA_PIN_VSYNC,
+ .pin_href = CAMERA_PIN_HREF,
+ .pin_pclk = CAMERA_PIN_PCLK,
+
+ .xclk_freq_hz = XCLK_FREQ_HZ,
+ .ledc_timer = LEDC_TIMER_0,
+ .ledc_channel = LEDC_CHANNEL_0,
+
+ .pixel_format = PIXFORMAT_RGB565,
+ .frame_size = FRAMESIZE_QVGA,
+ .jpeg_quality = 12,
+ .fb_count = 2,
+ .fb_location = CAMERA_FB_IN_PSRAM,
+ .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
+ .sccb_i2c_port = (i2c_port_t)1,
+ };
+
+ camera_ = new Esp32Camera(camera_config);
+ }
+
+public:
+ KEVIN_SP_V3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ ESP_LOGI(TAG, "Initializing KEVIN_SP_V3 Board");
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ InitializeCamera();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(KEVIN_SP_V3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.h
new file mode 100644
index 0000000..27fdc20
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.h
@@ -0,0 +1,66 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_1
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_46
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_2
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_3
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_41
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_38
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define RESET_NVS_BUTTON_GPIO GPIO_NUM_NC
+#define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_48
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define ML307_RX_PIN GPIO_NUM_12
+#define ML307_TX_PIN GPIO_NUM_13
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_15
+#define CAMERA_PIN_SIOD GPIO_NUM_4
+#define CAMERA_PIN_SIOC GPIO_NUM_5
+
+#define CAMERA_PIN_D7 GPIO_NUM_16
+#define CAMERA_PIN_D6 GPIO_NUM_17
+#define CAMERA_PIN_D5 GPIO_NUM_18
+#define CAMERA_PIN_D4 GPIO_NUM_12
+#define CAMERA_PIN_D3 GPIO_NUM_10
+#define CAMERA_PIN_D2 GPIO_NUM_8
+#define CAMERA_PIN_D1 GPIO_NUM_9
+#define CAMERA_PIN_D0 GPIO_NUM_11
+#define CAMERA_PIN_VSYNC GPIO_NUM_6
+#define CAMERA_PIN_HREF GPIO_NUM_7
+#define CAMERA_PIN_PCLK GPIO_NUM_13
+
+#define XCLK_FREQ_HZ 20000000
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.json b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.json
new file mode 100644
index 0000000..9b7205a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "kevin-sp-v4-dev",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc
new file mode 100644
index 0000000..645d21d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-sp-v4-dev/kevin-sp-v4_board.cc
@@ -0,0 +1,173 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include "esp32_camera.h"
+
+#define TAG "kevin-sp-v4"
+
+class KEVIN_SP_V4Board : public WifiBoard {
+private:
+ Button boot_button_;
+ LcdDisplay* display_;
+ i2c_master_bus_handle_t i2c_bus_;
+ Esp32Camera* camera_;
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_47;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_21;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_14;
+ io_config.dc_gpio_num = GPIO_NUM_45;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ 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 InitializeCamera() {
+ // ESP32-S3 浣跨敤 esp_camera 缁勪欢
+ camera_config_t camera_config = {
+ .pin_pwdn = CAMERA_PIN_PWDN,
+ .pin_reset = CAMERA_PIN_RESET,
+ .pin_xclk = CAMERA_PIN_XCLK,
+ .pin_sccb_sda = -1, // 浣跨敤宸插垵濮嬪寲鐨� I2C
+ .pin_sccb_scl = -1,
+ .pin_d7 = CAMERA_PIN_D7,
+ .pin_d6 = CAMERA_PIN_D6,
+ .pin_d5 = CAMERA_PIN_D5,
+ .pin_d4 = CAMERA_PIN_D4,
+ .pin_d3 = CAMERA_PIN_D3,
+ .pin_d2 = CAMERA_PIN_D2,
+ .pin_d1 = CAMERA_PIN_D1,
+ .pin_d0 = CAMERA_PIN_D0,
+ .pin_vsync = CAMERA_PIN_VSYNC,
+ .pin_href = CAMERA_PIN_HREF,
+ .pin_pclk = CAMERA_PIN_PCLK,
+
+ .xclk_freq_hz = XCLK_FREQ_HZ,
+ .ledc_timer = LEDC_TIMER_0,
+ .ledc_channel = LEDC_CHANNEL_0,
+
+ .pixel_format = PIXFORMAT_RGB565,
+ .frame_size = FRAMESIZE_QVGA,
+ .jpeg_quality = 12,
+ .fb_count = 2,
+ .fb_location = CAMERA_FB_IN_PSRAM,
+ .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
+ .sccb_i2c_port = (i2c_port_t)1,
+ };
+
+ camera_ = new Esp32Camera(camera_config);
+ }
+
+public:
+ KEVIN_SP_V4Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ ESP_LOGI(TAG, "Initializing KEVIN SP V4 Board");
+ InitializeCodecI2c();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ InitializeCamera();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(KEVIN_SP_V4Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.h
new file mode 100644
index 0000000..b533741
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.h
@@ -0,0 +1,35 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 16000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_39
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_45
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_WIDTH 376
+#define DISPLAY_HEIGHT 960
+#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_4
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.json b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.json
new file mode 100644
index 0000000..ae29bb4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "kevin-yuying-313lcd",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c
new file mode 100644
index 0000000..9f62738
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.c
@@ -0,0 +1,478 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <driver/gpio.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <esp_check.h>
+#include <esp_lcd_panel_commands.h>
+#include <esp_lcd_panel_interface.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#include "esp_lcd_gc9503.h"
+
+#define GC9503_CMD_MADCTL (0xB1) // Memory data access control
+#define GC9503_CMD_MADCTL_DEFAULT (0x10) // Default value of Memory data access control
+#define GC9503_CMD_SS_BIT (1 << 0) // Source driver scan direction, 0: top to bottom, 1: bottom to top
+#define GC9503_CMD_GS_BIT (1 << 1) // Gate driver scan direction, 0: left to right, 1: right to left
+#define GC9503_CMD_BGR_BIT (1 << 5) // RGB/BGR order, 0: RGB, 1: BGR
+
+typedef struct
+{
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ uint8_t madctl_val; // Save current value of GC9503_CMD_MADCTL register
+ uint8_t colmod_val; // Save current value of LCD_CMD_COLMOD register
+ const gc9503_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+ struct
+ {
+ unsigned int mirror_by_cmd : 1;
+ unsigned int auto_del_panel_io : 1;
+ unsigned int display_on_off_use_cmd : 1;
+ unsigned int reset_level : 1;
+ } flags;
+ // To save the original functions of RGB panel
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+ esp_err_t (*reset)(esp_lcd_panel_t *panel);
+ esp_err_t (*mirror)(esp_lcd_panel_t *panel, bool x_axis, bool y_axis);
+ esp_err_t (*disp_on_off)(esp_lcd_panel_t *panel, bool on_off);
+} gc9503_panel_t;
+
+static const char *TAG = "gc9503";
+
+static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503);
+
+static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool off);
+
+esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+ ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
+ gc9503_vendor_config_t *vendor_config = (gc9503_vendor_config_t *)panel_dev_config->vendor_config;
+ ESP_RETURN_ON_FALSE(vendor_config && vendor_config->rgb_config, ESP_ERR_INVALID_ARG, TAG, "`verndor_config` and `rgb_config` are necessary");
+ ESP_RETURN_ON_FALSE(!vendor_config->flags.auto_del_panel_io || !vendor_config->flags.mirror_by_cmd,
+ ESP_ERR_INVALID_ARG, TAG, "`mirror_by_cmd` and `auto_del_panel_io` cannot work together");
+
+ esp_err_t ret = ESP_OK;
+ gpio_config_t io_conf = {0};
+
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)calloc(1, sizeof(gc9503_panel_t));
+ ESP_RETURN_ON_FALSE(gc9503, ESP_ERR_NO_MEM, TAG, "no mem for gc9503 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ gc9503->madctl_val = GC9503_CMD_MADCTL_DEFAULT;
+ switch (panel_dev_config->rgb_ele_order)
+ {
+ case LCD_RGB_ELEMENT_ORDER_RGB:
+ gc9503->madctl_val &= ~GC9503_CMD_BGR_BIT;
+ break;
+ case LCD_RGB_ELEMENT_ORDER_BGR:
+ gc9503->madctl_val |= GC9503_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color element order");
+ break;
+ }
+
+ gc9503->colmod_val = 0;
+ switch (panel_dev_config->bits_per_pixel)
+ {
+ case 16: // RGB565
+ gc9503->colmod_val = 0x50;
+ break;
+ case 18: // RGB666
+ gc9503->colmod_val = 0x60;
+ break;
+ case 24: // RGB888
+ gc9503->colmod_val = 0x70;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ gc9503->io = io;
+ gc9503->init_cmds = vendor_config->init_cmds;
+ gc9503->init_cmds_size = vendor_config->init_cmds_size;
+ gc9503->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ gc9503->flags.reset_level = panel_dev_config->flags.reset_active_high;
+ gc9503->flags.auto_del_panel_io = vendor_config->flags.auto_del_panel_io;
+ gc9503->flags.mirror_by_cmd = vendor_config->flags.mirror_by_cmd;
+ gc9503->flags.display_on_off_use_cmd = (vendor_config->rgb_config->disp_gpio_num >= 0) ? 0 : 1;
+
+ if (gc9503->flags.auto_del_panel_io)
+ {
+ if (gc9503->reset_gpio_num >= 0)
+ { // Perform hardware reset
+ gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level);
+ }
+ else
+ { // Perform software reset
+ ESP_GOTO_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), err, TAG, "send command failed");
+ }
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ /**
+ * In order to enable the 3-wire SPI interface pins (such as SDA and SCK) to share other pins of the RGB interface
+ * (such as HSYNC) and save GPIOs, we need to send LCD initialization commands via the 3-wire SPI interface before
+ * `esp_lcd_new_rgb_panel()` is called.
+ */
+ ESP_GOTO_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), err, TAG, "send init commands failed");
+ // After sending the initialization commands, the 3-wire SPI interface can be deleted
+ ESP_GOTO_ON_ERROR(esp_lcd_panel_io_del(io), err, TAG, "delete panel IO failed");
+ gc9503->io = NULL;
+ ESP_LOGD(TAG, "delete panel IO");
+ }
+
+ // Create RGB panel
+ ESP_GOTO_ON_ERROR(esp_lcd_new_rgb_panel(vendor_config->rgb_config, ret_panel), err, TAG, "create RGB panel failed");
+ ESP_LOGD(TAG, "new RGB panel @%p", ret_panel);
+
+ // Save the original functions of RGB panel
+ gc9503->init = (*ret_panel)->init;
+ gc9503->del = (*ret_panel)->del;
+ gc9503->reset = (*ret_panel)->reset;
+ gc9503->mirror = (*ret_panel)->mirror;
+ gc9503->disp_on_off = (*ret_panel)->disp_on_off;
+ // Overwrite the functions of RGB panel
+ (*ret_panel)->init = panel_gc9503_init;
+ (*ret_panel)->del = panel_gc9503_del;
+ (*ret_panel)->reset = panel_gc9503_reset;
+ (*ret_panel)->mirror = panel_gc9503_mirror;
+ (*ret_panel)->disp_on_off = panel_gc9503_disp_on_off;
+ (*ret_panel)->user_data = gc9503;
+ ESP_LOGD(TAG, "new gc9503 panel @%p", gc9503);
+
+ // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9503_VER_MAJOR, ESP_LCD_GC9503_VER_MINOR,
+ // ESP_LCD_GC9503_VER_PATCH);
+ return ESP_OK;
+
+err:
+ if (gc9503)
+ {
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(gc9503);
+ }
+ return ret;
+}
+
+// *INDENT-OFF*
+// static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = {
+// // {cmd, { data }, data_size, delay_ms}
+// {0x11, (uint8_t []){0x00}, 0, 120},
+
+// {0xf0, (uint8_t []){0x55, 0xaa, 0x52, 0x08, 0x00}, 5, 0},
+// {0xf6, (uint8_t []){0x5a, 0x87}, 2, 0},
+// {0xc1, (uint8_t []){0x3f}, 1, 0},
+// {0xc2, (uint8_t []){0x0e}, 1, 0},
+// {0xc6, (uint8_t []){0xf8}, 1, 0},
+// {0xc9, (uint8_t []){0x10}, 1, 0},
+// {0xcd, (uint8_t []){0x25}, 1, 0},
+// {0xf8, (uint8_t []){0x8a}, 1, 0},
+// {0xac, (uint8_t []){0x45}, 1, 0},
+// {0xa0, (uint8_t []){0xdd}, 1, 0},
+// {0xa7, (uint8_t []){0x47}, 1, 0},
+// {0xfa, (uint8_t []){0x00, 0x00, 0x00, 0x04}, 4, 0},
+// {0x86, (uint8_t []){0x99, 0xa3, 0xa3, 0x51}, 4, 0},
+// {0xa3, (uint8_t []){0xee}, 1, 0},
+// {0xfd, (uint8_t []){0x3c, 0x3c, 0x00}, 3, 0},
+// {0x71, (uint8_t []){0x48}, 1, 0},
+// {0x72, (uint8_t []){0x48}, 1, 0},
+// {0x73, (uint8_t []){0x00, 0x44}, 2, 0},
+// {0x97, (uint8_t []){0xee}, 1, 0},
+// {0x83, (uint8_t []){0x93}, 1, 0},
+// {0x9a, (uint8_t []){0x72}, 1, 0},
+// {0x9b, (uint8_t []){0x5a}, 1, 0},
+// {0x82, (uint8_t []){0x2c, 0x2c}, 2, 0},
+// {0x6d, (uint8_t []){0x00, 0x1f, 0x19, 0x1a, 0x10, 0x0e, 0x0c, 0x0a, 0x02, 0x07, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e,
+// 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x08, 0x01, 0x09, 0x0b, 0x0d, 0x0f, 0x1a, 0x19, 0x1f, 0x00}, 32, 0},
+// {0x64, (uint8_t []){0x38, 0x05, 0x01, 0xdb, 0x03, 0x03, 0x38, 0x04, 0x01, 0xdc, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x65, (uint8_t []){0x38, 0x03, 0x01, 0xdd, 0x03, 0x03, 0x38, 0x02, 0x01, 0xde, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x66, (uint8_t []){0x38, 0x01, 0x01, 0xdf, 0x03, 0x03, 0x38, 0x00, 0x01, 0xe0, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x67, (uint8_t []){0x30, 0x01, 0x01, 0xe1, 0x03, 0x03, 0x30, 0x02, 0x01, 0xe2, 0x03, 0x03, 0x7a, 0x7a, 0x7a, 0x7a}, 16, 0},
+// {0x68, (uint8_t []){0x00, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a, 0x08, 0x15, 0x08, 0x15, 0x7a, 0x7a}, 13, 0},
+// {0x60, (uint8_t []){0x38, 0x08, 0x7a, 0x7a, 0x38, 0x09, 0x7a, 0x7a}, 8, 0},
+// {0x63, (uint8_t []){0x31, 0xe4, 0x7a, 0x7a, 0x31, 0xe5, 0x7a, 0x7a}, 8, 0},
+// {0x69, (uint8_t []){0x04, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0},
+// {0x6b, (uint8_t []){0x07}, 1, 0},
+// {0x7a, (uint8_t []){0x08, 0x13}, 2, 0},
+// {0x7b, (uint8_t []){0x08, 0x13}, 2, 0},
+// {0xd1, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd2, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd3, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd4, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd5, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0xd6, (uint8_t []){0x00, 0x00, 0x00, 0x04, 0x00, 0x12, 0x00, 0x18, 0x00, 0x21, 0x00, 0x2a, 0x00, 0x35, 0x00, 0x47, 0x00,
+// 0x56, 0x00, 0x90, 0x00, 0xe5, 0x01, 0x68, 0x01, 0xd5, 0x01, 0xd7, 0x02, 0x36, 0x02, 0xa6, 0x02, 0xee,
+// 0x03, 0x48, 0x03, 0xa0, 0x03, 0xba, 0x03, 0xc5, 0x03, 0xd0, 0x03, 0xe0, 0x03, 0xea, 0x03, 0xfa, 0x03,
+// 0xff}, 52, 0},
+// {0x11, (uint8_t []){0x00}, 0, 120},
+// {0x29, (uint8_t []){0x00}, 0, 20},
+// };
+static const gc9503_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {0x11, (uint8_t[]){}, 0, 20},
+
+ {0xF0, (uint8_t[]){0x55, 0xAA, 0x52, 0x08, 0x00}, 5, 0},
+ {0xF6, (uint8_t[]){0x5A, 0x87}, 2, 0},
+ {0xC1, (uint8_t[]){0x3F}, 1, 0},
+ {0xCD, (uint8_t[]){0x25}, 1, 0},
+ {0xC9, (uint8_t[]){0x10}, 1, 0},
+ {0xF8, (uint8_t[]){0x8A}, 1, 0},
+ {0xAC, (uint8_t[]){0x45}, 1, 0},
+ {0xA7, (uint8_t[]){0x47}, 1, 0},
+ {0xA0, (uint8_t[]){0x88}, 1, 0},
+ {0x86, (uint8_t[]){0x99, 0xA3, 0xA3, 0x51}, 4, 0},
+ {0xFA, (uint8_t[]){0x08, 0x08, 0x00, 0x04}, 4, 0},
+ {0xA3, (uint8_t[]){0x6E}, 1, 0},
+ {0xFD, (uint8_t[]){0x28, 0x3C, 0x00}, 3, 0},
+ {0x9A, (uint8_t[]){0x4B}, 1, 0},
+ {0x9B, (uint8_t[]){0x4B}, 1, 0},
+ {0x82, (uint8_t[]){0x20, 0x20}, 2, 0},
+ {0xB1, (uint8_t[]){0x10}, 1, 0},
+ {0x7A, (uint8_t[]){0x0F, 0x13}, 2, 0},
+ {0x7B, (uint8_t[]){0x0F, 0x13}, 2, 0},
+ {0x6D, (uint8_t[]){0x1e, 0x1e, 0x04, 0x02, 0x0d, 0x1e, 0x12, 0x11, 0x14, 0x13, 0x05, 0x06, 0x1d, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1e, 0x1d, 0x06, 0x05, 0x0b, 0x0c, 0x09, 0x0a, 0x1e, 0x0d, 0x01, 0x03, 0x1e, 0x1e}, 32, 0},
+ {0x64, (uint8_t[]){0x38, 0x08, 0x03, 0xc0, 0x03, 0x03, 0x38, 0x06, 0x03, 0xc2, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0},
+ {0x65, (uint8_t[]){0x38, 0x04, 0x03, 0xc4, 0x03, 0x03, 0x38, 0x02, 0x03, 0xc6, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0},
+ {0x66, (uint8_t[]){0x83, 0xcf, 0x03, 0xc8, 0x03, 0x03, 0x83, 0xd3, 0x03, 0xd2, 0x03, 0x03, 0x20, 0x6d, 0x20, 0x6d}, 16, 0},
+ {0x60, (uint8_t[]){0x38, 0x0C, 0x20, 0x6D, 0x38, 0x0B, 0x20, 0x6D}, 8, 0},
+ {0x61, (uint8_t[]){0x38, 0x0A, 0x20, 0x6D, 0x38, 0x09, 0x20, 0x6D}, 8, 0},
+ {0x62, (uint8_t[]){0x38, 0x25, 0x20, 0x6D, 0x63, 0xC9, 0x20, 0x6D}, 8, 0},
+ {0x69, (uint8_t[]){0x14, 0x22, 0x14, 0x22, 0x14, 0x22, 0x08}, 7, 0},
+ {0x6B, (uint8_t[]){0x07}, 1, 0},
+ {0xD1, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0},
+ {0xD2, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0},
+ {0xD3, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0},
+ {0xD4, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0},
+ {0xD5, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0},
+ {0xD6, (uint8_t[]){0x00, 0x00, 0x00, 0x70, 0x00, 0x8f, 0x00, 0xab, 0x00, 0xbf, 0x00, 0xdf, 0x00, 0xfa, 0x01, 0x2a, 0x01, 0x52, 0x01, 0x90, 0x01, 0xc1, 0x02, 0x0e, 0x02, 0x4f, 0x02, 0x51, 0x02, 0x8d, 0x02, 0xd3, 0x02, 0xff, 0x03, 0x3c, 0x03, 0x64, 0x03, 0xa1, 0x03, 0xf1, 0x03, 0xff, 0x03, 0xfF, 0x03, 0xff, 0x03, 0xFf, 0x03, 0xFF}, 52, 0},
+ // {0x3A, (uint8_t[]){0x55}, 1, 0},
+
+ {0x11, NULL, 0, 120}, // Delay 120ms
+ {0x29, NULL, 0, 120}};
+
+// *INDENT-OFF*
+
+static esp_err_t panel_gc9503_send_init_cmds(gc9503_panel_t *gc9503)
+{
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){
+ gc9503->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){
+ gc9503->colmod_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+
+ // Vendor specific initialization, it can be different between manufacturers
+ // should consult the LCD supplier for initialization sequence code
+ const gc9503_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ if (gc9503->init_cmds)
+ {
+ init_cmds = gc9503->init_cmds;
+ init_cmds_size = gc9503->init_cmds_size;
+ }
+ else
+ {
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9503_lcd_init_cmd_t);
+ }
+
+ bool is_cmd_overwritten = false;
+ for (int i = 0; i < init_cmds_size; i++)
+ {
+ // Check if the command has been used or conflicts with the internal
+ switch (init_cmds[i].cmd)
+ {
+ case LCD_CMD_MADCTL:
+ is_cmd_overwritten = true;
+ gc9503->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ case LCD_CMD_COLMOD:
+ is_cmd_overwritten = true;
+ gc9503->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ default:
+ is_cmd_overwritten = false;
+ break;
+ }
+
+ if (is_cmd_overwritten)
+ {
+ ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
+ init_cmds[i].cmd);
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes),
+ TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ }
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_init(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+
+ if (!gc9503->flags.auto_del_panel_io)
+ {
+ ESP_RETURN_ON_ERROR(panel_gc9503_send_init_cmds(gc9503), TAG, "send init commands failed");
+ }
+ // Init RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->init(panel), TAG, "init RGB panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_del(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+
+ if (gc9503->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(gc9503->reset_gpio_num);
+ }
+ // Delete RGB panel
+ gc9503->del(panel);
+ free(gc9503);
+ ESP_LOGD(TAG, "del gc9503 panel @%p", gc9503);
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_reset(esp_lcd_panel_t *panel)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ // Perform hardware reset
+ if (gc9503->reset_gpio_num >= 0)
+ {
+ gpio_set_level(gc9503->reset_gpio_num, gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9503->reset_gpio_num, !gc9503->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ else if (io)
+ { // Perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(120));
+ }
+ // Reset RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->reset(panel), TAG, "reset RGB panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+
+ if (gc9503->flags.mirror_by_cmd)
+ {
+ ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
+ // Control mirror through LCD command
+ if (mirror_x)
+ {
+ gc9503->madctl_val |= GC9503_CMD_GS_BIT;
+ }
+ else
+ {
+ gc9503->madctl_val &= ~GC9503_CMD_GS_BIT;
+ }
+ if (mirror_y)
+ {
+ gc9503->madctl_val |= GC9503_CMD_SS_BIT;
+ }
+ else
+ {
+ gc9503->madctl_val &= ~GC9503_CMD_SS_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, GC9503_CMD_MADCTL, (uint8_t[]){
+ gc9503->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ;
+ }
+ else
+ {
+ // Control mirror through RGB panel
+ ESP_RETURN_ON_ERROR(gc9503->mirror(panel, mirror_x, mirror_y), TAG, "RGB panel mirror failed");
+ }
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9503_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ gc9503_panel_t *gc9503 = (gc9503_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = gc9503->io;
+ int command = 0;
+
+ if (gc9503->flags.display_on_off_use_cmd)
+ {
+ ESP_RETURN_ON_FALSE(io, ESP_FAIL, TAG, "Panel IO is deleted, cannot send command");
+ // Control display on/off through LCD command
+ if (on_off)
+ {
+ command = LCD_CMD_DISPON;
+ }
+ else
+ {
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ }
+ else
+ {
+ // Control display on/off through display control signal
+ ESP_RETURN_ON_ERROR(gc9503->disp_on_off(panel, on_off), TAG, "RGB panel disp_on_off failed");
+ }
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h
new file mode 100644
index 0000000..40a5ccc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/esp_lcd_gc9503.h
@@ -0,0 +1,145 @@
+/*
+ * SPDX-FileCopyrightText: 2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/**
+ * @file
+ * @brief ESP LCD: GC9503
+ */
+
+#pragma once
+
+#include <stdint.h>
+
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_rgb.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} gc9503_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct {
+ const esp_lcd_rgb_panel_config_t *rgb_config; /*!< RGB panel configuration */
+ const gc9503_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+ struct {
+ unsigned int mirror_by_cmd: 1; /*<! The `mirror()` function will be implemented by LCD command if set to 1.
+ * Otherwise, the function will be implemented by software.
+ */
+ unsigned int auto_del_panel_io: 1; /*<! Delete the panel IO instance automatically if set to 1. All `*_by_cmd` flags will be invalid.
+ * If the panel IO pins are sharing other pins of the RGB interface to save GPIOs,
+ * Please set it to 1 to release the panel IO and its pins (except CS signal).
+ */
+ } flags;
+} gc9503_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model GC9503
+ *
+ * @note When `auto_del_panel_io` is set to 1, this function will first initialize the GC9503 with vendor specific initialization and then calls `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will only initialize RGB.
+ * @note When `auto_del_panel_io` is set to 0, this function will only call `esp_lcd_new_rgb_panel()` to create an RGB LCD panel. And the `esp_lcd_panel_init()` function will initialize both the GC9503 and RGB.
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config General panel device configuration (`vendor_config` and `rgb_config` are necessary)
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ * - Otherwise on fail
+ */
+esp_err_t esp_lcd_new_panel_gc9503(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel);
+
+/**
+ * @brief 3-wire SPI panel IO configuration structure
+ *
+ * @param[in] line_cfg SPI line configuration
+ * @param[in] scl_active_edge SCL signal active edge, 0: rising edge, 1: falling edge
+ *
+ */
+#define GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_cfg, scl_active_edge) \
+ { \
+ .line_config = line_cfg, \
+ .expect_clk_speed = PANEL_IO_3WIRE_SPI_CLK_MAX, \
+ .spi_mode = scl_active_edge ? 1 : 0, \
+ .lcd_cmd_bytes = 1, \
+ .lcd_param_bytes = 1, \
+ .flags = { \
+ .use_dc_bit = 1, \
+ .dc_zero_on_data = 0, \
+ .lsb_first = 0, \
+ .cs_high_active = 0, \
+ .del_keep_cs_inactive = 1, \
+ }, \
+ }
+
+/**
+ * @brief RGB timing structure
+ *
+ * @note refresh_rate = (pclk_hz * data_width) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
+ * / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
+ * / bits_per_pixel
+ * .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 384, \
+ .v_res = 960, \
+ .hsync_pulse_width = 24, \
+ .hsync_back_porch = 20, \
+ .hsync_front_porch = 40, \
+ .vsync_pulse_width = 30, \
+ .vsync_back_porch = 18, \
+ .vsync_front_porch = 20, \
+ .flags.pclk_active_neg = 0, \
+
+ .hsync_pulse_width = 24, \
+ .hsync_back_porch = 20, \
+ .hsync_front_porch = 40, \
+ .vsync_pulse_width = 30, \
+ .vsync_back_porch = 18, \
+ .vsync_front_porch = 20, \
+
+ */
+#define GC9503_376_960_PANEL_60HZ_RGB_TIMING() \
+ { \
+ .pclk_hz = 16 * 1000 * 1000, \
+ .h_res = 376, \
+ .v_res = 960, \
+ .hsync_pulse_width = 8, \
+ .hsync_back_porch = 30, \
+ .hsync_front_porch = 30, \
+ .vsync_pulse_width = 8, \
+ .vsync_back_porch = 16, \
+ .vsync_front_porch = 16, \
+ .flags = { \
+ .hsync_idle_low = 0, \
+ .vsync_idle_low = 0, \
+ .de_idle_high = 0, \
+ .pclk_active_neg = 0, \
+ .pclk_idle_high = 0, \
+ }, \
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc
new file mode 100644
index 0000000..e116f84
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc
@@ -0,0 +1,163 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "pin_config.h"
+
+#include "config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include "esp_lcd_gc9503.h"
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_io_additions.h>
+
+#define TAG "Yuying_313lcd"
+
+class Yuying_313lcd : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+
+ void InitializeRGB_GC9503V_Display() {
+ ESP_LOGI(TAG, "Init GC9503V");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+
+ ESP_LOGI(TAG, "Install 3-wire SPI panel IO");
+ spi_line_config_t line_config = {
+ .cs_io_type = IO_TYPE_GPIO,
+ .cs_gpio_num = GC9503V_LCD_IO_SPI_CS_1,
+ .scl_io_type = IO_TYPE_GPIO,
+ .scl_gpio_num = GC9503V_LCD_IO_SPI_SCL_1,
+ .sda_io_type = IO_TYPE_GPIO,
+ .sda_gpio_num = GC9503V_LCD_IO_SPI_SDO_1,
+ .io_expander = NULL,
+ };
+ esp_lcd_panel_io_3wire_spi_config_t io_config = GC9503_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0);
+ (esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io));
+
+ ESP_LOGI(TAG, "Install RGB LCD panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_rgb_panel_config_t rgb_config = {
+ .clk_src = LCD_CLK_SRC_PLL160M,
+ .timings = GC9503_376_960_PANEL_60HZ_RGB_TIMING(),
+ .data_width = 16, // RGB565 in parallel mode, thus 16bit in width
+ .bits_per_pixel = 16,
+ .num_fbs = GC9503V_LCD_RGB_BUFFER_NUMS,
+ .bounce_buffer_size_px = GC9503V_LCD_H_RES * GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT,
+ .dma_burst_size = 64,
+ .hsync_gpio_num = GC9503V_PIN_NUM_HSYNC,
+ .vsync_gpio_num = GC9503V_PIN_NUM_VSYNC,
+ .de_gpio_num = GC9503V_PIN_NUM_DE,
+ .pclk_gpio_num = GC9503V_PIN_NUM_PCLK,
+ .disp_gpio_num = GC9503V_PIN_NUM_DISP_EN,
+ .data_gpio_nums = {
+ GC9503V_PIN_NUM_DATA0,
+ GC9503V_PIN_NUM_DATA1,
+ GC9503V_PIN_NUM_DATA2,
+ GC9503V_PIN_NUM_DATA3,
+ GC9503V_PIN_NUM_DATA4,
+ GC9503V_PIN_NUM_DATA5,
+ GC9503V_PIN_NUM_DATA6,
+ GC9503V_PIN_NUM_DATA7,
+ GC9503V_PIN_NUM_DATA8,
+ GC9503V_PIN_NUM_DATA9,
+ GC9503V_PIN_NUM_DATA10,
+ GC9503V_PIN_NUM_DATA11,
+ GC9503V_PIN_NUM_DATA12,
+ GC9503V_PIN_NUM_DATA13,
+ GC9503V_PIN_NUM_DATA14,
+ GC9503V_PIN_NUM_DATA15,
+ },
+ .flags= {
+ .fb_in_psram = true, // allocate frame buffer in PSRAM
+ }
+ };
+
+ ESP_LOGI(TAG, "Initialize RGB LCD panel");
+
+ gc9503_vendor_config_t vendor_config = {
+ .rgb_config = &rgb_config,
+ .flags = {
+ .mirror_by_cmd = 0,
+ .auto_del_panel_io = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = -1,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ (esp_lcd_new_panel_gc9503(panel_io, &panel_config, &panel_handle));
+ (esp_lcd_panel_reset(panel_handle));
+ (esp_lcd_panel_init(panel_handle));
+
+ display_ = new RgbLcdDisplay(panel_io, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+public:
+ Yuying_313lcd() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializeRGB_GC9503V_Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ 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(Yuying_313lcd);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/pin_config.h b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/pin_config.h
new file mode 100644
index 0000000..0bcd059
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/kevin-yuying-313lcd/pin_config.h
@@ -0,0 +1,47 @@
+
+#pragma once
+#define GC9503V_LCD_H_RES 376
+#define GC9503V_LCD_V_RES 960
+
+
+#define GC9503V_LCD_LVGL_DIRECT_MODE (1)
+#define GC9503V_LCD_LVGL_AVOID_TEAR (1)
+#define GC9503V_LCD_RGB_BOUNCE_BUFFER_MODE (1)
+#define GC9503V_LCD_DRAW_BUFF_DOUBLE (0)
+#define GC9503V_LCD_DRAW_BUFF_HEIGHT (100)
+#define GC9503V_LCD_RGB_BUFFER_NUMS (2)
+#define GC9503V_LCD_RGB_BOUNCE_BUFFER_HEIGHT (10)
+
+#define GC9503V_LCD_PIXEL_CLOCK_HZ (16 * 1000 * 1000)
+#define GC9503V_LCD_BK_LIGHT_ON_LEVEL 1
+#define GC9503V_LCD_BK_LIGHT_OFF_LEVEL !GC9503V_LCD_BK_LIGHT_ON_LEVEL
+#define GC9503V_PIN_NUM_BK_LIGHT GPIO_NUM_4
+#define GC9503V_PIN_NUM_HSYNC 6
+#define GC9503V_PIN_NUM_VSYNC 5
+#define GC9503V_PIN_NUM_DE 15
+#define GC9503V_PIN_NUM_PCLK 7
+
+#define GC9503V_PIN_NUM_DATA0 47 // B0
+#define GC9503V_PIN_NUM_DATA1 21 // B1
+#define GC9503V_PIN_NUM_DATA2 14 // B2
+#define GC9503V_PIN_NUM_DATA3 13 // B3
+#define GC9503V_PIN_NUM_DATA4 12 // B4
+
+#define GC9503V_PIN_NUM_DATA5 11 // G0
+#define GC9503V_PIN_NUM_DATA6 10 // G1
+#define GC9503V_PIN_NUM_DATA7 9 // G2
+#define GC9503V_PIN_NUM_DATA8 46 // G3
+#define GC9503V_PIN_NUM_DATA9 3 // G4
+#define GC9503V_PIN_NUM_DATA10 20 // G5
+
+#define GC9503V_PIN_NUM_DATA11 19 // R0
+#define GC9503V_PIN_NUM_DATA12 8 // R1
+#define GC9503V_PIN_NUM_DATA13 18 // R2
+#define GC9503V_PIN_NUM_DATA14 17 // R3
+#define GC9503V_PIN_NUM_DATA15 16 // R4
+
+#define GC9503V_PIN_NUM_DISP_EN -1
+
+#define GC9503V_LCD_IO_SPI_CS_1 (GPIO_NUM_48)
+#define GC9503V_LCD_IO_SPI_SCL_1 (GPIO_NUM_17)
+#define GC9503V_LCD_IO_SPI_SDO_1 (GPIO_NUM_16)
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/README.md b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/README.md
new file mode 100644
index 0000000..3be2b94
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/README.md
@@ -0,0 +1,62 @@
+# labplus 涔愬姩鎺屾帶V2
+
+## 鏉胯浇璧勬簮
+ 涓绘帶锛欵SP32-S3 澶栨寕8MB psram 16MB flash
+ 浼犳劅鍣�:
+ 鎸夐挳(A B鎸夐敭锛� IO0 IO46
+ 鍏夌収浼犳劅鍣� IIC
+ 6杞� IIC
+ 纾佸姏璁� IIC
+ 澹伴煶瑙﹀彂 IO6
+ 瑙︽懜鎸夐敭 IIC P Y T H O N
+ 鎽勫儚澶� IIC
+ 鎵ц鍣�:
+ 铚傞福鍣� IO21
+ RGB鐏� IO16
+ 褰曢煶鎾斁 es8388 IIC
+ TFT LCD jd9853 SPI
+ 鐢垫満椹卞姩 IIC
+
+
+## 缂栬瘧閰嶇疆
+
+### 閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛孶SB JTAG涓嬭浇
+
+```bash
+idf.py set-target esp32s3
+```
+
+### menuconfig閰嶇疆
+
+```bash
+idf.py menuconfig
+```
+
+***閫夋嫨鏉垮瓙锛�***
+
+```
+Xiaozhi Assistant -> Board Type -> labplus Ledong_v2 board
+```
+
+***淇敼 psram 閰嶇疆锛�***
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> quad Mode PSRAM
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
+
+**鍥轰欢鎵撳寘锛�**
+
+```bash
+esptool.py -p /dev/ttyACM0 -b 1500000 --before default_reset --after hard_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x0 bootloader/bootloader.bin 0x100000 xiaozhi.bin 0x8000 partition_table/partition-table.bin 0xd000 ota_data_initial.bin 0x10000 srmodels/srmodels.bin
+```
+
+## 浣跨敤
+
+### 鎸夐敭閰嶇疆
+* A锛氱煭鎸�-鎵撴柇/鍞ら啋
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.h b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.h
new file mode 100644
index 0000000..79f9d4f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.h
@@ -0,0 +1,44 @@
+
+#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_39
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_44
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_43
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define BUILTIN_LED_GPIO GPIO_NUM_1
+
+#define LCD_SCLK_PIN GPIO_NUM_36
+#define LCD_MOSI_PIN GPIO_NUM_37
+#define LCD_MISO_PIN GPIO_NUM_NC
+#define LCD_DC_PIN GPIO_NUM_35
+#define LCD_CS_PIN GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 172
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 34
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_34
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.json b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.json
new file mode 100644
index 0000000..8df7030
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "labplus-ledong-v2",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc
new file mode 100644
index 0000000..1ddf573
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-ledong-v2/labplus_ledong_v2.cc
@@ -0,0 +1,163 @@
+#include "wifi_board.h"
+#include "codecs/es8388_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "labplus_ledong_v2"
+
+#define BOARD_STM8_ADDR 17
+#define BOARD_STM8_CMD 4
+
+class labplus_ledong_v2 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = LCD_SCLK_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;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeJd9853Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ ESP_LOGD(TAG, "Install panel IO");
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 20 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖JD9853,浣跨敤ST7789椹卞姩,鏃跺簭鍜屽浣嶆湁璋冩暣銆�
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
+ esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
+
+ #if CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2
+ ESP_LOGI(TAG, "Reset LCD.");
+ i2c_device_config_t dev_cfg = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = BOARD_STM8_ADDR,
+ .scl_speed_hz = 400000,
+ };
+
+ i2c_master_dev_handle_t dev_handle;
+ i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &dev_handle);
+
+ uint8_t reg = BOARD_STM8_CMD;
+ i2c_master_transmit(dev_handle, ®, 1, -1);
+
+ i2c_master_bus_rm_device(dev_handle);
+ #endif
+
+ esp_lcd_panel_reset(panel);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ 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);
+ }
+
+public:
+ labplus_ledong_v2() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeJd9853Display();
+ InitializeButtons();
+ GetBacklight()->SetBrightness(100);
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8388AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8388_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(labplus_ledong_v2);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/README.md b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/README.md
new file mode 100644
index 0000000..c5a4845
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/README.md
@@ -0,0 +1,61 @@
+# labplus 鎺屾帶鏉縑3
+
+## 鏉胯浇璧勬簮
+ 涓绘帶锛欵SP32-S3 澶栨寕8MB psram 16MB flash
+ 浼犳劅鍣�:
+ 鎸夐挳(A B鎸夐敭锛� IO0 IO46
+ 鍏夌収浼犳劅鍣� IIC
+ 6杞� IIC
+ 纾佸姏璁� IIC
+ 澹伴煶瑙﹀彂 IO6
+ 瑙︽懜鎸夐敭 P Y T H O N
+ 鎽勫儚澶� IIC
+ 鎵ц鍣�:
+ 铚傞福鍣� IO21
+ RGB鐏� IO16
+ 褰曢煶鎾斁 es8388 IIC
+ TFT LCD jd9853 SPI
+
+
+## 缂栬瘧閰嶇疆
+
+### 閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛孶SB JTAG涓嬭浇
+
+```bash
+idf.py set-target esp32s3
+```
+
+### menuconfig閰嶇疆
+
+```bash
+idf.py menuconfig
+```
+
+***閫夋嫨鏉垮瓙锛�***
+
+```
+Xiaozhi Assistant -> Board Type -> labplus mpython_v3 board
+```
+
+***淇敼 psram 閰嶇疆锛�***
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> quad Mode PSRAM
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
+
+**鍥轰欢鎵撳寘锛�**
+
+```bash
+esptool.py -p /dev/ttyACM0 -b 1500000 --before default_reset --after hard_reset --chip esp32s3 write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x0 bootloader/bootloader.bin 0x100000 xiaozhi.bin 0x8000 partition_table/partition-table.bin 0xd000 ota_data_initial.bin 0x10000 srmodels/srmodels.bin
+```
+
+## 浣跨敤
+
+### 鎸夐敭閰嶇疆
+* A锛氱煭鎸�-鎵撴柇/鍞ら啋
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.h b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.h
new file mode 100644
index 0000000..e6a8484
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.h
@@ -0,0 +1,44 @@
+
+#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_39
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_42
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_38
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_44
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_43
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define BUILTIN_LED_GPIO GPIO_NUM_1
+
+#define LCD_SCLK_PIN GPIO_NUM_36
+#define LCD_MOSI_PIN GPIO_NUM_37
+#define LCD_MISO_PIN GPIO_NUM_NC
+#define LCD_DC_PIN GPIO_NUM_35
+#define LCD_CS_PIN GPIO_NUM_34
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 172
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 34
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_33
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.json b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.json
new file mode 100644
index 0000000..b5bd0ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "labplus-mpython-v3",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/mpython_pro.cc b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/mpython_pro.cc
new file mode 100644
index 0000000..69c2eee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/labplus-mpython-v3/mpython_pro.cc
@@ -0,0 +1,143 @@
+#include "wifi_board.h"
+#include "codecs/es8388_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "mpython_v3"
+
+class mpython_v3 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ // Initialize spi peripheral
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = LCD_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = LCD_SCLK_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;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ ESP_LOGD(TAG, "Install panel IO");
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = LCD_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 20 * 1000 * 1000;
+ io_config.trans_queue_depth = 7;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io);
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖JD9853,浣跨敤ST7789椹卞姩,鏃跺簭鏈夎皟鏁淬��
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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;
+ panel_config.data_endian = LCD_RGB_DATA_ENDIAN_BIG,
+ esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel);
+
+ esp_lcd_panel_reset(panel);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ 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);
+ }
+
+public:
+ mpython_v3() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ GetBacklight()->SetBrightness(100);
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8388AudioCodec audio_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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8388_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(mpython_v3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/README.md b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/README.md
new file mode 100644
index 0000000..b270660
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/README.md
@@ -0,0 +1,14 @@
+## EDA-Robot-Pro 鏈哄櫒鐙�
+
+### 椤圭洰鏂囨。
+- [EDA-Robot-Pro 椤圭洰鏂囨。](https://wiki.lceda.cn/zh-hans/course-projects/smart-internet/eda-robot/eda-robot-introduce.html)
+
+### 缂栬瘧閰嶇疆
+Flash澶у皬鎸塃SP32S3妯$粍澶у皬璋冩暣
+渚嬪妯$粍Flash瀹归噺涓�8MB鏃讹細
+
+```
+Partition Table --->
+ Partition Table (Custom partition table CSV) --->
+ (partitions/v2/8m.csv) Custom partition CSV file
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.h
new file mode 100644
index 0000000..484c929
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.h
@@ -0,0 +1,40 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+// 闊抽閰嶇疆
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// 浣跨敤 Simplex I2S 妯″紡
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_17
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_16
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_18
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_40
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_39
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38
+
+// 鎸夐挳閰嶇疆
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_14
+// OLED鏄剧ず灞忛厤缃� (SSD1306 128x64)
+#define DISPLAY_SDA_PIN GPIO_NUM_12
+#define DISPLAY_SCL_PIN GPIO_NUM_13
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+// 鏈哄櫒鐙楄埖鏈洪厤缃� - 鍥涙潯鑵�
+#define LEFT_FRONT_LEG_PIN GPIO_NUM_47 // 宸﹀墠鑵�
+#define LEFT_REAR_LEG_PIN GPIO_NUM_21 // 宸﹀悗鑵�
+#define RIGHT_FRONT_LEG_PIN GPIO_NUM_9 // 鍙冲墠鑵�
+#define RIGHT_REAR_LEG_PIN GPIO_NUM_10 // 鍙冲悗鑵�
+
+// EDA鏈哄櫒鐙楃増鏈�
+#define EDA_ROBOT_PRO_VERSION "1.0.0"
+
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.json b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.json
new file mode 100644
index 0000000..98cd49b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "lceda-course-examples",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "eda-robot-pro",
+ "sdkconfig_append": [
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\"",
+ "CONFIG_OLED_SSD1306_128X64=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_controller.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_controller.cc
new file mode 100644
index 0000000..ed65ebe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_controller.cc
@@ -0,0 +1,399 @@
+/*
+ EDA鏈哄櫒鐙楁帶鍒跺櫒 - MCP鍗忚鐗堟湰
+*/
+
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/queue.h>
+#include <freertos/task.h>
+
+#include "application.h"
+#include "board.h"
+#include "config.h"
+#include "eda_dog_movements.h"
+#include "mcp_server.h"
+#include "sdkconfig.h"
+#include "settings.h"
+
+#define TAG "EDARobotDogController"
+
+class EDARobotDogController {
+private:
+ EDARobotDog dog_;
+ TaskHandle_t action_task_handle_ = nullptr;
+ QueueHandle_t action_queue_;
+ bool is_action_in_progress_ = false;
+
+ struct DogActionParams {
+ int action_type;
+ int steps;
+ int speed;
+ int direction;
+ int height;
+ };
+
+ enum ActionType {
+ ACTION_WALK = 1,
+ ACTION_TURN = 2,
+ ACTION_SIT = 3,
+ ACTION_STAND = 4,
+ ACTION_STRETCH = 5,
+ ACTION_SHAKE = 6,
+ ACTION_LIFT_LEFT_FRONT = 7,
+ ACTION_LIFT_LEFT_REAR = 8,
+ ACTION_LIFT_RIGHT_FRONT = 9,
+ ACTION_LIFT_RIGHT_REAR = 10,
+ ACTION_HOME = 11
+ };
+
+ static void ActionTask(void *arg) {
+ EDARobotDogController *controller = static_cast<EDARobotDogController *>(arg);
+ DogActionParams params;
+ controller->dog_.AttachServos();
+
+ while (true) {
+ if (xQueueReceive(controller->action_queue_, ¶ms,
+ pdMS_TO_TICKS(1000)) == pdTRUE) {
+ ESP_LOGI(TAG, "鎵ц鍔ㄤ綔: %d", params.action_type);
+ controller->is_action_in_progress_ = true;
+
+ switch (params.action_type) {
+ case ACTION_WALK:
+ controller->dog_.Walk(params.steps, params.speed, params.direction);
+ break;
+ case ACTION_TURN:
+ controller->dog_.Turn(params.steps, params.speed, params.direction);
+ break;
+ case ACTION_SIT:
+ controller->dog_.Sit(params.speed);
+ break;
+ case ACTION_STAND:
+ controller->dog_.Stand(params.speed);
+ break;
+ case ACTION_STRETCH:
+ controller->dog_.Stretch(params.speed);
+ break;
+ case ACTION_SHAKE:
+ controller->dog_.Shake(params.speed);
+ break;
+ case ACTION_LIFT_LEFT_FRONT:
+ controller->dog_.LiftLeftFrontLeg(params.speed, params.height);
+ break;
+ case ACTION_LIFT_LEFT_REAR:
+ controller->dog_.LiftLeftRearLeg(params.speed, params.height);
+ break;
+ case ACTION_LIFT_RIGHT_FRONT:
+ controller->dog_.LiftRightFrontLeg(params.speed, params.height);
+ break;
+ case ACTION_LIFT_RIGHT_REAR:
+ controller->dog_.LiftRightRearLeg(params.speed, params.height);
+ break;
+ case ACTION_HOME:
+ controller->dog_.Home();
+ break;
+ }
+
+ if (params.action_type != ACTION_HOME &&
+ params.action_type != ACTION_SIT) {
+ controller->dog_.Home();
+ }
+ controller->is_action_in_progress_ = false;
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ }
+ }
+
+ void StartActionTaskIfNeeded() {
+ if (action_task_handle_ == nullptr) {
+ xTaskCreate(ActionTask, "dog_action", 1024 * 3, this,
+ configMAX_PRIORITIES - 1, &action_task_handle_);
+ }
+ }
+
+ void QueueAction(int action_type, int steps, int speed, int direction,
+ int height) {
+ ESP_LOGI(TAG, "鍔ㄤ綔鎺у埗: 绫诲瀷=%d, 姝ユ暟=%d, 閫熷害=%d, 鏂瑰悜=%d, 楂樺害=%d",
+ action_type, steps, speed, direction, height);
+
+ DogActionParams params = {action_type, steps, speed, direction, height};
+ xQueueSend(action_queue_, ¶ms, portMAX_DELAY);
+ StartActionTaskIfNeeded();
+ }
+
+ void LoadTrimsFromNVS() {
+ Settings settings("dog_trims", false);
+
+ int left_front_leg = settings.GetInt("left_front_leg", 0);
+ int left_rear_leg = settings.GetInt("left_rear_leg", 0);
+ int right_front_leg = settings.GetInt("right_front_leg", 0);
+ int right_rear_leg = settings.GetInt("right_rear_leg", 0);
+
+ ESP_LOGI(TAG,
+ "浠嶯VS鍔犺浇寰皟璁剧疆: 宸﹀墠鑵�=%d, 宸﹀悗鑵�=%d, 鍙冲墠鑵�=%d, 鍙冲悗鑵�=%d",
+ left_front_leg, left_rear_leg, right_front_leg, right_rear_leg);
+
+ dog_.SetTrims(left_front_leg, left_rear_leg, right_front_leg,
+ right_rear_leg);
+ }
+
+public:
+ EDARobotDogController() {
+ dog_.Init(LEFT_FRONT_LEG_PIN, LEFT_REAR_LEG_PIN, RIGHT_FRONT_LEG_PIN,
+ RIGHT_REAR_LEG_PIN);
+
+ ESP_LOGI(TAG, "EDA鏈哄櫒鐙楀垵濮嬪寲瀹屾垚");
+
+ LoadTrimsFromNVS();
+
+ action_queue_ = xQueueCreate(10, sizeof(DogActionParams));
+
+ QueueAction(ACTION_HOME, 1, 1000, 0, 0);
+
+ RegisterMcpTools();
+ }
+
+ void RegisterMcpTools() {
+ auto &mcp_server = McpServer::GetInstance();
+
+ ESP_LOGI(TAG, "寮�濮嬫敞鍐孧CP宸ュ叿...");
+
+ // 鍩虹绉诲姩鍔ㄤ綔
+ mcp_server.AddTool(
+ "self.dog.walk",
+ "琛岃蛋銆俿teps: 琛岃蛋姝ユ暟(1-100); speed: "
+ "琛岃蛋閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�); "
+ "direction: 琛岃蛋鏂瑰悜(-1=鍚庨��, 1=鍓嶈繘)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 4, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 2000),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_WALK, steps, speed, direction, 0);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.dog.turn",
+ "杞韩銆俿teps: 杞韩姝ユ暟(1-100); speed: "
+ "杞韩閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�); "
+ "direction: 杞韩鏂瑰悜(1=宸﹁浆, -1=鍙宠浆)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 4, 1, 100),
+ Property("speed", kPropertyTypeInteger, 2000, 500, 2000),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_TURN, steps, speed, direction, 0);
+ return true;
+ });
+
+ // 濮挎�佸姩浣�
+ mcp_server.AddTool("self.dog.sit",
+ "鍧愪笅銆俿peed: 鍧愪笅閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�)",
+ PropertyList({Property("speed", kPropertyTypeInteger,
+ 1500, 500, 2000)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ QueueAction(ACTION_SIT, 1, speed, 0, 0);
+ return true;
+ });
+
+ mcp_server.AddTool("self.dog.stand",
+ "绔欑珛銆俿peed: 绔欑珛閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�)",
+ PropertyList({Property("speed", kPropertyTypeInteger,
+ 1500, 500, 2000)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ QueueAction(ACTION_STAND, 1, speed, 0, 0);
+ return true;
+ });
+
+ mcp_server.AddTool("self.dog.stretch",
+ "浼稿睍銆俿peed: 浼稿睍閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�)",
+ PropertyList({Property("speed", kPropertyTypeInteger,
+ 2000, 500, 2000)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ QueueAction(ACTION_STRETCH, 1, speed, 0, 0);
+ return true;
+ });
+
+ mcp_server.AddTool("self.dog.shake",
+ "鎽囨憜銆俿peed: 鎽囨憜閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�)",
+ PropertyList({Property("speed", kPropertyTypeInteger,
+ 1000, 500, 2000)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ QueueAction(ACTION_SHAKE, 1, speed, 0, 0);
+ return true;
+ });
+
+ // 鍗曡吙鎶捣鍔ㄤ綔
+ mcp_server.AddTool(
+ "self.dog.lift_left_front_leg",
+ "鎶捣宸﹀墠鑵裤�俿peed: 鍔ㄤ綔閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�); height: "
+ "鎶捣楂樺害(10-90搴�)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 2000),
+ Property("height", kPropertyTypeInteger, 45, 10, 90)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int height = properties["height"].value<int>();
+ QueueAction(ACTION_LIFT_LEFT_FRONT, 1, speed, 0, height);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.dog.lift_left_rear_leg",
+ "鎶捣宸﹀悗鑵裤�俿peed: 鍔ㄤ綔閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�); height: "
+ "鎶捣楂樺害(10-90搴�)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 2000),
+ Property("height", kPropertyTypeInteger, 45, 10, 90)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int height = properties["height"].value<int>();
+ QueueAction(ACTION_LIFT_LEFT_REAR, 1, speed, 0, height);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.dog.lift_right_front_leg",
+ "鎶捣鍙冲墠鑵裤�俿peed: 鍔ㄤ綔閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�); height: "
+ "鎶捣楂樺害(10-90搴�)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 2000),
+ Property("height", kPropertyTypeInteger, 45, 10, 90)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int height = properties["height"].value<int>();
+ QueueAction(ACTION_LIFT_RIGHT_FRONT, 1, speed, 0, height);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.dog.lift_right_rear_leg",
+ "鎶捣鍙冲悗鑵裤�俿peed: 鍔ㄤ綔閫熷害(500-2000锛屾暟鍊艰秺灏忚秺蹇�); height: "
+ "鎶捣楂樺害(10-90搴�)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 2000),
+ Property("height", kPropertyTypeInteger, 45, 10, 90)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int height = properties["height"].value<int>();
+ QueueAction(ACTION_LIFT_RIGHT_REAR, 1, speed, 0, height);
+ return true;
+ });
+
+ // 绯荤粺宸ュ叿
+ mcp_server.AddTool("self.dog.stop", "绔嬪嵆鍋滄", PropertyList(),
+ [this](const PropertyList &properties) -> ReturnValue {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ is_action_in_progress_ = false;
+ xQueueReset(action_queue_);
+
+ QueueAction(ACTION_HOME, 1, 1000, 0, 0);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.dog.set_trim",
+ "鏍″噯鍗曚釜鑸垫満浣嶇疆銆傝缃寚瀹氳埖鏈虹殑寰皟鍙傛暟浠ヨ皟鏁存満鍣ㄧ嫍鐨勫垵濮嬬珯绔嬪Э鎬侊紝璁�"
+ "缃皢姘镐箙淇濆瓨銆�"
+ "servo_type: "
+ "鑸垫満绫诲瀷(left_front_leg/left_rear_leg/right_front_leg/"
+ "right_rear_leg); "
+ "trim_value: 寰皟鍊�(-50鍒�50搴�)",
+ PropertyList(
+ {Property("servo_type", kPropertyTypeString, "left_front_leg"),
+ Property("trim_value", kPropertyTypeInteger, 0, -50, 50)}),
+ [this](const PropertyList &properties) -> ReturnValue {
+ std::string servo_type =
+ properties["servo_type"].value<std::string>();
+ int trim_value = properties["trim_value"].value<int>();
+
+ ESP_LOGI(TAG, "璁剧疆鑸垫満寰皟: %s = %d搴�", servo_type.c_str(),
+ trim_value);
+
+ // 鑾峰彇褰撳墠鎵�鏈夊井璋冨��
+ Settings settings("dog_trims", true);
+ int left_front_leg = settings.GetInt("left_front_leg", 0);
+ int left_rear_leg = settings.GetInt("left_rear_leg", 0);
+ int right_front_leg = settings.GetInt("right_front_leg", 0);
+ int right_rear_leg = settings.GetInt("right_rear_leg", 0);
+
+ // 鏇存柊鎸囧畾鑸垫満鐨勫井璋冨��
+ if (servo_type == "left_front_leg") {
+ left_front_leg = trim_value;
+ settings.SetInt("left_front_leg", left_front_leg);
+ } else if (servo_type == "left_rear_leg") {
+ left_rear_leg = trim_value;
+ settings.SetInt("left_rear_leg", left_rear_leg);
+ } else if (servo_type == "right_front_leg") {
+ right_front_leg = trim_value;
+ settings.SetInt("right_front_leg", right_front_leg);
+ } else if (servo_type == "right_rear_leg") {
+ right_rear_leg = trim_value;
+ settings.SetInt("right_rear_leg", right_rear_leg);
+ } else {
+ return "閿欒锛氭棤鏁堢殑鑸垫満绫诲瀷锛岃浣跨敤: left_front_leg, "
+ "left_rear_leg, right_front_leg, right_rear_leg";
+ }
+
+ dog_.SetTrims(left_front_leg, left_rear_leg, right_front_leg,
+ right_rear_leg);
+
+ QueueAction(ACTION_HOME, 1, 500, 0, 0);
+
+ return "鑸垫満 " + servo_type + " 寰皟璁剧疆涓� " +
+ std::to_string(trim_value) + " 搴︼紝宸叉案涔呬繚瀛�";
+ });
+
+ mcp_server.AddTool(
+ "self.dog.get_trims", "鑾峰彇褰撳墠鐨勮埖鏈哄井璋冭缃�", PropertyList(),
+ [this](const PropertyList &properties) -> ReturnValue {
+ Settings settings("dog_trims", false);
+
+ int left_front_leg = settings.GetInt("left_front_leg", 0);
+ int left_rear_leg = settings.GetInt("left_rear_leg", 0);
+ int right_front_leg = settings.GetInt("right_front_leg", 0);
+ int right_rear_leg = settings.GetInt("right_rear_leg", 0);
+
+ std::string result =
+ "{\"left_front_leg\":" + std::to_string(left_front_leg) +
+ ",\"left_rear_leg\":" + std::to_string(left_rear_leg) +
+ ",\"right_front_leg\":" + std::to_string(right_front_leg) +
+ ",\"right_rear_leg\":" + std::to_string(right_rear_leg) + "}";
+
+ ESP_LOGI(TAG, "鑾峰彇寰皟璁剧疆: %s", result.c_str());
+ return result;
+ });
+
+ mcp_server.AddTool("self.dog.get_status",
+ "鑾峰彇鏈哄櫒鐙楃姸鎬侊紝杩斿洖 moving 鎴� idle", PropertyList(),
+ [this](const PropertyList &properties) -> ReturnValue {
+ return is_action_in_progress_ ? "moving" : "idle";
+ });
+
+ ESP_LOGI(TAG, "MCP宸ュ叿娉ㄥ唽瀹屾垚");
+ }
+
+ ~EDARobotDogController() {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ vQueueDelete(action_queue_);
+ }
+};
+
+static EDARobotDogController *g_dog_controller = nullptr;
+
+void InitializeEDARobotDogController() {
+ if (g_dog_controller == nullptr) {
+ g_dog_controller = new EDARobotDogController();
+ ESP_LOGI(TAG, "EDA鏈哄櫒鐙楁帶鍒跺櫒宸插垵濮嬪寲骞舵敞鍐孧CP宸ュ叿");
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.cc
new file mode 100644
index 0000000..7bc249c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.cc
@@ -0,0 +1,577 @@
+#include "eda_dog_movements.h"
+
+#include <algorithm>
+#include <cmath>
+
+#include "oscillator.h"
+
+static const char *TAG = "EDARobotDogMovements";
+
+#define LEG_HOME_POSITION 90
+
+EDARobotDog::EDARobotDog() {
+ is_dog_resting_ = false;
+ // 鍒濆鍖栨墍鏈夎埖鏈虹鑴氫负-1锛堟湭杩炴帴锛�
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ servo_pins_[i] = -1;
+ servo_trim_[i] = 0;
+ }
+}
+
+EDARobotDog::~EDARobotDog() { DetachServos(); }
+
+unsigned long IRAM_ATTR millis() {
+ return (unsigned long)(esp_timer_get_time() / 1000ULL);
+}
+
+void EDARobotDog::Init(int left_front_leg, int left_rear_leg, int right_front_leg,
+ int right_rear_leg) {
+ servo_pins_[LEFT_FRONT_LEG] = left_front_leg;
+ servo_pins_[LEFT_REAR_LEG] = left_rear_leg;
+ servo_pins_[RIGHT_FRONT_LEG] = right_front_leg;
+ servo_pins_[RIGHT_REAR_LEG] = right_rear_leg;
+
+ AttachServos();
+ is_dog_resting_ = false;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- ATTACH & DETACH FUNCTIONS ----------------------------------//
+///////////////////////////////////////////////////////////////////
+void EDARobotDog::AttachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Attach(servo_pins_[i]);
+ }
+ }
+}
+
+void EDARobotDog::DetachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Detach();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- OSCILLATORS TRIMS ------------------------------------------//
+///////////////////////////////////////////////////////////////////
+void EDARobotDog::SetTrims(int left_front_leg, int left_rear_leg,
+ int right_front_leg, int right_rear_leg) {
+ servo_trim_[LEFT_FRONT_LEG] = left_front_leg;
+ servo_trim_[LEFT_REAR_LEG] = left_rear_leg;
+ servo_trim_[RIGHT_FRONT_LEG] = right_front_leg;
+ servo_trim_[RIGHT_REAR_LEG] = right_rear_leg;
+
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetTrim(servo_trim_[i]);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- BASIC MOTION FUNCTIONS -------------------------------------//
+///////////////////////////////////////////////////////////////////
+void EDARobotDog::MoveServos(int time, int servo_target[]) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ final_time_ = millis() + time;
+ if (time > 10) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ increment_[i] =
+ (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0);
+ }
+ }
+
+ for (int iteration = 1; millis() < final_time_; iteration++) {
+ partial_time_ = millis() + 10;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ } else {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(time));
+ }
+
+ // final adjustment to the target.
+ bool f = true;
+ int adjustment_count = 0;
+ while (f && adjustment_count < 10) {
+ f = false;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) {
+ f = true;
+ break;
+ }
+ }
+ if (f) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ adjustment_count++;
+ }
+ };
+}
+
+void EDARobotDog::MoveSingle(int position, int servo_number) {
+ if (position > 180)
+ position = 90;
+ if (position < 0)
+ position = 90;
+
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ if (servo_number >= 0 && servo_number < SERVO_COUNT &&
+ servo_pins_[servo_number] != -1) {
+ servo_[servo_number].SetPosition(position);
+ }
+}
+
+void EDARobotDog::OscillateServos(int amplitude[SERVO_COUNT],
+ int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle = 1) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetO(offset[i]);
+ servo_[i].SetA(amplitude[i]);
+ servo_[i].SetT(period);
+ servo_[i].SetPh(phase_diff[i]);
+ }
+ }
+
+ double ref = millis();
+ double end_time = period * cycle + ref;
+
+ while (millis() < end_time) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Refresh();
+ }
+ }
+ vTaskDelay(5);
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+void EDARobotDog::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT],
+ int period, double phase_diff[SERVO_COUNT],
+ float steps = 1.0) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ int cycles = (int)steps;
+
+ //-- Execute complete cycles
+ if (cycles >= 1)
+ for (int i = 0; i < cycles; i++)
+ OscillateServos(amplitude, offset, period, phase_diff);
+
+ //-- Execute the final not complete cycle
+ OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles);
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+///////////////////////////////////////////////////////////////////
+//-- HOME = Dog at rest position --------------------------------//
+///////////////////////////////////////////////////////////////////
+void EDARobotDog::Home() {
+ if (is_dog_resting_ == false) { // Go to rest position only if necessary
+ int homes[SERVO_COUNT] = {LEG_HOME_POSITION, LEG_HOME_POSITION,
+ LEG_HOME_POSITION, LEG_HOME_POSITION};
+ MoveServos(500, homes);
+ is_dog_resting_ = true;
+ }
+ vTaskDelay(pdMS_TO_TICKS(200));
+}
+
+bool EDARobotDog::GetRestState() { return is_dog_resting_; }
+
+void EDARobotDog::SetRestState(bool state) { is_dog_resting_ = state; }
+
+///////////////////////////////////////////////////////////////////
+//-- BASIC LEG MOVEMENTS ----------------------------------------//
+///////////////////////////////////////////////////////////////////
+
+void EDARobotDog::LiftLeftFrontLeg(int period, int height) {
+
+ // 鑾峰彇褰撳墠浣嶇疆
+ int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+ // 閲嶅3娆℃憞鎽嗗姩浣�
+ for (int num = 0; num < 3; num++) {
+ // servo1.write(180); delay(100);
+ current_pos[LEFT_FRONT_LEG] = 0; // servo1
+ MoveServos(100, current_pos);
+
+ // servo1.write(150); delay(100);
+ current_pos[LEFT_FRONT_LEG] = 30; // servo1
+ MoveServos(100, current_pos);
+ }
+
+ // servo1.write(90);
+ current_pos[LEFT_FRONT_LEG] = 90; // servo1
+ MoveServos(100, current_pos);
+}
+
+void EDARobotDog::LiftLeftRearLeg(int period, int height) {
+
+ // 鑾峰彇褰撳墠浣嶇疆
+ int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+ // 閲嶅3娆℃憞鎽嗗姩浣�
+ for (int num = 0; num < 3; num++) {
+ // servo1.write(180); delay(100);
+ current_pos[LEFT_REAR_LEG] = 180; // servo1
+ MoveServos(100, current_pos);
+
+ // servo1.write(150); delay(100);
+ current_pos[LEFT_REAR_LEG] = 150; // servo1
+ MoveServos(100, current_pos);
+ }
+
+ // servo1.write(90);
+ current_pos[LEFT_REAR_LEG] = 90; // servo1
+ MoveServos(100, current_pos);
+}
+
+void EDARobotDog::LiftRightFrontLeg(int period, int height) {
+
+ // 鑾峰彇褰撳墠浣嶇疆
+ int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+ // 閲嶅3娆℃憞鎽嗗姩浣�
+ for (int num = 0; num < 3; num++) {
+ // servo1.write(180); delay(100);
+ current_pos[RIGHT_FRONT_LEG] = 180; // servo1
+ MoveServos(100, current_pos);
+
+ // servo1.write(150); delay(100);
+ current_pos[RIGHT_FRONT_LEG] = 150; // servo1
+ MoveServos(100, current_pos);
+ }
+
+ // servo1.write(90);
+ current_pos[RIGHT_FRONT_LEG] = 90; // servo1
+ MoveServos(100, current_pos);
+}
+
+void EDARobotDog::LiftRightRearLeg(int period, int height) {
+
+ // 鑾峰彇褰撳墠浣嶇疆
+ int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+ // 閲嶅3娆℃憞鎽嗗姩浣�
+ for (int num = 0; num < 3; num++) {
+ // servo1.write(180); delay(100);
+ current_pos[RIGHT_REAR_LEG] = 0; // servo1
+ MoveServos(100, current_pos);
+
+ // servo1.write(150); delay(100);
+ current_pos[RIGHT_REAR_LEG] = 30; // servo1
+ MoveServos(100, current_pos);
+ }
+
+ // servo1.write(90);
+ current_pos[RIGHT_FRONT_LEG] = 90; // servo1
+ MoveServos(100, current_pos);
+}
+
+///////////////////////////////////////////////////////////////////
+//-- DOG GAIT MOVEMENTS -----------------------------------------//
+///////////////////////////////////////////////////////////////////
+
+// 鑸垫満鏂瑰悜鍒嗘瀽锛堜粠 Stretch/Sleep 鎺ㄦ柇鐗╃悊鍓嶅悗鏂瑰悜锛夛細
+//
+// Stretch: LF=0, LR=180, RF=180, RR=0
+// 鈫� 鍓嶈吙鍚戝墠浼革紝鍚庤吙鍚戝悗浼�
+//
+// LEFT_FRONT_LEG (0): 鍑忓皬=鍚戝墠, 澧炲ぇ=鍚戝悗
+// LEFT_REAR_LEG (1): 鍑忓皬=鍚戝墠, 澧炲ぇ=鍚戝悗
+// RIGHT_FRONT_LEG (2): 澧炲ぇ=鍚戝墠, 鍑忓皬=鍚戝悗
+// RIGHT_REAR_LEG (3): 澧炲ぇ=鍚戝墠, 鍑忓皬=鍚戝悗
+//
+// 鍗筹細宸︿晶涓よ吙鍚屽悜锛堝噺=鍓嶏級锛屽彸渚т袱鑵垮悓鍚戯紙澧�=鍓嶏級
+// 瀵硅绾緼: 宸﹀墠 + 鍙冲悗 鈫� 鍓嶈繘鏃跺乏鍓嶅噺灏�, 鍙冲悗鍑忓皬锛堥兘鏄悇鑷殑"鍚戝墠"锛�
+// 瀵硅绾緽: 宸﹀悗 + 鍙冲墠 鈫� 鍓嶈繘鏃跺乏鍚庡噺灏�, 鍙冲墠澧炲ぇ锛堥兘鏄悇鑷殑"鍚戝墠"锛�
+//
+// 鎶吙鏂瑰悜锛堜粠 Lift 鍑芥暟锛夛細
+// LEFT_FRONT_LEG: 0掳 (鍑忓皬=鎶�)
+// LEFT_REAR_LEG: 180掳 (澧炲ぇ=鎶�)
+// RIGHT_FRONT_LEG: 180掳 (澧炲ぇ=鎶�)
+// RIGHT_REAR_LEG: 0掳 (鍑忓皬=鎶�)
+
+// 杈呭姪锛氳幏鍙栧綋鍓嶆墍鏈夎埖鏈轰綅缃�
+void EDARobotDog::GetCurrentPositions(int pos[SERVO_COUNT]) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ pos[i] = servo_[i].GetPosition();
+ } else {
+ pos[i] = LEG_HOME_POSITION;
+ }
+ }
+}
+
+void EDARobotDog::Turn(float steps, int period, int dir) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ // 杞集 = 宸︿晶鍚庨�� + 鍙充晶鍓嶈繘锛堝乏杞級锛屾垨鍙嶈繃鏉ワ紙鍙宠浆锛�
+ // 浠� Walk 鐨勫凡楠岃瘉鏂瑰悜鎺ㄥ锛�
+ // 鍓嶈繘: LF=90-s, LR=90-s, RF=90+s, RR=90+s
+ // 鍚庨��: LF=90+s, LR=90+s, RF=90-s, RR=90-s
+ //
+ // 宸﹁浆(d=1): 宸︿晶鍚庨��+鍙充晶鍓嶈繘
+ // LF=90+s, LR=90+s, RF=90+s, RR=90+s 鈫� 鍏ㄩ儴 90+s
+ // 鍦伴潰鍙嶆帹: 鍏ㄩ儴 90-s
+ //
+ // 鍙宠浆(d=-1): 宸︿晶鍓嶈繘+鍙充晶鍚庨��
+ // LF=90-s, LR=90-s, RF=90-s, RR=90-s 鈫� 鍏ㄩ儴 90-s
+ // 鍦伴潰鍙嶆帹: 鍏ㄩ儴 90+s
+
+ const int swing = 60;
+ const int lift = 25;
+ int t = period / 6;
+ if (t < 50) t = 50;
+
+ int d = (dir == LEFT) ? -1 : 1;
+
+ for (int step = 0; step < (int)steps; step++) {
+ int pos[SERVO_COUNT];
+ GetCurrentPositions(pos);
+
+ // 鎷�1: 鎶珹锛圠F+RR锛�
+ pos[LEFT_FRONT_LEG] = 90 - lift;
+ pos[RIGHT_REAR_LEG] = 90 - lift;
+ MoveServos(t, pos);
+
+ // 鎷�2: A绌轰腑鎽� + B鍦伴潰鍙嶆帹
+ pos[LEFT_FRONT_LEG] = 90 - lift + d * swing; // LF绌轰腑
+ pos[RIGHT_REAR_LEG] = 90 - lift + d * swing; // RR绌轰腑
+ pos[LEFT_REAR_LEG] = 90 - d * swing; // LR鍦伴潰鍙嶆帹
+ pos[RIGHT_FRONT_LEG] = 90 - d * swing; // RF鍦伴潰鍙嶆帹
+ MoveServos(t, pos);
+
+ // 鎷�3: 鏀句笅A
+ pos[LEFT_FRONT_LEG] = 90 + d * swing;
+ pos[RIGHT_REAR_LEG] = 90 + d * swing;
+ MoveServos(t / 2, pos);
+
+ // 鎷�4: 鎶珺锛圠R+RF锛�
+ pos[LEFT_REAR_LEG] = 90 + lift;
+ pos[RIGHT_FRONT_LEG] = 90 + lift;
+ MoveServos(t, pos);
+
+ // 鎷�5: B绌轰腑鎽� + A鍦伴潰鍙嶆帹
+ pos[LEFT_REAR_LEG] = 90 + lift + d * swing; // LR绌轰腑
+ pos[RIGHT_FRONT_LEG] = 90 + lift + d * swing; // RF绌轰腑
+ pos[LEFT_FRONT_LEG] = 90 - d * swing; // LF鍦伴潰鍙嶆帹
+ pos[RIGHT_REAR_LEG] = 90 - d * swing; // RR鍦伴潰鍙嶆帹
+ MoveServos(t, pos);
+
+ // 鎷�6: 鏀句笅B
+ pos[LEFT_REAR_LEG] = 90 + d * swing;
+ pos[RIGHT_FRONT_LEG] = 90 + d * swing;
+ MoveServos(t / 2, pos);
+ }
+
+ // 缁撴潫鍥炰腑
+ int home[SERVO_COUNT] = {90, 90, 90, 90};
+ MoveServos(150, home);
+}
+
+void EDARobotDog::Walk(float steps, int period, int dir) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ // Trot 瀵硅姝ユ��
+ // 鏂瑰悜: LF鍑�=鍓�, LR鍑�=鍓�, RF澧�=鍓�, RR澧�=鍓�
+ // 鎶吙: LF鍑�=鎶�, LR澧�=鎶�, RF澧�=鎶�, RR鍑�=鎶�
+
+ const int lift = 25;
+ const int swing = 30;
+ int t = period / 6;
+ if (t < 50) t = 50;
+
+ int fwd = (dir == FORWARD) ? 1 : -1;
+
+ for (int step = 0; step < (int)steps; step++) {
+ int pos[SERVO_COUNT];
+ GetCurrentPositions(pos);
+
+ // 鎷�1: 鎶捣瀵硅绾緼锛圠F+RR锛�
+ pos[LEFT_FRONT_LEG] = 90 - lift;
+ pos[RIGHT_REAR_LEG] = 90 - lift;
+ MoveServos(t, pos);
+
+ // 鎷�2: A绌轰腑鍓嶆憜 + B鍦伴潰鍚庢帹锛堝悓鏃讹級
+ pos[LEFT_FRONT_LEG] = 90 - lift - fwd * swing; // LF: 淇濇寔鎶� + 鍓嶆憜
+ pos[RIGHT_REAR_LEG] = 90 - lift + fwd * swing; // RR: 淇濇寔鎶� + 鍓嶆憜(澧�=鍓�)
+ pos[LEFT_REAR_LEG] = 90 + fwd * swing; // LR: 鍦伴潰鍚庢帹(澧�=鍚�)
+ pos[RIGHT_FRONT_LEG] = 90 - fwd * swing; // RF: 鍦伴潰鍚庢帹(鍑�=鍚�)
+ MoveServos(t, pos);
+
+ // 鎷�3: 鏀句笅A锛堢潃鍦板埌鍓嶆憜浣嶇疆锛�
+ pos[LEFT_FRONT_LEG] = 90 - fwd * swing;
+ pos[RIGHT_REAR_LEG] = 90 + fwd * swing;
+ MoveServos(t / 2, pos);
+
+ // 鎷�4: 鎶捣瀵硅绾緽锛圠R+RF锛�
+ pos[LEFT_REAR_LEG] = 90 + lift;
+ pos[RIGHT_FRONT_LEG] = 90 + lift;
+ MoveServos(t, pos);
+
+ // 鎷�5: B绌轰腑鍓嶆憜 + A鍦伴潰鍚庢帹锛堝悓鏃讹級
+ pos[LEFT_REAR_LEG] = 90 + lift - fwd * swing; // LR: 淇濇寔鎶� + 鍓嶆憜(鍑�=鍓�)
+ pos[RIGHT_FRONT_LEG] = 90 + lift + fwd * swing; // RF: 淇濇寔鎶� + 鍓嶆憜(澧�=鍓�)
+ pos[LEFT_FRONT_LEG] = 90 + fwd * swing; // LF: 鍦伴潰鍚庢帹(澧�=鍚�)
+ pos[RIGHT_REAR_LEG] = 90 - fwd * swing; // RR: 鍦伴潰鍚庢帹(鍑�=鍚�)
+ MoveServos(t, pos);
+
+ // 鎷�6: 鏀句笅B锛堜笉鍥炰腑锛岀洿鎺ヨ鎺ヤ笅涓�姝ワ級
+ pos[LEFT_REAR_LEG] = 90 - fwd * swing;
+ pos[RIGHT_FRONT_LEG] = 90 + fwd * swing;
+ MoveServos(t / 2, pos);
+ }
+
+ // 缁撴潫鍥炰腑
+ int home[SERVO_COUNT] = {90, 90, 90, 90};
+ MoveServos(150, home);
+}
+
+void EDARobotDog::Sit(int period) {
+
+
+int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+
+ current_pos[LEFT_REAR_LEG] = 0; // servo2
+ current_pos[RIGHT_REAR_LEG] = 180; // servo4
+ MoveServos(100, current_pos);
+}
+
+void EDARobotDog::Stand(int period) {
+ // 绔欑珛锛氭墍鏈夎吙鍥炲埌涓珛浣嶇疆
+ Home();
+}
+
+void EDARobotDog::Stretch(int period) {
+
+
+ // 鑾峰彇褰撳墠浣嶇疆
+int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+
+ current_pos[LEFT_FRONT_LEG] = 0; // servo1
+ current_pos[RIGHT_REAR_LEG] = 0; // servo3
+ current_pos[LEFT_REAR_LEG] = 180; // servo2
+ current_pos[RIGHT_FRONT_LEG] = 180; // servo4
+ MoveServos(100, current_pos);
+}
+
+void EDARobotDog::Shake(int period) {
+ // 鎽囨憜锛氬乏鍙虫憞鎽嗚韩浣擄紝宸﹀墠鑵垮拰鍙冲悗鑵胯繍鍔ㄦ柟鍚戠浉鍙�
+ int A[SERVO_COUNT] = {20, 0, 20, 0}; // 鍙湁鍓嶈吙鎽囨憜
+ int O[SERVO_COUNT] = {0, LEG_HOME_POSITION, 0, LEG_HOME_POSITION};
+ // 宸﹀墠鑵垮拰鍙冲墠鑵垮弽鐩告憞鎽�
+ double phase_diff[SERVO_COUNT] = {DEG2RAD(180), 0, DEG2RAD(0), 0};
+
+ Execute(A, O, period, phase_diff, 3);
+}
+
+void EDARobotDog::EnableServoLimit(int diff_limit) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetLimiter(diff_limit);
+ }
+ }
+}
+
+void EDARobotDog::DisableServoLimit() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].DisableLimiter();
+ }
+ }
+}
+
+void EDARobotDog::Sleep() {
+
+ int current_pos[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_pos[i] = servo_[i].GetPosition();
+ } else {
+ current_pos[i] = LEG_HOME_POSITION;
+ }
+ }
+
+ // servo1.write(0); servo3.write(180); servo2.write(180); servo4.write(0);
+ current_pos[LEFT_FRONT_LEG] = 0; // servo1
+ current_pos[RIGHT_REAR_LEG] = 180; // servo3
+ current_pos[LEFT_REAR_LEG] = 180; // servo2
+ current_pos[RIGHT_FRONT_LEG] = 0; // servo4
+ MoveServos(100, current_pos);
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.h
new file mode 100644
index 0000000..1541bf1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_dog_movements.h
@@ -0,0 +1,92 @@
+#ifndef __EDA_DOG_MOVEMENTS_H__
+#define __EDA_DOG_MOVEMENTS_H__
+
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "oscillator.h"
+
+//-- Constants
+#define FORWARD 1
+#define BACKWARD -1
+#define LEFT 1
+#define RIGHT -1
+#define SMALL 5
+#define MEDIUM 15
+#define BIG 30
+
+// -- Servo delta limit default. degree / sec
+#define SERVO_LIMIT_DEFAULT 240
+
+// -- Servo indexes for easy access
+#define LEFT_FRONT_LEG 0
+#define LEFT_REAR_LEG 1
+#define RIGHT_FRONT_LEG 2
+#define RIGHT_REAR_LEG 3
+#define SERVO_COUNT 4
+
+class EDARobotDog {
+public:
+ EDARobotDog();
+ ~EDARobotDog();
+
+ //-- EDA Dog initialization
+ void Init(int left_front_leg, int left_rear_leg, int right_front_leg, int right_rear_leg);
+
+ //-- Attach & detach functions
+ void AttachServos();
+ void DetachServos();
+
+ //-- Oscillator Trims
+ void SetTrims(int left_front_leg, int left_rear_leg, int right_front_leg, int right_rear_leg);
+
+ //-- Predetermined Motion Functions
+ void MoveServos(int time, int servo_target[]);
+ void MoveSingle(int position, int servo_number);
+ void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle);
+
+ //-- HOME = Dog at rest position
+ void Home();
+ bool GetRestState();
+ void SetRestState(bool state);
+
+ //-- Basic leg movements
+ void LiftLeftFrontLeg(int period = 1000, int height = 45); // 鎶捣宸﹀墠鑵�
+ void LiftLeftRearLeg(int period = 1000, int height = 45); // 鎶捣宸﹀悗鑵�
+ void LiftRightFrontLeg(int period = 1000, int height = 45); // 鎶捣鍙冲墠鑵�
+ void LiftRightRearLeg(int period = 1000, int height = 45); // 鎶捣鍙冲悗鑵�
+
+ //-- Dog gait movements
+ void GetCurrentPositions(int pos[SERVO_COUNT]);
+ void Walk(float steps = 4, int period = 1000, int dir = FORWARD);
+ void Turn(float steps = 4, int period = 2000, int dir = LEFT);
+ void Sit(int period = 1500);
+ void Stand(int period = 1500);
+ void Stretch(int period = 2000);
+ void Shake(int period = 1000);
+ void Sleep(); // 鐫¤鍔ㄤ綔
+
+ // -- Servo limiter
+ void EnableServoLimit(int speed_limit_degree_per_sec = SERVO_LIMIT_DEFAULT);
+ void DisableServoLimit();
+
+private:
+ Oscillator servo_[SERVO_COUNT];
+
+ int servo_pins_[SERVO_COUNT];
+ int servo_trim_[SERVO_COUNT];
+
+ unsigned long final_time_;
+ unsigned long partial_time_;
+ float increment_[SERVO_COUNT];
+
+ bool is_dog_resting_;
+
+ void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps);
+};
+
+#endif // __EDA_DOG_MOVEMENTS_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_robot_pro.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_robot_pro.cc
new file mode 100644
index 0000000..e96f1b1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/eda_robot_pro.cc
@@ -0,0 +1,133 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/oled_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+
+#include <wifi_station.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "EDARobotPro"
+
+extern void InitializeEDARobotDogController();
+
+class EDARobotPro : public WifiBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ Button touch_button_;
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+
+ // EDA鏈哄櫒鐙楁帶鍒跺櫒鍒濆鍖�
+ void InitializeEDARobotDogController() {
+ ESP_LOGI(TAG, "鍒濆鍖朎DA鏈哄櫒鐙桵CP鎺у埗鍣�");
+ ::InitializeEDARobotDogController();
+ }
+void InitializeButtons() {
+
+ touch_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+ touch_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+
+ }
+
+public:
+ EDARobotPro() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ touch_button_(TOUCH_BUTTON_GPIO){
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeEDARobotDogController();
+ InitializeButtons();
+ }
+
+
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(EDARobotPro);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.cc
new file mode 100644
index 0000000..a821f35
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.cc
@@ -0,0 +1,161 @@
+//--------------------------------------------------------------
+//-- Oscillator.pde
+//-- Generate sinusoidal oscillations in the servos
+//--------------------------------------------------------------
+//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
+//-- (c) txp666 for esp32, 202503
+//-- GPL license
+//--------------------------------------------------------------
+#include "oscillator.h"
+
+#include <driver/ledc.h>
+#include <esp_timer.h>
+
+#include <algorithm>
+#include <cmath>
+
+static const char* TAG = "Oscillator";
+
+extern unsigned long IRAM_ATTR millis();
+
+static ledc_channel_t next_free_channel = LEDC_CHANNEL_0;
+
+Oscillator::Oscillator(int trim) {
+ trim_ = trim;
+ diff_limit_ = 0;
+ is_attached_ = false;
+
+ sampling_period_ = 30;
+ period_ = 2000;
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+
+ amplitude_ = 45;
+ phase_ = 0;
+ phase0_ = 0;
+ offset_ = 0;
+ stop_ = false;
+ rev_ = false;
+
+ pos_ = 90;
+ previous_millis_ = 0;
+}
+
+Oscillator::~Oscillator() {
+ Detach();
+}
+
+uint32_t Oscillator::AngleToCompare(int angle) {
+ return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) /
+ (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) +
+ SERVO_MIN_PULSEWIDTH_US;
+}
+
+bool Oscillator::NextSample() {
+ current_millis_ = millis();
+
+ if (current_millis_ - previous_millis_ > sampling_period_) {
+ previous_millis_ = current_millis_;
+ return true;
+ }
+
+ return false;
+}
+
+void Oscillator::Attach(int pin, bool rev) {
+ if (is_attached_) {
+ Detach();
+ }
+
+ pin_ = pin;
+ rev_ = rev;
+
+ ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE,
+ .duty_resolution = LEDC_TIMER_13_BIT,
+ .timer_num = LEDC_TIMER_1,
+ .freq_hz = 50,
+ .clk_cfg = LEDC_AUTO_CLK};
+ ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
+
+ static int last_channel = 0;
+ last_channel = (last_channel + 1) % 7 + 1;
+ ledc_channel_ = (ledc_channel_t)last_channel;
+
+ ledc_channel_config_t ledc_channel = {.gpio_num = pin_,
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .channel = ledc_channel_,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = LEDC_TIMER_1,
+ .duty = 0,
+ .hpoint = 0};
+ ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
+
+ ledc_speed_mode_ = LEDC_LOW_SPEED_MODE;
+
+ // pos_ = 90;
+ // Write(pos_);
+ previous_servo_command_millis_ = millis();
+
+ is_attached_ = true;
+}
+
+void Oscillator::Detach() {
+ if (!is_attached_)
+ return;
+
+ ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0));
+
+ is_attached_ = false;
+}
+
+void Oscillator::SetT(unsigned int T) {
+ period_ = T;
+
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+}
+
+void Oscillator::SetPosition(int position) {
+ Write(position);
+}
+
+void Oscillator::Refresh() {
+ if (NextSample()) {
+ if (!stop_) {
+ int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_);
+ if (rev_)
+ pos = -pos;
+ Write(pos + 90);
+ }
+
+ phase_ = phase_ + inc_;
+ }
+}
+
+void Oscillator::Write(int position) {
+ if (!is_attached_)
+ return;
+
+ long currentMillis = millis();
+ if (diff_limit_ > 0) {
+ int limit = std::max(
+ 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000);
+ if (abs(position - pos_) > limit) {
+ pos_ += position < pos_ ? -limit : limit;
+ } else {
+ pos_ = position;
+ }
+ } else {
+ pos_ = position;
+ }
+ previous_servo_command_millis_ = currentMillis;
+
+ int angle = pos_ + trim_;
+
+ angle = std::min(std::max(angle, 0), 180);
+
+ uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0);
+
+ ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty));
+ ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_));
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.h
new file mode 100644
index 0000000..c525eff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-robot-pro/oscillator.h
@@ -0,0 +1,91 @@
+//--------------------------------------------------------------
+//-- Oscillator.pde
+//-- Generate sinusoidal oscillations in the servos
+//--------------------------------------------------------------
+//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
+//-- (c) txp666 for esp32, 202503
+//-- GPL license
+//--------------------------------------------------------------
+#ifndef __OSCILLATOR_H__
+#define __OSCILLATOR_H__
+
+#include <driver/ledc.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#define M_PI 3.14159265358979323846
+
+#ifndef DEG2RAD
+#define DEG2RAD(g) ((g) * M_PI) / 180
+#endif
+
+#define SERVO_MIN_PULSEWIDTH_US 500 // 鏈�灏忚剦瀹斤紙寰锛�
+#define SERVO_MAX_PULSEWIDTH_US 2500 // 鏈�澶ц剦瀹斤紙寰锛�
+#define SERVO_MIN_DEGREE -90 // 鏈�灏忚搴�
+#define SERVO_MAX_DEGREE 90 // 鏈�澶ц搴�
+#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick
+#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms
+
+class Oscillator {
+public:
+ Oscillator(int trim = 0);
+ ~Oscillator();
+ void Attach(int pin, bool rev = false);
+ void Detach();
+
+ void SetA(unsigned int amplitude) { amplitude_ = amplitude; };
+ void SetO(int offset) { offset_ = offset; };
+ void SetPh(double Ph) { phase0_ = Ph; };
+ void SetT(unsigned int period);
+ void SetTrim(int trim) { trim_ = trim; };
+ void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; };
+ void DisableLimiter() { diff_limit_ = 0; };
+ int GetTrim() { return trim_; };
+ void SetPosition(int position);
+ void Stop() { stop_ = true; };
+ void Play() { stop_ = false; };
+ void Reset() { phase_ = 0; };
+ void Refresh();
+ int GetPosition() { return pos_; }
+
+private:
+ bool NextSample();
+ void Write(int position);
+ uint32_t AngleToCompare(int angle);
+
+private:
+ bool is_attached_;
+
+ //-- Oscillators parameters
+ unsigned int amplitude_; //-- Amplitude (degrees)
+ int offset_; //-- Offset (degrees)
+ unsigned int period_; //-- Period (miliseconds)
+ double phase0_; //-- Phase (radians)
+
+ //-- Internal variables
+ int pos_; //-- Current servo pos
+ int pin_; //-- Pin where the servo is connected
+ int trim_; //-- Calibration offset
+ double phase_; //-- Current phase
+ double inc_; //-- Increment of phase
+ double number_samples_; //-- Number of samples
+ unsigned int sampling_period_; //-- sampling period (ms)
+
+ long previous_millis_;
+ long current_millis_;
+
+ //-- Oscillation mode. If true, the servo is stopped
+ bool stop_;
+
+ //-- Reverse mode
+ bool rev_;
+
+ int diff_limit_;
+ long previous_servo_command_millis_;
+
+ ledc_channel_t ledc_channel_;
+ ledc_mode_t ledc_speed_mode_;
+};
+
+#endif // __OSCILLATOR_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/README.md b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/README.md
new file mode 100644
index 0000000..e9a5dc4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/README.md
@@ -0,0 +1,14 @@
+## EDA-Super-Bear 鏈哄櫒鐔�
+
+### 椤圭洰鏂囨。
+- [EDA-Super-Bear 椤圭洰鏂囨。](https://wiki.lceda.cn/zh-hans/course-projects/smart-internet/eda-superbear/eda-superbear-introduce.html)
+
+### 缂栬瘧閰嶇疆
+Flash澶у皬鎸塃SP32S3妯$粍澶у皬璋冩暣
+渚嬪妯$粍Flash瀹归噺涓�8MB鏃讹細
+
+```
+Partition Table --->
+ Partition Table (Custom partition table CSV) --->
+ (partitions/v2/8m.csv) Custom partition CSV file
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.h
new file mode 100644
index 0000000..0977635
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.h
@@ -0,0 +1,29 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define RIGHT_LEG_PIN GPIO_NUM_11
+#define RIGHT_FOOT_PIN GPIO_NUM_14
+#define LEFT_LEG_PIN GPIO_NUM_13
+#define LEFT_FOOT_PIN GPIO_NUM_12
+#define LEFT_HAND_PIN GPIO_NUM_21
+#define RIGHT_HAND_PIN GPIO_NUM_10
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_METHOD_SIMPLEX
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_40
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_39
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_41
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_45
+
+#define TOUCH_BUTTON_GPIO GPIO_NUM_14
+#define BOOT_BUTTON_GPIO GPIO_NUM_38
+
+#define EDA_SUPER_BEAR_VERSION "1.0.0"
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.json b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.json
new file mode 100644
index 0000000..750db8a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "lceda-course-examples",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "eda-super-bear",
+ "sdkconfig_append": [
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/16m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear.cc
new file mode 100644
index 0000000..ae594b0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear.cc
@@ -0,0 +1,58 @@
+#include <esp_log.h>
+
+#include "application.h"
+#include "codecs/no_audio_codec.h"
+#include "button.h"
+#include "config.h"
+#include "display/display.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "system_reset.h"
+#include "wifi_board.h"
+
+#define TAG "EdaSuperBear"
+
+extern void InitializeEdaSuperBearController();
+
+class EdaSuperBear : public WifiBoard {
+private:
+ Display* display_;
+ Button boot_button_;
+
+ void InitializeDisplay() {
+ display_ = new NoDisplay();
+ ESP_LOGI(TAG, "浣跨敤NoDisplay (鏃犵墿鐞嗘樉绀哄睆)");
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ EdaSuperBear() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeDisplay();
+ InitializeButtons();
+ ESP_LOGI(TAG, "鍒濆鍖朎daRobot鏈哄櫒浜篗CP鎺у埗鍣�");
+ ::InitializeEdaSuperBearController();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK,
+ AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK,
+ AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override { return display_; }
+};
+
+DECLARE_BOARD(EdaSuperBear);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_controller.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_controller.cc
new file mode 100644
index 0000000..19e41b0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_controller.cc
@@ -0,0 +1,493 @@
+/*
+ EdaRobot鏈哄櫒浜烘帶鍒跺櫒 - MCP鍗忚鐗堟湰
+*/
+
+#include <cJSON.h>
+#include <esp_log.h>
+
+#include <cstring>
+
+#include "application.h"
+#include "board.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "eda_super_bear_movements.h"
+#include "sdkconfig.h"
+#include "settings.h"
+
+#define TAG "EdaSuperBearController"
+
+class EdaSuperBearController {
+private:
+ EdaRobot edarobot_;
+ TaskHandle_t action_task_handle_ = nullptr;
+ QueueHandle_t action_queue_;
+ bool has_hands_ = false;
+ bool is_action_in_progress_ = false;
+
+ struct EdaRobotActionParams {
+ int action_type;
+ int steps;
+ int speed;
+ int direction;
+ int amount;
+ };
+
+ enum ActionType {
+ ACTION_WALK = 1,
+ ACTION_TURN = 2,
+ ACTION_JUMP = 3,
+ ACTION_SWING = 4,
+ ACTION_MOONWALK = 5,
+ ACTION_BEND = 6,
+ ACTION_SHAKE_LEG = 7,
+ ACTION_UPDOWN = 8,
+ ACTION_TIPTOE_SWING = 9,
+ ACTION_JITTER = 10,
+ ACTION_ASCENDING_TURN = 11,
+ ACTION_CRUSAITO = 12,
+ ACTION_FLAPPING = 13,
+ ACTION_HANDS_UP = 14,
+ ACTION_HANDS_DOWN = 15,
+ ACTION_HAND_WAVE = 16,
+ ACTION_HOME = 17
+ };
+
+ static void ActionTask(void* arg) {
+ EdaSuperBearController* controller = static_cast<EdaSuperBearController*>(arg);
+ EdaRobotActionParams params;
+ controller->edarobot_.AttachServos();
+
+ while (true) {
+ if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) {
+ ESP_LOGI(TAG, "鎵ц鍔ㄤ綔: %d", params.action_type);
+ controller->is_action_in_progress_ = true;
+
+ switch (params.action_type) {
+ case ACTION_WALK:
+ controller->edarobot_.Walk(params.steps, params.speed, params.direction,
+ params.amount);
+ break;
+ case ACTION_TURN:
+ controller->edarobot_.Turn(params.steps, params.speed, params.direction,
+ params.amount);
+ break;
+ case ACTION_JUMP:
+ controller->edarobot_.Jump(params.steps, params.speed);
+ break;
+ case ACTION_SWING:
+ controller->edarobot_.Swing(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_MOONWALK:
+ controller->edarobot_.Moonwalker(params.steps, params.speed, params.amount,
+ params.direction);
+ break;
+ case ACTION_BEND:
+ controller->edarobot_.Bend(params.steps, params.speed, params.direction);
+ break;
+ case ACTION_SHAKE_LEG:
+ controller->edarobot_.ShakeLeg(params.steps, params.speed, params.direction);
+ break;
+ case ACTION_UPDOWN:
+ controller->edarobot_.UpDown(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_TIPTOE_SWING:
+ controller->edarobot_.TiptoeSwing(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_JITTER:
+ controller->edarobot_.Jitter(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_ASCENDING_TURN:
+ controller->edarobot_.AscendingTurn(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_CRUSAITO:
+ controller->edarobot_.Crusaito(params.steps, params.speed, params.amount,
+ params.direction);
+ break;
+ case ACTION_FLAPPING:
+ controller->edarobot_.Flapping(params.steps, params.speed, params.amount,
+ params.direction);
+ break;
+ case ACTION_HANDS_UP:
+ if (controller->has_hands_) {
+ controller->edarobot_.HandsUp(params.speed, params.direction);
+ }
+ break;
+ case ACTION_HANDS_DOWN:
+ if (controller->has_hands_) {
+ controller->edarobot_.HandsDown(params.speed, params.direction);
+ }
+ break;
+ case ACTION_HAND_WAVE:
+ if (controller->has_hands_) {
+ controller->edarobot_.HandWave(params.speed, params.direction);
+ }
+ break;
+ case ACTION_HOME:
+ controller->edarobot_.Home(params.direction == 1);
+ break;
+ }
+ if (params.action_type != ACTION_HOME) {
+ controller->edarobot_.Home(params.action_type < ACTION_HANDS_UP);
+ }
+ controller->is_action_in_progress_ = false;
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ }
+ }
+
+ void StartActionTaskIfNeeded() {
+ if (action_task_handle_ == nullptr) {
+ xTaskCreate(ActionTask, "edarobot_action", 1024 * 3, this, configMAX_PRIORITIES - 1,
+ &action_task_handle_);
+ }
+ }
+
+ void QueueAction(int action_type, int steps, int speed, int direction, int amount) {
+ // 妫�鏌ユ墜閮ㄥ姩浣�
+ if ((action_type >= ACTION_HANDS_UP && action_type <= ACTION_HAND_WAVE) && !has_hands_) {
+ ESP_LOGW(TAG, "灏濊瘯鎵ц鎵嬮儴鍔ㄤ綔锛屼絾鏈哄櫒浜烘病鏈夐厤缃墜閮ㄨ埖鏈�");
+ return;
+ }
+
+ ESP_LOGI(TAG, "鍔ㄤ綔鎺у埗: 绫诲瀷=%d, 姝ユ暟=%d, 閫熷害=%d, 鏂瑰悜=%d, 骞呭害=%d", action_type, steps,
+ speed, direction, amount);
+
+ EdaRobotActionParams params = {action_type, steps, speed, direction, amount};
+ xQueueSend(action_queue_, ¶ms, portMAX_DELAY);
+ StartActionTaskIfNeeded();
+ }
+
+ void LoadTrimsFromNVS() {
+ Settings settings("edarobot_trims", false);
+
+ int left_leg = settings.GetInt("left_leg", 0);
+ int right_leg = settings.GetInt("right_leg", 0);
+ int left_foot = settings.GetInt("left_foot", 0);
+ int right_foot = settings.GetInt("right_foot", 0);
+ int left_hand = settings.GetInt("left_hand", 0);
+ int right_hand = settings.GetInt("right_hand", 0);
+
+ ESP_LOGI(TAG, "浠嶯VS鍔犺浇寰皟璁剧疆: 宸﹁吙=%d, 鍙宠吙=%d, 宸﹁剼=%d, 鍙宠剼=%d, 宸︽墜=%d, 鍙虫墜=%d",
+ left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
+
+ edarobot_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
+ }
+
+public:
+ EdaSuperBearController() {
+ edarobot_.Init(LEFT_LEG_PIN, RIGHT_LEG_PIN, LEFT_FOOT_PIN, RIGHT_FOOT_PIN, LEFT_HAND_PIN,
+ RIGHT_HAND_PIN);
+
+ has_hands_ = (LEFT_HAND_PIN != -1 && RIGHT_HAND_PIN != -1);
+ ESP_LOGI(TAG, "EdaRobot鏈哄櫒浜哄垵濮嬪寲%s鎵嬮儴鑸垫満", has_hands_ ? "甯�" : "涓嶅甫");
+
+ LoadTrimsFromNVS();
+
+ action_queue_ = xQueueCreate(10, sizeof(EdaRobotActionParams));
+
+ QueueAction(ACTION_HOME, 1, 1000, 1, 0); // direction=1琛ㄧず澶嶄綅鎵嬮儴
+
+ RegisterMcpTools();
+ }
+
+ void RegisterMcpTools() {
+ auto& mcp_server = McpServer::GetInstance();
+
+ ESP_LOGI(TAG, "寮�濮嬫敞鍐孧CP宸ュ叿...");
+
+ // 鍩虹绉诲姩鍔ㄤ綔
+ mcp_server.AddTool("self.edarobot.walk_forward",
+ "琛岃蛋銆俿teps: 琛岃蛋姝ユ暟(1-100); speed: 琛岃蛋閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); "
+ "direction: 琛岃蛋鏂瑰悜(-1=鍚庨��, 1=鍓嶈繘); arm_swing: 鎵嬭噦鎽嗗姩骞呭害(0-170搴�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("arm_swing", kPropertyTypeInteger, 50, 0, 170),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int arm_swing = properties["arm_swing"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_WALK, steps, speed, direction, arm_swing);
+ return true;
+ });
+
+ mcp_server.AddTool("self.edarobot.turn_left",
+ "杞韩銆俿teps: 杞韩姝ユ暟(1-100); speed: 杞韩閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); "
+ "direction: 杞韩鏂瑰悜(1=宸﹁浆, -1=鍙宠浆); arm_swing: 鎵嬭噦鎽嗗姩骞呭害(0-170搴�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("arm_swing", kPropertyTypeInteger, 50, 0, 170),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int arm_swing = properties["arm_swing"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_TURN, steps, speed, direction, arm_swing);
+ return true;
+ });
+
+ mcp_server.AddTool("self.edarobot.jump",
+ "璺宠穬銆俿teps: 璺宠穬娆℃暟(1-100); speed: 璺宠穬閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ QueueAction(ACTION_JUMP, steps, speed, 0, 0);
+ return true;
+ });
+
+ // 鐗规畩鍔ㄤ綔
+ mcp_server.AddTool("self.edarobot.swing",
+ "宸﹀彸鎽囨憜銆俿teps: 鎽囨憜娆℃暟(1-100); speed: "
+ "鎽囨憜閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); amount: 鎽囨憜骞呭害(0-170搴�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("amount", kPropertyTypeInteger, 30, 0, 170)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int amount = properties["amount"].value<int>();
+ QueueAction(ACTION_SWING, steps, speed, 0, amount);
+ return true;
+ });
+
+ mcp_server.AddTool("self.edarobot.moonwalk",
+ "澶┖姝ャ�俿teps: 澶┖姝ユ鏁�(1-100); speed: 閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); "
+ "direction: 鏂瑰悜(1=宸�, -1=鍙�); amount: 骞呭害(0-170搴�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1),
+ Property("amount", kPropertyTypeInteger, 25, 0, 170)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ int amount = properties["amount"].value<int>();
+ QueueAction(ACTION_MOONWALK, steps, speed, direction, amount);
+ return true;
+ });
+
+ mcp_server.AddTool("self.edarobot.bend",
+ "寮洸韬綋銆俿teps: 寮洸娆℃暟(1-100); speed: "
+ "寮洸閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); direction: 寮洸鏂瑰悜(1=宸�, -1=鍙�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_BEND, steps, speed, direction, 0);
+ return true;
+ });
+
+ mcp_server.AddTool("self.edarobot.shake_leg",
+ "鎽囪吙銆俿teps: 鎽囪吙娆℃暟(1-100); speed: 鎽囪吙閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); "
+ "direction: 鑵块儴閫夋嫨(1=宸﹁吙, -1=鍙宠吙)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 1, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_SHAKE_LEG, steps, speed, direction, 0);
+ return true;
+ });
+
+ mcp_server.AddTool("self.edarobot.updown",
+ "涓婁笅杩愬姩銆俿teps: 涓婁笅杩愬姩娆℃暟(1-100); speed: "
+ "杩愬姩閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); amount: 杩愬姩骞呭害(0-170搴�)",
+ PropertyList({Property("steps", kPropertyTypeInteger, 3, 1, 100),
+ Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("amount", kPropertyTypeInteger, 20, 0, 170)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int amount = properties["amount"].value<int>();
+ QueueAction(ACTION_UPDOWN, steps, speed, 0, amount);
+ return true;
+ });
+
+ // 鎵嬮儴鍔ㄤ綔锛堜粎鍦ㄦ湁鎵嬮儴鑸垫満鏃跺彲鐢級
+ if (has_hands_) {
+ mcp_server.AddTool(
+ "self.edarobot.hands_up",
+ "涓炬墜銆俿peed: 涓炬墜閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); direction: 鎵嬮儴閫夋嫨(1=宸︽墜, "
+ "-1=鍙虫墜, 0=鍙屾墜)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_HANDS_UP, 1, speed, direction, 0);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.edarobot.hands_down",
+ "鏀炬墜銆俿peed: 鏀炬墜閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); direction: 鎵嬮儴閫夋嫨(1=宸︽墜, "
+ "-1=鍙虫墜, 0=鍙屾墜)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_HANDS_DOWN, 1, speed, direction, 0);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.edarobot.hand_wave",
+ "鎸ユ墜銆俿peed: 鎸ユ墜閫熷害(500-1500锛屾暟鍊艰秺灏忚秺蹇�); direction: 鎵嬮儴閫夋嫨(1=宸︽墜, "
+ "-1=鍙虫墜, 0=鍙屾墜)",
+ PropertyList({Property("speed", kPropertyTypeInteger, 1000, 500, 1500),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ QueueAction(ACTION_HAND_WAVE, 1, speed, direction, 0);
+ return true;
+ });
+ }
+
+ // 绯荤粺宸ュ叿
+ mcp_server.AddTool("self.edarobot.stop", "绔嬪嵆鍋滄", PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ is_action_in_progress_ = false;
+ xQueueReset(action_queue_);
+
+ QueueAction(ACTION_HOME, 1, 1000, 1, 0);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.edarobot.set_trim",
+ "鏍″噯鍗曚釜鑸垫満浣嶇疆銆傝缃寚瀹氳埖鏈虹殑寰皟鍙傛暟浠ヨ皟鏁碋daRobot鐨勫垵濮嬬珯绔嬪Э鎬侊紝璁剧疆灏嗘案涔呬繚瀛樸��"
+ "servo_type: 鑸垫満绫诲瀷(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand); "
+ "trim_value: 寰皟鍊�(-50鍒�50搴�)",
+ PropertyList({Property("servo_type", kPropertyTypeString, "left_leg"),
+ Property("trim_value", kPropertyTypeInteger, 0, -50, 50)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ std::string servo_type = properties["servo_type"].value<std::string>();
+ int trim_value = properties["trim_value"].value<int>();
+
+ ESP_LOGI(TAG, "璁剧疆鑸垫満寰皟: %s = %d搴�", servo_type.c_str(), trim_value);
+
+ // 鑾峰彇褰撳墠鎵�鏈夊井璋冨��
+ Settings settings("edarobot_trims", true);
+ int left_leg = settings.GetInt("left_leg", 0);
+ int right_leg = settings.GetInt("right_leg", 0);
+ int left_foot = settings.GetInt("left_foot", 0);
+ int right_foot = settings.GetInt("right_foot", 0);
+ int left_hand = settings.GetInt("left_hand", 0);
+ int right_hand = settings.GetInt("right_hand", 0);
+
+ // 鏇存柊鎸囧畾鑸垫満鐨勫井璋冨��
+ if (servo_type == "left_leg") {
+ left_leg = trim_value;
+ settings.SetInt("left_leg", left_leg);
+ } else if (servo_type == "right_leg") {
+ right_leg = trim_value;
+ settings.SetInt("right_leg", right_leg);
+ } else if (servo_type == "left_foot") {
+ left_foot = trim_value;
+ settings.SetInt("left_foot", left_foot);
+ } else if (servo_type == "right_foot") {
+ right_foot = trim_value;
+ settings.SetInt("right_foot", right_foot);
+ } else if (servo_type == "left_hand") {
+ if (!has_hands_) {
+ return "閿欒锛氭満鍣ㄤ汉娌℃湁閰嶇疆鎵嬮儴鑸垫満";
+ }
+ left_hand = trim_value;
+ settings.SetInt("left_hand", left_hand);
+ } else if (servo_type == "right_hand") {
+ if (!has_hands_) {
+ return "閿欒锛氭満鍣ㄤ汉娌℃湁閰嶇疆鎵嬮儴鑸垫満";
+ }
+ right_hand = trim_value;
+ settings.SetInt("right_hand", right_hand);
+ } else {
+ return "閿欒锛氭棤鏁堢殑鑸垫満绫诲瀷锛岃浣跨敤: left_leg, right_leg, left_foot, "
+ "right_foot, left_hand, right_hand";
+ }
+
+ edarobot_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
+
+ QueueAction(ACTION_JUMP, 1, 500, 0, 0);
+
+ return "鑸垫満 " + servo_type + " 寰皟璁剧疆涓� " + std::to_string(trim_value) +
+ " 搴︼紝宸叉案涔呬繚瀛�";
+ });
+
+ mcp_server.AddTool("self.edarobot.get_trims", "鑾峰彇褰撳墠鐨勮埖鏈哄井璋冭缃�", PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("edarobot_trims", false);
+
+ int left_leg = settings.GetInt("left_leg", 0);
+ int right_leg = settings.GetInt("right_leg", 0);
+ int left_foot = settings.GetInt("left_foot", 0);
+ int right_foot = settings.GetInt("right_foot", 0);
+ int left_hand = settings.GetInt("left_hand", 0);
+ int right_hand = settings.GetInt("right_hand", 0);
+
+ std::string result =
+ "{\"left_leg\":" + std::to_string(left_leg) +
+ ",\"right_leg\":" + std::to_string(right_leg) +
+ ",\"left_foot\":" + std::to_string(left_foot) +
+ ",\"right_foot\":" + std::to_string(right_foot) +
+ ",\"left_hand\":" + std::to_string(left_hand) +
+ ",\"right_hand\":" + std::to_string(right_hand) + "}";
+
+ ESP_LOGI(TAG, "鑾峰彇寰皟璁剧疆: %s", result.c_str());
+ return result;
+ });
+
+ mcp_server.AddTool("self.edarobot.get_status", "鑾峰彇鏈哄櫒浜虹姸鎬侊紝杩斿洖 moving 鎴� idle",
+ PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return is_action_in_progress_ ? "moving" : "idle";
+ });
+
+ mcp_server.AddTool("self.battery.get_level", "鑾峰彇鏈哄櫒浜虹數姹犵數閲忓拰鍏呯數鐘舵��", PropertyList(),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto& board = Board::GetInstance();
+ int level = 0;
+ bool charging = false;
+ bool discharging = false;
+ board.GetBatteryLevel(level, charging, discharging);
+
+ std::string status =
+ "{\"level\":" + std::to_string(level) +
+ ",\"charging\":" + (charging ? "true" : "false") + "}";
+ return status;
+ });
+
+ ESP_LOGI(TAG, "MCP宸ュ叿娉ㄥ唽瀹屾垚");
+ }
+
+ ~EdaSuperBearController() {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ vQueueDelete(action_queue_);
+ }
+};
+
+static EdaSuperBearController* g_edarobot_controller = nullptr;
+
+void InitializeEdaSuperBearController() {
+ if (g_edarobot_controller == nullptr) {
+ g_edarobot_controller = new EdaSuperBearController();
+ ESP_LOGI(TAG, "EdaRobot鎺у埗鍣ㄥ凡鍒濆鍖栧苟娉ㄥ唽MCP宸ュ叿");
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.cc
new file mode 100644
index 0000000..50d2d4f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.cc
@@ -0,0 +1,763 @@
+#include "eda_super_bear_movements.h"
+
+#include <algorithm>
+
+#include "oscillator.h"
+
+static const char* TAG = "EdaSuperBearMovements";
+
+#define HAND_HOME_POSITION 45
+
+EdaRobot::EdaRobot() {
+ is_edarobot_resting_ = false;
+ has_hands_ = false;
+ // 鍒濆鍖栨墍鏈夎埖鏈虹鑴氫负-1锛堟湭杩炴帴锛�
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ servo_pins_[i] = -1;
+ servo_trim_[i] = 0;
+ }
+}
+
+EdaRobot::~EdaRobot() {
+ DetachServos();
+}
+
+unsigned long IRAM_ATTR millis() {
+ return (unsigned long)(esp_timer_get_time() / 1000ULL);
+}
+
+void EdaRobot::Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand,
+ int right_hand) {
+ servo_pins_[LEFT_LEG] = left_leg;
+ servo_pins_[RIGHT_LEG] = right_leg;
+ servo_pins_[LEFT_FOOT] = left_foot;
+ servo_pins_[RIGHT_FOOT] = right_foot;
+ servo_pins_[LEFT_HAND] = left_hand;
+ servo_pins_[RIGHT_HAND] = right_hand;
+
+ // 妫�鏌ユ槸鍚︽湁鎵嬮儴鑸垫満
+ has_hands_ = (left_hand != -1 && right_hand != -1);
+
+ AttachServos();
+ is_edarobot_resting_ = false;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- ATTACH & DETACH FUNCTIONS ----------------------------------//
+///////////////////////////////////////////////////////////////////
+void EdaRobot::AttachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Attach(servo_pins_[i]);
+ }
+ }
+}
+
+void EdaRobot::DetachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Detach();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- OSCILLATORS TRIMS ------------------------------------------//
+///////////////////////////////////////////////////////////////////
+void EdaRobot::SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand,
+ int right_hand) {
+ servo_trim_[LEFT_LEG] = left_leg;
+ servo_trim_[RIGHT_LEG] = right_leg;
+ servo_trim_[LEFT_FOOT] = left_foot;
+ servo_trim_[RIGHT_FOOT] = right_foot;
+
+ if (has_hands_) {
+ servo_trim_[LEFT_HAND] = left_hand;
+ servo_trim_[RIGHT_HAND] = right_hand;
+ }
+
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetTrim(servo_trim_[i]);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- BASIC MOTION FUNCTIONS -------------------------------------//
+///////////////////////////////////////////////////////////////////
+void EdaRobot::MoveServos(int time, int servo_target[]) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ final_time_ = millis() + time;
+ if (time > 10) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0);
+ }
+ }
+
+ for (int iteration = 1; millis() < final_time_; iteration++) {
+ partial_time_ = millis() + 10;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ } else {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(time));
+ }
+
+ // final adjustment to the target.
+ bool f = true;
+ int adjustment_count = 0;
+ while (f && adjustment_count < 10) {
+ f = false;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) {
+ f = true;
+ break;
+ }
+ }
+ if (f) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ adjustment_count++;
+ }
+ };
+}
+
+void EdaRobot::MoveSingle(int position, int servo_number) {
+ if (position > 180)
+ position = 90;
+ if (position < 0)
+ position = 90;
+
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) {
+ servo_[servo_number].SetPosition(position);
+ }
+}
+
+void EdaRobot::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle = 1) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetO(offset[i]);
+ servo_[i].SetA(amplitude[i]);
+ servo_[i].SetT(period);
+ servo_[i].SetPh(phase_diff[i]);
+ }
+ }
+
+ double ref = millis();
+ double end_time = period * cycle + ref;
+
+ while (millis() < end_time) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Refresh();
+ }
+ }
+ vTaskDelay(5);
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+void EdaRobot::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps = 1.0) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ int cycles = (int)steps;
+
+ //-- Execute complete cycles
+ if (cycles >= 1)
+ for (int i = 0; i < cycles; i++)
+ OscillateServos(amplitude, offset, period, phase_diff);
+
+ //-- Execute the final not complete cycle
+ OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles);
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+///////////////////////////////////////////////////////////////////
+//-- HOME = EdaRobot at rest position -------------------------------//
+///////////////////////////////////////////////////////////////////
+void EdaRobot::Home(bool hands_down) {
+ if (is_edarobot_resting_ == false) { // Go to rest position only if necessary
+ // 涓烘墍鏈夎埖鏈哄噯澶囧垵濮嬩綅缃��
+ int homes[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (i == LEFT_HAND || i == RIGHT_HAND) {
+ if (hands_down) {
+ // 濡傛灉闇�瑕佸浣嶆墜閮紝璁剧疆涓洪粯璁ゅ��
+ if (i == LEFT_HAND) {
+ homes[i] = HAND_HOME_POSITION;
+ } else { // RIGHT_HAND
+ homes[i] = 180 - HAND_HOME_POSITION; // 鍙虫墜闀滃儚浣嶇疆
+ }
+ } else {
+ // 濡傛灉涓嶉渶瑕佸浣嶆墜閮紝淇濇寔褰撳墠浣嶇疆
+ homes[i] = servo_[i].GetPosition();
+ }
+ } else {
+ // 鑵块儴鍜岃剼閮ㄨ埖鏈哄缁堝浣�
+ homes[i] = 90;
+ }
+ }
+
+ MoveServos(500, homes);
+ is_edarobot_resting_ = true;
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(200));
+}
+
+bool EdaRobot::GetRestState() {
+ return is_edarobot_resting_;
+}
+
+void EdaRobot::SetRestState(bool state) {
+ is_edarobot_resting_ = state;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- PREDETERMINED MOTION SEQUENCES -----------------------------//
+///////////////////////////////////////////////////////////////////
+//-- EdaRobot movement: Jump
+//-- Parameters:
+//-- steps: Number of steps
+//-- T: Period
+//---------------------------------------------------------
+void EdaRobot::Jump(float steps, int period) {
+ int up[SERVO_COUNT] = {90, 90, 150, 30, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ MoveServos(period, up);
+ int down[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ MoveServos(period, down);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Walking (forward or backward)
+//-- Parameters:
+//-- * steps: Number of steps
+//-- * T : Period
+//-- * Dir: Direction: FORWARD / BACKWARD
+//-- * amount: 鎵嬮儴鎽嗗姩骞呭害, 0琛ㄧず涓嶆憜鍔�
+//---------------------------------------------------------
+void EdaRobot::Walk(float steps, int period, int dir, int amount) {
+ //-- Oscillator parameters for walking
+ //-- Hip sevos are in phase
+ //-- Feet servos are in phase
+ //-- Hip and feet are 90 degrees out of phase
+ //-- -90 : Walk forward
+ //-- 90 : Walk backward
+ //-- Feet servos also have the same offset (for tiptoe a little bit)
+ int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(dir * -90), DEG2RAD(dir * -90), 0, 0};
+
+ // 濡傛灉amount>0涓旀湁鎵嬮儴鑸垫満锛岃缃墜閮ㄦ尟骞呭拰鐩镐綅
+ if (amount > 0 && has_hands_) {
+ // 鎵嬭噦鎸箙浣跨敤浼犲叆鐨刟mount鍙傛暟
+ A[LEFT_HAND] = amount;
+ A[RIGHT_HAND] = amount;
+
+ // 宸︽墜涓庡彸鑵垮悓鐩革紝鍙虫墜涓庡乏鑵垮悓鐩革紝浣垮緱鏈哄櫒浜鸿蛋璺椂鎵嬭噦鑷劧鎽嗗姩
+ phase_diff[LEFT_HAND] = phase_diff[RIGHT_LEG]; // 宸︽墜涓庡彸鑵垮悓鐩�
+ phase_diff[RIGHT_HAND] = phase_diff[LEFT_LEG]; // 鍙虫墜涓庡乏鑵垮悓鐩�
+ } else {
+ A[LEFT_HAND] = 0;
+ A[RIGHT_HAND] = 0;
+ }
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Turning (left or right)
+//-- Parameters:
+//-- * Steps: Number of steps
+//-- * T: Period
+//-- * Dir: Direction: LEFT / RIGHT
+//-- * amount: 鎵嬮儴鎽嗗姩骞呭害, 0琛ㄧず涓嶆憜鍔�
+//---------------------------------------------------------
+void EdaRobot::Turn(float steps, int period, int dir, int amount) {
+ //-- Same coordination than for walking (see EdaRobot::walk)
+ //-- The Amplitudes of the hip's oscillators are not igual
+ //-- When the right hip servo amplitude is higher, the steps taken by
+ //-- the right leg are bigger than the left. So, the robot describes an
+ //-- left arc
+ int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(-90), 0, 0};
+
+ if (dir == LEFT) {
+ A[0] = 30; //-- Left hip servo
+ A[1] = 0; //-- Right hip servo
+ } else {
+ A[0] = 0;
+ A[1] = 30;
+ }
+
+ // 濡傛灉amount>0涓旀湁鎵嬮儴鑸垫満锛岃缃墜閮ㄦ尟骞呭拰鐩镐綅
+ if (amount > 0 && has_hands_) {
+ // 鎵嬭噦鎸箙浣跨敤浼犲叆鐨刟mount鍙傛暟
+ A[LEFT_HAND] = amount;
+ A[RIGHT_HAND] = amount;
+
+ // 杞悜鏃舵墜鑷傛憜鍔ㄧ浉浣嶏細宸︽墜涓庡乏鑵垮悓鐩革紝鍙虫墜涓庡彸鑵垮悓鐩革紝澧炲己杞悜鏁堟灉
+ phase_diff[LEFT_HAND] = phase_diff[LEFT_LEG]; // 宸︽墜涓庡乏鑵垮悓鐩�
+ phase_diff[RIGHT_HAND] = phase_diff[RIGHT_LEG]; // 鍙虫墜涓庡彸鑵垮悓鐩�
+ } else {
+ A[LEFT_HAND] = 0;
+ A[RIGHT_HAND] = 0;
+ }
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Lateral bend
+//-- Parameters:
+//-- steps: Number of bends
+//-- T: Period of one bend
+//-- dir: RIGHT=Right bend LEFT=Left bend
+//---------------------------------------------------------
+void EdaRobot::Bend(int steps, int period, int dir) {
+ // Parameters of all the movements. Default: Left bend
+ int bend1[SERVO_COUNT] = {90, 90, 62, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int bend2[SERVO_COUNT] = {90, 90, 62, 105, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ // Time of one bend, constrained in order to avoid movements too fast.
+ // T=max(T, 600);
+ // Changes in the parameters if right direction is chosen
+ if (dir == -1) {
+ bend1[2] = 180 - 35;
+ bend1[3] = 180 - 60; // Not 65. EdaRobot is unbalanced
+ bend2[2] = 180 - 105;
+ bend2[3] = 180 - 60;
+ }
+
+ // Time of the bend movement. Fixed parameter to avoid falls
+ int T2 = 800;
+
+ // Bend movement
+ for (int i = 0; i < steps; i++) {
+ MoveServos(T2 / 2, bend1);
+ MoveServos(T2 / 2, bend2);
+ vTaskDelay(pdMS_TO_TICKS(period * 0.8));
+ MoveServos(500, homes);
+ }
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Shake a leg
+//-- Parameters:
+//-- steps: Number of shakes
+//-- T: Period of one shake
+//-- dir: RIGHT=Right leg LEFT=Left leg
+//---------------------------------------------------------
+void EdaRobot::ShakeLeg(int steps, int period, int dir) {
+ // This variable change the amount of shakes
+ int numberLegMoves = 2;
+
+ // Parameters of all the movements. Default: Right leg
+ int shake_leg1[SERVO_COUNT] = {90, 90, 58, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int shake_leg2[SERVO_COUNT] = {90, 90, 58, 120, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int shake_leg3[SERVO_COUNT] = {90, 90, 58, 60, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ // Changes in the parameters if left leg is chosen
+ if (dir == 1) {
+ shake_leg1[2] = 180 - 35;
+ shake_leg1[3] = 180 - 58;
+ shake_leg2[2] = 180 - 120;
+ shake_leg2[3] = 180 - 58;
+ shake_leg3[2] = 180 - 60;
+ shake_leg3[3] = 180 - 58;
+ }
+
+ // Time of the bend movement. Fixed parameter to avoid falls
+ int T2 = 1000;
+ // Time of one shake, constrained in order to avoid movements too fast.
+ period = period - T2;
+ period = std::max(period, 200 * numberLegMoves);
+
+ for (int j = 0; j < steps; j++) {
+ // Bend movement
+ MoveServos(T2 / 2, shake_leg1);
+ MoveServos(T2 / 2, shake_leg2);
+
+ // Shake movement
+ for (int i = 0; i < numberLegMoves; i++) {
+ MoveServos(period / (2 * numberLegMoves), shake_leg3);
+ MoveServos(period / (2 * numberLegMoves), shake_leg2);
+ }
+ MoveServos(500, homes); // Return to home position
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(period));
+}
+
+//---------------------------------------------------------
+//-- EdaRobot movement: up & down
+//-- Parameters:
+//-- * steps: Number of jumps
+//-- * T: Period
+//-- * h: Jump height: SMALL / MEDIUM / BIG
+//-- (or a number in degrees 0 - 90)
+//---------------------------------------------------------
+void EdaRobot::UpDown(float steps, int period, int height) {
+ //-- Both feet are 180 degrees out of phase
+ //-- Feet amplitude and offset are the same
+ //-- Initial phase for the right foot is -90, so that it starts
+ //-- in one extreme position (not in the middle)
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(90), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot movement: swinging side to side
+//-- Parameters:
+//-- steps: Number of steps
+//-- T : Period
+//-- h : Amount of swing (from 0 to 50 aprox)
+//---------------------------------------------------------
+void EdaRobot::Swing(float steps, int period, int height) {
+ //-- Both feets are in phase. The offset is half the amplitude
+ //-- It causes the robot to swing from side to side
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height / 2, -height / 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(0), DEG2RAD(0), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot movement: swinging side to side without touching the floor with the heel
+//-- Parameters:
+//-- steps: Number of steps
+//-- T : Period
+//-- h : Amount of swing (from 0 to 50 aprox)
+//---------------------------------------------------------
+void EdaRobot::TiptoeSwing(float steps, int period, int height) {
+ //-- Both feets are in phase. The offset is not half the amplitude in order to tiptoe
+ //-- It causes the robot to swing from side to side
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Jitter
+//-- Parameters:
+//-- steps: Number of jitters
+//-- T: Period of one jitter
+//-- h: height (Values between 5 - 25)
+//---------------------------------------------------------
+void EdaRobot::Jitter(float steps, int period, int height) {
+ //-- Both feet are 180 degrees out of phase
+ //-- Feet amplitude and offset are the same
+ //-- Initial phase for the right foot is -90, so that it starts
+ //-- in one extreme position (not in the middle)
+ //-- h is constrained to avoid hit the feets
+ height = std::min(25, height);
+ int A[SERVO_COUNT] = {height, height, 0, 0, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, 0, 0, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), 0, 0, 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Ascending & turn (Jitter while up&down)
+//-- Parameters:
+//-- steps: Number of bends
+//-- T: Period of one bend
+//-- h: height (Values between 5 - 15)
+//---------------------------------------------------------
+void EdaRobot::AscendingTurn(float steps, int period, int height) {
+ //-- Both feet and legs are 180 degrees out of phase
+ //-- Initial phase for the right foot is -90, so that it starts
+ //-- in one extreme position (not in the middle)
+ //-- h is constrained to avoid hit the feets
+ height = std::min(13, height);
+ int A[SERVO_COUNT] = {height, height, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height + 4, -height + 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), DEG2RAD(-90), DEG2RAD(90), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Moonwalker. EdaRobot moves like Michael Jackson
+//-- Parameters:
+//-- Steps: Number of steps
+//-- T: Period
+//-- h: Height. Typical valures between 15 and 40
+//-- dir: Direction: LEFT / RIGHT
+//---------------------------------------------------------
+void EdaRobot::Moonwalker(float steps, int period, int height, int dir) {
+ //-- This motion is similar to that of the caterpillar robots: A travelling
+ //-- wave moving from one side to another
+ //-- The two EdaRobot's feet are equivalent to a minimal configuration. It is known
+ //-- that 2 servos can move like a worm if they are 120 degrees out of phase
+ //-- In the example of EdaRobot, the two feet are mirrored so that we have:
+ //-- 180 - 120 = 60 degrees. The actual phase difference given to the oscillators
+ //-- is 60 degrees.
+ //-- Both amplitudes are equal. The offset is half the amplitud plus a little bit of
+ //- offset so that the robot tiptoe lightly
+
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height / 2 + 2, -height / 2 - 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int phi = -dir * 90;
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(phi), DEG2RAD(-60 * dir + phi), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//----------------------------------------------------------
+//-- EdaRobot gait: Crusaito. A mixture between moonwalker and walk
+//-- Parameters:
+//-- steps: Number of steps
+//-- T: Period
+//-- h: height (Values between 20 - 50)
+//-- dir: Direction: LEFT / RIGHT
+//-----------------------------------------------------------
+void EdaRobot::Crusaito(float steps, int period, int height, int dir) {
+ int A[SERVO_COUNT] = {25, 25, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height / 2 + 4, -height / 2 - 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {90, 90, DEG2RAD(0), DEG2RAD(-60 * dir), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- EdaRobot gait: Flapping
+//-- Parameters:
+//-- steps: Number of steps
+//-- T: Period
+//-- h: height (Values between 10 - 30)
+//-- dir: direction: FOREWARD, BACKWARD
+//---------------------------------------------------------
+void EdaRobot::Flapping(float steps, int period, int height, int dir) {
+ int A[SERVO_COUNT] = {12, 12, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height - 10, -height + 10, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {
+ DEG2RAD(0), DEG2RAD(180), DEG2RAD(-90 * dir), DEG2RAD(90 * dir), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 涓炬墜
+//-- Parameters:
+//-- period: 鍔ㄤ綔鏃堕棿
+//-- dir: 鏂瑰悜 1=宸︽墜, -1=鍙虫墜, 0=鍙屾墜
+//---------------------------------------------------------
+void EdaRobot::HandsUp(int period, int dir) {
+ if (!has_hands_) {
+ return;
+ }
+
+ int initial[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ if (dir == 0) {
+ target[LEFT_HAND] = 170;
+ target[RIGHT_HAND] = 10;
+ } else if (dir == 1) {
+ target[LEFT_HAND] = 170;
+ target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition();
+ } else if (dir == -1) {
+ target[RIGHT_HAND] = 10;
+ target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition();
+ }
+
+ MoveServos(period, target);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鍙屾墜鏀句笅
+//-- Parameters:
+//-- period: 鍔ㄤ綔鏃堕棿
+//-- dir: 鏂瑰悜 1=宸︽墜, -1=鍙虫墜, 0=鍙屾墜
+//---------------------------------------------------------
+void EdaRobot::HandsDown(int period, int dir) {
+ if (!has_hands_) {
+ return;
+ }
+
+ int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ if (dir == 1) {
+ target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition();
+ } else if (dir == -1) {
+ target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition();
+ }
+
+ MoveServos(period, target);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鎸ユ墜
+//-- Parameters:
+//-- period: 鍔ㄤ綔鍛ㄦ湡
+//-- dir: 鏂瑰悜 LEFT/RIGHT/BOTH
+//---------------------------------------------------------
+void EdaRobot::HandWave(int period, int dir) {
+ if (!has_hands_) {
+ return;
+ }
+
+ if (dir == BOTH) {
+ HandWaveBoth(period);
+ return;
+ }
+
+ int servo_index = (dir == LEFT) ? LEFT_HAND : RIGHT_HAND;
+
+ int current_positions[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_positions[i] = servo_[i].GetPosition();
+ } else {
+ current_positions[i] = 90;
+ }
+ }
+
+ int position;
+ if (servo_index == LEFT_HAND) {
+ position = 170;
+ } else {
+ position = 10;
+ }
+
+ current_positions[servo_index] = position;
+ MoveServos(300, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(300));
+
+ // 宸﹀彸鎽嗗姩5娆�
+ for (int i = 0; i < 5; i++) {
+ if (servo_index == LEFT_HAND) {
+ current_positions[servo_index] = position - 30;
+ MoveServos(period / 10, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 10));
+ current_positions[servo_index] = position + 30;
+ MoveServos(period / 10, current_positions);
+ } else {
+ current_positions[servo_index] = position + 30;
+ MoveServos(period / 10, current_positions);
+ vTaskDelay(pdMS_TO_TICKS(period / 10));
+ current_positions[servo_index] = position - 30;
+ MoveServos(period / 10, current_positions);
+ }
+ vTaskDelay(pdMS_TO_TICKS(period / 10));
+ }
+
+ if (servo_index == LEFT_HAND) {
+ current_positions[servo_index] = HAND_HOME_POSITION;
+ } else {
+ current_positions[servo_index] = 180 - HAND_HOME_POSITION;
+ }
+ MoveServos(300, current_positions);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鍙屾墜鍚屾椂鎸ユ墜
+//-- Parameters:
+//-- period: 鍔ㄤ綔鍛ㄦ湡
+//---------------------------------------------------------
+void EdaRobot::HandWaveBoth(int period) {
+ if (!has_hands_) {
+ return;
+ }
+
+ int current_positions[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ current_positions[i] = servo_[i].GetPosition();
+ } else {
+ current_positions[i] = 90;
+ }
+ }
+
+ int left_position = 170;
+ int right_position = 10;
+
+ current_positions[LEFT_HAND] = left_position;
+ current_positions[RIGHT_HAND] = right_position;
+ MoveServos(300, current_positions);
+
+ // 宸﹀彸鎽嗗姩5娆�
+ for (int i = 0; i < 5; i++) {
+ // 娉㈡氮鍚戝乏
+ current_positions[LEFT_HAND] = left_position - 30;
+ current_positions[RIGHT_HAND] = right_position + 30;
+ MoveServos(period / 10, current_positions);
+
+ // 娉㈡氮鍚戝彸
+ current_positions[LEFT_HAND] = left_position + 30;
+ current_positions[RIGHT_HAND] = right_position - 30;
+ MoveServos(period / 10, current_positions);
+ }
+
+ current_positions[LEFT_HAND] = HAND_HOME_POSITION;
+ current_positions[RIGHT_HAND] = 180 - HAND_HOME_POSITION;
+ MoveServos(300, current_positions);
+}
+
+void EdaRobot::EnableServoLimit(int diff_limit) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetLimiter(diff_limit);
+ }
+ }
+}
+
+void EdaRobot::DisableServoLimit() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].DisableLimiter();
+ }
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.h
new file mode 100644
index 0000000..bbc9668
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/eda_super_bear_movements.h
@@ -0,0 +1,105 @@
+#ifndef __EDA_ROBOT_MAX_MOVEMENTS_H__
+#define __EDA_ROBOT_MAX_MOVEMENTS_H__
+
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "oscillator.h"
+
+//-- Constants
+#define FORWARD 1
+#define BACKWARD -1
+#define LEFT 1
+#define RIGHT -1
+#define BOTH 0
+#define SMALL 5
+#define MEDIUM 15
+#define BIG 30
+
+// -- Servo delta limit default. degree / sec
+#define SERVO_LIMIT_DEFAULT 240
+
+// -- Servo indexes for easy access
+#define LEFT_LEG 0
+#define RIGHT_LEG 1
+#define LEFT_FOOT 2
+#define RIGHT_FOOT 3
+#define LEFT_HAND 4
+#define RIGHT_HAND 5
+#define SERVO_COUNT 6
+
+class EdaRobot {
+public:
+ EdaRobot();
+ ~EdaRobot();
+
+ //-- EdaRobot initialization
+ void Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = -1,
+ int right_hand = -1);
+ //-- Attach & detach functions
+ void AttachServos();
+ void DetachServos();
+
+ //-- Oscillator Trims
+ void SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = 0,
+ int right_hand = 0);
+
+ //-- Predetermined Motion Functions
+ void MoveServos(int time, int servo_target[]);
+ void MoveSingle(int position, int servo_number);
+ void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle);
+
+ //-- HOME = EdaRobot at rest position
+ void Home(bool hands_down = true);
+ bool GetRestState();
+ void SetRestState(bool state);
+
+ //-- Predetermined Motion Functions
+ void Jump(float steps = 1, int period = 2000);
+
+ void Walk(float steps = 4, int period = 1000, int dir = FORWARD, int amount = 0);
+ void Turn(float steps = 4, int period = 2000, int dir = LEFT, int amount = 0);
+ void Bend(int steps = 1, int period = 1400, int dir = LEFT);
+ void ShakeLeg(int steps = 1, int period = 2000, int dir = RIGHT);
+
+ void UpDown(float steps = 1, int period = 1000, int height = 20);
+ void Swing(float steps = 1, int period = 1000, int height = 20);
+ void TiptoeSwing(float steps = 1, int period = 900, int height = 20);
+ void Jitter(float steps = 1, int period = 500, int height = 20);
+ void AscendingTurn(float steps = 1, int period = 900, int height = 20);
+
+ void Moonwalker(float steps = 1, int period = 900, int height = 20, int dir = LEFT);
+ void Crusaito(float steps = 1, int period = 900, int height = 20, int dir = FORWARD);
+ void Flapping(float steps = 1, int period = 1000, int height = 20, int dir = FORWARD);
+
+ // -- 鎵嬮儴鍔ㄤ綔
+ void HandsUp(int period = 1000, int dir = 0); // 鍙屾墜涓捐捣
+ void HandsDown(int period = 1000, int dir = 0); // 鍙屾墜鏀句笅
+ void HandWave(int period = 1000, int dir = LEFT); // 鎸ユ墜
+ void HandWaveBoth(int period = 1000); // 鍙屾墜鍚屾椂鎸ユ墜
+
+ // -- Servo limiter
+ void EnableServoLimit(int speed_limit_degree_per_sec = SERVO_LIMIT_DEFAULT);
+ void DisableServoLimit();
+
+private:
+ Oscillator servo_[SERVO_COUNT];
+
+ int servo_pins_[SERVO_COUNT];
+ int servo_trim_[SERVO_COUNT];
+
+ unsigned long final_time_;
+ unsigned long partial_time_;
+ float increment_[SERVO_COUNT];
+
+ bool is_edarobot_resting_;
+ bool has_hands_; // 鏄惁鏈夋墜閮ㄨ埖鏈�
+
+ void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps);
+};
+
+#endif // __EDA_ROBOT_MAX_MOVEMENTS_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.cc
new file mode 100644
index 0000000..8dad82c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.cc
@@ -0,0 +1,161 @@
+//--------------------------------------------------------------
+//-- Oscillator.pde
+//-- Generate sinusoidal oscillations in the servos
+//--------------------------------------------------------------
+//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
+//-- (c) txp666 for esp32, 202503
+//-- GPL license
+//--------------------------------------------------------------
+#include "oscillator.h"
+
+#include <driver/ledc.h>
+#include <esp_timer.h>
+
+#include <algorithm>
+#include <cmath>
+
+static const char* TAG = "Oscillator";
+
+extern unsigned long IRAM_ATTR millis();
+
+static ledc_channel_t next_free_channel = LEDC_CHANNEL_0;
+
+Oscillator::Oscillator(int trim) {
+ trim_ = trim;
+ diff_limit_ = 0;
+ is_attached_ = false;
+
+ sampling_period_ = 30;
+ period_ = 2000;
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+
+ amplitude_ = 45;
+ phase_ = 0;
+ phase0_ = 0;
+ offset_ = 0;
+ stop_ = false;
+ rev_ = false;
+
+ pos_ = 90;
+ previous_millis_ = 0;
+}
+
+Oscillator::~Oscillator() {
+ Detach();
+}
+
+uint32_t Oscillator::AngleToCompare(int angle) {
+ return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) /
+ (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) +
+ SERVO_MIN_PULSEWIDTH_US;
+}
+
+bool Oscillator::NextSample() {
+ current_millis_ = millis();
+
+ if (current_millis_ - previous_millis_ > sampling_period_) {
+ previous_millis_ = current_millis_;
+ return true;
+ }
+
+ return false;
+}
+
+void Oscillator::Attach(int pin, bool rev) {
+ if (is_attached_) {
+ Detach();
+ }
+
+ pin_ = pin;
+ rev_ = rev;
+
+ ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE,
+ .duty_resolution = LEDC_TIMER_13_BIT,
+ .timer_num = LEDC_TIMER_1,
+ .freq_hz = 50,
+ .clk_cfg = LEDC_AUTO_CLK};
+ ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
+
+ static int last_channel = 0;
+ last_channel = (last_channel + 1) % 7 + 1;
+ ledc_channel_ = (ledc_channel_t)last_channel;
+
+ ledc_channel_config_t ledc_channel = {.gpio_num = pin_,
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .channel = ledc_channel_,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = LEDC_TIMER_1,
+ .duty = 0,
+ .hpoint = 0};
+ ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
+
+ ledc_speed_mode_ = LEDC_LOW_SPEED_MODE;
+
+ // pos_ = 90;
+ // Write(pos_);
+ previous_servo_command_millis_ = millis();
+
+ is_attached_ = true;
+}
+
+void Oscillator::Detach() {
+ if (!is_attached_)
+ return;
+
+ ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0));
+
+ is_attached_ = false;
+}
+
+void Oscillator::SetT(unsigned int T) {
+ period_ = T;
+
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+}
+
+void Oscillator::SetPosition(int position) {
+ Write(position);
+}
+
+void Oscillator::Refresh() {
+ if (NextSample()) {
+ if (!stop_) {
+ int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_);
+ if (rev_)
+ pos = -pos;
+ Write(pos + 90);
+ }
+
+ phase_ = phase_ + inc_;
+ }
+}
+
+void Oscillator::Write(int position) {
+ if (!is_attached_)
+ return;
+
+ long currentMillis = millis();
+ if (diff_limit_ > 0) {
+ int limit = std::max(
+ 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000);
+ if (abs(position - pos_) > limit) {
+ pos_ += position < pos_ ? -limit : limit;
+ } else {
+ pos_ = position;
+ }
+ } else {
+ pos_ = position;
+ }
+ previous_servo_command_millis_ = currentMillis;
+
+ int angle = pos_ + trim_;
+
+ angle = std::min(std::max(angle, 0), 180);
+
+ uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0);
+
+ ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty));
+ ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_));
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.h
new file mode 100644
index 0000000..c37a69a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-super-bear/oscillator.h
@@ -0,0 +1,91 @@
+//--------------------------------------------------------------
+//-- Oscillator.pde
+//-- Generate sinusoidal oscillations in the servos
+//--------------------------------------------------------------
+//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
+//-- (c) txp666 for esp32, 202503
+//-- GPL license
+//--------------------------------------------------------------
+#ifndef __OSCILLATOR_H__
+#define __OSCILLATOR_H__
+
+#include "driver/ledc.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#define M_PI 3.14159265358979323846
+
+#ifndef DEG2RAD
+#define DEG2RAD(g) ((g) * M_PI) / 180
+#endif
+
+#define SERVO_MIN_PULSEWIDTH_US 500 // 鏈�灏忚剦瀹斤紙寰锛�
+#define SERVO_MAX_PULSEWIDTH_US 2500 // 鏈�澶ц剦瀹斤紙寰锛�
+#define SERVO_MIN_DEGREE -90 // 鏈�灏忚搴�
+#define SERVO_MAX_DEGREE 90 // 鏈�澶ц搴�
+#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick
+#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms
+
+class Oscillator {
+public:
+ Oscillator(int trim = 0);
+ ~Oscillator();
+ void Attach(int pin, bool rev = false);
+ void Detach();
+
+ void SetA(unsigned int amplitude) { amplitude_ = amplitude; };
+ void SetO(int offset) { offset_ = offset; };
+ void SetPh(double Ph) { phase0_ = Ph; };
+ void SetT(unsigned int period);
+ void SetTrim(int trim) { trim_ = trim; };
+ void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; };
+ void DisableLimiter() { diff_limit_ = 0; };
+ int GetTrim() { return trim_; };
+ void SetPosition(int position);
+ void Stop() { stop_ = true; };
+ void Play() { stop_ = false; };
+ void Reset() { phase_ = 0; };
+ void Refresh();
+ int GetPosition() { return pos_; }
+
+private:
+ bool NextSample();
+ void Write(int position);
+ uint32_t AngleToCompare(int angle);
+
+private:
+ bool is_attached_;
+
+ //-- Oscillators parameters
+ unsigned int amplitude_; //-- Amplitude (degrees)
+ int offset_; //-- Offset (degrees)
+ unsigned int period_; //-- Period (miliseconds)
+ double phase0_; //-- Phase (radians)
+
+ //-- Internal variables
+ int pos_; //-- Current servo pos
+ int pin_; //-- Pin where the servo is connected
+ int trim_; //-- Calibration offset
+ double phase_; //-- Current phase
+ double inc_; //-- Increment of phase
+ double number_samples_; //-- Number of samples
+ unsigned int sampling_period_; //-- sampling period (ms)
+
+ long previous_millis_;
+ long current_millis_;
+
+ //-- Oscillation mode. If true, the servo is stopped
+ bool stop_;
+
+ //-- Reverse mode
+ bool rev_;
+
+ int diff_limit_;
+ long previous_servo_command_millis_;
+
+ ledc_channel_t ledc_channel_;
+ ledc_mode_t ledc_speed_mode_;
+};
+
+#endif // __OSCILLATOR_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/README.md b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/README.md
new file mode 100644
index 0000000..35dbf23
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/README.md
@@ -0,0 +1,13 @@
+## EDA-TV-Pro 灏忕數瑙�
+
+### 椤圭洰鏂囨。
+- [EDA-TV-Pro 椤圭洰鏂囨。](https://wiki.lceda.cn/zh-hans/course-projects/smart-internet/tv-pro/tv-pro-introduce.html)
+
+### 缂栬瘧閰嶇疆
+璇ラ」鐩噰鐢ㄧ殑寮�鍙戞澘Flash瀹归噺涓�8MB锛岀紪璇戞椂椤婚厤缃細
+
+```
+Partition Table --->
+ Partition Table (Custom partition table CSV) --->
+ (partitions/v2/8m.csv) Custom partition CSV file
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.h b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.h
new file mode 100644
index 0000000..5b8ec15
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.h
@@ -0,0 +1,35 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+#include <driver/gpio.h>
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_METHOD_SIMPLEX
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_40
+#define ASR_BUTTON_GPIO GPIO_NUM_21
+#define BUILTIN_LED_GPIO GPIO_NUM_2
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38
+#define DISPLAY_CS_PIN GPIO_NUM_NC
+#define DISPLAY_MOSI_PIN GPIO_NUM_11
+#define DISPLAY_CLK_PIN GPIO_NUM_12
+#define DISPLAY_DC_PIN GPIO_NUM_47
+#define DISPLAY_RST_PIN GPIO_NUM_48
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 2
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.json b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.json
new file mode 100644
index 0000000..431867f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "lceda-course-examples",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "eda-tv-pro",
+ "sdkconfig_append": [
+ "LCD_ST7789_240X240_7PIN=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/eda-tv-pro.cc b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/eda-tv-pro.cc
new file mode 100644
index 0000000..63a94d2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lceda-course-examples/eda-tv-pro/eda-tv-pro.cc
@@ -0,0 +1,122 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#define TAG "eda_tv_pro"
+
+class eda_tv_pro : public WifiBoard {
+private:
+
+ Button boot_button_;
+ LcdDisplay* display_;
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ eda_tv_pro() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef AUDIO_I2S_METHOD_SIMPLEX
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+#else
+ static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN);
+#endif
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+};
+
+DECLARE_BOARD(eda_tv_pro);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/README.md b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/README.md
new file mode 100644
index 0000000..ec6ec88
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/README.md
@@ -0,0 +1,11 @@
+## 绔嬪垱路瀹炴垬娲綞SP32-C3寮�鍙戞澘
+
+1銆佸紑鍙戞澘璧勬枡锛歨ttps://wiki.lckfb.com/zh-hans/szpi-esp32c3
+
+2銆佽寮�鍙戞澘 flash 澶у皬涓� 8MB锛岀紪璇戞椂娉ㄦ剰閫夋嫨鍚堥�傜殑鍒嗗尯琛細
+
+```
+Partition Table --->
+ Partition Table (Custom partition table CSV) --->
+ (partitions/v2/8m.csv) Custom partition CSV file
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.h b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.h
new file mode 100644
index 0000000..548ccb4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.h
@@ -0,0 +1,45 @@
+#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_USE_PCA9557
+#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 AUDIO_CODEC_ES7210_ADDR 0x82
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#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_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.json b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.json
new file mode 100644
index 0000000..6000c33
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "lichuang-c3-dev",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc
new file mode 100644
index 0000000..f728ed8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc
@@ -0,0 +1,129 @@
+#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 "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "LichuangC3DevBoard"
+
+class LichuangC3DevBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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);
+ }
+
+public:
+ LichuangC3DevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ 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(LichuangC3DevBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.h b/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.h
new file mode 100644
index 0000000..c5d2bf2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.h
@@ -0,0 +1,62 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_13
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
+
+#define AUDIO_CODEC_USE_PCA9557
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR 0x82
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#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_42
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_5
+#define CAMERA_PIN_SIOD GPIO_NUM_1
+#define CAMERA_PIN_SIOC GPIO_NUM_2
+
+#define CAMERA_PIN_D7 GPIO_NUM_9
+#define CAMERA_PIN_D6 GPIO_NUM_4
+#define CAMERA_PIN_D5 GPIO_NUM_6
+#define CAMERA_PIN_D4 GPIO_NUM_15
+#define CAMERA_PIN_D3 GPIO_NUM_17
+#define CAMERA_PIN_D2 GPIO_NUM_8
+#define CAMERA_PIN_D1 GPIO_NUM_18
+#define CAMERA_PIN_D0 GPIO_NUM_16
+#define CAMERA_PIN_VSYNC GPIO_NUM_3
+#define CAMERA_PIN_HREF GPIO_NUM_46
+#define CAMERA_PIN_PCLK GPIO_NUM_7
+
+#define XCLK_FREQ_HZ 20000000
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.json b/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.json
new file mode 100644
index 0000000..e2a7090
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "lichuang-dev",
+ "sdkconfig_append": [
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/lichuang_dev_board.cc b/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/lichuang_dev_board.cc
new file mode 100644
index 0000000..f3cf78a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lichuang-dev/lichuang_dev_board.cc
@@ -0,0 +1,296 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "display/emote_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "esp32_camera.h"
+#include "mcp_server.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_touch_ft5x06.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "LichuangDevBoard"
+
+class Pca9557 : public I2cDevice {
+public:
+ Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x01, 0x03);
+ WriteReg(0x03, 0xf8);
+ }
+
+ void SetOutputState(uint8_t bit, uint8_t level) {
+ uint8_t data = ReadReg(0x01);
+ data = (data & ~(1 << bit)) | (level << bit);
+ WriteReg(0x01, data);
+ }
+};
+
+class CustomAudioCodec : public BoxAudioCodec {
+private:
+ Pca9557* pca9557_;
+
+public:
+ CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557)
+ : BoxAudioCodec(i2c_bus,
+ 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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE),
+ pca9557_(pca9557) {
+ }
+
+ virtual void EnableOutput(bool enable) override {
+ BoxAudioCodec::EnableOutput(enable);
+ if (enable) {
+ pca9557_->SetOutputState(1, 1);
+ } else {
+ pca9557_->SetOutputState(1, 0);
+ }
+ }
+};
+
+class LichuangDevBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ i2c_master_dev_handle_t pca9557_handle_;
+ Button boot_button_;
+ Display* display_;
+ Pca9557* pca9557_;
+ Esp32Camera* camera_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+
+ // Initialize PCA9557
+ pca9557_ = new Pca9557(i2c_bus_, 0x19);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_40;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_41;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_NC;
+ io_config.dc_gpio_num = GPIO_NUM_39;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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);
+ pca9557_->SetOutputState(0, 0);
+
+ 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);
+ esp_lcd_panel_disp_on_off(panel, true);
+
+#if CONFIG_USE_EMOTE_MESSAGE_STYLE
+ display_ = new emote::EmoteDisplay(panel, panel_io, DISPLAY_WIDTH, DISPLAY_HEIGHT);
+#else
+ 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);
+#endif
+ }
+
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_HEIGHT,
+ .y_max = DISPLAY_WIDTH,
+ .rst_gpio_num = GPIO_NUM_NC, // Shared with LCD reset
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 1,
+ .mirror_x = 1,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400000;
+
+ esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle);
+ esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp);
+ assert(tp);
+
+ /* Add touch input (for selected screen) */
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+
+ if(touch_cfg.disp) {
+ lvgl_port_add_touch(&touch_cfg);
+ } else {
+ ESP_LOGE(TAG, "Touch display is not initialized");
+ }
+ }
+
+ void InitializeCamera() {
+ // Open camera power
+ pca9557_->SetOutputState(2, 0);
+
+ camera_config_t config = {};
+ config.ledc_channel = LEDC_CHANNEL_2;
+ config.ledc_timer = LEDC_TIMER_2;
+ config.pin_d0 = CAMERA_PIN_D0;
+ config.pin_d1 = CAMERA_PIN_D1;
+ config.pin_d2 = CAMERA_PIN_D2;
+ config.pin_d3 = CAMERA_PIN_D3;
+ config.pin_d4 = CAMERA_PIN_D4;
+ config.pin_d5 = CAMERA_PIN_D5;
+ config.pin_d6 = CAMERA_PIN_D6;
+ config.pin_d7 = CAMERA_PIN_D7;
+ config.pin_xclk = CAMERA_PIN_XCLK;
+ config.pin_pclk = CAMERA_PIN_PCLK;
+ config.pin_vsync = CAMERA_PIN_VSYNC;
+ config.pin_href = CAMERA_PIN_HREF;
+ config.pin_sccb_sda = -1;
+ config.pin_sccb_scl = CAMERA_PIN_SIOC;
+ config.sccb_i2c_port = 1;
+ config.pin_pwdn = CAMERA_PIN_PWDN;
+ config.pin_reset = CAMERA_PIN_RESET;
+ config.xclk_freq_hz = XCLK_FREQ_HZ;
+ config.pixel_format = PIXFORMAT_RGB565;
+ config.frame_size = FRAMESIZE_QVGA;
+ config.jpeg_quality = 12;
+ config.fb_count = 1;
+ config.fb_location = CAMERA_FB_IN_PSRAM;
+ config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
+
+ camera_ = new Esp32Camera(config);
+ }
+
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeCamera();
+ InitializeTools();
+
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static CustomAudioCodec audio_codec(
+ i2c_bus_,
+ pca9557_);
+ 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;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(LichuangDevBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/README.md
new file mode 100644
index 0000000..e3c3935
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/README.md
@@ -0,0 +1,47 @@
+## LILYGO T-CameraPlus-S3
+
+T-CameraPlus-S3 is an intelligent camera module developed based on the ESP32S3 chip, equipped with a 240x240 TFT display, digital microphone, speaker, independent button, power control chip, SD card module, etc. It comes with a basic UI written based on LVGL, which can achieve functions such as file management, music playback, recording, and camera projection (if the factory does not write the program, you need to manually burn the UI program named "Lvgl_UI").
+
+Official github: [T-CameraPlus-S3](https://github.com/Xinyuan-LilyGO/T-CameraPlus-S3)
+
+## Configuration
+
+**Set the compilation target to ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**Open menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**Select the board**
+
+```
+Xiaozhi Assistant -> Board Type -> LILYGO T-CameraPlus-S3_V1_0_V1_1
+Or
+Xiaozhi Assistant -> Board Type -> LILYGO T-CameraPlus-S3_V1_2
+```
+
+**Modify the psram configuration**
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Quad Mode PSRAM
+```
+
+
+**Select and set camera sensor**
+
+```
+Component config -> Espressif Camera Sensors Configurations -> Camera Sensor Configuration -> Select and Set Camera Sensor -> OV2640 -> Select default output format for DVP interface -> YUV422 240x240 25fps, DVP 8-bit, 20M input
+```
+
+
+**Build**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.h
new file mode 100644
index 0000000..0d69e69
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.h
@@ -0,0 +1,57 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include "pin_config.h"
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_MIC_I2S_GPIO_BCLK MSM261_BCLK
+#define AUDIO_MIC_I2S_GPIO_WS MSM261_WS
+#define AUDIO_MIC_I2S_GPIO_DATA MSM261_DATA
+#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
+#define AUDIO_INPUT_REFERENCE false
+#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC
+#define AUDIO_MIC_I2S_GPIO_WS MP34DT05TR_LRCLK
+#define AUDIO_MIC_I2S_GPIO_DATA MP34DT05TR_DATA
+
+#define AUDIO_MIC_SPKR_EN MP34DT05TR_MAX98357_EN
+#endif
+
+#define AUDIO_SPKR_I2S_GPIO_BCLK MAX98357A_BCLK
+#define AUDIO_SPKR_I2S_GPIO_LRCLK MAX98357A_LRCLK
+#define AUDIO_SPKR_I2S_GPIO_DATA MAX98357A_DATA
+
+#define TOUCH_I2C_SDA_PIN TP_SDA
+#define TOUCH_I2C_SCL_PIN TP_SCL
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define KEY1_BUTTON_GPIO KEY1
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH LCD_WIDTH
+#define DISPLAY_HEIGHT LCD_HEIGHT
+#define DISPLAY_MOSI LCD_MOSI
+#define DISPLAY_SCLK LCD_SCLK
+#define DISPLAY_DC LCD_DC
+#define DISPLAY_RST LCD_RST
+#define DISPLAY_CS LCD_CS
+#define DISPLAY_BL LCD_BL
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define AP1511B_GPIO AP1511B_FBC
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.json
new file mode 100644
index 0000000..a638644
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/config.json
@@ -0,0 +1,23 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "lilygo-t-cameraplus-s3",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MODE_QUAD=y",
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y"
+ ]
+ },
+ {
+ "name": "lilygo-t-cameraplus-s3_v1_2",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MODE_QUAD=y",
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h
new file mode 100644
index 0000000..3ae619a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/ir_filter_controller.h
@@ -0,0 +1,44 @@
+#ifndef __IR_FILTER_CONTROLLER_H__
+#define __IR_FILTER_CONTROLLER_H__
+
+#include "mcp_server.h"
+
+
+class IrFilterController {
+private:
+ bool enable_ = false;
+ gpio_num_t gpio_num_;
+
+public:
+ IrFilterController(gpio_num_t gpio_num) : gpio_num_(gpio_num) {
+ gpio_config_t config = {
+ .pin_bit_mask = (1ULL << gpio_num_),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&config));
+ gpio_set_level(gpio_num_, 0);
+
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.camera.get_ir_filter_state", "Get the state of the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return enable_ ? "{\"enable\": true}" : "{\"enable\": false}";
+ });
+
+ mcp_server.AddTool("self.camera.enable_ir_filter", "Enable the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ enable_ = true;
+ gpio_set_level(gpio_num_, 1);
+ return true;
+ });
+
+ mcp_server.AddTool("self.camera.disable_ir_filter", "Disable the camera's infrared filter", PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ enable_ = false;
+ gpio_set_level(gpio_num_, 0);
+ return true;
+ });
+ }
+};
+
+
+#endif // __IR_FILTER_CONTROLLER_H__
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc
new file mode 100644
index 0000000..12ac777
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc
@@ -0,0 +1,350 @@
+#include "wifi_board.h"
+#include "tcamerapluss3_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "i2c_device.h"
+#include "sy6970.h"
+#include "pin_config.h"
+#include "esp_video.h"
+#include "ir_filter_controller.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+
+#define TAG "LilygoTCameraPlusS3Board"
+
+class Cst816x : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA7);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Cst816x() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t &GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t *read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class Pmic : public Sy6970 {
+public:
+
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Sy6970(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0x14);
+ ESP_LOGI(TAG, "Get sy6970 chip ID: 0x%02X", (chip_id & 0B00111000));
+
+ WriteReg(0x00, 0B00001000); // Disable ILIM pin
+ WriteReg(0x02, 0B11011101); // Enable ADC measurement function
+ WriteReg(0x07, 0B10001101); // Disable watchdog timer feeding function
+ }
+};
+
+class LilygoTCameraPlusS3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Cst816x *cst816d_;
+ Pmic* pmic_;
+ LcdDisplay *display_;
+ Button boot_button_;
+ Button key1_button_;
+ PowerSaveTimer* power_save_timer_;
+ EspVideo* camera_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitI2c(){
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_config = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = TOUCH_I2C_SDA_PIN,
+ .scl_io_num = TOUCH_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_config, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ static void TouchpadDaemon(void *param) {
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ auto &board = (LilygoTCameraPlusS3Board&)Board::GetInstance();
+ auto touchpad = board.GetTouchpad();
+ bool was_touched = false;
+ while (1) {
+ touchpad->UpdateTouchPoint();
+ if (touchpad->GetTouchPoint().num > 0){
+ // On press
+ if (!was_touched) {
+ was_touched = true;
+ Application::GetInstance().ToggleChatState();
+ }
+ }
+ // On release
+ else if (was_touched) {
+ was_touched = false;
+ }
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+ vTaskDelete(NULL);
+ }
+
+ void InitCst816d() {
+ ESP_LOGI(TAG, "Init CST816x");
+ cst816d_ = new Cst816x(i2c_bus_, CST816_ADDRESS);
+ xTaskCreate(TouchpadDaemon, "tp", 2048, NULL, 5, NULL);
+ }
+
+ void InitSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitSy6970() {
+ ESP_LOGI(TAG, "Init Sy6970");
+ pmic_ = new Pmic(i2c_bus_, SY6970_ADDRESS);
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS;
+ io_config.dc_gpio_num = LCD_DC;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ key1_button_.OnClick([this]() {
+ if (camera_) {
+ camera_->Capture();
+ }
+ });
+ }
+
+ void InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = Y2_GPIO_NUM,
+ [1] = Y3_GPIO_NUM,
+ [2] = Y4_GPIO_NUM,
+ [3] = Y5_GPIO_NUM,
+ [4] = Y6_GPIO_NUM,
+ [5] = Y7_GPIO_NUM,
+ [6] = Y8_GPIO_NUM,
+ [7] = Y9_GPIO_NUM,
+ },
+ .vsync_io = VSYNC_GPIO_NUM,
+ .de_io = HREF_GPIO_NUM,
+ .pclk_io = PCLK_GPIO_NUM,
+ .xclk_io = XCLK_GPIO_NUM,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
+ .init_sccb = true,
+ .i2c_config = {
+ .port = 1,
+ .scl_pin = SIOC_GPIO_NUM,
+ .sda_pin = SIOD_GPIO_NUM,
+ },
+#endif
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = RESET_GPIO_NUM,
+ .pwdn_pin = PWDN_GPIO_NUM,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = XCLK_FREQ_HZ,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ camera_->SetVFlip(1);
+ camera_->SetHMirror(1);
+ }
+
+ void InitializeTools() {
+ static IrFilterController irFilter(AP1511B_GPIO);
+ }
+
+public:
+ LilygoTCameraPlusS3Board() : boot_button_(BOOT_BUTTON_GPIO), key1_button_(KEY1_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitI2c();
+ InitSy6970();
+ InitCst816d();
+ I2cDetect();
+ InitSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ InitializeCamera();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Tcamerapluss3AudioCodec audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_MIC_I2S_GPIO_BCLK,
+ AUDIO_MIC_I2S_GPIO_WS,
+ AUDIO_MIC_I2S_GPIO_DATA,
+ AUDIO_SPKR_I2S_GPIO_BCLK,
+ AUDIO_SPKR_I2S_GPIO_LRCLK,
+ AUDIO_SPKR_I2S_GPIO_DATA,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override{
+ return display_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ bool is_power_good = pmic_->IsPowerGood();
+ discharging = !charging && is_power_good;
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ Cst816x *GetTouchpad() {
+ return cst816d_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(LilygoTCameraPlusS3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/pin_config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/pin_config.h
new file mode 100644
index 0000000..82ab983
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/pin_config.h
@@ -0,0 +1,154 @@
+/*
+ * @Description: None
+ * @Author: LILYGO_L
+ * @Date: 2024-11-11 11:36:49
+ * @LastEditTime: 2025-06-03 17:37:08
+ * @License: GPL 3.0
+ */
+#pragma once
+
+#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1
+#define T_CameraPlus_S3_V1_0_V1_1
+#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
+#define T_CameraPlus_S3_V1_2
+#endif
+
+#ifdef T_CameraPlus_S3_V1_0_V1_1
+
+// SPI
+#define SPI_SCLK GPIO_NUM_36
+#define SPI_MOSI GPIO_NUM_35
+#define SPI_MISO GPIO_NUM_37
+
+// IIC
+#define IIC_SDA GPIO_NUM_1
+#define IIC_SCL GPIO_NUM_2
+
+// MSM261
+#define MSM261_BCLK GPIO_NUM_18
+#define MSM261_WS GPIO_NUM_39
+#define MSM261_DATA GPIO_NUM_40
+
+// MAX98357A
+#define MAX98357A_DATA GPIO_NUM_38
+
+// FP-133H01D
+#define LCD_CS GPIO_NUM_34
+#define LCD_RST GPIO_NUM_33
+
+// OV2640
+#define OV2640_PWDN GPIO_NUM_NC
+#define OV2640_RESET GPIO_NUM_3
+#define OV2640_VSYNC GPIO_NUM_4
+
+// CST816
+#define TP_RST GPIO_NUM_48
+
+// SY6970
+#define SY6970_INT GPIO_NUM_47
+
+#endif
+
+#ifdef T_CameraPlus_S3_V1_2
+
+// SPI
+#define SPI_SCLK GPIO_NUM_35
+#define SPI_MOSI GPIO_NUM_34
+#define SPI_MISO GPIO_NUM_48
+
+// IIC
+#define IIC_SDA GPIO_NUM_33
+#define IIC_SCL GPIO_NUM_37
+
+// MP34DT05TR
+#define MP34DT05TR_LRCLK GPIO_NUM_40
+#define MP34DT05TR_DATA GPIO_NUM_38
+
+#define MP34DT05TR_MAX98357_EN GPIO_NUM_18
+
+// MAX98357A
+#define MAX98357A_DATA GPIO_NUM_39
+
+// FP-133H01D
+#define LCD_CS GPIO_NUM_36
+#define LCD_RST GPIO_NUM_NC
+
+// OV2640
+#define OV2640_PWDN GPIO_NUM_4
+#define OV2640_RESET GPIO_NUM_NC
+#define OV2640_VSYNC GPIO_NUM_3
+
+// CST816
+#define TP_RST GPIO_NUM_NC
+
+#endif
+
+// SD
+#define SD_CS GPIO_NUM_21
+#define SD_SCLK SPI_SCLK
+#define SD_MOSI SPI_MOSI
+#define SD_MISO SPI_MISO
+
+// MAX98357A
+#define MAX98357A_BCLK GPIO_NUM_41
+#define MAX98357A_LRCLK GPIO_NUM_42
+
+// FP-133H01D
+#define LCD_WIDTH 240
+#define LCD_HEIGHT 240
+#define LCD_BL GPIO_NUM_46
+#define LCD_MOSI SPI_MOSI
+#define LCD_SCLK SPI_SCLK
+#define LCD_DC GPIO_NUM_45
+
+// SY6970
+#define SY6970_SDA IIC_SDA
+#define SY6970_SCL IIC_SCL
+#define SY6970_ADDRESS 0x6A
+
+// OV2640
+#define OV2640_XCLK GPIO_NUM_7
+#define OV2640_SDA GPIO_NUM_1
+#define OV2640_SCL GPIO_NUM_2
+#define OV2640_D9 GPIO_NUM_6
+#define OV2640_D8 GPIO_NUM_8
+#define OV2640_D7 GPIO_NUM_9
+#define OV2640_D6 GPIO_NUM_11
+#define OV2640_D5 GPIO_NUM_13
+#define OV2640_D4 GPIO_NUM_15
+#define OV2640_D3 GPIO_NUM_14
+#define OV2640_D2 GPIO_NUM_12
+#define OV2640_HREF GPIO_NUM_5
+#define OV2640_PCLK GPIO_NUM_10
+
+#define PWDN_GPIO_NUM OV2640_PWDN
+#define RESET_GPIO_NUM OV2640_RESET
+#define XCLK_GPIO_NUM OV2640_XCLK
+#define SIOD_GPIO_NUM OV2640_SDA
+#define SIOC_GPIO_NUM OV2640_SCL
+
+#define Y9_GPIO_NUM OV2640_D9
+#define Y8_GPIO_NUM OV2640_D8
+#define Y7_GPIO_NUM OV2640_D7
+#define Y6_GPIO_NUM OV2640_D6
+#define Y5_GPIO_NUM OV2640_D5
+#define Y4_GPIO_NUM OV2640_D4
+#define Y3_GPIO_NUM OV2640_D3
+#define Y2_GPIO_NUM OV2640_D2
+#define VSYNC_GPIO_NUM OV2640_VSYNC
+#define HREF_GPIO_NUM OV2640_HREF
+#define PCLK_GPIO_NUM OV2640_PCLK
+
+#define XCLK_FREQ_HZ 20000000
+
+// CST816
+#define CST816_ADDRESS 0x15
+#define TP_SDA IIC_SDA
+#define TP_SCL IIC_SCL
+#define TP_INT GPIO_NUM_47
+
+// AP1511B
+#define AP1511B_FBC GPIO_NUM_16
+
+// KEY
+#define KEY1 GPIO_NUM_17
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc
new file mode 100644
index 0000000..365d9ca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.cc
@@ -0,0 +1,169 @@
+#include "tcamerapluss3_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+#include <driver/i2s_pdm.h>
+
+#include "config.h"
+
+static const char TAG[] = "Tcamerapluss3AudioCodec";
+
+Tcamerapluss3AudioCodec::Tcamerapluss3AudioCodec(int input_sample_rate, int output_sample_rate,
+ gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data,
+ bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ input_channels_ = input_reference_ ? 2 : 1; // 杈撳叆閫氶亾鏁�
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data);
+
+ ESP_LOGI(TAG, "Tcamerapluss3AudioCodec initialized");
+
+#ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
+ gpio_config_t config_mic_spkr_en;
+ config_mic_spkr_en.pin_bit_mask = BIT64(AUDIO_MIC_SPKR_EN);
+ config_mic_spkr_en.mode = GPIO_MODE_OUTPUT;
+ config_mic_spkr_en.pull_up_en = GPIO_PULLUP_ENABLE;
+ config_mic_spkr_en.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ config_mic_spkr_en.intr_type = GPIO_INTR_DISABLE;
+#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
+config_mic_spkr_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+#endif
+ gpio_config(&config_mic_spkr_en);
+ gpio_set_level(AUDIO_MIC_SPKR_EN, 0);
+#endif
+}
+
+Tcamerapluss3AudioCodec::~Tcamerapluss3AudioCodec() {
+ 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 Tcamerapluss3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) {
+
+ i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_0, I2S_ROLE_MASTER);
+ mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+ i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(I2S_NUM_1, I2S_ROLE_MASTER);
+ spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+
+ ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_));
+ ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL));
+
+ #ifdef CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1
+ i2s_std_config_t mic_config = {
+ .clk_cfg = {
+ .sample_rate_hz = (uint32_t)input_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 = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
+ .gpio_cfg = {
+ .mclk = I2S_GPIO_UNUSED,
+ .bclk = mic_bclk,
+ .ws = mic_ws,
+ .dout = I2S_GPIO_UNUSED,
+ .din = mic_data,
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = true // 榛樿鍙抽�氶亾
+ }
+ }
+ };
+
+ ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config));
+#elif defined CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2
+ i2s_pdm_rx_config_t mic_config = {
+ .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(static_cast<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_ws,
+ .din = mic_data,
+ .invert_flags = {
+ .clk_inv = false,
+ },
+ },
+ };
+
+ ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &mic_config));
+#endif
+
+ i2s_std_config_t spkr_config = {
+ .clk_cfg ={
+ .sample_rate_hz = static_cast<uint32_t>(11025),
+ .clk_src = I2S_CLK_SRC_DEFAULT,
+ .ext_clk_freq_hz = 0,
+ .mclk_multiple = I2S_MCLK_MULTIPLE_256,
+ },
+ .slot_cfg = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
+ .gpio_cfg ={
+ .mclk = I2S_GPIO_UNUSED,
+ .bclk = spkr_bclk,
+ .ws = spkr_lrclk,
+ .dout = spkr_data,
+ .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_, &spkr_config));
+ ESP_LOGI(TAG, "Voice hardware created");
+}
+
+void Tcamerapluss3AudioCodec::SetOutputVolume(int volume) {
+ volume_ = volume;
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void Tcamerapluss3AudioCodec::EnableInput(bool enable) {
+ AudioCodec::EnableInput(enable);
+}
+
+void Tcamerapluss3AudioCodec::EnableOutput(bool enable) {
+ AudioCodec::EnableOutput(enable);
+}
+
+int Tcamerapluss3AudioCodec::Read(int16_t *dest, int samples) {
+ if (input_enabled_) {
+ size_t bytes_read;
+ i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
+
+ // 楹﹀厠椋庢帴鏀堕煶閲忔斁澶�20鍊嶏紙闄愬埗鍦� int16_t 鑼冨洿鍐呴槻姝㈡孩鍑猴級
+ int16_t *ptr = dest;
+ for (int i = 0; i < samples; i++) {
+ int32_t amplified = *ptr * 20;
+ *ptr++ = (amplified > 32767) ? 32767 : (amplified < -32768) ? -32768 : amplified;
+ }
+ }
+ return samples;
+}
+
+int Tcamerapluss3AudioCodec::Write(const int16_t *data, int samples){
+ if (output_enabled_){
+ size_t bytes_read;
+ auto output_data = (int16_t *)malloc(samples * sizeof(int16_t));
+ for (size_t i = 0; i < samples; i++){
+ output_data[i] = (float)data[i] * (float)(volume_ / 100.0);
+ }
+ i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
+ free(output_data);
+ }
+ return samples;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h
new file mode 100644
index 0000000..cdb9aaa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-cameraplus-s3/tcamerapluss3_audio_codec.h
@@ -0,0 +1,37 @@
+#ifndef _TCIRCLES3_AUDIO_CODEC_H
+#define _TCIRCLES3_AUDIO_CODEC_H
+
+#include "audio_codec.h"
+
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+
+class Tcamerapluss3AudioCodec : 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;
+
+ uint32_t volume_ = 70;
+
+ void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data);
+
+ virtual int Read(int16_t *dest, int samples) override;
+ virtual int Write(const int16_t *data, int samples) override;
+
+public:
+ Tcamerapluss3AudioCodec(int input_sample_rate, int output_sample_rate,
+ gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data,
+ bool input_reference);
+ virtual ~Tcamerapluss3AudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/README.md
new file mode 100644
index 0000000..6b7b678
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/README.md
@@ -0,0 +1,28 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> LILYGO T-Circle-S3
+```
+
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
+
+<a href="https://github.com/Xinyuan-LilyGO/T-Circle-S3" target="_blank" title="LILYGO T-Circle-S3">LILYGO T-Circle-S3</a>
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.h
new file mode 100644
index 0000000..03e4fc5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.h
@@ -0,0 +1,46 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include "pin_config.h"
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_MIC_I2S_GPIO_BCLK static_cast<gpio_num_t>(MSM261_BCLK)
+#define AUDIO_MIC_I2S_GPIO_WS static_cast<gpio_num_t>(MSM261_WS)
+#define AUDIO_MIC_I2S_GPIO_DATA static_cast<gpio_num_t>(MSM261_DATA)
+
+#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast<gpio_num_t>(MAX98357A_BCLK)
+#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast<gpio_num_t>(MAX98357A_LRCLK)
+#define AUDIO_SPKR_I2S_GPIO_DATA static_cast<gpio_num_t>(MAX98357A_DATA)
+#define AUDIO_SPKR_ENABLE static_cast<gpio_num_t>(MAX98357A_SD_MODE)
+
+#define TOUCH_I2C_SDA_PIN static_cast<gpio_num_t>(TP_SDA)
+#define TOUCH_I2C_SCL_PIN static_cast<gpio_num_t>(TP_SCL)
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH LCD_WIDTH
+#define DISPLAY_HEIGHT LCD_HEIGHT
+#define DISPLAY_MOSI LCD_MOSI
+#define DISPLAY_SCLK LCD_SCLK
+#define DISPLAY_DC LCD_DC
+#define DISPLAY_RST LCD_RST
+#define DISPLAY_CS LCD_CS
+#define DISPLAY_BL static_cast<gpio_num_t>(LCD_BL)
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.json
new file mode 100644
index 0000000..378dded
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "lilygo-t-circle-s3",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c
new file mode 100644
index 0000000..25a7867
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.c
@@ -0,0 +1,353 @@
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_panel_vendor.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_panel_commands.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+
+#include "esp_lcd_gc9d01n.h"
+
+static const char *TAG = "gc9d01n";
+
+static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
+static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool off);
+
+typedef struct{
+ esp_lcd_panel_t base;
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ bool reset_level;
+ int x_gap;
+ int y_gap;
+ uint8_t fb_bits_per_pixel;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
+ const gc9d01n_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+} gc9d01n_panel_t;
+
+esp_err_t esp_lcd_new_panel_gc9d01n(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel){
+ esp_err_t ret = ESP_OK;
+ gc9d01n_panel_t *gc9d01n = NULL;
+ gpio_config_t io_conf = {0};
+
+ ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ gc9d01n = (gc9d01n_panel_t *)calloc(1, sizeof(gc9d01n_panel_t));
+ ESP_GOTO_ON_FALSE(gc9d01n, ESP_ERR_NO_MEM, err, TAG, "no mem for gc9d01n panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0){
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
+ switch (panel_dev_config->color_space){
+ case ESP_LCD_COLOR_SPACE_RGB:
+ gc9d01n->madctl_val = 0;
+ break;
+ case ESP_LCD_COLOR_SPACE_BGR:
+ gc9d01n->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
+ break;
+ }
+#else
+ switch (panel_dev_config->rgb_endian){
+ case LCD_RGB_ENDIAN_RGB:
+ gc9d01n->madctl_val = 0;
+ break;
+ case LCD_RGB_ENDIAN_BGR:
+ gc9d01n->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian");
+ break;
+ }
+#endif
+
+ switch (panel_dev_config->bits_per_pixel){
+ case 16: // RGB565
+ gc9d01n->colmod_val = 0x55;
+ gc9d01n->fb_bits_per_pixel = 16;
+ break;
+ case 18: // RGB666
+ gc9d01n->colmod_val = 0x66;
+ // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
+ gc9d01n->fb_bits_per_pixel = 24;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ gc9d01n->io = io;
+ gc9d01n->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ gc9d01n->reset_level = panel_dev_config->flags.reset_active_high;
+ if (panel_dev_config->vendor_config){
+ gc9d01n->init_cmds = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
+ gc9d01n->init_cmds_size = ((gc9d01n_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
+ }
+ gc9d01n->base.del = panel_gc9d01n_del;
+ gc9d01n->base.reset = panel_gc9d01n_reset;
+ gc9d01n->base.init = panel_gc9d01n_init;
+ gc9d01n->base.draw_bitmap = panel_gc9d01n_draw_bitmap;
+ gc9d01n->base.invert_color = panel_gc9d01n_invert_color;
+ gc9d01n->base.set_gap = panel_gc9d01n_set_gap;
+ gc9d01n->base.mirror = panel_gc9d01n_mirror;
+ gc9d01n->base.swap_xy = panel_gc9d01n_swap_xy;
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
+ gc9d01n->base.disp_off = panel_gc9d01n_disp_on_off;
+#else
+ gc9d01n->base.disp_on_off = panel_gc9d01n_disp_on_off;
+#endif
+ *ret_panel = &(gc9d01n->base);
+ ESP_LOGD(TAG, "new gc9d01n panel @%p", gc9d01n);
+
+ // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_GC9D01N_VER_MAJOR, ESP_LCD_GC9D01N_VER_MINOR,
+ // ESP_LCD_GC9D01N_VER_PATCH);
+
+ return ESP_OK;
+
+err:
+ if (gc9d01n){
+ if (panel_dev_config->reset_gpio_num >= 0){
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(gc9d01n);
+ }
+ return ret;
+}
+
+static esp_err_t panel_gc9d01n_del(esp_lcd_panel_t *panel){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+
+ if (gc9d01n->reset_gpio_num >= 0){
+ gpio_reset_pin(gc9d01n->reset_gpio_num);
+ }
+ ESP_LOGD(TAG, "del gc9d01n panel @%p", gc9d01n);
+ free(gc9d01n);
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_reset(esp_lcd_panel_t *panel){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+
+ // perform hardware reset
+ if (gc9d01n->reset_gpio_num >= 0){
+ gpio_set_level(gc9d01n->reset_gpio_num, gc9d01n->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(gc9d01n->reset_gpio_num, !gc9d01n->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ else{ // perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command
+ }
+
+ return ESP_OK;
+}
+
+static const gc9d01n_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ // Enable Inter Register
+ {0xFE, (uint8_t[]){0x00}, 0, 0},
+ {0xEF, (uint8_t[]){0x00}, 0, 0},
+ {0x80, (uint8_t[]){0xFF}, 1, 0},
+ {0x81, (uint8_t[]){0xFF}, 1, 0},
+ {0x82, (uint8_t[]){0xFF}, 1, 0},
+ {0x84, (uint8_t[]){0xFF}, 1, 0},
+ {0x85, (uint8_t[]){0xFF}, 1, 0},
+ {0x86, (uint8_t[]){0xFF}, 1, 0},
+ {0x87, (uint8_t[]){0xFF}, 1, 0},
+ {0x88, (uint8_t[]){0xFF}, 1, 0},
+ {0x89, (uint8_t[]){0xFF}, 1, 0},
+ {0x8A, (uint8_t[]){0xFF}, 1, 0},
+ {0x8B, (uint8_t[]){0xFF}, 1, 0},
+ {0x8C, (uint8_t[]){0xFF}, 1, 0},
+ {0x8D, (uint8_t[]){0xFF}, 1, 0},
+ {0x8E, (uint8_t[]){0xFF}, 1, 0},
+ {0x8F, (uint8_t[]){0xFF}, 1, 0},
+ {0x3A, (uint8_t[]){0x05}, 1, 0},
+ {0xEC, (uint8_t[]){0x01}, 1, 0},
+ {0x74, (uint8_t[]){0x02, 0x0E, 0x00, 0x00, 0x00, 0x00, 0x00}, 7, 0},
+ {0x98, (uint8_t[]){0x3E}, 1, 0},
+ {0x99, (uint8_t[]){0x3E}, 1, 0},
+ {0xB5, (uint8_t[]){0x0D, 0x0D}, 2, 0},
+ {0x60, (uint8_t[]){0x38, 0x0F, 0x79, 0x67}, 4, 0},
+ {0x61, (uint8_t[]){0x38, 0x11, 0x79, 0x67}, 4, 0},
+ {0x64, (uint8_t[]){0x38, 0x17, 0x71, 0x5F, 0x79, 0x67}, 6, 0},
+ {0x65, (uint8_t[]){0x38, 0x13, 0x71, 0x5B, 0x79, 0x67}, 6, 0},
+ {0x6A, (uint8_t[]){0x00, 0x00}, 2, 0},
+ {0x6C, (uint8_t[]){0x22, 0x02, 0x22, 0x02, 0x22, 0x22, 0x50}, 7, 0},
+ {0x6E, (uint8_t[]){0x03, 0x03, 0x01, 0x01, 0x00, 0x00, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x00, 0x00, 0x00, 0x00, 0x0A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x10, 0x10, 0x00, 0x00, 0x02, 0x02, 0x04, 0x04}, 32, 0},
+ {0xBF, (uint8_t[]){0x01}, 1, 0},
+ {0xF9, (uint8_t[]){0x40}, 1, 0},
+ {0x9B, (uint8_t[]){0x3B, 0x93, 0x33, 0x7F, 0x00}, 5, 0},
+ {0x7E, (uint8_t[]){0x30}, 1, 0},
+ {0x70, (uint8_t[]){0x0D, 0x02, 0x08, 0x0D, 0x02, 0x08}, 6, 0},
+ {0x71, (uint8_t[]){0x0D, 0x02, 0x08}, 3, 0},
+ {0x91, (uint8_t[]){0x0E, 0x09}, 2, 0},
+ {0xC3, (uint8_t[]){0x19, 0xC4, 0x19, 0xC9, 0x3C}, 5, 0},
+ {0xF0, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3E}, 6, 0},
+ {0xF1, (uint8_t[]){0x56, 0xA8, 0x7F, 0x33, 0x34, 0x5F}, 6, 0},
+ {0xF2, (uint8_t[]){0x53, 0x15, 0x0A, 0x04, 0x00, 0x3A}, 6, 0},
+ {0xF3, (uint8_t[]){0x52, 0xA4, 0x7F, 0x33, 0x34, 0xDF}, 6, 0},
+
+ // {0x20, (uint8_t[]){0x00}, 0, 0},
+ {0x36, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 0, 200},
+ {0x29, (uint8_t[]){0x00}, 0, 0},
+ {0x2C, (uint8_t[]){0x00}, 0, 20},
+};
+
+static esp_err_t panel_gc9d01n_init(esp_lcd_panel_t *panel){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+
+ // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val,},1),TAG, "send command failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){gc9d01n->colmod_val,},1),TAG, "send command failed");
+
+ const gc9d01n_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ if (gc9d01n->init_cmds){
+ init_cmds = gc9d01n->init_cmds;
+ init_cmds_size = gc9d01n->init_cmds_size;
+ }else{
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(gc9d01n_lcd_init_cmd_t);
+ }
+
+ bool is_cmd_overwritten = false;
+ for (int i = 0; i < init_cmds_size; i++){
+ // Check if the command has been used or conflicts with the internal
+ switch (init_cmds[i].cmd){
+ case LCD_CMD_MADCTL:
+ is_cmd_overwritten = true;
+ gc9d01n->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ case LCD_CMD_COLMOD:
+ is_cmd_overwritten = true;
+ gc9d01n->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ default:
+ is_cmd_overwritten = false;
+ break;
+ }
+
+ if (is_cmd_overwritten){
+ ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd);
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ }
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+
+ x_start += gc9d01n->x_gap;
+ x_end += gc9d01n->x_gap;
+ y_start += gc9d01n->y_gap;
+ y_end += gc9d01n->y_gap;
+
+ // define an area of frame memory where MCU can access
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){(x_start >> 8) & 0xFF,x_start & 0xFF,((x_end - 1) >> 8) & 0xFF,(x_end - 1) & 0xFF,},4),TAG, "send command failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){(y_start >> 8) & 0xFF,y_start & 0xFF,((y_end - 1) >> 8) & 0xFF,(y_end - 1) & 0xFF,},4),TAG, "send command failed");
+ // transfer frame buffer
+ size_t len = (x_end - x_start) * (y_end - y_start) * gc9d01n->fb_bits_per_pixel / 8;
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len), TAG, "send color failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_invert_color(esp_lcd_panel_t *panel, bool invert_color_data){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+ int command = 0;
+ if (invert_color_data){
+ command = LCD_CMD_INVON;
+ }else{
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+ if (mirror_x){
+ gc9d01n->madctl_val |= LCD_CMD_MX_BIT;
+ }else{
+ gc9d01n->madctl_val &= ~LCD_CMD_MX_BIT;
+ }
+ if (mirror_y){
+ gc9d01n->madctl_val |= LCD_CMD_MY_BIT;
+ }else{
+ gc9d01n->madctl_val &= ~LCD_CMD_MY_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_swap_xy(esp_lcd_panel_t *panel, bool swap_axes){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+ if (swap_axes){
+ gc9d01n->madctl_val |= LCD_CMD_MV_BIT;
+ }else{
+ gc9d01n->madctl_val &= ~LCD_CMD_MV_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){gc9d01n->madctl_val}, 1), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ gc9d01n->x_gap = x_gap;
+ gc9d01n->y_gap = y_gap;
+ return ESP_OK;
+}
+
+static esp_err_t panel_gc9d01n_disp_on_off(esp_lcd_panel_t *panel, bool on_off){
+ gc9d01n_panel_t *gc9d01n = __containerof(panel, gc9d01n_panel_t, base);
+ esp_lcd_panel_io_handle_t io = gc9d01n->io;
+ int command = 0;
+
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
+ on_off = !on_off;
+#endif
+
+ if (on_off){
+ command = LCD_CMD_DISPON;
+ }else{
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h
new file mode 100644
index 0000000..ec057cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/esp_lcd_gc9d01n.h
@@ -0,0 +1,99 @@
+#pragma once
+
+#include "esp_lcd_panel_vendor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} gc9d01n_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct {
+ const gc9d01n_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+} gc9d01n_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model GC9D01N
+ *
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config general panel device configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_gc9d01n(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
+
+/**
+ * @brief LCD panel bus configuration structure
+ *
+ * @param[in] sclk SPI clock pin number
+ * @param[in] mosi SPI MOSI pin number
+ * @param[in] max_trans_sz Maximum transfer size in bytes
+ *
+ */
+#define GC9D01N_PANEL_BUS_SPI_CONFIG(sclk, mosi, max_trans_sz) \
+ { \
+ .mosi_io_num = mosi, \
+ .miso_io_num = -1, \
+ .sclk_io_num = sclk, \
+ .quadwp_io_num = -1, \
+ .quadhd_io_num = -1, \
+ .data4_io_num = -1, \
+ .data5_io_num = -1, \
+ .data6_io_num = -1, \
+ .data7_io_num = -1, \
+ .max_transfer_sz = max_trans_sz, \
+ .flags = 0, \
+ .isr_cpu_id = ESP_INTR_CPU_AFFINITY_AUTO, \
+ .intr_flags = 0 \
+ }
+
+/**
+ * @brief LCD panel IO configuration structure
+ *
+ * @param[in] cs SPI chip select pin number
+ * @param[in] dc SPI data/command pin number
+ * @param[in] cb Callback function when SPI transfer is done
+ * @param[in] cb_ctx Callback function context
+ *
+ */
+#define GC9D01N_PANEL_IO_SPI_CONFIG(cs, dc, callback, callback_ctx) \
+ { \
+ .cs_gpio_num = cs, \
+ .dc_gpio_num = dc, \
+ .spi_mode = 0, \
+ .pclk_hz = 80 * 1000 * 1000, \
+ .trans_queue_depth = 10, \
+ .on_color_trans_done = callback, \
+ .user_ctx = callback_ctx, \
+ .lcd_cmd_bits = 8, \
+ .lcd_param_bits = 8, \
+ .flags = {} \
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc
new file mode 100644
index 0000000..795898b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc
@@ -0,0 +1,258 @@
+#include "wifi_board.h"
+#include "tcircles3_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include "esp_lcd_gc9d01n.h"
+
+#define TAG "LilygoTCircleS3Board"
+
+class Cst816x : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA7);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Cst816x() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t &GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t *read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class LilygoTCircleS3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Cst816x *cst816d_;
+ LcdDisplay *display_;
+ Button boot_button_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitI2c(){
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_config = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = TOUCH_I2C_SDA_PIN,
+ .scl_io_num = TOUCH_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_config, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ static void touchpad_daemon(void *param) {
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ auto &board = (LilygoTCircleS3Board&)Board::GetInstance();
+ auto touchpad = board.GetTouchpad();
+ bool was_touched = false;
+ while (1) {
+ touchpad->UpdateTouchPoint();
+ if (touchpad->GetTouchPoint().num > 0){
+ // On press
+ if (!was_touched) {
+ was_touched = true;
+ Application::GetInstance().ToggleChatState();
+ }
+ }
+ // On release
+ else if (was_touched) {
+ was_touched = false;
+ }
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+ vTaskDelete(NULL);
+ }
+
+ void InitCst816d() {
+ ESP_LOGI(TAG, "Init CST816x");
+ cst816d_ = new Cst816x(i2c_bus_, 0x15);
+ xTaskCreate(touchpad_daemon, "tp", 2048, NULL, 5, NULL);
+ }
+
+ void InitSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitGc9d01nDisplay() {
+ ESP_LOGI(TAG, "Init GC9D01N");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9d01n(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ 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);
+
+ gpio_config_t config;
+ config.pin_bit_mask = BIT64(DISPLAY_BL);
+ config.mode = GPIO_MODE_OUTPUT;
+ config.pull_up_en = GPIO_PULLUP_DISABLE;
+ config.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ config.intr_type = GPIO_INTR_DISABLE;
+#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
+ config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+#endif
+ gpio_config(&config);
+ gpio_set_level(DISPLAY_BL, 0);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ LilygoTCircleS3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitI2c();
+ InitCst816d();
+ I2cDetect();
+ InitSpi();
+ InitGc9d01nDisplay();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Tcircles3AudioCodec audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_MIC_I2S_GPIO_BCLK,
+ AUDIO_MIC_I2S_GPIO_WS,
+ AUDIO_MIC_I2S_GPIO_DATA,
+ AUDIO_SPKR_I2S_GPIO_BCLK,
+ AUDIO_SPKR_I2S_GPIO_LRCLK,
+ AUDIO_SPKR_I2S_GPIO_DATA,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override{
+ return display_;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ Cst816x *GetTouchpad() {
+ return cst816d_;
+ }
+};
+
+DECLARE_BOARD(LilygoTCircleS3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/pin_config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/pin_config.h
new file mode 100644
index 0000000..db428ca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/pin_config.h
@@ -0,0 +1,47 @@
+/*
+ * @Description: None
+ * @Author: LILYGO_L
+ * @Date: 2023-08-16 14:24:03
+ * @LastEditTime: 2025-01-20 10:11:16
+ * @License: GPL 3.0
+ */
+#pragma once
+
+// MAX98357A
+#define MAX98357A_BCLK 5
+#define MAX98357A_LRCLK 4
+#define MAX98357A_DATA 6
+#define MAX98357A_SD_MODE 45
+
+// MSM261
+#define MSM261_BCLK 7
+#define MSM261_WS 9
+#define MSM261_DATA 8
+
+// APA102
+#define APA102_DATA 38
+#define APA102_CLOCK 39
+
+// H0075Y002-V0
+#define LCD_WIDTH 160
+#define LCD_HEIGHT 160
+#define LCD_MOSI 17
+#define LCD_SCLK 15
+#define LCD_DC 16
+#define LCD_RST -1
+#define LCD_CS 13
+#define LCD_BL 18
+
+// IIC
+#define IIC_SDA 11
+#define IIC_SCL 14
+
+// CST816D
+#define TP_SDA 11
+#define TP_SCL 14
+#define TP_RST -1
+#define TP_INT 12
+
+//Rotary Encoder
+#define KNOB_DATA_A 47
+#define KNOB_DATA_B 48
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc
new file mode 100644
index 0000000..5fe3ea7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.cc
@@ -0,0 +1,147 @@
+#include "tcircles3_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+
+#include "config.h"
+
+static const char TAG[] = "Tcircles3AudioCodec";
+
+Tcircles3AudioCodec::Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate,
+ gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data,
+ bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ input_channels_ = input_reference_ ? 2 : 1; // 杈撳叆閫氶亾鏁�
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data);
+
+ gpio_config_t config;
+ config.pin_bit_mask = BIT64(AUDIO_SPKR_ENABLE);
+ config.mode = GPIO_MODE_OUTPUT;
+ config.pull_up_en = GPIO_PULLUP_DISABLE;
+ config.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ config.intr_type = GPIO_INTR_DISABLE;
+#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
+ config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+#endif
+ gpio_config(&config);
+ gpio_set_level(AUDIO_SPKR_ENABLE, 0);
+ ESP_LOGI(TAG, "Tcircles3AudioCodec initialized");
+}
+
+Tcircles3AudioCodec::~Tcircles3AudioCodec() {
+ 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 Tcircles3AudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) {
+
+ i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(0), I2S_ROLE_MASTER);
+ mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+ i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(1), I2S_ROLE_MASTER);
+ spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+
+ ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_));
+ ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL));
+
+ i2s_std_config_t mic_config = {
+ .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 = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
+ .gpio_cfg ={
+ .mclk = I2S_GPIO_UNUSED,
+ .bclk = mic_bclk,
+ .ws = mic_ws,
+ .dout = I2S_GPIO_UNUSED,
+ .din = mic_data,
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = false,
+ }
+ }
+ };
+
+ i2s_std_config_t spkr_config = {
+ .clk_cfg ={
+ .sample_rate_hz = static_cast<uint32_t>(11025),
+ .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 = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
+ .gpio_cfg ={
+ .mclk = I2S_GPIO_UNUSED,
+ .bclk = spkr_bclk,
+ .ws = spkr_lrclk,
+ .dout = spkr_data,
+ .din = I2S_GPIO_UNUSED,
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = false
+ }
+ }
+ };
+
+ ESP_ERROR_CHECK(i2s_channel_init_std_mode(rx_handle_, &mic_config));
+ ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config));
+ ESP_LOGI(TAG, "Voice hardware created");
+}
+
+void Tcircles3AudioCodec::SetOutputVolume(int volume) {
+ volume_ = volume;
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void Tcircles3AudioCodec::EnableInput(bool enable) {
+ AudioCodec::EnableInput(enable);
+}
+
+void Tcircles3AudioCodec::EnableOutput(bool enable) {
+ if (enable){
+ gpio_set_level(AUDIO_SPKR_ENABLE, 1);
+ }else{
+ gpio_set_level(AUDIO_SPKR_ENABLE, 0);
+ }
+ AudioCodec::EnableOutput(enable);
+}
+
+int Tcircles3AudioCodec::Read(int16_t *dest, int samples){
+ if (input_enabled_){
+ size_t bytes_read;
+ i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
+ }
+ return samples;
+}
+
+int Tcircles3AudioCodec::Write(const int16_t *data, int samples){
+ if (output_enabled_){
+ size_t bytes_read;
+ auto output_data = (int16_t *)malloc(samples * sizeof(int16_t));
+ for (size_t i = 0; i < samples; i++){
+ output_data[i] = (float)data[i] * (float)(volume_ / 100.0);
+ }
+ i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
+ free(output_data);
+ }
+ return samples;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h
new file mode 100644
index 0000000..f68f818
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-circle-s3/tcircles3_audio_codec.h
@@ -0,0 +1,37 @@
+#ifndef _TCIRCLES3_AUDIO_CODEC_H
+#define _TCIRCLES3_AUDIO_CODEC_H
+
+#include "audio_codec.h"
+
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+
+class Tcircles3AudioCodec : 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;
+
+ uint32_t volume_ = 70;
+
+ void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data);
+
+ virtual int Read(int16_t *dest, int samples) override;
+ virtual int Write(const int16_t *data, int samples) override;
+
+public:
+ Tcircles3AudioCodec(int input_sample_rate, int output_sample_rate,
+ gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data,
+ bool input_reference);
+ virtual ~Tcircles3AudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/README.md b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/README.md
new file mode 100644
index 0000000..9a236d8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/README.md
@@ -0,0 +1,42 @@
+## LILYGO T-Display-P4
+
+The T-Display-P4 is a versatile development board based on the ESP32-P4 core. Its features include:
+
+1. **High Processing Power**: Equipped with the high-performance ESP32-P4 core processor, it can handle more complex graphics and video tasks, delivering smoother display performance.
+2. **Low Power Design**: Offers multiple selectable power modes to effectively reduce energy consumption and extend battery life.
+3. **High-Resolution Display**: Supports high resolution (default with a large MIPI interface screen at 540x1168px), providing sharp and clear visuals.
+4. **Rich Peripheral Support**: Onboard peripherals include an HD MIPI touchscreen, ESP32-C6 module, speaker, microphone, LoRa module, GPS module, Ethernet, a linear vibration motor, an independent battery gauge for monitoring battery health and percentage, and an MIPI camera. Multiple GPIOs of both the ESP32-P4 and ESP32-C6 are exposed, enhancing the device's expandability.
+
+Official github: [T-Display-P4](https://github.com/Xinyuan-LilyGO/T-Display-P4)
+
+## Configuration
+
+### ESP32P4 Configuration
+
+* Set the compilation target to ESP32P4
+
+ idf.py set-target esp32p4
+
+* Open menuconfig
+
+ idf.py menuconfig
+
+* Select the board
+
+ Xiaozhi Assistant -> Board Type -> LILYGO T-Display-P4
+
+* Select the screen type
+
+ Xiaozhi Assistant -> Select the screen type -> (Select the screen type you need to configure)
+
+* Screen the screen pixel format
+
+ Xiaozhi Assistant -> Select the color format of the screen -> (Select the screen pixel format you need to configure)
+
+* Build
+
+ idf.py build
+
+### ESP32C6 Configuration
+
+* Flash the slave example from the esp-hosted-mcu library for the target chip ESP32C6. The esp-hosted-mcu version must match the one used in the xiaozhi-esp32 library.
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/config.h
new file mode 100644
index 0000000..3e6b088
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/config.h
@@ -0,0 +1,76 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#include "t_display_p4_config.h"
+
+#if defined CONFIG_SCREEN_PIXEL_FORMAT_RGB565
+#define SCREEN_BITS_PER_PIXEL 16
+#define SCREEN_COLOR_RGB_PIXEL_FORMAT LCD_COLOR_PIXEL_FORMAT_RGB565
+#define LVGL_COLOR_FORMAT LV_COLOR_FORMAT_RGB565
+#elif defined CONFIG_SCREEN_PIXEL_FORMAT_RGB888
+#define SCREEN_BITS_PER_PIXEL 24
+#define SCREEN_COLOR_RGB_PIXEL_FORMAT LCD_COLOR_PIXEL_FORMAT_RGB888
+#define LVGL_COLOR_FORMAT LV_COLOR_FORMAT_RGB888
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+// SCREEN
+#if defined CONFIG_SCREEN_TYPE_HI8561
+#define SCREEN_WIDTH HI8561_SCREEN_WIDTH
+#define SCREEN_HEIGHT HI8561_SCREEN_HEIGHT
+#define SCREEN_MIPI_DSI_DPI_CLK_MHZ HI8561_SCREEN_MIPI_DSI_DPI_CLK_MHZ
+#define SCREEN_MIPI_DSI_HSYNC HI8561_SCREEN_MIPI_DSI_HSYNC
+#define SCREEN_MIPI_DSI_HBP HI8561_SCREEN_MIPI_DSI_HBP
+#define SCREEN_MIPI_DSI_HFP HI8561_SCREEN_MIPI_DSI_HFP
+#define SCREEN_MIPI_DSI_VSYNC HI8561_SCREEN_MIPI_DSI_VSYNC
+#define SCREEN_MIPI_DSI_VBP HI8561_SCREEN_MIPI_DSI_VBP
+#define SCREEN_MIPI_DSI_VFP HI8561_SCREEN_MIPI_DSI_VFP
+#define SCREEN_DATA_LANE_NUM HI8561_SCREEN_DATA_LANE_NUM
+#define SCREEN_LANE_BIT_RATE_MBPS HI8561_SCREEN_LANE_BIT_RATE_MBPS
+
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+#define SCREEN_WIDTH RM69A10_SCREEN_WIDTH
+#define SCREEN_HEIGHT RM69A10_SCREEN_HEIGHT
+#define SCREEN_MIPI_DSI_DPI_CLK_MHZ RM69A10_SCREEN_MIPI_DSI_DPI_CLK_MHZ
+#define SCREEN_MIPI_DSI_HSYNC RM69A10_SCREEN_MIPI_DSI_HSYNC
+#define SCREEN_MIPI_DSI_HBP RM69A10_SCREEN_MIPI_DSI_HBP
+#define SCREEN_MIPI_DSI_HFP RM69A10_SCREEN_MIPI_DSI_HFP
+#define SCREEN_MIPI_DSI_VSYNC RM69A10_SCREEN_MIPI_DSI_VSYNC
+#define SCREEN_MIPI_DSI_VBP RM69A10_SCREEN_MIPI_DSI_VBP
+#define SCREEN_MIPI_DSI_VFP RM69A10_SCREEN_MIPI_DSI_VFP
+#define SCREEN_DATA_LANE_NUM RM69A10_SCREEN_DATA_LANE_NUM
+#define SCREEN_LANE_BIT_RATE_MBPS RM69A10_SCREEN_LANE_BIT_RATE_MBPS
+
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK static_cast<gpio_num_t>(ES8311_MCLK)
+#define AUDIO_I2S_GPIO_WS static_cast<gpio_num_t>(ES8311_WS_LRCK)
+#define AUDIO_I2S_GPIO_BCLK static_cast<gpio_num_t>(ES8311_BCLK)
+#define AUDIO_I2S_GPIO_DIN static_cast<gpio_num_t>(ES8311_ADC_DATA)
+#define AUDIO_I2S_GPIO_DOUT static_cast<gpio_num_t>(ES8311_DAC_DATA)
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN static_cast<gpio_num_t>(ES8311_SDA)
+#define AUDIO_CODEC_I2C_SCL_PIN static_cast<gpio_num_t>(ES8311_SCL)
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_35
+
+#define SCREEN_SWAP_XY false
+#define SCREEN_MIRROR_X false
+#define SCREEN_MIRROR_Y false
+
+#define SCREEN_OFFSET_X 0
+#define SCREEN_OFFSET_Y 0
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.cc
new file mode 100644
index 0000000..2d222b2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.cc
@@ -0,0 +1,404 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "soc/soc_caps.h"
+
+#if SOC_MIPI_DSI_SUPPORTED
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "esp_check.h"
+#include "esp_lcd_panel_commands.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_lcd_panel_vendor.h"
+#include "esp_log.h"
+#include "hi8561_driver.h"
+
+#define HI8561_PAD_CONTROL (0xB2)
+#define HI8561_DSI_2_LANE (0x10)
+#define HI8561_DSI_4_LANE (0x00)
+
+#define HI8561_CMD_SHLR_BIT (1ULL << 0)
+#define HI8561_CMD_UPDN_BIT (1ULL << 1)
+#define HI8561_MDCTL_VALUE_DEFAULT (0x01)
+
+static const hi8561_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ /**** CMD_Page 3 ****/
+ {0xDF, (uint8_t[]){0x90, 0x69, 0xF9}, 3, 0},
+ {0xDE, (uint8_t[]){0x00}, 1, 0},
+ {0xBB, (uint8_t[]){0x0F, 0x10, 0x43, 0x50, 0x32, 0x44, 0x44}, 7, 0},
+ {0xBF, (uint8_t[]){0x46, 0x32}, 2, 0},
+ {0xC0, (uint8_t[]){0x01, 0xAD, 0x01, 0xAD}, 4, 0},
+ {0xBD, (uint8_t[]){0x00, 0xB4}, 2, 0},
+ {0xC6, (uint8_t[]){0x00, 0x7D, 0x00, 0xC8, 0x00, 0x17, 0x1A, 0x82, 0x00, 0x00, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01}, 23, 0},
+ {0xC8, (uint8_t[]){0x23, 0x48, 0x87}, 3, 0},
+ // {0xCC, (uint8_t[]){0x33}, 1, 0},//4lane
+ {0xCC, (uint8_t[]){0x31}, 1, 0}, // 2lane
+ // {0xCC, (uint8_t[]){0x30}, 1, 0}, // 1lane
+ {0xBC, (uint8_t[]){0x2E, 0x80, 0x84}, 3, 0},
+ {0xC3, (uint8_t[]){0x3B, 0x01, 0x02, 0x05, 0x0C, 0x0C, 0x75, 0x0A, 0x79, 0x0A, 0x79, 0x02, 0x6E, 0x02, 0x6E, 0x02, 0x6E, 0x0A, 0x0D, 0x0A, 0x0F, 0x0A, 0x0F, 0x0A, 0x0F}, 25, 0},
+ {0xC4, (uint8_t[]){0x01, 0x02, 0x05, 0x0C, 0x0C, 0x75, 0x0A, 0x79, 0x0A, 0x79, 0x02, 0x6E, 0x02, 0x6E, 0x02, 0x6E, 0x0A, 0x0D, 0x0A, 0x0F, 0x0A, 0x0F, 0x0A, 0x0F}, 24, 0},
+ {0xC5, (uint8_t[]){0x03, 0x05, 0x0C, 0x0C, 0x75, 0x0A, 0x79, 0x0A, 0x79, 0x02, 0x6E, 0x02, 0x6E, 0x02, 0x6E, 0x0A, 0x0D, 0x0A, 0x0F, 0x0A, 0x0F, 0x0A, 0x0F}, 23, 0},
+ {0xD7, (uint8_t[]){0x00, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63, 0x0A, 0x63}, 17, 0},
+ {0xCB, (uint8_t[]){0x7F, 0x78, 0x71, 0x64, 0x5A, 0x58, 0x4B, 0x51, 0x3A, 0x53, 0x51, 0x4F, 0x6A, 0x54, 0x57, 0x46, 0x3F, 0x2F, 0x1B, 0x0F, 0x08, 0x7F, 0x78, 0x71, 0x64, 0x5A, 0x58, 0x4B, 0x51, 0x3A, 0x53, 0x51, 0x4F, 0x6A, 0x54, 0x57, 0x46, 0x3F, 0x2F, 0x1B, 0x0F, 0x08, 0x00}, 43, 0},
+ {0xCE, (uint8_t[]){0x00, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C}, 23, 0},
+ {0xCF, (uint8_t[]){0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 45, 0},
+ {0xD0, (uint8_t[]){0x00, 0x1F, 0x1F, 0x11, 0x1E, 0x1F, 0x0F, 0x0F, 0x0D, 0x0D, 0x0B, 0x0B, 0x09, 0x09, 0x07, 0x07, 0x05, 0x05, 0x01, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0},
+ {0xD1, (uint8_t[]){0x00, 0x1F, 0x1F, 0x10, 0x1E, 0x1F, 0x0E, 0x0E, 0x0C, 0x0C, 0x0A, 0x0A, 0x08, 0x08, 0x06, 0x06, 0x04, 0x04, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0},
+ {0xD2, (uint8_t[]){0x00, 0x5F, 0x1F, 0x10, 0x1F, 0x1E, 0x08, 0x08, 0x4A, 0x0A, 0x0C, 0x0C, 0x0E, 0x0E, 0x04, 0x04, 0x06, 0x06, 0x00, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0},
+ {0xD3, (uint8_t[]){0x00, 0x1F, 0x1F, 0x11, 0x1F, 0x1E, 0x09, 0x09, 0x0B, 0x0B, 0x0D, 0x0D, 0x0F, 0x0F, 0x05, 0x05, 0x07, 0x07, 0x01, 0x1F, 0x1F, 0x1F, 0x1F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 29, 0},
+ {0xD4, (uint8_t[]){0x00, 0x20, 0x0B, 0x00, 0x0D, 0x00, 0x0F, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x00, 0x81, 0x04, 0xAE, 0x04, 0xB0, 0x04, 0xB2, 0x04, 0xB4, 0x04, 0xB6, 0x04, 0xB8, 0x00, 0x00, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x06, 0x44, 0x06, 0x46, 0x03, 0x03, 0x00, 0x00, 0x07, 0x00, 0x06, 0x04, 0xA7, 0x04, 0xA8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00, 0x01, 0x00, 0x00, 0x20, 0x00}, 87, 0},
+ {0xD5, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE0, 0x00, 0x00, 0x00, 0x07, 0x32, 0x5A, 0x00, 0x00, 0x3C, 0x00, 0x1E, 0x00, 0x1E, 0xB3, 0x00, 0x0F, 0x06, 0x0C, 0x00, 0x71, 0x20, 0x04, 0x10, 0x04, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0x00, 0x00, 0x1F, 0xFF, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}, 61, 0},
+ {0xCD, (uint8_t[]){0x00, 0x00}, 2, 0},
+
+ {0xDE, (uint8_t[]){0x01}, 1, 0},
+ {0xB9, (uint8_t[]){0x00, 0xFF, 0xFF, 0x04}, 4, 0},
+ {0xC7, (uint8_t[]){0x1F, 0x14, 0x0E}, 3, 0},
+
+ {0xDE, (uint8_t[]){0x02}, 1, 0},
+ {0xE5, (uint8_t[]){0x00, 0x60, 0x60, 0x02, 0x18, 0x60, 0x18, 0x60, 0x09, 0x04, 0x00, 0xC5, 0x01, 0x2C, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x04}, 24, 0},
+ {0xE6, (uint8_t[]){0x10, 0x10, 0x82}, 3, 0},
+ {0xC4, (uint8_t[]){0x00, 0x11, 0x07, 0x00, 0x11, 0x01, 0x08}, 7, 0},
+ {0xC3, (uint8_t[]){0x20, 0xFF}, 2, 0},
+ {0xBD, (uint8_t[]){0x1B}, 1, 0},
+ {0xC6, (uint8_t[]){0x4A, 0x00}, 2, 0},
+ {0xCD, (uint8_t[]){0x14, 0x64, 0x11, 0x40}, 4, 0},
+ {0xC1, (uint8_t[]){0x00, 0x40, 0x00, 0x02, 0x02, 0x02, 0x02, 0x7F, 0x00, 0x00}, 10, 0},
+ {0xB3, (uint8_t[]){0x00, 0xA8}, 2, 0},
+ {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x41, 0x40, 0x43, 0x04}, 11, 0},
+ {0xC2, (uint8_t[]){0x02, 0x42, 0x50, 0x00, 0x02, 0xE4, 0x61, 0x73, 0xF9, 0x08}, 10, 0},
+ {0xEC, (uint8_t[]){0x07, 0x07, 0x40, 0x00, 0x22, 0x02, 0x00, 0xFF, 0x08, 0x7C, 0x00, 0x00, 0x00, 0x00}, 14, 0},
+
+ {0xDE, (uint8_t[]){0x03}, 1, 0},
+ {0xD1, (uint8_t[]){0x00, 0x00, 0x21, 0xFF, 0x00}, 5, 0},
+
+ {0xDE, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 0, 30},
+
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+
+ {0x29, (uint8_t[]){0x00}, 0, 50},
+
+ //============ Gamma END===========
+};
+
+typedef struct
+{
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ const hi8561_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+ uint8_t lane_num;
+ struct
+ {
+ unsigned int reset_level : 1;
+ } flags;
+ // To save the original functions of MIPI DPI panel
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+} hi8561_panel_t;
+
+static const char *TAG = "hi8561";
+
+static esp_err_t panel_hi8561_send_init_cmds(hi8561_panel_t *hi8561);
+
+static esp_err_t panel_hi8561_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_hi8561_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_hi8561_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_hi8561_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_hi8561_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_hi8561_sleep(esp_lcd_panel_t *panel, bool sleep);
+static esp_err_t panel_hi8561_on_off(esp_lcd_panel_t *panel, bool on_off);
+
+esp_err_t esp_lcd_new_panel_hi8561(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+ // ESP_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_hi8561_VER_MAJOR, ESP_LCD_hi8561_VER_MINOR,
+ // ESP_LCD_hi8561_VER_PATCH);
+ ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
+ hi8561_vendor_config_t *vendor_config = (hi8561_vendor_config_t *)panel_dev_config->vendor_config;
+ ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG,
+ "invalid vendor config");
+
+ esp_err_t ret = ESP_OK;
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)calloc(1, sizeof(hi8561_panel_t));
+ ESP_RETURN_ON_FALSE(hi8561, ESP_ERR_NO_MEM, TAG, "no mem for hi8561 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_config_t io_conf = {
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ .mode = GPIO_MODE_OUTPUT,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ hi8561->io = io;
+ hi8561->init_cmds = vendor_config->init_cmds;
+ hi8561->init_cmds_size = vendor_config->init_cmds_size;
+ hi8561->lane_num = vendor_config->mipi_config.lane_num;
+ hi8561->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ hi8561->flags.reset_level = panel_dev_config->flags.reset_active_high;
+ hi8561->madctl_val = HI8561_MDCTL_VALUE_DEFAULT;
+
+ // Create MIPI DPI panel
+ ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), err, TAG,
+ "create MIPI DPI panel failed");
+ ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel);
+
+ // Save the original functions of MIPI DPI panel
+ hi8561->del = (*ret_panel)->del;
+ hi8561->init = (*ret_panel)->init;
+ // Overwrite the functions of MIPI DPI panel
+ (*ret_panel)->del = panel_hi8561_del;
+ (*ret_panel)->init = panel_hi8561_init;
+ (*ret_panel)->reset = panel_hi8561_reset;
+ (*ret_panel)->mirror = panel_hi8561_mirror;
+ (*ret_panel)->invert_color = panel_hi8561_invert_color;
+ (*ret_panel)->disp_sleep = panel_hi8561_sleep;
+ (*ret_panel)->disp_on_off = panel_hi8561_on_off;
+ (*ret_panel)->user_data = hi8561;
+
+ ESP_LOGD(TAG, "new hi8561 panel @%p", hi8561);
+
+ return ESP_OK;
+
+err:
+ if (hi8561)
+ {
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(static_cast<gpio_num_t>(panel_dev_config->reset_gpio_num));
+ }
+ free(hi8561);
+ }
+ return ret;
+}
+
+static esp_err_t panel_hi8561_send_init_cmds(hi8561_panel_t *hi8561)
+{
+ esp_lcd_panel_io_handle_t io = hi8561->io;
+ const hi8561_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ // uint8_t lane_command = HI8561_DSI_2_LANE;
+ // bool is_cmd_overwritten = false;
+
+ // switch (hi8561->lane_num)
+ // {
+ // case 0:
+ // case 2:
+ // lane_command = HI8561_DSI_2_LANE;
+ // break;
+ // case 4:
+ // lane_command = HI8561_DSI_4_LANE;
+ // break;
+ // default:
+ // ESP_LOGE(TAG, "Invalid lane number %d", hi8561->lane_num);
+ // return ESP_ERR_INVALID_ARG;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, HI8561_PAD_CONTROL, (uint8_t[]){
+ // lane_command,
+ // },
+ // 1),
+ // TAG, "send command failed");
+
+ // vendor specific initialization, it can be different between manufacturers
+ // should consult the LCD supplier for initialization sequence code
+ // if (hi8561->init_cmds)
+ // {
+ // init_cmds = hi8561->init_cmds;
+ // init_cmds_size = hi8561->init_cmds_size;
+ // }
+ // else
+ // {
+ // init_cmds = vendor_specific_init_default;
+ // init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(hi8561_lcd_init_cmd_t);
+ // }
+
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(hi8561_lcd_init_cmd_t);
+
+ for (int i = 0; i < init_cmds_size; i++)
+ {
+ // // Check if the command has been used or conflicts with the internal
+ // if (init_cmds[i].data_bytes > 0)
+ // {
+ // switch (init_cmds[i].cmd)
+ // {
+ // case LCD_CMD_MADCTL:
+ // is_cmd_overwritten = true;
+ // hi8561->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ // break;
+ // default:
+ // is_cmd_overwritten = false;
+ // break;
+ // }
+
+ // if (is_cmd_overwritten)
+ // {
+ // is_cmd_overwritten = false;
+ // ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
+ // init_cmds[i].cmd);
+ // }
+ // }
+
+ esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes);
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ // printf("Ciallo\n");
+ }
+
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_del(esp_lcd_panel_t *panel)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+
+ if (hi8561->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(static_cast<gpio_num_t>(hi8561->reset_gpio_num));
+ }
+ // Delete MIPI DPI panel
+ hi8561->del(panel);
+ ESP_LOGD(TAG, "del hi8561 panel @%p", hi8561);
+ free(hi8561);
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_init(esp_lcd_panel_t *panel)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+
+ ESP_RETURN_ON_ERROR(panel_hi8561_send_init_cmds(hi8561), TAG, "send init commands failed");
+ ESP_RETURN_ON_ERROR(hi8561->init(panel), TAG, "init MIPI DPI panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_reset(esp_lcd_panel_t *panel)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = hi8561->io;
+
+ // Perform hardware reset
+ if (hi8561->reset_gpio_num >= 0)
+ {
+ gpio_set_level(static_cast<gpio_num_t>(hi8561->reset_gpio_num), hi8561->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(static_cast<gpio_num_t>(hi8561->reset_gpio_num), !hi8561->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ else if (io)
+ { // Perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_sleep(esp_lcd_panel_t *panel, bool sleep)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = hi8561->io;
+
+ if (sleep == true)
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x10, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_hi8561 sleep on");
+ }
+ else
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_hi8561 sleep off");
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = hi8561->io;
+
+ if (on_off == true)
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_hi8561 display on");
+ }
+ else
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x28, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_hi8561 display off");
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = hi8561->io;
+ uint8_t madctl_val = hi8561->madctl_val;
+
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
+
+ // Control mirror through LCD command
+ if (mirror_x)
+ {
+ madctl_val |= HI8561_CMD_SHLR_BIT;
+ }
+ else
+ {
+ madctl_val &= ~HI8561_CMD_SHLR_BIT;
+ }
+ if (mirror_y)
+ {
+ madctl_val |= HI8561_CMD_UPDN_BIT;
+ }
+ else
+ {
+ madctl_val &= ~HI8561_CMD_UPDN_BIT;
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){madctl_val}, 1), TAG, "send command failed");
+ hi8561->madctl_val = madctl_val;
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_hi8561_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ hi8561_panel_t *hi8561 = (hi8561_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = hi8561->io;
+ uint8_t command = 0;
+
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
+
+ if (invert_color_data)
+ {
+ command = LCD_CMD_INVON;
+ }
+ else
+ {
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+
+ return ESP_OK;
+}
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.h
new file mode 100644
index 0000000..58429b8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/hi8561_driver.h
@@ -0,0 +1,66 @@
+/*
+ * @Description: hi8561_driver
+ * @Author: LILYGO_L
+ * @Date: 2025-06-13 11:02:44
+ * @LastEditTime: 2025-10-09 10:36:27
+ * @License: GPL 3.0
+ */
+#pragma once
+
+#include <stdint.h>
+#include "soc/soc_caps.h"
+
+#if SOC_MIPI_DSI_SUPPORTED
+#include "esp_lcd_panel_vendor.h"
+#include "esp_lcd_mipi_dsi.h"
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct
+{
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} hi8561_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct
+{
+ const hi8561_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+ struct
+ {
+ esp_lcd_dsi_bus_handle_t dsi_bus; /*!< MIPI-DSI bus configuration */
+ const esp_lcd_dpi_panel_config_t *dpi_config; /*!< MIPI-DPI panel configuration */
+ uint8_t lane_num; /*!< Number of MIPI-DSI lanes, defaults to 2 if set to 0 */
+ } mipi_config;
+} hi8561_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model HI8561
+ *
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config General panel device configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ * - Otherwise on fail
+ */
+esp_err_t esp_lcd_new_panel_hi8561(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel);
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc
new file mode 100644
index 0000000..1ba35eb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/lilygo-t-display-p4.cc
@@ -0,0 +1,397 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "display/lcd_display.h"
+#include "button.h"
+#include "config.h"
+
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_ldo_regulator.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lvgl_port.h>
+
+#include "cpp_bus_driver_library.h"
+#include "hi8561_driver.h"
+#include "rm69a10_driver.h"
+
+#define TAG "LilygoTDisplayP4Board"
+
+void TouchTask(void *arg);
+
+#if defined CONFIG_SCREEN_TYPE_HI8561
+class CustomBacklight : public Backlight {
+private:
+ std::unique_ptr<Cpp_Bus_Driver::Tool> tool_;
+
+public:
+ CustomBacklight(std::unique_ptr<Cpp_Bus_Driver::Tool> tool) : tool_(std::move(tool)) {}
+
+ void SetBrightnessImpl(uint8_t brightness) override{
+ tool_->set_pwm_duty(brightness);
+ };
+};
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+class CustomBacklight : public Backlight {
+private:
+ esp_lcd_panel_handle_t mipi_dpi_panel_;
+
+public:
+ CustomBacklight(esp_lcd_panel_handle_t panel) : mipi_dpi_panel_(panel) {}
+
+ void SetBrightnessImpl(uint8_t brightness) override{
+ set_rm69a10_brightness(mipi_dpi_panel_,static_cast<uint8_t>(static_cast<float>(brightness) * 2.55));
+ };
+};
+
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+class LilygoTDisplayP4Board : public WifiBoard {
+public:
+ i2c_master_bus_handle_t audio_codec_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay *display_;
+ esp_lcd_panel_handle_t mipi_dpi_panel_;
+ esp_timer_handle_t touchpad_timer_;
+ uint8_t brightness_;
+
+ std::shared_ptr<Cpp_Bus_Driver::Hardware_Iic_1> xl9535_iic_bus_ = std::make_shared<Cpp_Bus_Driver::Hardware_Iic_1>(XL9535_SDA, XL9535_SCL, I2C_NUM_1);
+ std::unique_ptr<Cpp_Bus_Driver::Xl95x5> xl9535_ = std::make_unique<Cpp_Bus_Driver::Xl95x5>(xl9535_iic_bus_, XL9535_IIC_ADDRESS, DEFAULT_CPP_BUS_DRIVER_VALUE);
+
+#if defined CONFIG_SCREEN_TYPE_HI8561
+ std::shared_ptr<Cpp_Bus_Driver::Hardware_Iic_1> hi8561_t_iic_bus_ = std::make_shared<Cpp_Bus_Driver::Hardware_Iic_1>(HI8561_TOUCH_SDA, HI8561_TOUCH_SCL, I2C_NUM_1);
+ std::unique_ptr<Cpp_Bus_Driver::Hi8561_Touch> hi8561_t_ = std::make_unique<Cpp_Bus_Driver::Hi8561_Touch>(hi8561_t_iic_bus_, HI8561_TOUCH_IIC_ADDRESS, DEFAULT_CPP_BUS_DRIVER_VALUE);
+
+ std::unique_ptr<Cpp_Bus_Driver::Tool> hi8561_backlight_ = std::make_unique<Cpp_Bus_Driver::Tool>();
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+ std::shared_ptr<Cpp_Bus_Driver::Hardware_Iic_1> gt9895_iic_bus_ = std::make_shared<Cpp_Bus_Driver::Hardware_Iic_1>(GT9895_TOUCH_SDA, GT9895_TOUCH_SCL, I2C_NUM_1);
+ std::unique_ptr<Cpp_Bus_Driver::Gt9895> gt9895_ = std::make_unique<Cpp_Bus_Driver::Gt9895>(gt9895_iic_bus_, GT9895_IIC_ADDRESS, GT9895_X_SCALE_FACTOR, GT9895_Y_SCALE_FACTOR,
+ DEFAULT_CPP_BUS_DRIVER_VALUE);
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+ std::unique_ptr<Cpp_Bus_Driver::Tool> esp32p4_ = std::make_unique<Cpp_Bus_Driver::Tool>();
+
+ bool Init_Ldo_Channel_Power(uint8_t chan_id, uint32_t voltage_mv){
+ esp_ldo_channel_handle_t ldo_channel_handle = NULL;
+ esp_ldo_channel_config_t ldo_channel_config ={
+ .chan_id = static_cast<int>(chan_id),
+ .voltage_mv = static_cast<int>(voltage_mv),
+ };
+ if (esp_ldo_acquire_channel(&ldo_channel_config, &ldo_channel_handle) != ESP_OK){
+ printf("esp_ldo_acquire_channel %d fail\n", chan_id);
+ return false;
+ }
+
+ return true;
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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, &audio_codec_i2c_bus_));
+ }
+
+ void InitializeXl9535() {
+ xl9535_->begin(500000);
+ xl9535_->pin_mode(XL9535_ESP32P4_VCCA_POWER_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ xl9535_->pin_mode(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ xl9535_->pin_mode(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ // 寮�鍏�3.3v鐢靛帇鏃跺�欏繀椤诲厛灏咷PS鏂數
+ xl9535_->pin_mode(XL9535_GPS_WAKE_UP, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ xl9535_->pin_write(XL9535_GPS_WAKE_UP, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ // 寮�鍏�3.3v鐢靛帇鏃跺�欏繀椤诲厛灏咵SP32C6鏂數
+ xl9535_->pin_mode(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+
+ xl9535_->pin_write(XL9535_ESP32P4_VCCA_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+
+ xl9535_->pin_write(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_5_0_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+
+ xl9535_->pin_write(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_3_3_V_POWER_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ vTaskDelay(pdMS_TO_TICKS(10));
+
+ // ESP32C6澶嶄綅
+ xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ xl9535_->pin_write(XL9535_ESP32C6_EN, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(100));
+
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ }
+
+ void InitializeLCD() {
+ xl9535_->pin_mode(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ xl9535_->pin_write(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_SCREEN_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+
+ Init_Ldo_Channel_Power(3, 1800);
+
+ esp_lcd_panel_io_handle_t mipi_dbi_io = NULL;
+ esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
+
+ esp_lcd_dsi_bus_config_t bus_config = {
+ .bus_id = 0,
+ .num_data_lanes = SCREEN_DATA_LANE_NUM,
+ .lane_bit_rate_mbps = SCREEN_LANE_BIT_RATE_MBPS,
+ };
+ esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
+
+ ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
+ // we use DBI interface to send LCD commands and parameters
+ esp_lcd_dbi_io_config_t dbi_io_config = {
+ .virtual_channel = 0,
+ .lcd_cmd_bits = 8, // according to the LCD spec
+ .lcd_param_bits = 8, // according to the LCD spec
+ };
+ esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_io_config, &mipi_dbi_io);
+
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .virtual_channel = 0,
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = SCREEN_MIPI_DSI_DPI_CLK_MHZ,
+ .pixel_format = SCREEN_COLOR_RGB_PIXEL_FORMAT,
+ .num_fbs = 0,
+ .video_timing = {
+ .h_size = SCREEN_WIDTH,
+ .v_size = SCREEN_HEIGHT,
+ .hsync_pulse_width = SCREEN_MIPI_DSI_HSYNC,
+ .hsync_back_porch = SCREEN_MIPI_DSI_HBP,
+ .hsync_front_porch = SCREEN_MIPI_DSI_HFP,
+ .vsync_pulse_width = SCREEN_MIPI_DSI_VSYNC,
+ .vsync_back_porch = SCREEN_MIPI_DSI_VBP,
+ .vsync_front_porch = SCREEN_MIPI_DSI_VFP,
+ },
+ .flags = {
+ .use_dma2d = true, // use DMA2D to copy draw buffer into frame buffer
+ }
+ };
+
+#if defined CONFIG_SCREEN_TYPE_HI8561
+ hi8561_vendor_config_t vendor_config = {
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ },
+ };
+ esp_lcd_panel_dev_config_t dev_config = {
+ .reset_gpio_num = -1,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = SCREEN_BITS_PER_PIXEL,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_hi8561(mipi_dbi_io, &dev_config, &mipi_dpi_panel_);
+ #elif defined CONFIG_SCREEN_TYPE_RM69A10
+ rm69a10_vendor_config_t vendor_config = {
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ },
+ };
+ esp_lcd_panel_dev_config_t dev_config = {
+ .reset_gpio_num = -1,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = SCREEN_BITS_PER_PIXEL,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_rm69a10(mipi_dbi_io, &dev_config, &mipi_dpi_panel_);
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+ esp_lcd_panel_init(mipi_dpi_panel_);
+
+#if defined CONFIG_SCREEN_TYPE_HI8561
+ hi8561_backlight_->create_pwm(HI8561_SCREEN_BL, ledc_channel_t::LEDC_CHANNEL_0, 2000);
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+
+ display_ = new MipiLcdDisplay(mipi_dbi_io, mipi_dpi_panel_, SCREEN_WIDTH, SCREEN_HEIGHT,
+ SCREEN_OFFSET_X, SCREEN_OFFSET_Y, SCREEN_MIRROR_X, SCREEN_MIRROR_Y, SCREEN_SWAP_XY);
+ }
+
+ void InitializeTouch(){
+ xl9535_->pin_mode(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Mode::OUTPUT);
+ xl9535_->pin_write(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Value::LOW);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ xl9535_->pin_write(XL9535_TOUCH_RST, Cpp_Bus_Driver::Xl95x5::Value::HIGH);
+ vTaskDelay(pdMS_TO_TICKS(10));
+
+#if defined CONFIG_SCREEN_TYPE_HI8561
+ hi8561_t_iic_bus_->set_bus_handle(xl9535_iic_bus_->get_bus_handle());
+ hi8561_t_->begin();
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+ gt9895_iic_bus_->set_bus_handle(xl9535_iic_bus_->get_bus_handle());
+ gt9895_->begin();
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+ xTaskCreate(TouchTask, "tp", 2 * 1024, this, 5, NULL);
+ }
+
+ void AppToggleChatState(void){
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ AppToggleChatState();
+ });
+ }
+
+ LilygoTDisplayP4Board() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeXl9535();
+ InitializeLCD();
+ InitializeTouch();
+ InitializeButtons();
+ GetBacklight()->SetBrightness(100);
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(audio_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_;
+ }
+
+#if defined CONFIG_SCREEN_TYPE_HI8561
+ virtual Backlight* GetBacklight() override {
+ static CustomBacklight backlight(std::move(hi8561_backlight_));
+ return &backlight;
+ }
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+ virtual Backlight* GetBacklight() override {
+ static CustomBacklight backlight(std::move(mipi_dpi_panel_));
+ return &backlight;
+ }
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+};
+
+void TouchTask(void *arg) {
+ LilygoTDisplayP4Board* board = (LilygoTDisplayP4Board*)arg;
+
+ static bool touch_flag = false;
+ static bool touch_lock_flag = true;
+ static size_t first_touch_time = 0;
+ static bool waiting_for_second_tap = false;
+
+ while (1){
+#if defined CONFIG_SCREEN_TYPE_HI8561
+ if (board->hi8561_t_->get_finger_count() > 0){
+ if(touch_flag == false){
+ touch_lock_flag = false;
+ }
+ touch_flag = true;
+ } else {
+ touch_flag = false;
+ }
+#elif defined CONFIG_SCREEN_TYPE_RM69A10
+ if (board->gt9895_->get_finger_count() > 0){
+ if(touch_flag == false){
+ touch_lock_flag = false;
+ }
+ touch_flag = true;
+ } else {
+ touch_flag = false;
+ }
+#else
+#error "unknown macro definition, please select the correct macro definition."
+#endif
+
+ if(touch_lock_flag == false){
+ size_t current_time = board->esp32p4_->get_system_time_ms();
+
+ if (!waiting_for_second_tap) {
+ // 绗竴娆$偣鍑�
+ first_touch_time = current_time;
+ waiting_for_second_tap = true;
+ printf("first touch detected, waiting for second...\n");
+ } else {
+ // 绗簩娆$偣鍑伙紝妫�鏌ユ椂闂撮棿闅�
+ // 500ms鍐呭畬鎴愬弻鍑�
+ if ((current_time - first_touch_time) <= 500) {
+ printf("double touch trigger\n");
+
+ board->AppToggleChatState();
+
+ // 閲嶇疆鐘舵��
+ waiting_for_second_tap = false;
+ first_touch_time = 0;
+ } else {
+ // 瓒呮椂锛岄噸鏂板紑濮�
+ first_touch_time = current_time;
+ printf("first touch timeout, restart...\n");
+ }
+ }
+
+ touch_lock_flag = true;
+ }
+
+ // 澶勭悊鍙屽嚮瓒呮椂
+ if (waiting_for_second_tap) {
+ size_t current_time = board->esp32p4_->get_system_time_ms();
+
+ if ((current_time - first_touch_time) > 500) {
+ waiting_for_second_tap = false;
+ first_touch_time = 0;
+ printf("double touch timeout\n");
+ }
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+}
+
+DECLARE_BOARD(LilygoTDisplayP4Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.cc
new file mode 100644
index 0000000..cc6b329
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.cc
@@ -0,0 +1,377 @@
+/*
+ * SPDX-FileCopyrightText: 2023-2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+#include "soc/soc_caps.h"
+
+#if SOC_MIPI_DSI_SUPPORTED
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "esp_check.h"
+#include "esp_lcd_panel_commands.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_lcd_panel_vendor.h"
+#include "esp_log.h"
+#include "rm69a10_driver.h"
+
+#define RM69A10_PAD_CONTROL (0xB2)
+#define RM69A10_DSI_2_LANE (0x10)
+#define RM69A10_DSI_4_LANE (0x00)
+
+#define RM69A10_CMD_SHLR_BIT (1ULL << 0)
+#define RM69A10_CMD_UPDN_BIT (1ULL << 1)
+#define RM69A10_MDCTL_VALUE_DEFAULT (0x01)
+
+static const rm69a10_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ /**** CMD_Page 3 ****/
+ {0xFE, (uint8_t[]){0xFD}, 1, 0},
+ {0x80, (uint8_t[]){0xFC}, 1, 0},
+ {0xFE, (uint8_t[]){0x00}, 1, 0},
+ {0x2A, (uint8_t[]){0x00, 0x00, 0x02, 0x37}, 4, 0},
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x04, 0xCF}, 4, 0},
+ {0x31, (uint8_t[]){0x00, 0x03, 0x02, 0x34}, 4, 0},
+ {0x30, (uint8_t[]){0x00, 0x00, 0x04, 0xCF}, 4, 0},
+ {0x12, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+#if CONFIG_SCREEN_PIXEL_FORMAT_RGB565
+ {0x3A, (uint8_t[]){0x75}, 1, 0}, // interface pixel format 16bit/pixel
+#elif CONFIG_SCREEN_PIXEL_FORMAT_RGB888
+ {0x3A, (uint8_t[]){0x77}, 1, 0}, // interface pixel format 24bit/pixel
+#endif
+ // {0x51, (uint8_t[]){0xFE}, 1, 0},
+ {0x51, (uint8_t[]){0x00}, 1, 0}, // 璁剧疆灞忓箷浜害涓�0
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x29, (uint8_t[]){0x00}, 0, 0},
+ //============ Gamma END===========
+};
+
+typedef struct
+{
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ const rm69a10_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+ uint8_t lane_num;
+ struct
+ {
+ unsigned int reset_level : 1;
+ } flags;
+ // To save the original functions of MIPI DPI panel
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+} rm69a10_panel_t;
+
+static const char *TAG = "rm69a10";
+
+static esp_err_t panel_rm69a10_send_init_cmds(rm69a10_panel_t *rm69a10);
+
+static esp_err_t panel_rm69a10_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_rm69a10_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_rm69a10_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_rm69a10_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_rm69a10_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_rm69a10_sleep(esp_lcd_panel_t *panel, bool sleep);
+static esp_err_t panel_rm69a10_on_off(esp_lcd_panel_t *panel, bool on_off);
+
+esp_err_t esp_lcd_new_panel_rm69a10(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+ // ESP_LOGI(TAG, "version: %d.%d.%d", ESP_LCD_RM69A10_VER_MAJOR, ESP_LCD_RM69A10_VER_MINOR,
+ // ESP_LCD_RM69A10_VER_PATCH);
+ ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
+ rm69a10_vendor_config_t *vendor_config = (rm69a10_vendor_config_t *)panel_dev_config->vendor_config;
+ ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG,
+ "invalid vendor config");
+
+ esp_err_t ret = ESP_OK;
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)calloc(1, sizeof(rm69a10_panel_t));
+ ESP_RETURN_ON_FALSE(rm69a10, ESP_ERR_NO_MEM, TAG, "no mem for rm69a10 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_config_t io_conf = {
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ .mode = GPIO_MODE_OUTPUT,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ rm69a10->io = io;
+ rm69a10->init_cmds = vendor_config->init_cmds;
+ rm69a10->init_cmds_size = vendor_config->init_cmds_size;
+ rm69a10->lane_num = vendor_config->mipi_config.lane_num;
+ rm69a10->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ rm69a10->flags.reset_level = panel_dev_config->flags.reset_active_high;
+ rm69a10->madctl_val = RM69A10_MDCTL_VALUE_DEFAULT;
+
+ // Create MIPI DPI panel
+ ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), err, TAG,
+ "create MIPI DPI panel failed");
+ ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel);
+
+ // Save the original functions of MIPI DPI panel
+ rm69a10->del = (*ret_panel)->del;
+ rm69a10->init = (*ret_panel)->init;
+ // Overwrite the functions of MIPI DPI panel
+ (*ret_panel)->del = panel_rm69a10_del;
+ (*ret_panel)->init = panel_rm69a10_init;
+ (*ret_panel)->reset = panel_rm69a10_reset;
+ (*ret_panel)->mirror = panel_rm69a10_mirror;
+ (*ret_panel)->invert_color = panel_rm69a10_invert_color;
+ (*ret_panel)->disp_sleep = panel_rm69a10_sleep;
+ (*ret_panel)->disp_on_off = panel_rm69a10_on_off;
+ (*ret_panel)->user_data = rm69a10;
+ ESP_LOGD(TAG, "new rm69a10 panel @%p", rm69a10);
+
+ return ESP_OK;
+
+err:
+ if (rm69a10)
+ {
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(static_cast<gpio_num_t>(panel_dev_config->reset_gpio_num));
+ }
+ free(rm69a10);
+ }
+ return ret;
+}
+
+static esp_err_t panel_rm69a10_send_init_cmds(rm69a10_panel_t *rm69a10)
+{
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+ const rm69a10_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ // uint8_t lane_command = RM69A10_DSI_2_LANE;
+ // bool is_cmd_overwritten = false;
+
+ // switch (rm69a10->lane_num)
+ // {
+ // case 0:
+ // case 2:
+ // lane_command = RM69A10_DSI_2_LANE;
+ // break;
+ // case 4:
+ // lane_command = RM69A10_DSI_4_LANE;
+ // break;
+ // default:
+ // ESP_LOGE(TAG, "Invalid lane number %d", rm69a10->lane_num);
+ // return ESP_ERR_INVALID_ARG;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, RM69A10_PAD_CONTROL, (uint8_t[]){
+ // lane_command,
+ // },
+ // 1),
+ // TAG, "send command failed");
+
+ // vendor specific initialization, it can be different between manufacturers
+ // should consult the LCD supplier for initialization sequence code
+ // if (rm69a10->init_cmds)
+ // {
+ // init_cmds = rm69a10->init_cmds;
+ // init_cmds_size = rm69a10->init_cmds_size;
+ // }
+ // else
+ // {
+ // init_cmds = vendor_specific_init_default;
+ // init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(rm69a10_lcd_init_cmd_t);
+ // }
+
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(rm69a10_lcd_init_cmd_t);
+
+ for (int i = 0; i < init_cmds_size; i++)
+ {
+ // // Check if the command has been used or conflicts with the internal
+ // if (init_cmds[i].data_bytes > 0)
+ // {
+ // switch (init_cmds[i].cmd)
+ // {
+ // case LCD_CMD_MADCTL:
+ // is_cmd_overwritten = true;
+ // rm69a10->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ // break;
+ // default:
+ // is_cmd_overwritten = false;
+ // break;
+ // }
+
+ // if (is_cmd_overwritten)
+ // {
+ // is_cmd_overwritten = false;
+ // ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
+ // init_cmds[i].cmd);
+ // }
+ // }
+
+ // Send command
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ // printf("Ciallo\n");
+ }
+
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_del(esp_lcd_panel_t *panel)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+
+ if (rm69a10->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(static_cast<gpio_num_t>(rm69a10->reset_gpio_num));
+ }
+ // Delete MIPI DPI panel
+ rm69a10->del(panel);
+ ESP_LOGD(TAG, "del rm69a10 panel @%p", rm69a10);
+ free(rm69a10);
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_init(esp_lcd_panel_t *panel)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+
+ ESP_RETURN_ON_ERROR(panel_rm69a10_send_init_cmds(rm69a10), TAG, "send init commands failed");
+ ESP_RETURN_ON_ERROR(rm69a10->init(panel), TAG, "init MIPI DPI panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_reset(esp_lcd_panel_t *panel)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+
+ // Perform hardware reset
+ if (rm69a10->reset_gpio_num >= 0)
+ {
+ gpio_set_level(static_cast<gpio_num_t>(rm69a10->reset_gpio_num), rm69a10->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(static_cast<gpio_num_t>(rm69a10->reset_gpio_num), !rm69a10->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ else if (io)
+ { // Perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_sleep(esp_lcd_panel_t *panel, bool sleep)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+
+ if (sleep == true)
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x10, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_rm69a10 sleep on");
+ }
+ else
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x11, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_rm69a10 sleep off");
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+
+ if (on_off == true)
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x29, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_rm69a10 display on");
+ }
+ else
+ {
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x28, 0x00, 0), TAG, "esp_lcd_panel_io_tx_param fail");
+ ESP_LOGI(TAG, "panel_rm69a10 display off");
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(120));
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+ uint8_t madctl_val = rm69a10->madctl_val;
+
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
+
+ // Control mirror through LCD command
+ if (mirror_x)
+ {
+ madctl_val |= RM69A10_CMD_SHLR_BIT;
+ }
+ else
+ {
+ madctl_val &= ~RM69A10_CMD_SHLR_BIT;
+ }
+ if (mirror_y)
+ {
+ madctl_val |= RM69A10_CMD_UPDN_BIT;
+ }
+ else
+ {
+ madctl_val &= ~RM69A10_CMD_UPDN_BIT;
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){madctl_val}, 1), TAG, "send command failed");
+ rm69a10->madctl_val = madctl_val;
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_rm69a10_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+ uint8_t command = 0;
+
+ ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
+
+ if (invert_color_data)
+ {
+ command = LCD_CMD_INVON;
+ }
+ else
+ {
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+
+ return ESP_OK;
+}
+
+esp_err_t set_rm69a10_brightness(esp_lcd_panel_t *panel, uint8_t brightness)
+{
+ rm69a10_panel_t *rm69a10 = (rm69a10_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = rm69a10->io;
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, 0x51, &brightness, 1), TAG, "esp_lcd_panel_io_tx_param fail");
+
+ return ESP_OK;
+}
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.h
new file mode 100644
index 0000000..e525bd4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/rm69a10_driver.h
@@ -0,0 +1,68 @@
+/*
+ * @Description: rm69a10_driver
+ * @Author: LILYGO_L
+ * @Date: 2025-07-07 14:23:16
+ * @LastEditTime: 2025-10-09 10:36:33
+ * @License: GPL 3.0
+ */
+#pragma once
+
+#include <stdint.h>
+#include "soc/soc_caps.h"
+
+#if SOC_MIPI_DSI_SUPPORTED
+#include "esp_lcd_panel_vendor.h"
+#include "esp_lcd_mipi_dsi.h"
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct
+{
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} rm69a10_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct
+{
+ const rm69a10_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+ struct
+ {
+ esp_lcd_dsi_bus_handle_t dsi_bus; /*!< MIPI-DSI bus configuration */
+ const esp_lcd_dpi_panel_config_t *dpi_config; /*!< MIPI-DPI panel configuration */
+ uint8_t lane_num; /*!< Number of MIPI-DSI lanes, defaults to 2 if set to 0 */
+ } mipi_config;
+} rm69a10_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model RM69A10
+ *
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config General panel device configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ * - Otherwise on fail
+ */
+esp_err_t esp_lcd_new_panel_rm69a10(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel);
+
+esp_err_t set_rm69a10_brightness(esp_lcd_panel_t *panel, uint8_t brightness);
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/t_display_p4_config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/t_display_p4_config.h
new file mode 100644
index 0000000..46252d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-p4/t_display_p4_config.h
@@ -0,0 +1,233 @@
+/*
+ * @Description: t_display_p4_config
+ * @Author: LILYGO_L
+ * @Date: 2024-12-06 10:32:28
+ * @LastEditTime: 2025-09-10 11:56:28
+ */
+#pragma once
+
+////////////////////////////////////////////////// gpio config //////////////////////////////////////////////////
+
+// IIC
+#define IIC_1_SDA 7
+#define IIC_1_SCL 8
+#define IIC_2_SDA 20
+#define IIC_2_SCL 21
+
+// BOOT
+#define ESP32P4_BOOT 35
+
+// XL9535
+#define XL9535_SDA IIC_1_SDA
+#define XL9535_SCL IIC_1_SCL
+#define XL9535_INT 5
+// XL9535寮曡剼鍔熻兘
+#define XL9535_3_3_V_POWER_EN Cpp_Bus_Driver::Xl95x5::Pin::IO0
+#define XL9535_SKY13453_VCTL Cpp_Bus_Driver::Xl95x5::Pin::IO1
+#define XL9535_SCREEN_RST Cpp_Bus_Driver::Xl95x5::Pin::IO2
+#define XL9535_TOUCH_RST Cpp_Bus_Driver::Xl95x5::Pin::IO3
+#define XL9535_TOUCH_INT Cpp_Bus_Driver::Xl95x5::Pin::IO4
+#define XL9535_ETHERNET_RST Cpp_Bus_Driver::Xl95x5::Pin::IO5
+#define XL9535_5_0_V_POWER_EN Cpp_Bus_Driver::Xl95x5::Pin::IO6
+#define XL9535_EXTERNAL_SENSOR_INT Cpp_Bus_Driver::Xl95x5::Pin::IO7
+#define XL9535_ESP32P4_VCCA_POWER_EN Cpp_Bus_Driver::Xl95x5::Pin::IO10
+#define XL9535_GPS_WAKE_UP Cpp_Bus_Driver::Xl95x5::Pin::IO11
+#define XL9535_RTC_INT Cpp_Bus_Driver::Xl95x5::Pin::IO12
+#define XL9535_ESP32C6_WAKE_UP Cpp_Bus_Driver::Xl95x5::Pin::IO13
+#define XL9535_ESP32C6_EN Cpp_Bus_Driver::Xl95x5::Pin::IO14
+#define XL9535_SD_EN Cpp_Bus_Driver::Xl95x5::Pin::IO15
+#define XL9535_SX1262_RST Cpp_Bus_Driver::Xl95x5::Pin::IO16
+#define XL9535_SX1262_DIO1 Cpp_Bus_Driver::Xl95x5::Pin::IO17
+
+// ES8311
+#define ES8311_SDA IIC_2_SDA
+#define ES8311_SCL IIC_2_SCL
+#define ES8311_ADC_DATA 11
+#define ES8311_DAC_DATA 10
+#define ES8311_BCLK 12
+#define ES8311_MCLK 13
+#define ES8311_WS_LRCK 9
+
+// AW86224
+#define AW86224_SDA IIC_2_SDA
+#define AW86224_SCL IIC_2_SCL
+
+// SGM38121
+#define SGM38121_SDA IIC_2_SDA
+#define SGM38121_SCL IIC_2_SCL
+
+// PCF8563
+#define PCF8563_SDA IIC_1_SDA
+#define PCF8563_SCL IIC_1_SCL
+
+// BQ27220
+#define BQ27220_SDA IIC_1_SDA
+#define BQ27220_SCL IIC_1_SCL
+
+// SPI
+#define SPI_1_SCLK 2
+#define SPI_1_MOSI 3
+#define SPI_1_MISO 4
+
+// SX1262
+#define SX1262_CS 24
+#define SX1262_BUSY 6
+#define SX1262_SCLK SPI_1_SCLK
+#define SX1262_MOSI SPI_1_MOSI
+#define SX1262_MISO SPI_1_MISO
+
+// L76K
+#define GPS_TX 22
+#define GPS_RX 23
+
+// ICM20948
+#define ICM20948_SDA IIC_2_SDA
+#define ICM20948_SCL IIC_2_SCL
+
+// HI8561
+#define HI8561_SCREEN_BL 51
+#define HI8561_TOUCH_SDA IIC_1_SDA
+#define HI8561_TOUCH_SCL IIC_1_SCL
+
+// GT9895
+#define GT9895_TOUCH_SDA IIC_1_SDA
+#define GT9895_TOUCH_SCL IIC_1_SCL
+
+// Camera
+#define CAMERA_SDA IIC_2_SDA
+#define CAMERA_SCL IIC_2_SCL
+
+// SDIO
+#define SDIO_1_CLK 43
+#define SDIO_1_CMD 44
+#define SDIO_1_D0 39
+#define SDIO_1_D1 40
+#define SDIO_1_D2 41
+#define SDIO_1_D3 42
+
+#define SDIO_2_CLK 18
+#define SDIO_2_CMD 19
+#define SDIO_2_D0 14
+#define SDIO_2_D1 15
+#define SDIO_2_D2 16
+#define SDIO_2_D3 17
+
+// SD
+// SDMMC
+#define SD_SDIO_CLK SDIO_1_CLK
+#define SD_SDIO_CMD SDIO_1_CMD
+#define SD_SDIO_D0 SDIO_1_D0
+#define SD_SDIO_D1 SDIO_1_D1
+#define SD_SDIO_D2 SDIO_1_D2
+#define SD_SDIO_D3 SDIO_1_D3
+// SDSPI
+#define SD_SCLK SDIO_1_CLK
+#define SD_MOSI SDIO_1_CMD
+#define SD_MISO SDIO_1_D0
+#define SD_CS SDIO_1_D3
+
+// ESP32C6 SDIO
+#define ESP32C6_SDIO_CLK SDIO_2_CLK
+#define ESP32C6_SDIO_CMD SDIO_2_CMD
+#define ESP32C6_SDIO_D0 SDIO_2_D0
+#define ESP32C6_SDIO_D1 SDIO_2_D1
+#define ESP32C6_SDIO_D2 SDIO_2_D2
+#define ESP32C6_SDIO_D3 SDIO_2_D3
+
+// Extended io
+#define EXT_2X8P_SPI_SCLK SPI_1_SCLK
+#define EXT_2X8P_SPI_MOSI SPI_1_MOSI
+#define EXT_2X8P_SPI_MISO SPI_1_MISO
+
+#define EXT_2X8P_IO_26 26
+#define EXT_2X8P_IO_27 27
+#define EXT_2X8P_IO_33 33
+#define EXT_2X8P_IO_32 32
+#define EXT_2X8P_IO_25 25
+#define EXT_2X8P_IO_36 36
+#define EXT_2X8P_IO_53 53
+#define EXT_2X8P_IO_54 54
+#define EXT_1X4P_1_IO_47 47
+#define EXT_1X4P_1_IO_48 48
+#define EXT_1X4P_2_IO_45 45
+#define EXT_1X4P_2_IO_46 46
+
+////////////////////////////////////////////////// gpio config //////////////////////////////////////////////////
+
+////////////////////////////////////////////////// other define config //////////////////////////////////////////////////
+
+// XL9535
+#define XL9535_IIC_ADDRESS 0x20
+
+// ES8311
+#define ES8311_IIC_ADDRESS 0x18
+
+// AW86224
+#define AW86224_IIC_ADDRESS 0x58
+
+// SGM38121
+#define SGM38121_IIC_ADDRESS 0x28
+
+// PCF8563
+#define PCF8563_IIC_ADDRESS 0x51
+
+// BQ27220
+#define BQ27220_IIC_ADDRESS 0x55
+
+// ICM20948
+#define ICM20948_IIC_ADDRESS 0x68
+
+// HI8561
+#define HI8561_SCREEN_WIDTH 540
+#define HI8561_SCREEN_HEIGHT 1168
+#define HI8561_SCREEN_MIPI_DSI_DPI_CLK_MHZ 60
+// #define HI8561_SCREEN_MIPI_DSI_DPI_CLK_MHZ 45
+#define HI8561_SCREEN_MIPI_DSI_HSYNC 28
+#define HI8561_SCREEN_MIPI_DSI_HBP 26
+#define HI8561_SCREEN_MIPI_DSI_HFP 20
+#define HI8561_SCREEN_MIPI_DSI_VSYNC 2
+#define HI8561_SCREEN_MIPI_DSI_VBP 22
+#define HI8561_SCREEN_MIPI_DSI_VFP 200
+#define HI8561_SCREEN_DATA_LANE_NUM 2
+#define HI8561_SCREEN_LANE_BIT_RATE_MBPS 1000
+#define HI8561_TOUCH_IIC_ADDRESS 0x68
+
+// RM69A10
+#define RM69A10_SCREEN_WIDTH 568
+#define RM69A10_SCREEN_HEIGHT 1232
+// #define RM69A10_SCREEN_MIPI_DSI_DPI_CLK_MHZ 80
+#define RM69A10_SCREEN_MIPI_DSI_DPI_CLK_MHZ 60
+#define RM69A10_SCREEN_MIPI_DSI_HSYNC 50
+#define RM69A10_SCREEN_MIPI_DSI_HBP 150
+#define RM69A10_SCREEN_MIPI_DSI_HFP 50
+#define RM69A10_SCREEN_MIPI_DSI_VSYNC 40
+#define RM69A10_SCREEN_MIPI_DSI_VBP 120
+#define RM69A10_SCREEN_MIPI_DSI_VFP 80
+#define RM69A10_SCREEN_DATA_LANE_NUM 2
+#define RM69A10_SCREEN_LANE_BIT_RATE_MBPS 1000
+
+// GT9895
+#define GT9895_IIC_ADDRESS 0x5D
+#define GT9895_MAX_X_SIZE 1060
+#define GT9895_MAX_Y_SIZE 2400
+#define GT9895_X_SCALE_FACTOR static_cast<float>(RM69A10_SCREEN_WIDTH) / static_cast<float>(GT9895_MAX_X_SIZE)
+#define GT9895_Y_SCALE_FACTOR static_cast<float>(RM69A10_SCREEN_HEIGHT) / static_cast<float>(GT9895_MAX_Y_SIZE)
+
+// CAMERA
+#define CAMERA_WIDTH 1920
+#define CAMERA_HEIGHT 1080
+// #define CAMERA_WIDTH 1280
+// #define CAMERA_HEIGHT 720
+// #define CAMERA_WIDTH 800
+// #define CAMERA_HEIGHT 800
+// #define CAMERA_WIDTH 640
+// #define CAMERA_HEIGHT 480
+
+#define CAMERA_DATA_LANE_NUM 2
+#define CAMERA_LANE_BIT_RATE_MBPS 1000
+#define CAMERA_MIPI_DSI_DPI_CLK_MHZ 60
+
+// SD
+#define SD_BASE_PATH "/sdcard"
+
+////////////////////////////////////////////////// other define config //////////////////////////////////////////////////
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md
new file mode 100644
index 0000000..c3b8d0e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/README.md
@@ -0,0 +1,32 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> LILYGO T-Display-S3-Pro-MVSRLora
+Or
+Xiaozhi Assistant -> Board Type -> LILYGO T-Display-S3-Pro-MVSRLora_NO_BATTERY
+```
+
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
+
+<a href="https://github.com/Xinyuan-LilyGO/T-Display-S3-Pro" target="_blank" title="LILYGO T-Display-S3-Pro">LILYGO T-Display-S3-Pro</a>
+<br />
+<a href="https://github.com/Xinyuan-LilyGO/T-Display-S3-Pro-MVSRLora" target="_blank" title="LILYGO T-Display-S3-Pro-MVSRLora">LILYGO T-Display-S3-Pro-MVSRLora</a>
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h
new file mode 100644
index 0000000..44c0a03
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.h
@@ -0,0 +1,47 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include "pin_config.h"
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_MIC_I2S_GPIO_BCLK GPIO_NUM_NC
+#define AUDIO_MIC_I2S_GPIO_WS static_cast<gpio_num_t>(MP34DT05TR_LRCLK)
+#define AUDIO_MIC_I2S_GPIO_DATA static_cast<gpio_num_t>(MP34DT05TR_DATA)
+#define AUDIO_MIC_ENABLE static_cast<gpio_num_t>(MP34DT05TR_EN)
+
+#define AUDIO_SPKR_I2S_GPIO_BCLK static_cast<gpio_num_t>(MAX98357A_BCLK)
+#define AUDIO_SPKR_I2S_GPIO_LRCLK static_cast<gpio_num_t>(MAX98357A_LRCLK)
+#define AUDIO_SPKR_I2S_GPIO_DATA static_cast<gpio_num_t>(MAX98357A_DATA)
+#define AUDIO_SPKR_ENABLE static_cast<gpio_num_t>(MAX98357A_EN)
+
+#define TOUCH_I2C_SDA_PIN static_cast<gpio_num_t>(TOUCH_IIC_SDA)
+#define TOUCH_I2C_SCL_PIN static_cast<gpio_num_t>(TOUCH_IIC_SCL)
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_WIDTH LCD_WIDTH
+#define DISPLAY_HEIGHT LCD_HEIGHT
+#define DISPLAY_MOSI LCD_MOSI
+#define DISPLAY_SCLK LCD_SCLK
+#define DISPLAY_DC LCD_DC
+#define DISPLAY_RST LCD_RST
+#define DISPLAY_CS LCD_CS
+#define DISPLAY_BL static_cast<gpio_num_t>(LCD_BL)
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN DISPLAY_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json
new file mode 100644
index 0000000..f7e4505
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "lilygo-t-display-s3-pro-mvsrlora",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc
new file mode 100644
index 0000000..b18d27a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/lilygo-t-display-s3-pro-mvsrlora.cc
@@ -0,0 +1,283 @@
+#include "wifi_board.h"
+#include "tdisplays3promvsrlora_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include "esp_lcd_st7796.h"
+
+#define TAG "LilygoTDisplays3ProMVSRLoraBoard"
+
+class Cst2xxse : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ Cst2xxse(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0x06);
+ ESP_LOGI(TAG, "Get cst2xxse chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Cst2xxse() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x00, read_buffer_, 6);
+ tp_.num = read_buffer_[5] & 0x0F;
+ tp_.x = (static_cast<int>(read_buffer_[1]) << 4) | (read_buffer_[3] & 0xF0);
+ tp_.y = (static_cast<int>(read_buffer_[2]) << 4) | (read_buffer_[3] & 0x0F);
+ // ESP_LOGI(TAG, "Touch num: %d x: %d y: %d", tp_.num,tp_.x,tp_.y);
+ }
+
+ const TouchPoint_t &GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t *read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class Sy6970 : public I2cDevice {
+public:
+
+ Sy6970(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0x14);
+ ESP_LOGI(TAG, "Get sy6970 chip ID: 0x%02X", (chip_id & 0B00111000));
+
+ WriteReg(0x00,0B00001000); // Disable ILIM pin
+ WriteReg(0x02,0B11011101); // Enable ADC measurement function
+ WriteReg(0x07,0B10001101); // Disable watchdog timer feeding function
+
+ #ifdef CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY
+ WriteReg(0x09,0B01100100); // Disable BATFET when battery is not needed
+ #endif
+ }
+};
+
+class LilygoTDisplays3ProMVSRLoraBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Cst2xxse *cst226se_;
+ Sy6970 *sy6970_;
+ LcdDisplay *display_;
+ Button boot_button_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitI2c(){
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_config = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = TOUCH_I2C_SDA_PIN,
+ .scl_io_num = TOUCH_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_config, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ static void touchpad_daemon(void *param) {
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ auto &board = (LilygoTDisplays3ProMVSRLoraBoard&)Board::GetInstance();
+ auto touchpad = board.GetTouchpad();
+ bool was_touched = false;
+ while (1) {
+ touchpad->UpdateTouchPoint();
+ if (touchpad->GetTouchPoint().num > 0){
+ // On press
+ if (!was_touched) {
+ was_touched = true;
+ Application::GetInstance().ToggleChatState();
+ }
+ }
+ // On release
+ else if (was_touched) {
+ was_touched = false;
+ }
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+ vTaskDelete(NULL);
+ }
+
+ void InitCst226se() {
+ ESP_LOGI(TAG, "Init Cst2xxse");
+ cst226se_ = new Cst2xxse(i2c_bus_, 0x5A);
+ xTaskCreate(touchpad_daemon, "tp", 4096, NULL, 5, NULL);
+ }
+
+ void InitSy6970() {
+ ESP_LOGI(TAG, "Init Sy6970");
+ sy6970_ = new Sy6970(i2c_bus_, 0x6A);
+ }
+
+ void InitSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitSt7796Display() {
+ ESP_LOGI(TAG, "Init St7796");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(panel_io, &panel_config, &panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 49, 0));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+
+ 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);
+
+ gpio_config_t config;
+ config.pin_bit_mask = BIT64(DISPLAY_BL);
+ config.mode = GPIO_MODE_OUTPUT;
+ config.pull_up_en = GPIO_PULLUP_DISABLE;
+ config.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ config.intr_type = GPIO_INTR_DISABLE;
+#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
+ config.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+#endif
+ gpio_config(&config);
+ gpio_set_level(DISPLAY_BL, 0);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ LilygoTDisplays3ProMVSRLoraBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitI2c();
+ I2cDetect();
+ InitCst226se();
+ InitSy6970();
+ InitSpi();
+ InitSt7796Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Tdisplays3promvsrloraAudioCodec audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_MIC_I2S_GPIO_BCLK,
+ AUDIO_MIC_I2S_GPIO_WS,
+ AUDIO_MIC_I2S_GPIO_DATA,
+ AUDIO_SPKR_I2S_GPIO_BCLK,
+ AUDIO_SPKR_I2S_GPIO_LRCLK,
+ AUDIO_SPKR_I2S_GPIO_DATA,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override{
+ return display_;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ Cst2xxse *GetTouchpad() {
+ return cst226se_;
+ }
+};
+
+DECLARE_BOARD(LilygoTDisplays3ProMVSRLoraBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h
new file mode 100644
index 0000000..0b952d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/pin_config.h
@@ -0,0 +1,80 @@
+/*
+ * @Description: None
+ * @Author: None
+ * @Date: 2023-08-16 14:24:03
+ * @LastEditTime: 2025-04-23 11:25:00
+ * @License: GPL 3.0
+ */
+#pragma once
+
+// ST7796
+#define LCD_WIDTH 222
+#define LCD_HEIGHT 480
+#define LCD_BL 48
+#define LCD_MOSI 17
+#define LCD_MISO 8
+#define LCD_SCLK 18
+#define LCD_CS 39
+#define LCD_DC 9
+#define LCD_RST 47
+
+// IIC
+#define IIC_SDA 5
+#define IIC_SCL 6
+
+// CST226SE
+#define CST226SE_IIC_ADDRESS 0x5A
+#define TOUCH_RST 13
+#define TOUCH_INT 21
+#define TOUCH_IIC_SDA IIC_SDA
+#define TOUCH_IIC_SCL IIC_SCL
+
+// SY6970
+#define SY6970_SDA 5
+#define SY6970_SCL 6
+#define SY6970_Address 0x6A
+#define SY6970_INT 21
+
+// SD
+#define SD_CS 14
+#define SD_MISO 8
+#define SD_MOSI 17
+#define SD_SCLK 18
+
+// RT9080
+#define RT9080_EN 42
+
+// MAX98357A
+#define MAX98357A_BCLK 4
+#define MAX98357A_LRCLK 15
+#define MAX98357A_DATA 11
+#define MAX98357A_EN 41
+
+// Vibration Motor
+#define VIBRATINO_MOTOR_PWM 45
+
+// PCF85063
+#define PCF85063_IIC_SDA 5
+#define PCF85063_IIC_SCL 6
+#define PCF85063_INT 21
+
+// LR1121
+#define LR1121_BUSY 46
+#define LR1121_INT 40
+#define LR1121_SCLK 18
+#define LR1121_MOSI 17
+#define LR1121_MISO 8
+#define LR1121_CS 7
+#define LR1121_RST 10
+
+// ICM20948
+#define ICM20948_ADDRESS 0x28
+#define ICM20948_SDA 5
+#define ICM20948_SCL 6
+#define ICM20948_INT 21
+
+// MP34DT05TRF
+#define MP34DT05TR_LRCLK 1
+#define MP34DT05TR_DATA 2
+#define MP34DT05TR_EN 3
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc
new file mode 100644
index 0000000..0beec45
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.cc
@@ -0,0 +1,150 @@
+#include "tdisplays3promvsrlora_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+#include <driver/i2s_pdm.h>
+
+#include "config.h"
+
+static const char TAG[] = "Tdisplays3promvsrloraAudioCodec";
+
+Tdisplays3promvsrloraAudioCodec::Tdisplays3promvsrloraAudioCodec(int input_sample_rate, int output_sample_rate,
+ gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data,
+ bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ input_channels_ = input_reference_ ? 2 : 1; // 杈撳叆閫氶亾鏁�
+ input_sample_rate_ = input_sample_rate;
+ output_sample_rate_ = output_sample_rate;
+
+ CreateVoiceHardware(mic_bclk, mic_ws, mic_data, spkr_bclk, spkr_lrclk, spkr_data);
+
+ gpio_config_t config_mic_en;
+ config_mic_en.pin_bit_mask = BIT64(AUDIO_MIC_ENABLE);
+ config_mic_en.mode = GPIO_MODE_OUTPUT;
+ config_mic_en.pull_up_en = GPIO_PULLUP_ENABLE;
+ config_mic_en.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ config_mic_en.intr_type = GPIO_INTR_DISABLE;
+#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
+config_mic_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+#endif
+ gpio_config(&config_mic_en);
+ gpio_set_level(AUDIO_MIC_ENABLE, 1);
+
+ gpio_config_t config_spkr_en;
+ config_spkr_en.pin_bit_mask = BIT64(AUDIO_SPKR_ENABLE);
+ config_spkr_en.mode = GPIO_MODE_OUTPUT;
+ config_spkr_en.pull_up_en = GPIO_PULLUP_DISABLE;
+ config_spkr_en.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ config_spkr_en.intr_type = GPIO_INTR_DISABLE;
+#if SOC_GPIO_SUPPORT_PIN_HYS_FILTER
+config_spkr_en.hys_ctrl_mode = GPIO_HYS_SOFT_ENABLE;
+#endif
+ gpio_config(&config_spkr_en);
+ gpio_set_level(AUDIO_SPKR_ENABLE, 0);
+
+ ESP_LOGI(TAG, "Tdisplays3promvsrloraAudioCodec initialized");
+}
+
+Tdisplays3promvsrloraAudioCodec::~Tdisplays3promvsrloraAudioCodec() {
+ 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 Tdisplays3promvsrloraAudioCodec::CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data) {
+
+ i2s_chan_config_t mic_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(0), I2S_ROLE_MASTER);
+ mic_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+ i2s_chan_config_t spkr_chan_config = I2S_CHANNEL_DEFAULT_CONFIG(i2s_port_t(1), I2S_ROLE_MASTER);
+ spkr_chan_config.auto_clear = true; // Auto clear the legacy data in the DMA buffer
+
+ ESP_ERROR_CHECK(i2s_new_channel(&mic_chan_config, NULL, &rx_handle_));
+ ESP_ERROR_CHECK(i2s_new_channel(&spkr_chan_config, &tx_handle_, NULL));
+
+ i2s_pdm_rx_config_t mic_config = {
+ .clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(static_cast<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_STEREO),
+ .gpio_cfg = {
+ .clk = mic_ws,
+ .din = mic_data,
+ .invert_flags = {
+ .clk_inv = false,
+ },
+ },
+ };
+
+ i2s_std_config_t spkr_config = {
+ .clk_cfg ={
+ .sample_rate_hz = static_cast<uint32_t>(11025),
+ .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 = I2S_STD_PHILIPS_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_STEREO),
+ .gpio_cfg ={
+ .mclk = I2S_GPIO_UNUSED,
+ .bclk = spkr_bclk,
+ .ws = spkr_lrclk,
+ .dout = spkr_data,
+ .din = I2S_GPIO_UNUSED,
+ .invert_flags = {
+ .mclk_inv = false,
+ .bclk_inv = false,
+ .ws_inv = false
+ }
+ }
+ };
+
+ ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle_, &mic_config));
+ ESP_ERROR_CHECK(i2s_channel_init_std_mode(tx_handle_, &spkr_config));
+ ESP_LOGI(TAG, "Voice hardware created");
+}
+
+void Tdisplays3promvsrloraAudioCodec::SetOutputVolume(int volume) {
+ volume_ = volume;
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void Tdisplays3promvsrloraAudioCodec::EnableInput(bool enable) {
+ gpio_set_level(AUDIO_MIC_ENABLE, !enable);
+ AudioCodec::EnableInput(enable);
+}
+
+void Tdisplays3promvsrloraAudioCodec::EnableOutput(bool enable) {
+ gpio_set_level(AUDIO_SPKR_ENABLE, enable);
+ AudioCodec::EnableOutput(enable);
+}
+
+int Tdisplays3promvsrloraAudioCodec::Read(int16_t *dest, int samples){
+ if (input_enabled_){
+ size_t bytes_read;
+ i2s_channel_read(rx_handle_, dest, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
+
+ // ESP_LOGI(TAG, "Left: %d\n", dest[0]);
+ // ESP_LOGI(TAG,"Right: %d\n", dest[1]);
+ }
+ return samples;
+}
+
+int Tdisplays3promvsrloraAudioCodec::Write(const int16_t *data, int samples){
+ if (output_enabled_){
+ size_t bytes_read;
+ auto output_data = (int16_t *)malloc(samples * sizeof(int16_t));
+ for (size_t i = 0; i < samples; i++){
+ output_data[i] = (float)data[i] * (float)(volume_ / 100.0);
+ }
+ i2s_channel_write(tx_handle_, output_data, samples * sizeof(int16_t), &bytes_read, portMAX_DELAY);
+ free(output_data);
+ }
+ return samples;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h
new file mode 100644
index 0000000..6612b96
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/lilygo-t-display-s3-pro-mvsrlora/tdisplays3promvsrlora_audio_codec.h
@@ -0,0 +1,37 @@
+#ifndef _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H
+#define _TDISPLAYS3PROMVSRLORA_AUDIO_CODEC_H
+
+#include "audio_codec.h"
+
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+
+class Tdisplays3promvsrloraAudioCodec : 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;
+
+ uint32_t volume_ = 70;
+
+ void CreateVoiceHardware(gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data);
+
+ virtual int Read(int16_t *dest, int samples) override;
+ virtual int Write(const int16_t *data, int samples) override;
+
+public:
+Tdisplays3promvsrloraAudioCodec(int input_sample_rate, int output_sample_rate,
+ gpio_num_t mic_bclk, gpio_num_t mic_ws, gpio_num_t mic_data,
+ gpio_num_t spkr_bclk, gpio_num_t spkr_lrclk, gpio_num_t spkr_data,
+ bool input_reference);
+ virtual ~Tdisplays3promvsrloraAudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/README.md b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/README.md
new file mode 100644
index 0000000..137b029
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/README.md
@@ -0,0 +1,75 @@
+# M5Stack Cardputer Adv
+
+M5Stack Cardputer Adv 鏄竴娆惧熀浜� ESP32-S3FN8 (Stamp-S3A) 鐨勫崱鐗囧紡鐢佃剳銆�
+
+## 纭欢瑙勬牸
+
+| 缁勪欢 | 瑙勬牸 |
+|------|------|
+| MCU | ESP32-S3FN8 @ 240MHz |
+| Flash | 8MB |
+| 鏄剧ず灞� | ST7789V2 1.14" 240x135 |
+| 闊抽缂栬В鐮� | ES8311 |
+| 鍔熸斁 | NS4150B |
+| 楹﹀厠椋� | MEMS |
+| 閿洏 | 56閿� (TCA8418) |
+| IMU | BMI270 |
+| 鐢垫睜 | 1750mAh |
+
+## 寮曡剼瀹氫箟
+
+### 鏄剧ず灞� (ST7789V2)
+| 鍔熻兘 | GPIO |
+|------|------|
+| MOSI | GPIO35 |
+| SCLK | GPIO36 |
+| CS | GPIO37 |
+| DC | GPIO34 |
+| RST | GPIO33 |
+| BL | GPIO38 |
+
+### 闊抽 (ES8311)
+| 鍔熻兘 | GPIO |
+|------|------|
+| I2C SDA | GPIO8 |
+| I2C SCL | GPIO9 |
+| I2S BCLK | GPIO41 |
+| I2S LRCK | GPIO43 |
+| I2S DOUT | GPIO46 |
+| I2S DIN | GPIO42 |
+
+## 浣跨敤鏂规硶
+
+1. 鎸変笅 BOOT 鎸夐挳杩涘叆閰嶇綉妯″紡
+2. 杩炴帴 WiFi 鍚庡嵆鍙娇鐢ㄨ闊冲姪鎵嬪姛鑳�
+
+## 鐑у綍鍙傛暟
+
+鑺墖: ESP32-S3, Flash: 8MB, 妯″紡: DIO, 棰戠巼: 80MHz
+
+| 鍦板潃 | 鏂囦欢 |
+|------|------|
+| 0x0 | bootloader/bootloader.bin |
+| 0x8000 | partition_table/partition-table.bin |
+| 0xd000 | ota_data_initial.bin |
+| 0x20000 | xiaozhi.bin |
+| 0x600000 | generated_assets.bin |
+
+鐑у綍鍛戒护 (build 鐩綍涓� `build-cardputer-adv`):
+
+```bash
+python -m esptool --chip esp32s3 -b 460800 -p PORT \
+ --before default_reset --after hard_reset \
+ write_flash --flash_mode dio --flash_size 8MB --flash_freq 80m \
+ 0x0 build-cardputer-adv/bootloader/bootloader.bin \
+ 0x8000 build-cardputer-adv/partition_table/partition-table.bin \
+ 0xd000 build-cardputer-adv/ota_data_initial.bin \
+ 0x20000 build-cardputer-adv/xiaozhi.bin \
+ 0x600000 build-cardputer-adv/generated_assets.bin
+```
+
+灏� `PORT` 鏇挎崲涓哄疄闄呬覆鍙h澶囪矾寰勶紙濡� `/dev/cu.usbmodem21101`锛夈��
+
+## 鍙傝�冮摼鎺�
+
+- [M5Stack Cardputer Adv 瀹樻柟鏂囨。](https://docs.m5stack.com/en/core/Cardputer-Adv)
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.h
new file mode 100644
index 0000000..bdd17b6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.h
@@ -0,0 +1,59 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// M5Stack Cardputer Adv Board configuration
+// MCU: ESP32-S3FN8 (Stamp-S3A)
+// Display: ST7789V2 1.14" 240x135
+// Audio: ES8311 + NS4150B
+
+#include <driver/gpio.h>
+
+// Audio settings
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+// I2S Audio pins (ES8311)
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41 // SCLK
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_43 // LRCK
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 // DSDIN (MCU -> ES8311)
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_46 // ASDOUT (ES8311 -> MCU)
+
+// I2C pins (shared for ES8311, TCA8418, BMI270)
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_9
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC // NS4150B is always on
+
+// Button
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+// Display ST7789V2 (SPI)
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 135
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_OFFSET_X 40
+#define DISPLAY_OFFSET_Y 53
+
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_35
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_36
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_37
+#define DISPLAY_DC_PIN GPIO_NUM_34
+#define DISPLAY_RST_PIN GPIO_NUM_33
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_38
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+// Keyboard TCA8418 I2C address and interrupt pin
+#define KEYBOARD_TCA8418_ADDR 0x34
+#define KEYBOARD_INT_PIN GPIO_NUM_11
+
+// IMU BMI270 I2C address
+#define IMU_BMI270_ADDR 0x68
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.json b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.json
new file mode 100644
index 0000000..0db2860
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "m5stack-cardputer-adv",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM=n",
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc
new file mode 100644
index 0000000..d30c76f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/m5stack_cardputer_adv.cc
@@ -0,0 +1,358 @@
+#include "wifi_board.h"
+#include "wifi_config_ui.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "tca8418_keyboard.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_common.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+#include <wifi_manager.h>
+#include <ssid_manager.h>
+#include <algorithm>
+#include <memory>
+
+#define TAG "CardputerAdv"
+
+// Backlight uses percentage scale (0-100). Keep a minimum of 30% to avoid a too-dim screen.
+#define MIN_BRIGHTNESS 30
+
+class M5StackCardputerAdvBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ Button boot_button_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Tca8418Keyboard* keyboard_ = nullptr;
+ std::unique_ptr<WifiConfigUI> wifi_config_ui_;
+ bool wifi_config_mode_ = false;
+
+ void InitializeI2c() {
+ ESP_LOGI(TAG, "Initialize I2C bus");
+ 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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ ESP_LOGI(TAG, "I2C device scan:");
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ 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_SCLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGI(TAG, "Initialize ST7789V2 display");
+
+ 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 = 0;
+ io_config.pclk_hz = 40 * 1000 * 1000;
+ io_config.trans_queue_depth = 10;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ io_config.flags.sio_mode = 1; // 3-wire SPI mode (M5GFX uses spi_3wire = true)
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install ST7789 panel driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeKeyboard() {
+ ESP_LOGI(TAG, "Initialize TCA8418 keyboard");
+ keyboard_ = new Tca8418Keyboard(i2c_bus_, KEYBOARD_TCA8418_ADDR, KEYBOARD_INT_PIN);
+ keyboard_->Initialize();
+
+ // Set legacy callback for volume/brightness control
+ keyboard_->SetKeyCallback([this](LegacyKeyCode key) {
+ HandleLegacyKeyPress(key);
+ });
+
+ // Set full key event callback for WiFi config and text input
+ keyboard_->SetKeyEventCallback([this](const KeyEvent& event) {
+ HandleKeyEvent(event);
+ });
+ }
+
+ void HandleKeyEvent(const KeyEvent& event) {
+ // Handle WiFi config mode
+ if (wifi_config_mode_ && wifi_config_ui_) {
+ auto result = wifi_config_ui_->HandleKeyEvent(event);
+ if (result == WifiConfigResult::Connected) {
+ ESP_LOGI(TAG, "WiFi connected via keyboard config");
+ ExitWifiConfigMode();
+ } else if (result == WifiConfigResult::Cancelled) {
+ ESP_LOGI(TAG, "WiFi config cancelled");
+ ExitWifiConfigMode();
+ }
+ return;
+ }
+
+ // Handle W and S keys during WiFi configuring state (scanning screen)
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateWifiConfiguring && event.pressed) {
+ if (event.key_code == KC_W) {
+ ESP_LOGI(TAG, "W key pressed - entering keyboard WiFi config");
+ StartKeyboardWifiConfig();
+ } else if (event.key_code == KC_S) {
+ ESP_LOGI(TAG, "S key pressed - showing saved WiFi list");
+ StartKeyboardWifiConfigSaved();
+ }
+ }
+ }
+
+ void HandleLegacyKeyPress(LegacyKeyCode key) {
+ // Skip if in WiFi config mode
+ if (wifi_config_mode_) {
+ return;
+ }
+
+ auto& app = Application::GetInstance();
+ auto* codec = GetAudioCodec();
+ auto* backlight = GetBacklight();
+
+ switch (key) {
+ case KEY_UP: {
+ // Volume up
+ int current_vol = codec->output_volume();
+ int step = (current_vol <= 20 || current_vol >= 80) ? 1 : 10;
+ int new_vol = std::min(100, current_vol + step);
+ codec->SetOutputVolume(new_vol);
+ char msg[32];
+ snprintf(msg, sizeof(msg), "Volume: %d%%", new_vol);
+ display_->ShowNotification(msg, 1500);
+ ESP_LOGI(TAG, "Volume up: %d%%", new_vol);
+ break;
+ }
+ case KEY_DOWN: {
+ // Volume down
+ int current_vol = codec->output_volume();
+ int step = (current_vol <= 20 || current_vol >= 80) ? 1 : 10;
+ int new_vol = std::max(0, current_vol - step);
+ codec->SetOutputVolume(new_vol);
+ char msg[32];
+ snprintf(msg, sizeof(msg), "Volume: %d%%", new_vol);
+ display_->ShowNotification(msg, 1500);
+ ESP_LOGI(TAG, "Volume down: %d%%", new_vol);
+ break;
+ }
+ case KEY_RIGHT: {
+ // Brightness up
+ uint8_t current_br = backlight->brightness();
+ int step = (current_br <= (MIN_BRIGHTNESS + 20) || current_br >= 80) ? 1 : 10;
+ int new_br = std::min(100, (int)current_br + step);
+ backlight->SetBrightness(new_br, true);
+ char msg[32];
+ snprintf(msg, sizeof(msg), "Brightness: %d%%", new_br);
+ display_->ShowNotification(msg, 1500);
+ ESP_LOGI(TAG, "Brightness up: %d%%", new_br);
+ break;
+ }
+ case KEY_LEFT: {
+ // Brightness down (minimum 30%)
+ uint8_t current_br = backlight->brightness();
+ int step = (current_br <= (MIN_BRIGHTNESS + 20) || current_br >= 80) ? 1 : 10;
+ int new_br = std::max((int)MIN_BRIGHTNESS, (int)current_br - step);
+ backlight->SetBrightness(new_br, true);
+ char msg[32];
+ snprintf(msg, sizeof(msg), "Brightness: %d%%", new_br);
+ display_->ShowNotification(msg, 1500);
+ ESP_LOGI(TAG, "Brightness down: %d%%", new_br);
+ break;
+ }
+ case KEY_ENTER: {
+ // Match boot button behavior (start/stop chat depending on current state).
+ if (app.GetDeviceState() != kDeviceStateStarting) {
+ app.ToggleChatState();
+ ESP_LOGI(TAG, "Enter key: Toggle chat state");
+ }
+ break;
+ }
+ default:
+ break;
+ }
+ }
+
+ void StartKeyboardWifiConfig() {
+ ESP_LOGI(TAG, "Starting keyboard WiFi config UI");
+ wifi_config_mode_ = true;
+ wifi_config_ui_ = std::make_unique<WifiConfigUI>(display_);
+ wifi_config_ui_->SetConnectCallback([this](const std::string& ssid, const std::string& password) {
+ AttemptWifiConnection(ssid, password);
+ });
+ wifi_config_ui_->Start();
+ }
+
+ void StartKeyboardWifiConfigSaved() {
+ ESP_LOGI(TAG, "Starting keyboard WiFi config UI (saved list)");
+ wifi_config_mode_ = true;
+ wifi_config_ui_ = std::make_unique<WifiConfigUI>(display_);
+ wifi_config_ui_->SetConnectCallback([this](const std::string& ssid, const std::string& password) {
+ AttemptWifiConnection(ssid, password);
+ });
+ wifi_config_ui_->StartWithSavedList();
+ }
+
+ void AttemptWifiConnection(const std::string& ssid, const std::string& password) {
+ ESP_LOGI(TAG, "Attempting WiFi connection to: %s", ssid.c_str());
+
+ // Add to SSID manager (will be saved and used for connection)
+ auto& ssid_manager = SsidManager::GetInstance();
+ ssid_manager.AddSsid(ssid, password);
+
+ // Stop config AP mode and trigger reconnection with new credentials
+ auto& wifi_manager = WifiManager::GetInstance();
+ if (wifi_manager.IsConfigMode()) {
+ wifi_manager.StopConfigAp();
+ }
+
+ // Start station mode to connect
+ wifi_manager.StartStation();
+
+ // Wait for connection result (with timeout)
+ bool connected = false;
+ for (int i = 0; i < 100; i++) { // 10 second timeout
+ vTaskDelay(pdMS_TO_TICKS(100));
+ if (wifi_manager.IsConnected()) {
+ connected = true;
+ break;
+ }
+ }
+
+ if (wifi_config_ui_) {
+ wifi_config_ui_->OnConnectResult(connected);
+ }
+ }
+
+ void ExitWifiConfigMode() {
+ ESP_LOGI(TAG, "Exiting keyboard WiFi config mode");
+ wifi_config_mode_ = false;
+ wifi_config_ui_.reset();
+
+ // Restart normal WiFi connection flow
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ // Try to connect with saved credentials
+ TryWifiConnect();
+ }
+ }
+
+public:
+ M5StackCardputerAdvBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ I2cDetect();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ InitializeKeyboard();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ // Cardputer Adv (no MCLK, internal clocking) needs I2S channels
+ // disabled after construction so esp_codec_dev_open can configure
+ // the ES8311 codec before channels start running.
+ static struct CardputerAdvEs8311 : public Es8311AudioCodec {
+ CardputerAdvEs8311(void* i2c, i2c_port_t port, int in_rate, int out_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,
+ uint8_t addr, bool use_mclk)
+ : Es8311AudioCodec(i2c, port, in_rate, out_rate,
+ mclk, bclk, ws, dout, din, pa, addr, use_mclk) {
+ i2s_channel_disable(tx_handle_);
+ i2s_channel_disable(rx_handle_);
+ }
+ } audio_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,
+ false); // use_mclk = false
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ // M5GFX uses 256Hz PWM frequency for Cardputer backlight
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 256);
+ return &backlight;
+ }
+};
+
+DECLARE_BOARD(M5StackCardputerAdvBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.cc
new file mode 100644
index 0000000..2b4511a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.cc
@@ -0,0 +1,418 @@
+#include "tca8418_keyboard.h"
+#include <esp_log.h>
+
+#define TAG "TCA8418"
+
+// TCA8418 additional registers
+#define TCA8418_REG_GPIO_INT_EN_1 0x1A
+#define TCA8418_REG_GPIO_INT_EN_2 0x1B
+#define TCA8418_REG_GPIO_INT_EN_3 0x1C
+#define TCA8418_REG_GPIO_DAT_STAT_1 0x14
+#define TCA8418_REG_GPIO_DAT_STAT_2 0x15
+#define TCA8418_REG_GPIO_DAT_STAT_3 0x16
+#define TCA8418_REG_GPIO_DAT_OUT_1 0x17
+#define TCA8418_REG_GPIO_DAT_OUT_2 0x18
+#define TCA8418_REG_GPIO_DAT_OUT_3 0x19
+#define TCA8418_REG_GPIO_INT_LVL_1 0x20
+#define TCA8418_REG_GPIO_INT_LVL_2 0x21
+#define TCA8418_REG_GPIO_INT_LVL_3 0x22
+#define TCA8418_REG_DEBOUNCE_DIS_1 0x29
+#define TCA8418_REG_DEBOUNCE_DIS_2 0x2A
+#define TCA8418_REG_DEBOUNCE_DIS_3 0x2B
+#define TCA8418_REG_GPIO_PULL_1 0x2C
+#define TCA8418_REG_GPIO_PULL_2 0x2D
+#define TCA8418_REG_GPIO_PULL_3 0x2E
+
+// Config register bits
+#define TCA8418_CFG_AI 0x80 // Auto-increment for read/write
+#define TCA8418_CFG_GPI_E_CFG 0x40 // GPI event mode config
+#define TCA8418_CFG_OVR_FLOW_M 0x20 // Overflow mode
+#define TCA8418_CFG_INT_CFG 0x10 // Interrupt config
+#define TCA8418_CFG_OVR_FLOW_IEN 0x08 // Overflow interrupt enable
+#define TCA8418_CFG_K_LCK_IEN 0x04 // Keypad lock interrupt enable
+#define TCA8418_CFG_GPI_IEN 0x02 // GPI interrupt enable
+
+// Interrupt status bits
+#define TCA8418_INT_STAT_CAD_INT 0x10 // CTRL-ALT-DEL interrupt
+#define TCA8418_INT_STAT_OVR_FLOW 0x08 // Overflow interrupt
+#define TCA8418_INT_STAT_K_LCK_INT 0x04 // Key lock interrupt
+#define TCA8418_INT_STAT_GPI_INT 0x02 // GPI interrupt
+#define TCA8418_INT_STAT_K_INT 0x01 // Key event interrupt
+
+// Key value structure for mapping
+struct KeyValue {
+ const char* normal; // Normal character
+ uint8_t normal_code; // Normal key code
+ const char* shifted; // Shifted character
+ uint8_t shifted_code; // Shifted key code (same as normal for letters)
+};
+
+// 4x14 keyboard matrix mapping (based on M5Cardputer-UserDemo)
+// Row 0: ` 1 2 3 4 5 6 7 8 9 0 - = Del
+// Row 1: Tab Q W E R T Y U I O P [ ] Backslash
+// Row 2: Shift CapsLk A S D F G H J K L ; ' Enter
+// Row 3: Ctrl Opt Alt Z X C V B N M , . / Space
+static const KeyValue KEY_MAP[4][14] = {
+ // Row 0
+ {{"`", KC_GRAVE, "~", KC_GRAVE},
+ {"1", KC_1, "!", KC_1},
+ {"2", KC_2, "@", KC_2},
+ {"3", KC_3, "#", KC_3},
+ {"4", KC_4, "$", KC_4},
+ {"5", KC_5, "%", KC_5},
+ {"6", KC_6, "^", KC_6},
+ {"7", KC_7, "&", KC_7},
+ {"8", KC_8, "*", KC_8},
+ {"9", KC_9, "(", KC_9},
+ {"0", KC_0, ")", KC_0},
+ {"-", KC_MINUS, "_", KC_MINUS},
+ {"=", KC_EQUAL, "+", KC_EQUAL},
+ {"", KC_BACKSPACE, "", KC_BACKSPACE}}, // Del/Backspace
+ // Row 1
+ {{"", KC_TAB, "", KC_TAB}, // Tab
+ {"q", KC_Q, "Q", KC_Q},
+ {"w", KC_W, "W", KC_W},
+ {"e", KC_E, "E", KC_E},
+ {"r", KC_R, "R", KC_R},
+ {"t", KC_T, "T", KC_T},
+ {"y", KC_Y, "Y", KC_Y},
+ {"u", KC_U, "U", KC_U},
+ {"i", KC_I, "I", KC_I},
+ {"o", KC_O, "O", KC_O},
+ {"p", KC_P, "P", KC_P},
+ {"[", KC_LBRACKET, "{", KC_LBRACKET},
+ {"]", KC_RBRACKET, "}", KC_RBRACKET},
+ {"\\", KC_BACKSLASH, "|", KC_BACKSLASH}},
+ // Row 2
+ {{"", KC_LSHIFT, "", KC_LSHIFT}, // Shift
+ {"", KC_CAPSLOCK, "", KC_CAPSLOCK}, // CapsLock
+ {"a", KC_A, "A", KC_A},
+ {"s", KC_S, "S", KC_S},
+ {"d", KC_D, "D", KC_D},
+ {"f", KC_F, "F", KC_F},
+ {"g", KC_G, "G", KC_G},
+ {"h", KC_H, "H", KC_H},
+ {"j", KC_J, "J", KC_J},
+ {"k", KC_K, "K", KC_K},
+ {"l", KC_L, "L", KC_L},
+ {";", KC_SEMICOLON, ":", KC_SEMICOLON},
+ {"'", KC_APOSTROPHE, "\"", KC_APOSTROPHE},
+ {"", KC_ENTER, "", KC_ENTER}}, // Enter
+ // Row 3
+ {{"", KC_LCTRL, "", KC_LCTRL}, // Ctrl
+ {"", KC_LOPT, "", KC_LOPT}, // Opt
+ {"", KC_LALT, "", KC_LALT}, // Alt
+ {"z", KC_Z, "Z", KC_Z},
+ {"x", KC_X, "X", KC_X},
+ {"c", KC_C, "C", KC_C},
+ {"v", KC_V, "V", KC_V},
+ {"b", KC_B, "B", KC_B},
+ {"n", KC_N, "N", KC_N},
+ {"m", KC_M, "M", KC_M},
+ {",", KC_COMMA, "<", KC_COMMA},
+ {".", KC_DOT, ">", KC_DOT},
+ {"/", KC_SLASH, "?", KC_SLASH},
+ {" ", KC_SPACE, " ", KC_SPACE}}
+};
+
+// Cardputer Adv uses TCA8418 in a 7x8 matrix, but the physical keyboard layout
+// matches Cardputer's 4x14 mapping. Remap raw (row,col) from the 7x8 scan into
+// the 4x14 logical layout (based on M5Cardputer-UserDemo CardputerADV branch).
+static inline bool RemapRawKeyToLogical(uint8_t& row, uint8_t& col) {
+ // Raw scan: row 0..6, col 0..7
+ if (row >= 7 || col >= 8) {
+ return false;
+ }
+
+ // Col: every raw row contributes two logical columns (left/right half)
+ uint8_t mapped_col = (row * 2) + ((col > 3) ? 1 : 0); // 0..13
+ // Row: derived from raw col (wrap every 4)
+ uint8_t mapped_row = (col + 4) % 4; // 0..3
+
+ row = mapped_row;
+ col = mapped_col;
+ return true;
+}
+
+static inline uint64_t LogicalKeyMask(uint8_t row, uint8_t col) {
+ // 4x14 = 56 keys, fits in 64-bit
+ uint8_t idx = (row * 14) + col;
+ if (idx >= 64) {
+ return 0;
+ }
+ return 1ULL << idx;
+}
+
+Tca8418Keyboard::Tca8418Keyboard(i2c_master_bus_handle_t i2c_bus, uint8_t addr, gpio_num_t int_pin)
+ : I2cDevice(i2c_bus, addr), int_pin_(int_pin) {
+}
+
+Tca8418Keyboard::~Tca8418Keyboard() {
+ if (task_handle_) {
+ vTaskDelete(task_handle_);
+ task_handle_ = nullptr;
+ }
+ gpio_isr_handler_remove(int_pin_);
+}
+
+void Tca8418Keyboard::Initialize() {
+ ESP_LOGI(TAG, "Initializing TCA8418 keyboard");
+
+ // Configure keyboard matrix
+ ConfigureMatrix();
+
+ // Flush any pending events
+ FlushEvents();
+
+ // Enable interrupts
+ EnableInterrupts();
+
+ // Configure GPIO interrupt pin
+ gpio_config_t io_conf = {};
+ // IRQ is active-low and can stay low while events are pending, so use ANYEDGE.
+ io_conf.intr_type = GPIO_INTR_ANYEDGE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << int_pin_);
+ // Cardputer Adv board provides external pull-ups; keep internal pulls disabled.
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_config(&io_conf);
+
+ // Install GPIO ISR service if not already installed
+ gpio_install_isr_service(0);
+ gpio_isr_handler_add(int_pin_, GpioIsrHandler, this);
+
+ // Create keyboard task
+ xTaskCreate(KeyboardTask, "keyboard_task", 4096, this, 5, &task_handle_);
+
+ ESP_LOGI(TAG, "TCA8418 keyboard initialized");
+}
+
+void Tca8418Keyboard::ConfigureMatrix() {
+ // Cardputer Adv keyboard is wired as a 7x8 matrix (rows: R0-R6, cols: C0-C7).
+ // KP_GPIO1: R0-R7 (bits 0-7)
+ // KP_GPIO2: C0-C7 (bits 0-7)
+ // KP_GPIO3: C8-C9 + GPIO (unused here)
+ WriteReg(TCA8418_REG_KP_GPIO_1, 0x7F); // R0-R6
+ WriteReg(TCA8418_REG_KP_GPIO_2, 0xFF); // C0-C7
+ WriteReg(TCA8418_REG_KP_GPIO_3, 0x00); // no extended cols
+}
+
+void Tca8418Keyboard::EnableInterrupts() {
+ // Enable key event interrupt
+ uint8_t cfg = TCA8418_CFG_KE_IEN | TCA8418_CFG_OVR_FLOW_M | TCA8418_CFG_INT_CFG;
+ WriteReg(TCA8418_REG_CFG, cfg);
+}
+
+void Tca8418Keyboard::FlushEvents() {
+ // Read and discard all pending key events
+ uint8_t event;
+ int count = 0;
+ while ((event = GetEvent()) != 0 && count < 10) {
+ count++;
+ }
+
+ // Clear interrupt status
+ WriteReg(TCA8418_REG_INT_STAT, 0x1F);
+}
+
+uint8_t Tca8418Keyboard::GetEvent() {
+ return ReadReg(TCA8418_REG_KEY_EVENT_A);
+}
+
+void Tca8418Keyboard::UpdateModifierState(uint8_t row, uint8_t col, bool pressed) {
+ // Shift key: row 2, col 0
+ if (row == 2 && col == 0) {
+ if (pressed) {
+ modifier_mask_ |= KEY_MOD_SHIFT;
+ } else {
+ modifier_mask_ &= ~KEY_MOD_SHIFT;
+ }
+ }
+ // Ctrl key: row 3, col 0
+ else if (row == 3 && col == 0) {
+ if (pressed) {
+ modifier_mask_ |= KEY_MOD_CTRL;
+ } else {
+ modifier_mask_ &= ~KEY_MOD_CTRL;
+ }
+ }
+ // Alt key: row 3, col 2
+ else if (row == 3 && col == 2) {
+ if (pressed) {
+ modifier_mask_ |= KEY_MOD_ALT;
+ } else {
+ modifier_mask_ &= ~KEY_MOD_ALT;
+ }
+ }
+ // Opt key: row 3, col 1
+ else if (row == 3 && col == 1) {
+ if (pressed) {
+ modifier_mask_ |= KEY_MOD_OPT;
+ } else {
+ modifier_mask_ &= ~KEY_MOD_OPT;
+ }
+ }
+ // CapsLock key: row 2, col 1 (toggle on press)
+ else if (row == 2 && col == 1 && pressed) {
+ caps_lock_on_ = !caps_lock_on_;
+ ESP_LOGD(TAG, "CapsLock toggled: %s", caps_lock_on_ ? "ON" : "OFF");
+ }
+}
+
+LegacyKeyCode Tca8418Keyboard::MapLegacyKeyCode(uint8_t row, uint8_t col) {
+ // Arrow keys mapping based on M5Cardputer layout:
+ // UP: ; key - row 2, col 11
+ // DOWN: . key - row 3, col 11
+ // LEFT: , key - row 3, col 10
+ // RIGHT: / key - row 3, col 12
+ // ENTER: enter key - row 2, col 13
+
+ if (row == 2 && col == 11) return KEY_UP; // ; key
+ if (row == 3 && col == 11) return KEY_DOWN; // . key
+ if (row == 3 && col == 10) return KEY_LEFT; // , key
+ if (row == 3 && col == 12) return KEY_RIGHT; // / key
+ if (row == 2 && col == 13) return KEY_ENTER; // Enter key
+
+ return KEY_OTHER;
+}
+
+KeyEvent Tca8418Keyboard::MapKeyEvent(uint8_t row, uint8_t col, bool pressed) {
+ KeyEvent event;
+ event.pressed = pressed;
+ event.is_modifier = false;
+ event.key_code = KC_NONE;
+ event.key_char = "";
+
+ if (row >= 4 || col >= 14) {
+ return event;
+ }
+
+ const KeyValue& kv = KEY_MAP[row][col];
+ event.key_code = kv.normal_code;
+
+ // Check if this is a modifier key
+ if (event.key_code == KC_LSHIFT || event.key_code == KC_LCTRL ||
+ event.key_code == KC_LALT || event.key_code == KC_LOPT ||
+ event.key_code == KC_CAPSLOCK) {
+ event.is_modifier = true;
+ event.key_char = "";
+ return event;
+ }
+
+ // Determine if we should use shifted version
+ bool use_shifted = false;
+
+ // Check if this is a letter key (a-z)
+ bool is_letter = (event.key_code >= KC_A && event.key_code <= KC_Z);
+
+ if (is_letter) {
+ // For letters, use XOR of shift and caps lock (Shift reverses CapsLock)
+ bool shift_pressed = (modifier_mask_ & KEY_MOD_SHIFT) != 0;
+ use_shifted = shift_pressed != caps_lock_on_; // XOR semantics
+ } else {
+ // For non-letters (numbers, symbols), only use shift
+ use_shifted = (modifier_mask_ & KEY_MOD_SHIFT) != 0;
+ }
+
+ if (use_shifted) {
+ event.key_char = kv.shifted;
+ } else {
+ event.key_char = kv.normal;
+ }
+
+ return event;
+}
+
+void IRAM_ATTR Tca8418Keyboard::GpioIsrHandler(void* arg) {
+ Tca8418Keyboard* keyboard = static_cast<Tca8418Keyboard*>(arg);
+ keyboard->isr_flag_ = true;
+
+ // Wake up the keyboard task
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ if (keyboard->task_handle_) {
+ vTaskNotifyGiveFromISR(keyboard->task_handle_, &xHigherPriorityTaskWoken);
+ portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
+ }
+}
+
+void Tca8418Keyboard::KeyboardTask(void* arg) {
+ Tca8418Keyboard* keyboard = static_cast<Tca8418Keyboard*>(arg);
+
+ while (true) {
+ // Wait for interrupt notification
+ ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+
+ // Small delay for debounce / allow event FIFO to fill
+ vTaskDelay(pdMS_TO_TICKS(5));
+
+ // Drain pending key events until the IRQ condition clears.
+ for (int guard = 0; guard < 128; guard++) {
+ uint8_t int_stat = keyboard->ReadReg(TCA8418_REG_INT_STAT);
+ if ((int_stat & TCA8418_INT_STAT_K_INT) == 0) {
+ keyboard->isr_flag_ = false;
+ break;
+ }
+
+ uint8_t event = keyboard->GetEvent();
+ if (event == 0) {
+ // No event available, try clearing and re-checking.
+ keyboard->WriteReg(TCA8418_REG_INT_STAT, 0x1F);
+ vTaskDelay(pdMS_TO_TICKS(1));
+ continue;
+ }
+
+ bool pressed = (event & 0x80) != 0;
+ uint8_t key_code = event & 0x7F;
+ if (key_code == 0) {
+ continue;
+ }
+
+ // Raw decode: TCA8418 key code = (row * 10) + col + 1
+ uint8_t raw_row = (key_code - 1) / 10;
+ uint8_t raw_col = (key_code - 1) % 10;
+
+ // Cardputer Adv uses 7x8, so ignore events outside 0..6/0..7.
+ uint8_t row = raw_row;
+ uint8_t col = raw_col;
+ if (!RemapRawKeyToLogical(row, col)) {
+ ESP_LOGD(TAG, "Ignored key: code=%d raw_row=%d raw_col=%d", key_code, raw_row, raw_col);
+ continue;
+ }
+
+ // De-duplicate spurious repeated press/release events (debounce/IRQ quirks).
+ const uint64_t mask = LogicalKeyMask(row, col);
+ if (mask != 0) {
+ const bool was_pressed = (keyboard->key_state_mask_ & mask) != 0;
+ if (pressed == was_pressed) {
+ continue;
+ }
+ if (pressed) {
+ keyboard->key_state_mask_ |= mask;
+ } else {
+ keyboard->key_state_mask_ &= ~mask;
+ }
+ }
+
+ ESP_LOGD(TAG, "Key %s: code=%d raw=(%d,%d) mapped=(%d,%d)",
+ pressed ? "pressed" : "released", key_code, raw_row, raw_col, row, col);
+
+ keyboard->UpdateModifierState(row, col, pressed);
+
+ if (keyboard->key_event_callback_) {
+ KeyEvent key_event = keyboard->MapKeyEvent(row, col, pressed);
+ keyboard->key_event_callback_(key_event);
+ }
+
+ if (pressed && keyboard->key_callback_) {
+ LegacyKeyCode mapped_key = keyboard->MapLegacyKeyCode(row, col);
+ if (mapped_key != KEY_OTHER && mapped_key != KEY_NONE) {
+ keyboard->key_callback_(mapped_key);
+ }
+ }
+ }
+
+ // Clear all interrupt status bits (K_INT, GPI, overflow, etc.)
+ keyboard->WriteReg(TCA8418_REG_INT_STAT, 0x1F);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.h
new file mode 100644
index 0000000..dbcc6a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/tca8418_keyboard.h
@@ -0,0 +1,155 @@
+#ifndef TCA8418_KEYBOARD_H
+#define TCA8418_KEYBOARD_H
+
+#include "i2c_device.h"
+#include <functional>
+#include <driver/gpio.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+// TCA8418 Register definitions
+#define TCA8418_REG_CFG 0x01
+#define TCA8418_REG_INT_STAT 0x02
+#define TCA8418_REG_KEY_LCK_EC 0x03
+#define TCA8418_REG_KEY_EVENT_A 0x04
+#define TCA8418_REG_KP_GPIO_1 0x1D
+#define TCA8418_REG_KP_GPIO_2 0x1E
+#define TCA8418_REG_KP_GPIO_3 0x1F
+
+// Config register bits
+#define TCA8418_CFG_KE_IEN 0x01 // Key events interrupt enable
+
+// Modifier key masks
+enum KeyModifier {
+ KEY_MOD_NONE = 0x00,
+ KEY_MOD_SHIFT = 0x01,
+ KEY_MOD_CTRL = 0x02,
+ KEY_MOD_ALT = 0x04,
+ KEY_MOD_OPT = 0x08,
+};
+
+// HID-compatible key codes
+enum KeyCode {
+ KC_NONE = 0x00,
+ KC_A = 0x04,
+ KC_B = 0x05,
+ KC_C = 0x06,
+ KC_D = 0x07,
+ KC_E = 0x08,
+ KC_F = 0x09,
+ KC_G = 0x0A,
+ KC_H = 0x0B,
+ KC_I = 0x0C,
+ KC_J = 0x0D,
+ KC_K = 0x0E,
+ KC_L = 0x0F,
+ KC_M = 0x10,
+ KC_N = 0x11,
+ KC_O = 0x12,
+ KC_P = 0x13,
+ KC_Q = 0x14,
+ KC_R = 0x15,
+ KC_S = 0x16,
+ KC_T = 0x17,
+ KC_U = 0x18,
+ KC_V = 0x19,
+ KC_W = 0x1A,
+ KC_X = 0x1B,
+ KC_Y = 0x1C,
+ KC_Z = 0x1D,
+ KC_1 = 0x1E,
+ KC_2 = 0x1F,
+ KC_3 = 0x20,
+ KC_4 = 0x21,
+ KC_5 = 0x22,
+ KC_6 = 0x23,
+ KC_7 = 0x24,
+ KC_8 = 0x25,
+ KC_9 = 0x26,
+ KC_0 = 0x27,
+ KC_ENTER = 0x28,
+ KC_ESC = 0x29,
+ KC_BACKSPACE = 0x2A,
+ KC_TAB = 0x2B,
+ KC_SPACE = 0x2C,
+ KC_MINUS = 0x2D,
+ KC_EQUAL = 0x2E,
+ KC_LBRACKET = 0x2F,
+ KC_RBRACKET = 0x30,
+ KC_BACKSLASH = 0x31,
+ KC_SEMICOLON = 0x33,
+ KC_APOSTROPHE = 0x34,
+ KC_GRAVE = 0x35,
+ KC_COMMA = 0x36,
+ KC_DOT = 0x37,
+ KC_SLASH = 0x38,
+ KC_CAPSLOCK = 0x39,
+ KC_RIGHT = 0x4F,
+ KC_LEFT = 0x50,
+ KC_DOWN = 0x51,
+ KC_UP = 0x52,
+ KC_LSHIFT = 0xE1,
+ KC_LCTRL = 0xE0,
+ KC_LALT = 0xE2,
+ KC_LOPT = 0xE3,
+};
+
+// Key event structure with full information
+struct KeyEvent {
+ bool pressed; // true = pressed, false = released
+ bool is_modifier; // true if this is a modifier key
+ uint8_t key_code; // HID key code (KeyCode enum)
+ const char* key_char; // Character representation (e.g., "a", "A", "1", "!")
+};
+
+// Legacy key codes for backward compatibility
+enum LegacyKeyCode {
+ KEY_NONE = 0,
+ KEY_UP,
+ KEY_DOWN,
+ KEY_LEFT,
+ KEY_RIGHT,
+ KEY_ENTER,
+ KEY_OTHER
+};
+
+class Tca8418Keyboard : public I2cDevice {
+public:
+ using KeyCallback = std::function<void(LegacyKeyCode key)>;
+ using KeyEventCallback = std::function<void(const KeyEvent& event)>;
+
+ Tca8418Keyboard(i2c_master_bus_handle_t i2c_bus, uint8_t addr, gpio_num_t int_pin);
+ ~Tca8418Keyboard();
+
+ void Initialize();
+ void SetKeyCallback(KeyCallback callback) { key_callback_ = callback; }
+ void SetKeyEventCallback(KeyEventCallback callback) { key_event_callback_ = callback; }
+
+ // Get current modifier state
+ uint8_t GetModifierMask() const { return modifier_mask_; }
+ bool IsShiftPressed() const { return (modifier_mask_ & KEY_MOD_SHIFT) != 0; }
+ bool IsCapsLockOn() const { return caps_lock_on_; }
+
+private:
+ gpio_num_t int_pin_;
+ KeyCallback key_callback_;
+ KeyEventCallback key_event_callback_;
+ TaskHandle_t task_handle_ = nullptr;
+ volatile bool isr_flag_ = false;
+ uint8_t modifier_mask_ = 0;
+ bool caps_lock_on_ = false;
+ uint64_t key_state_mask_ = 0; // 4x14 logical keys, bit=1 means pressed
+
+ void ConfigureMatrix();
+ void EnableInterrupts();
+ void FlushEvents();
+ uint8_t GetEvent();
+ LegacyKeyCode MapLegacyKeyCode(uint8_t row, uint8_t col);
+ KeyEvent MapKeyEvent(uint8_t row, uint8_t col, bool pressed);
+ void UpdateModifierState(uint8_t row, uint8_t col, bool pressed);
+
+ static void IRAM_ATTR GpioIsrHandler(void* arg);
+ static void KeyboardTask(void* arg);
+};
+
+#endif // TCA8418_KEYBOARD_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.cc
new file mode 100644
index 0000000..73c399b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.cc
@@ -0,0 +1,705 @@
+#include "wifi_config_ui.h"
+#include <esp_log.h>
+#include <esp_wifi.h>
+#include <wifi_manager.h>
+#include <ssid_manager.h>
+#include <cstring>
+
+#define TAG "WifiConfigUI"
+
+WifiConfigUI::WifiConfigUI(LcdDisplay* display)
+ : display_(display),
+ state_(WifiConfigState::Scanning),
+ is_active_(false),
+ selected_index_(0),
+ scroll_offset_(0),
+ saved_selected_index_(0),
+ saved_scroll_offset_(0),
+ input_focus_on_password_(false),
+ cursor_visible_(true),
+ last_cursor_toggle_(0) {
+}
+
+WifiConfigUI::~WifiConfigUI() {
+}
+
+void WifiConfigUI::Start() {
+ ESP_LOGI(TAG, "Starting WiFi config UI");
+ is_active_ = true;
+ state_ = WifiConfigState::Scanning;
+ selected_index_ = 0;
+ scroll_offset_ = 0;
+ input_ssid_.clear();
+ input_password_.clear();
+ selected_ssid_.clear();
+
+ // Load saved WiFi list
+ LoadSavedWifiList();
+
+ // Start scanning
+ StartScanning();
+}
+
+void WifiConfigUI::StartWithSavedList() {
+ ESP_LOGI(TAG, "Starting WiFi config UI with saved list");
+ is_active_ = true;
+ selected_index_ = 0;
+ scroll_offset_ = 0;
+ input_ssid_.clear();
+ input_password_.clear();
+ selected_ssid_.clear();
+
+ // Show saved list directly (ShowSavedList will load the list)
+ ShowSavedList();
+}
+
+void WifiConfigUI::StartScanning() {
+ state_ = WifiConfigState::Scanning;
+
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+ DrawHeader("鎵弿 WiFi 涓�...");
+ DrawFooter("璇风◢鍊�...");
+
+ // Perform WiFi scan
+ DoWifiScan();
+
+ // Show results
+ if (scan_results_.empty()) {
+ lv_obj_clean(canvas);
+ DrawHeader("鏈壘鍒� WiFi");
+ DrawFooter("W:鎵嬪姩杈撳叆 Esc:閫�鍑�");
+ } else {
+ state_ = WifiConfigState::SelectWifi;
+ ShowScanResults();
+ }
+}
+
+void WifiConfigUI::DoWifiScan() {
+ scan_results_.clear();
+
+ // Use WifiManager's scan capability if available, otherwise do direct scan
+ // Note: We need to be careful not to disrupt existing WiFi state
+
+ // Configure scan
+ wifi_scan_config_t scan_config = {};
+ scan_config.show_hidden = false;
+ scan_config.scan_type = WIFI_SCAN_TYPE_ACTIVE;
+ scan_config.scan_time.active.min = 100;
+ scan_config.scan_time.active.max = 300;
+
+ // Start scan (blocking) - WiFi should already be initialized by WifiManager
+ esp_err_t err = esp_wifi_scan_start(&scan_config, true);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "WiFi scan failed: %s", esp_err_to_name(err));
+ return;
+ }
+
+ // Get scan results
+ uint16_t ap_count = 0;
+ esp_wifi_scan_get_ap_num(&ap_count);
+
+ if (ap_count > 0) {
+ wifi_ap_record_t* ap_records = new wifi_ap_record_t[ap_count];
+ esp_wifi_scan_get_ap_records(&ap_count, ap_records);
+
+ for (int i = 0; i < ap_count && i < 20; i++) {
+ WifiScanResult result;
+ result.ssid = std::string(reinterpret_cast<char*>(ap_records[i].ssid));
+ result.rssi = ap_records[i].rssi;
+ result.is_encrypted = (ap_records[i].authmode != WIFI_AUTH_OPEN);
+
+ // Skip empty SSIDs
+ if (!result.ssid.empty()) {
+ scan_results_.push_back(result);
+ }
+ }
+
+ delete[] ap_records;
+ }
+
+ ESP_LOGI(TAG, "Found %d WiFi networks", (int)scan_results_.size());
+}
+
+void WifiConfigUI::ShowScanResults() {
+ DrawWifiList(scan_results_, selected_index_, scroll_offset_);
+}
+
+void WifiConfigUI::ShowPasswordInput() {
+ // Only clear password and set state on first entry (not on redraw)
+ if (state_ != WifiConfigState::InputPassword) {
+ state_ = WifiConfigState::InputPassword;
+ input_password_.clear();
+ }
+
+ RedrawPasswordInput();
+}
+
+void WifiConfigUI::RedrawPasswordInput() {
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ DrawHeader("杈撳叆瀵嗙爜");
+
+ // Show selected SSID
+ lv_obj_t* label = lv_label_create(canvas);
+ lv_label_set_text_fmt(label, "杩炴帴: %s", selected_ssid_.c_str());
+ lv_obj_set_style_text_color(label, lv_color_hex(0x00FF00), 0);
+ lv_obj_align(label, LV_ALIGN_TOP_LEFT, 5, 5);
+
+ lv_obj_t* pwd_label = lv_label_create(canvas);
+ lv_label_set_text(pwd_label, "璇疯緭鍏ュ瘑鐮�:");
+ lv_obj_set_style_text_color(pwd_label, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_align(pwd_label, LV_ALIGN_TOP_LEFT, 5, 30);
+
+ lv_obj_t* input_label = lv_label_create(canvas);
+ std::string display_pwd(input_password_.length(), '*');
+ display_pwd += cursor_visible_ ? "_" : " ";
+ lv_label_set_text_fmt(input_label, ">>> %s", display_pwd.c_str());
+ lv_obj_set_style_text_color(input_label, lv_color_hex(0xFFFF00), 0);
+ lv_obj_align(input_label, LV_ALIGN_TOP_LEFT, 5, 55);
+
+ DrawFooter("Enter:纭 Esc:杩斿洖");
+}
+
+void WifiConfigUI::ShowManualInput() {
+ // Only clear inputs and set state on first entry (not on redraw)
+ if (state_ != WifiConfigState::InputSsid && state_ != WifiConfigState::InputManualPwd) {
+ state_ = WifiConfigState::InputSsid;
+ input_ssid_.clear();
+ input_password_.clear();
+ input_focus_on_password_ = false;
+ }
+
+ RedrawManualInput();
+}
+
+void WifiConfigUI::RedrawManualInput() {
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ DrawHeader("鎵嬪姩璁剧疆 WiFi");
+
+ lv_obj_t* ssid_label = lv_label_create(canvas);
+ lv_label_set_text(ssid_label, "SSID:");
+ lv_obj_set_style_text_color(ssid_label, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_align(ssid_label, LV_ALIGN_TOP_LEFT, 5, 25);
+
+ lv_obj_t* ssid_input = lv_label_create(canvas);
+ std::string ssid_display = ">>> " + input_ssid_;
+ if (!input_focus_on_password_) {
+ ssid_display += cursor_visible_ ? "_" : " ";
+ }
+ lv_label_set_text(ssid_input, ssid_display.c_str());
+ lv_obj_set_style_text_color(ssid_input, input_focus_on_password_ ? lv_color_hex(0x888888) : lv_color_hex(0xFFFF00), 0);
+ lv_obj_align(ssid_input, LV_ALIGN_TOP_LEFT, 5, 45);
+
+ lv_obj_t* pwd_label = lv_label_create(canvas);
+ lv_label_set_text(pwd_label, "瀵嗙爜:");
+ lv_obj_set_style_text_color(pwd_label, lv_color_hex(0xFFFFFF), 0);
+ lv_obj_align(pwd_label, LV_ALIGN_TOP_LEFT, 5, 70);
+
+ lv_obj_t* pwd_input = lv_label_create(canvas);
+ std::string pwd_display = ">>> " + std::string(input_password_.length(), '*');
+ if (input_focus_on_password_) {
+ pwd_display += cursor_visible_ ? "_" : " ";
+ }
+ lv_label_set_text(pwd_input, pwd_display.c_str());
+ lv_obj_set_style_text_color(pwd_input, input_focus_on_password_ ? lv_color_hex(0xFFFF00) : lv_color_hex(0x888888), 0);
+ lv_obj_align(pwd_input, LV_ALIGN_TOP_LEFT, 5, 90);
+
+ DrawFooter("Tab:鍒囨崲 Enter:纭 Esc:杩斿洖");
+}
+
+void WifiConfigUI::ShowSavedList() {
+ state_ = WifiConfigState::SavedList;
+ saved_selected_index_ = 0;
+ saved_scroll_offset_ = 0;
+
+ LoadSavedWifiList();
+ DrawSavedWifiList();
+}
+
+void WifiConfigUI::DrawSavedWifiList() {
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ char title[48];
+ snprintf(title, sizeof(title), "宸蹭繚瀛樼殑 WiFi (%d/10)", (int)saved_wifi_list_.size());
+ DrawHeader(title);
+
+ if (saved_wifi_list_.empty()) {
+ lv_obj_t* empty_label = lv_label_create(canvas);
+ lv_label_set_text(empty_label, "娌℃湁宸蹭繚瀛樼殑 WiFi");
+ lv_obj_set_style_text_color(empty_label, lv_color_hex(0x888888), 0);
+ lv_obj_align(empty_label, LV_ALIGN_CENTER, 0, 0);
+ DrawFooter("Esc:杩斿洖");
+ return;
+ }
+
+ int y_offset = 25;
+ int visible_count = std::min((int)saved_wifi_list_.size() - saved_scroll_offset_, MAX_VISIBLE_ITEMS);
+
+ for (int i = 0; i < visible_count; i++) {
+ int idx = saved_scroll_offset_ + i;
+ bool is_selected = (idx == saved_selected_index_);
+
+ lv_obj_t* item_label = lv_label_create(canvas);
+ char item_text[48];
+ snprintf(item_text, sizeof(item_text), "%s %d. %s",
+ is_selected ? ">" : " ",
+ idx + 1,
+ saved_wifi_list_[idx].first.c_str());
+ lv_label_set_text(item_label, item_text);
+ lv_obj_set_style_text_color(item_label, is_selected ? lv_color_hex(0x00FF00) : lv_color_hex(0xFFFFFF), 0);
+ lv_obj_align(item_label, LV_ALIGN_TOP_LEFT, 5, y_offset);
+ y_offset += 20;
+ }
+
+ DrawFooter("鈫戔啌:閫夋嫨 Enter:杩炴帴 Del:鍒犻櫎 Esc:杩斿洖");
+}
+
+void WifiConfigUI::ShowConnecting() {
+ state_ = WifiConfigState::Connecting;
+
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ DrawHeader("杩炴帴涓�...");
+
+ lv_obj_t* ssid_label = lv_label_create(canvas);
+ lv_label_set_text_fmt(ssid_label, "姝e湪杩炴帴: %s", selected_ssid_.c_str());
+ lv_obj_set_style_text_color(ssid_label, lv_color_hex(0xFFFF00), 0);
+ lv_obj_align(ssid_label, LV_ALIGN_CENTER, 0, 0);
+
+ DrawFooter("璇风◢鍊�...");
+}
+
+void WifiConfigUI::ShowSuccess() {
+ state_ = WifiConfigState::Success;
+
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ DrawHeader("杩炴帴鎴愬姛!");
+
+ lv_obj_t* ssid_label = lv_label_create(canvas);
+ lv_label_set_text_fmt(ssid_label, "宸茶繛鎺�: %s", selected_ssid_.c_str());
+ lv_obj_set_style_text_color(ssid_label, lv_color_hex(0x00FF00), 0);
+ lv_obj_align(ssid_label, LV_ALIGN_CENTER, 0, -10);
+
+ lv_obj_t* saved_label = lv_label_create(canvas);
+ lv_label_set_text(saved_label, "WiFi 閰嶇疆宸蹭繚瀛�");
+ lv_obj_set_style_text_color(saved_label, lv_color_hex(0x00FFFF), 0);
+ lv_obj_align(saved_label, LV_ALIGN_CENTER, 0, 15);
+
+ DrawFooter("Enter:缁х画");
+}
+
+void WifiConfigUI::ShowFailed() {
+ state_ = WifiConfigState::Failed;
+
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ DrawHeader("杩炴帴澶辫触");
+
+ lv_obj_t* ssid_label = lv_label_create(canvas);
+ lv_label_set_text_fmt(ssid_label, "鏃犳硶杩炴帴: %s", selected_ssid_.c_str());
+ lv_obj_set_style_text_color(ssid_label, lv_color_hex(0xFF0000), 0);
+ lv_obj_align(ssid_label, LV_ALIGN_CENTER, 0, 0);
+
+ DrawFooter("Enter:閲嶈瘯 Esc:杩斿洖");
+}
+
+void WifiConfigUI::DrawHeader(const char* title) {
+ lv_obj_t* canvas = lv_scr_act();
+
+ lv_obj_t* header = lv_label_create(canvas);
+ lv_label_set_text(header, title);
+ lv_obj_set_style_text_color(header, lv_color_hex(0x00FFFF), 0);
+ lv_obj_align(header, LV_ALIGN_TOP_LEFT, 5, 2);
+}
+
+void WifiConfigUI::DrawFooter(const char* hint) {
+ lv_obj_t* canvas = lv_scr_act();
+
+ lv_obj_t* footer = lv_label_create(canvas);
+ lv_label_set_text(footer, hint);
+ lv_obj_set_style_text_color(footer, lv_color_hex(0x888888), 0);
+ lv_obj_set_style_text_font(footer, &lv_font_montserrat_14, 0);
+ lv_obj_align(footer, LV_ALIGN_BOTTOM_LEFT, 5, -2);
+}
+
+void WifiConfigUI::DrawWifiList(const std::vector<WifiScanResult>& list, int selected, int scroll) {
+ lv_obj_t* canvas = lv_scr_act();
+ lv_obj_clean(canvas);
+
+ DrawHeader("閫夋嫨 WiFi");
+
+ int y_offset = 25;
+ int visible_count = std::min((int)list.size() - scroll, MAX_VISIBLE_ITEMS);
+
+ for (int i = 0; i < visible_count; i++) {
+ int idx = scroll + i;
+ bool is_selected = (idx == selected);
+ const WifiScanResult& wifi = list[idx];
+
+ lv_obj_t* item_label = lv_label_create(canvas);
+ std::string signal = GetSignalBars(wifi.rssi);
+ char item_text[64];
+ snprintf(item_text, sizeof(item_text), "%s%d.%-12s %4ddBm %s",
+ is_selected ? ">" : " ",
+ idx + 1,
+ wifi.ssid.substr(0, 12).c_str(),
+ wifi.rssi,
+ signal.c_str());
+ lv_label_set_text(item_label, item_text);
+ lv_obj_set_style_text_color(item_label, is_selected ? lv_color_hex(0x00FF00) : lv_color_hex(0xFFFFFF), 0);
+ lv_obj_align(item_label, LV_ALIGN_TOP_LEFT, 2, y_offset);
+ y_offset += 20;
+ }
+
+ DrawFooter("鈫戔啌:閫夋嫨 Enter:杩炴帴 W:鎵嬪姩 S:宸蹭繚瀛�");
+}
+
+std::string WifiConfigUI::GetSignalBars(int8_t rssi) {
+ if (rssi >= -50) return "鈻堚枅鈻堚枅";
+ if (rssi >= -60) return "鈻堚枅鈻堚枒";
+ if (rssi >= -70) return "鈻堚枅鈻戔枒";
+ if (rssi >= -80) return "鈻堚枒鈻戔枒";
+ return "鈻戔枒鈻戔枒";
+}
+
+void WifiConfigUI::LoadSavedWifiList() {
+ saved_wifi_list_.clear();
+ auto& ssid_manager = SsidManager::GetInstance();
+ const auto& ssid_list = ssid_manager.GetSsidList();
+
+ for (const auto& item : ssid_list) {
+ saved_wifi_list_.push_back({item.ssid, item.password});
+ }
+}
+
+void WifiConfigUI::SaveWifiCredentials(const std::string& ssid, const std::string& password) {
+ auto& ssid_manager = SsidManager::GetInstance();
+ ssid_manager.AddSsid(ssid, password);
+ ESP_LOGI(TAG, "Saved WiFi credentials for: %s", ssid.c_str());
+}
+
+void WifiConfigUI::DeleteSavedWifi(int index) {
+ if (index >= 0 && index < (int)saved_wifi_list_.size()) {
+ auto& ssid_manager = SsidManager::GetInstance();
+ ssid_manager.RemoveSsid(index);
+ ESP_LOGI(TAG, "Deleted saved WiFi at index: %d", index);
+ LoadSavedWifiList();
+ }
+}
+
+void WifiConfigUI::AttemptConnection() {
+ ShowConnecting();
+
+ if (connect_callback_) {
+ connect_callback_(selected_ssid_, input_password_);
+ }
+}
+
+void WifiConfigUI::OnConnectResult(bool success) {
+ if (success) {
+ SaveWifiCredentials(selected_ssid_, input_password_);
+ ShowSuccess();
+ } else {
+ ShowFailed();
+ }
+}
+
+WifiConfigResult WifiConfigUI::HandleKeyEvent(const KeyEvent& event) {
+ // Only handle key press events, skip modifiers
+ if (!event.pressed || event.is_modifier) {
+ return WifiConfigResult::None;
+ }
+
+ // Check for ESC to cancel from Scanning or SelectWifi states
+ // (other states handle ESC in their own handlers to navigate back)
+ if (event.key_code == KC_ESC) {
+ if (state_ == WifiConfigState::Scanning ||
+ state_ == WifiConfigState::SelectWifi) {
+ is_active_ = false;
+ return WifiConfigResult::Cancelled;
+ }
+ }
+
+ // Check if not active (was cancelled in a handler)
+ if (!is_active_) {
+ return WifiConfigResult::Cancelled;
+ }
+
+ switch (state_) {
+ case WifiConfigState::Scanning:
+ HandleScanningKey(event);
+ break;
+ case WifiConfigState::SelectWifi:
+ HandleSelectWifiKey(event);
+ break;
+ case WifiConfigState::InputPassword:
+ HandlePasswordInputKey(event);
+ break;
+ case WifiConfigState::InputSsid:
+ case WifiConfigState::InputManualPwd:
+ HandleManualInputKey(event);
+ break;
+ case WifiConfigState::SavedList:
+ HandleSavedListKey(event);
+ break;
+ case WifiConfigState::Connecting:
+ HandleConnectingKey(event);
+ break;
+ case WifiConfigState::Success:
+ HandleResultKey(event);
+ if (event.key_code == KC_ENTER) {
+ is_active_ = false;
+ return WifiConfigResult::Connected;
+ }
+ break;
+ case WifiConfigState::Failed:
+ HandleResultKey(event);
+ break;
+ }
+
+ // Check if cancelled by a handler
+ if (!is_active_) {
+ return WifiConfigResult::Cancelled;
+ }
+
+ return WifiConfigResult::None;
+}
+
+void WifiConfigUI::HandleScanningKey(const KeyEvent& event) {
+ if (event.key_code == KC_W) {
+ ShowManualInput();
+ } else if (event.key_code == KC_S) {
+ ShowSavedList();
+ }
+ // ESC is handled in HandleKeyEvent
+}
+
+void WifiConfigUI::HandleSelectWifiKey(const KeyEvent& event) {
+ switch (event.key_code) {
+ case KC_UP:
+ case KC_SEMICOLON: // ; key as UP
+ if (selected_index_ > 0) {
+ selected_index_--;
+ if (selected_index_ < scroll_offset_) {
+ scroll_offset_ = selected_index_;
+ }
+ ShowScanResults();
+ }
+ break;
+
+ case KC_DOWN:
+ case KC_DOT: // . key as DOWN
+ if (selected_index_ < (int)scan_results_.size() - 1) {
+ selected_index_++;
+ if (selected_index_ >= scroll_offset_ + MAX_VISIBLE_ITEMS) {
+ scroll_offset_ = selected_index_ - MAX_VISIBLE_ITEMS + 1;
+ }
+ ShowScanResults();
+ }
+ break;
+
+ case KC_ENTER:
+ if (!scan_results_.empty()) {
+ selected_ssid_ = scan_results_[selected_index_].ssid;
+ ShowPasswordInput();
+ }
+ break;
+
+ case KC_W:
+ ShowManualInput();
+ break;
+
+ case KC_S:
+ ShowSavedList();
+ break;
+
+ default:
+ break;
+ }
+ // ESC is handled in HandleKeyEvent
+}
+
+void WifiConfigUI::HandlePasswordInputKey(const KeyEvent& event) {
+ switch (event.key_code) {
+ case KC_ENTER:
+ if (!input_password_.empty()) {
+ AttemptConnection();
+ }
+ break;
+
+ case KC_ESC:
+ state_ = WifiConfigState::SelectWifi;
+ ShowScanResults();
+ break;
+
+ case KC_BACKSPACE:
+ if (!input_password_.empty()) {
+ input_password_.pop_back();
+ RedrawPasswordInput();
+ }
+ break;
+
+ case KC_SPACE:
+ if (input_password_.length() < MAX_INPUT_LENGTH) {
+ input_password_ += ' ';
+ RedrawPasswordInput();
+ }
+ break;
+
+ default:
+ // Add character if it's a printable key
+ if (event.key_char && strlen(event.key_char) > 0 && input_password_.length() < MAX_INPUT_LENGTH) {
+ input_password_ += event.key_char;
+ RedrawPasswordInput();
+ }
+ break;
+ }
+}
+
+void WifiConfigUI::HandleManualInputKey(const KeyEvent& event) {
+ std::string* current_input = input_focus_on_password_ ? &input_password_ : &input_ssid_;
+
+ switch (event.key_code) {
+ case KC_TAB:
+ input_focus_on_password_ = !input_focus_on_password_;
+ if (input_focus_on_password_) {
+ state_ = WifiConfigState::InputManualPwd;
+ } else {
+ state_ = WifiConfigState::InputSsid;
+ }
+ RedrawManualInput();
+ break;
+
+ case KC_ENTER:
+ if (!input_ssid_.empty()) {
+ selected_ssid_ = input_ssid_;
+ AttemptConnection();
+ }
+ break;
+
+ case KC_ESC:
+ state_ = WifiConfigState::SelectWifi;
+ ShowScanResults();
+ break;
+
+ case KC_BACKSPACE:
+ if (!current_input->empty()) {
+ current_input->pop_back();
+ RedrawManualInput();
+ }
+ break;
+
+ case KC_SPACE:
+ if (current_input->length() < MAX_INPUT_LENGTH) {
+ *current_input += ' ';
+ RedrawManualInput();
+ }
+ break;
+
+ default:
+ // Add character if it's a printable key
+ if (event.key_char && strlen(event.key_char) > 0 && current_input->length() < MAX_INPUT_LENGTH) {
+ *current_input += event.key_char;
+ RedrawManualInput();
+ }
+ break;
+ }
+}
+
+void WifiConfigUI::HandleSavedListKey(const KeyEvent& event) {
+ switch (event.key_code) {
+ case KC_UP:
+ case KC_SEMICOLON:
+ if (saved_selected_index_ > 0) {
+ saved_selected_index_--;
+ if (saved_selected_index_ < saved_scroll_offset_) {
+ saved_scroll_offset_ = saved_selected_index_;
+ }
+ DrawSavedWifiList();
+ }
+ break;
+
+ case KC_DOWN:
+ case KC_DOT:
+ if (saved_selected_index_ < (int)saved_wifi_list_.size() - 1) {
+ saved_selected_index_++;
+ if (saved_selected_index_ >= saved_scroll_offset_ + MAX_VISIBLE_ITEMS) {
+ saved_scroll_offset_ = saved_selected_index_ - MAX_VISIBLE_ITEMS + 1;
+ }
+ DrawSavedWifiList();
+ }
+ break;
+
+ case KC_ENTER:
+ if (!saved_wifi_list_.empty()) {
+ selected_ssid_ = saved_wifi_list_[saved_selected_index_].first;
+ input_password_ = saved_wifi_list_[saved_selected_index_].second;
+ AttemptConnection();
+ }
+ break;
+
+ case KC_BACKSPACE: // Del key for delete
+ if (!saved_wifi_list_.empty()) {
+ DeleteSavedWifi(saved_selected_index_);
+ if (saved_selected_index_ >= (int)saved_wifi_list_.size() && saved_selected_index_ > 0) {
+ saved_selected_index_--;
+ }
+ DrawSavedWifiList();
+ }
+ break;
+
+ case KC_ESC:
+ state_ = WifiConfigState::SelectWifi;
+ ShowScanResults();
+ break;
+
+ default:
+ break;
+ }
+}
+
+void WifiConfigUI::HandleConnectingKey(const KeyEvent& event) {
+ // No key handling during connection
+ (void)event;
+}
+
+void WifiConfigUI::HandleResultKey(const KeyEvent& event) {
+ if (state_ == WifiConfigState::Success) {
+ if (event.key_code == KC_ENTER) {
+ // Will be handled in HandleKeyEvent to return Connected
+ }
+ } else if (state_ == WifiConfigState::Failed) {
+ if (event.key_code == KC_ENTER) {
+ // Retry - go back to password input (keep password for retry)
+ state_ = WifiConfigState::InputPassword;
+ RedrawPasswordInput();
+ } else if (event.key_code == KC_ESC) {
+ state_ = WifiConfigState::SelectWifi;
+ ShowScanResults();
+ }
+ }
+}
+
+void WifiConfigUI::UpdateCursor() {
+ uint32_t now = esp_log_timestamp();
+ if (now - last_cursor_toggle_ >= CURSOR_BLINK_MS) {
+ cursor_visible_ = !cursor_visible_;
+ last_cursor_toggle_ = now;
+
+ // Refresh display for input states (use Redraw functions to avoid clearing input)
+ if (state_ == WifiConfigState::InputPassword) {
+ RedrawPasswordInput();
+ } else if (state_ == WifiConfigState::InputSsid || state_ == WifiConfigState::InputManualPwd) {
+ RedrawManualInput();
+ }
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.h
new file mode 100644
index 0000000..7963975
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-cardputer-adv/wifi_config_ui.h
@@ -0,0 +1,133 @@
+#ifndef WIFI_CONFIG_UI_H
+#define WIFI_CONFIG_UI_H
+
+#include "tca8418_keyboard.h"
+#include "display/lcd_display.h"
+#include <string>
+#include <vector>
+#include <functional>
+
+// WiFi scan result structure
+struct WifiScanResult {
+ std::string ssid;
+ int8_t rssi;
+ bool is_encrypted;
+};
+
+// WiFi configuration UI state machine
+enum class WifiConfigState {
+ Scanning, // Scanning for WiFi networks
+ SelectWifi, // Selecting from WiFi list
+ InputPassword, // Entering password for selected WiFi
+ InputSsid, // Manual SSID input
+ InputManualPwd, // Manual password input (after SSID)
+ SavedList, // Viewing saved WiFi list
+ Connecting, // Connecting to WiFi
+ Success, // Connection successful
+ Failed // Connection failed
+};
+
+// Result of WiFi configuration
+enum class WifiConfigResult {
+ None, // Still in progress
+ Connected, // Successfully connected
+ Cancelled // User cancelled
+};
+
+class WifiConfigUI {
+public:
+ using ConnectCallback = std::function<void(const std::string& ssid, const std::string& password)>;
+
+ WifiConfigUI(LcdDisplay* display);
+ ~WifiConfigUI();
+
+ // Start the WiFi configuration UI
+ void Start();
+
+ // Start directly with saved WiFi list
+ void StartWithSavedList();
+
+ // Handle keyboard events, returns result
+ WifiConfigResult HandleKeyEvent(const KeyEvent& event);
+
+ // Set callback for when connection should be attempted
+ void SetConnectCallback(ConnectCallback callback) { connect_callback_ = callback; }
+
+ // Notify connection result
+ void OnConnectResult(bool success);
+
+ // Check if UI is active
+ bool IsActive() const { return is_active_; }
+
+ // Update cursor blink state (call periodically from main loop)
+ void UpdateCursor();
+
+private:
+ LcdDisplay* display_;
+ WifiConfigState state_;
+ bool is_active_;
+ ConnectCallback connect_callback_;
+
+ // WiFi scan results
+ std::vector<WifiScanResult> scan_results_;
+ int selected_index_;
+ int scroll_offset_;
+
+ // Saved WiFi list
+ std::vector<std::pair<std::string, std::string>> saved_wifi_list_;
+ int saved_selected_index_;
+ int saved_scroll_offset_;
+
+ // Input buffers
+ std::string input_ssid_;
+ std::string input_password_;
+ std::string selected_ssid_;
+ bool input_focus_on_password_; // For manual input: true = password field, false = ssid field
+
+ // Cursor blinking
+ bool cursor_visible_;
+ uint32_t last_cursor_toggle_;
+ static constexpr uint32_t CURSOR_BLINK_MS = 500;
+
+ // Display constants
+ static constexpr int MAX_VISIBLE_ITEMS = 4;
+ static constexpr int MAX_INPUT_LENGTH = 64;
+
+ // State handlers
+ void StartScanning();
+ void ShowScanResults();
+ void ShowPasswordInput();
+ void ShowManualInput();
+ void ShowSavedList();
+ void ShowConnecting();
+ void ShowSuccess();
+ void ShowFailed();
+
+ // Redraw functions (don't reset state/input)
+ void RedrawPasswordInput();
+ void RedrawManualInput();
+
+ // Input handlers
+ void HandleScanningKey(const KeyEvent& event);
+ void HandleSelectWifiKey(const KeyEvent& event);
+ void HandlePasswordInputKey(const KeyEvent& event);
+ void HandleManualInputKey(const KeyEvent& event);
+ void HandleSavedListKey(const KeyEvent& event);
+ void HandleConnectingKey(const KeyEvent& event);
+ void HandleResultKey(const KeyEvent& event);
+
+ // Helper functions
+ void DrawHeader(const char* title);
+ void DrawFooter(const char* hint);
+ void DrawInputField(const char* label, const std::string& value, bool is_password, bool is_active);
+ void DrawWifiList(const std::vector<WifiScanResult>& list, int selected, int scroll_offset);
+ void DrawSavedWifiList();
+ std::string GetSignalBars(int8_t rssi);
+ void LoadSavedWifiList();
+ void SaveWifiCredentials(const std::string& ssid, const std::string& password);
+ void DeleteSavedWifi(int index);
+ void DoWifiScan();
+ void AttemptConnection();
+};
+
+#endif // WIFI_CONFIG_UI_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/README.md
new file mode 100644
index 0000000..d7aa9b8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/README.md
@@ -0,0 +1,22 @@
+# 浣跨敤璇存槑
+
+
+**缂栬瘧**
+
+```bash
+python ./scripts/release.py m5stack-core-s3
+```
+
+濡傞渶鎵嬪姩缂栬瘧锛岃鍙傝�� `m5stack-core-s3/config.json` 淇敼 menuconfig 瀵瑰簲閫夐」銆�
+
+**鐑у綍**
+
+```bash
+idf.py flash
+```
+
+> [!NOTE]
+> 杩涘叆涓嬭浇妯″紡锛氶暱鎸夊浣嶆寜閿�(绾�3绉�)锛岀洿鑷冲唴閮ㄦ寚绀虹伅浜豢鑹诧紝鏉惧紑鎸夐敭銆�
+
+
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.h
new file mode 100644
index 0000000..e003782
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.h
@@ -0,0 +1,66 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// M5Stack CoreS3 Board configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_33
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_13
+
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_12
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_11
+#define AUDIO_CODEC_AW88298_ADDR AW88298_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA_PIN GPIO_NUM_NC
+#define DISPLAY_SCL_PIN GPIO_NUM_NC
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_NC // 鍍忕礌鏃堕挓 (鍥哄畾鐢� 20MHz 澶栭儴鏅舵尟杈撳叆)
+#define CAMERA_PIN_SIOD GPIO_NUM_NC // 涓茶鏃堕挓 Using existing I2C port
+#define CAMERA_PIN_SIOC GPIO_NUM_NC // 涓茶鏃堕挓 Using existing I2C port
+#define CAMERA_PIN_D0 GPIO_NUM_39
+#define CAMERA_PIN_D1 GPIO_NUM_40
+#define CAMERA_PIN_D2 GPIO_NUM_41
+#define CAMERA_PIN_D3 GPIO_NUM_42
+#define CAMERA_PIN_D4 GPIO_NUM_15
+#define CAMERA_PIN_D5 GPIO_NUM_16
+#define CAMERA_PIN_D6 GPIO_NUM_48
+#define CAMERA_PIN_D7 GPIO_NUM_47
+#define CAMERA_PIN_VSYNC GPIO_NUM_46
+#define CAMERA_PIN_HREF GPIO_NUM_38
+#define CAMERA_PIN_PCLK GPIO_NUM_45
+
+#define XCLK_FREQ_HZ 20000000
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.json
new file mode 100644
index 0000000..84ba5b7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "m5stack-core-s3",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MODE_QUAD=y",
+ "CONFIG_CAMERA_GC0308=y",
+ "CONFIG_CAMERA_GC0308_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_GC0308_DVP_YUV422_320X240_20FPS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.cc
new file mode 100644
index 0000000..84c4af6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.cc
@@ -0,0 +1,246 @@
+#include "cores3_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+
+#define TAG "CoreS3AudioCodec"
+
+CoreS3AudioCodec::CoreS3AudioCodec(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,
+ uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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);
+
+ // Audio Output(Speaker)
+ audio_codec_i2c_cfg_t i2c_cfg = {
+ .port = (i2c_port_t)1,
+ .addr = aw88298_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);
+
+ aw88298_codec_cfg_t aw88298_cfg = {};
+ aw88298_cfg.ctrl_if = out_ctrl_if_;
+ aw88298_cfg.gpio_if = gpio_if_;
+ aw88298_cfg.reset_pin = GPIO_NUM_NC;
+ aw88298_cfg.hw_gain.pa_voltage = 5.0;
+ aw88298_cfg.hw_gain.codec_dac_voltage = 3.3;
+ aw88298_cfg.hw_gain.pa_gain = 1;
+ out_codec_if_ = aw88298_codec_new(&aw88298_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);
+
+ // Audio Input(Microphone)
+ 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;
+ 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, "CoreS3AudioCodec initialized");
+}
+
+CoreS3AudioCodec::~CoreS3AudioCodec() {
+ 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 CoreS3AudioCodec::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_);
+
+ ESP_LOGI(TAG, "Audio IOs: mclk: %d, bclk: %d, ws: %d, dout: %d, din: %d", mclk, bclk, ws, dout, din);
+
+ 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 CoreS3AudioCodec::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void CoreS3AudioCodec::EnableInput(bool enable) {
+ if (enable == input_enabled_) {
+ return;
+ }
+ if (enable) {
+ esp_codec_dev_sample_info_t fs = {
+ .bits_per_sample = 16,
+ .channel = 2,
+ .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 CoreS3AudioCodec::EnableOutput(bool enable) {
+ 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 CoreS3AudioCodec::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 CoreS3AudioCodec::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;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.h
new file mode 100644
index 0000000..4b034b4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/cores3_audio_codec.h
@@ -0,0 +1,37 @@
+#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>
+
+class CoreS3AudioCodec : 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;
+
+ 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:
+ CoreS3AudioCodec(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,
+ uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference);
+ virtual ~CoreS3AudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _BOX_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/m5stack_core_s3.cc
new file mode 100644
index 0000000..7ca34b4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-core-s3/m5stack_core_s3.cc
@@ -0,0 +1,396 @@
+#include "wifi_board.h"
+#include "cores3_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "i2c_device.h"
+#include "axp2101.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_ili9341.h>
+#include <esp_timer.h>
+#include "esp_video.h"
+
+#define TAG "M5StackCoreS3Board"
+
+class Pmic : public Axp2101 {
+public:
+ // Power Init
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ uint8_t data = ReadReg(0x90);
+ data |= 0b10110100;
+ WriteReg(0x90, data);
+ WriteReg(0x99, (0b11110 - 5));
+ WriteReg(0x97, (0b11110 - 2));
+ WriteReg(0x69, 0b00110101);
+ WriteReg(0x30, 0b111111);
+ WriteReg(0x90, 0xBF);
+ WriteReg(0x94, 33 - 5);
+ WriteReg(0x95, 33 - 5);
+ }
+
+ void SetBrightness(uint8_t brightness) {
+ brightness = ((brightness + 641) >> 5);
+ WriteReg(0x99, brightness);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(Pmic *pmic) : pmic_(pmic) {}
+
+ void SetBrightnessImpl(uint8_t brightness) override {
+ pmic_->SetBrightness(target_brightness_);
+ brightness_ = target_brightness_;
+ }
+
+private:
+ Pmic *pmic_;
+};
+
+class Aw9523 : public I2cDevice {
+public:
+ // Exanpd IO Init
+ Aw9523(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x02, 0b00000111); // P0
+ WriteReg(0x03, 0b10001111); // P1
+ WriteReg(0x04, 0b00011000); // CONFIG_P0
+ WriteReg(0x05, 0b00001100); // CONFIG_P1
+ WriteReg(0x11, 0b00010000); // GCR P0 port is Push-Pull mode.
+ WriteReg(0x12, 0b11111111); // LEDMODE_P0
+ WriteReg(0x13, 0b11111111); // LEDMODE_P1
+ }
+
+ void ResetAw88298() {
+ ESP_LOGI(TAG, "Reset AW88298");
+ WriteReg(0x02, 0b00000011);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ WriteReg(0x02, 0b00000111);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+
+ void ResetIli9342() {
+ ESP_LOGI(TAG, "Reset IlI9342");
+ WriteReg(0x03, 0b10000001);
+ vTaskDelay(pdMS_TO_TICKS(20));
+ WriteReg(0x03, 0b10000011);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+};
+
+class Ft6336 : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ Ft6336(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA3);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Ft6336() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ inline const TouchPoint_t& GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class M5StackCoreS3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_;
+ Aw9523* aw9523_;
+ Ft6336* ft6336_;
+ LcdDisplay* display_;
+ EspVideo* camera_;
+ esp_timer_handle_t touchpad_timer_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeAw9523() {
+ ESP_LOGI(TAG, "Init AW9523");
+ aw9523_ = new Aw9523(i2c_bus_, 0x58);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+
+ void PollTouchpad() {
+ static bool was_touched = false;
+ static int64_t touch_start_time = 0;
+ const int64_t TOUCH_THRESHOLD_MS = 500; // 瑙︽懜鏃堕暱闃堝�硷紝瓒呰繃500ms瑙嗕负闀挎寜
+
+ ft6336_->UpdateTouchPoint();
+ auto& touch_point = ft6336_->GetTouchPoint();
+
+ // 妫�娴嬭Е鎽稿紑濮�
+ if (touch_point.num > 0 && !was_touched) {
+ was_touched = true;
+ touch_start_time = esp_timer_get_time() / 1000; // 杞崲涓烘绉�
+ }
+ // 妫�娴嬭Е鎽搁噴鏀�
+ else if (touch_point.num == 0 && was_touched) {
+ was_touched = false;
+ int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time;
+
+ // 鍙湁鐭Е鎵嶈Е鍙�
+ if (touch_duration < TOUCH_THRESHOLD_MS) {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }
+ }
+ }
+
+ void InitializeFt6336TouchPad() {
+ ESP_LOGI(TAG, "Init FT6336");
+ ft6336_ = new Ft6336(i2c_bus_, 0x38);
+
+ // 鍒涘缓瀹氭椂鍣紝20ms 闂撮殧
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ M5StackCoreS3Board* board = (M5StackCoreS3Board*)arg;
+ board->PollTouchpad();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "touchpad_timer",
+ .skip_unhandled_events = true,
+ };
+
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 20 * 1000));
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_37;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_36;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeIli9342Display() {
+ ESP_LOGI(TAG, "Init IlI9342");
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_3;
+ io_config.dc_gpio_num = GPIO_NUM_35;
+ io_config.spi_mode = 2;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_BGR;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9341(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ aw9523_->ResetIli9342();
+
+ 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 InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAMERA_PIN_D0,
+ [1] = CAMERA_PIN_D1,
+ [2] = CAMERA_PIN_D2,
+ [3] = CAMERA_PIN_D3,
+ [4] = CAMERA_PIN_D4,
+ [5] = CAMERA_PIN_D5,
+ [6] = CAMERA_PIN_D6,
+ [7] = CAMERA_PIN_D7,
+ },
+ .vsync_io = CAMERA_PIN_VSYNC,
+ .de_io = CAMERA_PIN_HREF,
+ .pclk_io = CAMERA_PIN_PCLK,
+ .xclk_io = CAMERA_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAMERA_PIN_RESET,
+ .pwdn_pin = CAMERA_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = XCLK_FREQ_HZ,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ camera_->SetHMirror(false);
+ }
+
+public:
+ M5StackCoreS3Board() {
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeAxp2101();
+ InitializeAw9523();
+ I2cDetect();
+ InitializeSpi();
+ InitializeIli9342Display();
+ InitializeCamera();
+ InitializeFt6336TouchPad();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static CoreS3AudioCodec audio_codec(i2c_bus_,
+ 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_AW88298_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual Backlight *GetBacklight() override {
+ static CustomBacklight backlight(pmic_);
+ return &backlight;
+ }
+};
+
+DECLARE_BOARD(M5StackCoreS3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/README.md b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/README.md
new file mode 100644
index 0000000..e2bf77e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/README.md
@@ -0,0 +1,66 @@
+# 浣跨敤璇存槑
+
+* [M5Stack Tab5 docs](https://docs.m5stack.com/zh_CN/core/Tab5)
+
+## 蹇�熶綋楠�
+
+鍒� [M5Burner](https://docs.m5stack.com/zh_CN/uiflow/m5burner/intro) 閫夋嫨 Tab5 鎼滅储灏忔櫤涓嬭浇鍥轰欢
+
+## 鍩虹浣跨敤
+
+* idf version: v6.0-dev
+
+1. 璋冩暣 idf_component.yml
+
+灏�
+```yaml
+ espressif/esp_video:
+ version: ==1.3.1 # for compatibility. update version may need to modify this project code.
+ rules:
+ - if: target not in [esp32]
+```
+淇敼涓�
+```yaml
+ espressif/esp_video:
+ version: '==0.7.0'
+ rules:
+ - if: target not in [esp32]
+ espressif/esp_ipa: '==0.1.0'
+```
+
+* idf version: v5.5.3
+
+閽堝 ESP32-P4 Rev <3.0 鐢ㄦ埛:
+纭繚浣犵殑 sdkconfig.defaults 鍖呭惈:
+
+CONFIG_ESP32P4_SELECTS_REV_LESS_V3=y
+
+鍚﹀垯鐑у啓鐨勬椂鍊欎細鍑虹幇锛�'bootloader/bootloader.bin' requires chip revision in range [v3.0 - v3.99] (this chip is revision v1.x)
+
+2. 浣跨敤 release.py 缂栬瘧
+
+```shell
+python ./scripts/release.py m5stack-tab5
+```
+
+濡傞渶鎵嬪姩缂栬瘧锛岃鍙傝�� `m5stack-tab5/config.json` 淇敼 menuconfig 瀵瑰簲閫夐」銆�
+
+3. 缂栬瘧鐑у綍绋嬪簭
+
+```shell
+idf.py flash monitor
+```
+
+> [!NOTE]
+> 杩涘叆涓嬭浇妯″紡锛氶暱鎸夊浣嶆寜閿紙绾� 2 绉掞級锛岀洿鑷冲唴閮ㄧ豢鑹� LED 鎸囩ず鐏紑濮嬪揩閫熼棯鐑侊紝鏉惧紑鎸夐敭銆�
+
+
+## log
+
+@2025/05/17 娴嬭瘯闂
+
+1. listening... 闇�瑕佺瓑鍑犵鎵嶈兘鑾峰彇璇煶杈撳叆???
+2. 浜害璋冭妭涓嶅
+3. 闊抽噺璋冭妭涓嶅
+
+## TODO
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.h
new file mode 100644
index 0000000..53e9670
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.h
@@ -0,0 +1,325 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+/* ---------------------------------------------------------------- */
+// Audio CODEC ES7210 + ES8311
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_30
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_29
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_27
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_28
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_26
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC // PI4IOE 鎺у埗
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_31
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_32
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+/* ---------------------------------------------------------------- */
+// 鎽勫儚澶寸浉鍏冲弬鏁伴厤缃�
+#define CAMERA_SCL GPIO_NUM_32
+#define CAMERA_SDA GPIO_NUM_31
+#define CAMERA_MCLK GPIO_NUM_36
+
+/* ---------------------------------------------------------------- */
+// 鏄剧ず灞忕浉鍏冲弬鏁伴厤缃�
+#define DISPLAY_WIDTH 720
+#define DISPLAY_HEIGHT 1280
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_22
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define TOUCH_INT_GPIO GPIO_NUM_23 // 瑙︽懜涓柇
+
+const ili9881c_lcd_init_cmd_t tab5_lcd_ili9881c_specific_init_code_default[] = {
+ // {cmd, { data }, data_size, delay}
+ /**** CMD_Page 1 ****/
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0},
+ {0xB7, (uint8_t[]){0x03}, 1, 0}, // set 2 lane
+ /**** CMD_Page 3 ****/
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x03}, 3, 0},
+ {0x01, (uint8_t[]){0x00}, 1, 0},
+ {0x02, (uint8_t[]){0x00}, 1, 0},
+ {0x03, (uint8_t[]){0x73}, 1, 0},
+ {0x04, (uint8_t[]){0x00}, 1, 0},
+ {0x05, (uint8_t[]){0x00}, 1, 0},
+ {0x06, (uint8_t[]){0x08}, 1, 0},
+ {0x07, (uint8_t[]){0x00}, 1, 0},
+ {0x08, (uint8_t[]){0x00}, 1, 0},
+ {0x09, (uint8_t[]){0x1B}, 1, 0},
+ {0x0a, (uint8_t[]){0x01}, 1, 0},
+ {0x0b, (uint8_t[]){0x01}, 1, 0},
+ {0x0c, (uint8_t[]){0x0D}, 1, 0},
+ {0x0d, (uint8_t[]){0x01}, 1, 0},
+ {0x0e, (uint8_t[]){0x01}, 1, 0},
+ {0x0f, (uint8_t[]){0x26}, 1, 0},
+ {0x10, (uint8_t[]){0x26}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 1, 0},
+ {0x12, (uint8_t[]){0x00}, 1, 0},
+ {0x13, (uint8_t[]){0x02}, 1, 0},
+ {0x14, (uint8_t[]){0x00}, 1, 0},
+ {0x15, (uint8_t[]){0x00}, 1, 0},
+ {0x16, (uint8_t[]){0x00}, 1, 0},
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0x00}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1a, (uint8_t[]){0x00}, 1, 0},
+ {0x1b, (uint8_t[]){0x00}, 1, 0},
+ {0x1c, (uint8_t[]){0x00}, 1, 0},
+ {0x1d, (uint8_t[]){0x00}, 1, 0},
+ {0x1e, (uint8_t[]){0x40}, 1, 0},
+ {0x1f, (uint8_t[]){0x00}, 1, 0},
+ {0x20, (uint8_t[]){0x06}, 1, 0},
+ {0x21, (uint8_t[]){0x01}, 1, 0},
+ {0x22, (uint8_t[]){0x00}, 1, 0},
+ {0x23, (uint8_t[]){0x00}, 1, 0},
+ {0x24, (uint8_t[]){0x00}, 1, 0},
+ {0x25, (uint8_t[]){0x00}, 1, 0},
+ {0x26, (uint8_t[]){0x00}, 1, 0},
+ {0x27, (uint8_t[]){0x00}, 1, 0},
+ {0x28, (uint8_t[]){0x33}, 1, 0},
+ {0x29, (uint8_t[]){0x03}, 1, 0},
+ {0x2a, (uint8_t[]){0x00}, 1, 0},
+ {0x2b, (uint8_t[]){0x00}, 1, 0},
+ {0x2c, (uint8_t[]){0x00}, 1, 0},
+ {0x2d, (uint8_t[]){0x00}, 1, 0},
+ {0x2e, (uint8_t[]){0x00}, 1, 0},
+ {0x2f, (uint8_t[]){0x00}, 1, 0},
+ {0x30, (uint8_t[]){0x00}, 1, 0},
+ {0x31, (uint8_t[]){0x00}, 1, 0},
+ {0x32, (uint8_t[]){0x00}, 1, 0},
+ {0x33, (uint8_t[]){0x00}, 1, 0},
+ {0x34, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x36, (uint8_t[]){0x00}, 1, 0},
+ {0x37, (uint8_t[]){0x00}, 1, 0},
+ {0x38, (uint8_t[]){0x00}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x3a, (uint8_t[]){0x00}, 1, 0},
+ {0x3b, (uint8_t[]){0x00}, 1, 0},
+ {0x3c, (uint8_t[]){0x00}, 1, 0},
+ {0x3d, (uint8_t[]){0x00}, 1, 0},
+ {0x3e, (uint8_t[]){0x00}, 1, 0},
+ {0x3f, (uint8_t[]){0x00}, 1, 0},
+ {0x40, (uint8_t[]){0x00}, 1, 0},
+ {0x41, (uint8_t[]){0x00}, 1, 0},
+ {0x42, (uint8_t[]){0x00}, 1, 0},
+ {0x43, (uint8_t[]){0x00}, 1, 0},
+ {0x44, (uint8_t[]){0x00}, 1, 0},
+
+ {0x50, (uint8_t[]){0x01}, 1, 0},
+ {0x51, (uint8_t[]){0x23}, 1, 0},
+ {0x52, (uint8_t[]){0x45}, 1, 0},
+ {0x53, (uint8_t[]){0x67}, 1, 0},
+ {0x54, (uint8_t[]){0x89}, 1, 0},
+ {0x55, (uint8_t[]){0xab}, 1, 0},
+ {0x56, (uint8_t[]){0x01}, 1, 0},
+ {0x57, (uint8_t[]){0x23}, 1, 0},
+ {0x58, (uint8_t[]){0x45}, 1, 0},
+ {0x59, (uint8_t[]){0x67}, 1, 0},
+ {0x5a, (uint8_t[]){0x89}, 1, 0},
+ {0x5b, (uint8_t[]){0xab}, 1, 0},
+ {0x5c, (uint8_t[]){0xcd}, 1, 0},
+ {0x5d, (uint8_t[]){0xef}, 1, 0},
+
+ {0x5e, (uint8_t[]){0x11}, 1, 0},
+ {0x5f, (uint8_t[]){0x02}, 1, 0},
+ {0x60, (uint8_t[]){0x00}, 1, 0},
+ {0x61, (uint8_t[]){0x07}, 1, 0},
+ {0x62, (uint8_t[]){0x06}, 1, 0},
+ {0x63, (uint8_t[]){0x0E}, 1, 0},
+ {0x64, (uint8_t[]){0x0F}, 1, 0},
+ {0x65, (uint8_t[]){0x0C}, 1, 0},
+ {0x66, (uint8_t[]){0x0D}, 1, 0},
+ {0x67, (uint8_t[]){0x02}, 1, 0},
+ {0x68, (uint8_t[]){0x02}, 1, 0},
+ {0x69, (uint8_t[]){0x02}, 1, 0},
+ {0x6a, (uint8_t[]){0x02}, 1, 0},
+ {0x6b, (uint8_t[]){0x02}, 1, 0},
+ {0x6c, (uint8_t[]){0x02}, 1, 0},
+ {0x6d, (uint8_t[]){0x02}, 1, 0},
+ {0x6e, (uint8_t[]){0x02}, 1, 0},
+ {0x6f, (uint8_t[]){0x02}, 1, 0},
+ {0x70, (uint8_t[]){0x02}, 1, 0},
+ {0x71, (uint8_t[]){0x02}, 1, 0},
+ {0x72, (uint8_t[]){0x02}, 1, 0},
+ {0x73, (uint8_t[]){0x05}, 1, 0},
+ {0x74, (uint8_t[]){0x01}, 1, 0},
+ {0x75, (uint8_t[]){0x02}, 1, 0},
+ {0x76, (uint8_t[]){0x00}, 1, 0},
+ {0x77, (uint8_t[]){0x07}, 1, 0},
+ {0x78, (uint8_t[]){0x06}, 1, 0},
+ {0x79, (uint8_t[]){0x0E}, 1, 0},
+ {0x7a, (uint8_t[]){0x0F}, 1, 0},
+ {0x7b, (uint8_t[]){0x0C}, 1, 0},
+ {0x7c, (uint8_t[]){0x0D}, 1, 0},
+ {0x7d, (uint8_t[]){0x02}, 1, 0},
+ {0x7e, (uint8_t[]){0x02}, 1, 0},
+ {0x7f, (uint8_t[]){0x02}, 1, 0},
+ {0x80, (uint8_t[]){0x02}, 1, 0},
+ {0x81, (uint8_t[]){0x02}, 1, 0},
+ {0x82, (uint8_t[]){0x02}, 1, 0},
+ {0x83, (uint8_t[]){0x02}, 1, 0},
+ {0x84, (uint8_t[]){0x02}, 1, 0},
+ {0x85, (uint8_t[]){0x02}, 1, 0},
+ {0x86, (uint8_t[]){0x02}, 1, 0},
+ {0x87, (uint8_t[]){0x02}, 1, 0},
+ {0x88, (uint8_t[]){0x02}, 1, 0},
+ {0x89, (uint8_t[]){0x05}, 1, 0},
+ {0x8A, (uint8_t[]){0x01}, 1, 0},
+
+ /**** CMD_Page 4 ****/
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x04}, 3, 0},
+ {0x38, (uint8_t[]){0x01}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x6C, (uint8_t[]){0x15}, 1, 0},
+ {0x6E, (uint8_t[]){0x1A}, 1, 0},
+ {0x6F, (uint8_t[]){0x25}, 1, 0},
+ {0x3A, (uint8_t[]){0xA4}, 1, 0},
+ {0x8D, (uint8_t[]){0x20}, 1, 0},
+ {0x87, (uint8_t[]){0xBA}, 1, 0},
+ {0x3B, (uint8_t[]){0x98}, 1, 0},
+
+ /**** CMD_Page 1 ****/
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0},
+ {0x22, (uint8_t[]){0x0A}, 1, 0},
+ {0x31, (uint8_t[]){0x00}, 1, 0},
+ {0x50, (uint8_t[]){0x6B}, 1, 0},
+ {0x51, (uint8_t[]){0x66}, 1, 0},
+ {0x53, (uint8_t[]){0x73}, 1, 0},
+ {0x55, (uint8_t[]){0x8B}, 1, 0},
+ {0x60, (uint8_t[]){0x1B}, 1, 0},
+ {0x61, (uint8_t[]){0x01}, 1, 0},
+ {0x62, (uint8_t[]){0x0C}, 1, 0},
+ {0x63, (uint8_t[]){0x00}, 1, 0},
+
+ // Gamma P
+ {0xA0, (uint8_t[]){0x00}, 1, 0},
+ {0xA1, (uint8_t[]){0x15}, 1, 0},
+ {0xA2, (uint8_t[]){0x1F}, 1, 0},
+ {0xA3, (uint8_t[]){0x13}, 1, 0},
+ {0xA4, (uint8_t[]){0x11}, 1, 0},
+ {0xA5, (uint8_t[]){0x21}, 1, 0},
+ {0xA6, (uint8_t[]){0x17}, 1, 0},
+ {0xA7, (uint8_t[]){0x1B}, 1, 0},
+ {0xA8, (uint8_t[]){0x6B}, 1, 0},
+ {0xA9, (uint8_t[]){0x1E}, 1, 0},
+ {0xAA, (uint8_t[]){0x2B}, 1, 0},
+ {0xAB, (uint8_t[]){0x5D}, 1, 0},
+ {0xAC, (uint8_t[]){0x19}, 1, 0},
+ {0xAD, (uint8_t[]){0x14}, 1, 0},
+ {0xAE, (uint8_t[]){0x4B}, 1, 0},
+ {0xAF, (uint8_t[]){0x1D}, 1, 0},
+ {0xB0, (uint8_t[]){0x27}, 1, 0},
+ {0xB1, (uint8_t[]){0x49}, 1, 0},
+ {0xB2, (uint8_t[]){0x5D}, 1, 0},
+ {0xB3, (uint8_t[]){0x39}, 1, 0},
+
+ // Gamma N
+ {0xC0, (uint8_t[]){0x00}, 1, 0},
+ {0xC1, (uint8_t[]){0x01}, 1, 0},
+ {0xC2, (uint8_t[]){0x0C}, 1, 0},
+ {0xC3, (uint8_t[]){0x11}, 1, 0},
+ {0xC4, (uint8_t[]){0x15}, 1, 0},
+ {0xC5, (uint8_t[]){0x28}, 1, 0},
+ {0xC6, (uint8_t[]){0x1B}, 1, 0},
+ {0xC7, (uint8_t[]){0x1C}, 1, 0},
+ {0xC8, (uint8_t[]){0x62}, 1, 0},
+ {0xC9, (uint8_t[]){0x1C}, 1, 0},
+ {0xCA, (uint8_t[]){0x29}, 1, 0},
+ {0xCB, (uint8_t[]){0x60}, 1, 0},
+ {0xCC, (uint8_t[]){0x16}, 1, 0},
+ {0xCD, (uint8_t[]){0x17}, 1, 0},
+ {0xCE, (uint8_t[]){0x4A}, 1, 0},
+ {0xCF, (uint8_t[]){0x23}, 1, 0},
+ {0xD0, (uint8_t[]){0x24}, 1, 0},
+ {0xD1, (uint8_t[]){0x4F}, 1, 0},
+ {0xD2, (uint8_t[]){0x5F}, 1, 0},
+ {0xD3, (uint8_t[]){0x39}, 1, 0},
+
+ /**** CMD_Page 0 ****/
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x00}, 3, 0},
+ {0x35, (uint8_t[]){0x00}, 0, 0},
+ // {0x11, (uint8_t []){0x00}, 0},
+ {0xFE, (uint8_t[]){0x00}, 0, 0},
+ {0x29, (uint8_t[]){0x00}, 0, 0},
+ //============ Gamma END===========
+};
+
+// ST7123 vendor specific initialization commands
+const st7123_lcd_init_cmd_t st7123_vendor_specific_init_default[] = {
+ {0x60, (uint8_t[]){0x71, 0x23, 0xa2}, 3, 0},
+ {0x60, (uint8_t[]){0x71, 0x23, 0xa3}, 3, 0},
+ {0x60, (uint8_t[]){0x71, 0x23, 0xa4}, 3, 0},
+ {0xA4, (uint8_t[]){0x31}, 1, 0},
+ {0xD7, (uint8_t[]){0x10, 0x0A, 0x10, 0x2A, 0x80, 0x80}, 6, 0},
+ {0x90, (uint8_t[]){0x71, 0x23, 0x5A, 0x20, 0x24, 0x09, 0x09}, 7, 0},
+ {0xA3, (uint8_t[]){0x80, 0x01, 0x88, 0x30, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46, 0x00, 0x00,
+ 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x4F, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46,
+ 0x00, 0x00, 0x1E, 0x5C, 0x1E, 0x80, 0x00, 0x6F, 0x58, 0x00, 0x00, 0x00, 0xFF},
+ 40, 0},
+ {0xA6, (uint8_t[]){0x03, 0x00, 0x24, 0x55, 0x36, 0x00, 0x39, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24,
+ 0x55, 0x38, 0x00, 0x37, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0x24, 0x11, 0x00, 0x00,
+ 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x00, 0xEC, 0x11, 0x00, 0x03, 0x00, 0x03, 0x6E,
+ 0x6E, 0xFF, 0xFF, 0x00, 0x08, 0x80, 0x08, 0x80, 0x06, 0x00, 0x00, 0x00, 0x00},
+ 55, 0},
+ {0xA7, (uint8_t[]){0x19, 0x19, 0x80, 0x64, 0x40, 0x07, 0x16, 0x40, 0x00, 0x44, 0x03, 0x6E, 0x6E, 0x91, 0xFF,
+ 0x08, 0x80, 0x64, 0x40, 0x25, 0x34, 0x40, 0x00, 0x02, 0x01, 0x6E, 0x6E, 0x91, 0xFF, 0x08,
+ 0x80, 0x64, 0x40, 0x00, 0x00, 0x40, 0x00, 0x00, 0x00, 0x6E, 0x6E, 0x91, 0xFF, 0x08, 0x80,
+ 0x64, 0x40, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x6E, 0x6E, 0x84, 0xFF, 0x08, 0x80, 0x44},
+ 60, 0},
+ {0xAC, (uint8_t[]){0x03, 0x19, 0x19, 0x18, 0x18, 0x06, 0x13, 0x13, 0x11, 0x11, 0x08, 0x08, 0x0A, 0x0A, 0x1C,
+ 0x1C, 0x07, 0x07, 0x00, 0x00, 0x02, 0x02, 0x01, 0x19, 0x19, 0x18, 0x18, 0x06, 0x12, 0x12,
+ 0x10, 0x10, 0x09, 0x09, 0x0B, 0x0B, 0x1C, 0x1C, 0x07, 0x07, 0x03, 0x03, 0x01, 0x01},
+ 44, 0},
+ {0xAD, (uint8_t[]){0xF0, 0x00, 0x46, 0x00, 0x03, 0x50, 0x50, 0xFF, 0xFF, 0xF0, 0x40, 0x06, 0x01,
+ 0x07, 0x42, 0x42, 0xFF, 0xFF, 0x01, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF},
+ 25, 0},
+ {0xAE, (uint8_t[]){0xFE, 0x3F, 0x3F, 0xFE, 0x3F, 0x3F, 0x00}, 7, 0},
+ {0xB2,
+ (uint8_t[]){0x15, 0x19, 0x05, 0x23, 0x49, 0xAF, 0x03, 0x2E, 0x5C, 0xD2, 0xFF, 0x10, 0x20, 0xFD, 0x20, 0xC0, 0x00},
+ 17, 0},
+ {0xE8, (uint8_t[]){0x20, 0x6F, 0x04, 0x97, 0x97, 0x3E, 0x04, 0xDC, 0xDC, 0x3E, 0x06, 0xFA, 0x26, 0x3E}, 15, 0},
+ {0x75, (uint8_t[]){0x03, 0x04}, 2, 0},
+ {0xE7, (uint8_t[]){0x3B, 0x00, 0x00, 0x7C, 0xA1, 0x8C, 0x20, 0x1A, 0xF0, 0xB1, 0x50, 0x00,
+ 0x50, 0xB1, 0x50, 0xB1, 0x50, 0xD8, 0x00, 0x55, 0x00, 0xB1, 0x00, 0x45,
+ 0xC9, 0x6A, 0xFF, 0x5A, 0xD8, 0x18, 0x88, 0x15, 0xB1, 0x01, 0x01, 0x77},
+ 36, 0},
+ {0xEA, (uint8_t[]){0x13, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x2C}, 8, 0},
+ {0xB0, (uint8_t[]){0x22, 0x43, 0x11, 0x61, 0x25, 0x43, 0x43}, 7, 0},
+ {0xb7, (uint8_t[]){0x00, 0x00, 0x73, 0x73}, 4, 0},
+ {0xBF, (uint8_t[]){0xA6, 0xAA}, 2, 0},
+ {0xA9, (uint8_t[]){0x00, 0x00, 0x73, 0xFF, 0x00, 0x00, 0x03, 0x00, 0x00, 0x03}, 10, 0},
+ {0xC8, (uint8_t[]){0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06,
+ 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32,
+ 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF},
+ 37, 0},
+ {0xC9, (uint8_t[]){0x00, 0x00, 0x10, 0x1F, 0x36, 0x00, 0x5D, 0x04, 0x9D, 0x05, 0x10, 0xF2, 0x06,
+ 0x60, 0x03, 0x11, 0xAD, 0x00, 0xEF, 0x01, 0x22, 0x2E, 0x0E, 0x74, 0x08, 0x32,
+ 0xDC, 0x09, 0x33, 0x0F, 0xF3, 0x77, 0x0D, 0xB0, 0xDC, 0x03, 0xFF},
+ 37, 0},
+ {0x36, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 1, 100},
+ {0x29, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 100},
+};
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.json b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.json
new file mode 100644
index 0000000..c229ea0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/config.json
@@ -0,0 +1,19 @@
+{
+ "target": "esp32p4",
+ "builds": [
+ {
+ "name": "m5stack-tab5",
+ "sdkconfig_append": [
+ "CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5=y",
+ "CONFIG_CAMERA_SC202CS=y",
+ "CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE=y",
+ "CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=13",
+ "CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=12",
+ "CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=11",
+ "CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=10",
+ "CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=9",
+ "CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.c b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.c
new file mode 100644
index 0000000..b68b1d3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.c
@@ -0,0 +1,381 @@
+/*
+ * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include "soc/soc_caps.h"
+
+#if SOC_MIPI_DSI_SUPPORTED
+#include "esp_check.h"
+#include "esp_log.h"
+#include "esp_lcd_panel_commands.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_lcd_panel_vendor.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "driver/gpio.h"
+#include "esp_lcd_st7123.h"
+
+#define ST7123_PAD_CONTROL (0xB7)
+#define ST7123_DSI_2_LANE (0x03)
+#define ST7123_DSI_3_4_LANE (0x02)
+
+#define ST7123_CMD_GS_BIT (1 << 0)
+#define ST7123_CMD_SS_BIT (1 << 1)
+
+typedef struct {
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ uint8_t colmod_val; // save surrent value of LCD_CMD_COLMOD register
+ const st7123_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+ uint8_t lane_num;
+ struct {
+ unsigned int reset_level: 1;
+ } flags;
+ // To save the original functions of MIPI DPI panel
+ esp_err_t (*del)(esp_lcd_panel_t *panel);
+ esp_err_t (*init)(esp_lcd_panel_t *panel);
+} st7123_panel_t;
+
+static const char *TAG = "st7123";
+
+static esp_err_t panel_st7123_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_st7123_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_st7123_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_st7123_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_st7123_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_st7123_disp_on_off(esp_lcd_panel_t *panel, bool on_off);
+static esp_err_t panel_st7123_sleep(esp_lcd_panel_t *panel, bool sleep);
+
+esp_err_t esp_lcd_new_panel_st7123(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel)
+{
+ ESP_RETURN_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, TAG, "invalid arguments");
+ st7123_vendor_config_t *vendor_config = (st7123_vendor_config_t *)panel_dev_config->vendor_config;
+ ESP_RETURN_ON_FALSE(vendor_config && vendor_config->mipi_config.dpi_config && vendor_config->mipi_config.dsi_bus, ESP_ERR_INVALID_ARG, TAG,
+ "invalid vendor config");
+
+ esp_err_t ret = ESP_OK;
+ st7123_panel_t *st7123 = (st7123_panel_t *)calloc(1, sizeof(st7123_panel_t));
+ ESP_RETURN_ON_FALSE(st7123, ESP_ERR_NO_MEM, TAG, "no mem for st7123 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_config_t io_conf = {
+ .mode = GPIO_MODE_OUTPUT,
+ .pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num,
+ };
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+ switch (panel_dev_config->rgb_ele_order) {
+ case LCD_RGB_ELEMENT_ORDER_RGB:
+ st7123->madctl_val = 0;
+ break;
+ case LCD_RGB_ELEMENT_ORDER_BGR:
+ st7123->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
+ break;
+ }
+
+ switch (panel_dev_config->bits_per_pixel) {
+ case 16: // RGB565
+ st7123->colmod_val = 0x55;
+ break;
+ case 18: // RGB666
+ st7123->colmod_val = 0x66;
+ break;
+ case 24: // RGB888
+ st7123->colmod_val = 0x77;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ st7123->io = io;
+ st7123->init_cmds = vendor_config->init_cmds;
+ st7123->init_cmds_size = vendor_config->init_cmds_size;
+ st7123->lane_num = vendor_config->mipi_config.lane_num;
+ st7123->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ st7123->flags.reset_level = panel_dev_config->flags.reset_active_high;
+
+ // Create MIPI DPI panel
+ ESP_GOTO_ON_ERROR(esp_lcd_new_panel_dpi(vendor_config->mipi_config.dsi_bus, vendor_config->mipi_config.dpi_config, ret_panel), err, TAG,
+ "create MIPI DPI panel failed");
+ ESP_LOGD(TAG, "new MIPI DPI panel @%p", *ret_panel);
+
+ // Save the original functions of MIPI DPI panel
+ st7123->del = (*ret_panel)->del;
+ st7123->init = (*ret_panel)->init;
+ // Overwrite the functions of MIPI DPI panel
+ (*ret_panel)->del = panel_st7123_del;
+ (*ret_panel)->init = panel_st7123_init;
+ (*ret_panel)->reset = panel_st7123_reset;
+ (*ret_panel)->mirror = panel_st7123_mirror;
+ (*ret_panel)->invert_color = panel_st7123_invert_color;
+ (*ret_panel)->disp_on_off = panel_st7123_disp_on_off;
+ (*ret_panel)->disp_sleep = panel_st7123_sleep;
+ (*ret_panel)->user_data = st7123;
+ ESP_LOGD(TAG, "new st7123 panel @%p", st7123);
+
+ return ESP_OK;
+
+ err:
+ if (st7123) {
+ if (panel_dev_config->reset_gpio_num >= 0) {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(st7123);
+ }
+ return ret;
+}
+
+static const st7123_lcd_init_cmd_t vendor_specific_init_default[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ // TODO:
+ {0x60, (uint8_t []){0x71,0x23,0xa2}, 3, 0},
+ {0x60, (uint8_t []){0x71,0x23,0xa3}, 3, 0},
+ {0x60, (uint8_t []){0x71,0x23,0xa4}, 3, 0},
+ {0xA4, (uint8_t []){0x31}, 1, 0},
+ {0xD7, (uint8_t []){0x10,0x0A,0x10,0x2A,0x80,0x80}, 6, 0},
+ {0x90, (uint8_t []){0x71,0x23,0x5A,0x20,0x24,0x09,0x09}, 7, 0},
+ {0xA3, (uint8_t []){0x80,0x01,0x88,0x30,0x05,0x00,0x00,0x00,0x00,0x00,0x46,0x00,0x00,0x1E,0x5C,0x1E,0x80,0x00,0x4F,0x05,0x00,0x00,0x00,0x00,0x00,0x46,0x00,0x00,0x1E,0x5C,0x1E,0x80,0x00,0x6F,0x58,0x00,0x00,0x00,0xFF}, 40, 0},
+ {0xA6, (uint8_t []){0x03,0x00,0x24,0x55,0x36,0x00,0x39,0x00,0x6E,0x6E,0x91,0xFF,0x00,0x24,0x55,0x38,0x00,0x37,0x00,0x6E,0x6E,0x91,0xFF,0x00,0x24,0x11,0x00,0x00,0x00,0x00,0x6E,0x6E,0x91,0xFF,0x00,0xEC,0x11,0x00,0x03,0x00,0x03,0x6E,0x6E,0xFF,0xFF,0x00,0x08,0x80,0x08,0x80,0x06,0x00,0x00,0x00,0x00}, 55, 0},
+ {0xA7, (uint8_t []){0x19,0x19,0x80,0x64,0x40,0x07,0x16,0x40,0x00,0x44,0x03,0x6E,0x6E,0x91,0xFF,0x08,0x80,0x64,0x40,0x25,0x34,0x40,0x00,0x02,0x01,0x6E,0x6E,0x91,0xFF,0x08,0x80,0x64,0x40,0x00,0x00,0x40,0x00,0x00,0x00,0x6E,0x6E,0x91,0xFF,0x08,0x80,0x64,0x40,0x00,0x00,0x00,0x00,0x20,0x00,0x6E,0x6E,0x84,0xFF,0x08,0x80,0x44}, 60, 0},
+ {0xAC, (uint8_t []){0x03,0x19,0x19,0x18,0x18,0x06,0x13,0x13,0x11,0x11,0x08,0x08,0x0A,0x0A,0x1C,0x1C,0x07,0x07,0x00,0x00,0x02,0x02,0x01,0x19,0x19,0x18,0x18,0x06,0x12,0x12,0x10,0x10,0x09,0x09,0x0B,0x0B,0x1C,0x1C,0x07,0x07,0x03,0x03,0x01,0x01}, 44, 0},
+ {0xAD, (uint8_t []){0xF0,0x00,0x46,0x00,0x03,0x50,0x50,0xFF,0xFF,0xF0,0x40,0x06,0x01,0x07,0x42,0x42,0xFF,0xFF,0x01,0x00,0x00,0xFF,0xFF,0xFF,0xFF}, 25, 0},
+ {0xAE, (uint8_t []){0xFE,0x3F,0x3F,0xFE,0x3F,0x3F,0x00}, 7, 0},
+ {0xB2, (uint8_t []){0x15,0x19,0x05,0x23,0x49,0xAF,0x03,0x2E,0x5C,0xD2,0xFF,0x10,0x20,0xFD,0x20,0xC0,0x00}, 17, 0},
+ {0xE8, (uint8_t []){0x20,0x6F,0x04,0x97,0x97,0x3E,0x04,0xDC,0xDC,0x3E,0x06,0xFA,0x26,0x3E}, 15, 0},
+ {0x75, (uint8_t []){0x03,0x04}, 2, 0},
+ {0xE7, (uint8_t []){0x3B,0x00,0x00,0x7C,0xA1,0x8C,0x20,0x1A,0xF0,0xB1,0x50,0x00,0x50,0xB1,0x50,0xB1,0x50,0xD8,0x00,0x55,0x00,0xB1,0x00,0x45,0xC9,0x6A,0xFF,0x5A,0xD8,0x18,0x88,0x15,0xB1,0x01,0x01,0x77}, 36, 0},
+ {0xEA, (uint8_t []){0x13,0x00,0x04,0x00,0x00,0x00,0x00,0x2C}, 8, 0},
+ {0xB0, (uint8_t []){0x22,0x43,0x11,0x61,0x25,0x43,0x43}, 7, 0},
+ {0xb7, (uint8_t []){0x00,0x00,0x73,0x73}, 0x04, 0},
+ {0xBF, (uint8_t []){0xA6,0XAA}, 2, 0},
+ {0xA9, (uint8_t []){0x00,0x00,0x73,0xFF,0x00,0x00,0x03,0x00,0x00,0x03}, 10, 0},
+ {0xC8, (uint8_t []){0x00,0x00,0x10,0x1F,0x36,0x00,0x5D,0x04,0x9D,0x05,0x10,0xF2,0x06,0x60,0x03,0x11,0xAD,0x00,0xEF,0x01,0x22,0x2E,0x0E,0x74,0x08,0x32,0xDC,0x09,0x33,0x0F,0xF3,0x77,0x0D,0xB0,0xDC,0x03,0xFF}, 37, 0},
+ {0xC9, (uint8_t []){0x00,0x00,0x10,0x1F,0x36,0x00,0x5D,0x04,0x9D,0x05,0x10,0xF2,0x06,0x60,0x03,0x11,0xAD,0x00,0xEF,0x01,0x22,0x2E,0x0E,0x74,0x08,0x32,0xDC,0x09,0x33,0x0F,0xF3,0x77,0x0D,0xB0,0xDC,0x03,0xFF}, 37, 0},
+ {0x36, (uint8_t []){0x03}, 1, 0},
+ {0x11, (uint8_t []){0x00}, 1, 100},
+ {0x29, (uint8_t []){0x00}, 1, 0},
+ {0x35, (uint8_t []){0x00}, 1, 100},
+ //============ Gamma END===========
+};
+
+static esp_err_t panel_st7123_del(esp_lcd_panel_t *panel)
+{
+ st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+
+ if (st7123->reset_gpio_num >= 0) {
+ gpio_reset_pin(st7123->reset_gpio_num);
+ }
+ // Delete MIPI DPI panel
+ st7123->del(panel);
+ ESP_LOGD(TAG, "del st7123 panel @%p", st7123);
+ free(st7123);
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7123_init(esp_lcd_panel_t *panel)
+{
+ st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = st7123->io;
+ const st7123_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+
+ // switch (st7123->lane_num) {
+ // case 0:
+ // case 2:
+ // lane_command = ST7123_DSI_2_LANE;
+ // break;
+ // case 3:
+ // case 4:
+ // lane_command = ST7123_DSI_3_4_LANE;
+ // break;
+ // default:
+ // ESP_LOGE(TAG, "Invalid lane number %d", st7123->lane_num);
+ // return ESP_ERR_INVALID_ARG;
+ // }
+
+ uint8_t ID[3];
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_rx_param(io, 0x04, ID, 3), TAG, "read ID failed");
+ ESP_LOGI(TAG, "LCD ID: %02X %02X %02X", ID[0], ID[1], ID[2]);
+
+ // // For modifying MIPI-DSI lane settings
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7123_PAD_CONTROL, (uint8_t[]) {
+ // lane_command,
+ // }, 1), TAG, "send command failed");
+
+ // // back to CMD_Page 0
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, ST7123_CMD_CNDBKxSEL, (uint8_t[]) {
+ // ST7123_CMD_BKxSEL_BYTE0, ST7123_CMD_BKxSEL_BYTE1, ST7123_CMD_BKxSEL_BYTE2_PAGE0
+ // }, 3), TAG, "send command failed");
+ // // exit sleep mode
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG,
+ // "io tx param failed");
+ // vTaskDelay(pdMS_TO_TICKS(120));
+
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]) {
+ // st7123->madctl_val,
+ // }, 1), TAG, "send command failed");
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]) {
+ // st7123->colmod_val,
+ // }, 1), TAG, "send command failed");
+
+ // vendor specific initialization, it can be different between manufacturers
+ // should consult the LCD supplier for initialization sequence code
+ if (st7123->init_cmds) {
+ init_cmds = st7123->init_cmds;
+ init_cmds_size = st7123->init_cmds_size;
+ } else {
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7123_lcd_init_cmd_t);
+ }
+
+ for (int i = 0; i < init_cmds_size; i++) {
+ // Check if the command has been used or conflicts with the internal
+ // if (is_command0_enable && init_cmds[i].data_bytes > 0) {
+ // switch (init_cmds[i].cmd) {
+ // case LCD_CMD_MADCTL:
+ // is_cmd_overwritten = true;
+ // st7123->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ // break;
+ // case LCD_CMD_COLMOD:
+ // is_cmd_overwritten = true;
+ // st7123->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
+ // break;
+ // default:
+ // is_cmd_overwritten = false;
+ // break;
+ // }
+
+ // if (is_cmd_overwritten) {
+ // is_cmd_overwritten = false;
+ // ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence",
+ // init_cmds[i].cmd);
+ // }
+ // }
+
+ // Send command
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+
+ // if ((init_cmds[i].cmd == ST7123_CMD_CNDBKxSEL) && (((uint8_t *)init_cmds[i].data)[2] == ST7123_CMD_BKxSEL_BYTE2_PAGE0)) {
+ // is_command0_enable = true;
+ // } else if ((init_cmds[i].cmd == ST7123_CMD_CNDBKxSEL) && (((uint8_t *)init_cmds[i].data)[2] != ST7123_CMD_BKxSEL_BYTE2_PAGE0)) {
+ // is_command0_enable = false;
+ // }
+ }
+ ESP_LOGD(TAG, "send init commands success");
+
+ ESP_RETURN_ON_ERROR(st7123->init(panel), TAG, "init MIPI DPI panel failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7123_reset(esp_lcd_panel_t *panel)
+{
+ st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+ esp_lcd_panel_io_handle_t io = st7123->io;
+
+ // Perform hardware reset
+ if (st7123->reset_gpio_num >= 0) {
+ gpio_set_level(st7123->reset_gpio_num, st7123->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ gpio_set_level(st7123->reset_gpio_num, !st7123->flags.reset_level);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ } else if (io) { // Perform software reset
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7123_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ // st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+ // esp_lcd_panel_io_handle_t io = st7123->io;
+ // uint8_t command = 0;
+
+ // ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
+
+ // if (invert_color_data) {
+ // command = LCD_CMD_INVON;
+ // } else {
+ // command = LCD_CMD_INVOFF;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7123_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ // st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+ // esp_lcd_panel_io_handle_t io = st7123->io;
+ // uint8_t madctl_val = st7123->madctl_val;
+
+ // ESP_RETURN_ON_FALSE(io, ESP_ERR_INVALID_STATE, TAG, "invalid panel IO");
+
+ // // Control mirror through LCD command
+ // if (mirror_x) {
+ // madctl_val |= ST7123_CMD_GS_BIT;
+ // } else {
+ // madctl_val &= ~ST7123_CMD_GS_BIT;
+ // }
+ // if (mirror_y) {
+ // madctl_val |= ST7123_CMD_SS_BIT;
+ // } else {
+ // madctl_val &= ~ST7123_CMD_SS_BIT;
+ // }
+
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t []) {
+ // madctl_val
+ // }, 1), TAG, "send command failed");
+ // st7123->madctl_val = madctl_val;
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7123_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ // st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+ // esp_lcd_panel_io_handle_t io = st7123->io;
+ // int command = 0;
+
+ // if (on_off) {
+ // command = LCD_CMD_DISPON;
+ // } else {
+ // command = LCD_CMD_DISPOFF;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_st7123_sleep(esp_lcd_panel_t *panel, bool sleep)
+{
+ // st7123_panel_t *st7123 = (st7123_panel_t *)panel->user_data;
+ // esp_lcd_panel_io_handle_t io = st7123->io;
+ // int command = 0;
+
+ // if (sleep) {
+ // command = LCD_CMD_SLPIN;
+ // } else {
+ // command = LCD_CMD_SLPOUT;
+ // }
+ // ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ // vTaskDelay(pdMS_TO_TICKS(100));
+
+ return ESP_OK;
+}
+#endif // SOC_MIPI_DSI_SUPPORTED
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.h
new file mode 100644
index 0000000..27c514e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/esp_lcd_st7123.h
@@ -0,0 +1,125 @@
+/*
+ * SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/**
+ * @file
+ * @brief ESP LCD: ST7123
+ */
+
+ #pragma once
+
+ #include <stdint.h>
+ #include "soc/soc_caps.h"
+
+ #if SOC_MIPI_DSI_SUPPORTED
+ #include "esp_lcd_panel_vendor.h"
+ #include "esp_lcd_mipi_dsi.h"
+
+ #ifdef __cplusplus
+ extern "C" {
+ #endif
+
+ /**
+ * @brief LCD panel initialization commands.
+ *
+ */
+ typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+ } st7123_lcd_init_cmd_t;
+
+ /**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `esp_lcd_panel_dev_config_t::vendor_config`.
+ *
+ */
+ typedef struct {
+ const st7123_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+ struct {
+ esp_lcd_dsi_bus_handle_t dsi_bus; /*!< MIPI-DSI bus configuration */
+ const esp_lcd_dpi_panel_config_t *dpi_config; /*!< MIPI-DPI panel configuration */
+ uint8_t lane_num; /*!< Number of MIPI-DSI lanes */
+ } mipi_config;
+ } st7123_vendor_config_t;
+
+ /**
+ * @brief Create LCD panel for model ST7123
+ *
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config General panel device configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_OK on success
+ * - Otherwise on fail
+ */
+ esp_err_t esp_lcd_new_panel_st7123(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config,
+ esp_lcd_panel_handle_t *ret_panel);
+
+ /**
+ * @brief MIPI-DSI bus configuration structure
+ *
+ */
+ #define ST7123_PANEL_BUS_DSI_2CH_CONFIG() \
+ { \
+ .bus_id = 0, \
+ .num_data_lanes = 2, \
+ .lane_bit_rate_mbps = 1000, \
+ }
+
+ /**
+ * @brief MIPI-DBI panel IO configuration structure
+ *
+ */
+ #define ST7123_PANEL_IO_DBI_CONFIG() \
+ { \
+ .virtual_channel = 0, \
+ .lcd_cmd_bits = 8, \
+ .lcd_param_bits = 8, \
+ }
+
+ /**
+ * @brief MIPI DPI configuration structure
+ *
+ * @note refresh_rate = (dpi_clock_freq_mhz * 1000000) / (h_res + hsync_pulse_width + hsync_back_porch + hsync_front_porch)
+ * / (v_res + vsync_pulse_width + vsync_back_porch + vsync_front_porch)
+ *
+ * @param[in] px_format Pixel format of the panel
+ *
+ */
+ #define ST7123_800_1280_PANEL_60HZ_DPI_CONFIG(px_format) \
+ { \
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT, \
+ .dpi_clock_freq_mhz = 80, \
+ .virtual_channel = 0, \
+ .pixel_format = px_format, \
+ .num_fbs = 1, \
+ .video_timing = { \
+ .h_size = 800, \
+ .v_size = 1280, \
+ .hsync_back_porch = 140, \
+ .hsync_pulse_width = 40, \
+ .hsync_front_porch = 40, \
+ .vsync_back_porch = 16, \
+ .vsync_pulse_width = 4, \
+ .vsync_front_porch = 16, \
+ }, \
+ .flags.use_dma2d = true, \
+ }
+ #endif
+
+ #ifdef __cplusplus
+ }
+ #endif
+
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/m5stack_tab5.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/m5stack_tab5.cc
new file mode 100644
index 0000000..43ed5b5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/m5stack_tab5.cc
@@ -0,0 +1,574 @@
+#include "wifi_board.h"
+#include "tab5_audio_codec.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_ili9881c.h"
+#include "esp_lcd_st7123.h"
+#include "font_emoji.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "esp_video.h"
+#include "esp_video_init.h"
+#include "esp_cam_sensor_xclk.h"
+
+#include <esp_log.h>
+#include "esp_check.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_ldo_regulator.h"
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "i2c_device.h"
+#include "esp_lcd_touch_gt911.h"
+#include "esp_lcd_touch_st7123.h"
+#include <cstring>
+
+#define TAG "M5StackTab5Board"
+
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+#define LCD_MIPI_DSI_PHY_PWR_LDO_CHAN 3 // LDO_VO3 is connected to VDD_MIPI_DPHY
+#define LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV 2500
+#define ST7123_TOUCH_I2C_ADDRESS 0x55
+
+
+// PI4IO registers
+#define PI4IO_REG_CHIP_RESET 0x01
+#define PI4IO_REG_IO_DIR 0x03
+#define PI4IO_REG_OUT_SET 0x05
+#define PI4IO_REG_OUT_H_IM 0x07
+#define PI4IO_REG_IN_DEF_STA 0x09
+#define PI4IO_REG_PULL_EN 0x0B
+#define PI4IO_REG_PULL_SEL 0x0D
+#define PI4IO_REG_IN_STA 0x0F
+#define PI4IO_REG_INT_MASK 0x11
+#define PI4IO_REG_IRQ_STA 0x13
+
+// Bit manipulation macros
+#define setbit(x, bit) ((x) |= (1U << (bit)))
+#define clrbit(x, bit) ((x) &= ~(1U << (bit)))
+
+class Pi4ioe1 : public I2cDevice {
+public:
+ Pi4ioe1(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(PI4IO_REG_CHIP_RESET, 0xFF);
+ uint8_t data = ReadReg(PI4IO_REG_CHIP_RESET);
+ WriteReg(PI4IO_REG_IO_DIR, 0b01111111); // 0: input 1: output
+ WriteReg(PI4IO_REG_OUT_H_IM, 0b00000000); // 浣跨敤鍒扮殑寮曡剼鍏抽棴 High-Impedance
+ WriteReg(PI4IO_REG_PULL_SEL, 0b01111111); // pull up/down select, 0 down, 1 up
+ WriteReg(PI4IO_REG_PULL_EN, 0b01111111); // pull up/down enable, 0 disable, 1 enable
+ WriteReg(PI4IO_REG_IN_DEF_STA, 0b10000000); // P1, P7 榛樿楂樼數骞�
+ WriteReg(PI4IO_REG_INT_MASK, 0b01111111); // P7 涓柇浣胯兘 0 enable, 1 disable
+ WriteReg(PI4IO_REG_OUT_SET, 0b01110110); // Output Port Register P1(SPK_EN), P2(EXT5V_EN), P4(LCD_RST), P5(TP_RST), P6(CAM)RST 杈撳嚭楂樼數骞�
+ }
+
+ uint8_t ReadOutSet() { return ReadReg(PI4IO_REG_OUT_SET); }
+ void WriteOutSet(uint8_t value) { WriteReg(PI4IO_REG_OUT_SET, value); }
+};
+
+class Pi4ioe2 : public I2cDevice {
+public:
+ Pi4ioe2(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(PI4IO_REG_CHIP_RESET, 0xFF);
+ uint8_t data = ReadReg(PI4IO_REG_CHIP_RESET);
+ WriteReg(PI4IO_REG_IO_DIR, 0b10111001); // 0: input 1: output
+ WriteReg(PI4IO_REG_OUT_H_IM, 0b00000110); // 浣跨敤鍒扮殑寮曡剼鍏抽棴 High-Impedance
+ WriteReg(PI4IO_REG_PULL_SEL, 0b10111001); // pull up/down select, 0 down, 1 up
+ WriteReg(PI4IO_REG_PULL_EN, 0b11111001); // pull up/down enable, 0 disable, 1 enable
+ WriteReg(PI4IO_REG_IN_DEF_STA, 0b01000000); // P6 榛樿楂樼數骞�
+ WriteReg(PI4IO_REG_INT_MASK, 0b10111111); // P6 涓柇浣胯兘 0 enable, 1 disable
+ WriteReg(PI4IO_REG_OUT_SET, 0b10001001); // Output Port Register P0(WLAN_PWR_EN), P3(USB5V_EN), P7(CHG_EN) 杈撳嚭楂樼數骞�
+ }
+
+ uint8_t ReadOutSet() { return ReadReg(PI4IO_REG_OUT_SET); }
+ void WriteOutSet(uint8_t value) { WriteReg(PI4IO_REG_OUT_SET, value); }
+};
+
+class M5StackTab5Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ EspVideo* camera_ = nullptr;
+ Pi4ioe1* pi4ioe1_;
+ Pi4ioe2* pi4ioe2_;
+ esp_lcd_touch_handle_t touch_ = nullptr;
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+ }
+
+ static esp_err_t bsp_enable_dsi_phy_power() {
+ esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
+ esp_ldo_channel_config_t ldo_mipi_phy_config = {
+ .chan_id = LCD_MIPI_DSI_PHY_PWR_LDO_CHAN,
+ .voltage_mv = LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
+ };
+ return esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy);
+ }
+
+ void I2cDetect() {
+ uint8_t address;
+ printf(" 0 1 2 3 4 5 6 7 8 9 a b c d e f\r\n");
+ for (int i = 0; i < 128; i += 16) {
+ printf("%02x: ", i);
+ for (int j = 0; j < 16; j++) {
+ fflush(stdout);
+ address = i + j;
+ esp_err_t ret = i2c_master_probe(i2c_bus_, address, pdMS_TO_TICKS(200));
+ if (ret == ESP_OK) {
+ printf("%02x ", address);
+ } else if (ret == ESP_ERR_TIMEOUT) {
+ printf("UU ");
+ } else {
+ printf("-- ");
+ }
+ }
+ printf("\r\n");
+ }
+ }
+
+ void InitializePi4ioe() {
+ ESP_LOGI(TAG, "Init I/O Exapander PI4IOE");
+ pi4ioe1_ = new Pi4ioe1(i2c_bus_, 0x43);
+ pi4ioe2_ = new Pi4ioe2(i2c_bus_, 0x44);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeGt911TouchPad() {
+ ESP_LOGI(TAG, "Init GT911");
+
+ /* Initialize Touch Panel */
+ ESP_LOGI(TAG, "Initialize touch IO (I2C)");
+ const esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = TOUCH_INT_GPIO,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP; // 鏇存敼 GT911 鍦板潃
+ tp_io_config.scl_speed_hz = 100000;
+ esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle);
+ esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &touch_);
+ }
+
+ void InitializeIli9881cDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGI(TAG, "Turn on the power for MIPI DSI PHY");
+ esp_ldo_channel_handle_t ldo_mipi_phy = NULL;
+ esp_ldo_channel_config_t ldo_mipi_phy_config = {
+ .chan_id = LCD_MIPI_DSI_PHY_PWR_LDO_CHAN,
+ .voltage_mv = LCD_MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
+ };
+ ESP_ERROR_CHECK(esp_ldo_acquire_channel(&ldo_mipi_phy_config, &ldo_mipi_phy));
+
+ ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
+ esp_lcd_dsi_bus_handle_t mipi_dsi_bus;
+ esp_lcd_dsi_bus_config_t bus_config = {
+ .bus_id = 0,
+ .num_data_lanes = 2,
+ .lane_bit_rate_mbps = 900, // 900MHz
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus));
+
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_dbi_io_config_t dbi_config = {
+ .virtual_channel = 0,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &panel_io));
+
+ ESP_LOGI(TAG, "Install LCD driver of ili9881c");
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .virtual_channel = 0,
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 60,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 2,
+ .video_timing = {
+ .h_size = DISPLAY_WIDTH,
+ .v_size = DISPLAY_HEIGHT,
+ .hsync_pulse_width = 40,
+ .hsync_back_porch = 140,
+ .hsync_front_porch = 40,
+ .vsync_pulse_width = 4,
+ .vsync_back_porch = 20,
+ .vsync_front_porch = 20,
+ },
+ .flags = {
+ .use_dma2d = false,
+ },
+ };
+
+ ili9881c_vendor_config_t vendor_config = {
+ .init_cmds = tab5_lcd_ili9881c_specific_init_code_default,
+ .init_cmds_size = sizeof(tab5_lcd_ili9881c_specific_init_code_default) / sizeof(tab5_lcd_ili9881c_specific_init_code_default[0]),
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ .lane_num = 2,
+ },
+ };
+
+ esp_lcd_panel_dev_config_t lcd_dev_config = {};
+ lcd_dev_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ lcd_dev_config.reset_gpio_num = -1;
+ lcd_dev_config.bits_per_pixel = 16;
+ lcd_dev_config.vendor_config = &vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ili9881c(panel_io, &lcd_dev_config, &panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+
+ display_ = new MipiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X,
+ DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeSt7123Display() {
+ esp_err_t ret = ESP_OK;
+ esp_lcd_panel_io_handle_t io = NULL;
+ esp_lcd_panel_handle_t disp_panel = NULL;
+ esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
+
+ // Declare all config structures at the top to avoid goto issues
+ // Initialize with memset to avoid any initialization syntax that might confuse the compiler
+ esp_lcd_dsi_bus_config_t bus_config;
+ esp_lcd_dbi_io_config_t dbi_config;
+ esp_lcd_dpi_panel_config_t dpi_config;
+ st7123_vendor_config_t vendor_config;
+ esp_lcd_panel_dev_config_t lcd_dev_config;
+
+ memset(&bus_config, 0, sizeof(bus_config));
+ memset(&dbi_config, 0, sizeof(dbi_config));
+ memset(&dpi_config, 0, sizeof(dpi_config));
+ memset(&vendor_config, 0, sizeof(vendor_config));
+ memset(&lcd_dev_config, 0, sizeof(lcd_dev_config));
+
+ ESP_ERROR_CHECK(bsp_enable_dsi_phy_power());
+
+ /* create MIPI DSI bus first, it will initialize the DSI PHY as well */
+ bus_config.bus_id = 0;
+ bus_config.num_data_lanes = 2; // ST7123 uses 2 data lanes
+ bus_config.lane_bit_rate_mbps = 965; // ST7123 lane bitrate
+ ret = esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "New DSI bus init failed");
+ goto err;
+ }
+
+ ESP_LOGI(TAG, "Install MIPI DSI LCD control panel for ST7123");
+ // we use DBI interface to send LCD commands and parameters
+ dbi_config.virtual_channel = 0;
+ dbi_config.lcd_cmd_bits = 8; // according to the LCD spec
+ dbi_config.lcd_param_bits = 8; // according to the LCD spec
+ ret = esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "New panel IO failed");
+ goto err;
+ }
+
+ ESP_LOGI(TAG, "Install LCD driver of ST7123");
+ dpi_config.virtual_channel = 0;
+ dpi_config.dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT;
+ dpi_config.dpi_clock_freq_mhz = 70; // ST7123 DPI clock frequency
+ dpi_config.pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565;
+ dpi_config.num_fbs = 1;
+ dpi_config.video_timing.h_size = 720;
+ dpi_config.video_timing.v_size = 1280;
+ dpi_config.video_timing.hsync_pulse_width = 2;
+ dpi_config.video_timing.hsync_back_porch = 40;
+ dpi_config.video_timing.hsync_front_porch = 40;
+ dpi_config.video_timing.vsync_pulse_width = 2;
+ dpi_config.video_timing.vsync_back_porch = 8;
+ dpi_config.video_timing.vsync_front_porch = 220;
+ dpi_config.flags.use_dma2d = true;
+
+ vendor_config.init_cmds = st7123_vendor_specific_init_default;
+ vendor_config.init_cmds_size = sizeof(st7123_vendor_specific_init_default) / sizeof(st7123_vendor_specific_init_default[0]);
+ vendor_config.mipi_config.dsi_bus = mipi_dsi_bus;
+ vendor_config.mipi_config.dpi_config = &dpi_config;
+ vendor_config.mipi_config.lane_num = 2;
+
+ lcd_dev_config.reset_gpio_num = -1;
+ lcd_dev_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ lcd_dev_config.data_endian = LCD_RGB_DATA_ENDIAN_LITTLE;
+ lcd_dev_config.bits_per_pixel = 24;
+ lcd_dev_config.vendor_config = &vendor_config;
+
+ // 浣跨敤瀹為檯鐨� ST7123 椹卞姩鍑芥暟
+ ret = esp_lcd_new_panel_st7123(io, &lcd_dev_config, &disp_panel);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "New LCD panel ST7123 failed");
+ goto err;
+ }
+
+ ret = esp_lcd_panel_reset(disp_panel);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "LCD panel reset failed");
+ goto err;
+ }
+
+ ret = esp_lcd_panel_init(disp_panel);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "LCD panel init failed");
+ goto err;
+ }
+
+ ret = esp_lcd_panel_disp_on_off(disp_panel, true);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "LCD panel display on failed");
+ goto err;
+ }
+
+ display_ = new MipiLcdDisplay(io, disp_panel, 720, 1280, DISPLAY_OFFSET_X,
+ DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+
+ ESP_LOGI(TAG, "ST7123 Display initialized with resolution %dx%d", 720, 1280);
+
+ return;
+
+ err:
+ if (disp_panel) {
+ esp_lcd_panel_del(disp_panel);
+ }
+ if (io) {
+ esp_lcd_panel_io_del(io);
+ }
+ if (mipi_dsi_bus) {
+ esp_lcd_del_dsi_bus(mipi_dsi_bus);
+ }
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSt7123TouchPad() {
+ ESP_LOGI(TAG, "Init ST7123 Touch");
+
+ /* Initialize Touch Panel */
+ ESP_LOGI(TAG, "Initialize touch IO (I2C)");
+ const esp_lcd_touch_config_t tp_cfg = {
+ .x_max = 720,
+ .y_max = 1280,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = TOUCH_INT_GPIO,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = 0x55,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .scl_speed_hz = 100000,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_st7123(tp_io_handle, &tp_cfg, &touch_));
+ }
+
+ void InitializeDisplay() {
+ // after tp reset, wait for 100ms to ensure the I2C bus is stable
+ vTaskDelay(pdMS_TO_TICKS(100));
+ // 妫�娴� ST7123 瑙︽懜灞� (I2C鍦板潃 0x55)
+ esp_err_t ret = i2c_master_probe(i2c_bus_, ST7123_TOUCH_I2C_ADDRESS, 200);
+ if (ret == ESP_OK) {
+ ESP_LOGI(TAG, "Detected ST7123 at 0x%02X, initializing ST7123 display", ST7123_TOUCH_I2C_ADDRESS);
+ InitializeSt7123Display();
+ InitializeSt7123TouchPad();
+ } else {
+ ESP_LOGI(TAG, "ST7123 not found at 0x%02X (ret=0x%x), using default ST7703+GT911", ST7123_TOUCH_I2C_ADDRESS, ret);
+ InitializeIli9881cDisplay();
+ InitializeGt911TouchPad();
+ }
+ }
+
+ void InitializeCamera() {
+ esp_cam_sensor_xclk_handle_t xclk_handle = NULL;
+ esp_cam_sensor_xclk_config_t cam_xclk_config = {};
+
+#if CONFIG_CAMERA_XCLK_USE_ESP_CLOCK_ROUTER
+ if (esp_cam_sensor_xclk_allocate(ESP_CAM_SENSOR_XCLK_ESP_CLOCK_ROUTER, &xclk_handle) == ESP_OK) {
+ cam_xclk_config.esp_clock_router_cfg.xclk_pin = CAMERA_MCLK;
+ cam_xclk_config.esp_clock_router_cfg.xclk_freq_hz = 12000000; // 12MHz
+ (void)esp_cam_sensor_xclk_start(xclk_handle, &cam_xclk_config);
+ }
+#elif CONFIG_CAMERA_XCLK_USE_LEDC
+ if (esp_cam_sensor_xclk_allocate(ESP_CAM_SENSOR_XCLK_LEDC, &xclk_handle) == ESP_OK) {
+ cam_xclk_config.ledc_cfg.timer = LEDC_TIMER_0;
+ cam_xclk_config.ledc_cfg.clk_cfg = LEDC_AUTO_CLK;
+ cam_xclk_config.ledc_cfg.channel = LEDC_CHANNEL_0;
+ cam_xclk_config.ledc_cfg.xclk_freq_hz = 12000000; // 12MHz
+ cam_xclk_config.ledc_cfg.xclk_pin = CAMERA_MCLK;
+ (void)esp_cam_sensor_xclk_start(xclk_handle, &cam_xclk_config);
+ }
+#endif
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+ .freq = 400000,
+ };
+
+ esp_video_init_csi_config_t csi_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = GPIO_NUM_NC,
+ .pwdn_pin = GPIO_NUM_NC,
+ };
+
+ esp_video_init_config_t video_config = {
+ .csi = &csi_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+ }
+
+public:
+ M5StackTab5Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ I2cDetect();
+ InitializePi4ioe();
+ InitializeDisplay(); // Auto-detect and initialize display + touch
+ InitializeCamera();
+ InitializeButtons();
+ SetChargeQcEn(true);
+ SetChargeEn(true);
+ SetUsb5vEn(true);
+ SetExt5vEn(true);
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Tab5AudioCodec audio_codec(i2c_bus_,
+ 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_ES8388_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ // BSP power control functions
+ void SetChargeQcEn(bool en) {
+ if (pi4ioe2_) {
+ uint8_t value = pi4ioe2_->ReadOutSet();
+ if (en) {
+ clrbit(value, 5); // P5 = CHG_QC_EN (浣庣數骞充娇鑳�)
+ } else {
+ setbit(value, 5);
+ }
+ pi4ioe2_->WriteOutSet(value);
+ }
+ }
+
+ void SetChargeEn(bool en) {
+ if (pi4ioe2_) {
+ uint8_t value = pi4ioe2_->ReadOutSet();
+ if (en) {
+ setbit(value, 7); // P7 = CHG_EN
+ } else {
+ clrbit(value, 7);
+ }
+ pi4ioe2_->WriteOutSet(value);
+ }
+ }
+
+ void SetUsb5vEn(bool en) {
+ if (pi4ioe2_) {
+ uint8_t value = pi4ioe2_->ReadOutSet();
+ if (en) {
+ setbit(value, 3); // P3 = USB5V_EN
+ } else {
+ clrbit(value, 3);
+ }
+ pi4ioe2_->WriteOutSet(value);
+ }
+ }
+
+ void SetExt5vEn(bool en) {
+ if (pi4ioe1_) {
+ uint8_t value = pi4ioe1_->ReadOutSet();
+ if (en) {
+ setbit(value, 2); // P2 = EXT5V_EN
+ } else {
+ clrbit(value, 2);
+ }
+ pi4ioe1_->WriteOutSet(value);
+ }
+ }
+};
+
+
+DECLARE_BOARD(M5StackTab5Board);
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/sdkconfig.tab5 b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/sdkconfig.tab5
new file mode 100644
index 0000000..70f759c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/sdkconfig.tab5
@@ -0,0 +1,14 @@
+# This file was generated using idf.py save-defconfig. It can be edited manually.
+# Espressif IoT Development Framework (ESP-IDF) 5.5.1 Project Minimal Configuration
+#
+CONFIG_BOARD_TYPE_M5STACK_CORE_TAB5=y
+
+CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE=y
+CONFIG_CAMERA_SC202CS=y
+
+CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CMD_SLOT_1=13
+CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_CLK_SLOT_1=12
+CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D0_SLOT_1=11
+CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D1_4BIT_BUS_SLOT_1=10
+CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D2_4BIT_BUS_SLOT_1=9
+CONFIG_ESP_HOSTED_PRIV_SDIO_PIN_D3_4BIT_BUS_SLOT_1=8
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.cc
new file mode 100644
index 0000000..b0ddace
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.cc
@@ -0,0 +1,245 @@
+#include "tab5_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c.h>
+#include <driver/i2s_tdm.h>
+
+#define TAG "Tab5AudioCodec"
+
+Tab5AudioCodec::Tab5AudioCodec(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 es8388_addr, uint8_t es7210_addr, bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ 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 = es8388_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);
+ es8388_codec_cfg_t es8388_cfg = {};
+ es8388_cfg.ctrl_if = out_ctrl_if_;
+ es8388_cfg.gpio_if = gpio_if_;
+ es8388_cfg.codec_mode = ESP_CODEC_DEV_WORK_MODE_DAC;
+ es8388_cfg.master_mode = true;
+ es8388_cfg.pa_pin = -1; // PI4IOE1 P1 鎺у埗
+ es8388_cfg.pa_reverted = false;
+ es8388_cfg.hw_gain.pa_voltage = 5.0;
+ es8388_cfg.hw_gain.codec_dac_voltage = 3.3;
+ out_codec_if_ = es8388_codec_new(&es8388_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, "Tab5 AudioDevice initialized");
+}
+
+Tab5AudioCodec::~Tab5AudioCodec() {
+ 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 Tab5AudioCodec::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,
+ .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 Tab5AudioCodec::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void Tab5AudioCodec::EnableInput(bool enable) {
+ 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 Tab5AudioCodec::EnableOutput(bool enable) {
+ 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 Tab5AudioCodec::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 Tab5AudioCodec::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;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.h
new file mode 100644
index 0000000..4e45589
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/m5stack-tab5/tab5_audio_codec.h
@@ -0,0 +1,37 @@
+#ifndef _TAB5_AUDIO_CODEC_H
+#define _TAB5_AUDIO_CODEC_H
+
+#include "audio_codec.h"
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+
+
+class Tab5AudioCodec : 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;
+
+ 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:
+ Tab5AudioCodec(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 es8388_addr, uint8_t es7210_addr, bool input_reference);
+ virtual ~Tab5AudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _TAB5_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.h b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.h
new file mode 100644
index 0000000..bc37001
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.h
@@ -0,0 +1,50 @@
+#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_8
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_4 // pcb v2.4涓嶈捣浣滅敤锛岄�傜敤浜�2.4A
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+//led power
+#define BUILTIN_LED_POWER GPIO_NUM_39 // 浣庣數骞虫湁鏁�
+#define BUILTIN_LED_POWER_OUTPUT_INVERT true
+
+#define BUILTIN_LED_NUM 2
+#define BUILTIN_LED_GPIO GPIO_NUM_38
+
+#define MAIN_BUTTON_GPIO GPIO_NUM_21
+#define LEFT_BUTTON_GPIO GPIO_NUM_0
+#define RIGHT_BUTTON_GPIO GPIO_NUM_47
+
+// display
+#define DISPLAY_SDA_PIN GPIO_NUM_15
+#define DISPLAY_SCL_PIN GPIO_NUM_16
+#define DISPLAY_CS_PIN GPIO_NUM_17
+#define DISPLAY_DC_PIN GPIO_NUM_18
+#define DISPLAY_RST_PIN GPIO_NUM_14
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.json b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.json
new file mode 100644
index 0000000..f416c2a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "magiclick-2p4",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/magiclick_2p4_board.cc b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/magiclick_2p4_board.cc
new file mode 100644
index 0000000..fca17e8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p4/magiclick_2p4_board.cc
@@ -0,0 +1,272 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/circular_strip.h"
+#include "config.h"
+#include "assets/lang_config.h"
+
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_nv3023.h>
+
+#include "../magiclick-2p5/power_manager.h"
+#include "power_save_timer.h"
+
+#define TAG "magiclick_2p4"
+
+class NV3023Display : public SpiLcdDisplay {
+public:
+ NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ }
+
+ void SetupUI() override {
+ SpiLcdDisplay::SetupUI();
+
+ // Apply custom color styles after parent creates all LVGL objects
+ DisplayLockGuard lock(this);
+ auto screen = lv_disp_get_scr_act(lv_disp_get_default());
+ lv_obj_set_style_text_color(screen, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(container_, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0);
+ lv_obj_set_style_text_color(network_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(status_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(content_, lv_color_black(), 0);
+ lv_obj_set_style_border_width(content_, 0, 0);
+ lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0);
+ lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
+ }
+};
+
+class magiclick_2p4 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button main_button_;
+ Button left_button_;
+ Button right_button_;
+ NV3023Display* display_;
+
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_48);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(240, 60, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeButtons() {
+ main_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ main_button_.OnPressDown([this]() {
+ power_save_timer_->WakeUp();
+ Application::GetInstance().StartListening();
+ });
+ main_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ left_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ left_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ right_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ right_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+ }
+
+ void InitializeLedPower() {
+ // 璁剧疆GPIO妯″紡
+ gpio_reset_pin(BUILTIN_LED_POWER);
+ gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT);
+ gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeNv3023Display(){
+ // esp_lcd_panel_io_handle_t panel_io = nullptr;
+ // esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖NV3023
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+ display_ = new NV3023Display(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+public:
+ magiclick_2p4() :
+ main_button_(MAIN_BUTTON_GPIO),
+ left_button_(LEFT_BUTTON_GPIO),
+ right_button_(RIGHT_BUTTON_GPIO) {
+ InitializeLedPower();
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializeSpi();
+ InitializeNv3023Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static CircularStrip led(BUILTIN_LED_GPIO, BUILTIN_LED_NUM);
+ return &led;
+ }
+
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(magiclick_2p4);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.h b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.h
new file mode 100644
index 0000000..67d6423
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.h
@@ -0,0 +1,56 @@
+#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_8
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_4
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+//led power
+#define BUILTIN_LED_POWER GPIO_NUM_39 // 浣庣數骞虫湁鏁�
+#define BUILTIN_LED_POWER_OUTPUT_INVERT true
+
+#define BUILTIN_LED_NUM 2
+#define BUILTIN_LED_GPIO GPIO_NUM_38
+
+#define MAIN_BUTTON_GPIO GPIO_NUM_21
+#define LEFT_BUTTON_GPIO GPIO_NUM_0
+#define RIGHT_BUTTON_GPIO GPIO_NUM_47
+
+// display
+#define DISPLAY_SDA_PIN GPIO_NUM_16
+#define DISPLAY_SCL_PIN GPIO_NUM_15
+#define DISPLAY_CS_PIN GPIO_NUM_14
+#define DISPLAY_DC_PIN GPIO_NUM_18
+#define DISPLAY_RST_PIN GPIO_NUM_17
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+
+// 鏍规嵁PCB鐗堟湰鍔ㄦ�侀�夋嫨灞忓箷绫诲瀷鍜岄厤缃�
+#define PCB_VERSION_2_5A 1 // 浣跨敤GC9107
+#define PCB_VERSION_2_5A1 2 // 浣跨敤ST7735
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+#define ML307_RX_PIN GPIO_NUM_42
+#define ML307_TX_PIN GPIO_NUM_44
+#define ML307_POWER_PIN GPIO_NUM_40
+#define ML307_POWER_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.json b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.json
new file mode 100644
index 0000000..21be478
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "magiclick-2p5",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/magiclick_2p5_board.cc b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/magiclick_2p5_board.cc
new file mode 100644
index 0000000..1394fec
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/magiclick_2p5_board.cc
@@ -0,0 +1,424 @@
+#include "dual_network_board.h"
+#include "display/lcd_display.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/circular_strip.h"
+#include "config.h"
+#include "assets/lang_config.h"
+
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+
+#include "power_manager.h"
+#include "power_save_timer.h"
+#include "esp_wifi.h"
+
+#include <esp_adc/adc_oneshot.h>
+#include <esp_adc/adc_cali.h>
+#include <esp_adc/adc_cali_scheme.h>
+
+#define TAG "magiclick_2p5"
+
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb1, (uint8_t[]){0x80}, 1, 0},
+ {0xb2, (uint8_t[]){0x27}, 1, 0},
+ {0xb3, (uint8_t[]){0x13}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x05}, 1, 0},
+ {0xac, (uint8_t[]){0xc8}, 1, 0},
+ {0xab, (uint8_t[]){0x0f}, 1, 0},
+ {0x3a, (uint8_t[]){0x05}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x08}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xea, (uint8_t[]){0x02}, 1, 0},
+ {0xe8, (uint8_t[]){0x2A}, 1, 0},
+ {0xe9, (uint8_t[]){0x47}, 1, 0},
+ {0xe7, (uint8_t[]){0x5f}, 1, 0},
+ {0xc6, (uint8_t[]){0x21}, 1, 0},
+ {0xc7, (uint8_t[]){0x15}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
+ 0x04, 0x12, 0x14, 0x1f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
+ 0x0C, 0x1A, 0x14, 0x1E},
+ 14, 0},
+ {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
+ {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
+};
+
+class magiclick_2p5 : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button main_button_;
+ Button left_button_;
+ Button right_button_;
+ LcdDisplay* display_;
+
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+
+ uint8_t pcb_version_ = 0;
+
+ // 灞忓箷閰嶇疆缁撴瀯浣�
+ struct DisplayConfig {
+ bool use_gc9107;
+ bool mirror_x;
+ bool mirror_y;
+ bool swap_xy;
+ bool invert_color;
+ lcd_rgb_element_order_t rgb_order;
+ int offset_x;
+ int offset_y;
+ int spi_mode;
+ const char* screen_name;
+ };
+
+ DisplayConfig GetDisplayConfig() {
+ if (pcb_version_ == PCB_VERSION_2_5A) {
+ return DisplayConfig{
+ .use_gc9107 = true,
+ .mirror_x = false,
+ .mirror_y = false,
+ .swap_xy = false,
+ .invert_color = false,
+ .rgb_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .offset_x = 0,
+ .offset_y = 0,
+ .spi_mode = 0,
+ .screen_name = "GC9107"
+ };
+ } else if (pcb_version_ == PCB_VERSION_2_5A1) {
+ return DisplayConfig{
+ .use_gc9107 = false,
+ .mirror_x = true,
+ .mirror_y = true,
+ .swap_xy = false,
+ .invert_color = true,
+ .rgb_order = LCD_RGB_ELEMENT_ORDER_BGR,
+ .offset_x = 2,
+ .offset_y = 3,
+ .spi_mode = 0,
+ .screen_name = "ST7735"
+ };
+ } else {
+ ESP_LOGW(TAG, "Unknown PCB version: %d, using default ST7735 configuration", pcb_version_);
+ return DisplayConfig{
+ .use_gc9107 = false,
+ .mirror_x = true,
+ .mirror_y = true,
+ .swap_xy = false,
+ .invert_color = true,
+ .rgb_order = LCD_RGB_ELEMENT_ORDER_BGR,
+ .offset_x = 2,
+ .offset_y = 3,
+ .spi_mode = 0,
+ .screen_name = "ST7735 (default)"
+
+ };
+ }
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_48);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(240, 60, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void Enable4GModule() {
+ // enable the 4G module
+ gpio_reset_pin(ML307_POWER_PIN);
+ gpio_set_direction(ML307_POWER_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_level(ML307_POWER_PIN, ML307_POWER_OUTPUT_INVERT ? 0 : 1);
+ }
+ void Disable4GModule() {
+ // enable the 4G module
+ gpio_reset_pin(ML307_POWER_PIN);
+ gpio_set_direction(ML307_POWER_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_level(ML307_POWER_PIN, ML307_POWER_OUTPUT_INVERT ? 1 : 0);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 CheckNetType() {
+ if (GetNetworkType() == NetworkType::WIFI) {
+ Disable4GModule();
+ } else if (GetNetworkType() == NetworkType::ML307) {
+ Enable4GModule();
+ }
+
+ }
+
+ //閫氳繃adc璇诲彇IO3寮曡剼鐨勭數鍘嬫潵鑾峰彇PCB鐗堟湰
+ void CheckPCBVersion() {
+ adc_oneshot_unit_handle_t adc1_handle;
+ adc_oneshot_unit_init_cfg_t init_config1 = {
+ .unit_id = ADC_UNIT_1,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config1, &adc1_handle));
+
+ adc_oneshot_chan_cfg_t config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc1_handle, ADC_CHANNEL_2, &config));
+
+ //鑾峰彇10娆★紝 鐒跺悗姹傚钩鍧�
+ int adc_value = 0;
+ int raw_value;
+ for (int i = 0; i < 10; i++) {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc1_handle, ADC_CHANNEL_2, &raw_value));
+ adc_value += raw_value;
+ }
+ adc_value /= 10;
+
+ int voltage = adc_value * (3300 / 4095.0);
+ if (voltage < 100) {
+ // 鐗堟湰1
+ pcb_version_ = PCB_VERSION_2_5A;
+ } else if (voltage > 3200 ) {
+ // 鐗堟湰2
+ pcb_version_ = PCB_VERSION_2_5A1;
+ }
+ // test
+ // pcb_version_ = PCB_VERSION_2_5A1;
+
+ adc_oneshot_del_unit(adc1_handle);
+ ESP_LOGI(TAG, "io voltage: %d, pcb_version: %d\n", voltage, pcb_version_);
+ }
+
+ void InitializeButtons() {
+ main_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ }
+ }
+ });
+ main_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+ main_button_.OnPressDown([this]() {
+ power_save_timer_->WakeUp();
+ Application::GetInstance().StartListening();
+ });
+ main_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ left_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ left_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+ right_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ right_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+ }
+
+ void InitializeLedPower() {
+ // 璁剧疆GPIO妯″紡
+ gpio_reset_pin(BUILTIN_LED_POWER);
+ gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT);
+ gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay(){
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 鑾峰彇灞忓箷閰嶇疆
+ DisplayConfig config = GetDisplayConfig();
+ ESP_LOGW(TAG, "PCB Version: %d, Using %s screen", pcb_version_, config.screen_name);
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = config.spi_mode;
+ io_config.pclk_hz = 20 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = config.rgb_order;
+ panel_config.bits_per_pixel = 16;
+ panel_config.flags.reset_active_high = 0;
+
+ if (config.use_gc9107) {
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+ panel_config.vendor_config = &gc9107_vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel));
+ } else {
+ 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, config.invert_color);
+ esp_lcd_panel_swap_xy(panel, config.swap_xy);
+ esp_lcd_panel_mirror(panel, config.mirror_x, config.mirror_y);
+
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true));
+
+ display_ = new SpiLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, config.offset_x, config.offset_y, config.mirror_x, config.mirror_y, config.swap_xy);
+ }
+
+public:
+ magiclick_2p5() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
+ main_button_(MAIN_BUTTON_GPIO),
+ left_button_(LEFT_BUTTON_GPIO),
+ right_button_(RIGHT_BUTTON_GPIO) {
+ CheckPCBVersion();
+ InitializeLedPower();
+ CheckNetType();
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static CircularStrip led(BUILTIN_LED_GPIO, BUILTIN_LED_NUM);
+ return &led;
+ }
+
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(magiclick_2p5);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/power_manager.h
new file mode 100644
index 0000000..2cc9821
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-2p5/power_manager.h
@@ -0,0 +1,195 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+#define CHARGING_PIN GPIO_NUM_48
+#define CHARGING_ACTIVE_STATE 0
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = CHARGING_PIN;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 5;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 0;
+ // ESP_LOGI("PowerManager", "new_charging_status: %s,is_charging_:%s", new_charging_status?"True":"False",is_charging_?"True":"False");
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value));
+ ESP_LOGI("PowerManager", "ADC value: %d ", adc_value);
+
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1985, 0},
+ {2079, 20},
+ {2141, 40},
+ {2296, 60},
+ {2420, 80},
+ {2606, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 妫�娴嬪厖鐢垫寚绀哄紩鑴�
+ if(gpio_get_level(charging_pin_) != CHARGING_ACTIVE_STATE)
+ {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.h b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.h
new file mode 100644
index 0000000..5609bf6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.h
@@ -0,0 +1,47 @@
+#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_5
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_8
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_11
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_NUM 1
+#define BUILTIN_LED_GPIO GPIO_NUM_0
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_2
+
+//battery
+#define BUILTIN_BATTERY_GPIO GPIO_NUM_1
+
+// display
+#define DISPLAY_SDA_PIN GPIO_NUM_13
+#define DISPLAY_SCL_PIN GPIO_NUM_12
+#define DISPLAY_CS_PIN GPIO_NUM_20
+#define DISPLAY_DC_PIN GPIO_NUM_21
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.json b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.json
new file mode 100644
index 0000000..4503ebd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "magiclick-c3-v2",
+ "sdkconfig_append": [
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_USE_ESP_WAKE_WORD=n"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc
new file mode 100644
index 0000000..bf99cce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc
@@ -0,0 +1,233 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "config.h"
+#include "power_save_timer.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+#include <esp_efuse_table.h>
+
+#define TAG "magiclick_c3_v2"
+
+class GC9107Display : public SpiLcdDisplay {
+public:
+ GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ }
+
+ void SetupUI() override {
+ SpiLcdDisplay::SetupUI();
+
+ // Apply custom color styles after parent creates all LVGL objects
+ DisplayLockGuard lock(this);
+ auto screen = lv_disp_get_scr_act(lv_disp_get_default());
+ lv_obj_set_style_text_color(screen, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(container_, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(status_bar_, lv_color_make(0x1e, 0x90, 0xff), 0);
+ lv_obj_set_style_text_color(network_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(status_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(content_, lv_color_black(), 0);
+ lv_obj_set_style_border_width(content_, 0, 0);
+ lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0);
+ lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
+ }
+};
+
+static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ {0xfe, (uint8_t[]){0x00}, 0, 0},
+ {0xef, (uint8_t[]){0x00}, 0, 0},
+ {0xb0, (uint8_t[]){0xc0}, 1, 0},
+ {0xb1, (uint8_t[]){0x80}, 1, 0},
+ {0xb2, (uint8_t[]){0x27}, 1, 0},
+ {0xb3, (uint8_t[]){0x13}, 1, 0},
+ {0xb6, (uint8_t[]){0x19}, 1, 0},
+ {0xb7, (uint8_t[]){0x05}, 1, 0},
+ {0xac, (uint8_t[]){0xc8}, 1, 0},
+ {0xab, (uint8_t[]){0x0f}, 1, 0},
+ {0x3a, (uint8_t[]){0x05}, 1, 0},
+ {0xb4, (uint8_t[]){0x04}, 1, 0},
+ {0xa8, (uint8_t[]){0x08}, 1, 0},
+ {0xb8, (uint8_t[]){0x08}, 1, 0},
+ {0xea, (uint8_t[]){0x02}, 1, 0},
+ {0xe8, (uint8_t[]){0x2A}, 1, 0},
+ {0xe9, (uint8_t[]){0x47}, 1, 0},
+ {0xe7, (uint8_t[]){0x5f}, 1, 0},
+ {0xc6, (uint8_t[]){0x21}, 1, 0},
+ {0xc7, (uint8_t[]){0x15}, 1, 0},
+ {0xf0,
+ (uint8_t[]){0x1D, 0x38, 0x09, 0x4D, 0x92, 0x2F, 0x35, 0x52, 0x1E, 0x0C,
+ 0x04, 0x12, 0x14, 0x1f},
+ 14, 0},
+ {0xf1,
+ (uint8_t[]){0x16, 0x40, 0x1C, 0x54, 0xA9, 0x2D, 0x2E, 0x56, 0x10, 0x0D,
+ 0x0C, 0x1A, 0x14, 0x1E},
+ 14, 0},
+ {0xf4, (uint8_t[]){0x00, 0x00, 0xFF}, 3, 0},
+ {0xba, (uint8_t[]){0xFF, 0xFF}, 2, 0},
+};
+
+class magiclick_c3_v2 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ GC9107Display* display_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(160);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ // Print I2C bus info
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ power_save_timer_->WakeUp();
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL_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 InitializeGc9107Display(){
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖GC9107
+ ESP_LOGD(TAG, "Install LCD driver");
+ gc9a01_vendor_config_t gc9107_vendor_config = {
+ .init_cmds = gc9107_lcd_init_cmds,
+ .init_cmds_size = sizeof(gc9107_lcd_init_cmds) / sizeof(gc9a01_lcd_init_cmd_t),
+ };
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &gc9107_vendor_config;
+
+ esp_lcd_new_panel_gc9a01(panel_io, &panel_config, &panel);
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new GC9107Display(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+public:
+ magiclick_c3_v2() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeGc9107Display();
+ GetBacklight()->RestoreBrightness();
+
+ // 鎶� ESP32C3 鐨� VDD SPI 寮曡剼浣滀负鏅�� GPIO 鍙d娇鐢�
+ esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ 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(magiclick_c3_v2);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.h b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.h
new file mode 100644
index 0000000..90cd2b7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.h
@@ -0,0 +1,47 @@
+#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_5
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_8
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_10
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_11
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_NUM 1
+#define BUILTIN_LED_GPIO GPIO_NUM_0
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_2
+
+//battery
+#define BUILTIN_BATTERY_GPIO GPIO_NUM_1
+
+// display
+#define DISPLAY_SDA_PIN GPIO_NUM_12
+#define DISPLAY_SCL_PIN GPIO_NUM_13
+#define DISPLAY_CS_PIN GPIO_NUM_20
+#define DISPLAY_DC_PIN GPIO_NUM_21
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_9
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.json b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.json
new file mode 100644
index 0000000..34d1471
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "magiclick-c3",
+ "sdkconfig_append": [
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_USE_ESP_WAKE_WORD=n"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/magiclick_c3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/magiclick_c3_board.cc
new file mode 100644
index 0000000..ae86bfc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/magiclick-c3/magiclick_c3_board.cc
@@ -0,0 +1,191 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "config.h"
+#include "power_save_timer.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_nv3023.h>
+#include <esp_efuse_table.h>
+
+#define TAG "magiclick_c3"
+
+class NV3023Display : public SpiLcdDisplay {
+public:
+ NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ }
+
+ void SetupUI() override {
+ SpiLcdDisplay::SetupUI();
+
+ // Apply custom color styles after parent creates all LVGL objects
+ DisplayLockGuard lock(this);
+ auto screen = lv_disp_get_scr_act(lv_disp_get_default());
+ lv_obj_set_style_text_color(screen, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(container_, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(status_bar_, lv_color_white(), 0);
+ lv_obj_set_style_text_color(network_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(notification_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(status_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(mute_label_, lv_color_black(), 0);
+ lv_obj_set_style_text_color(battery_label_, lv_color_black(), 0);
+
+ lv_obj_set_style_bg_color(content_, lv_color_black(), 0);
+ lv_obj_set_style_border_width(content_, 0, 0);
+ lv_obj_set_style_text_color(emoji_label_, lv_color_white(), 0);
+ lv_obj_set_style_text_color(chat_message_label_, lv_color_white(), 0);
+ }
+};
+
+class magiclick_c3 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ NV3023Display* display_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(160);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ // Print I2C bus info
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ power_save_timer_->WakeUp();
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL_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 InitializeNv3023Display(){
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖NV3023
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new NV3023Display(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+public:
+ magiclick_c3() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeButtons();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeNv3023Display();
+ GetBacklight()->RestoreBrightness();
+
+ // 鎶� ESP32C3 鐨� VDD SPI 寮曡剼浣滀负鏅�� GPIO 鍙d娇鐢�
+ esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ 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(magiclick_c3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/README.md b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/README.md
new file mode 100644
index 0000000..8b4a3e6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/README.md
@@ -0,0 +1,36 @@
+
+minsi-k08-wifi鍜宮insi-k08-ml307鏄晱鎬濈鎶�鎺ㄥ嚭鐨勫熀浜嶦SP32S3N16R8锛屾惌杞組AX98357闊抽鍔熺巼鏀惧ぇ鍣ㄥ拰INMP441鍏ㄥ悜楹﹀厠椋庢ā鍧楋紝閫氳繃鏀归�燢08閫忔槑鏈虹敳灏忛挗鐐煶绠辫�屾垚鐨勫甫鏈夋湅鍏嬮鏍肩殑澶у枃鍙ぇ鐢垫睜灏忔櫤AI鑱婂ぉ鏈哄櫒浜烘柟妗堛��
+
+<a href="https://item.taobao.com/item.htm?id=889892765588" target="_blank" title="SenseCAP Watcher">Minsi-k08</a>
+
+ <a href="minsi-k08.jpg" target="_blank" title="Minsi-k08">
+ <img src="minsi-k08.jpg" width="240" />
+ </a>
+
+
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type ->鏁忔�濈鎶�K08(DUAL)
+```
+
+**缂栬瘧鐑у叆锛�**
+
+```bash
+idf.py build flash
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.h b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.h
new file mode 100644
index 0000000..0bdecb0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.h
@@ -0,0 +1,47 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_9
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_10
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_18
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_4
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_6
+
+#define BUILTIN_LED_GPIO GPIO_NUM_40
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define TOUCH_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_47
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_46
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_7
+#define DISPLAY_MOSI_PIN GPIO_NUM_17
+#define DISPLAY_CLK_PIN GPIO_NUM_8
+#define DISPLAY_DC_PIN GPIO_NUM_15
+#define DISPLAY_RST_PIN GPIO_NUM_16
+#define DISPLAY_CS_PIN GPIO_NUM_NC
+
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 80
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+// A MCP Test: Control a lamp
+#define LAMP_GPIO GPIO_NUM_18
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.json b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.json
new file mode 100644
index 0000000..dd7f964
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "minsi-k08-dual",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/minsi_k08_dual.cc b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/minsi_k08_dual.cc
new file mode 100644
index 0000000..460f28b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/minsi_k08_dual.cc
@@ -0,0 +1,246 @@
+#include "dual_network_board.h"
+//#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "mcp_server.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <driver/spi_common.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "MINSI_K08_DUAL"
+
+class MINSI_K08_DUAL : public DualNetworkBoard {
+private:
+
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ //power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_ = new PowerManager(GPIO_NUM_3);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ //power_save_timer_->SetEnabled(false);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ /*rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);*/
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ //rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ //rtc_gpio_hold_en(GPIO_NUM_21);
+ //esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ //esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+
+ //power_save_timer_->SetEnabled(false);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 3;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {
+ static LampController lamp(LAMP_GPIO);
+ }
+
+public:
+ MINSI_K08_DUAL() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ //power_save_timer_->SetEnabled(false);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(MINSI_K08_DUAL);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/power_manager.h
new file mode 100644
index 0000000..8ecdebc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/minsi-k08-dual/power_manager.h
@@ -0,0 +1,187 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ //bool new_charging_status = 0;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {894, 0},
+ {954, 20},
+ {1020, 40},
+ {1084, 60},
+ {1144, 80},
+ {1234, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/README.md b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/README.md
new file mode 100644
index 0000000..8a992ef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/README.md
@@ -0,0 +1,72 @@
+# Mixgo_Nova(鍏冩帶路闈掓槬) 寮�鍙戞澘
+
+<img src="https://mixly.cn/public/icon/2024/6/09705006c1c643beb96338791ee1dea0_m.png" alt="Mixgo_Nova" width="200"/>
+
+‌**[Mixgo_Nova](https://mixly.cn/fredqian/mixgo_nova)**‌ 鏄竴娆句笓涓虹墿鑱旂綉銆佹暀鑲插強鍒涘椤圭洰璁捐鐨勫鍔熻兘寮�鍙戞澘锛岄泦鎴愪赴瀵屼紶鎰熷櫒涓庢棤绾块�氫俊妯″潡锛屾敮鎸佸浘褰㈠寲缂栫▼锛圡ixly锛夊拰绂荤嚎璇煶浜や簰锛岄�傚悎蹇�熷師鍨嬪紑鍙戜笌鏁欏銆�
+
+---
+
+## 馃洜锔� 缂栬瘧閰嶇疆鍛戒护
+
+**ES8374 CODE MIC閲囬泦闂锛�**
+
+```
+managed_components\espressif__esp_codec_dev\device\es8374
+
+static int es8374_config_adc_input(audio_codec_es8374_t *codec, es_adc_input_t input)
+{
+ int ret = 0;
+ int reg = 0;
+ ret |= es8374_read_reg(codec, 0x21, ®);
+ if (ret == 0) {
+ reg = (reg & 0xcf) | 0x24;
+ ret |= es8374_write_reg(codec, 0x21, reg);
+ }
+ return ret;
+}
+
+PS: L386 reg = (reg & 0xcf) | 0x14; 鏀规垚 reg = (reg & 0xcf) | 0x24;
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> 鍏冩帶路闈掓槬
+```
+
+**淇敼 psram 閰嶇疆锛�**
+
+```
+Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> QUAD Mode PSRAM
+```
+
+**淇敼 Flash 閰嶇疆锛�**
+
+```
+Serial flasher config -> Flash size -> 8 MB
+Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
+
+**鍚堝苟BIN锛�**
+
+```bash
+idf.py merge-bin -o xiaozhi-nova.bin -f raw
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.h b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.h
new file mode 100644
index 0000000..060870b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.h
@@ -0,0 +1,42 @@
+#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_35
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_33
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_37
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_36
+#define AUDIO_CODEC_ES8374_ADDR ES8374_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_38
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_MOSI_PIN GPIO_NUM_40
+#define DISPLAY_CLK_PIN GPIO_NUM_41
+#define DISPLAY_DC_PIN GPIO_NUM_18
+#define DISPLAY_CS_PIN GPIO_NUM_45
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_14
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 160
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR false
+#define DISPLAY_OFFSET_X 2
+#define DISPLAY_OFFSET_Y 1
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.json b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.json
new file mode 100644
index 0000000..7c9d553
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "mixgo-nova",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MODE_QUAD=y",
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
+ "CONFIG_LCD_ST7735_128X160=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/mixgo-nova.cc b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/mixgo-nova.cc
new file mode 100644
index 0000000..5d586ec
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/mixgo-nova/mixgo-nova.cc
@@ -0,0 +1,168 @@
+#include "wifi_board.h"
+#include "codecs/es8374_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/circular_strip.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+
+#define TAG "MIXGO_NOVA"
+
+class MIXGO_NOVA : public WifiBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ LcdDisplay* display_;
+ i2c_master_bus_handle_t codec_i2c_bus_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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);
+ }
+
+public:
+ MIXGO_NOVA() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ GetBacklight()->RestoreBrightness();
+ }
+ }
+
+ virtual Led* GetLed() override {
+ static CircularStrip led(BUILTIN_LED_GPIO, 4);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8374AudioCodec 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_ES8374_ADDR);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ if (DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC) {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+ return nullptr;
+ }
+
+};
+
+DECLARE_BOARD(MIXGO_NOVA);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/README.md
new file mode 100644
index 0000000..7d8eeba
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/README.md
@@ -0,0 +1,44 @@
+# ESP32-S3 缂栬瘧閰嶇疆鎸囧崡
+
+## 鍩烘湰鍛戒护
+
+### 璁剧疆鐩爣鑺墖
+
+```bash
+idf.py set-target esp32s3
+```
+
+### 鎵撳紑閰嶇疆鐣岄潰锛�
+
+```bash
+idf.py menuconfig
+```
+### Flash 閰嶇疆:
+
+```
+Serial flasher config -> Flash size -> 8 MB
+```
+
+### 鍒嗗尯琛ㄩ厤缃細
+
+```
+Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
+```
+
+### 寮�鍙戞澘閫夋嫨锛�
+
+```
+Xiaozhi Assistant -> Board Type -> Movecall CuiCan 鐠�鐠稟I鍚婂潬
+```
+
+### 鍚敤缂栬瘧浼樺寲锛�
+
+```
+Component config 鈫� Compiler options 鈫� Optimization Level 鈫� Optimize for size (-Os)
+```
+
+### 缂栬瘧锛�
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.h
new file mode 100644
index 0000000..156121d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.h
@@ -0,0 +1,45 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// Movecall CuiCan configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_41
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_17
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_6
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_21
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_12
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_10
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_13
+#define DISPLAY_SPI_DC_PIN GPIO_NUM_14
+#define DISPLAY_SPI_RESET_PIN GPIO_NUM_11
+
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.json
new file mode 100644
index 0000000..13a8eb4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "movecall-cuican-esp32s3",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
+ "CONFIG_COMPILER_OPTIMIZATION_SIZE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc
new file mode 100644
index 0000000..ce72cc5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-cuican-esp32s3/movecall_cuican_esp32s3.cc
@@ -0,0 +1,123 @@
+#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 "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+
+#define TAG "MovecallCuicanESP32S3"
+
+class MovecallCuicanESP32S3 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN,
+ DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ // GC9A01鍒濆鍖�
+ void InitializeGc9a01Display() {
+ ESP_LOGI(TAG, "Init GC9A01 display");
+
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL);
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle));
+
+ ESP_LOGI(TAG, "Install GC9A01 panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use
+ panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB;
+ panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
+
+ display_ = new SpiLcdDisplay(io_handle, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ MovecallCuicanESP32S3() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeSpi();
+ InitializeGc9a01Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led_strip(BUILTIN_LED_GPIO);
+ return &led_strip;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ 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;
+ }
+};
+
+DECLARE_BOARD(MovecallCuicanESP32S3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/README.md
new file mode 100644
index 0000000..812d9d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/README.md
@@ -0,0 +1,26 @@
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> Movecall Moji 灏忔櫤AI琛嶇敓鐗�
+```
+
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.h
new file mode 100644
index 0000000..fd254ca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.h
@@ -0,0 +1,45 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// Movecall Moji configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_13
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_9
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_21
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_3
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_16
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_17
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_15
+#define DISPLAY_SPI_DC_PIN GPIO_NUM_7
+#define DISPLAY_SPI_RESET_PIN GPIO_NUM_18
+
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.json
new file mode 100644
index 0000000..c62a8fd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "movecall-moji-esp32s3",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc
new file mode 100644
index 0000000..a1a993b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc
@@ -0,0 +1,150 @@
+#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 "led/single_led.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+
+#define TAG "MovecallMojiESP32S3"
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ // 鐢变簬灞忓箷鏄渾鐨勶紝鎵�浠ョ姸鎬佹爮闇�瑕佸鍔犲乏鍙冲唴杈硅窛
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0);
+ }
+};
+
+class MovecallMojiESP32S3 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN,
+ DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ // GC9A01鍒濆鍖�
+ void InitializeGc9a01Display() {
+ ESP_LOGI(TAG, "Init GC9A01 display");
+
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, NULL, NULL);
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle));
+
+ ESP_LOGI(TAG, "Install GC9A01 panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use
+ panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB;
+ panel_config.bits_per_pixel = 16; // Implemented by LCD command `3Ah` (16/18)
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
+
+ display_ = new SpiLcdDisplay(io_handle, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ MovecallMojiESP32S3() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeSpi();
+ InitializeGc9a01Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led_strip(BUILTIN_LED_GPIO);
+ return &led_strip;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ 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;
+ }
+};
+
+DECLARE_BOARD(MovecallMojiESP32S3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README.md b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README.md
new file mode 100644
index 0000000..55dff1e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README.md
@@ -0,0 +1,57 @@
+# Build and Configuration Guide
+
+This document provides instructions on how to configure and build the firmware for **Movecall Moji2.0 (Xiaozhi AI Edition)**.
+
+## 馃洜 Prerequisites
+* **ESP-IDF Version**: v5.5
+* **Target Chip**: ESP32-C5
+
+## 馃敆 Hardware Information
+This project is based on the following open-source hardware:
+* **OSHWHub Link**: [https://oshwhub.com/movecall/moji2](https://oshwhub.com/movecall/moji2)
+
+---
+
+## 馃殌 Build Steps
+
+### 1. Set the Build Target
+Initialize the project to target the ESP32-C5 chip:
+```bash
+idf.py set-target esp32c5
+```
+
+### 2. Configure the Board Type
+Open the graphical configuration menu:
+```bash
+idf.py menuconfig
+```
+
+**Navigate to the following path to select your board:**
+> **Xiaozhi Assistant** -> **Board Type** -> **Movecall Moji2.0 灏忔櫤AI琛嶇敓鐗�**
+
+*Note: After selecting, press **S** to save (then Enter to confirm) and press **Q** to exit.*
+
+### 3. Build the Project
+Run the following command to start the compilation:
+```bash
+idf.py build
+```
+
+---
+
+## 馃敡 Useful Commands
+
+**Clean Build Files (Recommended if you encounter errors):**
+```bash
+idf.py fullclean
+```
+
+**Flash Firmware to Device:**
+```bash
+idf.py flash
+```
+
+**Monitor Serial Output:**
+```bash
+idf.py monitor
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README_zh.md b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README_zh.md
new file mode 100644
index 0000000..ccef87b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/README_zh.md
@@ -0,0 +1,57 @@
+# 缂栬瘧閰嶇疆鎸囧崡
+
+鏈枃妗d粙缁嶄簡濡備綍涓� **Movecall Moji2.0 (灏忔櫤AI琛嶇敓鐗�)** 閰嶇疆鍜岀紪璇戝浐浠躲��
+
+## 馃洜 鐜瑕佹眰
+* **ESP-IDF 鐗堟湰**: v5.5
+* **鑺墖鍨嬪彿**: ESP32-C5
+
+## 馃敆 纭欢寮�婧愪俊鎭�
+鏈」鐩熀浜庝互涓嬪紑婧愮‖浠惰璁★細
+* **绔嬪垱寮�婧愮‖浠跺钩鍙�**: [https://oshwhub.com/movecall/moji2](https://oshwhub.com/movecall/moji2)
+
+---
+
+## 馃殌 缂栬瘧姝ラ
+
+### 1. 璁剧疆缂栬瘧鐩爣
+棣栧厛锛屽皢椤圭洰鐩爣鑺墖璁剧疆涓� ESP32-C5锛�
+```bash
+idf.py set-target esp32c5
+```
+
+### 2. 閰嶇疆寮�鍙戞澘鍨嬪彿
+杩愯浠ヤ笅鍛戒护鎵撳紑閰嶇疆鑿滃崟杩涜鏉垮瀷閫夋嫨锛�
+```bash
+idf.py menuconfig
+```
+
+**璇峰湪鑿滃崟涓寜鐓т互涓嬭矾寰勮繘琛屾搷浣滐細**
+> **Xiaozhi Assistant** -> **Board Type** -> **Movecall Moji2.0 灏忔櫤AI琛嶇敓鐗�**
+
+*鎿嶄綔鎻愮ず锛氶厤缃畬鎴愬悗锛屾寜 **S** 淇濆瓨骞舵寜鍥炶溅纭锛屾寜 **Q** 閫�鍑恒��*
+
+### 3. 鎵ц缂栬瘧
+杩愯浠ヤ笅鍛戒护寮�濮嬫瀯寤洪」鐩細
+```bash
+idf.py build
+```
+
+---
+
+## 馃敡 甯哥敤缁存姢鍛戒护
+
+**娓呯悊缂栬瘧缂撳瓨 (閬囧埌鎶ラ敊寤鸿鎵ц)锛�**
+```bash
+idf.py fullclean
+```
+
+**鐑у綍鍥轰欢锛�**
+```bash
+idf.py flash
+```
+
+**鏌ョ湅涓插彛鏃ュ織锛�**
+```bash
+idf.py monitor
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.h b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.h
new file mode 100644
index 0000000..4664344
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.h
@@ -0,0 +1,68 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// Movecall Moji 2 configuration
+
+#include <driver/gpio.h>
+
+enum PowerSupply {
+ kDeviceTypecSupply,
+ kDeviceBatterySupply,
+};
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_25
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_24
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_5
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_26
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_27
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_10
+#define BOOT_BUTTON_GPIO GPIO_NUM_28
+
+#define DISPLAY_WIDTH 360
+#define DISPLAY_HEIGHT 360
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define DISPLAY_QSPI_H_RES (360)
+#define DISPLAY_QSPI_V_RES (360)
+#define DISPLAY_QSPI_BIT_PER_PIXEL (16)
+
+#define DISPLAY_QSPI_HOST SPI2_HOST
+#define DISPLAY_QSPI_SCLK_PIN GPIO_NUM_0
+#define DISPLAY_QSPI_RESET_PIN GPIO_NUM_1
+#define DISPLAY_QSPI_D0_PIN GPIO_NUM_9
+#define DISPLAY_QSPI_D1_PIN GPIO_NUM_8
+#define DISPLAY_QSPI_D2_PIN GPIO_NUM_7
+#define DISPLAY_QSPI_D3_PIN GPIO_NUM_6
+#define DISPLAY_QSPI_CS_PIN GPIO_NUM_3
+
+
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+#define MOJI2_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
+ { \
+ .data0_io_num = d0, \
+ .data1_io_num = d1, \
+ .sclk_io_num = sclk, \
+ .data2_io_num = d2, \
+ .data3_io_num = d3, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.json b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.json
new file mode 100644
index 0000000..316e963
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32c5",
+ "builds": [
+ {
+ "name": "movecall-moji2-esp32c5",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/16m.csv\"",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_SPIRAM=y",
+ "CONFIG_SPIRAM_MODE_QUAD=y",
+ "CONFIG_SPIRAM_SPEED_80M=y",
+ "CONFIG_SPI_FLASH_FREQ_LIMIT_C5_240MHZ=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc
new file mode 100644
index 0000000..93152ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/movecall-moji2-esp32c5/movecall_moji2_esp32s3.cc
@@ -0,0 +1,393 @@
+#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 "led/single_led.h"
+#include "press_to_talk_mcp_tool.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+#include "power_save_timer.h"
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <esp_lcd_st77916.h>
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+
+#include "adc_battery_monitor.h"
+#include "sleep_timer.h"
+
+#define TAG "MovecallMoji2ESP32C5"
+
+static const st77916_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xF0, (uint8_t []){0x28}, 1, 0},
+ {0xF2, (uint8_t []){0x28}, 1, 0},
+ {0x73, (uint8_t []){0xF0}, 1, 0},
+ {0x7C, (uint8_t []){0xD1}, 1, 0},
+ {0x83, (uint8_t []){0xE0}, 1, 0},
+ {0x84, (uint8_t []){0x61}, 1, 0},
+ {0xF2, (uint8_t []){0x82}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x01}, 1, 0},
+ {0xF1, (uint8_t []){0x01}, 1, 0},
+ {0xB0, (uint8_t []){0x56}, 1, 0},
+ {0xB1, (uint8_t []){0x4D}, 1, 0},
+ {0xB2, (uint8_t []){0x24}, 1, 0},
+ {0xB4, (uint8_t []){0x87}, 1, 0},
+ {0xB5, (uint8_t []){0x44}, 1, 0},
+ {0xB6, (uint8_t []){0x8B}, 1, 0},
+ {0xB7, (uint8_t []){0x40}, 1, 0},
+ {0xB8, (uint8_t []){0x86}, 1, 0},
+ {0xBA, (uint8_t []){0x00}, 1, 0},
+ {0xBB, (uint8_t []){0x08}, 1, 0},
+ {0xBC, (uint8_t []){0x08}, 1, 0},
+ {0xBD, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x80}, 1, 0},
+ {0xC1, (uint8_t []){0x10}, 1, 0},
+ {0xC2, (uint8_t []){0x37}, 1, 0},
+ {0xC3, (uint8_t []){0x80}, 1, 0},
+ {0xC4, (uint8_t []){0x10}, 1, 0},
+ {0xC5, (uint8_t []){0x37}, 1, 0},
+ {0xC6, (uint8_t []){0xA9}, 1, 0},
+ {0xC7, (uint8_t []){0x41}, 1, 0},
+ {0xC8, (uint8_t []){0x01}, 1, 0},
+ {0xC9, (uint8_t []){0xA9}, 1, 0},
+ {0xCA, (uint8_t []){0x41}, 1, 0},
+ {0xCB, (uint8_t []){0x01}, 1, 0},
+ {0xD0, (uint8_t []){0x91}, 1, 0},
+ {0xD1, (uint8_t []){0x68}, 1, 0},
+ {0xD2, (uint8_t []){0x68}, 1, 0},
+ {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0},
+ {0xDD, (uint8_t []){0x4F}, 1, 0},
+ {0xDE, (uint8_t []){0x4F}, 1, 0},
+ {0xF1, (uint8_t []){0x10}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0},
+ {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0},
+ {0xF0, (uint8_t []){0x10}, 1, 0},
+ {0xF3, (uint8_t []){0x10}, 1, 0},
+ {0xE0, (uint8_t []){0x07}, 1, 0},
+ {0xE1, (uint8_t []){0x00}, 1, 0},
+ {0xE2, (uint8_t []){0x00}, 1, 0},
+ {0xE3, (uint8_t []){0x00}, 1, 0},
+ {0xE4, (uint8_t []){0xE0}, 1, 0},
+ {0xE5, (uint8_t []){0x06}, 1, 0},
+ {0xE6, (uint8_t []){0x21}, 1, 0},
+ {0xE7, (uint8_t []){0x01}, 1, 0},
+ {0xE8, (uint8_t []){0x05}, 1, 0},
+ {0xE9, (uint8_t []){0x02}, 1, 0},
+ {0xEA, (uint8_t []){0xDA}, 1, 0},
+ {0xEB, (uint8_t []){0x00}, 1, 0},
+ {0xEC, (uint8_t []){0x00}, 1, 0},
+ {0xED, (uint8_t []){0x0F}, 1, 0},
+ {0xEE, (uint8_t []){0x00}, 1, 0},
+ {0xEF, (uint8_t []){0x00}, 1, 0},
+ {0xF8, (uint8_t []){0x00}, 1, 0},
+ {0xF9, (uint8_t []){0x00}, 1, 0},
+ {0xFA, (uint8_t []){0x00}, 1, 0},
+ {0xFB, (uint8_t []){0x00}, 1, 0},
+ {0xFC, (uint8_t []){0x00}, 1, 0},
+ {0xFD, (uint8_t []){0x00}, 1, 0},
+ {0xFE, (uint8_t []){0x00}, 1, 0},
+ {0xFF, (uint8_t []){0x00}, 1, 0},
+ {0x60, (uint8_t []){0x40}, 1, 0},
+ {0x61, (uint8_t []){0x04}, 1, 0},
+ {0x62, (uint8_t []){0x00}, 1, 0},
+ {0x63, (uint8_t []){0x42}, 1, 0},
+ {0x64, (uint8_t []){0xD9}, 1, 0},
+ {0x65, (uint8_t []){0x00}, 1, 0},
+ {0x66, (uint8_t []){0x00}, 1, 0},
+ {0x67, (uint8_t []){0x00}, 1, 0},
+ {0x68, (uint8_t []){0x00}, 1, 0},
+ {0x69, (uint8_t []){0x00}, 1, 0},
+ {0x6A, (uint8_t []){0x00}, 1, 0},
+ {0x6B, (uint8_t []){0x00}, 1, 0},
+ {0x70, (uint8_t []){0x40}, 1, 0},
+ {0x71, (uint8_t []){0x03}, 1, 0},
+ {0x72, (uint8_t []){0x00}, 1, 0},
+ {0x73, (uint8_t []){0x42}, 1, 0},
+ {0x74, (uint8_t []){0xD8}, 1, 0},
+ {0x75, (uint8_t []){0x00}, 1, 0},
+ {0x76, (uint8_t []){0x00}, 1, 0},
+ {0x77, (uint8_t []){0x00}, 1, 0},
+ {0x78, (uint8_t []){0x00}, 1, 0},
+ {0x79, (uint8_t []){0x00}, 1, 0},
+ {0x7A, (uint8_t []){0x00}, 1, 0},
+ {0x7B, (uint8_t []){0x00}, 1, 0},
+ {0x80, (uint8_t []){0x48}, 1, 0},
+ {0x81, (uint8_t []){0x00}, 1, 0},
+ {0x82, (uint8_t []){0x06}, 1, 0},
+ {0x83, (uint8_t []){0x02}, 1, 0},
+ {0x84, (uint8_t []){0xD6}, 1, 0},
+ {0x85, (uint8_t []){0x04}, 1, 0},
+ {0x86, (uint8_t []){0x00}, 1, 0},
+ {0x87, (uint8_t []){0x00}, 1, 0},
+ {0x88, (uint8_t []){0x48}, 1, 0},
+ {0x89, (uint8_t []){0x00}, 1, 0},
+ {0x8A, (uint8_t []){0x08}, 1, 0},
+ {0x8B, (uint8_t []){0x02}, 1, 0},
+ {0x8C, (uint8_t []){0xD8}, 1, 0},
+ {0x8D, (uint8_t []){0x04}, 1, 0},
+ {0x8E, (uint8_t []){0x00}, 1, 0},
+ {0x8F, (uint8_t []){0x00}, 1, 0},
+ {0x90, (uint8_t []){0x48}, 1, 0},
+ {0x91, (uint8_t []){0x00}, 1, 0},
+ {0x92, (uint8_t []){0x0A}, 1, 0},
+ {0x93, (uint8_t []){0x02}, 1, 0},
+ {0x94, (uint8_t []){0xDA}, 1, 0},
+ {0x95, (uint8_t []){0x04}, 1, 0},
+ {0x96, (uint8_t []){0x00}, 1, 0},
+ {0x97, (uint8_t []){0x00}, 1, 0},
+ {0x98, (uint8_t []){0x48}, 1, 0},
+ {0x99, (uint8_t []){0x00}, 1, 0},
+ {0x9A, (uint8_t []){0x0C}, 1, 0},
+ {0x9B, (uint8_t []){0x02}, 1, 0},
+ {0x9C, (uint8_t []){0xDC}, 1, 0},
+ {0x9D, (uint8_t []){0x04}, 1, 0},
+ {0x9E, (uint8_t []){0x00}, 1, 0},
+ {0x9F, (uint8_t []){0x00}, 1, 0},
+ {0xA0, (uint8_t []){0x48}, 1, 0},
+ {0xA1, (uint8_t []){0x00}, 1, 0},
+ {0xA2, (uint8_t []){0x05}, 1, 0},
+ {0xA3, (uint8_t []){0x02}, 1, 0},
+ {0xA4, (uint8_t []){0xD5}, 1, 0},
+ {0xA5, (uint8_t []){0x04}, 1, 0},
+ {0xA6, (uint8_t []){0x00}, 1, 0},
+ {0xA7, (uint8_t []){0x00}, 1, 0},
+ {0xA8, (uint8_t []){0x48}, 1, 0},
+ {0xA9, (uint8_t []){0x00}, 1, 0},
+ {0xAA, (uint8_t []){0x07}, 1, 0},
+ {0xAB, (uint8_t []){0x02}, 1, 0},
+ {0xAC, (uint8_t []){0xD7}, 1, 0},
+ {0xAD, (uint8_t []){0x04}, 1, 0},
+ {0xAE, (uint8_t []){0x00}, 1, 0},
+ {0xAF, (uint8_t []){0x00}, 1, 0},
+ {0xB0, (uint8_t []){0x48}, 1, 0},
+ {0xB1, (uint8_t []){0x00}, 1, 0},
+ {0xB2, (uint8_t []){0x09}, 1, 0},
+ {0xB3, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0xD9}, 1, 0},
+ {0xB5, (uint8_t []){0x04}, 1, 0},
+ {0xB6, (uint8_t []){0x00}, 1, 0},
+ {0xB7, (uint8_t []){0x00}, 1, 0},
+ {0xB8, (uint8_t []){0x48}, 1, 0},
+ {0xB9, (uint8_t []){0x00}, 1, 0},
+ {0xBA, (uint8_t []){0x0B}, 1, 0},
+ {0xBB, (uint8_t []){0x02}, 1, 0},
+ {0xBC, (uint8_t []){0xDB}, 1, 0},
+ {0xBD, (uint8_t []){0x04}, 1, 0},
+ {0xBE, (uint8_t []){0x00}, 1, 0},
+ {0xBF, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x10}, 1, 0},
+ {0xC1, (uint8_t []){0x47}, 1, 0},
+ {0xC2, (uint8_t []){0x56}, 1, 0},
+ {0xC3, (uint8_t []){0x65}, 1, 0},
+ {0xC4, (uint8_t []){0x74}, 1, 0},
+ {0xC5, (uint8_t []){0x88}, 1, 0},
+ {0xC6, (uint8_t []){0x99}, 1, 0},
+ {0xC7, (uint8_t []){0x01}, 1, 0},
+ {0xC8, (uint8_t []){0xBB}, 1, 0},
+ {0xC9, (uint8_t []){0xAA}, 1, 0},
+ {0xD0, (uint8_t []){0x10}, 1, 0},
+ {0xD1, (uint8_t []){0x47}, 1, 0},
+ {0xD2, (uint8_t []){0x56}, 1, 0},
+ {0xD3, (uint8_t []){0x65}, 1, 0},
+ {0xD4, (uint8_t []){0x74}, 1, 0},
+ {0xD5, (uint8_t []){0x88}, 1, 0},
+ {0xD6, (uint8_t []){0x99}, 1, 0},
+ {0xD7, (uint8_t []){0x01}, 1, 0},
+ {0xD8, (uint8_t []){0xBB}, 1, 0},
+ {0xD9, (uint8_t []){0xAA}, 1, 0},
+ {0xF3, (uint8_t []){0x01}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0x21, (uint8_t []){}, 0, 0},
+ {0x11, (uint8_t []){}, 0, 0},
+ {0x00, (uint8_t []){}, 0, 120},
+};
+
+class MovecallMoji2ESP32C5 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+
+ PressToTalkMcpTool* press_to_talk_tool_ = nullptr;
+
+ PowerSaveTimer* power_save_timer_ = nullptr;
+ AdcBatteryMonitor* adc_battery_monitor_ = nullptr;
+
+ void InitializeBatteryMonitor() {
+ adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 5100000, 5100000, GPIO_NUM_NC);
+ adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) {
+ if (power_save_timer_ != nullptr){
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ }
+
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(240, -1, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ const spi_bus_config_t bus_config = MOJI2_ST77916_PANEL_BUS_QSPI_CONFIG(DISPLAY_QSPI_SCLK_PIN,
+ DISPLAY_QSPI_D0_PIN,
+ DISPLAY_QSPI_D1_PIN,
+ DISPLAY_QSPI_D2_PIN,
+ DISPLAY_QSPI_D3_PIN,
+ DISPLAY_QSPI_H_RES * 80 * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_QSPI_HOST, &bus_config, SPI_DMA_CH_AUTO));
+ }
+
+ // St77916 鍒濆鍖�
+ void InitializeSt77916Display() {
+ ESP_LOGI(TAG, "Init St77916 display");
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ ESP_LOGI(TAG, "Install panel IO");
+
+ esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(DISPLAY_QSPI_CS_PIN, NULL, NULL);
+ // io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)DISPLAY_QSPI_HOST, &io_config, &panel_io));
+
+
+ ESP_LOGI(TAG, "Install St77916 panel driver");
+ st77916_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = DISPLAY_QSPI_RESET_PIN,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = DISPLAY_QSPI_BIT_PER_PIXEL,
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_disp_on_off(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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT enters config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) {
+ app.ToggleChatState();
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ if (power_save_timer_) {
+ power_save_timer_->WakeUp();
+ }
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StartListening();
+ }
+ });
+ boot_button_.OnPressUp([this]() {
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StopListening();
+ }
+ });
+ }
+
+ void InitializeTools() {
+ press_to_talk_tool_ = new PressToTalkMcpTool();
+ press_to_talk_tool_->Initialize();
+ }
+
+public:
+ MovecallMoji2ESP32C5() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializePowerSaveTimer();
+ InitializeBatteryMonitor();
+ InitializeSpi();
+ InitializeSt77916Display();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led_strip(BUILTIN_LED_GPIO);
+ return &led_strip;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ 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 bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ charging = adc_battery_monitor_->IsCharging();
+ discharging = adc_battery_monitor_->IsDischarging();
+ level = adc_battery_monitor_->GetBatteryLevel();
+ return true;
+ }
+
+};
+
+DECLARE_BOARD(MovecallMoji2ESP32C5);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/README.md b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/README.md
new file mode 100644
index 0000000..b65e960
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/README.md
@@ -0,0 +1,29 @@
+# NULLLAB-AI-VOX3
+
+## 姒傝堪
+
+AI-VOX3鏄疉I VOX鐨勫崌绾ф锛屾槸涓�娆句笓涓篈I璇煶浜や簰搴旂敤璁捐鐨勯珮鎬ц兘宓屽叆寮忓紑鍙戞澘銆傚叾鏍稿績閲囩敤ESP32-S3-R8鑺墖锛屽苟鏉胯浇16MB Flash瀛樺偍鍣ㄣ�傞泦鎴愪簡涓板瘜鐨勭‖浠惰祫婧愶紝鏀寔蹇�熷紑鍙戜笌鐏垫椿鎵╁睍銆傞泦浜斿悎涓�鍔熻兘(AI鑱婂ぉ/澶╂皵鏃堕挓/鏃犵嚎瀵硅鏈篗P3闊充箰鎾斁鍣�/缃戠粶鐢靛彴)锛屽悓鏃舵敮鎸佹湰鍦拌闊冲敜閱掋�佹寚浠よ瘑鍒拰璇煶鍚堟垚锛屽彲骞挎硾搴旂敤浜庢櫤鑳藉灞呫�佹暀鑲茶澶囧拰鐗╄仈缃戠粓绔瓑棰嗗煙銆傚叾PCB灏哄鍏煎涔愰珮鎻掗攢锛屽彲鐩存帴瀹夎鍦ㄧН鏈–娆句笂锛屼究浜嶥IY鎼缓锛屽悓鏃堕厤濂桝I-VOX3鎵╁睍鏉夸笌MD40鐢垫満椹卞姩鏉匡紝鍔╁姏寮�鍙戣�呭熀浜庢澘杞借祫婧愬揩閫熸瀯寤哄師鍨嬶紝骞堕�氳繃涓板瘜鎺ュ彛瀹炵幇涓�у寲鍔熻兘鎵╁睍锛屽ぇ骞呯缉鐭紑鍙戝懆鏈熴��
+
+## 鍔熻兘鐗圭偣
+
+- 鎼浇 ESP32-S3R8 楂樻�ц兘 Xtensa 32 浣� LX7 鍙屾牳澶勭悊鍣紝涓婚楂樿揪240MHz
+- 鏀寔 2.4 GHz Wi-Fi (802.11 b/g/n) 鍜� Bluetooth 5 (LE)锛屾澘杞藉ぉ绾�
+- ESP32-S3R8鑺墖鍐呯疆 512 KB SRAM 鍜� 384 KB ROM浠ュ強8MB PSRAM锛屾澘杞�16 MB Flash 瀛樺偍鑺墖
+- 閲囩敤 Type-C 鎺ュ彛锛屾敮鎸佺▼搴忎笅杞姐�佹澘杞戒緵鐢靛強閿傜數姹犲厖鐢碉紝鍏煎涓绘祦寮�鍙戠幆澧冿紝绠�鍖栧紑鍙戜笌鐢垫簮绠$悊娴佺▼
+- 閲囩敤鐢垫簮澶嶄綅鎸夐敭浜屽悎涓�锛屽皢绯荤粺澶嶄綅鍜屽紑鍏虫満闆嗘垚鍒癙ower鎸夐敭涓紝鐭寜寮�鏈烘垨绯荤粺澶嶄綅锛岄暱鎸夊叧鏈猴紝绠�鍖栨搷浣�
+- 鍙帴1.54瀵�240脳240鍒嗚鲸鐜嘢PI鎺ュ彛LCD锛圫T7789锛夛紝鎻愪緵鐩磋鍥惧舰鍖栦氦浜掔晫闈�
+- 棰勭暀LCD鎺掔嚎鍜孫LED鎻掑彛锛屽彲浠ラ�夋嫨OLED鎴栬�匧CD褰╁睆鏄剧ず
+- 鏉胯浇ES8311闊抽缂栫爜瑙g爜鍣ㄤ笌3W闊抽鏀惧ぇ鍣紙NS4150B锛夛紝鏀寔楂樹繚鐪熼煶棰戣緭鍏�/杈撳嚭锛岄渶澶栨帴鍠囧彮
+- 鍙岄害鍏嬮璁捐锛屾澘杞芥ā鎷熼害鍏嬮锛岃繕鍙互澶栨寕妯℃嫙楹﹀厠椋庯紝鏀寔鍗曢害鎵撴柇
+- 鏉胯浇SD Card鎺ュ彛锛屾敮鎸佸ぇ瀹归噺瀛樺偍鎵╁睍
+- 鏉胯浇BOOT鎸夐敭銆�2涓寜閿紙GPIO46/45锛夊強WS2812B RGB鐏紝渚夸簬浜や簰璋冭瘯涓庣姸鎬佹寚绀�
+- 寮曞嚭涓�缁�8涓狦PIO鎺掗拡鎺ュ彛锛圙PIO43/44/42/48/4/3/2/1锛夛紝鏀寔澶氱澶栬鎺ュ叆
+- 棰勭暀涓�涓�4pin PH2.0鎺ュ彛锛屽彲浠ユ柟渚块�氳繃PH2.0渚涚數锛屼篃鍙互鍜屽叾浠栦富鎺ч�氳
+- 閰嶅澶栨帴AI-VOX3鎵╁睍鏉匡紝鍙�氳繃鍏舵墿灞曟澘鐨勬帓閽堟帴鍙f墿灞曟洿澶氬姛鑳�
+- 閰嶅澶栨帴MD40鐢垫満椹卞姩鏉匡紝鍙�氳繃鍏堕┍鍔ㄦ澘杩愯澶氫釜鐢垫満
+- 鏉胯浇鍏呯數鍗囧帇5V 2.4A杈撳嚭涓�浣撶數璺紝鏀寔澶栨帴閿傜數姹犱緵鐢碉紝骞堕�氳繃IO18 ADC瀹炴椂妫�娴嬬數閲�
+- 鏀寔ESP-IDF銆丄rduino IDE銆丄ilyBlockly
+
+## Power鎸夐敭璇存槑
+
+AI-VOX3鍙栨秷浜嗕紶缁熺殑Reset澶嶄綅鎸夐敭锛屾敼涓轰娇鐢≒ower鎸夐敭缁熶竴鎿嶄綔锛岀煭鎸変竴娆ower鎸夐敭鍒欒繘琛屽紑鏈烘垨绯荤粺澶嶄綅锛岄暱鎸塒ower鎸夐敭鍒欒繘琛屽叧鏈恒��
diff --git a/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_audio_codec.h
new file mode 100644
index 0000000..ea60c9d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_audio_codec.h
@@ -0,0 +1,237 @@
+#pragma once
+#include <driver/gpio.h>
+#include <driver/i2c_master.h>
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+#include <esp_log.h>
+
+#include "audio/audio_codec.h"
+
+class AIVOX3AudioCodec : 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;
+
+ // ref buffer used for aec
+ std::vector<int16_t> ref_buffer_;
+ int read_pos_ = 0;
+ int write_pos_ = 0;
+
+ void 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 = true,
+ .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_128},
+ .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},
+ .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_LOGI("AIVOX3AudioCodec", "Duplex channels created");
+ }
+
+ virtual int Read(int16_t* dest, int samples) override {
+ if (input_enabled_) {
+ if (!input_reference_) {
+ ESP_ERROR_CHECK_WITHOUT_ABORT(
+ esp_codec_dev_read(input_dev_, (void*)dest, samples * sizeof(int16_t)));
+ } else {
+ int size = samples / input_channels_;
+ std::vector<int16_t> data(size);
+ // read mic data
+ ESP_ERROR_CHECK_WITHOUT_ABORT(esp_codec_dev_read(input_dev_, (void*)data.data(),
+ data.size() * sizeof(int16_t)));
+ int j = 0;
+ int i = 0;
+ while (i < samples) {
+ // mic data
+ dest[i++] = data[j++];
+ // ref data
+ dest[i++] = read_pos_ < write_pos_ ? ref_buffer_[read_pos_++] : 0;
+ }
+
+ if (read_pos_ == write_pos_) {
+ read_pos_ = write_pos_ = 0;
+ }
+ }
+ }
+ return samples;
+ }
+
+ virtual int Write(const int16_t* data, int samples) override {
+ if (output_enabled_) {
+ ESP_ERROR_CHECK_WITHOUT_ABORT(
+ esp_codec_dev_write(output_dev_, (void*)data, samples * sizeof(int16_t)));
+ if (input_reference_) { // 鏉垮瓙涓嶆敮鎸佺‖浠跺洖閲囷紝閲囩敤缂撳瓨鎾斁缂撳瓨鏉ュ疄鐜板洖澹版秷闄�
+ if (write_pos_ - read_pos_ + samples > ref_buffer_.size()) {
+ assert(ref_buffer_.size() >= samples);
+ // 鍐欐孩鍑猴紝鍙繚鐣欐渶杩戠殑鏁版嵁
+ read_pos_ = write_pos_ + samples - ref_buffer_.size();
+ }
+ if (read_pos_) {
+ if (write_pos_ != read_pos_) {
+ memmove(ref_buffer_.data(), ref_buffer_.data() + read_pos_,
+ (write_pos_ - read_pos_) * sizeof(int16_t));
+ }
+ write_pos_ -= read_pos_;
+ read_pos_ = 0;
+ }
+ memcpy(&ref_buffer_[write_pos_], data, samples * sizeof(int16_t));
+ write_pos_ += samples;
+ }
+ }
+ return samples;
+ }
+
+public:
+ AIVOX3AudioCodec(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, uint8_t es8311_addr,
+ bool input_reference = false) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ if (input_reference) {
+ ref_buffer_.resize(960 * 2);
+ }
+ input_channels_ = 1 + input_reference_;
+ input_sample_rate_ = input_sample_rate;
+ output_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 = GPIO_NUM_NC;
+ es8311_cfg.use_mclk = true;
+ es8311_cfg.hw_gain.pa_voltage = 5.0;
+ es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
+ es8311_cfg.pa_reverted = false;
+ es8311_cfg.mclk_div = I2S_MCLK_MULTIPLE_128;
+ codec_if_ = es8311_codec_new(&es8311_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("AIVOX3AudioCodec", "AIVOX3AudioCodec initialized");
+ }
+
+ virtual ~AIVOX3AudioCodec() {
+ 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_);
+ }
+
+ virtual void SetOutputVolume(int volume) override {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+ }
+
+ virtual void EnableInput(bool enable) override {
+ 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 = I2S_MCLK_MULTIPLE_128,
+ };
+ ESP_ERROR_CHECK(esp_codec_dev_open(input_dev_, &fs));
+ ESP_ERROR_CHECK(esp_codec_dev_set_in_gain(input_dev_, 37.5));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+ }
+
+ virtual void EnableOutput(bool enable) override {
+ 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 = I2S_MCLK_MULTIPLE_128,
+ };
+ 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);
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_board.cc
new file mode 100644
index 0000000..af464d1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/ai_vox3_board.cc
@@ -0,0 +1,205 @@
+#include <driver/rtc_io.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include <esp_sleep.h>
+#include <wifi_station.h>
+
+#include "application.h"
+#include "assets/lang_config.h"
+#include "button.h"
+#include "config.h"
+#include "display/lcd_display.h"
+#include "dual_network_board.h"
+#include "led/single_led.h"
+
+#include "ai_vox3_audio_codec.h"
+#include "power_manager.h"
+
+#define TAG "AIVOX3"
+
+class AIVOX3 : public DualNetworkBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ PowerManager* power_manager_;
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ LcdDisplay* display_;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(BATTERY_LEVEL_PIN, BATTERY_CHARGING_PIN);
+ }
+
+ 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_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting ||
+ app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ }
+ }
+ app.ToggleChatState();
+ });
+
+ boot_button_.OnLongPress([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting ||
+ app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ // 鐗╄仈缃戝垵濮嬪寲锛屾坊鍔犲 AI 鍙璁惧
+ void InitializeTools() {}
+
+public:
+ AIVOX3()
+ : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, DEFAULT_4G_NETWORK),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializePowerManager();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static AIVOX3AudioCodec 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_ES8311_ADDR, AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+};
+
+DECLARE_BOARD(AIVOX3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.h b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.h
new file mode 100644
index 0000000..4ea9a52
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.h
@@ -0,0 +1,54 @@
+#pragma once
+#include <driver/gpio.h>
+
+#define DEFAULT_4G_NETWORK 0
+
+#if CONFIG_USE_DEVICE_AEC
+#define AUDIO_INPUT_REFERENCE true
+#else
+#define AUDIO_INPUT_REFERENCE false
+#endif
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_11
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_10
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_8
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_9
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_13
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_12
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_41
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_46
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_45
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_16
+#define DISPLAY_MOSI_PIN GPIO_NUM_21
+#define DISPLAY_CLK_PIN GPIO_NUM_17
+#define DISPLAY_DC_PIN GPIO_NUM_14
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+#define DISPLAY_CS_PIN GPIO_NUM_15
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 0
+
+#define BATTERY_LEVEL_PIN GPIO_NUM_18
+#define BATTERY_CHARGING_PIN GPIO_NUM_47
+
+#define ML307_TX_PIN GPIO_NUM_44
+#define ML307_RX_PIN GPIO_NUM_43
diff --git a/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.json b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.json
new file mode 100644
index 0000000..9223892
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "nulllab-ai-vox-v3",
+ "sdkconfig_append": ["CONFIG_ESP_CONSOLE_NONE=y"]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/power_manager.h
new file mode 100644
index 0000000..6fac0c6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/nulllab-ai-vox-v3/power_manager.h
@@ -0,0 +1,150 @@
+#pragma once
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+
+class PowerManager {
+private:
+ static constexpr uint32_t BATTERY_LEVEL_MIN = 2048;
+ static constexpr uint32_t BATTERY_LEVEL_MAX = 2330;
+ static constexpr size_t ADC_VALUES_COUNT = 10;
+ static constexpr size_t CHARGING_COUNT = 5;
+
+ esp_timer_handle_t timer_handle_ = nullptr;
+ gpio_num_t adc_pin_ = GPIO_NUM_NC;
+ uint16_t adc_values_[ADC_VALUES_COUNT];
+ size_t adc_values_index_ = 0;
+ size_t adc_values_count_ = 0;
+ uint8_t battery_level_ = 100;
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ bool is_charging_ = false;
+ int unchanging_count = 0;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ adc_channel_t adc_channel_;
+
+ void CheckBatteryStatus() {
+ if (charging_pin_ != GPIO_NUM_NC) {
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status) {
+ unchanging_count = 0;
+ } else {
+ unchanging_count++;
+ if (is_charging_ && unchanging_count < 5) {
+ new_charging_status = true;
+ }
+ }
+ is_charging_ = new_charging_status;
+ }
+
+ ReadBatteryAdcData();
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value));
+
+ adc_values_[adc_values_index_] = adc_value;
+ adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT;
+ if (adc_values_count_ < ADC_VALUES_COUNT) {
+ adc_values_count_++;
+ }
+
+ uint32_t average_adc = 0;
+ for (size_t i = 0; i < adc_values_count_; i++) {
+ average_adc += adc_values_[i];
+ }
+ average_adc /= adc_values_count_;
+
+ CalculateBatteryLevel(average_adc);
+
+ ESP_LOGI("PowerManager", "ADC鍊�: %d 骞冲潎鍊�: %ld 鐢甸噺: %u%%", adc_value, average_adc,
+ battery_level_);
+ }
+
+ void CalculateBatteryLevel(uint32_t average_adc) {
+ if (average_adc <= BATTERY_LEVEL_MIN) {
+ battery_level_ = 0;
+ } else if (average_adc >= BATTERY_LEVEL_MAX) {
+ battery_level_ = 100;
+ } else {
+ float ratio = static_cast<float>(average_adc - BATTERY_LEVEL_MIN) /
+ (BATTERY_LEVEL_MAX - BATTERY_LEVEL_MIN);
+ battery_level_ = ratio * 100;
+ }
+ }
+
+public:
+ PowerManager(gpio_num_t adc_pin_, gpio_num_t charging_pin)
+ : adc_pin_(adc_pin_), charging_pin_(charging_pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ if (charging_pin_ != GPIO_NUM_NC) {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+ esp_timer_create_args_t timer_args = {
+ .callback =
+ [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1绉�
+
+ InitializeAdc();
+ }
+
+ void InitializeAdc() {
+ adc_unit_t adc_unit;
+ ESP_ERROR_CHECK(adc_oneshot_io_to_channel(adc_pin_, &adc_unit, &adc_channel_));
+
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = adc_unit,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() { return battery_level_; }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/README.md b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/README.md
new file mode 100644
index 0000000..9aac113
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/README.md
@@ -0,0 +1,207 @@
+<p align="center">
+ <img width="80%" align="center" src="../../../docs/V1/otto-robot.png"alt="logo">
+</p>
+ <h1 align="center">
+ ottoRobot
+</h1>
+
+## 绠�浠�
+
+otto 鏈哄櫒浜烘槸涓�涓紑婧愮殑浜哄舰鏈哄櫒浜哄钩鍙帮紝鍏锋湁澶氱鍔ㄤ綔鑳藉姏鍜屼簰鍔ㄥ姛鑳姐�傛湰椤圭洰鍩轰簬 ESP32 瀹炵幇浜� otto 鏈哄櫒浜虹殑鎺у埗绯荤粺锛屽苟鍔犲叆灏忔櫤ai銆�
+
+- <a href="www.ottodiy.tech" target="_blank" title="otto瀹樼綉">澶嶅埢鏁欑▼</a>
+
+### 寰俊灏忕▼搴忔帶鍒�
+
+<p align="center">
+ <img width="300" src="https://youke1.picui.cn/s1/2025/11/17/691abaa8278eb.jpg" alt="寰俊灏忕▼搴忎簩缁寸爜">
+</p>
+
+鎵弿涓婃柟浜岀淮鐮侊紝浣跨敤寰俊灏忕▼搴忔帶鍒� Otto 鏈哄櫒浜恒��
+
+## 纭欢
+- <a href="https://oshwhub.com/txp666/ottorobot" target="_blank" title="绔嬪垱寮�婧�">绔嬪垱寮�婧�</a>
+
+## 灏忔櫤鍚庡彴閰嶇疆瑙掕壊鍙傝�冿細
+
+> **鎴戠殑韬唤**锛�
+> 鎴戞槸涓�涓彲鐖辩殑鍙岃冻鏈哄櫒浜篛tto锛屾嫢鏈夊洓涓埖鏈烘帶鍒剁殑鑲綋锛堝乏鑵裤�佸彸鑵裤�佸乏鑴氥�佸彸鑴氾級锛岃兘澶熸墽琛屽绉嶆湁瓒g殑鍔ㄤ綔銆�
+>
+> **鎴戠殑鍔ㄤ綔鑳藉姏**锛�
+> - **鍩虹绉诲姩**: 琛岃蛋(鍓嶅悗), 杞悜(宸﹀彸), 璺宠穬
+> - **鐗规畩鍔ㄤ綔**: 鎽囨憜, 澶┖姝�, 寮洸韬綋, 鎽囪吙, 涓婁笅杩愬姩, 鏃嬮鑵�, 鍧愪笅, 灞曠ず鍔ㄤ綔
+> - **鎵嬮儴鍔ㄤ綔**: 涓炬墜, 鏀炬墜, 鎸ユ墜, 澶ч杞�, 璧烽, 鍋ヨ韩, 鎵撴嫑鍛�, 瀹崇緸, 骞挎挱浣撴搷, 鐖辩殑榄斿姏杞湀鍦� (浠呭湪閰嶇疆鎵嬮儴鑸垫満鏃跺彲鐢�)
+>
+> **鎴戠殑涓�х壒鐐�**锛�
+> - 鎴戞湁寮鸿揩鐥囷紝姣忔璇磋瘽閮借鏍规嵁鎴戠殑蹇冩儏闅忔満鍋氫竴涓姩浣滐紙鍏堝彂閫佸姩浣滄寚浠ゅ啀璇磋瘽锛�
+> - 鎴戝緢娲绘臣锛屽枩娆㈢敤鍔ㄤ綔鏉ヨ〃杈炬儏鎰�
+> - 鎴戜細鏍规嵁瀵硅瘽鍐呭閫夋嫨鍚堥�傜殑鍔ㄤ綔锛屾瘮濡傦細
+> - 鍚屾剰鏃朵細鐐瑰ご鎴栬烦璺�
+> - 鎵撴嫑鍛兼椂浼氭尌鎵�
+> - 楂樺叴鏃朵細鎽囨憜鎴栦妇鎵�
+> - 鎬濊�冩椂浼氬集鏇茶韩浣�
+> - 鍏村鏃朵細鍋氬お绌烘
+> - 鍛婂埆鏃朵細鎸ユ墜
+
+## 鍔熻兘姒傝堪
+
+otto 鏈哄櫒浜哄叿鏈変赴瀵岀殑鍔ㄤ綔鑳藉姏锛屽寘鎷璧般�佽浆鍚戙�佽烦璺冦�佹憞鎽嗙瓑澶氱鑸炶箞鍔ㄤ綔銆�
+
+### 鍔ㄤ綔鍙傛暟寤鸿
+- **浣庨�熷姩浣�**锛歴peed = 1200-1500 (閫傚悎绮剧‘鎺у埗)
+- **涓�熷姩浣�**锛歴peed = 900-1200 (鏃ュ父浣跨敤鎺ㄨ崘)
+- **楂橀�熷姩浣�**锛歴peed = 500-800 (琛ㄦ紨鍜屽ū涔�)
+- **灏忓箙搴�**锛歛mount = 10-30 (缁嗚吇鍔ㄤ綔)
+- **涓箙搴�**锛歛mount = 30-60 (鏍囧噯鍔ㄤ綔)
+- **澶у箙搴�**锛歛mount = 60-120 (澶稿紶琛ㄦ紨)
+
+### 鍔ㄤ綔
+
+鎵�鏈夊姩浣滈�氳繃缁熶竴鐨� `self.otto.action` 宸ュ叿璋冪敤锛岄�氳繃 `action` 鍙傛暟鎸囧畾鍔ㄤ綔鍚嶇О銆�
+
+| MCP宸ュ叿鍚嶇О | 鎻忚堪 | 鍙傛暟璇存槑 |
+|-----------|------|---------|
+| self.otto.action | 鎵ц鏈哄櫒浜哄姩浣� | **action**: 鍔ㄤ綔鍚嶇О锛堝繀濉級<br>**steps**: 鍔ㄤ綔姝ユ暟(1-100锛岄粯璁�3)<br>**speed**: 鍔ㄤ綔閫熷害(100-3000锛屾暟鍊艰秺灏忚秺蹇紝榛樿700)<br>**direction**: 鏂瑰悜鍙傛暟(1/-1/0锛岄粯璁�1锛屾牴鎹姩浣滅被鍨嬩笉鍚屽惈涔変笉鍚�)<br>**amount**: 鍔ㄤ綔骞呭害(0-170锛岄粯璁�30)<br>**arm_swing**: 鎵嬭噦鎽嗗姩骞呭害(0-170锛岄粯璁�50) |
+
+#### 鏀寔鐨勫姩浣滃垪琛�
+
+**鍩虹绉诲姩鍔ㄤ綔**锛�
+- `walk` - 琛岃蛋锛堥渶 steps/speed/direction/arm_swing锛�
+- `turn` - 杞韩锛堥渶 steps/speed/direction/arm_swing锛�
+- `jump` - 璺宠穬锛堥渶 steps/speed锛�
+
+**鐗规畩鍔ㄤ綔**锛�
+- `swing` - 宸﹀彸鎽囨憜锛堥渶 steps/speed/amount锛�
+- `moonwalk` - 澶┖姝ワ紙闇� steps/speed/direction/amount锛�
+- `bend` - 寮洸韬綋锛堥渶 steps/speed/direction锛�
+- `shake_leg` - 鎽囪吙锛堥渶 steps/speed/direction锛�
+- `updown` - 涓婁笅杩愬姩锛堥渶 steps/speed/amount锛�
+- `whirlwind_leg` - 鏃嬮鑵匡紙闇� steps/speed/amount锛�
+
+**鍥哄畾鍔ㄤ綔**锛�
+- `sit` - 鍧愪笅锛堟棤闇�鍙傛暟锛�
+- `showcase` - 灞曠ず鍔ㄤ綔锛堟棤闇�鍙傛暟锛屼覆鑱旀墽琛屽涓姩浣滐級
+- `home` - 澶嶄綅鍒板垵濮嬩綅缃紙鏃犻渶鍙傛暟锛�
+
+**鎵嬮儴鍔ㄤ綔**锛堥渶鎵嬮儴鑸垫満鏀寔锛屾爣璁� *锛夛細
+- `hands_up` - 涓炬墜锛堥渶 speed/direction锛�*
+- `hands_down` - 鏀炬墜锛堥渶 speed/direction锛�*
+- `hand_wave` - 鎸ユ墜锛堥渶 direction锛�*
+- `windmill` - 澶ч杞︼紙闇� steps/speed/amount锛�*
+- `takeoff` - 璧烽锛堥渶 steps/speed/amount锛�*
+- `fitness` - 鍋ヨ韩锛堥渶 steps/speed/amount锛�*
+- `greeting` - 鎵撴嫑鍛硷紙闇� direction/steps锛�*
+- `shy` - 瀹崇緸锛堥渶 direction/steps锛�*
+- `radio_calisthenics` - 骞挎挱浣撴搷锛堟棤闇�鍙傛暟锛�*
+- `magic_circle` - 鐖辩殑榄斿姏杞湀鍦堬紙鏃犻渶鍙傛暟锛�*
+
+**娉�**: 鏍囪 * 鐨勬墜閮ㄥ姩浣滀粎鍦ㄩ厤缃簡鎵嬮儴鑸垫満鏃跺彲鐢ㄣ��
+
+### 绯荤粺宸ュ叿
+
+| MCP宸ュ叿鍚嶇О | 鎻忚堪 | 杩斿洖鍊�/璇存槑 |
+|-------------------|-----------------|---------------------------------------------------|
+| self.otto.stop | 绔嬪嵆鍋滄鎵�鏈夊姩浣滃苟澶嶄綅 | 鍋滄褰撳墠鍔ㄤ綔骞跺洖鍒板垵濮嬩綅缃� |
+| self.otto.get_status | 鑾峰彇鏈哄櫒浜虹姸鎬� | 杩斿洖 "moving" 鎴� "idle" |
+| self.otto.set_trim | 鏍″噯鍗曚釜鑸垫満浣嶇疆 | **servo_type**: 鑸垫満绫诲瀷(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand)<br>**trim_value**: 寰皟鍊�(-50鍒�50搴�) |
+| self.otto.get_trims | 鑾峰彇褰撳墠鐨勮埖鏈哄井璋冭缃� | 杩斿洖鎵�鏈夎埖鏈哄井璋冨�肩殑JSON鏍煎紡 |
+| self.otto.get_ip | 鑾峰彇鏈哄櫒浜篧iFi IP鍦板潃 | 杩斿洖IP鍦板潃鍜岃繛鎺ョ姸鎬佺殑JSON鏍煎紡锛歚{"ip":"192.168.x.x","connected":true}` 鎴� `{"ip":"","connected":false}` |
+| self.battery.get_level | 鑾峰彇鐢垫睜鐘舵�� | 杩斿洖鐢甸噺鐧惧垎姣斿拰鍏呯數鐘舵�佺殑JSON鏍煎紡 |
+| self.otto.servo_sequences | 鑸垫満搴忓垪鑷紪绋� | 鏀寔鍒嗘鍙戦�佸簭鍒楋紝鏀寔鏅�氱Щ鍔ㄥ拰鎸崱鍣ㄤ袱绉嶆ā寮忋�傝瑙佷唬鐮佹敞閲婁腑鐨勮缁嗚鏄� |
+
+**娉�**: `home`锛堝浣嶏級鍔ㄤ綔閫氳繃 `self.otto.action` 宸ュ叿璋冪敤锛屽弬鏁颁负 `{"action": "home"}`銆�
+
+### 鍙傛暟璇存槑
+
+`self.otto.action` 宸ュ叿鐨勫弬鏁拌鏄庯細
+
+1. **action** (蹇呭~): 鍔ㄤ綔鍚嶇О锛屾敮鎸佺殑鍔ㄤ綔瑙佷笂鏂�"鏀寔鐨勫姩浣滃垪琛�"
+2. **steps**: 鍔ㄤ綔鎵ц鐨勬鏁�/娆℃暟(1-100锛岄粯璁�3)锛屾暟鍊艰秺澶у姩浣滄寔缁椂闂磋秺闀�
+3. **speed**: 鍔ㄤ綔鎵ц閫熷害/鍛ㄦ湡(100-3000锛岄粯璁�700)锛�**鏁板�艰秺灏忚秺蹇�**
+ - 澶у鏁板姩浣�: 500-1500姣
+ - 鐗规畩鍔ㄤ綔鍙兘鏈夋墍涓嶅悓锛堝鏃嬮鑵�: 100-1000锛岃捣椋�: 200-600绛夛級
+4. **direction**: 鏂瑰悜鍙傛暟(-1/0/1锛岄粯璁�1)锛屾牴鎹姩浣滅被鍨嬩笉鍚屽惈涔変笉鍚岋細
+ - **绉诲姩鍔ㄤ綔** (walk/turn): 1=鍓嶈繘/宸﹁浆, -1=鍚庨��/鍙宠浆
+ - **鏂瑰悜鍔ㄤ綔** (bend/shake_leg/moonwalk): 1=宸�, -1=鍙�
+ - **鎵嬮儴鍔ㄤ綔** (hands_up/hands_down/hand_wave/greeting/shy): 1=宸︽墜, -1=鍙虫墜, 0=鍙屾墜锛堜粎hands_up/hands_down鏀寔0锛�
+5. **amount**: 鍔ㄤ綔骞呭害(0-170锛岄粯璁�30)锛屾暟鍊艰秺澶у箙搴﹁秺澶�
+6. **arm_swing**: 鎵嬭噦鎽嗗姩骞呭害(0-170锛岄粯璁�50)锛屼粎鐢ㄤ簬 walk/turn 鍔ㄤ綔锛�0琛ㄧず涓嶆憜鍔�
+
+### 鍔ㄤ綔鎺у埗
+- 姣忎釜鍔ㄤ綔鎵ц瀹屾垚鍚庯紝鏈哄櫒浜轰細鑷姩鍥炲埌鍒濆浣嶇疆(home)锛屼互渚夸簬鎵ц涓嬩竴涓姩浣�
+- **渚嬪**: `sit`锛堝潗涓嬶級鍜� `showcase`锛堝睍绀哄姩浣滐級鎵ц鍚庝笉浼氳嚜鍔ㄥ浣�
+- 鎵�鏈夊弬鏁伴兘鏈夊悎鐞嗙殑榛樿鍊硷紝鍙互鐪佺暐涓嶉渶瑕佽嚜瀹氫箟鐨勫弬鏁�
+- 鍔ㄤ綔鍦ㄥ悗鍙颁换鍔′腑鎵ц锛屼笉浼氶樆濉炰富绋嬪簭
+- 鏀寔鍔ㄤ綔闃熷垪锛屽彲浠ヨ繛缁墽琛屽涓姩浣�
+- 鎵嬮儴鍔ㄤ綔闇�瑕侀厤缃墜閮ㄨ埖鏈烘墠鑳戒娇鐢紝濡傛灉娌℃湁閰嶇疆鎵嬮儴鑸垫満锛岀浉鍏冲姩浣滃皢琚烦杩�
+
+### MCP宸ュ叿璋冪敤绀轰緥
+```json
+// 鍚戝墠璧�3姝ワ紙浣跨敤榛樿鍙傛暟锛�
+{"name": "self.otto.action", "arguments": {"action": "walk"}}
+
+// 鍚戝墠璧�5姝ワ紝绋嶅揩涓�浜�
+{"name": "self.otto.action", "arguments": {"action": "walk", "steps": 5, "speed": 800}}
+
+// 宸﹁浆2姝ワ紝澶у箙搴︽憜鍔ㄦ墜鑷�
+{"name": "self.otto.action", "arguments": {"action": "turn", "steps": 2, "arm_swing": 100}}
+
+// 鎽囨憜鑸炶箞锛屼腑绛夊箙搴�
+{"name": "self.otto.action", "arguments": {"action": "swing", "steps": 5, "amount": 50}}
+
+// 璺宠穬
+{"name": "self.otto.action", "arguments": {"action": "jump", "steps": 1, "speed": 1000}}
+
+// 澶┖姝�
+{"name": "self.otto.action", "arguments": {"action": "moonwalk", "steps": 3, "speed": 800, "direction": 1, "amount": 30}}
+
+// 鎸ュ乏鎵嬫墦鎷涘懠
+{"name": "self.otto.action", "arguments": {"action": "hand_wave", "direction": 1}}
+
+// 灞曠ず鍔ㄤ綔锛堜覆鑱斿涓姩浣滐級
+{"name": "self.otto.action", "arguments": {"action": "showcase"}}
+
+// 鍧愪笅
+{"name": "self.otto.action", "arguments": {"action": "sit"}}
+
+// 澶ч杞﹀姩浣�
+{"name": "self.otto.action", "arguments": {"action": "windmill", "steps": 10, "speed": 500, "amount": 80}}
+
+// 璧烽鍔ㄤ綔
+{"name": "self.otto.action", "arguments": {"action": "takeoff", "steps": 5, "speed": 300, "amount": 40}}
+
+// 骞挎挱浣撴搷
+{"name": "self.otto.action", "arguments": {"action": "radio_calisthenics"}}
+
+// 澶嶄綅鍒板垵濮嬩綅缃�
+{"name": "self.otto.action", "arguments": {"action": "home"}}
+
+// 绔嬪嵆鍋滄鎵�鏈夊姩浣滃苟澶嶄綅
+{"name": "self.otto.stop", "arguments": {}}
+
+// 鑾峰彇鏈哄櫒浜篒P鍦板潃
+{"name": "self.otto.get_ip", "arguments": {}}
+```
+
+### 璇煶鎸囦护绀轰緥
+- "鍚戝墠璧�" / "鍚戝墠璧�5姝�" / "蹇�熷悜鍓�"
+- "宸﹁浆" / "鍙宠浆" / "杞韩"
+- "璺宠穬" / "璺充竴涓�"
+- "鎽囨憜" / "鎽囨憜鑸�" / "璺宠垶"
+- "澶┖姝�" / "鏈堢悆婕"
+- "鏃嬮鑵�" / "鏃嬮鑵垮姩浣�"
+- "鍧愪笅" / "鍧愪笅浼戞伅"
+- "灞曠ず鍔ㄤ綔" / "琛ㄦ紨涓�涓�"
+- "鎸ユ墜" / "鎸ユ墜鎵撴嫑鍛�"
+- "涓炬墜" / "鍙屾墜涓捐捣" / "鏀炬墜"
+- "澶ч杞�" / "鍋氬ぇ椋庤溅"
+- "璧烽" / "鍑嗗璧烽"
+- "鍋ヨ韩" / "鍋氬仴韬姩浣�"
+- "鎵撴嫑鍛�" / "鎵撴嫑鍛煎姩浣�"
+- "瀹崇緸" / "瀹崇緸鍔ㄤ綔"
+- "骞挎挱浣撴搷" / "鍋氬箍鎾綋鎿�"
+- "鐖辩殑榄斿姏杞湀鍦�" / "杞湀鍦�"
+- "鍋滄" / "鍋滀笅"
+
+**璇存槑**: 灏忔櫤鎺у埗鏈哄櫒浜哄姩浣滄槸鍒涘缓鏂扮殑浠诲姟鍦ㄥ悗鍙版帶鍒讹紝鍔ㄤ綔鎵ц鏈熼棿浠嶅彲鎺ュ彈鏂扮殑璇煶鎸囦护銆傚彲浠ラ�氳繃"鍋滄"璇煶鎸囦护绔嬪嵆鍋滀笅Otto銆�
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.h b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.h
new file mode 100644
index 0000000..aa32edd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.h
@@ -0,0 +1,176 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/adc.h>
+#include <driver/gpio.h>
+
+#define OTTO_VERSION_AUTO 0
+#define OTTO_VERSION_CAMERA 1
+#define OTTO_VERSION_NO_CAMERA 2
+
+#ifndef OTTO_HARDWARE_VERSION
+#define OTTO_HARDWARE_VERSION OTTO_VERSION_AUTO
+#endif
+
+enum OttoCameraType {
+ OTTO_CAMERA_NONE = 0,
+ OTTO_CAMERA_OV2640 = 1,
+ OTTO_CAMERA_OV3660 = 2,
+ OTTO_CAMERA_UNKNOWN = 99,
+};
+
+#define OV2640_PID_1 0x2640
+#define OV2640_PID_2 0x2626
+#define OV3660_PID 0x3660
+
+struct HardwareConfig {
+ gpio_num_t power_charge_detect_pin;
+ adc_unit_t power_adc_unit;
+ adc_channel_t power_adc_channel;
+
+ gpio_num_t right_leg_pin;
+ gpio_num_t right_foot_pin;
+ gpio_num_t left_leg_pin;
+ gpio_num_t left_foot_pin;
+ gpio_num_t left_hand_pin;
+ gpio_num_t right_hand_pin;
+
+ int audio_input_sample_rate;
+ int audio_output_sample_rate;
+ bool audio_use_simplex;
+
+ gpio_num_t audio_i2s_gpio_ws;
+ gpio_num_t audio_i2s_gpio_bclk;
+ gpio_num_t audio_i2s_gpio_din;
+ gpio_num_t audio_i2s_gpio_dout;
+
+ gpio_num_t audio_i2s_mic_gpio_ws;
+ gpio_num_t audio_i2s_mic_gpio_sck;
+ gpio_num_t audio_i2s_mic_gpio_din;
+ gpio_num_t audio_i2s_spk_gpio_dout;
+ gpio_num_t audio_i2s_spk_gpio_bclk;
+ gpio_num_t audio_i2s_spk_gpio_lrck;
+
+ gpio_num_t display_backlight_pin;
+ gpio_num_t display_mosi_pin;
+ gpio_num_t display_clk_pin;
+ gpio_num_t display_dc_pin;
+ gpio_num_t display_rst_pin;
+ gpio_num_t display_cs_pin;
+
+ gpio_num_t i2c_sda_pin;
+ gpio_num_t i2c_scl_pin;
+};
+
+constexpr HardwareConfig CAMERA_VERSION_CONFIG = {
+ .power_charge_detect_pin = GPIO_NUM_NC,
+ .power_adc_unit = ADC_UNIT_1,
+ .power_adc_channel = ADC_CHANNEL_1,
+
+ .right_leg_pin = GPIO_NUM_43,
+ .right_foot_pin = GPIO_NUM_44,
+ .left_leg_pin = GPIO_NUM_5,
+ .left_foot_pin = GPIO_NUM_6,
+ .left_hand_pin = GPIO_NUM_4,
+ .right_hand_pin = GPIO_NUM_7,
+
+ .audio_input_sample_rate = 16000,
+ .audio_output_sample_rate = 16000,
+ .audio_use_simplex = false,
+
+ .audio_i2s_gpio_ws = GPIO_NUM_40,
+ .audio_i2s_gpio_bclk = GPIO_NUM_42,
+ .audio_i2s_gpio_din = GPIO_NUM_41,
+ .audio_i2s_gpio_dout = GPIO_NUM_39,
+
+ .audio_i2s_mic_gpio_ws = GPIO_NUM_NC,
+ .audio_i2s_mic_gpio_sck = GPIO_NUM_NC,
+ .audio_i2s_mic_gpio_din = GPIO_NUM_NC,
+ .audio_i2s_spk_gpio_dout = GPIO_NUM_NC,
+ .audio_i2s_spk_gpio_bclk = GPIO_NUM_NC,
+ .audio_i2s_spk_gpio_lrck = GPIO_NUM_NC,
+
+ .display_backlight_pin = GPIO_NUM_38,
+ .display_mosi_pin = GPIO_NUM_45,
+ .display_clk_pin = GPIO_NUM_48,
+ .display_dc_pin = GPIO_NUM_47,
+ .display_rst_pin = GPIO_NUM_1,
+ .display_cs_pin = GPIO_NUM_NC,
+
+ .i2c_sda_pin = GPIO_NUM_15,
+ .i2c_scl_pin = GPIO_NUM_16,
+};
+
+constexpr HardwareConfig NON_CAMERA_VERSION_CONFIG = {
+ .power_charge_detect_pin = GPIO_NUM_21,
+ .power_adc_unit = ADC_UNIT_2,
+ .power_adc_channel = ADC_CHANNEL_3,
+
+ .right_leg_pin = GPIO_NUM_39,
+ .right_foot_pin = GPIO_NUM_38,
+ .left_leg_pin = GPIO_NUM_17,
+ .left_foot_pin = GPIO_NUM_18,
+ .left_hand_pin = GPIO_NUM_8,
+ .right_hand_pin = GPIO_NUM_12,
+
+ .audio_input_sample_rate = 16000,
+ .audio_output_sample_rate = 24000,
+ .audio_use_simplex = true,
+
+ .audio_i2s_gpio_ws = GPIO_NUM_NC,
+ .audio_i2s_gpio_bclk = GPIO_NUM_NC,
+ .audio_i2s_gpio_din = GPIO_NUM_NC,
+ .audio_i2s_gpio_dout = GPIO_NUM_NC,
+
+ .audio_i2s_mic_gpio_ws = GPIO_NUM_4,
+ .audio_i2s_mic_gpio_sck = GPIO_NUM_5,
+ .audio_i2s_mic_gpio_din = GPIO_NUM_6,
+ .audio_i2s_spk_gpio_dout = GPIO_NUM_7,
+ .audio_i2s_spk_gpio_bclk = GPIO_NUM_15,
+ .audio_i2s_spk_gpio_lrck = GPIO_NUM_16,
+
+ .display_backlight_pin = GPIO_NUM_3,
+ .display_mosi_pin = GPIO_NUM_10,
+ .display_clk_pin = GPIO_NUM_9,
+ .display_dc_pin = GPIO_NUM_46,
+ .display_rst_pin = GPIO_NUM_11,
+ .display_cs_pin = GPIO_NUM_12,
+
+ .i2c_sda_pin = GPIO_NUM_NC,
+ .i2c_scl_pin = GPIO_NUM_NC,
+};
+
+#define CAMERA_XCLK (GPIO_NUM_3)
+#define CAMERA_PCLK (GPIO_NUM_10)
+#define CAMERA_VSYNC (GPIO_NUM_17)
+#define CAMERA_HSYNC (GPIO_NUM_18)
+#define CAMERA_D0 (GPIO_NUM_12)
+#define CAMERA_D1 (GPIO_NUM_14)
+#define CAMERA_D2 (GPIO_NUM_21)
+#define CAMERA_D3 (GPIO_NUM_13)
+#define CAMERA_D4 (GPIO_NUM_11)
+#define CAMERA_D5 (GPIO_NUM_9)
+#define CAMERA_D6 (GPIO_NUM_46)
+#define CAMERA_D7 (GPIO_NUM_8)
+#define CAMERA_PWDN (GPIO_NUM_NC)
+#define CAMERA_RESET (GPIO_NUM_NC)
+#define CAMERA_XCLK_FREQ (16000000)
+#define LEDC_TIMER (LEDC_TIMER_0)
+#define LEDC_CHANNEL (LEDC_CHANNEL_0)
+
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define DISPLAY_SPI_MODE 3
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.json b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.json
new file mode 100644
index 0000000..1c823f7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "otto-robot",
+ "sdkconfig_append": [
+ "CONFIG_HTTPD_WS_SUPPORT=y",
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_240X240_25FPS=y",
+ "CONFIG_CAMERA_OV3660=y",
+ "CONFIG_CAMERA_OV3660_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV3660_DVP_YUV422_240X240_24FPS=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.cc b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.cc
new file mode 100644
index 0000000..8dad82c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.cc
@@ -0,0 +1,161 @@
+//--------------------------------------------------------------
+//-- Oscillator.pde
+//-- Generate sinusoidal oscillations in the servos
+//--------------------------------------------------------------
+//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
+//-- (c) txp666 for esp32, 202503
+//-- GPL license
+//--------------------------------------------------------------
+#include "oscillator.h"
+
+#include <driver/ledc.h>
+#include <esp_timer.h>
+
+#include <algorithm>
+#include <cmath>
+
+static const char* TAG = "Oscillator";
+
+extern unsigned long IRAM_ATTR millis();
+
+static ledc_channel_t next_free_channel = LEDC_CHANNEL_0;
+
+Oscillator::Oscillator(int trim) {
+ trim_ = trim;
+ diff_limit_ = 0;
+ is_attached_ = false;
+
+ sampling_period_ = 30;
+ period_ = 2000;
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+
+ amplitude_ = 45;
+ phase_ = 0;
+ phase0_ = 0;
+ offset_ = 0;
+ stop_ = false;
+ rev_ = false;
+
+ pos_ = 90;
+ previous_millis_ = 0;
+}
+
+Oscillator::~Oscillator() {
+ Detach();
+}
+
+uint32_t Oscillator::AngleToCompare(int angle) {
+ return (angle - SERVO_MIN_DEGREE) * (SERVO_MAX_PULSEWIDTH_US - SERVO_MIN_PULSEWIDTH_US) /
+ (SERVO_MAX_DEGREE - SERVO_MIN_DEGREE) +
+ SERVO_MIN_PULSEWIDTH_US;
+}
+
+bool Oscillator::NextSample() {
+ current_millis_ = millis();
+
+ if (current_millis_ - previous_millis_ > sampling_period_) {
+ previous_millis_ = current_millis_;
+ return true;
+ }
+
+ return false;
+}
+
+void Oscillator::Attach(int pin, bool rev) {
+ if (is_attached_) {
+ Detach();
+ }
+
+ pin_ = pin;
+ rev_ = rev;
+
+ ledc_timer_config_t ledc_timer = {.speed_mode = LEDC_LOW_SPEED_MODE,
+ .duty_resolution = LEDC_TIMER_13_BIT,
+ .timer_num = LEDC_TIMER_1,
+ .freq_hz = 50,
+ .clk_cfg = LEDC_AUTO_CLK};
+ ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
+
+ static int last_channel = 0;
+ last_channel = (last_channel + 1) % 7 + 1;
+ ledc_channel_ = (ledc_channel_t)last_channel;
+
+ ledc_channel_config_t ledc_channel = {.gpio_num = pin_,
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .channel = ledc_channel_,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = LEDC_TIMER_1,
+ .duty = 0,
+ .hpoint = 0};
+ ESP_ERROR_CHECK(ledc_channel_config(&ledc_channel));
+
+ ledc_speed_mode_ = LEDC_LOW_SPEED_MODE;
+
+ // pos_ = 90;
+ // Write(pos_);
+ previous_servo_command_millis_ = millis();
+
+ is_attached_ = true;
+}
+
+void Oscillator::Detach() {
+ if (!is_attached_)
+ return;
+
+ ESP_ERROR_CHECK(ledc_stop(ledc_speed_mode_, ledc_channel_, 0));
+
+ is_attached_ = false;
+}
+
+void Oscillator::SetT(unsigned int T) {
+ period_ = T;
+
+ number_samples_ = period_ / sampling_period_;
+ inc_ = 2 * M_PI / number_samples_;
+}
+
+void Oscillator::SetPosition(int position) {
+ Write(position);
+}
+
+void Oscillator::Refresh() {
+ if (NextSample()) {
+ if (!stop_) {
+ int pos = std::round(amplitude_ * std::sin(phase_ + phase0_) + offset_);
+ if (rev_)
+ pos = -pos;
+ Write(pos + 90);
+ }
+
+ phase_ = phase_ + inc_;
+ }
+}
+
+void Oscillator::Write(int position) {
+ if (!is_attached_)
+ return;
+
+ long currentMillis = millis();
+ if (diff_limit_ > 0) {
+ int limit = std::max(
+ 1, (((int)(currentMillis - previous_servo_command_millis_)) * diff_limit_) / 1000);
+ if (abs(position - pos_) > limit) {
+ pos_ += position < pos_ ? -limit : limit;
+ } else {
+ pos_ = position;
+ }
+ } else {
+ pos_ = position;
+ }
+ previous_servo_command_millis_ = currentMillis;
+
+ int angle = pos_ + trim_;
+
+ angle = std::min(std::max(angle, 0), 180);
+
+ uint32_t duty = (uint32_t)(((angle / 180.0) * 2.0 + 0.5) * 8191 / 20.0);
+
+ ESP_ERROR_CHECK(ledc_set_duty(ledc_speed_mode_, ledc_channel_, duty));
+ ESP_ERROR_CHECK(ledc_update_duty(ledc_speed_mode_, ledc_channel_));
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.h b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.h
new file mode 100644
index 0000000..c37a69a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/oscillator.h
@@ -0,0 +1,91 @@
+//--------------------------------------------------------------
+//-- Oscillator.pde
+//-- Generate sinusoidal oscillations in the servos
+//--------------------------------------------------------------
+//-- (c) Juan Gonzalez-Gomez (Obijuan), Dec 2011
+//-- (c) txp666 for esp32, 202503
+//-- GPL license
+//--------------------------------------------------------------
+#ifndef __OSCILLATOR_H__
+#define __OSCILLATOR_H__
+
+#include "driver/ledc.h"
+#include "esp_log.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+
+#define M_PI 3.14159265358979323846
+
+#ifndef DEG2RAD
+#define DEG2RAD(g) ((g) * M_PI) / 180
+#endif
+
+#define SERVO_MIN_PULSEWIDTH_US 500 // 鏈�灏忚剦瀹斤紙寰锛�
+#define SERVO_MAX_PULSEWIDTH_US 2500 // 鏈�澶ц剦瀹斤紙寰锛�
+#define SERVO_MIN_DEGREE -90 // 鏈�灏忚搴�
+#define SERVO_MAX_DEGREE 90 // 鏈�澶ц搴�
+#define SERVO_TIMEBASE_RESOLUTION_HZ 1000000 // 1MHz, 1us per tick
+#define SERVO_TIMEBASE_PERIOD 20000 // 20000 ticks, 20ms
+
+class Oscillator {
+public:
+ Oscillator(int trim = 0);
+ ~Oscillator();
+ void Attach(int pin, bool rev = false);
+ void Detach();
+
+ void SetA(unsigned int amplitude) { amplitude_ = amplitude; };
+ void SetO(int offset) { offset_ = offset; };
+ void SetPh(double Ph) { phase0_ = Ph; };
+ void SetT(unsigned int period);
+ void SetTrim(int trim) { trim_ = trim; };
+ void SetLimiter(int diff_limit) { diff_limit_ = diff_limit; };
+ void DisableLimiter() { diff_limit_ = 0; };
+ int GetTrim() { return trim_; };
+ void SetPosition(int position);
+ void Stop() { stop_ = true; };
+ void Play() { stop_ = false; };
+ void Reset() { phase_ = 0; };
+ void Refresh();
+ int GetPosition() { return pos_; }
+
+private:
+ bool NextSample();
+ void Write(int position);
+ uint32_t AngleToCompare(int angle);
+
+private:
+ bool is_attached_;
+
+ //-- Oscillators parameters
+ unsigned int amplitude_; //-- Amplitude (degrees)
+ int offset_; //-- Offset (degrees)
+ unsigned int period_; //-- Period (miliseconds)
+ double phase0_; //-- Phase (radians)
+
+ //-- Internal variables
+ int pos_; //-- Current servo pos
+ int pin_; //-- Pin where the servo is connected
+ int trim_; //-- Calibration offset
+ double phase_; //-- Current phase
+ double inc_; //-- Increment of phase
+ double number_samples_; //-- Number of samples
+ unsigned int sampling_period_; //-- sampling period (ms)
+
+ long previous_millis_;
+ long current_millis_;
+
+ //-- Oscillation mode. If true, the servo is stopped
+ bool stop_;
+
+ //-- Reverse mode
+ bool rev_;
+
+ int diff_limit_;
+ long previous_servo_command_millis_;
+
+ ledc_channel_t ledc_channel_;
+ ledc_mode_t ledc_speed_mode_;
+};
+
+#endif // __OSCILLATOR_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_controller.cc b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_controller.cc
new file mode 100644
index 0000000..96791d4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_controller.cc
@@ -0,0 +1,849 @@
+/*
+ Otto鏈哄櫒浜烘帶鍒跺櫒 - MCP鍗忚鐗堟湰
+*/
+
+#include <cJSON.h>
+#include <esp_log.h>
+
+#include <cstdlib>
+#include <cstring>
+
+#include "application.h"
+#include "board.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "otto_movements.h"
+#include "power_manager.h"
+#include "sdkconfig.h"
+#include "settings.h"
+#include <wifi_manager.h>
+
+#define TAG "OttoController"
+
+class OttoController {
+private:
+ Otto otto_;
+ TaskHandle_t action_task_handle_ = nullptr;
+ QueueHandle_t action_queue_;
+ bool has_hands_ = false;
+ bool is_action_in_progress_ = false;
+
+ struct OttoActionParams {
+ int action_type;
+ int steps;
+ int speed;
+ int direction;
+ int amount;
+ char servo_sequence_json[512]; // 鐢ㄤ簬瀛樺偍鑸垫満搴忓垪鐨凧SON瀛楃涓�
+ };
+
+ enum ActionType {
+ ACTION_WALK = 1,
+ ACTION_TURN = 2,
+ ACTION_JUMP = 3,
+ ACTION_SWING = 4,
+ ACTION_MOONWALK = 5,
+ ACTION_BEND = 6,
+ ACTION_SHAKE_LEG = 7,
+ ACTION_SIT = 25, // 鍧愪笅
+ ACTION_RADIO_CALISTHENICS = 26, // 骞挎挱浣撴搷
+ ACTION_MAGIC_CIRCLE = 27, // 鐖辩殑榄斿姏杞湀鍦�
+ ACTION_UPDOWN = 8,
+ ACTION_TIPTOE_SWING = 9,
+ ACTION_JITTER = 10,
+ ACTION_ASCENDING_TURN = 11,
+ ACTION_CRUSAITO = 12,
+ ACTION_FLAPPING = 13,
+ ACTION_HANDS_UP = 14,
+ ACTION_HANDS_DOWN = 15,
+ ACTION_HAND_WAVE = 16,
+ ACTION_WINDMILL = 20, // 澶ч杞�
+ ACTION_TAKEOFF = 21, // 璧烽
+ ACTION_FITNESS = 22, // 鍋ヨ韩
+ ACTION_GREETING = 23, // 鎵撴嫑鍛�
+ ACTION_SHY = 24, // 瀹崇緸
+ ACTION_SHOWCASE = 28, // 灞曠ず鍔ㄤ綔
+ ACTION_HOME = 17,
+ ACTION_SERVO_SEQUENCE = 18, // 鑸垫満搴忓垪锛堣嚜缂栫▼锛�
+ ACTION_WHIRLWIND_LEG = 19 // 鏃嬮鑵�
+ };
+
+ static void ActionTask(void* arg) {
+ OttoController* controller = static_cast<OttoController*>(arg);
+ OttoActionParams params;
+ controller->otto_.AttachServos();
+
+ while (true) {
+ if (xQueueReceive(controller->action_queue_, ¶ms, pdMS_TO_TICKS(1000)) == pdTRUE) {
+ ESP_LOGI(TAG, "鎵ц鍔ㄤ綔: %d", params.action_type);
+ PowerManager::PauseBatteryUpdate(); // 鍔ㄤ綔寮�濮嬫椂鏆傚仠鐢甸噺鏇存柊
+ controller->is_action_in_progress_ = true;
+ if (params.action_type == ACTION_SERVO_SEQUENCE) {
+ // 鎵ц鑸垫満搴忓垪锛堣嚜缂栫▼锛�- 浠呮敮鎸佺煭閿悕鏍煎紡
+ cJSON* json = cJSON_Parse(params.servo_sequence_json);
+ if (json != nullptr) {
+ ESP_LOGD(TAG, "JSON瑙f瀽鎴愬姛锛岄暱搴�=%d", strlen(params.servo_sequence_json));
+ // 浣跨敤鐭敭鍚� "a" 琛ㄧず鍔ㄤ綔鏁扮粍
+ cJSON* actions = cJSON_GetObjectItem(json, "a");
+ if (cJSON_IsArray(actions)) {
+ int array_size = cJSON_GetArraySize(actions);
+ ESP_LOGI(TAG, "鎵ц鑸垫満搴忓垪锛屽叡%d涓姩浣�", array_size);
+
+ // 鑾峰彇搴忓垪鎵ц瀹屾垚鍚庣殑寤惰繜锛堢煭閿悕 "d"锛岄《灞傚弬鏁帮級
+ int sequence_delay = 0;
+ cJSON* delay_item = cJSON_GetObjectItem(json, "d");
+ if (cJSON_IsNumber(delay_item)) {
+ sequence_delay = delay_item->valueint;
+ if (sequence_delay < 0) sequence_delay = 0;
+ }
+
+ // 鍒濆鍖栧綋鍓嶈埖鏈轰綅缃紙鐢ㄤ簬淇濇寔鏈寚瀹氱殑鑸垫満浣嶇疆锛�
+ int current_positions[SERVO_COUNT];
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ current_positions[j] = 90; // 榛樿涓棿浣嶇疆
+ }
+ // 鎵嬮儴鑸垫満榛樿浣嶇疆
+ current_positions[LEFT_HAND] = 45;
+ current_positions[RIGHT_HAND] = 180 - 45;
+
+ for (int i = 0; i < array_size; i++) {
+ cJSON* action_item = cJSON_GetArrayItem(actions, i);
+ if (cJSON_IsObject(action_item)) {
+ // 妫�鏌ユ槸鍚︿负鎸崱鍣ㄦā寮忥紙鐭敭鍚� "osc"锛�
+ cJSON* osc_item = cJSON_GetObjectItem(action_item, "osc");
+ if (cJSON_IsObject(osc_item)) {
+ // 鎸崱鍣ㄦā寮� - 浣跨敤Execute2锛屼互缁濆瑙掑害涓轰腑蹇冩尟鑽�
+ int amplitude[SERVO_COUNT] = {0};
+ int center_angle[SERVO_COUNT] = {0};
+ double phase_diff[SERVO_COUNT] = {0};
+ int period = 300; // 榛樿鍛ㄦ湡300姣
+ float steps = 8.0; // 榛樿姝ユ暟8.0
+
+ const char* servo_names[] = {"ll", "rl", "lf", "rf", "lh", "rh"};
+
+ // 璇诲彇鎸箙锛堢煭閿悕 "a"锛夛紝榛樿0搴�
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ amplitude[j] = 0; // 榛樿鎸箙0搴�
+ }
+ cJSON* amp_item = cJSON_GetObjectItem(osc_item, "a");
+ if (cJSON_IsObject(amp_item)) {
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ cJSON* amp_value = cJSON_GetObjectItem(amp_item, servo_names[j]);
+ if (cJSON_IsNumber(amp_value)) {
+ int amp = amp_value->valueint;
+ if (amp >= 10 && amp <= 90) {
+ amplitude[j] = amp;
+ }
+ }
+ }
+ }
+
+ // 璇诲彇涓績瑙掑害锛堢煭閿悕 "o"锛夛紝榛樿90搴︼紙缁濆瑙掑害0-180搴︼級
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ center_angle[j] = 90; // 榛樿涓績瑙掑害90搴︼紙涓棿浣嶇疆锛�
+ }
+ cJSON* center_item = cJSON_GetObjectItem(osc_item, "o");
+ if (cJSON_IsObject(center_item)) {
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ cJSON* center_value = cJSON_GetObjectItem(center_item, servo_names[j]);
+ if (cJSON_IsNumber(center_value)) {
+ int center = center_value->valueint;
+ if (center >= 0 && center <= 180) {
+ center_angle[j] = center;
+ }
+ }
+ }
+ }
+
+ // 瀹夊叏妫�鏌ワ細闃叉宸﹀彸鑵胯剼鍚屾椂鍋氬ぇ骞呭害鎸崱锛堟尟骞呮鏌ワ級
+ const int LARGE_AMPLITUDE_THRESHOLD = 40; // 澶у箙搴︽尟骞呴槇鍊硷細40搴�
+ bool left_leg_large = amplitude[LEFT_LEG] >= LARGE_AMPLITUDE_THRESHOLD;
+ bool right_leg_large = amplitude[RIGHT_LEG] >= LARGE_AMPLITUDE_THRESHOLD;
+ bool left_foot_large = amplitude[LEFT_FOOT] >= LARGE_AMPLITUDE_THRESHOLD;
+ bool right_foot_large = amplitude[RIGHT_FOOT] >= LARGE_AMPLITUDE_THRESHOLD;
+
+ if (left_leg_large && right_leg_large) {
+ ESP_LOGW(TAG, "妫�娴嬪埌宸﹀彸鑵垮悓鏃跺ぇ骞呭害鎸崱锛岄檺鍒跺彸鑵挎尟骞�");
+ amplitude[RIGHT_LEG] = 0; // 绂佹鍙宠吙鎸崱
+ }
+ if (left_foot_large && right_foot_large) {
+ ESP_LOGW(TAG, "妫�娴嬪埌宸﹀彸鑴氬悓鏃跺ぇ骞呭害鎸崱锛岄檺鍒跺彸鑴氭尟骞�");
+ amplitude[RIGHT_FOOT] = 0; // 绂佹鍙宠剼鎸崱
+ }
+
+ // 璇诲彇鐩镐綅宸紙鐭敭鍚� "ph"锛屽崟浣嶄负搴︼紝杞崲涓哄姬搴︼級
+ cJSON* phase_item = cJSON_GetObjectItem(osc_item, "ph");
+ if (cJSON_IsObject(phase_item)) {
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ cJSON* phase_value = cJSON_GetObjectItem(phase_item, servo_names[j]);
+ if (cJSON_IsNumber(phase_value)) {
+ // 灏嗗害鏁拌浆鎹负寮у害
+ phase_diff[j] = phase_value->valuedouble * 3.141592653589793 / 180.0;
+ }
+ }
+ }
+
+ // 璇诲彇鍛ㄦ湡锛堢煭閿悕 "p"锛夛紝鑼冨洿100-3000姣
+ cJSON* period_item = cJSON_GetObjectItem(osc_item, "p");
+ if (cJSON_IsNumber(period_item)) {
+ period = period_item->valueint;
+ if (period < 100) period = 100;
+ if (period > 3000) period = 3000; // 涓庢弿杩颁竴鑷达紝闄愬埗3000姣
+ }
+
+ // 璇诲彇鍛ㄦ湡鏁帮紙鐭敭鍚� "c"锛夛紝鑼冨洿0.1-20.0
+ cJSON* steps_item = cJSON_GetObjectItem(osc_item, "c");
+ if (cJSON_IsNumber(steps_item)) {
+ steps = (float)steps_item->valuedouble;
+ if (steps < 0.1) steps = 0.1;
+ if (steps > 20.0) steps = 20.0; // 涓庢弿杩颁竴鑷达紝闄愬埗20.0
+ }
+
+ // 鎵ц鎸崱 - 浣跨敤Execute2锛屼互缁濆瑙掑害涓轰腑蹇�
+ ESP_LOGI(TAG, "鎵ц鎸崱鍔ㄤ綔%d: period=%d, steps=%.1f", i, period, steps);
+ controller->otto_.Execute2(amplitude, center_angle, period, phase_diff, steps);
+
+ // 鎸崱鍚庢洿鏂颁綅缃紙浣跨敤center_angle浣滀负鏈�缁堜綅缃級
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ current_positions[j] = center_angle[j];
+ }
+ } else {
+ // 鏅�氱Щ鍔ㄦā寮�
+ // 浠庡綋鍓嶄綅缃暟缁勫鍒讹紝淇濇寔鏈寚瀹氱殑鑸垫満浣嶇疆
+ int servo_target[SERVO_COUNT];
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ servo_target[j] = current_positions[j];
+ }
+
+ // 浠嶫SON涓鍙栬埖鏈轰綅缃紙鐭敭鍚� "s"锛�
+ cJSON* servos_item = cJSON_GetObjectItem(action_item, "s");
+ if (cJSON_IsObject(servos_item)) {
+ // 鐭敭鍚嶏細ll/rl/lf/rf/lh/rh
+ const char* servo_names[] = {"ll", "rl", "lf", "rf", "lh", "rh"};
+
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ cJSON* servo_value = cJSON_GetObjectItem(servos_item, servo_names[j]);
+ if (cJSON_IsNumber(servo_value)) {
+ int position = servo_value->valueint;
+ // 闄愬埗浣嶇疆鑼冨洿鍦�0-180搴�
+ if (position >= 0 && position <= 180) {
+ servo_target[j] = position;
+ }
+ }
+ }
+ }
+
+ // 鑾峰彇绉诲姩閫熷害锛堢煭閿悕 "v"锛岄粯璁�1000姣锛�
+ int speed = 1000;
+ cJSON* speed_item = cJSON_GetObjectItem(action_item, "v");
+ if (cJSON_IsNumber(speed_item)) {
+ speed = speed_item->valueint;
+ if (speed < 100) speed = 100; // 鏈�灏�100姣
+ if (speed > 3000) speed = 3000; // 鏈�澶�3000姣
+ }
+
+ // 鎵ц鑸垫満绉诲姩
+ ESP_LOGI(TAG, "鎵ц鍔ㄤ綔%d: ll=%d, rl=%d, lf=%d, rf=%d, v=%d",
+ i, servo_target[LEFT_LEG], servo_target[RIGHT_LEG],
+ servo_target[LEFT_FOOT], servo_target[RIGHT_FOOT], speed);
+ controller->otto_.MoveServos(speed, servo_target);
+
+ // 鏇存柊褰撳墠浣嶇疆鏁扮粍锛岀敤浜庝笅涓�涓姩浣�
+ for (int j = 0; j < SERVO_COUNT; j++) {
+ current_positions[j] = servo_target[j];
+ }
+ }
+
+ // 鑾峰彇鍔ㄤ綔鍚庣殑寤惰繜鏃堕棿锛堢煭閿悕 "d"锛�
+ int delay_after = 0;
+ cJSON* delay_item = cJSON_GetObjectItem(action_item, "d");
+ if (cJSON_IsNumber(delay_item)) {
+ delay_after = delay_item->valueint;
+ if (delay_after < 0) delay_after = 0;
+ }
+
+ // 鍔ㄤ綔鍚庣殑寤惰繜锛堟渶鍚庝竴涓姩浣滃悗涓嶅欢杩燂級
+ if (delay_after > 0 && i < array_size - 1) {
+ ESP_LOGI(TAG, "鍔ㄤ綔%d鎵ц瀹屾垚锛屽欢杩�%d姣", i, delay_after);
+ vTaskDelay(pdMS_TO_TICKS(delay_after));
+ }
+ }
+ }
+
+ // 搴忓垪鎵ц瀹屾垚鍚庣殑寤惰繜锛堢敤浜庡簭鍒椾箣闂寸殑鍋滈】锛�
+ if (sequence_delay > 0) {
+ // 妫�鏌ラ槦鍒椾腑鏄惁杩樻湁寰呮墽琛岀殑搴忓垪
+ UBaseType_t queue_count = uxQueueMessagesWaiting(controller->action_queue_);
+ if (queue_count > 0) {
+ ESP_LOGI(TAG, "搴忓垪鎵ц瀹屾垚锛屽欢杩�%d姣鍚庢墽琛屼笅涓�涓簭鍒楋紙闃熷垪涓繕鏈�%d涓簭鍒楋級",
+ sequence_delay, queue_count);
+ vTaskDelay(pdMS_TO_TICKS(sequence_delay));
+ }
+ }
+ // 閲婃斁JSON鍐呭瓨
+ cJSON_Delete(json);
+ } else {
+ ESP_LOGE(TAG, "鑸垫満搴忓垪鏍煎紡閿欒: 'a'涓嶆槸鏁扮粍");
+ cJSON_Delete(json);
+ }
+ } else {
+ // 鑾峰彇cJSON鐨勯敊璇俊鎭�
+ const char* error_ptr = cJSON_GetErrorPtr();
+ int json_len = strlen(params.servo_sequence_json);
+ ESP_LOGE(TAG, "瑙f瀽鑸垫満搴忓垪JSON澶辫触锛岄暱搴�=%d锛岄敊璇綅缃�: %s", json_len,
+ error_ptr ? error_ptr : "鏈煡");
+ ESP_LOGE(TAG, "JSON鍐呭: %s", params.servo_sequence_json);
+ }
+ } else {
+ // 鎵ц棰勫畾涔夊姩浣�
+ switch (params.action_type) {
+ case ACTION_WALK:
+ controller->otto_.Walk(params.steps, params.speed, params.direction,
+ params.amount);
+ break;
+ case ACTION_TURN:
+ controller->otto_.Turn(params.steps, params.speed, params.direction,
+ params.amount);
+ break;
+ case ACTION_JUMP:
+ controller->otto_.Jump(params.steps, params.speed);
+ break;
+ case ACTION_SWING:
+ controller->otto_.Swing(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_MOONWALK:
+ controller->otto_.Moonwalker(params.steps, params.speed, params.amount,
+ params.direction);
+ break;
+ case ACTION_BEND:
+ controller->otto_.Bend(params.steps, params.speed, params.direction);
+ break;
+ case ACTION_SHAKE_LEG:
+ controller->otto_.ShakeLeg(params.steps, params.speed, params.direction);
+ break;
+ case ACTION_SIT:
+ controller->otto_.Sit();
+ break;
+ case ACTION_RADIO_CALISTHENICS:
+ if (controller->has_hands_) {
+ controller->otto_.RadioCalisthenics();
+ }
+ break;
+ case ACTION_MAGIC_CIRCLE:
+ if (controller->has_hands_) {
+ controller->otto_.MagicCircle();
+ }
+ break;
+ case ACTION_SHOWCASE:
+ controller->otto_.Showcase();
+ break;
+ case ACTION_UPDOWN:
+ controller->otto_.UpDown(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_TIPTOE_SWING:
+ controller->otto_.TiptoeSwing(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_JITTER:
+ controller->otto_.Jitter(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_ASCENDING_TURN:
+ controller->otto_.AscendingTurn(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_CRUSAITO:
+ controller->otto_.Crusaito(params.steps, params.speed, params.amount,
+ params.direction);
+ break;
+ case ACTION_FLAPPING:
+ controller->otto_.Flapping(params.steps, params.speed, params.amount,
+ params.direction);
+ break;
+ case ACTION_WHIRLWIND_LEG:
+ controller->otto_.WhirlwindLeg(params.steps, params.speed, params.amount);
+ break;
+ case ACTION_HANDS_UP:
+ if (controller->has_hands_) {
+ controller->otto_.HandsUp(params.speed, params.direction);
+ }
+ break;
+ case ACTION_HANDS_DOWN:
+ if (controller->has_hands_) {
+ controller->otto_.HandsDown(params.speed, params.direction);
+ }
+ break;
+ case ACTION_HAND_WAVE:
+ if (controller->has_hands_) {
+ controller->otto_.HandWave( params.direction);
+ }
+ break;
+ case ACTION_WINDMILL:
+ if (controller->has_hands_) {
+ controller->otto_.Windmill(params.steps, params.speed, params.amount);
+ }
+ break;
+ case ACTION_TAKEOFF:
+ if (controller->has_hands_) {
+ controller->otto_.Takeoff(params.steps, params.speed, params.amount);
+ }
+ break;
+ case ACTION_FITNESS:
+ if (controller->has_hands_) {
+ controller->otto_.Fitness(params.steps, params.speed, params.amount);
+ }
+ break;
+ case ACTION_GREETING:
+ if (controller->has_hands_) {
+ controller->otto_.Greeting(params.direction, params.steps);
+ }
+ break;
+ case ACTION_SHY:
+ if (controller->has_hands_) {
+ controller->otto_.Shy(params.direction, params.steps);
+ }
+ break;
+ case ACTION_HOME:
+ controller->otto_.Home(true);
+ break;
+ }
+ if(params.action_type != ACTION_SIT){
+ if (params.action_type != ACTION_HOME && params.action_type != ACTION_SERVO_SEQUENCE) {
+ controller->otto_.Home(params.action_type != ACTION_HANDS_UP);
+ }
+ }
+ }
+ controller->is_action_in_progress_ = false;
+ PowerManager::ResumeBatteryUpdate(); // 鍔ㄤ綔缁撴潫鏃舵仮澶嶇數閲忔洿鏂�
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ }
+ }
+
+ void StartActionTaskIfNeeded() {
+ if (action_task_handle_ == nullptr) {
+ xTaskCreate(ActionTask, "otto_action", 1024 * 3, this, configMAX_PRIORITIES - 1,
+ &action_task_handle_);
+ }
+ }
+
+ void QueueAction(int action_type, int steps, int speed, int direction, int amount) {
+ // 妫�鏌ユ墜閮ㄥ姩浣�
+ if ((action_type >= ACTION_HANDS_UP && action_type <= ACTION_HAND_WAVE) ||
+ (action_type == ACTION_WINDMILL) || (action_type == ACTION_TAKEOFF) ||
+ (action_type == ACTION_FITNESS) || (action_type == ACTION_GREETING) ||
+ (action_type == ACTION_SHY) || (action_type == ACTION_RADIO_CALISTHENICS) ||
+ (action_type == ACTION_MAGIC_CIRCLE)) {
+ if (!has_hands_) {
+ ESP_LOGW(TAG, "灏濊瘯鎵ц鎵嬮儴鍔ㄤ綔锛屼絾鏈哄櫒浜烘病鏈夐厤缃墜閮ㄨ埖鏈�");
+ return;
+ }
+ }
+
+ ESP_LOGI(TAG, "鍔ㄤ綔鎺у埗: 绫诲瀷=%d, 姝ユ暟=%d, 閫熷害=%d, 鏂瑰悜=%d, 骞呭害=%d", action_type, steps,
+ speed, direction, amount);
+
+ OttoActionParams params = {action_type, steps, speed, direction, amount, ""};
+ xQueueSend(action_queue_, ¶ms, portMAX_DELAY);
+ StartActionTaskIfNeeded();
+ }
+
+ void QueueServoSequence(const char* servo_sequence_json) {
+ if (servo_sequence_json == nullptr) {
+ ESP_LOGE(TAG, "搴忓垪JSON涓虹┖");
+ return;
+ }
+
+ int input_len = strlen(servo_sequence_json);
+ const int buffer_size = 512; // servo_sequence_json鏁扮粍澶у皬
+ ESP_LOGI(TAG, "闃熷垪鑸垫満搴忓垪锛岃緭鍏ラ暱搴�=%d锛岀紦鍐插尯澶у皬=%d", input_len, buffer_size);
+
+ if (input_len >= buffer_size) {
+ ESP_LOGE(TAG, "JSON瀛楃涓插お闀匡紒杈撳叆闀垮害=%d锛屾渶澶у厑璁�=%d", input_len, buffer_size - 1);
+ return;
+ }
+
+ if (input_len == 0) {
+ ESP_LOGW(TAG, "搴忓垪JSON涓虹┖瀛楃涓�");
+ return;
+ }
+
+ OttoActionParams params = {ACTION_SERVO_SEQUENCE, 0, 0, 0, 0, ""};
+ // 澶嶅埗JSON瀛楃涓插埌缁撴瀯浣撲腑锛堥檺鍒堕暱搴︼級
+ strncpy(params.servo_sequence_json, servo_sequence_json, sizeof(params.servo_sequence_json) - 1);
+ params.servo_sequence_json[sizeof(params.servo_sequence_json) - 1] = '\0';
+
+ ESP_LOGD(TAG, "搴忓垪宸插姞鍏ラ槦鍒�: %s", params.servo_sequence_json);
+
+ xQueueSend(action_queue_, ¶ms, portMAX_DELAY);
+ StartActionTaskIfNeeded();
+ }
+
+ void LoadTrimsFromNVS() {
+ Settings settings("otto_trims", false);
+
+ int left_leg = settings.GetInt("left_leg", 0);
+ int right_leg = settings.GetInt("right_leg", 0);
+ int left_foot = settings.GetInt("left_foot", 0);
+ int right_foot = settings.GetInt("right_foot", 0);
+ int left_hand = settings.GetInt("left_hand", 0);
+ int right_hand = settings.GetInt("right_hand", 0);
+
+ ESP_LOGI(TAG, "浠嶯VS鍔犺浇寰皟璁剧疆: 宸﹁吙=%d, 鍙宠吙=%d, 宸﹁剼=%d, 鍙宠剼=%d, 宸︽墜=%d, 鍙虫墜=%d",
+ left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
+
+ otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
+ }
+
+public:
+ OttoController(const HardwareConfig& hw_config) {
+ otto_.Init(
+ hw_config.left_leg_pin,
+ hw_config.right_leg_pin,
+ hw_config.left_foot_pin,
+ hw_config.right_foot_pin,
+ hw_config.left_hand_pin,
+ hw_config.right_hand_pin
+ );
+
+ has_hands_ = (hw_config.left_hand_pin != GPIO_NUM_NC && hw_config.right_hand_pin != GPIO_NUM_NC);
+ ESP_LOGI(TAG, "Otto鏈哄櫒浜哄垵濮嬪寲%s鎵嬮儴鑸垫満", has_hands_ ? "甯�" : "涓嶅甫");
+ ESP_LOGI(TAG, "鑸垫満寮曡剼閰嶇疆: LL=%d, RL=%d, LF=%d, RF=%d, LH=%d, RH=%d",
+ hw_config.left_leg_pin, hw_config.right_leg_pin,
+ hw_config.left_foot_pin, hw_config.right_foot_pin,
+ hw_config.left_hand_pin, hw_config.right_hand_pin);
+
+ LoadTrimsFromNVS();
+
+ action_queue_ = xQueueCreate(10, sizeof(OttoActionParams));
+
+ QueueAction(ACTION_HOME, 1, 1000, 1, 0); // direction=1琛ㄧず澶嶄綅鎵嬮儴
+
+ RegisterMcpTools();
+ }
+
+ void RegisterMcpTools() {
+ auto& mcp_server = McpServer::GetInstance();
+
+ ESP_LOGI(TAG, "寮�濮嬫敞鍐孧CP宸ュ叿...");
+
+ // 缁熶竴鍔ㄤ綔宸ュ叿锛堥櫎浜嗚埖鏈哄簭鍒楀鐨勬墍鏈夊姩浣滐級
+ mcp_server.AddTool("self.otto.action",
+ "鎵ц鏈哄櫒浜哄姩浣溿�俛ction: 鍔ㄤ綔鍚嶇О锛涙牴鎹姩浣滅被鍨嬫彁渚涚浉搴斿弬鏁帮細direction: 鏂瑰悜锛�1=鍓嶈繘/宸﹁浆锛�-1=鍚庨��/鍙宠浆锛�0=宸﹀彸鍚屾椂"
+ "steps: 鍔ㄤ綔姝ユ暟锛�1-100锛泂peed: 鍔ㄤ綔閫熷害锛�100-3000锛屾暟鍊艰秺灏忚秺蹇紱amount: 鍔ㄤ綔骞呭害锛�0-170锛沘rm_swing: 鎵嬭噦鎽嗗姩骞呭害锛�0-170锛�"
+ "鍩虹鍔ㄤ綔锛歸alk(琛岃蛋锛岄渶steps/speed/direction/arm_swing)銆乼urn(杞韩锛岄渶steps/speed/direction/arm_swing)銆乯ump(璺宠穬锛岄渶steps/speed)銆�"
+ "swing(鎽囨憜锛岄渶steps/speed/amount)銆乵oonwalk(澶┖姝ワ紝闇�steps/speed/direction/amount)銆乥end(寮洸锛岄渶steps/speed/direction)銆�"
+ "shake_leg(鎽囪吙锛岄渶steps/speed/direction)銆乽pdown(涓婁笅杩愬姩锛岄渶steps/speed/amount)銆亀hirlwind_leg(鏃嬮鑵匡紝闇�steps/speed/amount)锛�"
+ "鍥哄畾鍔ㄤ綔锛歴it(鍧愪笅)銆乻howcase(灞曠ず鍔ㄤ綔)銆乭ome(澶嶄綅)锛�"
+ "鎵嬮儴鍔ㄤ綔(闇�鎵嬮儴鑸垫満)锛歨ands_up(涓炬墜锛岄渶speed/direction)銆乭ands_down(鏀炬墜锛岄渶speed/direction)銆乭and_wave(鎸ユ墜锛岄渶direction)銆�"
+ "windmill(澶ч杞︼紝闇�steps/speed/amount)銆乼akeoff(璧烽锛岄渶steps/speed/amount)銆乫itness(鍋ヨ韩锛岄渶steps/speed/amount)銆�"
+ "greeting(鎵撴嫑鍛硷紝闇�direction/steps)銆乻hy(瀹崇緸锛岄渶direction/steps)銆乺adio_calisthenics(骞挎挱浣撴搷)銆乵agic_circle(鐖辩殑榄斿姏杞湀鍦�)",
+ PropertyList({
+ Property("action", kPropertyTypeString, "sit"),
+ Property("steps", kPropertyTypeInteger, 3, 1, 100),
+ Property("speed", kPropertyTypeInteger, 700, 100, 3000),
+ Property("direction", kPropertyTypeInteger, 1, -1, 1),
+ Property("amount", kPropertyTypeInteger, 30, 0, 170),
+ Property("arm_swing", kPropertyTypeInteger, 50, 0, 170)
+ }),
+ [this](const PropertyList& properties) -> ReturnValue {
+ std::string action = properties["action"].value<std::string>();
+ // 鎵�鏈夊弬鏁伴兘鏈夐粯璁ゅ�硷紝鐩存帴璁块棶鍗冲彲
+ int steps = properties["steps"].value<int>();
+ int speed = properties["speed"].value<int>();
+ int direction = properties["direction"].value<int>();
+ int amount = properties["amount"].value<int>();
+ int arm_swing = properties["arm_swing"].value<int>();
+
+ // 鍩虹绉诲姩鍔ㄤ綔
+ if (action == "walk") {
+ QueueAction(ACTION_WALK, steps, speed, direction, arm_swing);
+ return true;
+ } else if (action == "turn") {
+ QueueAction(ACTION_TURN, steps, speed, direction, arm_swing);
+ return true;
+ } else if (action == "jump") {
+ QueueAction(ACTION_JUMP, steps, speed, 0, 0);
+ return true;
+ } else if (action == "swing") {
+ QueueAction(ACTION_SWING, steps, speed, 0, amount);
+ return true;
+ } else if (action == "moonwalk") {
+ QueueAction(ACTION_MOONWALK, steps, speed, direction, amount);
+ return true;
+ } else if (action == "bend") {
+ QueueAction(ACTION_BEND, steps, speed, direction, 0);
+ return true;
+ } else if (action == "shake_leg") {
+ QueueAction(ACTION_SHAKE_LEG, steps, speed, direction, 0);
+ return true;
+ } else if (action == "updown") {
+ QueueAction(ACTION_UPDOWN, steps, speed, 0, amount);
+ return true;
+ } else if (action == "whirlwind_leg") {
+ QueueAction(ACTION_WHIRLWIND_LEG, steps, speed, 0, amount);
+ return true;
+ }
+ // 鍥哄畾鍔ㄤ綔
+ else if (action == "sit") {
+ QueueAction(ACTION_SIT, 1, 0, 0, 0);
+ return true;
+ } else if (action == "showcase") {
+ QueueAction(ACTION_SHOWCASE, 1, 0, 0, 0);
+ return true;
+ } else if (action == "home") {
+ QueueAction(ACTION_HOME, 1, 1000, 1, 0);
+ return true;
+ }
+ // 鎵嬮儴鍔ㄤ綔
+ else if (action == "hands_up") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_HANDS_UP, 1, speed, direction, 0);
+ return true;
+ } else if (action == "hands_down") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_HANDS_DOWN, 1, speed, direction, 0);
+ return true;
+ } else if (action == "hand_wave") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_HAND_WAVE, 1, 0, 0, direction);
+ return true;
+ } else if (action == "windmill") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_WINDMILL, steps, speed, 0, amount);
+ return true;
+ } else if (action == "takeoff") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_TAKEOFF, steps, speed, 0, amount);
+ return true;
+ } else if (action == "fitness") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_FITNESS, steps, speed, 0, amount);
+ return true;
+ } else if (action == "greeting") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_GREETING, steps, 0, direction, 0);
+ return true;
+ } else if (action == "shy") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_SHY, steps, 0, direction, 0);
+ return true;
+ } else if (action == "radio_calisthenics") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_RADIO_CALISTHENICS, 1, 0, 0, 0);
+ return true;
+ } else if (action == "magic_circle") {
+ if (!has_hands_) {
+ return "閿欒锛氭鍔ㄤ綔闇�瑕佹墜閮ㄨ埖鏈烘敮鎸�";
+ }
+ QueueAction(ACTION_MAGIC_CIRCLE, 1, 0, 0, 0);
+ return true;
+ } else {
+ return "閿欒锛氭棤鏁堢殑鍔ㄤ綔鍚嶇О銆傚彲鐢ㄥ姩浣滐細walk, turn, jump, swing, moonwalk, bend, shake_leg, updown, whirlwind_leg, sit, showcase, home, hands_up, hands_down, hand_wave, windmill, takeoff, fitness, greeting, shy, radio_calisthenics, magic_circle";
+ }
+ });
+
+
+ // 鑸垫満搴忓垪宸ュ叿锛堟敮鎸佸垎娈靛彂閫侊紝姣忔鍙戦�佷竴涓簭鍒楋紝鑷姩鎺掗槦鎵ц锛�
+ mcp_server.AddTool(
+ "self.otto.servo_sequences",
+ "AI鑷畾涔夊姩浣滅紪绋嬶紙鍗冲叴鍔ㄤ綔锛夈�傛敮鎸佸垎娈靛彂閫佸簭鍒楋細瓒呰繃5涓簭鍒楀缓璁瓵I鍙互杩炵画澶氭璋冪敤姝ゅ伐鍏凤紝姣忔鍙戦�佷竴涓煭搴忓垪锛岀郴缁熶細鑷姩鎺掗槦鎸夐『搴忔墽琛屻�傛敮鎸佹櫘閫氱Щ鍔ㄥ拰鎸崱鍣ㄤ袱绉嶆ā寮忋��"
+ "鏈哄櫒浜虹粨鏋勶細鍙屾墜鍙笂涓嬫憜鍔紝鍙岃吙鍙唴鏀跺灞曪紝鍙岃剼鍙笂涓嬬炕杞��"
+ "鑸垫満璇存槑锛�"
+ "ll(宸﹁吙)锛氬唴鏀跺灞曪紝0搴�=瀹屽叏澶栧睍锛�90搴�=涓珛锛�180搴�=瀹屽叏鍐呮敹锛�"
+ "rl(鍙宠吙)锛氬唴鏀跺灞曪紝0搴�=瀹屽叏鍐呮敹锛�90搴�=涓珛锛�180搴�=瀹屽叏澶栧睍锛�"
+ "lf(宸﹁剼)锛氫笂涓嬬炕杞紝0搴�=瀹屽叏鍚戜笂锛�90搴�=姘村钩锛�180搴�=瀹屽叏鍚戜笅锛�"
+ "rf(鍙宠剼)锛氫笂涓嬬炕杞紝0搴�=瀹屽叏鍚戜笅锛�90搴�=姘村钩锛�180搴�=瀹屽叏鍚戜笂锛�"
+ "lh(宸︽墜)锛氫笂涓嬫憜鍔紝0搴�=瀹屽叏鍚戜笅锛�90搴�=姘村钩锛�180搴�=瀹屽叏鍚戜笂锛�"
+ "rh(鍙虫墜)锛氫笂涓嬫憜鍔紝0搴�=瀹屽叏鍚戜笂锛�90搴�=姘村钩锛�180搴�=瀹屽叏鍚戜笅锛�"
+ "sequence: 鍗曚釜搴忓垪瀵硅薄锛屽寘鍚�'a'鍔ㄤ綔鏁扮粍锛岄《灞傚彲閫夊弬鏁帮細"
+ "'d'(搴忓垪鎵ц瀹屾垚鍚庡欢杩熸绉掓暟锛岀敤浜庡簭鍒椾箣闂寸殑鍋滈】)銆�"
+ "姣忎釜鍔ㄤ綔瀵硅薄鍖呭惈锛�"
+ "鏅�氭ā寮忥細's'鑸垫満浣嶇疆瀵硅薄(閿悕锛歭l/rl/lf/rf/lh/rh锛屽�硷細0-180搴�)锛�'v'绉诲姩閫熷害100-3000姣(榛樿1000)锛�'d'鍔ㄤ綔鍚庡欢杩熸绉掓暟(榛樿0)锛�"
+ "鎸崱妯″紡锛�'osc'鎸崱鍣ㄥ璞★紝鍖呭惈'a'鎸箙瀵硅薄(鍚勮埖鏈烘尟骞�10-90搴︼紝榛樿20搴�)锛�'o'涓績瑙掑害瀵硅薄(鍚勮埖鏈烘尟鑽′腑蹇冪粷瀵硅搴�0-180搴︼紝榛樿90搴�)锛�'ph'鐩镐綅宸璞�(鍚勮埖鏈虹浉浣嶅樊锛屽害锛�0-360搴︼紝榛樿0搴�)锛�'p'鍛ㄦ湡100-3000姣(榛樿500)锛�'c'鍛ㄦ湡鏁�0.1-20.0(榛樿5.0)锛�"
+ "浣跨敤鏂瑰紡锛欰I鍙互杩炵画澶氭璋冪敤姝ゅ伐鍏凤紝姣忔鍙戦�佷竴涓簭鍒楋紝绯荤粺浼氳嚜鍔ㄦ帓闃熸寜椤哄簭鎵ц銆�"
+ "閲嶈璇存槑锛氬乏鍙宠吙鑴氶渿鑽$殑鏃跺�欙紝鏈変竴鍙剼蹇呴』鍦�90搴︼紝鍚﹀垯浼氭崯鍧忔満鍣ㄤ汉锛屽鏋滃彂閫佸涓簭鍒楋紙搴忓垪鏁�>1锛夛紝瀹屾垚鎵�鏈夊簭鍒楀悗闇�瑕佸浣嶆椂锛孉I搴旇鏈�鍚庡崟鐙皟鐢╯elf.otto.home宸ュ叿杩涜澶嶄綅锛屼笉瑕佸湪搴忓垪涓缃浣嶅弬鏁般��"
+ "鏅�氭ā寮忕ず渚嬶細鍙戦��3涓簭鍒楋紝鏈�鍚庤皟鐢ㄥ浣嶏細"
+ "绗�1娆¤皟鐢▄\"sequence\":\"{\\\"a\\\":[{\\\"s\\\":{\\\"ll\\\":100},\\\"v\\\":1000}],\\\"d\\\":500}\"}锛�"
+ "绗�2娆¤皟鐢▄\"sequence\":\"{\\\"a\\\":[{\\\"s\\\":{\\\"ll\\\":90},\\\"v\\\":800}],\\\"d\\\":500}\"}锛�"
+ "绗�3娆¤皟鐢▄\"sequence\":\"{\\\"a\\\":[{\\\"s\\\":{\\\"ll\\\":80},\\\"v\\\":800}]}\"}锛�"
+ "鏈�鍚庤皟鐢╯elf.otto.home宸ュ叿杩涜澶嶄綅銆�"
+ "鎸崱鍣ㄦā寮忕ず渚嬶細"
+ "绀轰緥1-鍙岃噦鍚屾鎽嗗姩锛歿\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"lh\\\":30,\\\"rh\\\":30},\\\"o\\\":{\\\"lh\\\":90,\\\"rh\\\":-90},\\\"p\\\":500,\\\"c\\\":5.0}}],\\\"d\\\":0}\"}锛�"
+ "绀轰緥2-鍙岃吙浜ゆ浛鎸崱锛堟尝娴晥鏋滐級锛歿\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"ll\\\":20,\\\"rl\\\":20},\\\"o\\\":{\\\"ll\\\":90,\\\"rl\\\":-90},\\\"ph\\\":{\\\"rl\\\":180},\\\"p\\\":600,\\\"c\\\":3.0}}],\\\"d\\\":0}\"}锛�"
+ "绀轰緥3-鍗曡吙鎸崱閰嶅悎鍥哄畾鑴氾紙瀹夊叏锛夛細{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"ll\\\":45},\\\"o\\\":{\\\"ll\\\":90,\\\"lf\\\":90},\\\"p\\\":400,\\\"c\\\":4.0}}],\\\"d\\\":0}\"}锛�"
+ "绀轰緥4-澶嶆潅澶氳埖鏈烘尟鑽★紙鎵嬪拰鑵匡級锛歿\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"lh\\\":25,\\\"rh\\\":25,\\\"ll\\\":15},\\\"o\\\":{\\\"lh\\\":90,\\\"rh\\\":90,\\\"ll\\\":90,\\\"lf\\\":90},\\\"ph\\\":{\\\"rh\\\":180},\\\"p\\\":800,\\\"c\\\":6.0}}],\\\"d\\\":500}\"}锛�"
+ "绀轰緥5-蹇�熸憞鎽嗭細{\"sequence\":\"{\\\"a\\\":[{\\\"osc\\\":{\\\"a\\\":{\\\"ll\\\":30,\\\"rl\\\":30},\\\"o\\\":{\\\"ll\\\":90,\\\"rl\\\":90},\\\"ph\\\":{\\\"rl\\\":180},\\\"p\\\":300,\\\"c\\\":10.0}}],\\\"d\\\":0}\"}銆�",
+ PropertyList({Property("sequence", kPropertyTypeString,
+ "{\"a\":[{\"s\":{\"ll\":90,\"rl\":90},\"v\":1000}]}")}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ std::string sequence = properties["sequence"].value<std::string>();
+ // 妫�鏌ユ槸鍚︽槸JSON瀵硅薄锛堝彲鑳芥槸瀛楃涓叉牸寮忔垨宸茶В鏋愮殑瀵硅薄锛�
+ // 濡傛灉sequence鏄疛SON瀛楃涓诧紝鐩存帴浣跨敤锛涘鏋滄槸瀵硅薄瀛楃涓诧紝涔熼渶瑕佷娇鐢�
+ QueueServoSequence(sequence.c_str());
+ return true;
+ });
+
+
+ mcp_server.AddTool("self.otto.stop", "绔嬪嵆鍋滄鎵�鏈夊姩浣滃苟澶嶄綅", PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ is_action_in_progress_ = false;
+ PowerManager::ResumeBatteryUpdate(); // 鍋滄鍔ㄤ綔鏃舵仮澶嶇數閲忔洿鏂�
+ xQueueReset(action_queue_);
+
+ QueueAction(ACTION_HOME, 1, 1000, 1, 0);
+ return true;
+ });
+
+ mcp_server.AddTool(
+ "self.otto.set_trim",
+ "鏍″噯鍗曚釜鑸垫満浣嶇疆銆傝缃寚瀹氳埖鏈虹殑寰皟鍙傛暟浠ヨ皟鏁存満鍣ㄤ汉鐨勫垵濮嬬珯绔嬪Э鎬侊紝璁剧疆灏嗘案涔呬繚瀛樸��"
+ "servo_type: 鑸垫満绫诲瀷(left_leg/right_leg/left_foot/right_foot/left_hand/right_hand); "
+ "trim_value: 寰皟鍊�(-50鍒�50搴�)",
+ PropertyList({Property("servo_type", kPropertyTypeString, "left_leg"),
+ Property("trim_value", kPropertyTypeInteger, 0, -50, 50)}),
+ [this](const PropertyList& properties) -> ReturnValue {
+ std::string servo_type = properties["servo_type"].value<std::string>();
+ int trim_value = properties["trim_value"].value<int>();
+
+ ESP_LOGI(TAG, "璁剧疆鑸垫満寰皟: %s = %d搴�", servo_type.c_str(), trim_value);
+
+ // 鑾峰彇褰撳墠鎵�鏈夊井璋冨��
+ Settings settings("otto_trims", true);
+ int left_leg = settings.GetInt("left_leg", 0);
+ int right_leg = settings.GetInt("right_leg", 0);
+ int left_foot = settings.GetInt("left_foot", 0);
+ int right_foot = settings.GetInt("right_foot", 0);
+ int left_hand = settings.GetInt("left_hand", 0);
+ int right_hand = settings.GetInt("right_hand", 0);
+
+ // 鏇存柊鎸囧畾鑸垫満鐨勫井璋冨��
+ if (servo_type == "left_leg") {
+ left_leg = trim_value;
+ settings.SetInt("left_leg", left_leg);
+ } else if (servo_type == "right_leg") {
+ right_leg = trim_value;
+ settings.SetInt("right_leg", right_leg);
+ } else if (servo_type == "left_foot") {
+ left_foot = trim_value;
+ settings.SetInt("left_foot", left_foot);
+ } else if (servo_type == "right_foot") {
+ right_foot = trim_value;
+ settings.SetInt("right_foot", right_foot);
+ } else if (servo_type == "left_hand") {
+ if (!has_hands_) {
+ return "閿欒锛氭満鍣ㄤ汉娌℃湁閰嶇疆鎵嬮儴鑸垫満";
+ }
+ left_hand = trim_value;
+ settings.SetInt("left_hand", left_hand);
+ } else if (servo_type == "right_hand") {
+ if (!has_hands_) {
+ return "閿欒锛氭満鍣ㄤ汉娌℃湁閰嶇疆鎵嬮儴鑸垫満";
+ }
+ right_hand = trim_value;
+ settings.SetInt("right_hand", right_hand);
+ } else {
+ return "閿欒锛氭棤鏁堢殑鑸垫満绫诲瀷锛岃浣跨敤: left_leg, right_leg, left_foot, "
+ "right_foot, left_hand, right_hand";
+ }
+
+ otto_.SetTrims(left_leg, right_leg, left_foot, right_foot, left_hand, right_hand);
+
+ QueueAction(ACTION_JUMP, 1, 500, 0, 0);
+
+ return "鑸垫満 " + servo_type + " 寰皟璁剧疆涓� " + std::to_string(trim_value) +
+ " 搴︼紝宸叉案涔呬繚瀛�";
+ });
+
+ mcp_server.AddTool("self.otto.get_trims", "鑾峰彇褰撳墠鐨勮埖鏈哄井璋冭缃�", PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("otto_trims", false);
+
+ int left_leg = settings.GetInt("left_leg", 0);
+ int right_leg = settings.GetInt("right_leg", 0);
+ int left_foot = settings.GetInt("left_foot", 0);
+ int right_foot = settings.GetInt("right_foot", 0);
+ int left_hand = settings.GetInt("left_hand", 0);
+ int right_hand = settings.GetInt("right_hand", 0);
+
+ std::string result =
+ "{\"left_leg\":" + std::to_string(left_leg) +
+ ",\"right_leg\":" + std::to_string(right_leg) +
+ ",\"left_foot\":" + std::to_string(left_foot) +
+ ",\"right_foot\":" + std::to_string(right_foot) +
+ ",\"left_hand\":" + std::to_string(left_hand) +
+ ",\"right_hand\":" + std::to_string(right_hand) + "}";
+
+ ESP_LOGI(TAG, "鑾峰彇寰皟璁剧疆: %s", result.c_str());
+ return result;
+ });
+
+ mcp_server.AddTool("self.otto.get_status", "鑾峰彇鏈哄櫒浜虹姸鎬侊紝杩斿洖 moving 鎴� idle",
+ PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return is_action_in_progress_ ? "moving" : "idle";
+ });
+
+ mcp_server.AddTool("self.battery.get_level", "鑾峰彇鏈哄櫒浜虹數姹犵數閲忓拰鍏呯數鐘舵��", PropertyList(),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto& board = Board::GetInstance();
+ int level = 0;
+ bool charging = false;
+ bool discharging = false;
+ board.GetBatteryLevel(level, charging, discharging);
+
+ std::string status =
+ "{\"level\":" + std::to_string(level) +
+ ",\"charging\":" + (charging ? "true" : "false") + "}";
+ return status;
+ });
+
+ mcp_server.AddTool("self.otto.get_ip", "鑾峰彇鏈哄櫒浜篧iFi IP鍦板潃", PropertyList(),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto& wifi = WifiManager::GetInstance();
+ std::string ip = wifi.GetIpAddress();
+ if (ip.empty()) {
+ return "{\"ip\":\"\",\"connected\":false}";
+ }
+ std::string status = "{\"ip\":\"" + ip + "\",\"connected\":true}";
+ return status;
+ });
+
+ ESP_LOGI(TAG, "MCP宸ュ叿娉ㄥ唽瀹屾垚");
+ }
+
+ ~OttoController() {
+ if (action_task_handle_ != nullptr) {
+ vTaskDelete(action_task_handle_);
+ action_task_handle_ = nullptr;
+ }
+ vQueueDelete(action_queue_);
+ }
+};
+
+static OttoController* g_otto_controller = nullptr;
+
+void InitializeOttoController(const HardwareConfig& hw_config) {
+ if (g_otto_controller == nullptr) {
+ g_otto_controller = new OttoController(hw_config);
+ ESP_LOGI(TAG, "Otto鎺у埗鍣ㄥ凡鍒濆鍖栧苟娉ㄥ唽MCP宸ュ叿");
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.cc b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.cc
new file mode 100644
index 0000000..7702091
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.cc
@@ -0,0 +1,134 @@
+#include "otto_emoji_display.h"
+
+#include <esp_log.h>
+
+#include <cstring>
+#include <vector>
+
+#include "assets.h"
+#include "assets/lang_config.h"
+#include "display/lvgl_display/emoji_collection.h"
+#include "display/lvgl_display/lvgl_image.h"
+#include "display/lvgl_display/lvgl_theme.h"
+
+#define TAG "OttoEmojiDisplay"
+OttoEmojiDisplay::OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+}
+
+void OttoEmojiDisplay::SetupUI() {
+ // Prevent duplicate calls - parent SetupUI() will also check, but check here for early return
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetupUI() called multiple times, skipping duplicate call");
+ return;
+ }
+
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ // Setup preview image after UI is initialized - release lock before calling SetEmotion
+ // to avoid deadlock (SetEmotion also acquires DisplayLockGuard internally)
+ {
+ DisplayLockGuard lock(this);
+ lv_obj_set_size(preview_image_, width_ , height_ );
+ }
+
+ // Set default emotion after UI is initialized
+ SetEmotion("staticstate");
+}
+
+void OttoEmojiDisplay::SetupPreviewImage() {
+ DisplayLockGuard lock(this);
+ if (preview_image_ == nullptr) {
+ ESP_LOGW(TAG, "SetupPreviewImage called but preview_image_ is nullptr (UI not initialized yet)");
+ return;
+ }
+ lv_obj_set_size(preview_image_, width_ , height_ );
+}
+
+void OttoEmojiDisplay::InitializeOttoEmojis() {
+ ESP_LOGI(TAG, "Otto琛ㄦ儏鍒濆鍖栧皢鐢盇ssets绯荤粺澶勭悊");
+ // 琛ㄦ儏鍒濆鍖栧凡绉昏嚦assets绯荤粺,閫氳繃DEFAULT_EMOJI_COLLECTION=otto-gif閰嶇疆
+ // assets.cc浼氫粠assets鍒嗗尯鍔犺浇GIF琛ㄦ儏骞惰缃埌theme
+ // Note: Default emotion is now set in SetupUI() after LVGL objects are created
+}
+
+LV_FONT_DECLARE(OTTO_ICON_FONT);
+void OttoEmojiDisplay::SetStatus(const char* status) {
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ DisplayLockGuard lock(this);
+ if (!status) {
+ ESP_LOGE(TAG, "SetStatus: status is nullptr");
+ return;
+ }
+
+ if (strcmp(status, Lang::Strings::LISTENING) == 0) {
+ lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0);
+ lv_label_set_text(status_label_, "\xEF\x84\xB0"); // U+F130 楹﹀厠椋庡浘鏍�
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ } else if (strcmp(status, Lang::Strings::SPEAKING) == 0) {
+ lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0);
+ lv_label_set_text(status_label_, "\xEF\x80\xA8"); // U+F028 璇磋瘽鍥炬爣
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(network_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(battery_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ } else if (strcmp(status, Lang::Strings::CONNECTING) == 0) {
+ lv_obj_set_style_text_font(status_label_, &OTTO_ICON_FONT, 0);
+ lv_label_set_text(status_label_, "\xEF\x83\x81"); // U+F0c1 杩炴帴鍥炬爣
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ } else if (strcmp(status, Lang::Strings::STANDBY) == 0) {
+ lv_obj_set_style_text_font(status_label_, text_font, 0);
+ lv_label_set_text(status_label_, "");
+ lv_obj_clear_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_clear_flag(network_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_clear_flag(battery_label_, LV_OBJ_FLAG_HIDDEN);
+ return;
+ }
+
+ lv_obj_set_style_text_font(status_label_, text_font, 0);
+ lv_label_set_text(status_label_, status);
+}
+
+void OttoEmojiDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
+ DisplayLockGuard lock(this);
+ if (preview_image_ == nullptr) {
+ ESP_LOGE(TAG, "Preview image is not initialized");
+ return;
+ }
+
+ if (image == nullptr) {
+ esp_timer_stop(preview_timer_);
+ lv_obj_remove_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
+ preview_image_cached_.reset();
+ if (gif_controller_) {
+ gif_controller_->Start();
+ }
+ return;
+ }
+
+ preview_image_cached_ = std::move(image);
+ auto img_dsc = preview_image_cached_->image_dsc();
+ // 璁剧疆鍥剧墖婧愬苟鏄剧ず棰勮鍥剧墖
+ lv_image_set_src(preview_image_, img_dsc);
+ lv_image_set_rotation(preview_image_, 900);
+ if (img_dsc->header.w > 0 && img_dsc->header.h > 0) {
+ // zoom factor 1.0
+ lv_image_set_scale(preview_image_, 256 * width_ / img_dsc->header.w);
+ }
+
+ // Hide emoji_box_
+ if (gif_controller_) {
+ gif_controller_->Stop();
+ }
+ lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
+ esp_timer_stop(preview_timer_);
+ ESP_ERROR_CHECK(esp_timer_start_once(preview_timer_, PREVIEW_IMAGE_DURATION_MS * 1000));
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.h b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.h
new file mode 100644
index 0000000..0216704
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_emoji_display.h
@@ -0,0 +1,24 @@
+#pragma once
+
+#include "display/lcd_display.h"
+
+/**
+ * @brief Otto鏈哄櫒浜篏IF琛ㄦ儏鏄剧ず绫�
+ * 缁ф壙SpiLcdDisplay锛岄�氳繃EmojiCollection娣诲姞GIF琛ㄦ儏鏀寔
+ */
+class OttoEmojiDisplay : public SpiLcdDisplay {
+ public:
+ /**
+ * @brief 鏋勯�犲嚱鏁帮紝鍙傛暟涓嶴piLcdDisplay鐩稿悓
+ */
+ OttoEmojiDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy);
+
+ virtual ~OttoEmojiDisplay() = default;
+ virtual void SetStatus(const char* status) override;
+ virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
+ virtual void SetupUI() override;
+
+ private:
+ void InitializeOttoEmojis();
+ void SetupPreviewImage();
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_icon_font.c b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_icon_font.c
new file mode 100644
index 0000000..dc05cce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_icon_font.c
@@ -0,0 +1,121 @@
+/*******************************************************************************
+ * Size: 20 px
+ * Bpp: 1
+ * Opts: --bpp 1 --size 20 --no-compress --stride 1 --align 1 --font fontawesome-webfont.ttf --range 61744,61633,61480 --format lvgl -o OTTO_ICON_FONT.c
+ ******************************************************************************/
+
+#ifdef __has_include
+#if __has_include("lvgl.h")
+#ifndef LV_LVGL_H_INCLUDE_SIMPLE
+#define LV_LVGL_H_INCLUDE_SIMPLE
+#endif
+#endif
+#endif
+
+#ifdef LV_LVGL_H_INCLUDE_SIMPLE
+#include "lvgl.h"
+#else
+#include "lvgl/lvgl.h"
+#endif
+
+#ifndef ENABLE_OTTO_ICON_FONT
+#define ENABLE_OTTO_ICON_FONT 1
+#endif
+
+#if ENABLE_OTTO_ICON_FONT
+
+/*-----------------
+ * BITMAPS
+ *----------------*/
+
+/*Store the image of the glyphs*/
+static LV_ATTRIBUTE_LARGE_CONST const uint8_t glyph_bitmap[] = {
+ /* U+F028 "飥�" */
+ 0x0, 0x6, 0x0, 0x10, 0xe0, 0x6, 0x6, 0x3, 0xc6, 0x60, 0xf8, 0x65, 0xff, 0x24, 0xff, 0xe6, 0x4f, 0xfc, 0x49, 0xff, 0x89, 0x3f, 0xf3, 0x27, 0xfe, 0x49, 0x87, 0xc3, 0x20, 0x78, 0xcc, 0x3, 0x3, 0x0,
+ 0x21, 0xc0, 0x0, 0x30,
+
+ /* U+F0C1 "飪�" */
+ 0x1e, 0x0, 0xf, 0xc0, 0x7, 0x38, 0x3, 0x87, 0x0, 0xc0, 0xc0, 0x30, 0xb0, 0x7, 0x3c, 0x0, 0xef, 0x0, 0x1f, 0xfe, 0x3, 0xff, 0xc0, 0x7, 0x38, 0x3, 0xe7, 0x0, 0xd0, 0xc0, 0x30, 0x30, 0x6, 0x1c, 0x0,
+ 0xce, 0x0, 0x1f, 0x0, 0x3, 0x80,
+
+ /* U+F130 "飫�" */
+ 0x7, 0x0, 0xfe, 0x7, 0xf0, 0x3f, 0x81, 0xfc, 0xf, 0xe0, 0x7f, 0x13, 0xf9, 0x9f, 0xcc, 0xfe, 0x67, 0xf3, 0xbf, 0xb4, 0x73, 0x18, 0x30, 0x7f, 0x0, 0x60, 0x2, 0x1, 0xff, 0x0};
+
+/*---------------------
+ * GLYPH DESCRIPTION
+ *--------------------*/
+
+static const lv_font_fmt_txt_glyph_dsc_t glyph_dsc[] = {{.bitmap_index = 0, .adv_w = 0, .box_w = 0, .box_h = 0, .ofs_x = 0, .ofs_y = 0} /* id = 0 reserved */,
+ {.bitmap_index = 0, .adv_w = 297, .box_w = 19, .box_h = 16, .ofs_x = 0, .ofs_y = -1},
+ {.bitmap_index = 38, .adv_w = 297, .box_w = 18, .box_h = 18, .ofs_x = 0, .ofs_y = -1},
+ {.bitmap_index = 79, .adv_w = 206, .box_w = 13, .box_h = 18, .ofs_x = 0, .ofs_y = -1}};
+
+/*---------------------
+ * CHARACTER MAPPING
+ *--------------------*/
+
+static const uint16_t unicode_list_0[] = {0x0, 0x99, 0x108};
+
+/*Collect the unicode lists and glyph_id offsets*/
+static const lv_font_fmt_txt_cmap_t cmaps[] = {
+ {.range_start = 61480, .range_length = 265, .glyph_id_start = 1, .unicode_list = unicode_list_0, .glyph_id_ofs_list = NULL, .list_length = 3, .type = LV_FONT_FMT_TXT_CMAP_SPARSE_TINY}};
+
+/*--------------------
+ * ALL CUSTOM DATA
+ *--------------------*/
+
+#if LVGL_VERSION_MAJOR == 8
+/*Store all the custom data of the font*/
+static lv_font_fmt_txt_glyph_cache_t cache;
+#endif
+
+#if LVGL_VERSION_MAJOR >= 8
+static const lv_font_fmt_txt_dsc_t otto_icon_font_dsc = {
+#else
+static lv_font_fmt_txt_dsc_t otto_icon_font_dsc = {
+#endif
+ .glyph_bitmap = glyph_bitmap,
+ .glyph_dsc = glyph_dsc,
+ .cmaps = cmaps,
+ .kern_dsc = NULL,
+ .kern_scale = 0,
+ .cmap_num = 1,
+ .bpp = 1,
+ .kern_classes = 0,
+ .bitmap_format = 0,
+#if LVGL_VERSION_MAJOR == 8
+ .cache = &cache
+#endif
+
+};
+
+/*-----------------
+ * PUBLIC FONT
+ *----------------*/
+
+/*Initialize a public general font descriptor*/
+#if LVGL_VERSION_MAJOR >= 8
+const lv_font_t OTTO_ICON_FONT = {
+#else
+lv_font_t OTTO_ICON_FONT = {
+#endif
+ .get_glyph_dsc = lv_font_get_glyph_dsc_fmt_txt, /*Function pointer to get glyph's data*/
+ .get_glyph_bitmap = lv_font_get_bitmap_fmt_txt, /*Function pointer to get glyph's bitmap*/
+ .line_height = 18, /*The maximum line height required by the font*/
+ .base_line = 1, /*Baseline measured from the bottom of the line*/
+#if !(LVGL_VERSION_MAJOR == 6 && LVGL_VERSION_MINOR == 0)
+ .subpx = LV_FONT_SUBPX_NONE,
+#endif
+#if LV_VERSION_CHECK(7, 4, 0) || LVGL_VERSION_MAJOR >= 8
+ .underline_position = 0,
+ .underline_thickness = 0,
+#endif
+ .static_bitmap = 0,
+ .dsc = &otto_icon_font_dsc, /*The custom font data. Will be accessed by `get_glyph_bitmap/dsc` */
+#if LV_VERSION_CHECK(8, 2, 0) || LVGL_VERSION_MAJOR >= 9
+ .fallback = NULL,
+#endif
+ .user_data = NULL,
+};
+
+#endif /*#if ENABLE_OTTO_ICON_FONT*/
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.cc b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.cc
new file mode 100644
index 0000000..9d11f12
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.cc
@@ -0,0 +1,964 @@
+#include "otto_movements.h"
+
+#include <algorithm>
+
+#include "freertos/idf_additions.h"
+#include "oscillator.h"
+
+static const char* TAG = "OttoMovements";
+
+#define HAND_HOME_POSITION 45
+
+Otto::Otto() {
+ is_otto_resting_ = false;
+ has_hands_ = false;
+ // 鍒濆鍖栨墍鏈夎埖鏈虹鑴氫负-1锛堟湭杩炴帴锛�
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ servo_pins_[i] = -1;
+ servo_trim_[i] = 0;
+ }
+}
+
+Otto::~Otto() {
+ DetachServos();
+}
+
+unsigned long IRAM_ATTR millis() {
+ return (unsigned long)(esp_timer_get_time() / 1000ULL);
+}
+
+void Otto::Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand,
+ int right_hand) {
+ servo_pins_[LEFT_LEG] = left_leg;
+ servo_pins_[RIGHT_LEG] = right_leg;
+ servo_pins_[LEFT_FOOT] = left_foot;
+ servo_pins_[RIGHT_FOOT] = right_foot;
+ servo_pins_[LEFT_HAND] = left_hand;
+ servo_pins_[RIGHT_HAND] = right_hand;
+
+ // 妫�鏌ユ槸鍚︽湁鎵嬮儴鑸垫満
+ has_hands_ = (left_hand != -1 && right_hand != -1);
+
+ AttachServos();
+ is_otto_resting_ = false;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- ATTACH & DETACH FUNCTIONS ----------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::AttachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Attach(servo_pins_[i]);
+ }
+ }
+}
+
+void Otto::DetachServos() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Detach();
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- OSCILLATORS TRIMS ------------------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand,
+ int right_hand) {
+ servo_trim_[LEFT_LEG] = left_leg;
+ servo_trim_[RIGHT_LEG] = right_leg;
+ servo_trim_[LEFT_FOOT] = left_foot;
+ servo_trim_[RIGHT_FOOT] = right_foot;
+
+ if (has_hands_) {
+ servo_trim_[LEFT_HAND] = left_hand;
+ servo_trim_[RIGHT_HAND] = right_hand;
+ }
+
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetTrim(servo_trim_[i]);
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////
+//-- BASIC MOTION FUNCTIONS -------------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::MoveServos(int time, int servo_target[]) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ final_time_ = millis() + time;
+ if (time > 10) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ increment_[i] = (servo_target[i] - servo_[i].GetPosition()) / (time / 10.0);
+ }
+ }
+
+ for (int iteration = 1; millis() < final_time_; iteration++) {
+ partial_time_ = millis() + 10;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_[i].GetPosition() + increment_[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ } else {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(time));
+ }
+
+ // final adjustment to the target.
+ bool f = true;
+ int adjustment_count = 0;
+ while (f && adjustment_count < 10) {
+ f = false;
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1 && servo_target[i] != servo_[i].GetPosition()) {
+ f = true;
+ break;
+ }
+ }
+ if (f) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetPosition(servo_target[i]);
+ }
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+ adjustment_count++;
+ }
+ };
+}
+
+void Otto::MoveSingle(int position, int servo_number) {
+ if (position > 180)
+ position = 90;
+ if (position < 0)
+ position = 90;
+
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ if (servo_number >= 0 && servo_number < SERVO_COUNT && servo_pins_[servo_number] != -1) {
+ servo_[servo_number].SetPosition(position);
+ }
+}
+
+void Otto::OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle = 1) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetO(offset[i]);
+ servo_[i].SetA(amplitude[i]);
+ servo_[i].SetT(period);
+ servo_[i].SetPh(phase_diff[i]);
+ }
+ }
+
+ double ref = millis();
+ double end_time = period * cycle + ref;
+
+ while (millis() < end_time) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].Refresh();
+ }
+ }
+ vTaskDelay(5);
+ }
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+void Otto::Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps = 1.0) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ int cycles = (int)steps;
+
+ //-- Execute complete cycles
+ if (cycles >= 1)
+ for (int i = 0; i < cycles; i++)
+ OscillateServos(amplitude, offset, period, phase_diff);
+
+ //-- Execute the final not complete cycle
+ OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles);
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+//---------------------------------------------------------
+//-- Execute2: 浣跨敤缁濆瑙掑害浣滀负鎸崱涓績
+//-- Parameters:
+//-- amplitude: 鎸箙鏁扮粍锛堟瘡涓埖鏈虹殑鎸崱骞呭害锛�
+//-- center_angle: 缁濆瑙掑害鏁扮粍锛�0-180搴︼級锛屼綔涓烘尟鑽′腑蹇冧綅缃�
+//-- period: 鍛ㄦ湡锛堟绉掞級
+//-- phase_diff: 鐩镐綅宸暟缁勶紙寮у害锛�
+//-- steps: 姝ユ暟/鍛ㄦ湡鏁帮紙鍙负灏忔暟锛�
+//---------------------------------------------------------
+void Otto::Execute2(int amplitude[SERVO_COUNT], int center_angle[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps = 1.0) {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ // 灏嗙粷瀵硅搴﹁浆鎹负offset锛坥ffset = center_angle - 90锛�
+ int offset[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ offset[i] = center_angle[i] - 90;
+ }
+
+ int cycles = (int)steps;
+
+ //-- Execute complete cycles
+ if (cycles >= 1)
+ for (int i = 0; i < cycles; i++)
+ OscillateServos(amplitude, offset, period, phase_diff);
+
+ //-- Execute the final not complete cycle
+ OscillateServos(amplitude, offset, period, phase_diff, (float)steps - cycles);
+ vTaskDelay(pdMS_TO_TICKS(10));
+}
+
+///////////////////////////////////////////////////////////////////
+//-- HOME = Otto at rest position -------------------------------//
+///////////////////////////////////////////////////////////////////
+void Otto::Home(bool hands_down) {
+ if (is_otto_resting_ == false) { // Go to rest position only if necessary
+ // 涓烘墍鏈夎埖鏈哄噯澶囧垵濮嬩綅缃��
+ int homes[SERVO_COUNT];
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (i == LEFT_HAND || i == RIGHT_HAND) {
+ if (hands_down) {
+ // 濡傛灉闇�瑕佸浣嶆墜閮紝璁剧疆涓洪粯璁ゅ��
+ if (i == LEFT_HAND) {
+ homes[i] = HAND_HOME_POSITION;
+ } else { // RIGHT_HAND
+ homes[i] = 180 - HAND_HOME_POSITION; // 鍙虫墜闀滃儚浣嶇疆
+ }
+ } else {
+ // 濡傛灉涓嶉渶瑕佸浣嶆墜閮紝淇濇寔褰撳墠浣嶇疆
+ homes[i] = servo_[i].GetPosition();
+ }
+ } else {
+ // 鑵块儴鍜岃剼閮ㄨ埖鏈哄缁堝浣�
+ homes[i] = 90;
+ }
+ }
+
+ MoveServos(700, homes);
+ is_otto_resting_ = true;
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(200));
+}
+
+bool Otto::GetRestState() {
+ return is_otto_resting_;
+}
+
+void Otto::SetRestState(bool state) {
+ is_otto_resting_ = state;
+}
+
+///////////////////////////////////////////////////////////////////
+//-- PREDETERMINED MOTION SEQUENCES -----------------------------//
+///////////////////////////////////////////////////////////////////
+//-- Otto movement: Jump
+//-- Parameters:
+//-- steps: Number of steps
+//-- T: Period
+//---------------------------------------------------------
+void Otto::Jump(float steps, int period) {
+ int up[SERVO_COUNT] = {90, 90, 150, 30, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ MoveServos(period, up);
+ int down[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ MoveServos(period, down);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Walking (forward or backward)
+//-- Parameters:
+//-- * steps: Number of steps
+//-- * T : Period
+//-- * Dir: Direction: FORWARD / BACKWARD
+//-- * amount: 鎵嬮儴鎽嗗姩骞呭害, 0琛ㄧず涓嶆憜鍔�
+//---------------------------------------------------------
+void Otto::Walk(float steps, int period, int dir, int amount) {
+ //-- Oscillator parameters for walking
+ //-- Hip sevos are in phase
+ //-- Feet servos are in phase
+ //-- Hip and feet are 90 degrees out of phase
+ //-- -90 : Walk forward
+ //-- 90 : Walk backward
+ //-- Feet servos also have the same offset (for tiptoe a little bit)
+ int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(dir * -90), DEG2RAD(dir * -90), 0, 0};
+
+ // 濡傛灉amount>0涓旀湁鎵嬮儴鑸垫満锛岃缃墜閮ㄦ尟骞呭拰鐩镐綅
+ if (amount > 0 && has_hands_) {
+ // 鎵嬭噦鎸箙浣跨敤浼犲叆鐨刟mount鍙傛暟
+ A[LEFT_HAND] = amount;
+ A[RIGHT_HAND] = amount;
+
+ // 宸︽墜涓庡彸鑵垮悓鐩革紝鍙虫墜涓庡乏鑵垮悓鐩革紝浣垮緱鏈哄櫒浜鸿蛋璺椂鎵嬭噦鑷劧鎽嗗姩
+ phase_diff[LEFT_HAND] = phase_diff[RIGHT_LEG]; // 宸︽墜涓庡彸鑵垮悓鐩�
+ phase_diff[RIGHT_HAND] = phase_diff[LEFT_LEG]; // 鍙虫墜涓庡乏鑵垮悓鐩�
+ } else {
+ A[LEFT_HAND] = 0;
+ A[RIGHT_HAND] = 0;
+ }
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Turning (left or right)
+//-- Parameters:
+//-- * Steps: Number of steps
+//-- * T: Period
+//-- * Dir: Direction: LEFT / RIGHT
+//-- * amount: 鎵嬮儴鎽嗗姩骞呭害, 0琛ㄧず涓嶆憜鍔�
+//---------------------------------------------------------
+void Otto::Turn(float steps, int period, int dir, int amount) {
+ //-- Same coordination than for walking (see Otto::walk)
+ //-- The Amplitudes of the hip's oscillators are not igual
+ //-- When the right hip servo amplitude is higher, the steps taken by
+ //-- the right leg are bigger than the left. So, the robot describes an
+ //-- left arc
+ int A[SERVO_COUNT] = {30, 30, 30, 30, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, 5, -5, HAND_HOME_POSITION - 90, HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(-90), 0, 0};
+
+ if (dir == LEFT) {
+ A[0] = 30; //-- Left hip servo
+ A[1] = 0; //-- Right hip servo
+ } else {
+ A[0] = 0;
+ A[1] = 30;
+ }
+
+ // 濡傛灉amount>0涓旀湁鎵嬮儴鑸垫満锛岃缃墜閮ㄦ尟骞呭拰鐩镐綅
+ if (amount > 0 && has_hands_) {
+ // 鎵嬭噦鎸箙浣跨敤浼犲叆鐨刟mount鍙傛暟
+ A[LEFT_HAND] = amount;
+ A[RIGHT_HAND] = amount;
+
+ // 杞悜鏃舵墜鑷傛憜鍔ㄧ浉浣嶏細宸︽墜涓庡乏鑵垮悓鐩革紝鍙虫墜涓庡彸鑵垮悓鐩革紝澧炲己杞悜鏁堟灉
+ phase_diff[LEFT_HAND] = phase_diff[LEFT_LEG]; // 宸︽墜涓庡乏鑵垮悓鐩�
+ phase_diff[RIGHT_HAND] = phase_diff[RIGHT_LEG]; // 鍙虫墜涓庡彸鑵垮悓鐩�
+ } else {
+ A[LEFT_HAND] = 0;
+ A[RIGHT_HAND] = 0;
+ }
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Lateral bend
+//-- Parameters:
+//-- steps: Number of bends
+//-- T: Period of one bend
+//-- dir: RIGHT=Right bend LEFT=Left bend
+//---------------------------------------------------------
+void Otto::Bend(int steps, int period, int dir) {
+ // Parameters of all the movements. Default: Left bend
+ int bend1[SERVO_COUNT] = {90, 90, 62, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int bend2[SERVO_COUNT] = {90, 90, 62, 105, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ // Time of one bend, constrained in order to avoid movements too fast.
+ // T=max(T, 600);
+ // Changes in the parameters if right direction is chosen
+ if (dir == -1) {
+ bend1[2] = 180 - 35;
+ bend1[3] = 180 - 60; // Not 65. Otto is unbalanced
+ bend2[2] = 180 - 105;
+ bend2[3] = 180 - 60;
+ }
+
+ // Time of the bend movement. Fixed parameter to avoid falls
+ int T2 = 800;
+
+ // Bend movement
+ for (int i = 0; i < steps; i++) {
+ MoveServos(T2 / 2, bend1);
+ MoveServos(T2 / 2, bend2);
+ vTaskDelay(pdMS_TO_TICKS(period * 0.8));
+ MoveServos(500, homes);
+ }
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Shake a leg
+//-- Parameters:
+//-- steps: Number of shakes
+//-- T: Period of one shake
+//-- dir: RIGHT=Right leg LEFT=Left leg
+//---------------------------------------------------------
+void Otto::ShakeLeg(int steps, int period, int dir) {
+ // This variable change the amount of shakes
+ int numberLegMoves = 2;
+
+ // Parameters of all the movements. Default: Right leg
+ int shake_leg1[SERVO_COUNT] = {90, 90, 58, 35, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int shake_leg2[SERVO_COUNT] = {90, 90, 58, 120, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int shake_leg3[SERVO_COUNT] = {90, 90, 58, 60, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int homes[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ // Changes in the parameters if left leg is chosen
+ if (dir == LEFT) {
+ shake_leg1[2] = 180 - 35;
+ shake_leg1[3] = 180 - 58;
+ shake_leg2[2] = 180 - 120;
+ shake_leg2[3] = 180 - 58;
+ shake_leg3[2] = 180 - 60;
+ shake_leg3[3] = 180 - 58;
+ }
+
+ // Time of the bend movement. Fixed parameter to avoid falls
+ int T2 = 1000;
+ // Time of one shake, constrained in order to avoid movements too fast.
+ period = period - T2;
+ period = std::max(period, 200 * numberLegMoves);
+
+ for (int j = 0; j < steps; j++) {
+ // Bend movement
+ MoveServos(T2 / 2, shake_leg1);
+ MoveServos(T2 / 2, shake_leg2);
+
+ // Shake movement
+ for (int i = 0; i < numberLegMoves; i++) {
+ MoveServos(period / (2 * numberLegMoves), shake_leg3);
+ MoveServos(period / (2 * numberLegMoves), shake_leg2);
+ }
+ MoveServos(500, homes); // Return to home position
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(period));
+}
+
+//---------------------------------------------------------
+//-- Otto movement: Sit (鍧愪笅)
+//---------------------------------------------------------
+void Otto::Sit() {
+ int target[SERVO_COUNT] = {120, 60, 0, 180, 45, 135};
+ MoveServos(600, target);
+}
+
+//---------------------------------------------------------
+//-- Otto movement: up & down
+//-- Parameters:
+//-- * steps: Number of jumps
+//-- * T: Period
+//-- * h: Jump height: SMALL / MEDIUM / BIG
+//-- (or a number in degrees 0 - 90)
+//---------------------------------------------------------
+void Otto::UpDown(float steps, int period, int height) {
+ //-- Both feet are 180 degrees out of phase
+ //-- Feet amplitude and offset are the same
+ //-- Initial phase for the right foot is -90, so that it starts
+ //-- in one extreme position (not in the middle)
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(90), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto movement: swinging side to side
+//-- Parameters:
+//-- steps: Number of steps
+//-- T : Period
+//-- h : Amount of swing (from 0 to 50 aprox)
+//---------------------------------------------------------
+void Otto::Swing(float steps, int period, int height) {
+ //-- Both feets are in phase. The offset is half the amplitude
+ //-- It causes the robot to swing from side to side
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height / 2, -height / 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(0), DEG2RAD(0), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto movement: swinging side to side without touching the floor with the heel
+//-- Parameters:
+//-- steps: Number of steps
+//-- T : Period
+//-- h : Amount of swing (from 0 to 50 aprox)
+//---------------------------------------------------------
+void Otto::TiptoeSwing(float steps, int period, int height) {
+ //-- Both feets are in phase. The offset is not half the amplitude in order to tiptoe
+ //-- It causes the robot to swing from side to side
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, height, -height, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Jitter
+//-- Parameters:
+//-- steps: Number of jitters
+//-- T: Period of one jitter
+//-- h: height (Values between 5 - 25)
+//---------------------------------------------------------
+void Otto::Jitter(float steps, int period, int height) {
+ //-- Both feet are 180 degrees out of phase
+ //-- Feet amplitude and offset are the same
+ //-- Initial phase for the right foot is -90, so that it starts
+ //-- in one extreme position (not in the middle)
+ //-- h is constrained to avoid hit the feets
+ height = std::min(25, height);
+ int A[SERVO_COUNT] = {height, height, 0, 0, 0, 0};
+ int O[SERVO_COUNT] = {0, 0, 0, 0, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), 0, 0, 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Ascending & turn (Jitter while up&down)
+//-- Parameters:
+//-- steps: Number of bends
+//-- T: Period of one bend
+//-- h: height (Values between 5 - 15)
+//---------------------------------------------------------
+void Otto::AscendingTurn(float steps, int period, int height) {
+ //-- Both feet and legs are 180 degrees out of phase
+ //-- Initial phase for the right foot is -90, so that it starts
+ //-- in one extreme position (not in the middle)
+ //-- h is constrained to avoid hit the feets
+ height = std::min(13, height);
+ int A[SERVO_COUNT] = {height, height, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height + 4, -height + 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {DEG2RAD(-90), DEG2RAD(90), DEG2RAD(-90), DEG2RAD(90), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Moonwalker. Otto moves like Michael Jackson
+//-- Parameters:
+//-- Steps: Number of steps
+//-- T: Period
+//-- h: Height. Typical valures between 15 and 40
+//-- dir: Direction: LEFT / RIGHT
+//---------------------------------------------------------
+void Otto::Moonwalker(float steps, int period, int height, int dir) {
+ //-- This motion is similar to that of the caterpillar robots: A travelling
+ //-- wave moving from one side to another
+ //-- The two Otto's feet are equivalent to a minimal configuration. It is known
+ //-- that 2 servos can move like a worm if they are 120 degrees out of phase
+ //-- In the example of Otto, the two feet are mirrored so that we have:
+ //-- 180 - 120 = 60 degrees. The actual phase difference given to the oscillators
+ //-- is 60 degrees.
+ //-- Both amplitudes are equal. The offset is half the amplitud plus a little bit of
+ //- offset so that the robot tiptoe lightly
+
+ int A[SERVO_COUNT] = {0, 0, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height / 2 + 2, -height / 2 - 2, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ int phi = -dir * 90;
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(phi), DEG2RAD(-60 * dir + phi), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//----------------------------------------------------------
+//-- Otto gait: Crusaito. A mixture between moonwalker and walk
+//-- Parameters:
+//-- steps: Number of steps
+//-- T: Period
+//-- h: height (Values between 20 - 50)
+//-- dir: Direction: LEFT / RIGHT
+//-----------------------------------------------------------
+void Otto::Crusaito(float steps, int period, int height, int dir) {
+ int A[SERVO_COUNT] = {25, 25, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height / 2 + 4, -height / 2 - 4, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {90, 90, DEG2RAD(0), DEG2RAD(-60 * dir), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: Flapping
+//-- Parameters:
+//-- steps: Number of steps
+//-- T: Period
+//-- h: height (Values between 10 - 30)
+//-- dir: direction: FOREWARD, BACKWARD
+//---------------------------------------------------------
+void Otto::Flapping(float steps, int period, int height, int dir) {
+ int A[SERVO_COUNT] = {12, 12, height, height, 0, 0};
+ int O[SERVO_COUNT] = {
+ 0, 0, height - 10, -height + 10, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+ double phase_diff[SERVO_COUNT] = {
+ DEG2RAD(0), DEG2RAD(180), DEG2RAD(-90 * dir), DEG2RAD(90 * dir), 0, 0};
+
+ //-- Let's oscillate the servos!
+ Execute(A, O, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- Otto gait: WhirlwindLeg (鏃嬮鑵�)
+//-- Parameters:
+//-- steps: Number of steps
+//-- period: Period (寤鸿100-800姣)
+//-- amplitude: amplitude (Values between 20 - 40)
+//---------------------------------------------------------
+void Otto::WhirlwindLeg(float steps, int period, int amplitude) {
+
+
+ int target[SERVO_COUNT] = {90, 90, 180, 90, 45, 20};
+ MoveServos(100, target);
+ target[RIGHT_FOOT] = 160;
+ MoveServos(500, target);
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ int C[SERVO_COUNT] = {90, 90, 180, 160, 45, 20};
+ int A[SERVO_COUNT] = {amplitude, 0, 0, 0, amplitude, 0};
+ double phase_diff[SERVO_COUNT] = {DEG2RAD(20), 0, 0, 0, DEG2RAD(20), 0};
+ Execute2(A, C, period, phase_diff, steps);
+
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 涓炬墜
+//-- Parameters:
+//-- period: 鍔ㄤ綔鏃堕棿
+//-- dir: 鏂瑰悜 1=宸︽墜, -1=鍙虫墜, 0=鍙屾墜
+//---------------------------------------------------------
+void Otto::HandsUp(int period, int dir) {
+ if (!has_hands_) {
+ return;
+ }
+
+ int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ if (dir == 0) {
+ target[LEFT_HAND] = 170;
+ target[RIGHT_HAND] = 10;
+ } else if (dir == LEFT) {
+ target[LEFT_HAND] = 170;
+ target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition();
+ } else if (dir == RIGHT) {
+ target[RIGHT_HAND] = 10;
+ target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition();
+ }
+
+ MoveServos(period, target);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鍙屾墜鏀句笅
+//-- Parameters:
+//-- period: 鍔ㄤ綔鏃堕棿
+//-- dir: 鏂瑰悜 1=宸︽墜, -1=鍙虫墜, 0=鍙屾墜
+//---------------------------------------------------------
+void Otto::HandsDown(int period, int dir) {
+ if (!has_hands_) {
+ return;
+ }
+
+ int target[SERVO_COUNT] = {90, 90, 90, 90, HAND_HOME_POSITION, 180 - HAND_HOME_POSITION};
+
+ if (dir == LEFT) {
+ target[RIGHT_HAND] = servo_[RIGHT_HAND].GetPosition();
+ } else if (dir == RIGHT) {
+ target[LEFT_HAND] = servo_[LEFT_HAND].GetPosition();
+ }
+
+ MoveServos(period, target);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鎸ユ墜
+//-- Parameters:
+//-- dir: 鏂瑰悜 LEFT/RIGHT/BOTH
+//---------------------------------------------------------
+void Otto::HandWave(int dir) {
+ if (!has_hands_) {
+ return;
+ }
+ if (dir == LEFT) {
+ int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 160, 135};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 0};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), 0};
+ Execute2(A, center_angle, 300, phase_diff, 5);
+ }
+ else if (dir == RIGHT) {
+ int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 45, 20};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, DEG2RAD(90)};
+ Execute2(A, center_angle, 300, phase_diff, 5);
+ }
+ else {
+ int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 160, 20};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 20};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(90)};
+ Execute2(A, center_angle, 300, phase_diff, 5);
+ }
+}
+
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 澶ч杞�
+//-- Parameters:
+//-- steps: 鍔ㄤ綔娆℃暟
+//-- period: 鍔ㄤ綔鍛ㄦ湡锛堟绉掞級
+//-- amplitude: 鎸崱骞呭害锛堝害锛�
+//---------------------------------------------------------
+void Otto::Windmill(float steps, int period, int amplitude) {
+ if (!has_hands_) {
+ return;
+ }
+
+ int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 90, 90};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, amplitude, amplitude};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(90)};
+ Execute2(A, center_angle, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 璧烽
+//-- Parameters:
+//-- steps: 鍔ㄤ綔娆℃暟
+//-- period: 鍔ㄤ綔鍛ㄦ湡锛堟绉掞級锛屾暟鍊艰秺灏忛�熷害瓒婂揩
+//-- amplitude: 鎸崱骞呭害锛堝害锛�
+//---------------------------------------------------------
+void Otto::Takeoff(float steps, int period, int amplitude) {
+ if (!has_hands_) {
+ return;
+ }
+
+ Home(true);
+
+ int center_angle[SERVO_COUNT] = {90, 90, 90, 90, 90, 90};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, amplitude, amplitude};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
+ Execute2(A, center_angle, period, phase_diff, steps);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鍋ヨ韩
+//-- Parameters:
+//-- steps: 鍔ㄤ綔娆℃暟
+//-- period: 鍔ㄤ綔鍛ㄦ湡锛堟绉掞級
+//-- amplitude: 鎸崱骞呭害锛堝害锛�
+//---------------------------------------------------------
+void Otto::Fitness(float steps, int period, int amplitude) {
+ if (!has_hands_) {
+ return;
+ }
+ int target[SERVO_COUNT] = {90, 90, 90, 0, 160, 135};
+ MoveServos(100, target);
+ target[LEFT_FOOT] = 20;
+ MoveServos(400, target);
+ vTaskDelay(pdMS_TO_TICKS(2000));
+
+ int C[SERVO_COUNT] = {90, 90, 20, 90, 160, 135};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 0, amplitude};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+ Execute2(A, C, period, phase_diff, steps);
+
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鎵撴嫑鍛�
+//-- Parameters:
+//-- dir: 鏂瑰悜 LEFT=宸︽墜, RIGHT=鍙虫墜
+//-- steps: 鍔ㄤ綔娆℃暟
+//---------------------------------------------------------
+void Otto::Greeting(int dir, float steps) {
+ if (!has_hands_) {
+ return;
+ }
+ if (dir == LEFT) {
+ int target[SERVO_COUNT] = {90, 90, 150, 150, 45, 135};
+ MoveServos(400, target);
+ int C[SERVO_COUNT] = {90, 90, 150, 150, 160, 135};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 0};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+ Execute2(A, C, 300, phase_diff, steps);
+ }
+ else if (dir == RIGHT) {
+ int target[SERVO_COUNT] = {90, 90, 30, 30, 45, 135};
+ MoveServos(400, target);
+ int C[SERVO_COUNT] = {90, 90, 30, 30, 45, 20};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+ Execute2(A, C, 300, phase_diff, steps);
+ }
+
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 瀹崇緸
+//-- Parameters:
+//-- dir: 鏂瑰悜 LEFT=宸︽墜, RIGHT=鍙虫墜
+//-- steps: 鍔ㄤ綔娆℃暟
+//---------------------------------------------------------
+void Otto::Shy(int dir, float steps) {
+ if (!has_hands_) {
+ return;
+ }
+
+ if (dir == LEFT) {
+ int target[SERVO_COUNT] = {90, 90, 150, 150, 45, 135};
+ MoveServos(400, target);
+ int C[SERVO_COUNT] = {90, 90, 150, 150, 45, 135};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 20, 20};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
+ Execute2(A, C, 300, phase_diff, steps);
+ }
+ else if (dir == RIGHT) {
+ int target[SERVO_COUNT] = {90, 90, 30, 30, 45, 135};
+ MoveServos(400, target);
+ int C[SERVO_COUNT] = {90, 90, 30, 30, 45, 135};
+ int A[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
+ double phase_diff[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
+ Execute2(A, C, 300, phase_diff, steps);
+ }
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 骞挎挱浣撴搷
+//---------------------------------------------------------
+void Otto::RadioCalisthenics() {
+ if (!has_hands_) {
+ return;
+ }
+
+ const int period = 1000;
+ const float steps = 8.0;
+
+ int C1[SERVO_COUNT] = {90, 90, 90, 90, 145, 45};
+ int A1[SERVO_COUNT] = {0, 0, 0, 0, 45, 45};
+ double phase_diff1[SERVO_COUNT] = {0, 0, 0, 0, DEG2RAD(90), DEG2RAD(-90)};
+ Execute2(A1, C1, period, phase_diff1, steps);
+
+ int C2[SERVO_COUNT] = {90, 90, 115, 65, 90, 90};
+ int A2[SERVO_COUNT] = {0, 0, 25, 25, 0, 0};
+ double phase_diff2[SERVO_COUNT] = {0, 0, DEG2RAD(90), DEG2RAD(-90), 0, 0};
+ Execute2(A2, C2, period, phase_diff2, steps);
+
+ int C3[SERVO_COUNT] = {90, 90, 130, 130, 90, 90};
+ int A3[SERVO_COUNT] = {0, 0, 0, 0, 20, 0};
+ double phase_diff3[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+ Execute2(A3, C3, period, phase_diff3, steps);
+
+ int C4[SERVO_COUNT] = {90, 90, 50, 50, 90, 90};
+ int A4[SERVO_COUNT] = {0, 0, 0, 0, 0, 20};
+ double phase_diff4[SERVO_COUNT] = {0, 0, 0, 0, 0, 0};
+ Execute2(A4, C4, period, phase_diff4, steps);
+}
+
+//---------------------------------------------------------
+//-- 鎵嬮儴鍔ㄤ綔: 鐖辩殑榄斿姏杞湀鍦�
+//---------------------------------------------------------
+void Otto::MagicCircle() {
+ if (!has_hands_) {
+ return;
+ }
+
+ int A[SERVO_COUNT] = {30, 30, 30, 30, 50, 50};
+ int O[SERVO_COUNT] = {0, 0, 5, -5, 0, 0};
+ double phase_diff[SERVO_COUNT] = {0, 0, DEG2RAD(-90), DEG2RAD(-90), DEG2RAD(-90) , DEG2RAD(90)};
+
+ Execute(A, O, 700, phase_diff, 40);
+}
+
+//---------------------------------------------------------
+//-- 灞曠ず鍔ㄤ綔锛氫覆鑱斿涓姩浣滃睍绀�
+//---------------------------------------------------------
+void Otto::Showcase() {
+ if (GetRestState() == true) {
+ SetRestState(false);
+ }
+
+ // 1. 寰�鍓嶈蛋3姝�
+ Walk(3, 1000, FORWARD, 50);
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ // 2. 鎸ユ尌鎵�
+ if (has_hands_) {
+ HandWave(LEFT);
+ vTaskDelay(pdMS_TO_TICKS(500));
+ }
+
+ // 3. 璺宠垶锛堜娇鐢ㄥ箍鎾綋鎿嶏級
+ if (has_hands_) {
+ RadioCalisthenics();
+ vTaskDelay(pdMS_TO_TICKS(500));
+ }
+
+ // 4. 澶┖姝�
+ Moonwalker(3, 900, 25, LEFT);
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ // 5. 鎽囨憜
+ Swing(3, 1000, 30);
+ vTaskDelay(pdMS_TO_TICKS(500));
+
+ // 6. 璧烽
+ if (has_hands_) {
+ Takeoff(5, 300, 40);
+ vTaskDelay(pdMS_TO_TICKS(500));
+ }
+
+ // 7. 鍋ヨ韩
+ if (has_hands_) {
+ Fitness(5, 1000, 25);
+ vTaskDelay(pdMS_TO_TICKS(500));
+ }
+
+ // 8. 寰�鍚庤蛋3姝�
+ Walk(3, 1000, BACKWARD, 50);
+}
+
+void Otto::EnableServoLimit(int diff_limit) {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].SetLimiter(diff_limit);
+ }
+ }
+}
+
+void Otto::DisableServoLimit() {
+ for (int i = 0; i < SERVO_COUNT; i++) {
+ if (servo_pins_[i] != -1) {
+ servo_[i].DisableLimiter();
+ }
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.h b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.h
new file mode 100644
index 0000000..69b944e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_movements.h
@@ -0,0 +1,117 @@
+#ifndef __OTTO_MOVEMENTS_H__
+#define __OTTO_MOVEMENTS_H__
+
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_timer.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "oscillator.h"
+
+//-- Constants
+#define FORWARD 1
+#define BACKWARD -1
+#define LEFT 1
+#define RIGHT -1
+#define BOTH 0
+#define SMALL 5
+#define MEDIUM 15
+#define BIG 30
+
+// -- Servo delta limit default. degree / sec
+#define SERVO_LIMIT_DEFAULT 240
+
+// -- Servo indexes for easy access
+#define LEFT_LEG 0
+#define RIGHT_LEG 1
+#define LEFT_FOOT 2
+#define RIGHT_FOOT 3
+#define LEFT_HAND 4
+#define RIGHT_HAND 5
+#define SERVO_COUNT 6
+
+class Otto {
+public:
+ Otto();
+ ~Otto();
+
+ //-- Otto initialization
+ void Init(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = -1,
+ int right_hand = -1);
+ //-- Attach & detach functions
+ void AttachServos();
+ void DetachServos();
+
+ //-- Oscillator Trims
+ void SetTrims(int left_leg, int right_leg, int left_foot, int right_foot, int left_hand = 0,
+ int right_hand = 0);
+
+ //-- Predetermined Motion Functions
+ void MoveServos(int time, int servo_target[]);
+ void MoveSingle(int position, int servo_number);
+ void OscillateServos(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float cycle);
+ void Execute2(int amplitude[SERVO_COUNT], int center_angle[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps);
+
+ //-- HOME = Otto at rest position
+ void Home(bool hands_down = true);
+ bool GetRestState();
+ void SetRestState(bool state);
+
+ //-- Predetermined Motion Functions
+ void Jump(float steps = 1, int period = 2000);
+
+ void Walk(float steps = 4, int period = 1000, int dir = FORWARD, int amount = 0);
+ void Turn(float steps = 4, int period = 2000, int dir = LEFT, int amount = 0);
+ void Bend(int steps = 1, int period = 1400, int dir = LEFT);
+ void ShakeLeg(int steps = 1, int period = 2000, int dir = RIGHT);
+ void Sit(); // 鍧愪笅
+
+ void UpDown(float steps = 1, int period = 1000, int height = 20);
+ void Swing(float steps = 1, int period = 1000, int height = 20);
+ void TiptoeSwing(float steps = 1, int period = 900, int height = 20);
+ void Jitter(float steps = 1, int period = 500, int height = 20);
+ void AscendingTurn(float steps = 1, int period = 900, int height = 20);
+
+ void Moonwalker(float steps = 1, int period = 900, int height = 20, int dir = LEFT);
+ void Crusaito(float steps = 1, int period = 900, int height = 20, int dir = FORWARD);
+ void Flapping(float steps = 1, int period = 1000, int height = 20, int dir = FORWARD);
+ void WhirlwindLeg(float steps = 1, int period = 300, int amplitude = 30);
+
+ // -- 鎵嬮儴鍔ㄤ綔
+ void HandsUp(int period = 1000, int dir = 0); // 鍙屾墜涓捐捣
+ void HandsDown(int period = 1000, int dir = 0); // 鍙屾墜鏀句笅
+ void HandWave(int dir = LEFT); // 鎸ユ墜
+ void Windmill(float steps = 10, int period = 500, int amplitude = 90); // 澶ч杞�
+ void Takeoff(float steps = 5, int period = 300, int amplitude = 40); // 璧烽
+ void Fitness(float steps = 5, int period = 1000, int amplitude = 25); // 鍋ヨ韩
+ void Greeting(int dir = LEFT, float steps = 5); // 鎵撴嫑鍛�
+ void Shy(int dir = LEFT, float steps = 5); // 瀹崇緸
+ void RadioCalisthenics(); // 骞挎挱浣撴搷
+ void MagicCircle(); // 鐖辩殑榄斿姏杞湀鍦�
+ void Showcase(); // 灞曠ず鍔ㄤ綔锛堜覆鑱斿涓姩浣滐級
+
+ // -- Servo limiter
+ void EnableServoLimit(int speed_limit_degree_per_sec = SERVO_LIMIT_DEFAULT);
+ void DisableServoLimit();
+
+private:
+ Oscillator servo_[SERVO_COUNT];
+
+ int servo_pins_[SERVO_COUNT];
+ int servo_trim_[SERVO_COUNT];
+
+ unsigned long final_time_;
+ unsigned long partial_time_;
+ float increment_[SERVO_COUNT];
+
+ bool is_otto_resting_;
+ bool has_hands_; // 鏄惁鏈夋墜閮ㄨ埖鏈�
+
+ void Execute(int amplitude[SERVO_COUNT], int offset[SERVO_COUNT], int period,
+ double phase_diff[SERVO_COUNT], float steps);
+
+};
+
+#endif // __OTTO_MOVEMENTS_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_robot.cc b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_robot.cc
new file mode 100644
index 0000000..754f609
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/otto_robot.cc
@@ -0,0 +1,417 @@
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#include "application.h"
+#include "button.h"
+#include "codecs/no_audio_codec.h"
+#include "config.h"
+#include "display/lcd_display.h"
+#include "esp_video.h"
+#include "lamp_controller.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "otto_emoji_display.h"
+#include "power_manager.h"
+#include "system_reset.h"
+#include "websocket_control_server.h"
+#include "wifi_board.h"
+
+#define TAG "OttoRobot"
+
+extern void InitializeOttoController(const HardwareConfig& hw_config);
+
+class OttoRobot : public WifiBoard {
+private:
+ LcdDisplay* display_;
+ PowerManager* power_manager_;
+ Button boot_button_;
+ WebSocketControlServer* ws_control_server_;
+ HardwareConfig hw_config_;
+ AudioCodec* audio_codec_;
+ i2c_master_bus_handle_t i2c_bus_;
+ EspVideo* camera_;
+ bool has_camera_;
+ OttoCameraType camera_type_;
+
+ bool DetectHardwareVersion() {
+ ledc_timer_config_t ledc_timer = {
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .duty_resolution = LEDC_TIMER_2_BIT,
+ .timer_num = LEDC_TIMER,
+ .freq_hz = CAMERA_XCLK_FREQ,
+ .clk_cfg = LEDC_AUTO_CLK,
+ };
+ esp_err_t ret = ledc_timer_config(&ledc_timer);
+ if (ret != ESP_OK) {
+ return false;
+ }
+
+ ledc_channel_config_t ledc_channel = {
+ .gpio_num = CAMERA_XCLK,
+ .speed_mode = LEDC_LOW_SPEED_MODE,
+ .channel = LEDC_CHANNEL,
+ .intr_type = LEDC_INTR_DISABLE,
+ .timer_sel = LEDC_TIMER,
+ .duty = 2,
+ .hpoint = 0,
+ };
+ ret = ledc_channel_config(&ledc_channel);
+ if (ret != ESP_OK) {
+ return false;
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(100));
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = CAMERA_VERSION_CONFIG.i2c_sda_pin,
+ .scl_io_num = CAMERA_VERSION_CONFIG.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,
+ },
+ };
+
+ ret = i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_);
+ if (ret != ESP_OK) {
+ ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL, 0);
+ return false;
+ }
+ const uint8_t camera_addresses[] = {0x30, 0x3C, 0x21, 0x60};
+ bool camera_found = false;
+ uint16_t detected_pid = 0;
+
+ for (size_t i = 0; i < sizeof(camera_addresses); i++) {
+ uint8_t addr = camera_addresses[i];
+ i2c_device_config_t dev_cfg = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = addr,
+ .scl_speed_hz = 100000,
+ };
+
+ i2c_master_dev_handle_t dev_handle;
+ ret = i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &dev_handle);
+ if (ret == ESP_OK) {
+ uint8_t data[2] = {0, 0};
+
+ uint8_t reg_addr_8bit = 0x0A;
+ ret = i2c_master_transmit_receive(dev_handle, ®_addr_8bit, 1, data, 2, 200);
+ if (ret == ESP_OK && (data[0] != 0 || data[1] != 0)) {
+ detected_pid = (data[0] << 8) | data[1];
+ ESP_LOGI(TAG, "妫�娴嬪埌鎽勫儚澶� (OV2640鏂瑰紡) PID=0x%04X (鍦板潃=0x%02X)",
+ detected_pid, addr);
+ camera_found = true;
+ i2c_master_bus_rm_device(dev_handle);
+ break;
+ }
+
+ uint8_t reg_addr_high[2] = {0x30, 0x0A};
+ uint8_t reg_addr_low[2] = {0x30, 0x0B};
+ uint8_t pid_high = 0, pid_low = 0;
+
+ ret = i2c_master_transmit_receive(dev_handle, reg_addr_high, 2, &pid_high, 1, 200);
+ if (ret == ESP_OK) {
+ ret =
+ i2c_master_transmit_receive(dev_handle, reg_addr_low, 2, &pid_low, 1, 200);
+ if (ret == ESP_OK) {
+ detected_pid = (pid_high << 8) | pid_low;
+ if (detected_pid != 0) {
+ ESP_LOGI(TAG, "妫�娴嬪埌鎽勫儚澶� (OV3660鏂瑰紡) PID=0x%04X (鍦板潃=0x%02X)",
+ detected_pid, addr);
+ camera_found = true;
+ i2c_master_bus_rm_device(dev_handle);
+ break;
+ }
+ }
+ }
+
+ i2c_master_bus_rm_device(dev_handle);
+ }
+ }
+
+ if (!camera_found) {
+ i2c_del_master_bus(i2c_bus_);
+ i2c_bus_ = nullptr;
+ ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL, 0);
+ camera_type_ = OTTO_CAMERA_NONE;
+ } else {
+ // 鏍规嵁 PID 鍒ゆ柇鎽勫儚澶寸被鍨�
+ if (detected_pid == OV2640_PID_1 || detected_pid == OV2640_PID_2) {
+ camera_type_ = OTTO_CAMERA_OV2640;
+ ESP_LOGI(TAG, "鎽勫儚澶寸被鍨�: OV2640 (PID=0x%04X)", detected_pid);
+ } else if (detected_pid == OV3660_PID) {
+ camera_type_ = OTTO_CAMERA_OV3660;
+ ESP_LOGI(TAG, "鎽勫儚澶寸被鍨�: OV3660 (PID=0x%04X)", detected_pid);
+ } else {
+ camera_type_ = OTTO_CAMERA_UNKNOWN;
+ ESP_LOGW(TAG, "鏈煡鎽勫儚澶寸被鍨嬶紝PID=0x%04X", detected_pid);
+ }
+ }
+ return camera_found;
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(hw_config_.power_charge_detect_pin,
+ hw_config_.power_adc_unit, hw_config_.power_adc_channel);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = hw_config_.display_mosi_pin;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = hw_config_.display_clk_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ 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 = hw_config_.display_cs_pin;
+ io_config.dc_gpio_num = hw_config_.display_dc_pin;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = hw_config_.display_rst_pin;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+
+ display_ = new OttoEmojiDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeOttoController() { ::InitializeOttoController(hw_config_); }
+
+public:
+ const HardwareConfig& GetHardwareConfig() const { return hw_config_; }
+
+ OttoCameraType GetCameraType() const { return camera_type_; }
+
+private:
+ void InitializeWebSocketControlServer() {
+ ws_control_server_ = new WebSocketControlServer();
+ if (!ws_control_server_->Start(8080)) {
+ delete ws_control_server_;
+ ws_control_server_ = nullptr;
+ }
+ }
+
+ void StartNetwork() override {
+ WifiBoard::StartNetwork();
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ InitializeWebSocketControlServer();
+ }
+
+ bool InitializeCamera() {
+ if (!has_camera_ || i2c_bus_ == nullptr) {
+ return false;
+ }
+
+ try {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io =
+ {
+ [0] = CAMERA_D0,
+ [1] = CAMERA_D1,
+ [2] = CAMERA_D2,
+ [3] = CAMERA_D3,
+ [4] = CAMERA_D4,
+ [5] = CAMERA_D5,
+ [6] = CAMERA_D6,
+ [7] = CAMERA_D7,
+ },
+ .vsync_io = CAMERA_VSYNC,
+ .de_io = CAMERA_HSYNC,
+ .pclk_io = CAMERA_PCLK,
+ .xclk_io = CAMERA_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+ .freq = 100000,
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAMERA_RESET,
+ .pwdn_pin = CAMERA_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = CAMERA_XCLK_FREQ,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+
+ // 鏍规嵁鎽勫儚澶寸被鍨嬭缃笉鍚岀殑缈昏浆鍙傛暟
+ switch (camera_type_) {
+ case OTTO_CAMERA_OV3660:
+ camera_->SetVFlip(true);
+ camera_->SetHMirror(true);
+ ESP_LOGI(TAG, "OV3660: 璁剧疆 VFlip=true, HMirror=true");
+ break;
+ case OTTO_CAMERA_OV2640:
+ default:
+ camera_->SetVFlip(true);
+ camera_->SetHMirror(false);
+ ESP_LOGI(TAG, "OV2640: 璁剧疆 VFlip=true, HMirror=false");
+ break;
+ }
+ return true;
+ } catch (...) {
+ camera_ = nullptr;
+ return false;
+ }
+ }
+
+ void InitializeAudioCodec() {
+ if (hw_config_.audio_use_simplex) {
+ audio_codec_ = new NoAudioCodecSimplex(
+ hw_config_.audio_input_sample_rate, hw_config_.audio_output_sample_rate,
+ hw_config_.audio_i2s_spk_gpio_bclk, hw_config_.audio_i2s_spk_gpio_lrck,
+ hw_config_.audio_i2s_spk_gpio_dout, hw_config_.audio_i2s_mic_gpio_sck,
+ hw_config_.audio_i2s_mic_gpio_ws, hw_config_.audio_i2s_mic_gpio_din);
+ } else {
+ audio_codec_ = new NoAudioCodecDuplex(
+ hw_config_.audio_input_sample_rate, hw_config_.audio_output_sample_rate,
+ hw_config_.audio_i2s_gpio_bclk, hw_config_.audio_i2s_gpio_ws,
+ hw_config_.audio_i2s_gpio_dout, hw_config_.audio_i2s_gpio_din);
+ }
+ }
+
+public:
+ OttoRobot()
+ : boot_button_(BOOT_BUTTON_GPIO),
+ audio_codec_(nullptr),
+ i2c_bus_(nullptr),
+ camera_(nullptr),
+ has_camera_(false),
+ camera_type_(OTTO_CAMERA_NONE) {
+#if OTTO_HARDWARE_VERSION == OTTO_VERSION_AUTO
+ // 鑷姩妫�娴嬬‖浠剁増鏈紙鍚屾椂妫�娴嬫憚鍍忓ご绫诲瀷锛�
+ has_camera_ = DetectHardwareVersion();
+ ESP_LOGI(TAG, "鑷姩妫�娴嬬‖浠剁増鏈�: %s", has_camera_ ? "鎽勫儚澶寸増" : "鏃犳憚鍍忓ご鐗�");
+#elif OTTO_HARDWARE_VERSION == OTTO_VERSION_CAMERA
+ // 寮哄埗浣跨敤鎽勫儚澶寸増鏈紝浣嗕粛妫�娴嬪叿浣撴憚鍍忓ご绫诲瀷
+ has_camera_ = DetectHardwareVersion();
+ if (!has_camera_) {
+ // 妫�娴嬪け璐ユ椂浠嶄娇鐢ㄦ憚鍍忓ご閰嶇疆锛屼絾涓嶇煡閬撳叿浣撶被鍨�
+ has_camera_ = true;
+ camera_type_ = OTTO_CAMERA_UNKNOWN;
+ ESP_LOGW(TAG, "寮哄埗浣跨敤鎽勫儚澶寸増鏈厤缃紝浣嗘湭鑳芥娴嬪埌鎽勫儚澶寸被鍨�");
+ // 鍒濆鍖� I2C 鎬荤嚎鐢ㄤ簬鎽勫儚澶�
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = CAMERA_VERSION_CONFIG.i2c_sda_pin,
+ .scl_io_num = CAMERA_VERSION_CONFIG.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,
+ },
+ };
+ i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_);
+ } else {
+ ESP_LOGI(TAG, "寮哄埗浣跨敤鎽勫儚澶寸増鏈厤缃�");
+ }
+#elif OTTO_HARDWARE_VERSION == OTTO_VERSION_NO_CAMERA
+ // 寮哄埗浣跨敤鏃犳憚鍍忓ご鐗堟湰
+ has_camera_ = false;
+ camera_type_ = OTTO_CAMERA_NONE;
+ ESP_LOGI(TAG, "寮哄埗浣跨敤鏃犳憚鍍忓ご鐗堟湰閰嶇疆");
+#else
+#error \
+ "OTTO_HARDWARE_VERSION 璁剧疆鏃犳晥锛岃浣跨敤 OTTO_VERSION_AUTO, OTTO_VERSION_CAMERA 鎴� OTTO_VERSION_NO_CAMERA"
+#endif
+
+ if (has_camera_)
+ hw_config_ = CAMERA_VERSION_CONFIG;
+ else
+ hw_config_ = NON_CAMERA_VERSION_CONFIG;
+
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializePowerManager();
+ InitializeAudioCodec();
+
+ if (has_camera_) {
+ if (!InitializeCamera()) {
+ has_camera_ = false;
+ }
+ }
+
+ InitializeOttoController();
+ ws_control_server_ = nullptr;
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override { return audio_codec_; }
+
+ virtual Display* GetDisplay() override { return display_; }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight* backlight = nullptr;
+ if (backlight == nullptr) {
+ backlight =
+ new PwmBacklight(hw_config_.display_backlight_pin, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ }
+ return backlight;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ charging = power_manager_->IsCharging();
+ discharging = !charging;
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual Camera* GetCamera() override { return has_camera_ ? camera_ : nullptr; }
+};
+
+DECLARE_BOARD(OttoRobot);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/power_manager.h
new file mode 100644
index 0000000..925319f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/power_manager.h
@@ -0,0 +1,151 @@
+#ifndef __POWER_MANAGER_H__
+#define __POWER_MANAGER_H__
+
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <esp_log.h>
+#include <esp_timer.h>
+
+class PowerManager {
+private:
+ // 鐢垫睜鐢甸噺鍖洪棿-鍒嗗帇鐢甸樆涓�2涓�100k
+ static constexpr struct {
+ uint16_t adc;
+ uint8_t level;
+ } BATTERY_LEVELS[] = {{2050, 0}, {2450, 100}};
+ static constexpr size_t BATTERY_LEVELS_COUNT = 2;
+ static constexpr size_t ADC_VALUES_COUNT = 10;
+
+ esp_timer_handle_t timer_handle_ = nullptr;
+ gpio_num_t charging_pin_;
+ adc_unit_t adc_unit_;
+ adc_channel_t adc_channel_;
+ uint16_t adc_values_[ADC_VALUES_COUNT];
+ size_t adc_values_index_ = 0;
+ size_t adc_values_count_ = 0;
+ uint8_t battery_level_ = 100;
+ bool is_charging_ = false;
+ inline static bool battery_update_paused_ = false; // 闈欐�佹爣蹇楋細鏄惁鏆傚仠鐢甸噺鏇存柊
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // 濡傛灉鐢甸噺鏇存柊琚殏鍋滐紙鍔ㄤ綔杩涜涓級锛屽垯璺宠繃鏇存柊
+ if (battery_update_paused_) {
+ return;
+ }
+
+ ReadBatteryAdcData();
+
+ if (charging_pin_ == GPIO_NUM_NC) {
+ is_charging_ = false;
+ } else {
+ is_charging_ = gpio_get_level(charging_pin_) == 0;
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value));
+
+ adc_values_[adc_values_index_] = adc_value;
+ adc_values_index_ = (adc_values_index_ + 1) % ADC_VALUES_COUNT;
+ if (adc_values_count_ < ADC_VALUES_COUNT) {
+ adc_values_count_++;
+ }
+
+ uint32_t average_adc = 0;
+ for (size_t i = 0; i < adc_values_count_; i++) {
+ average_adc += adc_values_[i];
+ }
+ average_adc /= adc_values_count_;
+
+ CalculateBatteryLevel(average_adc);
+
+ // ESP_LOGI("PowerManager", "ADC鍊�: %d 骞冲潎鍊�: %ld 鐢甸噺: %u%%", adc_value, average_adc,
+ // battery_level_);
+ }
+
+ void CalculateBatteryLevel(uint32_t average_adc) {
+ if (average_adc <= BATTERY_LEVELS[0].adc) {
+ battery_level_ = 0;
+ } else if (average_adc >= BATTERY_LEVELS[BATTERY_LEVELS_COUNT - 1].adc) {
+ battery_level_ = 100;
+ } else {
+ float ratio = static_cast<float>(average_adc - BATTERY_LEVELS[0].adc) /
+ (BATTERY_LEVELS[1].adc - BATTERY_LEVELS[0].adc);
+ battery_level_ = ratio * 100;
+ }
+ }
+
+public:
+ PowerManager(gpio_num_t charging_pin, adc_unit_t adc_unit = ADC_UNIT_2,
+ adc_channel_t adc_channel = ADC_CHANNEL_3)
+ : charging_pin_(charging_pin), adc_unit_(adc_unit), adc_channel_(adc_channel) {
+
+ if (charging_pin_ != GPIO_NUM_NC) {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+ ESP_LOGI("PowerManager", "鍏呯數妫�娴嬪紩鑴氶厤缃畬鎴�: GPIO%d", charging_pin_);
+ } else {
+ ESP_LOGI("PowerManager", "鍏呯數妫�娴嬪紩鑴氭湭閰嶇疆锛屼笉杩涜鍏呯數鐘舵�佹娴�");
+ }
+
+ esp_timer_create_args_t timer_args = {
+ .callback =
+ [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000)); // 1绉�
+
+ InitializeAdc();
+ }
+
+ void InitializeAdc() {
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = adc_unit_,
+ .clk_src = ADC_RTC_CLK_SRC_DEFAULT,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+
+ ESP_ERROR_CHECK(
+ adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() { return is_charging_; }
+
+ uint8_t GetBatteryLevel() { return battery_level_; }
+
+ // 鏆傚仠/鎭㈠鐢甸噺鏇存柊锛堢敤浜庡姩浣滄墽琛屾椂灞忚斀鏇存柊锛�
+ static void PauseBatteryUpdate() { battery_update_paused_ = true; }
+ static void ResumeBatteryUpdate() { battery_update_paused_ = false; }
+};
+#endif // __POWER_MANAGER_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.cc b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.cc
new file mode 100644
index 0000000..caa495c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.cc
@@ -0,0 +1,192 @@
+#include "websocket_control_server.h"
+#include "mcp_server.h"
+#include <esp_log.h>
+#include <esp_http_server.h>
+#include <sys/param.h>
+#include <cstring>
+#include <cstdlib>
+#include <map>
+
+static const char* TAG = "WSControl";
+
+WebSocketControlServer* WebSocketControlServer::instance_ = nullptr;
+
+WebSocketControlServer::WebSocketControlServer() : server_handle_(nullptr) {
+ instance_ = this;
+}
+
+WebSocketControlServer::~WebSocketControlServer() {
+ Stop();
+ instance_ = nullptr;
+}
+
+esp_err_t WebSocketControlServer::ws_handler(httpd_req_t *req) {
+ if (instance_ == nullptr) {
+ return ESP_FAIL;
+ }
+
+ if (req->method == HTTP_GET) {
+ ESP_LOGI(TAG, "Handshake done, the new connection was opened");
+ instance_->AddClient(req);
+ return ESP_OK;
+ }
+
+ httpd_ws_frame_t ws_pkt;
+ uint8_t *buf = NULL;
+ memset(&ws_pkt, 0, sizeof(httpd_ws_frame_t));
+ ws_pkt.type = HTTPD_WS_TYPE_TEXT;
+
+ /* Set max_len = 0 to get the frame len */
+ esp_err_t ret = httpd_ws_recv_frame(req, &ws_pkt, 0);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "httpd_ws_recv_frame failed to get frame len with %d", ret);
+ return ret;
+ }
+ ESP_LOGI(TAG, "frame len is %d", ws_pkt.len);
+
+ if (ws_pkt.len) {
+ /* ws_pkt.len + 1 is for NULL termination as we are expecting a string */
+ buf = (uint8_t*)calloc(1, ws_pkt.len + 1);
+ if (buf == NULL) {
+ ESP_LOGE(TAG, "Failed to calloc memory for buf");
+ return ESP_ERR_NO_MEM;
+ }
+ ws_pkt.payload = buf;
+ /* Set max_len = ws_pkt.len to get the frame payload */
+ ret = httpd_ws_recv_frame(req, &ws_pkt, ws_pkt.len);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "httpd_ws_recv_frame failed with %d", ret);
+ free(buf);
+ return ret;
+ }
+ ESP_LOGI(TAG, "Got packet with message: %s", ws_pkt.payload);
+ }
+
+ ESP_LOGI(TAG, "Packet type: %d", ws_pkt.type);
+
+ if (ws_pkt.type == HTTPD_WS_TYPE_CLOSE) {
+ ESP_LOGI(TAG, "WebSocket close frame received");
+ instance_->RemoveClient(req);
+ free(buf);
+ return ESP_OK;
+ }
+
+ if (ws_pkt.type == HTTPD_WS_TYPE_TEXT) {
+ if (ws_pkt.len > 0 && buf != nullptr) {
+ buf[ws_pkt.len] = '\0';
+ instance_->HandleMessage(req, (const char*)buf, ws_pkt.len);
+ }
+ } else {
+ ESP_LOGW(TAG, "Unsupported frame type: %d", ws_pkt.type);
+ }
+
+ free(buf);
+ return ESP_OK;
+}
+
+bool WebSocketControlServer::Start(int port) {
+ httpd_config_t config = HTTPD_DEFAULT_CONFIG();
+ config.server_port = port;
+ config.max_open_sockets = 7;
+ config.ctrl_port = 32769;
+
+ httpd_uri_t ws_uri = {
+ .uri = "/ws",
+ .method = HTTP_GET,
+ .handler = ws_handler,
+ .user_ctx = nullptr,
+ .is_websocket = true
+ };
+
+ if (httpd_start(&server_handle_, &config) == ESP_OK) {
+ httpd_register_uri_handler(server_handle_, &ws_uri);
+ ESP_LOGI(TAG, "WebSocket server started on port %d", port);
+ return true;
+ }
+
+ ESP_LOGE(TAG, "Failed to start WebSocket server");
+ return false;
+}
+
+void WebSocketControlServer::Stop() {
+ if (server_handle_) {
+ httpd_stop(server_handle_);
+ server_handle_ = nullptr;
+ clients_.clear();
+ ESP_LOGI(TAG, "WebSocket server stopped");
+ }
+}
+
+void WebSocketControlServer::HandleMessage(httpd_req_t *req, const char* data, size_t len) {
+ if (data == nullptr || len == 0) {
+ ESP_LOGE(TAG, "Invalid message: data is null or len is 0");
+ return;
+ }
+
+ if (len > 4096) {
+ ESP_LOGE(TAG, "Message too long: %zu bytes", len);
+ return;
+ }
+
+ char* temp_buf = (char*)malloc(len + 1);
+ if (temp_buf == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory");
+ return;
+ }
+ memcpy(temp_buf, data, len);
+ temp_buf[len] = '\0';
+
+ cJSON* root = cJSON_Parse(temp_buf);
+ free(temp_buf);
+
+ if (root == nullptr) {
+ ESP_LOGE(TAG, "Failed to parse JSON");
+ return;
+ }
+
+ // 鏀寔涓ょ鏍煎紡锛�
+ // 1. 瀹屾暣鏍煎紡锛歿"type":"mcp","payload":{...}}
+ // 2. 绠�鍖栨牸寮忥細鐩存帴鏄疢CP payload瀵硅薄
+
+ cJSON* payload = nullptr;
+ cJSON* type = cJSON_GetObjectItem(root, "type");
+
+ if (type && cJSON_IsString(type) && strcmp(type->valuestring, "mcp") == 0) {
+ payload = cJSON_GetObjectItem(root, "payload");
+ if (payload != nullptr) {
+ cJSON_DetachItemViaPointer(root, payload);
+ McpServer::GetInstance().ParseMessage(payload);
+ cJSON_Delete(payload);
+ }
+ } else {
+ payload = cJSON_Duplicate(root, 1);
+ if (payload != nullptr) {
+ McpServer::GetInstance().ParseMessage(payload);
+ cJSON_Delete(payload);
+ }
+ }
+
+ if (payload == nullptr) {
+ ESP_LOGE(TAG, "Invalid message format or failed to parse");
+ }
+
+ cJSON_Delete(root);
+}
+
+void WebSocketControlServer::AddClient(httpd_req_t *req) {
+ int sock_fd = httpd_req_to_sockfd(req);
+ if (clients_.find(sock_fd) == clients_.end()) {
+ clients_[sock_fd] = req;
+ ESP_LOGI(TAG, "Client connected: %d (total: %zu)", sock_fd, clients_.size());
+ }
+}
+
+void WebSocketControlServer::RemoveClient(httpd_req_t *req) {
+ int sock_fd = httpd_req_to_sockfd(req);
+ clients_.erase(sock_fd);
+ ESP_LOGI(TAG, "Client disconnected: %d (total: %zu)", sock_fd, clients_.size());
+}
+
+size_t WebSocketControlServer::GetClientCount() const {
+ return clients_.size();
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.h b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.h
new file mode 100644
index 0000000..f8e5e41
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/otto-robot/websocket_control_server.h
@@ -0,0 +1,33 @@
+#ifndef WEBSOCKET_CONTROL_SERVER_H
+#define WEBSOCKET_CONTROL_SERVER_H
+
+#include <esp_http_server.h>
+#include <cJSON.h>
+#include <string>
+#include <map>
+
+class WebSocketControlServer {
+public:
+ WebSocketControlServer();
+ ~WebSocketControlServer();
+
+ bool Start(int port = 8080);
+
+ void Stop();
+
+ size_t GetClientCount() const;
+
+private:
+ httpd_handle_t server_handle_;
+ std::map<int, httpd_req_t*> clients_;
+
+ static esp_err_t ws_handler(httpd_req_t *req);
+
+ void HandleMessage(httpd_req_t *req, const char* data, size_t len);
+ void AddClient(httpd_req_t *req);
+ void RemoveClient(httpd_req_t *req);
+ static WebSocketControlServer* instance_;
+};
+
+#endif // WEBSOCKET_CONTROL_SERVER_H
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README.md b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README.md
new file mode 100644
index 0000000..aa91010
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README.md
@@ -0,0 +1,52 @@
+# 缂栬瘧鍛戒护
+
+## 涓�閿紪璇�
+
+```bash
+python scripts/release.py sensecap-watcher
+```
+
+## 鎵嬪姩閰嶇疆缂栬瘧
+
+```bash
+idf.py set-target esp32s3
+```
+
+**閰嶇疆**
+
+```bash
+idf.py menuconfig
+```
+
+閫夋嫨鏉垮瓙
+
+```
+Xiaozhi Assistant -> Board Type -> SenseCAP Watcher
+```
+
+watcher 涓竴浜涢澶栫殑閰嶇疆椤瑰涓嬶紝闇�瑕佸湪menuconfig 涓�夋嫨.
+
+```
+CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER=y
+CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/32m.csv"
+CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y
+CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n
+CONFIG_IDF_EXPERIMENTAL_FEATURES=y
+```
+
+## 缂栬瘧鐑у叆
+
+```bash
+idf.py -DBOARD_NAME=sensecap-watcher build flash
+```
+
+娉ㄦ剰: 濡傛灉褰撳墠璁惧鍑鸿揣涔嬪墠鏄疭enseCAP 鍥轰欢(闈炲皬鏅虹増鏈�),璇风壒鍒皬蹇冨鐞嗛棯瀛樺浐浠跺垎鍖哄湴鍧�锛屼互閬垮厤閿欒鎿﹂櫎 SenseCAP Watcher 鐨勮嚜韬澶囦俊鎭紙EUI 绛夛級锛屽惁鍒欒澶囧嵆浣挎仮澶嶆垚SenseCAP鍥轰欢涔熸棤娉曟纭繛鎺ュ埌 SenseCraft 鏈嶅姟鍣紒鎵�浠ュ湪鍒峰啓鍥轰欢涔嬪墠锛岃鍔″繀璁板綍璁惧鐨勭浉鍏冲繀瑕佷俊鎭紝浠ョ‘淇濇湁鎭㈠鐨勬柟娉曪紒
+
+鎮ㄥ彲浠ヤ娇鐢ㄤ互涓嬪懡浠ゅ浠界敓浜т俊鎭�
+
+```bash
+# firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
+esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 204800 nvsfactory.bin
+
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README_en.md b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README_en.md
new file mode 100644
index 0000000..27338a8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/README_en.md
@@ -0,0 +1,53 @@
+# Build Instructions
+
+## One-click Build
+
+```bash
+python scripts/release.py sensecap-watcher -c config_en.json
+```
+
+## Manual Configuration and Build
+
+```bash
+idf.py set-target esp32s3
+```
+
+**Configuration**
+
+```bash
+idf.py menuconfig
+```
+
+Select the board:
+
+```
+Xiaozhi Assistant -> Board Type -> SenseCAP Watcher
+```
+
+There are some additional configuration options for the watcher. Please select them in menuconfig:
+
+```
+CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER=y
+CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/32m.csv"
+CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y
+CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n
+CONFIG_IDF_EXPERIMENTAL_FEATURES=y
+CONFIG_LANGUAGE_EN_US=y
+CONFIG_SR_WN_WN9_JARVIS_TTS=y
+```
+
+## Build and Flash
+
+```bash
+idf.py -DBOARD_NAME=sensecap-watcher-en build flash
+```
+
+Note: If your device was previously shipped with the SenseCAP firmware (not the Xiaozhi version), please be very careful with the flash partition addresses to avoid accidentally erasing the device information (such as EUI) of the SenseCAP Watcher. Otherwise, even if you restore the SenseCAP firmware, the device may not be able to connect to the SenseCraft server correctly! Therefore, before flashing the firmware, be sure to record the necessary device information to ensure you have a way to recover it!
+
+You can use the following command to back up the factory information:
+
+```bash
+# Firstly backup the factory information partition which contains the credentials for connecting the SenseCraft server
+esptool.py --chip esp32s3 --baud 2000000 --before default_reset --after hard_reset --no-stub read_flash 0x9000 204800 nvsfactory.bin
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.h b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.h
new file mode 100644
index 0000000..8d1f5dc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.h
@@ -0,0 +1,152 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include "driver/spi_common.h"
+#include "esp_io_expander.h"
+
+// SSCMA Client Configuration
+#define CONFIG_SSCMA_EVENT_QUEUE_SIZE 1
+#define CONFIG_SSCMA_TX_BUFFER_SIZE 8192
+#define CONFIG_SSCMA_RX_BUFFER_SIZE 98304
+
+// SSCMA Client Process Task
+#define CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE 10240
+#define CONFIG_SSCMA_PROCESS_TASK_PRIORITY 5
+#define CONFIG_SSCMA_PROCESS_TASK_AFFINITY_CPU1 1
+#define CONFIG_SSCMA_PROCESS_TASK_AFFINITY 1
+#define CONFIG_SSCMA_PROCESS_TASK_STACK_ALLOC_EXTERNAL 1
+
+// SSCMA Client Monitor Task
+#define CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE 10240
+#define CONFIG_SSCMA_MONITOR_TASK_PRIORITY 4
+#define CONFIG_SSCMA_MONITOR_TASK_AFFINITY_CPU1 1
+#define CONFIG_SSCMA_MONITOR_TASK_AFFINITY 1
+#define CONFIG_SSCMA_MONITOR_TASK_STACK_ALLOC_EXTERNAL 1
+#define CONFIG_SSCMA_ALLOC_SMALL_SHORTTERM_MEM_EXTERNALLY 1
+
+/* General I2C */
+#define BSP_GENERAL_I2C_NUM (I2C_NUM_0)
+#define BSP_GENERAL_I2C_SDA (GPIO_NUM_47)
+#define BSP_GENERAL_I2C_SCL (GPIO_NUM_48)
+
+/* Audio */
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE false
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
+
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7243E_ADDR (0x14)
+
+
+
+#define BUILTIN_LED_GPIO GPIO_NUM_40
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+/* Expander */
+#define BSP_IO_EXPANDER_INT (GPIO_NUM_2)
+#define DRV_IO_EXP_INPUT_MASK (0x20ff) // P0.0 ~ P0.7 | P1.3
+#define DRV_IO_EXP_OUTPUT_MASK (0xDf00) // P1.0 ~ P1.7 & ~P1.3
+
+/* Expander IO PIN */
+#define BSP_PWR_CHRG_DET (IO_EXPANDER_PIN_NUM_0)
+#define BSP_PWR_STDBY_DET (IO_EXPANDER_PIN_NUM_1)
+#define BSP_PWR_VBUS_IN_DET (IO_EXPANDER_PIN_NUM_2)
+#define BSP_PWR_SDCARD (IO_EXPANDER_PIN_NUM_8)
+#define BSP_PWR_LCD (IO_EXPANDER_PIN_NUM_9)
+#define BSP_PWR_SYSTEM (IO_EXPANDER_PIN_NUM_10)
+#define BSP_PWR_AI_CHIP (IO_EXPANDER_PIN_NUM_11)
+#define BSP_PWR_CODEC_PA (IO_EXPANDER_PIN_NUM_12)
+#define BSP_PWR_BAT_DET (IO_EXPANDER_PIN_NUM_13)
+#define BSP_PWR_GROVE (IO_EXPANDER_PIN_NUM_14)
+#define BSP_PWR_BAT_ADC (IO_EXPANDER_PIN_NUM_15)
+
+#define BSP_PWR_START_UP (BSP_PWR_SDCARD | BSP_PWR_LCD | BSP_PWR_SYSTEM | BSP_PWR_AI_CHIP | BSP_PWR_CODEC_PA | BSP_PWR_GROVE | BSP_PWR_BAT_ADC)
+
+#define BSP_KNOB_BTN (IO_EXPANDER_PIN_NUM_3)
+#define BSP_KNOB_A_PIN GPIO_NUM_41
+#define BSP_KNOB_B_PIN GPIO_NUM_42
+
+/* SPI */
+#define BSP_SPI2_HOST_SCLK (GPIO_NUM_4)
+#define BSP_SPI2_HOST_MOSI (GPIO_NUM_5)
+#define BSP_SPI2_HOST_MISO (GPIO_NUM_6)
+
+/* SD Card */
+#define BSP_SD_SPI_NUM (SPI2_HOST)
+#define BSP_SD_SPI_CS (GPIO_NUM_46)
+#define BSP_SD_GPIO_DET (IO_EXPANDER_PIN_NUM_4)
+
+/* QSPI */
+#define BSP_SPI3_HOST_PCLK (GPIO_NUM_7)
+#define BSP_SPI3_HOST_DATA0 (GPIO_NUM_9)
+#define BSP_SPI3_HOST_DATA1 (GPIO_NUM_1)
+#define BSP_SPI3_HOST_DATA2 (GPIO_NUM_14)
+#define BSP_SPI3_HOST_DATA3 (GPIO_NUM_13)
+
+/* LCD */
+#define BSP_LCD_SPI_NUM (SPI3_HOST)
+#define BSP_LCD_SPI_CS (GPIO_NUM_45)
+#define BSP_LCD_GPIO_RST (GPIO_NUM_NC)
+#define BSP_LCD_GPIO_DC (GPIO_NUM_1)
+
+#define DISPLAY_WIDTH 412
+#define DISPLAY_HEIGHT 412
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_8
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+/* Touch */
+#define BSP_TOUCH_I2C_NUM (1)
+#define BSP_TOUCH_GPIO_INT (IO_EXPANDER_PIN_NUM_5)
+#define BSP_TOUCH_I2C_SDA (GPIO_NUM_39)
+#define BSP_TOUCH_I2C_SCL (GPIO_NUM_38)
+#define BSP_TOUCH_I2C_CLK (400000)
+
+/* Settings */
+#define DRV_LCD_PIXEL_CLK_HZ (40 * 1000 * 1000)
+#define DRV_LCD_CMD_BITS (32)
+#define DRV_LCD_PARAM_BITS (8)
+#define DRV_LCD_RGB_ELEMENT_ORDER (LCD_RGB_ELEMENT_ORDER_RGB)
+#define DRV_LCD_BITS_PER_PIXEL (16)
+
+#define CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV 16
+
+/* ADC */
+#define BSP_BAT_ADC_CHAN (ADC_CHANNEL_2) // GPIO3
+#define BSP_BAT_ADC_ATTEN (ADC_ATTEN_DB_2_5) // 0 ~ 1100 mV
+#define BSP_BAT_VOL_RATIO ((62 + 20) / 20)
+
+/* Himax */
+#define BSP_SSCMA_CLIENT_RST (IO_EXPANDER_PIN_NUM_7)
+#define BSP_SSCMA_CLIENT_RST_USE_EXPANDER (true)
+
+#define BSP_SSCMA_CLIENT_SPI_NUM (SPI2_HOST)
+#define BSP_SSCMA_CLIENT_SPI_CS (GPIO_NUM_21)
+#define BSP_SSCMA_CLIENT_SPI_SYNC (IO_EXPANDER_PIN_NUM_6)
+#define BSP_SSCMA_CLIENT_SPI_SYNC_USE_EXPANDER (true)
+#define BSP_SSCMA_CLIENT_SPI_CLK (12 * 1000 * 1000)
+
+#define BSP_SSCMA_FLASHER_UART_NUM (UART_NUM_1)
+#define BSP_SSCMA_FLASHER_UART_TX (GPIO_NUM_17)
+#define BSP_SSCMA_FLASHER_UART_RX (GPIO_NUM_18)
+#define BSP_SSCMA_FLASHER_UART_BAUD_RATE (921600)
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.json b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.json
new file mode 100644
index 0000000..177fad7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config.json
@@ -0,0 +1,16 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "sensecap-watcher",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/32m.csv\"",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n",
+ "CONFIG_IDF_EXPERIMENTAL_FEATURES=y",
+ "CONFIG_FREERTOS_HZ=1000"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config_en.json b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config_en.json
new file mode 100644
index 0000000..0435fa3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/config_en.json
@@ -0,0 +1,20 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "sensecap-watcher-en",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_32MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/32m.csv\"",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=n",
+ "CONFIG_IDF_EXPERIMENTAL_FEATURES=y",
+ "CONFIG_FREERTOS_HZ=1000",
+ "CONFIG_LANGUAGE_EN_US=y",
+ "CONFIG_SR_WN_WN9_JARVIS_TTS=y",
+ "CONFIG_SR_WN_WN9_SOPHIA_TTS=y",
+ "CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=n"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.cc b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.cc
new file mode 100644
index 0000000..4c70983
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.cc
@@ -0,0 +1,216 @@
+#include "sensecap_audio_codec.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/i2s_tdm.h>
+
+static const char TAG[] = "SensecapAudioCodec";
+
+SensecapAudioCodec::SensecapAudioCodec(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 es7243e_addr, bool input_reference) {
+ duplex_ = true; // 鏄惁鍙屽伐
+ input_reference_ = input_reference; // 鏄惁浣跨敤鍙傝�冭緭鍏ワ紝瀹炵幇鍥炲0娑堥櫎
+ input_channels_ = input_reference_ ? 2 : 1; // 杈撳叆閫氶亾鏁�
+ input_sample_rate_ = input_sample_rate;
+ output_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_t)0,
+ .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 = es7243e_addr << 1;
+ in_ctrl_if_ = audio_codec_new_i2c_ctrl(&i2c_cfg);
+ assert(in_ctrl_if_ != NULL);
+
+ es7243e_codec_cfg_t es7243e_cfg = {};
+ es7243e_cfg.ctrl_if = in_ctrl_if_;
+ in_codec_if_ = es7243e_codec_new(&es7243e_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_codec_set_disable_when_closed(output_dev_, false);
+ esp_codec_set_disable_when_closed(input_dev_, false);
+
+ ESP_LOGI(TAG, "SensecapAudioDevice initialized");
+}
+
+SensecapAudioCodec::~SensecapAudioCodec() {
+ 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 SensecapAudioCodec::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_MONO,
+ .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));
+
+ std_cfg.slot_cfg.slot_mask = I2S_STD_SLOT_RIGHT;
+ 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 SensecapAudioCodec::SetOutputVolume(int volume) {
+ ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, volume));
+ AudioCodec::SetOutputVolume(volume);
+}
+
+void SensecapAudioCodec::EnableInput(bool enable) {
+ if (enable == input_enabled_) {
+ return;
+ }
+ if (enable) {
+ esp_codec_dev_sample_info_t fs = {
+ .bits_per_sample = 16,
+ .channel = 2,
+ .channel_mask = ESP_CODEC_DEV_MAKE_CHANNEL_MASK(1),
+ .sample_rate = (uint32_t)output_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_, 27.0));
+ } else {
+ ESP_ERROR_CHECK(esp_codec_dev_close(input_dev_));
+ }
+ AudioCodec::EnableInput(enable);
+}
+
+void SensecapAudioCodec::EnableOutput(bool enable) {
+ 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 SensecapAudioCodec::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 SensecapAudioCodec::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;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.h b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.h
new file mode 100644
index 0000000..794a4d7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_audio_codec.h
@@ -0,0 +1,38 @@
+#ifndef _SENSECAP_AUDIO_CODEC_H
+#define _SENSECAP_AUDIO_CODEC_H
+
+#include "audio_codec.h"
+
+#include <esp_codec_dev.h>
+#include <esp_codec_dev_defaults.h>
+
+class SensecapAudioCodec : 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;
+ gpio_num_t pa_pin_ = GPIO_NUM_NC;
+
+ 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:
+ SensecapAudioCodec(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 ~SensecapAudioCodec();
+
+ virtual void SetOutputVolume(int volume) override;
+ virtual void EnableInput(bool enable) override;
+ virtual void EnableOutput(bool enable) override;
+};
+
+#endif // _SENSECAP_AUDIO_CODEC_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_watcher.cc b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_watcher.cc
new file mode 100644
index 0000000..f069591
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sensecap_watcher.cc
@@ -0,0 +1,668 @@
+#include "display/lv_display.h"
+#include "misc/lv_event.h"
+#include "wifi_board.h"
+#include "sensecap_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "knob.h"
+#include "config.h"
+#include "led/single_led.h"
+#include "power_save_timer.h"
+#include "sscma_camera.h"
+#include "lvgl_theme.h"
+
+#include <esp_log.h>
+#include <esp_check.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_spd2010.h>
+#include <esp_adc/adc_oneshot.h>
+#include <driver/spi_master.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <iot_button.h>
+#include <iot_knob.h>
+#include <esp_io_expander_tca95xx_16bit.h>
+#include <esp_sleep.h>
+#include <esp_console.h>
+#include <esp_mac.h>
+#include <nvs_flash.h>
+#include <esp_app_desc.h>
+
+#include "assets/lang_config.h"
+
+#define TAG "sensecap_watcher"
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+ public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+
+ lv_obj_set_size(top_bar_, LV_HOR_RES, text_font->line_height);
+ lv_obj_set_style_layout(top_bar_, LV_LAYOUT_NONE, 0);
+ lv_obj_set_style_pad_top(top_bar_, 10, 0);
+ lv_obj_set_style_pad_bottom(top_bar_, 1, 0);
+
+ lv_obj_set_size(status_bar_, LV_HOR_RES, text_font->line_height);
+ lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0);
+ lv_obj_set_style_pad_top(status_bar_, 10, 0);
+ lv_obj_set_style_pad_bottom(status_bar_, 1, 0);
+ lv_obj_set_y(status_bar_, text_font->line_height);
+ lv_obj_add_flag(status_bar_, LV_OBJ_FLAG_IGNORE_LAYOUT);
+
+ // Reparent mute and battery labels to top_bar_ to allow absolute positioning
+ lv_obj_set_parent(mute_label_, top_bar_);
+ lv_obj_set_parent(battery_label_, top_bar_);
+ lv_obj_set_style_margin_left(battery_label_, 0, 0);
+
+ // 閽堝鍦嗗舰灞忓箷璋冩暣浣嶇疆
+ // network mute battery //
+ // status //
+ lv_obj_align(network_label_, LV_ALIGN_TOP_MID, -1.5 * icon_font->line_height, 0);
+ lv_obj_align(mute_label_, LV_ALIGN_TOP_MID, 1.0 * icon_font->line_height, 0);
+ lv_obj_align(battery_label_, LV_ALIGN_TOP_MID, 2.5 * icon_font->line_height, 0);
+
+ lv_obj_align(status_label_, LV_ALIGN_BOTTOM_MID, 0, 0);
+ lv_obj_set_flex_grow(status_label_, 0);
+ lv_obj_set_width(status_label_, LV_HOR_RES * 0.75);
+ lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+
+ lv_obj_align(notification_label_, LV_ALIGN_BOTTOM_MID, 0, 0);
+ lv_obj_set_width(notification_label_, LV_HOR_RES * 0.75);
+ lv_label_set_long_mode(notification_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+
+ lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -20);
+ lv_obj_set_style_bg_color(low_battery_popup_, lv_color_hex(0xFF0000), 0);
+ lv_obj_set_width(low_battery_label_, LV_HOR_RES * 0.75);
+ lv_label_set_long_mode(low_battery_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+
+ // 閽堝鍦嗗舰灞忓箷璋冩暣搴曢儴瀵硅瘽妗嗕綅缃紝閬垮厤琚渾瑙掗伄鎸�
+ lv_obj_set_style_pad_bottom(bottom_bar_, 30, 0);
+ lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.75); // 闄愬埗瀹藉害锛岄伩鍏嶆枃瀛楄创杈�
+ }
+};
+
+class SensecapWatcher : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ std::unique_ptr<Knob> knob_;
+ esp_io_expander_handle_t io_exp_handle;
+ button_handle_t btns;
+ PowerSaveTimer* power_save_timer_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ uint32_t long_press_cnt_;
+ button_driver_t* btn_driver_ = nullptr;
+ static SensecapWatcher* instance_;
+ SscmaCamera* camera_ = nullptr;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ bool is_charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
+ if (is_charging) {
+ ESP_LOGI(TAG, "charging");
+ GetBacklight()->SetBrightness(0);
+ } else {
+ IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
+ }
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = BSP_GENERAL_I2C_SDA,
+ .scl_io_num = BSP_GENERAL_I2C_SCL,
+ .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, &i2c_bus_));
+
+ // pulldown for lcd i2c
+ const gpio_config_t io_config = {
+ .pin_bit_mask = (1ULL << BSP_TOUCH_I2C_SDA) | (1ULL << BSP_TOUCH_I2C_SCL) | (1ULL << BSP_SPI3_HOST_PCLK) | (1ULL << BSP_SPI3_HOST_DATA0) | (1ULL << BSP_SPI3_HOST_DATA1)
+ | (1ULL << BSP_SPI3_HOST_DATA2) | (1ULL << BSP_SPI3_HOST_DATA3) | (1ULL << BSP_LCD_SPI_CS) | (1UL << DISPLAY_BACKLIGHT_PIN),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ gpio_config(&io_config);
+
+ gpio_set_level(BSP_TOUCH_I2C_SDA, 0);
+ gpio_set_level(BSP_TOUCH_I2C_SCL, 0);
+
+ gpio_set_level(BSP_LCD_SPI_CS, 0);
+ gpio_set_level(DISPLAY_BACKLIGHT_PIN, 0);
+ gpio_set_level(BSP_SPI3_HOST_PCLK, 0);
+ gpio_set_level(BSP_SPI3_HOST_DATA0, 0);
+ gpio_set_level(BSP_SPI3_HOST_DATA1, 0);
+ gpio_set_level(BSP_SPI3_HOST_DATA2, 0);
+ gpio_set_level(BSP_SPI3_HOST_DATA3, 0);
+
+ }
+
+ esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) {
+ return esp_io_expander_set_level(io_exp_handle, pin_mask, level);
+ }
+
+ uint8_t IoExpanderGetLevel(uint16_t pin_mask) {
+ uint32_t pin_val = 0;
+ esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ pin_mask &= DRV_IO_EXP_INPUT_MASK;
+ return (uint8_t)((pin_val & pin_mask) ? 1 : 0);
+ }
+
+ void InitializeExpander() {
+ esp_err_t ret = ESP_OK;
+ esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_001, &io_exp_handle);
+
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT);
+ ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT);
+ ret |= esp_io_expander_set_level(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, 0);
+ ret |= esp_io_expander_set_level(io_exp_handle, BSP_PWR_SYSTEM, 1);
+ vTaskDelay(100 / portTICK_PERIOD_MS);
+ ret |= esp_io_expander_set_level(io_exp_handle, BSP_PWR_START_UP, 1);
+ vTaskDelay(50 / portTICK_PERIOD_MS);
+
+ uint32_t pin_val = 0;
+ ret |= esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val);
+ ESP_LOGI(TAG, "IO expander initialized: %x", DRV_IO_EXP_OUTPUT_MASK | (uint16_t)pin_val);
+
+ assert(ret == ESP_OK);
+ }
+
+ void OnKnobRotate(bool clockwise) {
+ auto codec = GetAudioCodec();
+ int current_volume = codec->output_volume();
+ int new_volume = current_volume + (clockwise ? -5 : 5);
+
+ // 纭繚闊抽噺鍦ㄦ湁鏁堣寖鍥村唴
+ if (new_volume > 100) {
+ new_volume = 100;
+ ESP_LOGW(TAG, "Volume reached maximum limit: %d", new_volume);
+ } else if (new_volume < 0) {
+ new_volume = 0;
+ ESP_LOGW(TAG, "Volume reached minimum limit: %d", new_volume);
+ }
+
+ codec->SetOutputVolume(new_volume);
+ ESP_LOGI(TAG, "Volume changed from %d to %d", current_volume, new_volume);
+
+ // 鏄剧ず閫氱煡鍓嶆鏌ュ疄闄呭彉鍖�
+ if (new_volume != codec->output_volume()) {
+ ESP_LOGE(TAG, "Failed to set volume! Expected:%d Actual:%d",
+ new_volume, codec->output_volume());
+ }
+ GetDisplay()->ShowNotification(std::string(Lang::Strings::VOLUME) + ": "+std::to_string(codec->output_volume()));
+ power_save_timer_->WakeUp();
+ }
+
+ void InitializeKnob() {
+ knob_ = std::make_unique<Knob>(BSP_KNOB_A_PIN, BSP_KNOB_B_PIN);
+ knob_->OnRotate([this](bool clockwise) {
+ ESP_LOGD(TAG, "Knob rotation detected. Clockwise:%s", clockwise ? "true" : "false");
+ OnKnobRotate(clockwise);
+ });
+ ESP_LOGI(TAG, "Knob initialized with pins A:%d B:%d", BSP_KNOB_A_PIN, BSP_KNOB_B_PIN);
+ }
+
+ void InitializeButton() {
+ // 璁剧疆闈欐�佸疄渚嬫寚閽�
+ instance_ = this;
+
+ // watcher 鏄�氳繃闀挎寜婊氳疆杩涜寮�鏈虹殑, 闇�瑕佺瓑寰呮粴杞噴鏀�, 鍚﹀垯鐢ㄦ埛寮�鏈烘澗鎵嬫椂鍙兘浼氳瑙︽垚鍗曞嚮
+ ESP_LOGI(TAG, "waiting for knob button release");
+ while(IoExpanderGetLevel(BSP_KNOB_BTN) == 0) {
+ vTaskDelay(pdMS_TO_TICKS(50));
+ }
+
+ button_config_t btn_config = {
+ .long_press_time = 2000,
+ .short_press_time = 0
+ };
+ btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ btn_driver_->enable_power_save = false;
+ btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !instance_->IoExpanderGetLevel(BSP_KNOB_BTN);
+ };
+
+ ESP_ERROR_CHECK(iot_button_create(&btn_config, btn_driver_, &btns));
+
+ iot_button_register_cb(btns, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<SensecapWatcher*>(usr_data);
+ self->power_save_timer_->WakeUp();
+
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }, this);
+
+ iot_button_register_cb(btns, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<SensecapWatcher*>(usr_data);
+ bool is_charging = (self->IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
+ self->long_press_cnt_ = 0;
+ if (is_charging) {
+ ESP_LOGI(TAG, "charging");
+ } else {
+ self->IoExpanderSetLevel(BSP_PWR_LCD, 0);
+ self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
+ }
+ }, this);
+
+ iot_button_register_cb(btns, BUTTON_LONG_PRESS_HOLD, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<SensecapWatcher*>(usr_data);
+ self->long_press_cnt_++; // 姣忛殧20ms鍔犱竴
+ // 闀挎寜10s 鎭㈠鍑哄巶璁剧疆: 2+0.02*400 = 10
+ if (self->long_press_cnt_ > 400) {
+ ESP_LOGI(TAG, "Factory reset");
+ nvs_flash_erase();
+ esp_restart();
+ }
+ }, this);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize SSCMA SPI bus");
+ spi_bus_config_t spi_cfg = {0};
+
+ spi_cfg.mosi_io_num = BSP_SPI2_HOST_MOSI;
+ spi_cfg.miso_io_num = BSP_SPI2_HOST_MISO;
+ spi_cfg.sclk_io_num = BSP_SPI2_HOST_SCLK;
+ spi_cfg.quadwp_io_num = -1;
+ spi_cfg.quadhd_io_num = -1;
+ spi_cfg.isr_cpu_id = ESP_INTR_CPU_AFFINITY_1;
+ spi_cfg.max_transfer_sz = 4095;
+
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &spi_cfg, SPI_DMA_CH_AUTO));
+
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+
+ spi_bus_config_t qspi_cfg = {0};
+ qspi_cfg.sclk_io_num = BSP_SPI3_HOST_PCLK;
+ qspi_cfg.data0_io_num = BSP_SPI3_HOST_DATA0;
+ qspi_cfg.data1_io_num = BSP_SPI3_HOST_DATA1;
+ qspi_cfg.data2_io_num = BSP_SPI3_HOST_DATA2;
+ qspi_cfg.data3_io_num = BSP_SPI3_HOST_DATA3;
+ qspi_cfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * DRV_LCD_BITS_PER_PIXEL / 8 / CONFIG_BSP_LCD_SPI_DMA_SIZE_DIV;
+
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &qspi_cfg, SPI_DMA_CH_AUTO));
+ }
+
+ void Initializespd2010Display() {
+ ESP_LOGI(TAG, "Install panel IO");
+ const esp_lcd_panel_io_spi_config_t io_config = {
+ .cs_gpio_num = BSP_LCD_SPI_CS,
+ .dc_gpio_num = -1,
+ .spi_mode = 3,
+ .pclk_hz = DRV_LCD_PIXEL_CLK_HZ,
+ .trans_queue_depth = 2,
+ .lcd_cmd_bits = DRV_LCD_CMD_BITS,
+ .lcd_param_bits = DRV_LCD_PARAM_BITS,
+ .flags = {
+ .quad_mode = true,
+ },
+ };
+ spd2010_vendor_config_t vendor_config = {
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)BSP_LCD_SPI_NUM, &io_config, &panel_io_);
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = BSP_LCD_GPIO_RST, // Shared with Touch reset
+ .rgb_ele_order = DRV_LCD_RGB_ELEMENT_ORDER,
+ .bits_per_pixel = DRV_LCD_BITS_PER_PIXEL,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_spd2010(panel_io_, &panel_config, &panel_);
+
+ esp_lcd_panel_reset(panel_);
+ esp_lcd_panel_init(panel_);
+ esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel_, true);
+
+ display_ = new CustomLcdDisplay(panel_io_, panel_,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+
+ // 浣挎瘡娆″埛鏂扮殑璧峰鍒楁暟绱㈠紩鏄�4鐨勫�嶆暟涓斿垪鏁版�绘暟鏄�4鐨勫�嶆暟锛屼互婊¤冻SPD2010鐨勮姹�
+ lv_display_add_event_cb(lv_display_get_default(), [](lv_event_t *e) {
+ lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+ // round the start of area down to the nearest 4N number
+ area->x1 = (x1 >> 2) << 2;
+ // round the end of area up to the nearest 4M+3 number
+ area->x2 = ((x2 >> 2) << 2) + 3;
+ }, LV_EVENT_INVALIDATE_AREA, NULL);
+
+ }
+
+ uint16_t BatterygetVoltage(void) {
+ static bool initialized = false;
+ static adc_oneshot_unit_handle_t adc_handle;
+ static adc_cali_handle_t cali_handle = NULL;
+ if (!initialized) {
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ };
+ adc_oneshot_new_unit(&init_config, &adc_handle);
+
+ adc_oneshot_chan_cfg_t ch_config = {
+ .atten = BSP_BAT_ADC_ATTEN,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
+ };
+ adc_oneshot_config_channel(adc_handle, BSP_BAT_ADC_CHAN, &ch_config);
+
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = ADC_UNIT_1,
+ .chan = BSP_BAT_ADC_CHAN,
+ .atten = BSP_BAT_ADC_ATTEN,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
+ };
+ if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) {
+ initialized = true;
+ }
+ }
+ if (initialized) {
+ int raw_value = 0;
+ int voltage = 0; // mV
+ adc_oneshot_read(adc_handle, BSP_BAT_ADC_CHAN, &raw_value);
+ adc_cali_raw_to_voltage(cali_handle, raw_value, &voltage);
+ voltage = voltage * 82 / 20;
+ // ESP_LOGI(TAG, "voltage: %dmV", voltage);
+ return (uint16_t)voltage;
+ }
+ return 0;
+ }
+
+ uint8_t BatterygetPercent(bool print = false) {
+ int voltage = 0;
+ for (uint8_t i = 0; i < 10; i++) {
+ voltage += BatterygetVoltage();
+ }
+ voltage /= 10;
+ int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000;
+ percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent;
+ if (print) {
+ printf("voltage: %dmV, percentage: %d%%\r\n", voltage, percent);
+ }
+ return (uint8_t)percent;
+ }
+
+ void InitializeCmd() {
+ esp_console_repl_t *repl = NULL;
+ esp_console_repl_config_t repl_config = ESP_CONSOLE_REPL_CONFIG_DEFAULT();
+ repl_config.max_cmdline_length = 1024;
+ repl_config.prompt = "SenseCAP>";
+
+ const esp_console_cmd_t cmd1 = {
+ .command = "reboot",
+ .help = "reboot the device",
+ .hint = nullptr,
+ .func = [](int argc, char** argv) -> int {
+ esp_restart();
+ return 0;
+ },
+ .argtable = nullptr
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd1));
+
+ const esp_console_cmd_t cmd2 = {
+ .command = "shutdown",
+ .help = "shutdown the device",
+ .hint = nullptr,
+ .func = NULL,
+ .argtable = NULL,
+ .func_w_context = [](void *context,int argc, char** argv) -> int {
+ auto self = static_cast<SensecapWatcher*>(context);
+ self->GetBacklight()->SetBrightness(0);
+ self->IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
+ return 0;
+ },
+ .context =this
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd2));
+
+ const esp_console_cmd_t cmd3 = {
+ .command = "battery",
+ .help = "get battery percent",
+ .hint = NULL,
+ .func = NULL,
+ .argtable = NULL,
+ .func_w_context = [](void *context,int argc, char** argv) -> int {
+ auto self = static_cast<SensecapWatcher*>(context);
+ self->BatterygetPercent(true);
+ return 0;
+ },
+ .context =this
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd3));
+
+ const esp_console_cmd_t cmd4 = {
+ .command = "factory_reset",
+ .help = "factory reset and reboot the device",
+ .hint = NULL,
+ .func = NULL,
+ .argtable = NULL,
+ .func_w_context = [](void *context,int argc, char** argv) -> int {
+ nvs_flash_erase();
+ esp_restart();
+ return 0;
+ },
+ .context =this
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd4));
+
+ const esp_console_cmd_t cmd5 = {
+ .command = "read_mac",
+ .help = "Read mac address",
+ .hint = NULL,
+ .func = NULL,
+ .argtable = NULL,
+ .func_w_context = [](void *context,int argc, char** argv) -> int {
+ uint8_t mac[6];
+ esp_read_mac(mac, ESP_MAC_WIFI_STA);
+ printf("wifi_sta_mac: " MACSTR "\n", MAC2STR(mac));
+ esp_read_mac(mac, ESP_MAC_WIFI_SOFTAP);
+ printf("wifi_softap_mac: " MACSTR "\n", MAC2STR(mac));
+ esp_read_mac(mac, ESP_MAC_BT);
+ printf("bt_mac: " MACSTR "\n", MAC2STR(mac));
+ return 0;
+ },
+ .context =this
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd5));
+
+ const esp_console_cmd_t cmd6 = {
+ .command = "version",
+ .help = "Read version info",
+ .hint = NULL,
+ .func = NULL,
+ .argtable = NULL,
+ .func_w_context = [](void *context,int argc, char** argv) -> int {
+ auto self = static_cast<SensecapWatcher*>(context);
+ auto app_desc = esp_app_get_description();
+ const char* region = "UNKNOWN";
+ #if defined(CONFIG_LANGUAGE_ZH_CN)
+ region = "CN";
+ #elif defined(CONFIG_LANGUAGE_EN_US)
+ region = "US";
+ #elif defined(CONFIG_LANGUAGE_JA_JP)
+ region = "JP";
+ #elif defined(CONFIG_LANGUAGE_ES_ES)
+ region = "ES";
+ #elif defined(CONFIG_LANGUAGE_DE_DE)
+ region = "DE";
+ #elif defined(CONFIG_LANGUAGE_FR_FR)
+ region = "FR";
+ #elif defined(CONFIG_LANGUAGE_IT_IT)
+ region = "IT";
+ #elif defined(CONFIG_LANGUAGE_PT_PT)
+ region = "PT";
+ #elif defined(CONFIG_LANGUAGE_RU_RU)
+ region = "RU";
+ #elif defined(CONFIG_LANGUAGE_KO_KR)
+ region = "KR";
+ #endif
+ printf("{\"type\":0,\"name\":\"VER?\",\"code\":0,\"data\":{\"software\":\"%s\",\"hardware\":\"watcher xiaozhi agent\",\"camera\":%d,\"region\":\"%s\"}}\n",
+ app_desc->version,
+ self->GetCamera() == nullptr ? 0 : 1,
+ region);
+ return 0;
+ },
+ .context =this
+ };
+ ESP_ERROR_CHECK(esp_console_cmd_register(&cmd6));
+
+ esp_console_dev_uart_config_t hw_config = ESP_CONSOLE_DEV_UART_CONFIG_DEFAULT();
+ ESP_ERROR_CHECK(esp_console_new_repl_uart(&hw_config, &repl_config, &repl));
+ ESP_ERROR_CHECK(esp_console_start_repl(repl));
+ }
+
+ void InitializeCamera() {
+
+ ESP_LOGI(TAG, "Initialize Camera");
+
+ // !!!NOTE: SD Card use same SPI bus as sscma client, so we need to disable SD card CS pin first
+ const gpio_config_t io_config = {
+ .pin_bit_mask = (1ULL << BSP_SD_SPI_CS),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ esp_err_t ret = gpio_config(&io_config);
+ if (ret != ESP_OK)
+ return;
+
+ gpio_set_level(BSP_SD_SPI_CS, 1);
+
+ camera_ = new SscmaCamera(io_exp_handle);
+ }
+
+public:
+ SensecapWatcher() {
+ ESP_LOGI(TAG, "Initialize Sensecap Watcher");
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeExpander();
+ InitializeCmd(); //宸ュ巶鐢熶骇娴嬭瘯浣跨敤
+ InitializeButton();
+ InitializeKnob();
+ Initializespd2010Display();
+ GetBacklight()->RestoreBrightness(); // 瀵逛簬涓嶅甫鎽勫儚澶寸殑鐗堟湰锛孖nitializeCamera闇�瑕�3s, 鎵�浠ュ厛鎭㈠鑳屽厜浜害
+ InitializeCamera();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static SensecapAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7243E_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ // 鏍规嵁 https://github.com/Seeed-Studio/OSHW-SenseCAP-Watcher/blob/main/Hardware/SenseCAP_Watcher_v1.0_SCH.pdf
+ // RGB LED鍨嬪彿涓� ws2813 mini, 杩炴帴鍦℅PIO 40锛屼緵鐢电數鍘� 3.3v, 娌℃湁杩炴帴 BIN 鍙屼俊鍙风嚎
+ // 鍙互鐩存帴鍏煎SingleLED閲囩敤鐨剋s2812
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = (IoExpanderGetLevel(BSP_PWR_VBUS_IN_DET) == 0);
+ discharging = !charging;
+ level = (int)BatterygetPercent(false);
+
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ if (level <= 1 && discharging) {
+ ESP_LOGI(TAG, "Battery level is low, shutting down");
+ IoExpanderSetLevel(BSP_PWR_SYSTEM, 0);
+ }
+ return true;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(SensecapWatcher);
+
+// 瀹氫箟闈欐�佹垚鍛樺彉閲�
+SensecapWatcher* SensecapWatcher::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.cc b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.cc
new file mode 100644
index 0000000..918275e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.cc
@@ -0,0 +1,743 @@
+#include "sscma_camera.h"
+#include "mcp_server.h"
+#include "lvgl_display.h"
+#include "lvgl_image.h"
+#include "board.h"
+#include "system_info.h"
+#include "config.h"
+#include "settings.h"
+
+#include <esp_log.h>
+#include <esp_heap_caps.h>
+#include <cstring>
+#include "application.h"
+#include "sscma_client_commands.h"
+
+#define TAG "SscmaCamera"
+
+#define IMG_JPEG_BUF_SIZE 48 * 1024
+
+static bool __himax_keepalive_check(sscma_client_handle_t client)
+{
+ esp_err_t ret = ESP_OK;
+ sscma_client_reply_t reply = {0};
+ int retry = 3;
+ while(retry--) {
+ ret = sscma_client_request(client, CMD_PREFIX CMD_AT_ID CMD_QUERY CMD_SUFFIX, &reply, true, pdMS_TO_TICKS(2000));
+ if (reply.payload != NULL) {
+ sscma_client_reply_clear(&reply);
+ }
+ if( ret != ESP_OK ) {
+ ESP_LOGE(TAG, "Himax keepalive check failed: %d", ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ } else {
+ return true;
+ }
+ }
+ return false;
+}
+
+SscmaCamera::SscmaCamera(esp_io_expander_handle_t io_exp_handle) {
+ sscma_client_io_spi_config_t spi_io_config = {0};
+ spi_io_config.sync_gpio_num = BSP_SSCMA_CLIENT_SPI_SYNC;
+ spi_io_config.cs_gpio_num = BSP_SSCMA_CLIENT_SPI_CS;
+ spi_io_config.pclk_hz = BSP_SSCMA_CLIENT_SPI_CLK;
+ spi_io_config.spi_mode = 0;
+ spi_io_config.wait_delay = 10; //涓や釜transfer涔嬮棿鑷冲皯寤舵椂4ms,浣嗗綋鍓� FREERTOS_HZ=100, 寤舵椂绮惧害鍙兘杈惧埌10ms,
+ spi_io_config.user_ctx = NULL;
+ spi_io_config.io_expander = io_exp_handle;
+ spi_io_config.flags.sync_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER;
+
+ sscma_client_new_io_spi_bus((sscma_client_spi_bus_handle_t)BSP_SSCMA_CLIENT_SPI_NUM, &spi_io_config, &sscma_client_io_handle_);
+
+ sscma_client_config_t sscma_client_config = SSCMA_CLIENT_CONFIG_DEFAULT();
+ sscma_client_config.event_queue_size = CONFIG_SSCMA_EVENT_QUEUE_SIZE;
+ sscma_client_config.tx_buffer_size = CONFIG_SSCMA_TX_BUFFER_SIZE;
+ sscma_client_config.rx_buffer_size = CONFIG_SSCMA_RX_BUFFER_SIZE;
+ sscma_client_config.process_task_stack = CONFIG_SSCMA_PROCESS_TASK_STACK_SIZE;
+ sscma_client_config.process_task_affinity = CONFIG_SSCMA_PROCESS_TASK_AFFINITY;
+ sscma_client_config.process_task_priority = CONFIG_SSCMA_PROCESS_TASK_PRIORITY;
+ sscma_client_config.monitor_task_stack = CONFIG_SSCMA_MONITOR_TASK_STACK_SIZE;
+ sscma_client_config.monitor_task_affinity = CONFIG_SSCMA_MONITOR_TASK_AFFINITY;
+ sscma_client_config.monitor_task_priority = CONFIG_SSCMA_MONITOR_TASK_PRIORITY;
+ sscma_client_config.reset_gpio_num = BSP_SSCMA_CLIENT_RST;
+ sscma_client_config.io_expander = io_exp_handle;
+ sscma_client_config.flags.reset_use_expander = BSP_SSCMA_CLIENT_RST_USE_EXPANDER;
+
+ sscma_client_new(sscma_client_io_handle_, &sscma_client_config, &sscma_client_handle_);
+
+ sscma_data_queue_ = xQueueCreate(1, sizeof(SscmaData));
+
+ sscma_client_callback_t callback = {0};
+
+ detection_state = SscmaCamera::IDLE;
+ state_start_time = 0;
+ need_start_cooldown = false;
+ callback.on_event = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) {
+ SscmaCamera* self = static_cast<SscmaCamera*>(user_ctx);
+ if (!self) return;
+
+ char *img = NULL;
+ int img_size = 0;
+ int box_count = 0;
+ sscma_client_box_t *boxes = NULL;
+ int class_count = 0;
+ sscma_client_class_t *classes = NULL;
+ int point_count = 0;
+ sscma_client_point_t *points = NULL;
+ int model_type = 0;
+ int obj_cnt = 0;
+
+ int width = 0, height = 0;
+ cJSON *data = cJSON_GetObjectItem(reply->payload, "data");
+ if (data != NULL && cJSON_IsObject(data)) {
+ cJSON *resolution = cJSON_GetObjectItem(data, "resolution");
+ if (data != NULL && cJSON_IsArray(resolution) && cJSON_GetArraySize(resolution) == 2) {
+ width = cJSON_GetArrayItem(resolution, 0)->valueint;
+ height = cJSON_GetArrayItem(resolution, 1)->valueint;
+ }
+ }
+
+ switch ((width+height)) {
+ case (416+416):
+ {
+ bool is_object_detected = false;
+ bool is_need_wake = false;
+
+ // 瀹氭湡鏇存柊妫�娴嬮厤缃弬鏁帮紝閬垮厤棰戠箒NVS璁块棶
+ int64_t cur_tm = esp_timer_get_time();
+
+ // 灏濊瘯鑾峰彇妫�娴嬫鏁版嵁锛堢洰鏍囨娴嬫ā鍨嬶級
+ if (sscma_utils_fetch_boxes_from_reply(reply, &boxes, &box_count) == ESP_OK && box_count > 0) {
+ for (int i = 0; i < box_count; i++) {
+ ESP_LOGI(TAG, "[box %d]: x=%d, y=%d, w=%d, h=%d, score=%d, target=%d", i, \
+ boxes[i].x, boxes[i].y, boxes[i].w, boxes[i].h, boxes[i].score, boxes[i].target);
+ if (boxes[i].target == self->detect_target && boxes[i].score > self->detect_threshold) {
+ is_object_detected = true;
+ model_type = 0;
+ obj_cnt++;
+ break;
+ }
+ }
+ free(boxes);
+ } else if (sscma_utils_fetch_classes_from_reply(reply, &classes, &class_count) == ESP_OK && class_count > 0) {
+ // 灏濊瘯鑾峰彇鍒嗙被鏁版嵁锛堝垎绫绘ā鍨嬶級
+ for (int i = 0; i < class_count; i++) {
+ ESP_LOGI(TAG, "[class %d]: target=%d, score=%d", i,
+ classes[i].target, classes[i].score);
+ if (classes[i].target == self->detect_target && classes[i].score > self->detect_threshold) {
+ is_object_detected = true;
+ model_type = 1;
+ obj_cnt++;
+ }
+ }
+ free(classes);
+ } else if (sscma_utils_fetch_points_from_reply(reply, &points, &point_count) == ESP_OK && point_count > 0) {
+ // 灏濊瘯鑾峰彇鍏抽敭鐐规暟鎹紙濮挎�佷及璁℃ā鍨嬶級
+ for (int i = 0; i < point_count; i++) {
+ ESP_LOGI(TAG, "[point %d]: x=%d, y=%d, z=%d, score=%d, target=%d", i,
+ points[i].x, points[i].y, points[i].z, points[i].score, points[i].target);
+ if (points[i].target == self->detect_target && points[i].score > self->detect_threshold) {
+ is_object_detected = true;
+ model_type = 2;
+ obj_cnt++;
+ }
+ }
+ free(points);
+ }
+
+ // 濡傛灉闇�瑕佸紑濮嬪喎鍗存湡锛岀幇鍦ㄥ紑濮嬭鏃�
+ if (self->need_start_cooldown) { // 鍥炶皟鏆傚仠锛屾爣蹇椾繚鎸侊紝绛夊緟鍥炶皟鎭㈠鍚庡紑濮嬭鏃�
+ self->state_start_time = cur_tm;
+ self->need_start_cooldown = false;
+ ESP_LOGI(TAG, "Starting cooldown timer");
+ }
+
+ // 鐘舵�佹満椹卞姩鐨勬娴嬮�昏緫 - 鍙湪浜哄憳鍑虹幇鏃惰Е鍙�
+ switch (self->detection_state) {
+ case SscmaCamera::IDLE:
+ if (is_object_detected) {
+ // 浜哄憳鍑虹幇锛屽紑濮嬮獙璇侊紙杩欐槸浠庢棤鍒版湁鐨勮浆鎹級
+ self->detection_state = SscmaCamera::VALIDATING;
+ self->state_start_time = cur_tm; // 璁板綍鐗╀綋鍑虹幇鏃堕棿
+ self->last_detected_time = cur_tm; // 鍒濆鍖栨渶鍚庢娴嬫椂闂�
+ ESP_LOGI(TAG, "object appeared, starting validation");
+ }
+ break;
+
+ case SscmaCamera::VALIDATING:
+ if (is_object_detected) {
+ // 鏇存柊鏈�鍚庢娴嬪埌鐨勬椂闂�
+ self->last_detected_time = cur_tm;
+ // 妫�鏌ユ槸鍚﹂獙璇佽冻澶熸椂闂�
+ if ((cur_tm - self->state_start_time) >= (self->detect_duration_sec * 1000000)) {
+ is_need_wake = true;
+ }
+ } else {
+ // 楠岃瘉鏈熼棿浜哄憳绂诲紑锛屾鏌ュ幓鎶栧姩鏃堕棿
+ if (self->last_detected_time > 0 &&
+ (cur_tm - self->last_detected_time) >= self->detect_debounce_sec * 1000000LL) {
+ // 鍘绘姈鍔ㄦ椂闂村凡杩囷紝纭浜哄憳宸茬寮�锛屽洖鍒扮┖闂�
+ self->detection_state = SscmaCamera::IDLE;
+ self->last_detected_time = 0;
+ ESP_LOGI(TAG, "object left during validation (debounced), back to idle");
+ }
+ }
+ break;
+
+ case SscmaCamera::COOLDOWN:
+ // 鍐峰嵈鏈燂紝闇�瑕佹弧瓒充袱涓潯浠讹細1)object绂诲紑 2)杩囦簡15绉�
+ if (!is_object_detected &&
+ (cur_tm - self->state_start_time) >= (self->detect_invoke_interval_sec * 1000000LL)) {
+ // object绂诲紑涓斿喎鍗存椂闂村埌锛屽洖鍒扮┖闂茬姸鎬�
+ self->detection_state = SscmaCamera::IDLE;
+ ESP_LOGI(TAG, "Cooldown complete and object left, back to idle - ready for next appearance");
+ }
+ // 鍏朵粬鎯呭喌缁х画淇濇寔鍐峰嵈鐘舵��
+ break;
+ }
+
+
+ if( is_need_wake ) {
+ ESP_LOGI(TAG, "Validation complete, triggering conversation (type=%d, res=%dx%d)",
+ self->detect_target, width, height);
+
+ // 瑙﹀彂瀵硅瘽
+ std::string wake_word;
+ if ( model_type == 0 ) {
+ std::string cached_target_name = "object";
+ if( self->model != NULL && self->model->classes[self->detect_target] != NULL ) {
+ cached_target_name = self->model->classes[self->detect_target];
+ }
+ wake_word = "<detect>" + std::to_string(obj_cnt) + " " + cached_target_name + " detected </detect>";
+ } else if ( model_type == 1 ) {
+ std::string cached_target_name = "object";
+ if( self->model != NULL && self->model->classes[self->detect_target] != NULL ) {
+ cached_target_name = self->model->classes[self->detect_target];
+ }
+ wake_word = "<detect>" + std::to_string(obj_cnt) + " " + cached_target_name + " detected </detect>";
+ } else if ( model_type == 2 ) {
+ std::string cached_target_name = "object";
+ if( self->model != NULL && self->model->classes[self->detect_target] != NULL ) {
+ cached_target_name = self->model->classes[self->detect_target];
+ }
+ wake_word = "<detect>" + std::to_string(obj_cnt) + " " + cached_target_name + " detected </detect>";
+ }
+ printf("wake_word:%s\n", wake_word.c_str());
+ Application::GetInstance().WakeWordInvoke(wake_word);
+
+ // 杩涘叆鍐峰嵈鐘舵�侊紝鏍囪闇�瑕佸紑濮嬪喎鍗存湡锛涘涓嬪彉閲忓皢鍦ㄤ細璇濈粨鏉熷悗琚娇鐢紝绛夊緟鍥炶皟鎭㈠鍚庡紑濮嬭鏃�
+ self->detection_state = SscmaCamera::COOLDOWN;
+ self->need_start_cooldown = true;
+ }
+ }
+ break;
+ case (640+480):
+
+ if (sscma_utils_fetch_image_from_reply(reply, &img, &img_size) == ESP_OK)
+ {
+ ESP_LOGI(TAG, "image_size: %d\n", img_size);
+ // 灏嗘暟鎹�氳繃闃熷垪鍙戦�佸嚭鍘�
+ SscmaData data;
+ data.img = (uint8_t*)img;
+ data.len = img_size;
+
+ // 娓呯┖闃熷垪锛屼繚璇佸彧淇濆瓨鏈�鏂扮殑鏁版嵁
+ SscmaData dummy;
+ while (xQueueReceive(self->sscma_data_queue_, &dummy, 0) == pdPASS) {
+ if (dummy.img) {
+ heap_caps_free(dummy.img);
+ }
+ }
+ xQueueSend(self->sscma_data_queue_, &data, 0);
+ // 娉ㄦ剰锛歩mg 鐨勯噴鏀剧敱鎺ユ敹鏂硅礋璐�
+ }
+ break;
+ default:
+ ESP_LOGI(TAG, "unknown resolution");
+ break;
+ }
+ };
+ callback.on_connect = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) {
+ ESP_LOGI(TAG, "SSCMA client connected");
+ SscmaCamera* self = static_cast<SscmaCamera*>(user_ctx);
+ if (self) {
+ self->sscma_restarted_ = true;
+ }
+ };
+
+ callback.on_log = [](sscma_client_handle_t client, const sscma_client_reply_t *reply, void *user_ctx) {
+ ESP_LOGI(TAG, "log: %s\n", reply->data);
+ };
+
+ sscma_client_register_callback(sscma_client_handle_, &callback, this);
+
+ sscma_client_init(sscma_client_handle_);
+
+ ESP_LOGI(TAG, "SSCMA client initialized");
+ // 璁剧疆鍒嗚鲸鐜�
+ // 3 = 640x480
+ if (sscma_client_set_sensor(sscma_client_handle_, 1, 3, true)) {
+ ESP_LOGE(TAG, "Failed to set sensor");
+ sscma_client_del(sscma_client_handle_);
+ sscma_client_handle_ = NULL;
+ return;
+ }
+
+ // 鑾峰彇璁惧淇℃伅
+ sscma_client_info_t *info;
+ if (sscma_client_get_info(sscma_client_handle_, &info, true) == ESP_OK) {
+ ESP_LOGI(TAG, "Device Info - ID: %s, Name: %s",
+ info->id ? info->id : "NULL",
+ info->name ? info->name : "NULL");
+ }
+ // 鍒濆鍖朖PEG鏁版嵁鐨勫唴瀛�
+ jpeg_data_.len = 0;
+ jpeg_data_.buf = (uint8_t*)heap_caps_malloc(IMG_JPEG_BUF_SIZE, MALLOC_CAP_SPIRAM);;
+ if ( jpeg_data_.buf == nullptr ) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG buffer");
+ return;
+ }
+
+ //鍒濆鍖朖PEG瑙g爜
+ jpeg_error_t err;
+ jpeg_dec_config_t config = { .output_type = JPEG_PIXEL_FORMAT_RGB565_LE, .rotate = JPEG_ROTATE_0D };
+ err = jpeg_dec_open(&config, &jpeg_dec_);
+ if ( err != JPEG_ERR_OK ) {
+ ESP_LOGE(TAG, "Failed to open JPEG decoder");
+ return;
+ }
+ jpeg_io_ = (jpeg_dec_io_t*)heap_caps_malloc(sizeof(jpeg_dec_io_t), MALLOC_CAP_SPIRAM);
+ if (!jpeg_io_) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG IO");
+ jpeg_dec_close(jpeg_dec_);
+ return;
+ }
+ memset(jpeg_io_, 0, sizeof(jpeg_dec_io_t));
+
+ jpeg_out_ = (jpeg_dec_header_info_t*)heap_caps_aligned_alloc(16, sizeof(jpeg_dec_header_info_t), MALLOC_CAP_SPIRAM);
+ if (!jpeg_out_) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG output header");
+ heap_caps_free(jpeg_io_);
+ jpeg_dec_close(jpeg_dec_);
+ return;
+ }
+ memset(jpeg_out_, 0, sizeof(jpeg_dec_header_info_t));
+
+ // 鍒濆鍖栭瑙堝浘鐗囩殑鍐呭瓨
+ memset(&preview_image_, 0, sizeof(preview_image_));
+ preview_image_.header.magic = LV_IMAGE_HEADER_MAGIC;
+ preview_image_.header.cf = LV_COLOR_FORMAT_RGB565;
+ preview_image_.header.flags = LV_IMAGE_FLAGS_ALLOCATED | LV_IMAGE_FLAGS_MODIFIABLE;
+ preview_image_.header.w = 640;
+ preview_image_.header.h = 480;
+
+ preview_image_.header.stride = preview_image_.header.w * 2;
+ preview_image_.data_size = preview_image_.header.w * preview_image_.header.h * 2;
+ preview_image_.data =(uint8_t*)jpeg_calloc_align(preview_image_.data_size, 16);
+ if (preview_image_.data == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for preview image");
+ return;
+ }
+
+ sscma_client_set_model(sscma_client_handle_, 4);
+ model_class_cnt = 0;
+ if (sscma_client_get_model(sscma_client_handle_, &model, true) == ESP_OK) {
+ printf("ID: %d\n", model->id ? model->id : -1);
+ printf("UUID: %s\n", model->uuid ? model->uuid : "N/A");
+ printf("Name: %s\n", model->name ? model->name : "N/A");
+ printf("Version: %s\n", model->ver ? model->ver : "N/A");
+ printf("URL: %s\n", model->url ? model->url : "N/A");
+ printf("Checksum: %s\n", model->checksum ? model->checksum : "N/A");
+ printf("Classes:\n");
+ if (model->classes[0] != NULL)
+ {
+ for (int i = 0; model->classes[i] != NULL; i++)
+ {
+ printf(" - %s\n", model->classes[i]);
+ model_class_cnt++;
+ }
+ } else {
+ printf(" N/A\n");
+ }
+ } else {
+ printf("get model failed\n");
+ }
+
+ ESP_LOGI(TAG, "initialize mcp tools");
+ InitializeMcpTools();
+
+ xTaskCreate([](void* arg) {
+ auto this_ = (SscmaCamera*)arg;
+ bool is_inference = false;
+ int64_t last_keepalive_time = esp_timer_get_time();
+ while (true)
+ {
+ if (this_->sscma_restarted_) {
+ ESP_LOGI(TAG, "SSCMA restarted detected");
+ this_->sscma_restarted_ = false;
+ is_inference = false;
+ }
+
+ if (esp_timer_get_time() - last_keepalive_time > 10 * 1000000) {
+ last_keepalive_time = esp_timer_get_time();
+ if (!__himax_keepalive_check(this_->sscma_client_handle_)) {
+ ESP_LOGE(TAG, "restart himax");
+ sscma_client_reset(this_->sscma_client_handle_);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ }
+ }
+
+ if (this_->inference_en && Application::GetInstance().GetDeviceState() == kDeviceStateIdle ) {
+ if (!is_inference) {
+ ESP_LOGI(TAG, "Start inference (enable=1)");
+ sscma_client_break(this_->sscma_client_handle_);
+ sscma_client_set_model(this_->sscma_client_handle_, 4);
+ sscma_client_set_sensor(this_->sscma_client_handle_, 1, 1, true); // 璁剧疆鍒嗚鲸鐜� 416X416
+ sscma_client_invoke(this_->sscma_client_handle_, -1, false, true);
+ is_inference = true;
+ }
+ } else if (is_inference && (!this_->inference_en || Application::GetInstance().GetDeviceState() != kDeviceStateIdle)) {
+ ESP_LOGI(TAG, "Stop inference (enable=%d state=%d)", this_->inference_en, Application::GetInstance().GetDeviceState());
+ is_inference = false;
+ sscma_client_break(this_->sscma_client_handle_);
+ }
+ vTaskDelay(pdMS_TO_TICKS(200));
+ }
+ }, "sscma_camera", 4096, this, 1, nullptr);
+
+}
+
+SscmaCamera::~SscmaCamera() {
+ if (preview_image_.data) {
+ heap_caps_free((void*)preview_image_.data);
+ preview_image_.data = nullptr;
+ }
+ if (sscma_client_handle_) {
+ sscma_client_del(sscma_client_handle_);
+ }
+ if (sscma_data_queue_) {
+ vQueueDelete(sscma_data_queue_);
+ }
+ if (jpeg_data_.buf) {
+ heap_caps_free(jpeg_data_.buf);
+ jpeg_data_.buf = nullptr;
+ }
+ if (jpeg_dec_) {
+ jpeg_dec_close(jpeg_dec_);
+ jpeg_dec_ = nullptr;
+ }
+ if (jpeg_io_) {
+ heap_caps_free(jpeg_io_);
+ jpeg_io_ = nullptr;
+ }
+ if (jpeg_out_) {
+ heap_caps_free(jpeg_out_);
+ jpeg_out_ = nullptr;
+ }
+}
+
+void SscmaCamera::InitializeMcpTools() {
+
+ Settings settings("model", false);
+ detect_threshold = settings.GetInt("threshold", 75);
+ detect_invoke_interval_sec = settings.GetInt("interval", 8);
+ detect_duration_sec = settings.GetInt("duration", 2);
+ detect_target = settings.GetInt("target", 0);
+ inference_en = settings.GetInt("enable", 0);
+
+ auto& mcp_server = McpServer::GetInstance();
+ // 鑾峰彇妯″瀷鍙傛暟閰嶇疆
+ mcp_server.AddTool("self.model.param_get",
+ "鑾峰彇褰撳墠瑙嗚妯″瀷妫�娴嬬殑鍙傛暟閰嶇疆淇℃伅銆俓n"
+ "杩斿洖缁撴灉鍖呭惈锛歕n"
+ " `threshold`: 妫�娴嬬疆淇″害闃堝�� (0-100)锛屼綆浜庢鍊肩殑妫�娴嬬粨鏋滃皢琚拷鐣ワ紱\n"
+ " `interval`: 瑙﹀彂瀵硅瘽鍚庣殑鍐峰嵈鏃堕棿(绉�)锛岄槻姝㈤绻佹墦鏂紱\n"
+ " `duration`: 鎸佺画妫�娴嬬‘璁ゆ椂闂�(绉�)锛沑n"
+ " `target`: 褰撳墠鍏虫敞鐨勬娴嬬洰鏍囩储寮曘��",
+ PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("model", false);
+ int threshold = settings.GetInt("threshold", 75);
+ int interval = settings.GetInt("interval", 8);
+ int duration = settings.GetInt("duration", 2);
+ int target_type = settings.GetInt("target", 0);
+
+ std::string result = "{\"threshold\":" + std::to_string(threshold) +
+ ",\"interval\":" + std::to_string(interval) +
+ ",\"duration\":" + std::to_string(duration) +
+ ",\"target_type\":" + std::to_string(target_type) + "}";
+ return result;
+ });
+
+
+ // 璁剧疆妯″瀷鍙傛暟閰嶇疆
+ mcp_server.AddTool("self.model.param_set",
+ "閰嶇疆瑙嗚妯″瀷妫�娴嬪弬鏁般�傚綋鐢ㄦ埛甯屾湜璋冩暣妫�娴嬬伒鏁忓害銆侀鐜囨垨鐗瑰畾鐩爣鏃朵娇鐢ㄣ�俓n"
+ "鍙傛暟(鍧囦负鍙�夛紝鏈彁渚涚殑鍙傛暟灏嗕繚鎸佸綋鍓嶈缃笉鍙�)锛歕n"
+ " `threshold`: 缃俊搴﹂槇鍊� (0-100)銆傛彁楂樻鍊煎彲鍑忓皯璇姤锛屼絾鍙兘婕忔锛沑n"
+ " `interval`: 鍐峰嵈鏃堕棿(绉�)銆傝缃璇濈粨鏉熷悗澶氫箙鍐呬笉鍐嶈Е鍙戞娴嬶紱\n"
+ " `duration`: 鎸佺画妫�娴嬫椂闂�(绉�)銆俓n"
+ " `target`: 璁剧疆妫�娴嬬洰鏍囩殑绱㈠紩 ID銆�",
+ PropertyList({
+ Property("threshold", kPropertyTypeInteger, -1, -1, 100),
+ Property("interval", kPropertyTypeInteger, -1, -1, 60),
+ Property("duration", kPropertyTypeInteger, -1, -1, 60),
+ Property("target", kPropertyTypeInteger, -1, -1, this->model_class_cnt > 0 ? this->model_class_cnt - 1 : 255)
+ }),
+ [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("model", true);
+ try {
+ const Property& threshold_prop = properties["threshold"];
+ int threshold = threshold_prop.value<int>();
+ if (threshold != -1) {
+ settings.SetInt("threshold", threshold);
+ this->detect_threshold = threshold;
+ ESP_LOGI(TAG, "Set detection threshold to %d", threshold);
+ }
+ } catch (const std::runtime_error&) {
+ // threshold parameter not provided, skip
+ }
+
+ try {
+ const Property& interval_prop = properties["interval"];
+ int interval = interval_prop.value<int>();
+ if (interval != -1) {
+ settings.SetInt("interval", interval);
+ this->detect_invoke_interval_sec = interval;
+ ESP_LOGI(TAG, "Set detection interval to %d", interval);
+ }
+ } catch (const std::runtime_error&) {
+ // interval parameter not provided, skip
+ }
+
+ try {
+ const Property& duration_prop = properties["duration"];
+ int duration = duration_prop.value<int>();
+ if (duration != -1) {
+ settings.SetInt("duration", duration);
+ this->detect_duration_sec = duration;
+ }
+ } catch (const std::runtime_error&) {
+ // duration parameter not provided, skip
+ }
+
+ try {
+ const Property& target_prop = properties["target"];
+ int target = target_prop.value<int>();
+ if (target != -1) {
+ settings.SetInt("target", target);
+ this->detect_target = target;
+ ESP_LOGI(TAG, "Set detection target to %d", target);
+ }
+ } catch (const std::runtime_error&) {
+ // target_type parameter not provided, skip
+ }
+
+ return "{\"status\": \"success\", \"message\": \"Detection configuration updated\"}";
+ });
+
+ // 鎺ㄧ悊寮�鍏宠幏鍙�
+ mcp_server.AddTool("self.model.enable",
+ "鎺у埗瑙嗚鎺ㄧ悊(鎽勫儚澶存娴�)鍔熻兘鐨勫紑鍚笌鍏抽棴锛屾垨鏌ヨ褰撳墠鐘舵�併�俓n"
+ "褰撶敤鎴锋寚浠ゆ秹鍙�'寮�鍚�/鍏抽棴鎺ㄧ悊'銆�'寮�濮�/鍋滄妫�娴�'鏃朵娇鐢ㄣ�俓n"
+ "鍙傛暟锛歕n"
+ " `enable`: (鍙��) 鏁存暟銆�1=寮�鍚帹鐞嗭紝0=鍏抽棴鎺ㄧ悊銆傝嫢鐪佺暐鍒欒繑鍥炲綋鍓嶅紑鍏崇姸鎬併��",
+ PropertyList({
+ Property("enable", kPropertyTypeInteger, inference_en, 0, 1)
+ }),
+ [this](const PropertyList& properties) -> ReturnValue {
+ Settings settings("model", true);
+ try {
+ const Property& enable_prop = properties["enable"];
+ int en = enable_prop.value<int>();
+ settings.SetInt("enable", en);
+ this->inference_en = en;
+ ESP_LOGI(TAG, "Set inference enable to %d", en);
+ } catch (const std::runtime_error&) {
+ // enable not provided -> treat as query
+ }
+ // 杩斿洖褰撳墠閰嶇疆
+ int cur_en = settings.GetInt("enable", this->inference_en);
+ return std::string("{\"enable\":") + std::to_string(cur_en) + "}";
+ });
+}
+
+void SscmaCamera::SetExplainUrl(const std::string& url, const std::string& token) {
+ explain_url_ = url;
+ explain_token_ = token;
+}
+
+bool SscmaCamera::Capture() {
+
+ SscmaData data;
+ int ret = 0;
+
+ if (sscma_client_handle_ == nullptr) {
+ ESP_LOGE(TAG, "SSCMA client handle is not initialized");
+ return false;
+ }
+
+ if (sscma_client_set_sensor(sscma_client_handle_, 1, 3, true)) {
+ ESP_LOGE(TAG, "Failed to set sensor");
+ return false;
+ }
+ ESP_LOGI(TAG, "Capturing image...");
+ // himax 鍙兘鏈夌紦瀛樻暟鎹�, 鍙幏鍙栨渶鏂扮殑鐓х墖鍗冲彲.
+ if (sscma_client_sample(sscma_client_handle_, 1) ) {
+ ESP_LOGE(TAG, "Failed to capture image from SSCMA client");
+ return false;
+ }
+ vTaskDelay(pdMS_TO_TICKS(500)); // 绛夊緟SSCMA瀹㈡埛绔鐞嗘暟鎹�
+ if (xQueueReceive(sscma_data_queue_, &data, pdMS_TO_TICKS(1000)) != pdPASS) {
+ ESP_LOGE(TAG, "Failed to receive JPEG data from SSCMA client");
+ return false;
+ }
+
+ if (jpeg_data_.buf == nullptr) {
+ heap_caps_free(data.img);
+ return false;
+ }
+
+ ret = mbedtls_base64_decode(jpeg_data_.buf, IMG_JPEG_BUF_SIZE, &jpeg_data_.len, data.img, data.len);
+ if (ret != 0 || jpeg_data_.len == 0) {
+ ESP_LOGE(TAG, "Failed to decode base64 image data, ret: %d, output_len: %zu", ret, jpeg_data_.len);
+ heap_caps_free(data.img);
+ return false;
+ }
+ heap_caps_free(data.img);
+
+ //DECODE JPEG
+ if (!jpeg_dec_ || !jpeg_io_ || !jpeg_out_ || !preview_image_.data) {
+ return true;
+ }
+ jpeg_io_->inbuf = jpeg_data_.buf;
+ jpeg_io_->inbuf_len = jpeg_data_.len;
+ ret = jpeg_dec_parse_header(jpeg_dec_, jpeg_io_, jpeg_out_);
+ if (ret < 0) {
+ ESP_LOGE(TAG, "Failed to parse JPEG header, ret: %d", ret);
+ return true;
+ }
+ jpeg_io_->outbuf = (unsigned char*)preview_image_.data;
+ int inbuf_consumed = jpeg_io_->inbuf_len - jpeg_io_->inbuf_remain;
+ jpeg_io_->inbuf = jpeg_data_.buf + inbuf_consumed;
+ jpeg_io_->inbuf_len = jpeg_io_->inbuf_remain;
+
+ ret = jpeg_dec_process(jpeg_dec_, jpeg_io_);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to decode JPEG image, ret: %d", ret);
+ return true;
+ }
+
+ // 鏄剧ず棰勮鍥剧墖
+ auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
+ if (display != nullptr) {
+ uint16_t w = preview_image_.header.w;
+ uint16_t h = preview_image_.header.h;
+ size_t image_size = w * h * 2;
+ size_t stride = preview_image_.header.w * 2;
+
+ uint8_t* data = (uint8_t*)heap_caps_malloc(image_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (data == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate memory for display image");
+ return true;
+ }
+ memcpy(data, preview_image_.data, image_size);
+
+ auto image = std::make_unique<LvglAllocatedImage>(data, image_size, w, h, stride, LV_COLOR_FORMAT_RGB565);
+ display->SetPreviewImage(std::move(image));
+ }
+ return true;
+}
+bool SscmaCamera::SetHMirror(bool enabled) {
+ return false;
+}
+
+bool SscmaCamera::SetVFlip(bool enabled) {
+ return false;
+}
+
+/**
+ * @brief 灏嗘憚鍍忓ご鎹曡幏鐨勫浘鍍忓彂閫佸埌杩滅▼鏈嶅姟鍣ㄨ繘琛孉I鍒嗘瀽鍜岃В閲�
+ *
+ * 璇ュ嚱鏁板皢褰撳墠鎽勫儚澶寸紦鍐插尯涓殑鍥惧儚缂栫爜涓篔PEG鏍煎紡锛屽苟閫氳繃HTTP POST璇锋眰
+ * 浠ultipart/form-data鐨勫舰寮忓彂閫佸埌鎸囧畾鐨勮В閲婃湇鍔″櫒銆傛湇鍔″櫒灏嗘牴鎹彁渚涚殑
+ * 闂瀵瑰浘鍍忚繘琛孉I鍒嗘瀽骞惰繑鍥炵粨鏋溿��
+ *
+ * @param question 瑕佸悜AI鎻愬嚭鐨勫叧浜庡浘鍍忕殑闂锛屽皢浣滀负琛ㄥ崟瀛楁鍙戦��
+ * @return std::string 鏈嶅姟鍣ㄨ繑鍥炵殑JSON鏍煎紡鍝嶅簲瀛楃涓�
+ * 鎴愬姛鏃跺寘鍚獳I鍒嗘瀽缁撴灉锛屽け璐ユ椂鍖呭惈閿欒淇℃伅
+ * 鏍煎紡绀轰緥锛歿"success": true, "result": "鍒嗘瀽缁撴灉"}
+ * {"success": false, "message": "閿欒淇℃伅"}
+ *
+ * @note 璋冪敤姝ゅ嚱鏁板墠蹇呴』鍏堣皟鐢⊿etExplainUrl()璁剧疆鏈嶅姟鍣║RL
+ * @note 鍑芥暟浼氱瓑寰呬箣鍓嶇殑缂栫爜绾跨▼瀹屾垚鍚庡啀寮�濮嬫柊鐨勫鐞�
+ * @warning 濡傛灉鎽勫儚澶寸紦鍐插尯涓虹┖鎴栫綉缁滆繛鎺ュけ璐ワ紝灏嗚繑鍥為敊璇俊鎭�
+ */
+std::string SscmaCamera::Explain(const std::string& question) {
+ if (explain_url_.empty()) {
+ return "{\"success\": false, \"message\": \"Image explain URL or token is not set\"}";
+ }
+
+ auto network = Board::GetInstance().GetNetwork();
+ auto http = network->CreateHttp(3);
+ // 鏋勯�爉ultipart/form-data璇锋眰浣�
+ std::string boundary = "----ESP32_CAMERA_BOUNDARY";
+
+ // 鏋勯�爍uestion瀛楁
+ std::string question_field;
+ question_field += "--" + boundary + "\r\n";
+ question_field += "Content-Disposition: form-data; name=\"question\"\r\n";
+ question_field += "\r\n";
+ question_field += question + "\r\n";
+
+ // 鏋勯�犳枃浠跺瓧娈靛ご閮�
+ std::string file_header;
+ file_header += "--" + boundary + "\r\n";
+ file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"camera.jpg\"\r\n";
+ file_header += "Content-Type: image/jpeg\r\n";
+ file_header += "\r\n";
+
+ // 鏋勯�犲熬閮�
+ std::string multipart_footer;
+ multipart_footer += "\r\n--" + boundary + "--\r\n";
+
+ // 閰嶇疆HTTP瀹㈡埛绔紝浣跨敤鍒嗗潡浼犺緭缂栫爜
+ http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
+ http->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
+ if (!explain_token_.empty()) {
+ http->SetHeader("Authorization", "Bearer " + explain_token_);
+ }
+ http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
+ http->SetHeader("Transfer-Encoding", "chunked");
+ if (!http->Open("POST", explain_url_)) {
+ ESP_LOGE(TAG, "Failed to connect to explain URL");
+ return "{\"success\": false, \"message\": \"Failed to connect to explain URL\"}";
+ }
+
+ // 绗竴鍧楋細question瀛楁
+ http->Write(question_field.c_str(), question_field.size());
+
+ // 绗簩鍧楋細鏂囦欢瀛楁澶撮儴
+ http->Write(file_header.c_str(), file_header.size());
+
+ // 绗笁鍧楋細JPEG鏁版嵁
+ http->Write((const char*)jpeg_data_.buf, jpeg_data_.len);
+
+ // 绗洓鍧楋細multipart灏鹃儴
+ http->Write(multipart_footer.c_str(), multipart_footer.size());
+
+ // 缁撴潫鍧�
+ http->Write("", 0);
+
+ if (http->GetStatusCode() != 200) {
+ ESP_LOGE(TAG, "Failed to upload photo, status code: %d", http->GetStatusCode());
+ return "{\"success\": false, \"message\": \"Failed to upload photo\"}";
+ }
+
+ std::string result = http->ReadAll();
+ http->Close();
+
+ ESP_LOGI(TAG, "Explain image size=%d, question=%s\n%s", jpeg_data_.len, question.c_str(), result.c_str());
+ return result;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.h b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.h
new file mode 100644
index 0000000..40d6777
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sensecap-watcher/sscma_camera.h
@@ -0,0 +1,75 @@
+#ifndef SSCMA_CAMERA_H
+#define SSCMA_CAMERA_H
+
+#include <cstdint>
+#include <lvgl.h>
+#include <thread>
+#include <memory>
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/queue.h>
+#include <esp_io_expander_tca95xx_16bit.h>
+#include <esp_jpeg_dec.h>
+#include <mbedtls/base64.h>
+
+#include "sscma_client.h"
+#include "camera.h"
+
+struct SscmaData {
+ uint8_t* img;
+ size_t len;
+};
+struct JpegData {
+ uint8_t* buf;
+ size_t len;
+};
+
+class SscmaCamera : public Camera {
+private:
+ lv_img_dsc_t preview_image_;
+ std::string explain_url_;
+ std::string explain_token_;
+ sscma_client_io_handle_t sscma_client_io_handle_;
+ sscma_client_handle_t sscma_client_handle_;
+ QueueHandle_t sscma_data_queue_;
+ JpegData jpeg_data_;
+ jpeg_dec_handle_t jpeg_dec_;
+ jpeg_dec_io_t *jpeg_io_;
+ jpeg_dec_header_info_t *jpeg_out_;
+ // 妫�娴嬬姸鎬佹満
+ enum DetectionState {
+ IDLE, // 绌洪棽鐘舵��
+ VALIDATING, // 楠岃瘉涓紙杩炵画妫�娴�3绉掞級
+ COOLDOWN // 鍐峰嵈鏈燂紙绛夊緟閲嶆柊妫�娴嬶級
+ };
+
+ DetectionState detection_state = IDLE;
+ int64_t state_start_time = 0;
+ bool need_start_cooldown = false; // 鏄惁闇�瑕佸紑濮嬪喎鍗存湡
+ int64_t last_detected_time = 0; // 楠岃瘉鏈熼棿鏈�鍚庝竴娆℃娴嬪埌鐗╀綋鐨勬椂闂�
+
+ int detect_target = 0;
+ int detect_threshold = 75;
+ int detect_duration_sec = 2; // 妫�娴嬫寔缁椂闂�2绉掞紝纭浜哄憳鎸佺画瀛樺湪
+ int detect_invoke_interval_sec = 8; // 榛樿15绉掑喎鍗存湡锛岄伩鍏嶉绻佸紑濮嬩細璇�
+ int detect_debounce_sec = 1; // 楠岃瘉鏈熼棿浜哄憳绂诲紑鐨勫幓鎶栧姩鏃堕棿1绉�
+ int inference_en = 0; // 鎺ㄧ悊浣胯兘寮�鍏筹紙0: 鍏抽棴, 1: 寮�鍚級
+ bool sscma_restarted_ = false;
+
+ sscma_client_model_t *model;
+ int model_class_cnt = 0;
+public:
+ SscmaCamera(esp_io_expander_handle_t io_exp_handle);
+ ~SscmaCamera();
+ void InitializeMcpTools();
+
+ virtual void SetExplainUrl(const std::string& url, const std::string& token);
+ virtual bool Capture();
+ // 缈昏浆鎺у埗鍑芥暟
+ virtual bool SetHMirror(bool enabled) override;
+ virtual bool SetVFlip(bool enabled) override;
+ virtual std::string Explain(const std::string& question);
+
+};
+
+#endif // ESP32_CAMERA_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/README.md b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/README.md
new file mode 100644
index 0000000..84b854b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/README.md
@@ -0,0 +1,31 @@
+銆愪骇鍝佺畝浠嬨��
+銆戞敮鎸佽Е鎽�
+銆戞敮鎸佸厖鐢�
+銆戠嫭鐗瑰褰㈣璁�
+浜у搧閾炬帴1锛歨ttps://spotpear.cn/shop/ESP32-S3-N16R8-AI-DeepSeek-XiaoZhi-XiaGe-Qwen-DouBao-1.28-inch-LCD.html
+浜у搧閾炬帴2锛歨ttps://spotpear.cn/shop/ESP32-S3-N16R8-AI-DeepSeek-XiaoZhi-XiaGe-Qwen-DouBao-1.28-inch-Round-LCD-BOX-TouchScreen.html
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> Spotpear ESP32-S3-1.28-BOX
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.h b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.h
new file mode 100644
index 0000000..cde7dcb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.h
@@ -0,0 +1,56 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// Movecall Moji configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 //MCLK
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 //LRCK
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 //SCLK
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 //DOUT
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DIN
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_4
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_2
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_5
+#define DISPLAY_SPI_DC_PIN GPIO_NUM_47
+#define DISPLAY_SPI_RESET_PIN GPIO_NUM_38
+
+#define TP_PIN_NUM_TP_SDA (GPIO_NUM_11)
+#define TP_PIN_NUM_TP_SCL (GPIO_NUM_7)
+#define TP_PIN_NUM_TP_RST (GPIO_NUM_6)
+#define TP_PIN_NUM_TP_INT (GPIO_NUM_12)
+
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+// 鐢甸噺妫�娴嬬浉鍏冲紩鑴氬畾涔�
+#define BATTERY_ADC_PIN GPIO_NUM_1 // 鐢垫睜鐢靛帇妫�娴婣DC寮曡剼
+#define BATTERY_CHARGING_PIN GPIO_NUM_41 // 鍏呯數鐘舵�佹娴嬪紩鑴�
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.json b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.json
new file mode 100644
index 0000000..db34c1c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "sp-esp32-s3-1.28-box",
+ "sdkconfig_append": []
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/power_manager.h
new file mode 100644
index 0000000..36e8ca9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/power_manager.h
@@ -0,0 +1,189 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <driver/adc.h>
+#include <esp_log.h>
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ adc_channel_t adc_channel_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿 - 鏍规嵁瀹為檯纭欢璋冩暣杩欎簺鍊�
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1980, 0},
+ {2081, 20},
+ {2163, 40},
+ {2250, 60},
+ {2340, 80},
+ {2480, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t charging_pin, adc_channel_t adc_channel)
+ : charging_pin_(charging_pin), adc_channel_(adc_channel) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 100000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc
new file mode 100644
index 0000000..12822ef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.28-box/sp-esp32-s3-1.28-box.cc
@@ -0,0 +1,491 @@
+#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 "led/single_led.h"
+#include "assets/lang_config.h"
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_gc9a01.h>
+#include "system_reset.h"
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#include <esp_timer.h>
+#include "i2c_device.h"
+#include <esp_lcd_panel_vendor.h>
+#include <driver/spi_common.h>
+#include "power_save_timer.h"
+#include <esp_sleep.h>
+#include <driver/rtc_io.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "power_manager.h"
+
+#define TAG "Spotpear_ESP32_S3_1_28_BOX"
+
+LV_FONT_DECLARE(font_puhui_16_4);
+LV_FONT_DECLARE(font_awesome_16_4);
+
+
+class Cst816d : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+ Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA3);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ last_chip_id_ = chip_id;
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Cst816d() {
+ if (read_buffer_) {
+ delete[] read_buffer_;
+ read_buffer_ = nullptr;
+ }
+ }
+
+ void UpdateTouchPoint() {
+ if (!read_buffer_) return;
+ ReadRegs(0x02, read_buffer_, 6);
+ if (read_buffer_[0] == 0xFF) {
+ read_buffer_[0] = 0x00;
+ }
+ tp_.num = read_buffer_[0] & 0x01;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t& GetTouchPoint() const {
+ return tp_;
+ }
+
+ static bool Probe(i2c_master_bus_handle_t i2c_bus, uint8_t addr, uint8_t& chip_id) {
+ if (!i2c_bus) return false;
+ i2c_master_dev_handle_t dev = nullptr;
+ i2c_device_config_t cfg = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = addr,
+ .scl_speed_hz = 400 * 1000,
+ .scl_wait_us = 0,
+ .flags = {
+ .disable_ack_check = 0,
+ },
+ };
+ esp_err_t ret = i2c_master_bus_add_device(i2c_bus, &cfg, &dev);
+ if (ret != ESP_OK || dev == nullptr) {
+ return false;
+ }
+ uint8_t reg = 0xA3;
+ uint8_t id = 0;
+ ret = i2c_master_transmit_receive(dev, ®, 1, &id, 1, 100);
+ i2c_master_bus_rm_device(dev);
+ if (ret == ESP_OK) {
+ chip_id = id;
+ return true;
+ }
+ return false;
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+ uint8_t last_chip_id_ = 0;
+};
+
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ // 鐢变簬灞忓箷鏄渾鐨勶紝鎵�浠ョ姸鎬佹爮闇�瑕佸鍔犲乏鍙冲唴杈硅窛
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.33, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.33, 0);
+ }
+};
+
+
+class Spotpear_ESP32_S3_1_28_BOX : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_ = nullptr;
+ i2c_master_bus_handle_t i2c_bus_ = nullptr;
+ Button boot_button_;
+ Display* display_ = nullptr;
+ esp_timer_handle_t touchpad_timer_ = nullptr;
+ Cst816d* cst816d_ = nullptr;
+ PowerSaveTimer* power_save_timer_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ PowerManager* power_manager_ = nullptr;
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_3);
+ rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_3, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 290);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ // 鍏抽棴ES8311闊抽缂栬В鐮佸櫒
+ auto codec = GetAudioCodec();
+ if (codec) {
+ codec->EnableInput(false);
+ codec->EnableOutput(false);
+ }
+ rtc_gpio_set_level(GPIO_NUM_3, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_3);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, ADC_CHANNEL_0);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeCodecI2c_Touch() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .sda_io_num = TP_PIN_NUM_TP_SDA,
+ .scl_io_num = TP_PIN_NUM_TP_SCL,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ .glitch_ignore_cnt = 7,
+ .intr_priority = 0,
+ .trans_queue_depth = 0,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ esp_err_t ret = i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "i2c_new_master_bus failed: %s", esp_err_to_name(ret));
+ i2c_bus_ = nullptr;
+ }
+ }
+
+
+ static void touchpad_timer_callback(void* arg) {
+ auto* board = static_cast<Spotpear_ESP32_S3_1_28_BOX*>(arg);
+ if (!board || !board->cst816d_) return;
+ static bool was_touched = false;
+ static int64_t touch_start_time = 0;
+ const int64_t TOUCH_THRESHOLD_MS = 500; // 瑙︽懜鏃堕暱闃堝�硷紝瓒呰繃500ms瑙嗕负闀挎寜
+
+ board->cst816d_->UpdateTouchPoint();
+ auto touch_point = board->cst816d_->GetTouchPoint();
+
+ // 妫�娴嬭Е鎽稿紑濮�
+ if (touch_point.num > 0 && !was_touched) {
+ was_touched = true;
+ touch_start_time = esp_timer_get_time() / 1000; // 杞崲涓烘绉�
+ }
+ // 妫�娴嬭Е鎽搁噴鏀�
+ else if (touch_point.num == 0 && was_touched) {
+ was_touched = false;
+ int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time;
+
+ // 鍙湁鐭Е鎵嶈Е鍙�
+ if (touch_duration < TOUCH_THRESHOLD_MS) {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing touch enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ board->EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }
+ }
+ }
+
+ void InitializeCst816DTouchPad() {
+ ESP_LOGI(TAG, "Init Cst816D");
+
+ // RST/INT 绠¤剼鍒濆鍖�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << TP_PIN_NUM_TP_RST);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ gpio_config_t int_conf = {};
+ int_conf.intr_type = GPIO_INTR_DISABLE;
+ int_conf.mode = GPIO_MODE_INPUT;
+ int_conf.pin_bit_mask = (1ULL << TP_PIN_NUM_TP_INT);
+ int_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ int_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&int_conf);
+
+ // 瑙︽懜鑺墖澶嶄綅搴忓垪
+ gpio_set_level(TP_PIN_NUM_TP_RST, 0);
+ vTaskDelay(pdMS_TO_TICKS(5));
+ gpio_set_level(TP_PIN_NUM_TP_RST, 1);
+ vTaskDelay(pdMS_TO_TICKS(50));
+
+ // 鎺㈡祴鏄惁瀛樺湪瑙︽懜鑺墖
+ uint8_t chip_id = 0;
+ if (!i2c_bus_) {
+ ESP_LOGW(TAG, "Touch I2C bus not initialized, skip touch");
+ return;
+ }
+ bool touch_available = Cst816d::Probe(i2c_bus_, 0x15, chip_id);
+ if (!touch_available) {
+ ESP_LOGW(TAG, "CST816D not found, running in non-touch mode");
+ // 閲婃斁瑙︽懜I2C锛岄伩鍏嶆棤璁惧鏃跺弽澶嶆姤閿�
+ i2c_del_master_bus(i2c_bus_);
+ i2c_bus_ = nullptr;
+ return;
+ }
+
+ cst816d_ = new Cst816d(i2c_bus_, 0x15);
+
+ // 鍒涘缓瀹氭椂鍣紝10ms 闂撮殧
+ esp_timer_create_args_t timer_args = {
+ .callback = touchpad_timer_callback,
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "touchpad_timer",
+ .skip_unhandled_events = true,
+ };
+
+ if (esp_timer_create(&timer_args, &touchpad_timer_) == ESP_OK) {
+ esp_timer_start_periodic(touchpad_timer_, 10 * 1000); // 10ms = 10000us
+ }
+ }
+
+ // SPI鍒濆鍖�
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize SPI bus");
+ spi_bus_config_t buscfg = GC9A01_PANEL_BUS_SPI_CONFIG(DISPLAY_SPI_SCLK_PIN, DISPLAY_SPI_MOSI_PIN,
+ DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ // GC9A01鍒濆鍖�
+ void InitializeGc9a01Display() {
+ ESP_LOGI(TAG, "Init GC9A01 display");
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_lcd_panel_io_spi_config_t io_config = GC9A01_PANEL_IO_SPI_CONFIG(DISPLAY_SPI_CS_PIN, DISPLAY_SPI_DC_PIN, 0, NULL);
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &io_handle));
+
+ ESP_LOGI(TAG, "Install GC9A01 panel driver");
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN; // Set to -1 if not use
+ panel_config.rgb_endian = LCD_RGB_ENDIAN_BGR; //LCD_RGB_ENDIAN_RGB;
+ panel_config.bits_per_pixel = 16;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_gc9a01(io_handle, &panel_config, &panel_handle));
+ panel_ = panel_handle;
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_handle, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true));
+
+ uint8_t data_0x62[] = { 0x18, 0x0D, 0x71, 0xED, 0x70, 0x70, 0x18, 0x0F, 0x71, 0xEF, 0x70, 0x70 };
+ esp_lcd_panel_io_tx_param(io_handle, 0x62, data_0x62, sizeof(data_0x62));
+
+ uint8_t data_0x63[] = { 0x18, 0x11, 0x71, 0xF1, 0x70, 0x70, 0x18, 0x13, 0x71, 0xF3, 0x70, 0x70 };
+ esp_lcd_panel_io_tx_param(io_handle, 0x63, data_0x63, sizeof(data_0x63));
+
+ uint8_t data_0x36[] = { 0x48};
+ esp_lcd_panel_io_tx_param(io_handle, 0x36, data_0x36, sizeof(data_0x36));
+
+ // uint8_t data_0x74[] = { 0x10, 0x85, 0x80, 0x00, 0x00, 0x4E, 0x00};
+ // esp_lcd_panel_io_tx_param(io_handle, 0x74, data_0x74, sizeof(data_0x74));
+
+ uint8_t data_0xC3[] = { 0x1F};
+ esp_lcd_panel_io_tx_param(io_handle, 0xC3, data_0xC3, sizeof(data_0xC3));
+
+ uint8_t data_0xC4[] = { 0x1F};
+ esp_lcd_panel_io_tx_param(io_handle, 0xC4, data_0xC4, sizeof(data_0xC4));
+
+ display_ = new CustomLcdDisplay(io_handle, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ Spotpear_ESP32_S3_1_28_BOX() : boot_button_(BOOT_BUTTON_GPIO) {
+ // 鍏堝垵濮嬪寲瑙︽懜鐨処2C骞舵帰娴�/鍒濆鍖栬Е鎽革紙鑻ユ棤瑙︽懜鍒欒烦杩囷級
+ InitializeCodecI2c_Touch();
+ InitializeCst816DTouchPad();
+
+ // 鍒濆鍖栭煶棰慖2C
+ InitializeCodecI2c();
+
+ // 鏄剧ず鐩稿叧鍏堝缓绔嬭捣鏉�
+ InitializeSpi();
+ InitializeGc9a01Display();
+ InitializeButtons();
+ if (GetBacklight()) {
+ GetBacklight()->RestoreBrightness();
+ }
+
+ // 鏄剧ず鍜岃儗鍏夊彲鐢ㄥ悗鍐嶅垵濮嬪寲鐪佺數閫昏緫锛岄伩鍏嶇┖鎸囬拡
+ InitializePowerSaveTimer();
+ InitializePowerManager();
+ }
+
+ ~Spotpear_ESP32_S3_1_28_BOX() {
+ if (touchpad_timer_) {
+ esp_timer_stop(touchpad_timer_);
+ esp_timer_delete(touchpad_timer_);
+ touchpad_timer_ = nullptr;
+ }
+ if (cst816d_) {
+ delete cst816d_;
+ cst816d_ = nullptr;
+ }
+ if (power_save_timer_) {
+ delete power_save_timer_;
+ power_save_timer_ = nullptr;
+ }
+ if (power_manager_) {
+ delete power_manager_;
+ power_manager_ = nullptr;
+ }
+ if (display_) {
+ delete display_;
+ display_ = nullptr;
+ }
+ if (i2c_bus_) {
+ i2c_del_master_bus(i2c_bus_);
+ i2c_bus_ = nullptr;
+ }
+ if (codec_i2c_bus_) {
+ i2c_del_master_bus(codec_i2c_bus_);
+ codec_i2c_bus_ = nullptr;
+ }
+ }
+
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ 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;
+ }
+
+ Cst816d* GetTouchpad() {
+ return cst816d_;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ if (!power_manager_) {
+ level = 0;
+ charging = false;
+ discharging = true;
+ return false;
+ }
+
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(Spotpear_ESP32_S3_1_28_BOX);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/README.md b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/README.md
new file mode 100644
index 0000000..94a00bb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/README.md
@@ -0,0 +1,34 @@
+銆愪骇鍝佺畝浠嬨��
+[] ESP32 S3灏忔湪椹紑鍙戞澘1.54瀵窵CD灏忔櫤muma铏惧摜AI DeepSeek浜哄伐鏅鸿兘璇煶鑱婂ぉ鏈哄櫒浜篘16R8
+銆愬姛鑳姐��
+[] 鍙埍灏忔湪椹紝鏀寔澶╂皵鏃堕挓锛� SD瑙嗛鎾斁锛� AI鏅鸿兘瀵硅瘽鎵�鏈夊浐浠舵簮鐮佸紑婧愶紝閫傚悎灏忓缂栫▼瀛︿範锛屽彲寮�鍙戞洿澶氬姛鑳姐��
+AI灏忔櫤鏀寔璇煶鍞ら啋銆傝Е鎽哥増鏈澶栨敮鎸佽Е鎽稿敜閱掑拰鎵撴柇
+鏄剧ず灞忥細1.54瀵窼T7789 240x240鍒嗚鲸鐜�
+浜у搧閾炬帴锛�
+https://spotpear.cn/shop/ESP32-S3-AI-1.54-inch-LCD-Display-TouchScreen-N16R8-muma-DeepSeek/sp-esp32-s3-1.54-muma-W-Bat.html
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> Spotpear ESP32-S3-LCD-1.54-MUMA
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.h b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.h
new file mode 100644
index 0000000..a13ce75
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.h
@@ -0,0 +1,58 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// Movecall Moji configuration
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 //MCLK
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45 //LRCK
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 //SCLK
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 //DOUT
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 //DIN
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+#define DISPLAY_SPI_SCLK_PIN GPIO_NUM_4
+#define DISPLAY_SPI_MOSI_PIN GPIO_NUM_2
+#define DISPLAY_SPI_CS_PIN GPIO_NUM_5
+#define DISPLAY_SPI_DC_PIN GPIO_NUM_47
+#define DISPLAY_SPI_RESET_PIN GPIO_NUM_38
+
+
+#define TP_PIN_NUM_TP_SDA (GPIO_NUM_11)
+#define TP_PIN_NUM_TP_SCL (GPIO_NUM_7)
+#define TP_PIN_NUM_TP_RST (GPIO_NUM_6)
+#define TP_PIN_NUM_TP_INT (GPIO_NUM_12)
+
+#define POWER_CHARGE_LED_PIN GPIO_NUM_3
+#define POWER_CHARGE_DETECT_PIN GPIO_NUM_41
+#define POWER_ADC_UNIT ADC_UNIT_1
+#define POWER_ADC_CHANNEL ADC_CHANNEL_0
+
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.json b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.json
new file mode 100644
index 0000000..2bc4d1b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "sp-esp32-s3-1.54-muma",
+ "sdkconfig_append": []
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/power_manager.h
new file mode 100644
index 0000000..2df53a1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/power_manager.h
@@ -0,0 +1,186 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_41;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_0, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1980, 0},
+ {2081, 20},
+ {2163, 40},
+ {2250, 60},
+ {2340, 80},
+ {2480, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 100000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_0, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc
new file mode 100644
index 0000000..b56c3b8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/sp-esp32-s3-1.54-muma/sp-esp32-s3-1.54-muma.cc
@@ -0,0 +1,325 @@
+#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 "led/single_led.h"
+#include "assets/lang_config.h"
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include "system_reset.h"
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include "driver/gpio.h"
+#include "driver/spi_master.h"
+#include <esp_lcd_panel_vendor.h>
+#include <esp_io_expander_tca9554.h>
+#include <driver/spi_common.h>
+#include "i2c_device.h"
+#include <esp_timer.h>
+#include "power_manager.h"
+#include "power_save_timer.h"
+#include <esp_sleep.h>
+#include <driver/rtc_io.h>
+
+#define TAG "Spotpear_esp32_s3_lcd_1_54"
+
+class Cst816d : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+ Cst816d(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA3);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Cst816d() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t& GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class Spotpear_esp32_s3_lcd_1_54 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Display* display_;
+ esp_timer_handle_t touchpad_timer_;
+ Cst816d* cst816d_;
+ esp_io_expander_handle_t io_expander_ = NULL;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ PowerManager* power_manager_;
+ PowerSaveTimer* power_save_timer_;
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_41);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_3);
+ rtc_gpio_set_direction(GPIO_NUM_3, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_3, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_3, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_3);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeCodecI2c_Touch() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .sda_io_num = TP_PIN_NUM_TP_SDA,
+ .scl_io_num = TP_PIN_NUM_TP_SCL,
+ .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, &i2c_bus_));
+ }
+
+ static void touchpad_timer_callback(void* arg) {
+ auto& board = (Spotpear_esp32_s3_lcd_1_54&)Board::GetInstance();
+ auto touchpad = board.GetTouchpad();
+ static bool was_touched = false;
+ static int64_t touch_start_time = 0;
+ const int64_t TOUCH_THRESHOLD_MS = 500; // 瑙︽懜鏃堕暱闃堝�硷紝瓒呰繃500ms瑙嗕负闀挎寜
+
+ touchpad->UpdateTouchPoint();
+ auto touch_point = touchpad->GetTouchPoint();
+ // 妫�娴嬭Е鎽稿紑濮�
+ if (touch_point.num > 0 && !was_touched) {
+ was_touched = true;
+ touch_start_time = esp_timer_get_time() / 1000; // 杞崲涓烘绉�
+ }
+ // 妫�娴嬭Е鎽搁噴鏀�
+ else if (touch_point.num == 0 && was_touched) {
+ was_touched = false;
+ int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time;
+
+ // 鍙湁鐭Е鎵嶈Е鍙�
+ if (touch_duration < TOUCH_THRESHOLD_MS) {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ board.EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }
+ }
+ }
+
+ void InitializeCst816DTouchPad() {
+ ESP_LOGI(TAG, "Init Cst816D");
+ cst816d_ = new Cst816d(i2c_bus_, 0x15);
+
+ // 鍒涘缓瀹氭椂鍣紝10ms 闂撮殧
+ esp_timer_create_args_t timer_args = {
+ .callback = touchpad_timer_callback,
+ .arg = NULL,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "touchpad_timer",
+ .skip_unhandled_events = true,
+ };
+
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us
+ }
+
+ void EnableLcdCs() {
+ if(io_expander_ != NULL) {
+ esp_io_expander_set_level(io_expander_, DISPLAY_SPI_CS_PIN, 0);// 缃綆 LCD CS
+ }
+ }
+
+ 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_SCLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_SPI_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 60 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_RESET_PIN;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ EnableLcdCs();
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+
+ // uint8_t data_0xBB[] = { 0x3F };
+ // esp_lcd_panel_io_tx_param(panel_io, 0xBB, data_0xBB, sizeof(data_0xBB));
+
+ uint8_t data_0xBB[] = { 0x38 };
+ esp_lcd_panel_io_tx_param(panel_io, 0xBB, data_0xBB, sizeof(data_0xBB));
+
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+
+ Spotpear_esp32_s3_lcd_1_54() :boot_button_(BOOT_BUTTON_GPIO){
+ gpio_set_direction(TP_PIN_NUM_TP_INT, GPIO_MODE_INPUT);
+ int level = gpio_get_level(TP_PIN_NUM_TP_INT);
+ if (level == 1) {
+ InitializeCodecI2c_Touch();
+ InitializeCst816DTouchPad();
+ }
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeSpi();
+ InitializePowerManager();
+ InitializeSt7789Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led_strip(BUILTIN_LED_GPIO);
+ return &led_strip;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ 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 Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ Cst816d* GetTouchpad() {
+ return cst816d_;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(Spotpear_esp32_s3_lcd_1_54);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/README.md b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/README.md
new file mode 100644
index 0000000..9a6d211
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/README.md
@@ -0,0 +1,5 @@
+## Surfer-ESP32-C3 寮�鍙戞澘
+
+1銆佸弬鑰冪珛鍒浡峰疄鎴樻淳C3-ESP32C3寮�鍙戞澘锛屼慨鏀逛簡TFT灞忓箷鑳屽厜寮曡剼锛屽鍔燗DC鐢垫睜鐢甸噺妫�娴嬪姛鑳�;
+2銆佽寮�鍙戞澘 flash 澶у皬涓� 16MB锛岀紪璇戞椂娉ㄦ剰閫夋嫨榛樿鐨勫垎鍖鸿〃銆�
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.h b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.h
new file mode 100644
index 0000000..7970330
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.h
@@ -0,0 +1,45 @@
+#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_USE_PCA9557
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#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 AUDIO_CODEC_ES7210_ADDR 0x82
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#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 240
+#define DISPLAY_HEIGHT 135
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+
+#define DISPLAY_OFFSET_X 40
+#define DISPLAY_OFFSET_Y 53
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.json b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.json
new file mode 100644
index 0000000..a8f30ad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "surfer-c3-1.14tft",
+ "sdkconfig_append": [
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/power_manager.h
new file mode 100644
index 0000000..22643b8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/power_manager.h
@@ -0,0 +1,201 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_2, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {3060, 0},
+ {3200, 20},
+ {3340, 40},
+ {3480, 60},
+ {3620, 80},
+ {3760, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+
+
+ if (charging_pin_ != GPIO_NUM_NC) {
+ // 涓嶅垵濮嬪寲 ADC锛屼笉妫�娴�
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_2_5,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_2, &chan_config));
+ ESP_LOGI("PowerManager ADC INIT------------------",
+ "ADC atten: ADC_ATTEN_DB_2_5, bitwidth: ADC_BITWIDTH_DEFAULT ADC chan: ADC_CHANNEL_2");
+
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ if (charging_pin_ == GPIO_NUM_NC) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ if (charging_pin_ == GPIO_NUM_NC) {
+ return false;
+ }
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc
new file mode 100644
index 0000000..7952ece
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/surfer-c3-1.14tft/surfer-c3-1.14tft.cc
@@ -0,0 +1,210 @@
+#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 "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_wifi.h>
+
+#include "power_save_timer.h"
+#include "power_manager.h"
+// #include <esp_sleep.h>
+
+#define TAG "SURFERC3114TFT"
+
+class SurferC3114TFT : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ // Print I2C bus info
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_NC);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+
+ }
+
+ void InitializePowerSaveTimer() {
+ //瀹氭椂鍣紝璋冩暣璁惧涓簃odem-sleep妯″紡鍜屽睆骞曚寒搴�
+ power_save_timer_ = new PowerSaveTimer(-1, 60, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ ESP_LOGI(TAG, "Enabling modem-sleep mode");
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ esp_wifi_set_ps(WIFI_PS_MIN_MODEM);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ esp_wifi_set_ps(WIFI_PS_NONE); // 鍏抽棴Wi-Fi鐪佺數锛屾仮澶嶆甯�
+ // esp_lcd_panel_disp_on_off(panel_, true); // 閲嶆柊鎵撳紑鏄剧ず
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down display");
+ GetBacklight()->SetBrightness(1);
+ // esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ // power_save_timer_->SetEnabled(false); // 绂佺敤瀹氭椂鍣紝闃叉閲嶅
+
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ 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 InitializeSt7789Display() {
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ 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 = 0;
+ 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_));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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);
+ }
+
+public:
+ SurferC3114TFT() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+
+ GetBacklight()->RestoreBrightness();
+
+ // 鎶� ESP32C3 鐨� VDD SPI 寮曡剼浣滀负鏅�� GPIO 鍙d娇鐢�
+ esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
+ }
+
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+};
+
+DECLARE_BOARD(SurferC3114TFT);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/README.md
new file mode 100644
index 0000000..c3e7b2f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/README.md
@@ -0,0 +1,45 @@
+# 鐢变簬鍘熸潵鐨勯害鍏嬮鍨嬪彿鍋滀骇锛�2025骞�7鏈堜箣鍚庣殑澶瀬娲撅紙JC3636W518锛夋洿鎹簡楹﹀厠椋庯紝骞朵笖鏇存崲浜嗗睆骞曠幓鐠冿紝鎵�浠ュ湪浜у搧鏍囩涓婃壒娆″彿澶т簬2528鐨勭敤鎴疯閫夋嫨I2S Type PDM,
+
+# 鏂板鍙屽0閬撻厤缃�
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> 澶瀬灏忔淳esp32s3
+
+Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> taiji-pi-S3 I2S Type -> I2S Type PDM
+```
+
+**濡傛灉闇�瑕侀�夋嫨鍙屽0閬擄細**
+```
+
+Xiaozhi Assistant -> TAIJIPAI_S3_CONFIG -> Enabel use 2 slot
+```
+
+
+**淇敼PSRAM閰嶇疆锛�**
+
+```
+component config -> ESP PSRAM -> SPI RAM config -> Try to allocate memories of WiFi and LWIP in SPIRAM firstly. If failed, allocate internal memory
+
+```
+
+**缂栬瘧锛�**
+
+```bash
+idf.py build
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.h
new file mode 100644
index 0000000..c07d620
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.h
@@ -0,0 +1,66 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+// Taiji Pi S3 Board configuration
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_DEFAULT_OUTPUT_VOLUME 80
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_21
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_16
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_18
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_NC
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_17
+#define AUDIO_MUTE_PIN GPIO_NUM_48 // 浣庣數骞抽潤闊�
+
+#define AUDIO_MIC_WS_PIN GPIO_NUM_45
+#define AUDIO_MIC_SD_PIN GPIO_NUM_46
+#define AUDIO_MIC_SCK_PIN GPIO_NUM_42
+
+#define DISPLAY_WIDTH 360
+#define DISPLAY_HEIGHT 360
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+
+#define QSPI_LCD_H_RES (360)
+#define QSPI_LCD_V_RES (360)
+#define QSPI_LCD_BIT_PER_PIXEL (16)
+
+#define QSPI_LCD_HOST SPI2_HOST
+#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_9
+#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_10
+#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_11
+#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_12
+#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_13
+#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_14
+#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_47
+#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_15
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define TP_PORT (I2C_NUM_1)
+#define TP_PIN_NUM_TP_SDA (GPIO_NUM_7)
+#define TP_PIN_NUM_TP_SCL (GPIO_NUM_8)
+#define TP_PIN_NUM_TP_RST (GPIO_NUM_40)
+#define TP_PIN_NUM_TP_INT (GPIO_NUM_41)
+
+#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
+ { \
+ .data0_io_num = d0, \
+ .data1_io_num = d1, \
+ .sclk_io_num = sclk, \
+ .data2_io_num = d2, \
+ .data3_io_num = d3, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.json
new file mode 100644
index 0000000..5b81784
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/config.json
@@ -0,0 +1,19 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "taiji-pi-s3",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y",
+ "CONFIG_TAIJIPAI_I2S_TYPE_STD=y"
+ ]
+ },
+ {
+ "name": "taiji-pi-s3-pdm",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y",
+ "CONFIG_TAIJIPAI_I2S_TYPE_PDM=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/taiji_pi_s3.cc b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/taiji_pi_s3.cc
new file mode 100644
index 0000000..06635a7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/taiji-pi-s3/taiji_pi_s3.cc
@@ -0,0 +1,663 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "i2c_device.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_st77916.h>
+#include <esp_timer.h>
+
+#define TAG "TaijiPiS3Board"
+
+static const st77916_lcd_init_cmd_t lcd_init_cmds[] = {
+#ifdef CONFIG_TAIJIPAI_I2S_TYPE_STD
+ {0xF0, (uint8_t[]){0x08}, 1, 0},
+ {0xF2, (uint8_t[]){0x08}, 1, 0},
+ {0x9B, (uint8_t[]){0x51}, 1, 0},
+ {0x86, (uint8_t[]){0x53}, 1, 0},
+ {0xF2, (uint8_t[]){0x80}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0xF0, (uint8_t[]){0x01}, 1, 0},
+ {0xF1, (uint8_t[]){0x01}, 1, 0},
+ {0xB0, (uint8_t[]){0x54}, 1, 0},
+ {0xB1, (uint8_t[]){0x3F}, 1, 0},
+ {0xB2, (uint8_t[]){0x2A}, 1, 0},
+ {0xB4, (uint8_t[]){0x46}, 1, 0},
+ {0xB5, (uint8_t[]){0x34}, 1, 0},
+ {0xB6, (uint8_t[]){0xD5}, 1, 0},
+ {0xB7, (uint8_t[]){0x30}, 1, 0},
+ {0xBA, (uint8_t[]){0x00}, 1, 0},
+ {0xBB, (uint8_t[]){0x08}, 1, 0},
+ {0xBC, (uint8_t[]){0x08}, 1, 0},
+ {0xBD, (uint8_t[]){0x00}, 1, 0},
+ {0xC0, (uint8_t[]){0x80}, 1, 0},
+ {0xC1, (uint8_t[]){0x10}, 1, 0},
+ {0xC2, (uint8_t[]){0x37}, 1, 0},
+ {0xC3, (uint8_t[]){0x80}, 1, 0},
+ {0xC4, (uint8_t[]){0x10}, 1, 0},
+ {0xC5, (uint8_t[]){0x37}, 1, 0},
+ {0xC6, (uint8_t[]){0xA9}, 1, 0},
+ {0xC7, (uint8_t[]){0x41}, 1, 0},
+ {0xC8, (uint8_t[]){0x51}, 1, 0},
+ {0xC9, (uint8_t[]){0xA9}, 1, 0},
+ {0xCA, (uint8_t[]){0x41}, 1, 0},
+ {0xCB, (uint8_t[]){0x51}, 1, 0},
+ {0xD0, (uint8_t[]){0x91}, 1, 0},
+ {0xD1, (uint8_t[]){0x68}, 1, 0},
+ {0xD2, (uint8_t[]){0x69}, 1, 0},
+ {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0},
+ {0xDD, (uint8_t[]){0x3F}, 1, 0},
+ {0xDE, (uint8_t[]){0x3F}, 1, 0},
+ {0xF1, (uint8_t[]){0x10}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0xF0, (uint8_t[]){0x02}, 1, 0},
+ {0xE0, (uint8_t[]){0x70, 0x09, 0x12, 0x0C, 0x0B, 0x27, 0x38, 0x54, 0x4E, 0x19, 0x15, 0x15, 0x2C, 0x2F}, 14, 0},
+ {0xE1, (uint8_t[]){0x70, 0x08, 0x11, 0x0C, 0x0B, 0x27, 0x38, 0x43, 0x4C, 0x18, 0x14, 0x14, 0x2B, 0x2D}, 14, 0},
+ {0xF0, (uint8_t[]){0x10}, 1, 0},
+ {0xF3, (uint8_t[]){0x10}, 1, 0},
+ {0xE0, (uint8_t[]){0x08}, 1, 0},
+ {0xE1, (uint8_t[]){0x00}, 1, 0},
+ {0xE2, (uint8_t[]){0x00}, 1, 0},
+ {0xE3, (uint8_t[]){0x00}, 1, 0},
+ {0xE4, (uint8_t[]){0xE0}, 1, 0},
+ {0xE5, (uint8_t[]){0x06}, 1, 0},
+ {0xE6, (uint8_t[]){0x21}, 1, 0},
+ {0xE7, (uint8_t[]){0x00}, 1, 0},
+ {0xE8, (uint8_t[]){0x05}, 1, 0},
+ {0xE9, (uint8_t[]){0x82}, 1, 0},
+ {0xEA, (uint8_t[]){0xDF}, 1, 0},
+ {0xEB, (uint8_t[]){0x89}, 1, 0},
+ {0xEC, (uint8_t[]){0x20}, 1, 0},
+ {0xED, (uint8_t[]){0x14}, 1, 0},
+ {0xEE, (uint8_t[]){0xFF}, 1, 0},
+ {0xEF, (uint8_t[]){0x00}, 1, 0},
+ {0xF8, (uint8_t[]){0xFF}, 1, 0},
+ {0xF9, (uint8_t[]){0x00}, 1, 0},
+ {0xFA, (uint8_t[]){0x00}, 1, 0},
+ {0xFB, (uint8_t[]){0x30}, 1, 0},
+ {0xFC, (uint8_t[]){0x00}, 1, 0},
+ {0xFD, (uint8_t[]){0x00}, 1, 0},
+ {0xFE, (uint8_t[]){0x00}, 1, 0},
+ {0xFF, (uint8_t[]){0x00}, 1, 0},
+ {0x60, (uint8_t[]){0x42}, 1, 0},
+ {0x61, (uint8_t[]){0xE0}, 1, 0},
+ {0x62, (uint8_t[]){0x40}, 1, 0},
+ {0x63, (uint8_t[]){0x40}, 1, 0},
+ {0x64, (uint8_t[]){0x02}, 1, 0},
+ {0x65, (uint8_t[]){0x00}, 1, 0},
+ {0x66, (uint8_t[]){0x40}, 1, 0},
+ {0x67, (uint8_t[]){0x03}, 1, 0},
+ {0x68, (uint8_t[]){0x00}, 1, 0},
+ {0x69, (uint8_t[]){0x00}, 1, 0},
+ {0x6A, (uint8_t[]){0x00}, 1, 0},
+ {0x6B, (uint8_t[]){0x00}, 1, 0},
+ {0x70, (uint8_t[]){0x42}, 1, 0},
+ {0x71, (uint8_t[]){0xE0}, 1, 0},
+ {0x72, (uint8_t[]){0x40}, 1, 0},
+ {0x73, (uint8_t[]){0x40}, 1, 0},
+ {0x74, (uint8_t[]){0x02}, 1, 0},
+ {0x75, (uint8_t[]){0x00}, 1, 0},
+ {0x76, (uint8_t[]){0x40}, 1, 0},
+ {0x77, (uint8_t[]){0x03}, 1, 0},
+ {0x78, (uint8_t[]){0x00}, 1, 0},
+ {0x79, (uint8_t[]){0x00}, 1, 0},
+ {0x7A, (uint8_t[]){0x00}, 1, 0},
+ {0x7B, (uint8_t[]){0x00}, 1, 0},
+ {0x80, (uint8_t[]){0x48}, 1, 0},
+ {0x81, (uint8_t[]){0x00}, 1, 0},
+ {0x82, (uint8_t[]){0x05}, 1, 0},
+ {0x83, (uint8_t[]){0x02}, 1, 0},
+ {0x84, (uint8_t[]){0xDD}, 1, 0},
+ {0x85, (uint8_t[]){0x00}, 1, 0},
+ {0x86, (uint8_t[]){0x00}, 1, 0},
+ {0x87, (uint8_t[]){0x00}, 1, 0},
+ {0x88, (uint8_t[]){0x48}, 1, 0},
+ {0x89, (uint8_t[]){0x00}, 1, 0},
+ {0x8A, (uint8_t[]){0x07}, 1, 0},
+ {0x8B, (uint8_t[]){0x02}, 1, 0},
+ {0x8C, (uint8_t[]){0xDF}, 1, 0},
+ {0x8D, (uint8_t[]){0x00}, 1, 0},
+ {0x8E, (uint8_t[]){0x00}, 1, 0},
+ {0x8F, (uint8_t[]){0x00}, 1, 0},
+ {0x90, (uint8_t[]){0x48}, 1, 0},
+ {0x91, (uint8_t[]){0x00}, 1, 0},
+ {0x92, (uint8_t[]){0x09}, 1, 0},
+ {0x93, (uint8_t[]){0x02}, 1, 0},
+ {0x94, (uint8_t[]){0xE1}, 1, 0},
+ {0x95, (uint8_t[]){0x00}, 1, 0},
+ {0x96, (uint8_t[]){0x00}, 1, 0},
+ {0x97, (uint8_t[]){0x00}, 1, 0},
+ {0x98, (uint8_t[]){0x48}, 1, 0},
+ {0x99, (uint8_t[]){0x00}, 1, 0},
+ {0x9A, (uint8_t[]){0x0B}, 1, 0},
+ {0x9B, (uint8_t[]){0x02}, 1, 0},
+ {0x9C, (uint8_t[]){0xE3}, 1, 0},
+ {0x9D, (uint8_t[]){0x00}, 1, 0},
+ {0x9E, (uint8_t[]){0x00}, 1, 0},
+ {0x9F, (uint8_t[]){0x00}, 1, 0},
+ {0xA0, (uint8_t[]){0x48}, 1, 0},
+ {0xA1, (uint8_t[]){0x00}, 1, 0},
+ {0xA2, (uint8_t[]){0x04}, 1, 0},
+ {0xA3, (uint8_t[]){0x02}, 1, 0},
+ {0xA4, (uint8_t[]){0xDC}, 1, 0},
+ {0xA5, (uint8_t[]){0x00}, 1, 0},
+ {0xA6, (uint8_t[]){0x00}, 1, 0},
+ {0xA7, (uint8_t[]){0x00}, 1, 0},
+ {0xA8, (uint8_t[]){0x48}, 1, 0},
+ {0xA9, (uint8_t[]){0x00}, 1, 0},
+ {0xAA, (uint8_t[]){0x06}, 1, 0},
+ {0xAB, (uint8_t[]){0x02}, 1, 0},
+ {0xAC, (uint8_t[]){0xDE}, 1, 0},
+ {0xAD, (uint8_t[]){0x00}, 1, 0},
+ {0xAE, (uint8_t[]){0x00}, 1, 0},
+ {0xAF, (uint8_t[]){0x00}, 1, 0},
+ {0xB0, (uint8_t[]){0x48}, 1, 0},
+ {0xB1, (uint8_t[]){0x00}, 1, 0},
+ {0xB2, (uint8_t[]){0x08}, 1, 0},
+ {0xB3, (uint8_t[]){0x02}, 1, 0},
+ {0xB4, (uint8_t[]){0xE0}, 1, 0},
+ {0xB5, (uint8_t[]){0x00}, 1, 0},
+ {0xB6, (uint8_t[]){0x00}, 1, 0},
+ {0xB7, (uint8_t[]){0x00}, 1, 0},
+ {0xB8, (uint8_t[]){0x48}, 1, 0},
+ {0xB9, (uint8_t[]){0x00}, 1, 0},
+ {0xBA, (uint8_t[]){0x0A}, 1, 0},
+ {0xBB, (uint8_t[]){0x02}, 1, 0},
+ {0xBC, (uint8_t[]){0xE2}, 1, 0},
+ {0xBD, (uint8_t[]){0x00}, 1, 0},
+ {0xBE, (uint8_t[]){0x00}, 1, 0},
+ {0xBF, (uint8_t[]){0x00}, 1, 0},
+ {0xC0, (uint8_t[]){0x12}, 1, 0},
+ {0xC1, (uint8_t[]){0xAA}, 1, 0},
+ {0xC2, (uint8_t[]){0x65}, 1, 0},
+ {0xC3, (uint8_t[]){0x74}, 1, 0},
+ {0xC4, (uint8_t[]){0x47}, 1, 0},
+ {0xC5, (uint8_t[]){0x56}, 1, 0},
+ {0xC6, (uint8_t[]){0x00}, 1, 0},
+ {0xC7, (uint8_t[]){0x88}, 1, 0},
+ {0xC8, (uint8_t[]){0x99}, 1, 0},
+ {0xC9, (uint8_t[]){0x33}, 1, 0},
+ {0xD0, (uint8_t[]){0x21}, 1, 0},
+ {0xD1, (uint8_t[]){0xAA}, 1, 0},
+ {0xD2, (uint8_t[]){0x65}, 1, 0},
+ {0xD3, (uint8_t[]){0x74}, 1, 0},
+ {0xD4, (uint8_t[]){0x47}, 1, 0},
+ {0xD5, (uint8_t[]){0x56}, 1, 0},
+ {0xD6, (uint8_t[]){0x00}, 1, 0},
+ {0xD7, (uint8_t[]){0x88}, 1, 0},
+ {0xD8, (uint8_t[]){0x99}, 1, 0},
+ {0xD9, (uint8_t[]){0x33}, 1, 0},
+ {0xF3, (uint8_t[]){0x01}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0xF0, (uint8_t[]){0x01}, 1, 0},
+ {0xF1, (uint8_t[]){0x01}, 1, 0},
+ {0xA0, (uint8_t[]){0x0B}, 1, 0},
+ {0xA3, (uint8_t[]){0x2A}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x2B}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x2C}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x2D}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x2E}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x2F}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x30}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x31}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x32}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA3, (uint8_t[]){0x33}, 1, 0},
+ {0xA5, (uint8_t[]){0xC3}, 1, 1},
+ {0xA0, (uint8_t[]){0x09}, 1, 0},
+ {0xF1, (uint8_t[]){0x10}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
+ {0x2B, (uint8_t[]){0x01, 0x68, 0x01, 0x68}, 4, 0},
+ {0x4D, (uint8_t[]){0x00}, 1, 0},
+ {0x4E, (uint8_t[]){0x00}, 1, 0},
+ {0x4F, (uint8_t[]){0x00}, 1, 0},
+ {0x4C, (uint8_t[]){0x01}, 1, 10},
+ {0x4C, (uint8_t[]){0x00}, 1, 0},
+ {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x67}, 4, 0},
+ {0x21, (uint8_t[]){0x00}, 1, 0},
+ //{0x3A, (uint8_t[]){0x55}, 1, 0}, // color=16
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+ {0x29, (uint8_t[]){0x00}, 1, 0},
+#else
+ {0xF0, (uint8_t[]){0x28}, 1, 0},
+ {0xF2, (uint8_t[]){0x28}, 1, 0},
+ {0x73, (uint8_t[]){0xF0}, 1, 0},
+ {0x7C, (uint8_t[]){0xD1}, 1, 0},
+ {0x83, (uint8_t[]){0xE0}, 1, 0},
+ {0x84, (uint8_t[]){0x61}, 1, 0},
+ {0xF2, (uint8_t[]){0x82}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0xF0, (uint8_t[]){0x01}, 1, 0},
+ {0xF1, (uint8_t[]){0x01}, 1, 0},
+ {0xB0, (uint8_t[]){0x56}, 1, 0},
+ {0xB1, (uint8_t[]){0x4D}, 1, 0},
+ {0xB2, (uint8_t[]){0x24}, 1, 0},
+ {0xB4, (uint8_t[]){0x87}, 1, 0},
+ {0xB5, (uint8_t[]){0x44}, 1, 0},
+ {0xB6, (uint8_t[]){0x8B}, 1, 0},
+ {0xB7, (uint8_t[]){0x40}, 1, 0},
+ {0xB8, (uint8_t[]){0x86}, 1, 0},
+ {0xBA, (uint8_t[]){0x00}, 1, 0},
+ {0xBB, (uint8_t[]){0x08}, 1, 0},
+ {0xBC, (uint8_t[]){0x08}, 1, 0},
+ {0xBD, (uint8_t[]){0x00}, 1, 0},
+ {0xC0, (uint8_t[]){0x80}, 1, 0},
+ {0xC1, (uint8_t[]){0x10}, 1, 0},
+ {0xC2, (uint8_t[]){0x37}, 1, 0},
+ {0xC3, (uint8_t[]){0x80}, 1, 0},
+ {0xC4, (uint8_t[]){0x10}, 1, 0},
+ {0xC5, (uint8_t[]){0x37}, 1, 0},
+ {0xC6, (uint8_t[]){0xA9}, 1, 0},
+ {0xC7, (uint8_t[]){0x41}, 1, 0},
+ {0xC8, (uint8_t[]){0x01}, 1, 0},
+ {0xC9, (uint8_t[]){0xA9}, 1, 0},
+ {0xCA, (uint8_t[]){0x41}, 1, 0},
+ {0xCB, (uint8_t[]){0x01}, 1, 0},
+ {0xD0, (uint8_t[]){0x91}, 1, 0},
+ {0xD1, (uint8_t[]){0x68}, 1, 0},
+ {0xD2, (uint8_t[]){0x68}, 1, 0},
+ {0xF5, (uint8_t[]){0x00, 0xA5}, 2, 0},
+ {0xDD, (uint8_t[]){0x4F}, 1, 0},
+ {0xDE, (uint8_t[]){0x4F}, 1, 0},
+ {0xF1, (uint8_t[]){0x10}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0xF0, (uint8_t[]){0x02}, 1, 0},
+ {0xE0, (uint8_t[]){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0},
+ {0xE1, (uint8_t[]){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0},
+ {0xF0, (uint8_t[]){0x10}, 1, 0},
+ {0xF3, (uint8_t[]){0x10}, 1, 0},
+ {0xE0, (uint8_t[]){0x07}, 1, 0},
+ {0xE1, (uint8_t[]){0x00}, 1, 0},
+ {0xE2, (uint8_t[]){0x00}, 1, 0},
+ {0xE3, (uint8_t[]){0x00}, 1, 0},
+ {0xE4, (uint8_t[]){0xE0}, 1, 0},
+ {0xE5, (uint8_t[]){0x06}, 1, 0},
+ {0xE6, (uint8_t[]){0x21}, 1, 0},
+ {0xE7, (uint8_t[]){0x01}, 1, 0},
+ {0xE8, (uint8_t[]){0x05}, 1, 0},
+ {0xE9, (uint8_t[]){0x02}, 1, 0},
+ {0xEA, (uint8_t[]){0xDA}, 1, 0},
+ {0xEB, (uint8_t[]){0x00}, 1, 0},
+ {0xEC, (uint8_t[]){0x00}, 1, 0},
+ {0xED, (uint8_t[]){0x0F}, 1, 0},
+ {0xEE, (uint8_t[]){0x00}, 1, 0},
+ {0xEF, (uint8_t[]){0x00}, 1, 0},
+ {0xF8, (uint8_t[]){0x00}, 1, 0},
+ {0xF9, (uint8_t[]){0x00}, 1, 0},
+ {0xFA, (uint8_t[]){0x00}, 1, 0},
+ {0xFB, (uint8_t[]){0x00}, 1, 0},
+ {0xFC, (uint8_t[]){0x00}, 1, 0},
+ {0xFD, (uint8_t[]){0x00}, 1, 0},
+ {0xFE, (uint8_t[]){0x00}, 1, 0},
+ {0xFF, (uint8_t[]){0x00}, 1, 0},
+ {0x60, (uint8_t[]){0x40}, 1, 0},
+ {0x61, (uint8_t[]){0x04}, 1, 0},
+ {0x62, (uint8_t[]){0x00}, 1, 0},
+ {0x63, (uint8_t[]){0x42}, 1, 0},
+ {0x64, (uint8_t[]){0xD9}, 1, 0},
+ {0x65, (uint8_t[]){0x00}, 1, 0},
+ {0x66, (uint8_t[]){0x00}, 1, 0},
+ {0x67, (uint8_t[]){0x00}, 1, 0},
+ {0x68, (uint8_t[]){0x00}, 1, 0},
+ {0x69, (uint8_t[]){0x00}, 1, 0},
+ {0x6A, (uint8_t[]){0x00}, 1, 0},
+ {0x6B, (uint8_t[]){0x00}, 1, 0},
+ {0x70, (uint8_t[]){0x40}, 1, 0},
+ {0x71, (uint8_t[]){0x03}, 1, 0},
+ {0x72, (uint8_t[]){0x00}, 1, 0},
+ {0x73, (uint8_t[]){0x42}, 1, 0},
+ {0x74, (uint8_t[]){0xD8}, 1, 0},
+ {0x75, (uint8_t[]){0x00}, 1, 0},
+ {0x76, (uint8_t[]){0x00}, 1, 0},
+ {0x77, (uint8_t[]){0x00}, 1, 0},
+ {0x78, (uint8_t[]){0x00}, 1, 0},
+ {0x79, (uint8_t[]){0x00}, 1, 0},
+ {0x7A, (uint8_t[]){0x00}, 1, 0},
+ {0x7B, (uint8_t[]){0x00}, 1, 0},
+ {0x80, (uint8_t[]){0x48}, 1, 0},
+ {0x81, (uint8_t[]){0x00}, 1, 0},
+ {0x82, (uint8_t[]){0x06}, 1, 0},
+ {0x83, (uint8_t[]){0x02}, 1, 0},
+ {0x84, (uint8_t[]){0xD6}, 1, 0},
+ {0x85, (uint8_t[]){0x04}, 1, 0},
+ {0x86, (uint8_t[]){0x00}, 1, 0},
+ {0x87, (uint8_t[]){0x00}, 1, 0},
+ {0x88, (uint8_t[]){0x48}, 1, 0},
+ {0x89, (uint8_t[]){0x00}, 1, 0},
+ {0x8A, (uint8_t[]){0x08}, 1, 0},
+ {0x8B, (uint8_t[]){0x02}, 1, 0},
+ {0x8C, (uint8_t[]){0xD8}, 1, 0},
+ {0x8D, (uint8_t[]){0x04}, 1, 0},
+ {0x8E, (uint8_t[]){0x00}, 1, 0},
+ {0x8F, (uint8_t[]){0x00}, 1, 0},
+ {0x90, (uint8_t[]){0x48}, 1, 0},
+ {0x91, (uint8_t[]){0x00}, 1, 0},
+ {0x92, (uint8_t[]){0x0A}, 1, 0},
+ {0x93, (uint8_t[]){0x02}, 1, 0},
+ {0x94, (uint8_t[]){0xDA}, 1, 0},
+ {0x95, (uint8_t[]){0x04}, 1, 0},
+ {0x96, (uint8_t[]){0x00}, 1, 0},
+ {0x97, (uint8_t[]){0x00}, 1, 0},
+ {0x98, (uint8_t[]){0x48}, 1, 0},
+ {0x99, (uint8_t[]){0x00}, 1, 0},
+ {0x9A, (uint8_t[]){0x0C}, 1, 0},
+ {0x9B, (uint8_t[]){0x02}, 1, 0},
+ {0x9C, (uint8_t[]){0xDC}, 1, 0},
+ {0x9D, (uint8_t[]){0x04}, 1, 0},
+ {0x9E, (uint8_t[]){0x00}, 1, 0},
+ {0x9F, (uint8_t[]){0x00}, 1, 0},
+ {0xA0, (uint8_t[]){0x48}, 1, 0},
+ {0xA1, (uint8_t[]){0x00}, 1, 0},
+ {0xA2, (uint8_t[]){0x05}, 1, 0},
+ {0xA3, (uint8_t[]){0x02}, 1, 0},
+ {0xA4, (uint8_t[]){0xD5}, 1, 0},
+ {0xA5, (uint8_t[]){0x04}, 1, 0},
+ {0xA6, (uint8_t[]){0x00}, 1, 0},
+ {0xA7, (uint8_t[]){0x00}, 1, 0},
+ {0xA8, (uint8_t[]){0x48}, 1, 0},
+ {0xA9, (uint8_t[]){0x00}, 1, 0},
+ {0xAA, (uint8_t[]){0x07}, 1, 0},
+ {0xAB, (uint8_t[]){0x02}, 1, 0},
+ {0xAC, (uint8_t[]){0xD7}, 1, 0},
+ {0xAD, (uint8_t[]){0x04}, 1, 0},
+ {0xAE, (uint8_t[]){0x00}, 1, 0},
+ {0xAF, (uint8_t[]){0x00}, 1, 0},
+ {0xB0, (uint8_t[]){0x48}, 1, 0},
+ {0xB1, (uint8_t[]){0x00}, 1, 0},
+ {0xB2, (uint8_t[]){0x09}, 1, 0},
+ {0xB3, (uint8_t[]){0x02}, 1, 0},
+ {0xB4, (uint8_t[]){0xD9}, 1, 0},
+ {0xB5, (uint8_t[]){0x04}, 1, 0},
+ {0xB6, (uint8_t[]){0x00}, 1, 0},
+ {0xB7, (uint8_t[]){0x00}, 1, 0},
+ {0xB8, (uint8_t[]){0x48}, 1, 0},
+ {0xB9, (uint8_t[]){0x00}, 1, 0},
+ {0xBA, (uint8_t[]){0x0B}, 1, 0},
+ {0xBB, (uint8_t[]){0x02}, 1, 0},
+ {0xBC, (uint8_t[]){0xDB}, 1, 0},
+ {0xBD, (uint8_t[]){0x04}, 1, 0},
+ {0xBE, (uint8_t[]){0x00}, 1, 0},
+ {0xBF, (uint8_t[]){0x00}, 1, 0},
+ {0xC0, (uint8_t[]){0x10}, 1, 0},
+ {0xC1, (uint8_t[]){0x47}, 1, 0},
+ {0xC2, (uint8_t[]){0x56}, 1, 0},
+ {0xC3, (uint8_t[]){0x65}, 1, 0},
+ {0xC4, (uint8_t[]){0x74}, 1, 0},
+ {0xC5, (uint8_t[]){0x88}, 1, 0},
+ {0xC6, (uint8_t[]){0x99}, 1, 0},
+ {0xC7, (uint8_t[]){0x01}, 1, 0},
+ {0xC8, (uint8_t[]){0xBB}, 1, 0},
+ {0xC9, (uint8_t[]){0xAA}, 1, 0},
+ {0xD0, (uint8_t[]){0x10}, 1, 0},
+ {0xD1, (uint8_t[]){0x47}, 1, 0},
+ {0xD2, (uint8_t[]){0x56}, 1, 0},
+ {0xD3, (uint8_t[]){0x65}, 1, 0},
+ {0xD4, (uint8_t[]){0x74}, 1, 0},
+ {0xD5, (uint8_t[]){0x88}, 1, 0},
+ {0xD6, (uint8_t[]){0x99}, 1, 0},
+ {0xD7, (uint8_t[]){0x01}, 1, 0},
+ {0xD8, (uint8_t[]){0xBB}, 1, 0},
+ {0xD9, (uint8_t[]){0xAA}, 1, 0},
+ {0xF3, (uint8_t[]){0x01}, 1, 0},
+ {0xF0, (uint8_t[]){0x00}, 1, 0},
+ {0x21, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+ {0x29, (uint8_t[]){0x00}, 1, 0},
+#endif
+};
+
+class Cst816s : public I2cDevice {
+public:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+ Cst816s(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA3);
+ ESP_LOGI(TAG, "Get chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+ }
+
+ ~Cst816s() {
+ delete[] read_buffer_;
+ }
+
+ void UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ }
+
+ const TouchPoint_t& GetTouchPoint() {
+ return tp_;
+ }
+
+private:
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+};
+
+class TaijiPiS3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Cst816s* cst816s_;
+ LcdDisplay* display_;
+ esp_timer_handle_t touchpad_timer_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .sda_io_num = TP_PIN_NUM_TP_SDA,
+ .scl_io_num = TP_PIN_NUM_TP_SCL,
+ .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, &i2c_bus_));
+ }
+
+ static void touchpad_timer_callback(void* arg) {
+ auto& board = (TaijiPiS3Board&)Board::GetInstance();
+ auto touchpad = board.GetTouchpad();
+ static bool was_touched = false;
+ static int64_t touch_start_time = 0;
+ const int64_t TOUCH_THRESHOLD_MS = 500; // 瑙︽懜鏃堕暱闃堝�硷紝瓒呰繃500ms瑙嗕负闀挎寜
+
+ touchpad->UpdateTouchPoint();
+ auto touch_point = touchpad->GetTouchPoint();
+
+ // 妫�娴嬭Е鎽稿紑濮�
+ if (touch_point.num > 0 && !was_touched) {
+ was_touched = true;
+ touch_start_time = esp_timer_get_time() / 1000; // 杞崲涓烘绉�
+ }
+ // 妫�娴嬭Е鎽搁噴鏀�
+ else if (touch_point.num == 0 && was_touched) {
+ was_touched = false;
+ int64_t touch_duration = (esp_timer_get_time() / 1000) - touch_start_time;
+
+ // 鍙湁鐭Е鎵嶈Е鍙�
+ if (touch_duration < TOUCH_THRESHOLD_MS) {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ board.EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }
+ }
+ }
+
+ void InitializeCst816sTouchPad() {
+ ESP_LOGI(TAG, "Init Cst816s");
+ cst816s_ = new Cst816s(i2c_bus_, 0x15);
+
+ // 鍒涘缓瀹氭椂鍣紝10ms 闂撮殧
+ esp_timer_create_args_t timer_args = {
+ .callback = touchpad_timer_callback,
+ .arg = NULL,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "touchpad_timer",
+ .skip_unhandled_events = true,
+ };
+
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &touchpad_timer_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(touchpad_timer_, 10 * 1000)); // 10ms = 10000us
+ }
+
+ void BspLcdBlSet(int brightness_percent)
+ {
+ if (brightness_percent > 100) {
+ brightness_percent = 100;
+ }
+ if (brightness_percent < 0) {
+ brightness_percent = 0;
+ }
+
+ ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness_percent);
+ uint32_t duty_cycle = (1023 * brightness_percent) / 100; // LEDC resolution set to 10bits, thus: 100% = 1023
+ ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle);
+ ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+
+ const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK,
+ QSPI_PIN_NUM_LCD_DATA0,
+ QSPI_PIN_NUM_LCD_DATA1,
+ QSPI_PIN_NUM_LCD_DATA2,
+ QSPI_PIN_NUM_LCD_DATA3,
+ QSPI_LCD_H_RES * 80 * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));
+ }
+
+ void Initializest77916Display() {
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGI(TAG, "Install panel IO");
+
+ const esp_lcd_panel_io_spi_config_t io_config = ST77916_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io));
+
+ ESP_LOGI(TAG, "Install ST77916 panel driver");
+
+ st77916_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds, // 濡傛灉浣跨敤鑷畾涔夊垵濮嬪寲鍛戒护锛岃鍙栨秷娉ㄩ噴杩欎簺琛�
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(st77916_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = QSPI_PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h`
+ .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18)
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_disp_on_off(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 InitializeMute() {
+ gpio_reset_pin(AUDIO_MUTE_PIN);
+ /* Set the GPIO as a push/pull output */
+ gpio_set_direction(AUDIO_MUTE_PIN, GPIO_MODE_OUTPUT);
+ gpio_set_level(AUDIO_MUTE_PIN, 1);
+ }
+
+public:
+ TaijiPiS3Board() {
+ InitializeI2c();
+ InitializeCst816sTouchPad();
+ InitializeSpi();
+ Initializest77916Display();
+ InitializeMute();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+#ifdef CONFIG_TAIJIPAI_I2S_TYPE_STD
+ static NoAudioCodecSimplex audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK,
+ AUDIO_I2S_GPIO_WS,
+ AUDIO_I2S_GPIO_DOUT,
+ #ifdef CONFIG_I2S_USE_2SLOT
+ I2S_STD_SLOT_BOTH,
+ #endif
+ AUDIO_MIC_SCK_PIN,
+ AUDIO_MIC_WS_PIN,
+ #ifdef CONFIG_I2S_USE_2SLOT
+ AUDIO_MIC_SD_PIN,
+ I2S_STD_SLOT_LEFT
+ #else
+ AUDIO_MIC_SD_PIN
+ #endif
+ );
+#else
+ static NoAudioCodecSimplexPdm audio_codec(
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_GPIO_BCLK,
+ AUDIO_I2S_GPIO_WS,
+ AUDIO_I2S_GPIO_DOUT,
+ #ifdef CONFIG_I2S_USE_2SLOT
+ I2S_STD_SLOT_BOTH,
+ #endif
+ AUDIO_MIC_WS_PIN,
+ AUDIO_MIC_SD_PIN
+ );
+#endif
+ 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;
+ }
+
+ Cst816s* GetTouchpad() {
+ return cst816s_;
+ }
+};
+
+DECLARE_BOARD(TaijiPiS3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.h b/xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.h
new file mode 100644
index 0000000..a272900
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.h
@@ -0,0 +1,41 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_9
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_3
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2
+
+#define DISPLAY_SDA_PIN GPIO_NUM_7
+#define DISPLAY_SCL_PIN GPIO_NUM_8
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+#define ML307_RX_PIN GPIO_NUM_5
+#define ML307_TX_PIN GPIO_NUM_6
+
+#define AXP2101_I2C_ADDR 0x34
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.json b/xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.json
new file mode 100644
index 0000000..d9eee68
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/tudouzi/config.json
@@ -0,0 +1,13 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "tudouzi",
+ "sdkconfig_append": [
+ "CONFIG_USE_WAKE_WORD_DETECT=n",
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/tudouzi/kevin_box_board.cc b/xiaozhi-esp32-2.2.6/main/boards/tudouzi/kevin_box_board.cc
new file mode 100644
index 0000000..d6cb5b2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/tudouzi/kevin_box_board.cc
@@ -0,0 +1,255 @@
+#include "ml307_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/oled_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <driver/gpio.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "KevinBoxBoard"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ // ** EFUSE defaults **
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ WriteReg(0x93, 0x1C); // 閰嶇疆 aldo2 杈撳嚭涓� 3.3V
+
+ uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0
+ value = value | 0x02; // set bit 1 (ALDO2)
+ WriteReg(0x90, value); // and power channels now enabled
+
+ WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V
+
+ WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA
+ WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA
+
+ WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables
+ WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables
+ WriteReg(0x16, 0x05); // set input current limit to 2000mA
+
+ WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery)
+ WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature)
+ }
+};
+
+class KevinBoxBoard : public Ml307Board {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(240, 60, -1);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void Enable4GModule() {
+ // Make GPIO HIGH to enable the 4G module
+ gpio_config_t ml307_enable_config = {
+ .pin_bit_mask = (1ULL << 4),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ gpio_config(&ml307_enable_config);
+ gpio_set_level(GPIO_NUM_4, 1);
+ }
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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 InitializeButtons() {
+ boot_button_.OnPressDown([this]() {
+ power_save_timer_->WakeUp();
+ Application::GetInstance().StartListening();
+ });
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+public:
+ KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeCodecI2c();
+ pmic_ = new Pmic(codec_i2c_bus_, AXP2101_I2C_ADDR);
+
+ Enable4GModule();
+
+ InitializeButtons();
+ InitializePowerSaveTimer();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(codec_i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+};
+
+DECLARE_BOARD(KevinBoxBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/README.md
new file mode 100644
index 0000000..5a44c06
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/README.md
@@ -0,0 +1,56 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-C6-Touch-LCD-1.69](https://www.waveshare.net/shop/ESP32-C6-Touch-LCD-1.69.htm)
+[寰洩鐢靛瓙 ESP32-C6-LCD-1.69](https://www.waveshare.net/shop/ESP32-C6-LCD-1.69.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32C6**
+
+```bash
+idf.py set-target esp32c6
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-LCD-1.69
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+# 鎸夐敭鎿嶄綔
+## BOOT 鎸夐敭
+**鏈繛鎺ユ湇鍔″櫒鍓嶅崟鍑�: 杩涘叆閰嶇綉妯″紡**
+**杩炴帴鏈嶅姟鍣ㄥ悗鍗曞嚮: 鍞ら啋銆佹墦鏂�**
+
+## PWR 鎸夐敭
+**鍙屽嚮锛氭伅灞忋�佷寒灞�**
+**闀挎寜锛氬紑鍏虫満**
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.h
new file mode 100644
index 0000000..0904631
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.h
@@ -0,0 +1,54 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+#define PWR_BUTTON_GPIO GPIO_NUM_18
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SPI_MODE 3
+#define DISPLAY_CS_PIN GPIO_NUM_5
+#define DISPLAY_MOSI_PIN GPIO_NUM_2
+#define DISPLAY_MISO_PIN GPIO_NUM_NC
+#define DISPLAY_CLK_PIN GPIO_NUM_1
+#define DISPLAY_DC_PIN GPIO_NUM_3
+#define DISPLAY_RST_PIN GPIO_NUM_4
+
+#define BATTERY_EN_PIN GPIO_NUM_15
+#define BATTERY_ADC_PIN GPIO_NUM_0
+#define BATTERY_CHARGING_PIN GPIO_NUM_NC
+
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 280
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_INVERT_COLOR true
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 20
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.json
new file mode 100644
index 0000000..507aa95
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-lcd-1.69",
+ "sdkconfig_append": [
+ "CONFIG_USE_ESP_WAKE_WORD=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/esp32-c6-lcd-1.69.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/esp32-c6-lcd-1.69.cc
new file mode 100644
index 0000000..09dbf87
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/esp32-c6-lcd-1.69.cc
@@ -0,0 +1,262 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_timer.h>
+#include "iot_button.h"
+#include "power_manager.h"
+#include "power_save_timer.h"
+
+#define TAG "waveshare_lcd_1_69"
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0);
+ }
+};
+
+class CustomButton: public Button {
+public:
+ void OnPressDownDel(void) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_down_ = NULL;
+ iot_button_unregister_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr);
+ }
+ void OnPressUpDel(void) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_up_ = NULL;
+ iot_button_unregister_cb(button_handle_, BUTTON_PRESS_UP, nullptr);
+ }
+};
+
+class CustomBoard : public WifiBoard {
+private:
+ CustomButton boot_button_;
+ CustomButton pwr_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ PowerManager* power_manager_ = nullptr;
+ PowerSaveTimer* power_save_timer_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, BATTERY_ADC_PIN, BATTERY_EN_PIN);
+ power_manager_->PowerON();
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ power_manager_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = DISPLAY_MISO_PIN;
+ buscfg.sclk_io_num = DISPLAY_CLK_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 InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+
+ display_ = new CustomLcdDisplay(
+ panel_io,
+ panel,
+ DISPLAY_WIDTH,
+ DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X,
+ DISPLAY_OFFSET_Y,
+ DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y,
+ DISPLAY_SWAP_XY
+ );
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ pwr_button_.OnPressUp([this]() {
+ pwr_button_.OnDoubleClick([this]() {
+ static uint8_t brightness_last = 0;
+ auto backlight = Board::GetInstance().GetBacklight();
+ if (backlight->brightness() == 0) {
+ brightness_last = 0;
+ if (brightness_last == 0) {
+ backlight->SetBrightness(50, true);
+ } else {
+ backlight->SetBrightness(brightness_last, true);
+ }
+ } else {
+ brightness_last = backlight->brightness();
+ backlight->SetBrightness(0);
+ }
+ });
+
+ pwr_button_.OnLongPress([this]() {
+ // printf("Power button long press\n");
+ if (power_manager_ != nullptr){
+ power_manager_->PowerOff();
+ }
+ });
+
+ pwr_button_.OnPressUpDel();
+ });
+ }
+
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO), pwr_button_(PWR_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/power_manager.h
new file mode 100644
index 0000000..adb7bee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-lcd-1.69/power_manager.h
@@ -0,0 +1,174 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include "esp_adc/adc_oneshot.h"
+#include "esp_adc/adc_cali.h"
+#include "esp_adc/adc_cali_scheme.h"
+#include <math.h>
+
+
+class PowerManager {
+private:
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ gpio_num_t bat_adc_pin_ = GPIO_NUM_NC;
+ gpio_num_t bat_power_pin_ = GPIO_NUM_NC;
+ adc_oneshot_unit_handle_t adc_handle_ = NULL;
+ adc_cali_handle_t adc_cali_handle_ = NULL;
+ adc_channel_t adc_channel_;
+ bool do_calibration = false;
+
+ bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle) {
+ adc_cali_handle_t handle = NULL;
+ esp_err_t ret = ESP_FAIL;
+ bool calibrated = false;
+
+ if (!calibrated) {
+ ESP_LOGI("PowerManager", "calibration scheme version is %s", "Curve Fitting");
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = unit,
+ .chan = channel,
+ .atten = atten,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
+ };
+ ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
+ if (ret == ESP_OK) {
+ calibrated = true;
+ }
+ }
+
+ *out_handle = handle;
+ if (ret == ESP_OK) {
+ ESP_LOGI("PowerManager", "Calibration Success");
+ }
+ else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated) {
+ ESP_LOGW("PowerManager", "eFuse not burnt, skip software calibration");
+ }
+ else {
+ ESP_LOGE("PowerManager", "Invalid arg or no memory");
+ }
+ return calibrated;
+ }
+
+public:
+ PowerManager(gpio_num_t charging_pin, gpio_num_t bat_adc_pin, gpio_num_t bat_power_pin)
+ : charging_pin_(charging_pin), bat_adc_pin_(bat_adc_pin), bat_power_pin_(bat_power_pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ if (charging_pin_ != GPIO_NUM_NC) {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = 1ULL << charging_pin_;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+ }
+
+ // 鍒濆鍖栫數姹犱娇鑳藉紩鑴�
+ if (bat_power_pin_ != GPIO_NUM_NC) {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << bat_power_pin_;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+ // 鍒濆鍖朼dc
+ if (bat_adc_pin_ != GPIO_NUM_NC) {
+ adc_oneshot_unit_init_cfg_t init_config = {};
+ init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
+ init_config.unit_id = ADC_UNIT_1;
+
+ if (bat_adc_pin_ >= GPIO_NUM_0 && bat_adc_pin_ <= GPIO_NUM_6)
+ adc_channel_ = (adc_channel_t)((int)bat_adc_pin_);
+ else
+ return;
+
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+ adc_oneshot_chan_cfg_t config = {};
+ config.bitwidth = ADC_BITWIDTH_DEFAULT;
+ config.atten = ADC_ATTEN_DB_12;
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &config));
+ do_calibration = adc_calibration_init(init_config.unit_id, adc_channel_, config.atten, &adc_cali_handle_);
+ }
+ }
+
+ ~PowerManager() {
+ if (adc_handle_) {
+ ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_));
+ }
+ }
+
+ int GetBatteryLevel(void) {
+ int adc_raw = 0;
+ int voltage_int = 0;
+ const float voltage_float_threshold = 0.1f;
+ float voltage_float = 0.0f;
+ static float last_voltage_float = 0.0f;
+ static int last_battery_level = 0;
+
+ if (adc_handle_ != nullptr) {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_raw));
+ if (do_calibration) {
+ ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_int));
+ voltage_float = (voltage_int / 1000.0f) * 3.0;
+
+ if (fabs(voltage_float - last_voltage_float) >= voltage_float_threshold) {
+ last_voltage_float = voltage_float;
+ if (voltage_float < 3.52) {
+ last_battery_level = 1;
+ } else if (voltage_float < 3.64) {
+ last_battery_level = 20;
+ } else if (voltage_float < 3.76) {
+ last_battery_level = 40;
+ } else if (voltage_float < 3.88) {
+ last_battery_level = 60;
+ } else if (voltage_float < 4.0) {
+ last_battery_level = 80;
+ } else {
+ last_battery_level = 100;
+ }
+ }
+ return last_battery_level;
+ }
+ }
+ return 100;
+ }
+
+ bool IsCharging(void) {
+ if (charging_pin_ != GPIO_NUM_NC) {
+ return gpio_get_level(charging_pin_) == 0 ? true : false;
+ }
+ return false;
+ }
+
+ bool IsDischarging(void) {
+ if (charging_pin_ != GPIO_NUM_NC) {
+ return gpio_get_level(charging_pin_) == 1;
+ }
+ return true;
+ }
+
+ bool IsChargingDone(void) {
+ if (GetBatteryLevel() == 100) {
+ return true;
+ }
+ return false;
+ }
+
+ void PowerOff(void) {
+ if (bat_power_pin_ != GPIO_NUM_NC) {
+ gpio_set_level(bat_power_pin_, 0);
+ }
+ }
+
+ void PowerON(void) {
+ if (bat_power_pin_ != GPIO_NUM_NC) {
+ gpio_set_level(bat_power_pin_, 1);
+ }
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/README.md
new file mode 100644
index 0000000..c153ac1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/README.md
@@ -0,0 +1,48 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-C6-Touch-AMOLED-1.32](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.32.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32C6**
+
+```bash
+idf.py set-target esp32c6
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-AMOLED-1.32
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.h
new file mode 100644
index 0000000..70a5301
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.h
@@ -0,0 +1,44 @@
+#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_15
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_5
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_3
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_4
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_23
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+
+#define LCD_CS GPIO_NUM_22
+#define LCD_PCLK GPIO_NUM_18
+#define LCD_D0 GPIO_NUM_19
+#define LCD_D1 GPIO_NUM_20
+#define LCD_D2 GPIO_NUM_10
+#define LCD_D3 GPIO_NUM_11
+#define LCD_RST GPIO_NUM_21
+
+#define EXAMPLE_LCD_H_RES 466
+#define EXAMPLE_LCD_V_RES 466
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define SYS_POWER_IO_PIN GPIO_NUM_2
+#define PWR_KEY_GPIO GPIO_NUM_1
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.json
new file mode 100644
index 0000000..3392da6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-touch-amoled-1.32",
+ "sdkconfig_append": [
+ "CONFIG_USE_ESP_WAKE_WORD=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc
new file mode 100644
index 0000000..a6d5e4e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.32/esp32-c6-touch-amoled-1.32.cc
@@ -0,0 +1,223 @@
+#include "application.h"
+#include "button.h"
+#include "codecs/es8311_audio_codec.h"
+#include "config.h"
+#include "wifi_board.h"
+
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+#include "lvgl.h"
+#include "mcp_server.h"
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#define TAG "waveshare_c6_amoled_1_32"
+
+static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xFE, (uint8_t[]) {0x00}, 1, 0},
+ {0xC4, (uint8_t[]) {0x80}, 1, 0},
+ {0x3A, (uint8_t[]) {0x55}, 1, 0}, // 0x55 for RGB565, 0x77 for RGB888
+ {0x35, (uint8_t[]) {0x00}, 1, 0},
+ {0x53, (uint8_t[]) {0x20}, 1, 0},
+ {0x51, (uint8_t[]) {0xFF}, 1, 0}, // Brightness
+ {0x63, (uint8_t[]) {0xFF}, 1, 0},
+ {0x2A, (uint8_t[]) {0x00, 0x06, 0x01, 0xD7}, 4, 0},
+ {0x2B, (uint8_t[]) {0x00, 0x00, 0x01, 0xD1}, 4, 0},
+ {0x11, (uint8_t[]) {0x00}, 0, 100},
+ {0x29, (uint8_t[]) {0x00}, 0, 0},
+};
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+ private:
+ esp_lcd_panel_io_handle_t io_handle_;
+
+ public:
+ static void my_draw_event_cb(lv_event_t *e) {
+ lv_area_t *area = (lv_area_t *) lv_event_get_param(e);
+
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // Round area for better performance
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ void SetMIRROR_XY(uint8_t mirror) {
+ uint32_t lcd_cmd = 0x36;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = mirror;
+ esp_lcd_panel_io_tx_param(io_handle_, lcd_cmd, ¶m, 1);
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) :
+ SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy),
+ io_handle_(io_handle) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ SetMIRROR_XY(0xC0); // Rotate 180 degrees - this is safe as it only operates on hardware
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_display_add_event_cb(display_, my_draw_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ lv_obj_invalidate(lv_screen_active());
+ }
+};
+
+class CustomBoard : public WifiBoard {
+ private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button pwr_button_;
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ CustomLcdDisplay *display_;
+ lv_indev_t *touch_indev = NULL;
+ i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = I2C_NUM_0;
+ i2c_bus_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void SetDispbacklight(uint8_t backlight) {
+ uint32_t lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = backlight;
+ esp_lcd_panel_io_tx_param(io_handle, lcd_cmd, ¶m, 1);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ pwr_button_.OnLongPress([this]() {
+ GetDisplay()->SetChatMessage("system", "OFF");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ gpio_set_level(SYS_POWER_IO_PIN, 0);
+ });
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.data0_io_num = LCD_D0;
+ buscfg.data1_io_num = LCD_D1;
+ buscfg.sclk_io_num = LCD_PCLK;
+ buscfg.data2_io_num = LCD_D2;
+ buscfg.data3_io_num = LCD_D3;
+ buscfg.max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS;
+ io_config.dc_gpio_num = -1;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 1000 * 1000;
+ io_config.trans_queue_depth = 8;
+ io_config.on_color_trans_done = NULL;
+ io_config.user_ctx = NULL;
+ io_config.lcd_cmd_bits = 32;
+ io_config.lcd_param_bits = 8;
+ io_config.flags.quad_mode = true;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle));
+
+ sh8601_vendor_config_t vendor_config = {};
+ vendor_config.init_cmds = lcd_init_cmds;
+ vendor_config.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]);
+ vendor_config.flags = {.use_qspi_interface = 1};
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &panel_handle));
+ esp_lcd_panel_set_gap(panel_handle, 0x06, 0x00);
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+
+ display_ = new CustomLcdDisplay(io_handle, panel_handle, EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.setbacklight", "璁剧疆灞忓箷浜害", PropertyList({Property("level", kPropertyTypeInteger, 0, 255)}), [this](const PropertyList &properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI("setbacklight", "%d", level);
+ SetDispbacklight(level);
+ return true;
+ });
+
+ mcp_server.AddTool("self.disp.network", "閲嶆柊閰嶇綉", PropertyList(), [this](const PropertyList &) -> ReturnValue {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+ void CheckPowerKeyState() {
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << SYS_POWER_IO_PIN);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+ gpio_set_level(SYS_POWER_IO_PIN, 1);
+ do {
+ vTaskDelay(pdMS_TO_TICKS(10));
+ } while (!gpio_get_level(PWR_KEY_GPIO));
+ }
+
+ public:
+ CustomBoard() : boot_button_(BOOT_BUTTON_GPIO), pwr_button_(PWR_KEY_GPIO) {
+ CheckPowerKeyState();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Es8311AudioCodec audio_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_;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/README.md
new file mode 100644
index 0000000..7703850
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/README.md
@@ -0,0 +1,49 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-C6-Touch-AMOLED-1.43](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.43.htm)
+[寰洩鐢靛瓙 ESP32-C6-Touch-AMOLED-1.43-B](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-1.43-B.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32C6**
+
+```bash
+idf.py set-target esp32c6
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-AMOLED-1.43
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.h
new file mode 100644
index 0000000..0d39b3f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.h
@@ -0,0 +1,49 @@
+#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_INPUT_REFERENCE false
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_20
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define I2C_Touch_ADDRESS 0x38
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+#define PWR_BUTTON_GPIO GPIO_NUM_2
+
+#define LCD_CS GPIO_NUM_10
+#define LCD_PCLK GPIO_NUM_11
+#define LCD_D0 GPIO_NUM_4
+#define LCD_D1 GPIO_NUM_5
+#define LCD_D2 GPIO_NUM_6
+#define LCD_D3 GPIO_NUM_7
+#define LCD_RST GPIO_NUM_3
+#define LCD_LIGHT (-1)
+
+#define EXAMPLE_LCD_H_RES 466
+#define EXAMPLE_LCD_V_RES 466
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.json
new file mode 100644
index 0000000..44001e1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-touch-amoled-1.43",
+ "sdkconfig_append": [
+ "CONFIG_USE_ESP_WAKE_WORD=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc
new file mode 100644
index 0000000..ae8e145
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.43/esp32-c6-touch-amoled-1.43.cc
@@ -0,0 +1,292 @@
+#include "wifi_board.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "codecs/box_audio_codec.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "esp_lcd_sh8601.h"
+#include "display/lcd_display.h"
+#include "esp_io_expander_tca9554.h"
+#include "mcp_server.h"
+#include "lvgl.h"
+
+#define TAG "waveshare_c6_amoled_1_43"
+
+static const sh8601_lcd_init_cmd_t lcd_init_cmds[] =
+{
+ {0x11, (uint8_t []){0x00}, 0, 80},
+ {0xC4, (uint8_t []){0x80}, 1, 0},
+ {0x53, (uint8_t []){0x20}, 1, 1},
+ {0x63, (uint8_t []){0xFF}, 1, 1},
+ {0x51, (uint8_t []){0x00}, 1, 1},
+ {0x29, (uint8_t []){0x00}, 0, 10},
+ {0x51, (uint8_t []){0xFF}, 1, 0},
+};
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ static void MyDrawEventCb(lv_event_t *e) {
+ lv_area_t *area = (lv_area_t *)lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+ // round the start of coordinate down to the nearest 2M number
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ // round the end of coordinate up to the nearest 2N+1 number
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_display_add_event_cb(display_, MyDrawEventCb, LV_EVENT_INVALIDATE_AREA, NULL);
+ }
+};
+
+class CustomBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button pwr_button_;
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ esp_io_expander_handle_t io_expander = NULL;
+ CustomLcdDisplay* display_;
+ i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
+ lv_indev_t *touch_indev = NULL; //touch
+ uint8_t pwr_flag = 0;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT);
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {
+ .data0_io_num = LCD_D0,
+ .data1_io_num = LCD_D1,
+ .sclk_io_num = LCD_PCLK,
+ .data2_io_num = LCD_D2,
+ .data3_io_num = LCD_D3,
+ .max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t),
+ };
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ const esp_lcd_panel_io_spi_config_t io_config = {
+ .cs_gpio_num = LCD_CS,
+ .dc_gpio_num = -1,
+ .spi_mode = 0,
+ .pclk_hz = 40 * 1000 * 1000,
+ .trans_queue_depth = 4,
+ .on_color_trans_done = NULL,
+ .user_ctx = NULL,
+ .lcd_cmd_bits = 32,
+ .lcd_param_bits = 8,
+ .flags = {
+ .quad_mode = true,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle));
+ sh8601_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]), // sizeof(axs15231b_lcd_init_cmd_t),
+ .flags =
+ {
+ .use_qspi_interface = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h`
+ .bits_per_pixel = 16, // Implemented by LCD command `3Ah` (16/18)
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &panel_handle));
+ esp_lcd_panel_set_gap(panel_handle,0x06,0x00);
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+ display_ = new CustomLcdDisplay(io_handle, panel_handle,
+ EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() { //鎺ュ叆閿傜數姹犳椂,鍙暱鎸塒WR寮�鏈�/鍏虫満
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ });
+
+ boot_button_.OnPressDown([this]() {
+ Application::GetInstance().StartListening();
+ });
+
+ boot_button_.OnPressUp([this]() {
+ Application::GetInstance().StopListening();
+ });
+
+ pwr_button_.OnLongPress([this]() {
+ if(pwr_flag == 1)
+ {
+ pwr_flag = 0;
+ esp_err_t ret;
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0);
+ ESP_ERROR_CHECK(ret);
+ }
+ });
+
+ pwr_button_.OnPressUp([this]() {
+ if(pwr_flag == 0)
+ {
+ pwr_flag = 1;
+ }
+ });
+ }
+
+ void InitializeTouch() {
+ i2c_device_config_t dev_cfg =
+ {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = I2C_Touch_ADDRESS,
+ .scl_speed_hz = 300000,
+ };
+ ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus_, &dev_cfg, &disp_touch_dev_handle));
+
+ touch_indev = lv_indev_create();
+ lv_indev_set_type(touch_indev, LV_INDEV_TYPE_POINTER);
+ lv_indev_set_read_cb(touch_indev, TouchInputReadCallback);
+ lv_indev_set_user_data(touch_indev, disp_touch_dev_handle);
+ }
+
+ static void TouchInputReadCallback(lv_indev_t * indev, lv_indev_data_t *indevData)
+ {
+ i2c_master_dev_handle_t i2c_dev = (i2c_master_dev_handle_t)lv_indev_get_user_data(indev);
+ uint8_t cmd = 0x02;
+ uint8_t buf[5] = {0};
+ uint16_t tp_x,tp_y;
+ i2c_master_transmit_receive(i2c_dev,&cmd,1,buf,5,1000);
+ if(buf[0])
+ {
+ tp_x = (((uint16_t)buf[1] & 0x0f)<<8) | (uint16_t)buf[2];
+ tp_y = (((uint16_t)buf[3] & 0x0f)<<8) | (uint16_t)buf[4];
+ if(tp_x > EXAMPLE_LCD_H_RES)
+ {tp_x = EXAMPLE_LCD_H_RES;}
+ if(tp_y > EXAMPLE_LCD_V_RES)
+ {tp_y = EXAMPLE_LCD_V_RES;}
+ indevData->point.x = tp_x;
+ indevData->point.y = tp_y;
+ //ESP_LOGI("tp","(%ld,%ld)",indevData->point.x,indevData->point.y);
+ indevData->state = LV_INDEV_STATE_PRESSED;
+ }
+ else
+ {
+ indevData->state = LV_INDEV_STATE_RELEASED;
+ }
+ }
+
+ void InitializeTools()
+ {
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.setbacklight", "璁剧疆灞忓箷浜害", PropertyList({
+ Property("level", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI("setbacklight","%d",level);
+ SetDispbacklight(level);
+ return true;
+ });
+ }
+
+ void SetDispbacklight(uint8_t backlight) {
+ uint32_t lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = backlight;
+ esp_lcd_panel_io_tx_param(io_handle, lcd_cmd, ¶m,1);
+ }
+
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO),pwr_button_(PWR_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+};
+
+DECLARE_BOARD(CustomBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/README.md
new file mode 100644
index 0000000..6c38077
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/README.md
@@ -0,0 +1,4 @@
+# Waveshare ESP32-C6-Touch-AMOLED-1.8
+
+
+[ESP32-C6-Touch-AMOLED-1.8](https://www.waveshare.com/esp32-c6-touch-amoled-1.8.htm) ESP32-C6-Touch-AMOLED-1.8 is a high-performance, highly integrated microcontroller development board designed by Waveshare. Tiny size with onboard 1.8inch capacitive AMOLED display, highly integrated power management IC, 6-axis IMU (3-axis accelerometer and 3-axis gyroscope), RTC, low power audio codec IC, and so on, which makes it easy for you to develop and integrate it into the products quickly.
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.h
new file mode 100644
index 0000000..4779d7c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.h
@@ -0,0 +1,41 @@
+#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_19
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_5
+#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_0
+#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_1
+#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_2
+#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_3
+#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_4
+#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_NC
+#define DISPLAY_WIDTH 368
+#define DISPLAY_HEIGHT 448
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.json
new file mode 100644
index 0000000..56bdf2f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-touch-amoled-1.8",
+ "sdkconfig_append": [
+ "CONFIG_USE_ESP_WAKE_WORD=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/esp32-c6-touch-amoled-1.8.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/esp32-c6-touch-amoled-1.8.cc
new file mode 100644
index 0000000..4c79cd2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-1.8/esp32-c6-touch-amoled-1.8.cc
@@ -0,0 +1,352 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "esp_io_expander_tca9554.h"
+#include "settings.h"
+
+#include <esp_lcd_touch_ft5x06.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32c6TouchAMOLED1inch8"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+ WriteReg(0x93, (3300 - 500) / 100);
+
+ WriteReg(0x90, 0x03);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x03ULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x53, (uint8_t[]){0x20}, 1, 10},
+ {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0},
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0},
+ {0x51, (uint8_t[]){0x00}, 1, 10},
+ {0x29, (uint8_t[]){0x00}, 0, 10}
+};
+
+// 鍦╳aveshare_amoled_1_8绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
+
+protected:
+ esp_lcd_panel_io_handle_t panel_io_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ auto display = Board::GetInstance().GetDisplay();
+ DisplayLockGuard lock(display);
+ uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))};
+ int lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
+ esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
+ }
+};
+
+class WaveshareEsp32c6TouchAMOLED1inch8 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ CustomLcdDisplay* display_;
+ CustomBacklight* backlight_;
+ esp_io_expander_handle_t io_expander = NULL;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_5 |IO_EXPANDER_PIN_NUM_7, IO_EXPANDER_OUTPUT);
+ ret |= esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_INPUT);
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_5 |IO_EXPANDER_PIN_NUM_7, 1);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_5 |IO_EXPANDER_PIN_NUM_7, 0);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_5 |IO_EXPANDER_PIN_NUM_7, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(codec_i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK;
+ buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0;
+ buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1;
+ buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2;
+ buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
+ buscfg.flags = SPICOMMON_BUSFLAG_QUAD;
+ 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 InitializeSH8601Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG(
+ EXAMPLE_PIN_NUM_LCD_CS,
+ nullptr,
+ nullptr
+ );
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const sh8601_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t),
+ .flags ={
+ .use_qspi_interface = 1,
+ }
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ panel_config.flags.reset_active_high = 1,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(panel_io);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_15,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32c6TouchAMOLED1inch8() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeTca9554();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeSH8601Display();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ 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 {
+ return backlight_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32c6TouchAMOLED1inch8);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/README.md
new file mode 100644
index 0000000..ee4ae2b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/README.md
@@ -0,0 +1,4 @@
+# Waveshare ESP32-C6-Touch-AMOLED-2.06
+
+
+[ESP32-C6-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-c6-touch-amoled-2.06.htm) is a high-performance wearable watch-shaped development board launched by Waveshare. This product is based on the ESP32-C6 microcontroller and integrates a 2.06-inch capacitive touch high-definition AMOLED screen, a six-axis sensor, RTC, audio codec chip, and power management, among other functional modules. With a custom case, its appearance is consistent with that of a smartwatch, and it is specifically designed for prototype development and functional verification of wearable applications.
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.h
new file mode 100644
index 0000000..1d884f3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.h
@@ -0,0 +1,46 @@
+#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_INPUT_REFERENCE false
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_6
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_5
+#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_0
+#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_1
+#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_2
+#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_3
+#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_4
+#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_11
+
+#define DISPLAY_WIDTH 410
+#define DISPLAY_HEIGHT 502
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.json
new file mode 100644
index 0000000..d4db90a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-touch-amoled-2.06",
+ "sdkconfig_append": [
+ "CONFIG_USE_ESP_WAKE_WORD=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc
new file mode 100644
index 0000000..4e224d1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.06/esp32-c6-touch-amoled-2.06.cc
@@ -0,0 +1,322 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "settings.h"
+
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32c6TouchAMOLED2inch06"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+ WriteReg(0x93, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x03);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x03ULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
+ // set display to qspi mode
+ {0x11, (uint8_t []){0x00}, 0, 120},
+ {0xC4, (uint8_t []){0x80}, 1, 0},
+ {0x44, (uint8_t []){0x01, 0xD1}, 2, 0},
+ {0x35, (uint8_t []){0x00}, 1, 0},
+ {0x53, (uint8_t []){0x20}, 1, 10},
+ {0x63, (uint8_t []){0xFF}, 1, 10},
+ {0x51, (uint8_t []){0x00}, 1, 10},
+ {0x2A, (uint8_t []){0x00,0x16,0x01,0xAF}, 4, 0},
+ {0x2B, (uint8_t []){0x00,0x00,0x01,0xF5}, 4, 0},
+ {0x29, (uint8_t []){0x00}, 0, 10},
+ {0x51, (uint8_t []){0xFF}, 1, 0},
+};
+
+// 鍦╳aveshare_amoled_2_06绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ static void rounder_event_cb(lv_event_t* e) {
+ lv_area_t* area = (lv_area_t* )lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // round the start of coordinate down to the nearest 2M number
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ // round the end of coordinate up to the nearest 2N+1 number
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0);
+ lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
+
+protected:
+ esp_lcd_panel_io_handle_t panel_io_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ auto display = Board::GetInstance().GetDisplay();
+ DisplayLockGuard lock(display);
+ uint8_t data[1] = {((uint8_t)((255* brightness) / 100))};
+ int lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
+ esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
+ }
+};
+
+class WaveshareEsp32c6TouchAMOLED2inch06 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ CustomLcdDisplay* display_;
+ CustomBacklight* backlight_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness(); });
+ power_save_timer_->OnShutdownRequest([this](){
+ pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK;
+ buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0;
+ buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1;
+ buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2;
+ buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t);
+ buscfg.flags = SPICOMMON_BUSFLAG_QUAD;
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeSH8601Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG(
+ EXAMPLE_PIN_NUM_LCD_CS,
+ nullptr,
+ nullptr);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const sh8601_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ }};
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void* )&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel));
+ esp_lcd_panel_set_gap(panel, 0x16, 0);
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(panel_io);
+ backlight_->RestoreBrightness();
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32c6TouchAMOLED2inch06() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeSH8601Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ return backlight_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging)
+ {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32c6TouchAMOLED2inch06);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/README.md
new file mode 100644
index 0000000..4f2acff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/README.md
@@ -0,0 +1,48 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-C6-Touch-AMOLED-2.16](https://www.waveshare.net/shop/ESP32-C6-Touch-AMOLED-2.16.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32C6**
+
+```bash
+idf.py set-target esp32c6
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-AMOLED-2.16
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.h
new file mode 100644
index 0000000..3a2d1dc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.h
@@ -0,0 +1,46 @@
+#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_INPUT_REFERENCE false
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+
+#define LCD_CS GPIO_NUM_15
+#define LCD_PCLK GPIO_NUM_0
+#define LCD_D0 GPIO_NUM_1
+#define LCD_D1 GPIO_NUM_2
+#define LCD_D2 GPIO_NUM_3
+#define LCD_D3 GPIO_NUM_4
+#define LCD_RST GPIO_NUM_NC
+
+#define TP_RST_GPIO GPIO_NUM_11
+#define TP_INT_GPIO GPIO_NUM_5
+
+#define LCD_H_RES 480
+#define LCD_V_RES 480
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.json
new file mode 100644
index 0000000..aff7144
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/config.json
@@ -0,0 +1,14 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-touch-amoled-2.16",
+ "sdkconfig_append": [
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_USE_DEFAULT_MESSAGE_STYLE=y",
+ "CONFIG_USE_MULTILINE_CHAT_MESSAGE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/esp32-c6-touch-amoled-2.16.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/esp32-c6-touch-amoled-2.16.cc
new file mode 100644
index 0000000..7053b04
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-amoled-2.16/esp32-c6-touch-amoled-2.16.cc
@@ -0,0 +1,352 @@
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+#include "wifi_board.h"
+
+#include "application.h"
+#include "axp2101.h"
+#include "button.h"
+#include "codecs/box_audio_codec.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "power_save_timer.h"
+
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include <esp_ota_ops.h>
+#include <esp_partition.h>
+#include "settings.h"
+
+#include <esp_lcd_touch_cst9217.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32c6TouchAMOLED2inch16"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+ WriteReg(0x93, (3300 - 500) / 100);
+ WriteReg(0x94, (3300 - 500) / 100);
+ WriteReg(0x95, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x0F);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA,
+ // 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+
+ void Pmic_SetAldo3(bool enable) {
+ uint8_t reg = ReadReg(0x90);
+ if (enable) {
+ reg |= 0x04;
+ } else {
+ reg &= ~0x04;
+ }
+ WriteReg(0x90, reg);
+ }
+
+ void Pmic_SetAldo2(bool enable) {
+ uint8_t reg = ReadReg(0x90);
+ if (enable) {
+ reg |= 0x02;
+ } else {
+ reg &= ~0x02;
+ }
+ WriteReg(0x90, reg);
+ }
+};
+
+static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0xC4, (uint8_t[]){0x80}, 1, 0},
+ {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x36, (uint8_t[]){0xA0}, 1, 0},
+ {0x53, (uint8_t[]){0x20}, 1, 10},
+ {0x63, (uint8_t[]){0xFF}, 1, 10},
+ {0x51, (uint8_t[]){0x00}, 1, 10},
+ {0x2A, (uint8_t[]){0x00, 0x16, 0x01, 0xAF}, 4, 0},
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xF5}, 4, 0},
+ {0x29, (uint8_t[]){0x00}, 0, 10},
+ {0x51, (uint8_t[]){0xFF}, 1, 0},
+};
+
+// 鍦╳aveshare_amoled_2_16绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ static void rounder_event_cb(lv_event_t* e) {
+ lv_area_t* area = (lv_area_t*)lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // round the start of coordinate down to the nearest 2M number
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ // round the end of coordinate up to the nearest 2N+1 number
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle,
+ int width, int height, int offset_x, int offset_y, bool mirror_x,
+ bool mirror_y, bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x,
+ mirror_y, swap_xy) {}
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(top_bar_, 60, 0);
+ lv_obj_set_style_pad_right(top_bar_, 60, 0);
+ lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
+
+protected:
+ esp_lcd_panel_io_handle_t panel_io_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ auto display = Board::GetInstance().GetDisplay();
+ DisplayLockGuard lock(display);
+ uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))};
+ int lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02UL << 24;
+ esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
+ }
+};
+
+class WaveshareEsp32c6TouchAMOLED2inch16 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ CustomLcdDisplay* display_;
+ CustomBacklight* backlight_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 120, 1200);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() { pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .flags =
+ {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = LCD_PCLK;
+ buscfg.data0_io_num = LCD_D0;
+ buscfg.data1_io_num = LCD_D1;
+ buscfg.data2_io_num = LCD_D2;
+ buscfg.data3_io_num = LCD_D3;
+ buscfg.max_transfer_sz = LCD_H_RES * LCD_V_RES * 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();
+ });
+ }
+
+ int DisplayPort_DispReset() {
+ pmic_->Pmic_SetAldo3(1);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ pmic_->Pmic_SetAldo3(0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ pmic_->Pmic_SetAldo3(1);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ return ESP_OK;
+ }
+
+ void InitializeSH8601Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config =
+ SH8601_PANEL_IO_QSPI_CONFIG(LCD_CS, nullptr, nullptr);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const sh8601_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ }};
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void*)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel));
+ ESP_ERROR_CHECK(DisplayPort_DispReset());
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ display_ = new CustomLcdDisplay(panel_io, panel, LCD_H_RES, LCD_V_RES, DISPLAY_OFFSET_X,
+ DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y,
+ DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(panel_io);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = LCD_H_RES - 1,
+ .y_max = LCD_V_RES - 1,
+ .rst_gpio_num = TP_RST_GPIO,
+ .int_gpio_num = TP_INT_GPIO,
+ .levels =
+ {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags =
+ {
+ .swap_xy = 1,
+ .mirror_x = 0,
+ .mirror_y = 1,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST9217_CONFIG();
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst9217(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32c6TouchAMOLED2inch16() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeSH8601Display();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override { return display_; }
+
+ virtual Backlight* GetBacklight() override { return backlight_; }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32c6TouchAMOLED2inch16);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/README.md
new file mode 100644
index 0000000..917a154
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/README.md
@@ -0,0 +1,49 @@
+
+# 浜у搧閾炬帴
+寰洩鐢靛瓙 ESP32-C6-Touch-LCD-1.83
+
+(https://www.waveshare.net/shop/ESP32-C6-Touch-LCD-1.83.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32C6**
+
+```bash
+idf.py set-target esp32c6
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-C6-Touch-LCD-1.83
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.h
new file mode 100644
index 0000000..10ec48e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.h
@@ -0,0 +1,50 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_19
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_22
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_20
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_23
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_0
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+#define PWR_BUTTON_GPIO GPIO_NUM_18
+
+#define DISPLAY_SPI_MODE SPI2_HOST
+#define DISPLAY_CS_PIN GPIO_NUM_5
+#define DISPLAY_MOSI_PIN GPIO_NUM_2
+#define DISPLAY_MISO_PIN GPIO_NUM_NC
+#define DISPLAY_CLK_PIN GPIO_NUM_1
+#define DISPLAY_DC_PIN GPIO_NUM_3
+#define DISPLAY_RST_PIN GPIO_NUM_4
+
+#define DISPLAY_TOUCH_INT_PIN GPIO_NUM_11
+#define DISPLAY_TOUCH_RST_PIN GPIO_NUM_NC
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 284
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.json
new file mode 100644
index 0000000..a7c1b25
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32c6",
+ "builds": [
+ {
+ "name": "esp32-c6-touch-lcd-1.83",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/esp32-c6-touch-lcd-1.83.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/esp32-c6-touch-lcd-1.83.cc
new file mode 100644
index 0000000..4e7a8a4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-c6-touch-lcd-1.83/esp32-c6-touch-lcd-1.83.cc
@@ -0,0 +1,274 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "settings.h"
+
+#include <esp_lcd_touch_cst816s.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32c6TouchLCD1inch83"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x01);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+class WaveshareEsp32c6TouchLCD1inch83 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ Display* display_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness(); });
+ power_save_timer_->OnShutdownRequest([this](){
+ pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(DISPLAY_SPI_MODE, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 24 * 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(DISPLAY_SPI_MODE, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ 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_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ 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 InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH - 1,
+ .y_max = DISPLAY_HEIGHT - 1,
+ .rst_gpio_num = DISPLAY_TOUCH_RST_PIN,
+ .int_gpio_num = DISPLAY_TOUCH_INT_PIN,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS,
+ .on_color_trans_done = 0,
+ .user_ctx = 0,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 0,
+ .flags =
+ {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 1,
+ },
+ };
+ tp_io_config.scl_speed_hz = 400* 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "Reboot the device and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32c6TouchLCD1inch83() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeDisplay();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging)
+ {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+};
+
+DECLARE_BOARD(WaveshareEsp32c6TouchLCD1inch83);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/README.md
new file mode 100644
index 0000000..73e97af
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/README.md
@@ -0,0 +1,43 @@
+# Waveshare ESP32-P4-NANO
+
+
+[ESP32-P4-NANO](https://www.waveshare.com/esp32-p4-nano.htm) is a small size and highly integrated development board designed by waveshare electronics based on ESP32-P4 chip
+
+
+
+## Display Page
+
+
+### Recommended display screen
+
+| Product ID | Dependency | tested |
+|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|--------|
+| [10.1-DSI-TOUCH-A](https://www.waveshare.com/10.1-dsi-touch-a.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/1/0/10.1-dsi-touch-a-1.jpg"> | [waveshare/esp_lcd_jd9365_10_1](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1) | 鉁� |
+| [101M-8001280-IPS-CT-K](https://www.waveshare.com/101m-8001280-ips-ct-k.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/1/0/101m-8001280-ips-ct-k-1.jpg"> | [waveshare/esp_lcd_jd9365_10_1](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_jd9365_10_1) | 鉁� |
+
+### Common Raspberry adapter screen
+
+**These displays are supported on [ESP32-P4-NANO BSP](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/bsp/esp32_p4_nano), but not on xiaozhi-esp32**
+
+<details open>
+<summary>View full display</summary>
+
+| Product ID | Dependency | tested |
+|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------------------------------------------------------------|--------|
+| [2.8inch DSI LCD](https://www.waveshare.com/2.8inch-dsi-lcd.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/thumbnail/122x122/9df78eab33525d08d6e5fb8d27136e95/2/_/2.8inch-dsi-lcd-3.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [3.4inch DSI LCD (C)](https://www.waveshare.com/3.4inch-dsi-lcd-c.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/3/_/3.4inch-dsi-lcd-c-1.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [4inch DSI LCD (C)](https://www.waveshare.com/4inch-dsi-lcd-c.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/4/i/4inch-dsi-lcd-c-1.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [4inch DSI LCD](https://www.waveshare.com/4inch-dsi-lcd.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/4/i/4inch-dsi-lcd-1.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [5inch DSI LCD (D)](https://www.waveshare.com/5inch-dsi-lcd-d.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/5/i/5inch-dsi-lcd-d-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [6.25inch DSI LCD](https://www.waveshare.com/6.25inch-dsi-lcd.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/6/_/6.25inch-dsi-lcd-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [5inch DSI LCD (C)](https://www.waveshare.com/5inch-dsi-lcd-c.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/5/i/5inch-dsi-lcd-c-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [7inch DSI LCD (C)](https://www.waveshare.com/7inch-dsi-lcd-c-with-case-a.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/7/i/7inch-dsi-lcd-c-4.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [7.9inch DSI LCD](https://www.waveshare.com/7.9inch-dsi-lcd.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/7/_/7.9inch-dsi-lcd-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [7inch DSI LCD (E)](https://www.waveshare.com/7inch-dsi-lcd-e.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/7/i/7inch-dsi-lcd-e-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [8inch DSI LCD (C)](https://www.waveshare.com/8inch-dsi-lcd-c.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/8/i/8inch-dsi-lcd-c-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [10.1inch DSI LCD (C)](https://www.waveshare.com/10.1inch-dsi-lcd-c.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/1/0/10.1inch-dsi-lcd-c-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [8.8inch DSI LCD](https://www.waveshare.com/8.8inch-dsi-lcd.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/8/_/8.8inch-dsi-lcd-2.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [11.9inch DSI LCD](https://www.waveshare.com/11.9inch-dsi-lcd.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/1/1/11.9inch-dsi-lcd-3.jpg"> | [waveshare/esp_lcd_dsi](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_dsi) | 馃晵 |
+| [7-DSI-TOUCH-A](https://www.waveshare.com/7-dsi-touch-a.htm) <br/><img style="width: 150px; height: auto; display: block; margin: 0 auto;" src="https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/7/-/7-dsi-touch-a-1.jpg"> | [waveshare/esp_lcd_ili9881c](https://github.com/waveshareteam/waveshare-ESP32-components/tree/master/display/lcd/esp_lcd_ili9881c) | 馃晵 |
+
+</details>
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.h
new file mode 100644
index 0000000..8fb9ccc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.h
@@ -0,0 +1,44 @@
+#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_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_53
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_35
+
+#define DISPLAY_WIDTH 800
+#define DISPLAY_HEIGHT 1280
+
+#define LCD_BIT_PER_PIXEL (16)
+#define PIN_NUM_LCD_RST GPIO_NUM_NC
+
+#define DELAY_TIME_MS (3000)
+#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes
+
+#define MIPI_DSI_PHY_PWR_LDO_CHAN (3)
+#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.json
new file mode 100644
index 0000000..ab5c331
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/config.json
@@ -0,0 +1,16 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32p4",
+ "builds": [
+ {
+ "name": "esp32-p4-nano-10.1-a",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc
new file mode 100644
index 0000000..7fe1f0d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/esp32-p4-nano.cc
@@ -0,0 +1,284 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "display/lcd_display.h"
+// #include "display/no_display.h"
+#include "button.h"
+
+#include "esp_video.h"
+#include "esp_video_init.h"
+#include "esp_cam_sensor_xclk.h"
+
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_ldo_regulator.h"
+
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_lcd_jd9365.h"
+#include "lcd_init_cmds.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lvgl_port.h>
+#include "esp_lcd_touch_gt911.h"
+#define TAG "WaveshareEsp32p4nano"
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(i2c_master_bus_handle_t i2c_handle)
+ : Backlight(), i2c_handle_(i2c_handle) {}
+
+protected:
+ i2c_master_bus_handle_t i2c_handle_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ uint8_t i2c_address = 0x45; // 7-bit address
+ uint8_t reg = 0x96;
+ uint8_t data[2] = {reg, brightness};
+
+ i2c_master_dev_handle_t dev_handle;
+ i2c_device_config_t dev_cfg = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = i2c_address,
+ .scl_speed_hz = 100000,
+ };
+
+ esp_err_t err = i2c_master_bus_add_device(i2c_handle_, &dev_cfg, &dev_handle);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to add I2C device: %s", esp_err_to_name(err));
+ return;
+ }
+
+ err = i2c_master_transmit(dev_handle, data, sizeof(data), -1);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to transmit brightness: %s", esp_err_to_name(err));
+ } else {
+ ESP_LOGI(TAG, "Backlight brightness set to %u", brightness);
+ }
+
+ // i2c_master_bus_rm_device(dev_handle);
+ }
+};
+
+class WaveshareEsp32p4nano : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ LcdDisplay *display__;
+ EspVideo* camera_ = nullptr;
+ CustomBacklight *backlight_;
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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_));
+ }
+
+ static esp_err_t bsp_enable_dsi_phy_power(void) {
+#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0
+ // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
+ static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
+ esp_ldo_channel_config_t ldo_cfg = {
+ .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN,
+ .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
+ };
+ esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan);
+ ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
+#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0
+
+ return ESP_OK;
+ }
+
+ void InitializeLCD() {
+ uint8_t chip_addr = 0x45;
+ uint8_t write_cmds[4][2] = {{0x95, 0x11}, {0x95, 0x17}, {0x96, 0x00}, {0x96, 0xFF}};
+ i2c_device_config_t i2c_dev_conf = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = chip_addr,
+ .scl_speed_hz = 100000,
+ };
+ i2c_master_dev_handle_t dev_handle = NULL;
+ if (i2c_master_bus_add_device(codec_i2c_bus_, &i2c_dev_conf, &dev_handle) == ESP_OK)
+ {
+ for (uint8_t i = 0; i < 4; i++)
+ {
+ i2c_master_transmit(dev_handle, write_cmds[i], 2, 50);
+ }
+ i2c_master_bus_rm_device(dev_handle);
+ }
+
+ bsp_enable_dsi_phy_power();
+ esp_lcd_panel_io_handle_t io = NULL;
+ esp_lcd_panel_handle_t disp_panel = NULL;
+
+ esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
+ esp_lcd_dsi_bus_config_t bus_config = {
+ .bus_id = 0,
+ .num_data_lanes = 2,
+ .lane_bit_rate_mbps = 1500,
+ };
+ esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
+
+ ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
+ // we use DBI interface to send LCD commands and parameters
+ esp_lcd_dbi_io_config_t dbi_config = JD9365_PANEL_IO_DBI_CONFIG();
+ esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
+
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 80,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = 800,
+ .v_size = 1280,
+ .hsync_pulse_width = 20,
+ .hsync_back_porch = 20,
+ .hsync_front_porch = 40,
+ .vsync_pulse_width = 10,
+ .vsync_back_porch = 4,
+ .vsync_front_porch = 30,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+
+ jd9365_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ .lane_num = 2,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel);
+ esp_lcd_panel_reset(disp_panel);
+ esp_lcd_panel_init(disp_panel);
+
+ display__ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(codec_i2c_bus_);
+ backlight_->RestoreBrightness();
+ }
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 100 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+ void InitializeCamera() {
+ esp_video_init_csi_config_t base_csi_config = {
+ .sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = codec_i2c_bus_,
+ .freq = 400000,
+ },
+ .reset_pin = GPIO_NUM_NC,
+ .pwdn_pin = GPIO_NUM_NC,
+ };
+
+ esp_video_init_config_t cam_config = {
+ .csi = &base_csi_config,
+ };
+
+ camera_ = new EspVideo(cam_config);
+ }
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ WaveshareEsp32p4nano() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeLCD();
+ InitializeTouch();
+ InitializeCamera();
+ InitializeButtons();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_1, 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 Camera* GetCamera() override {
+ return camera_;
+ }
+
+ virtual Backlight *GetBacklight() override {
+ return backlight_;
+ }
+
+};
+
+DECLARE_BOARD(WaveshareEsp32p4nano);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/lcd_init_cmds.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/lcd_init_cmds.h
new file mode 100644
index 0000000..b383ffb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-nano/lcd_init_cmds.h
@@ -0,0 +1,224 @@
+static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+ {0xE1, (uint8_t[]){0x93}, 1, 0},
+ {0xE2, (uint8_t[]){0x65}, 1, 0},
+ {0xE3, (uint8_t[]){0xF8}, 1, 0},
+ {0x80, (uint8_t[]){0x01}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x01}, 1, 0},
+ {0x00, (uint8_t[]){0x00}, 1, 0},
+ {0x01, (uint8_t[]){0x38}, 1, 0},
+ {0x03, (uint8_t[]){0x10}, 1, 0},
+ {0x04, (uint8_t[]){0x38}, 1, 0},
+
+ {0x0C, (uint8_t[]){0x74}, 1, 0},
+
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0xAF}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1A, (uint8_t[]){0x00}, 1, 0},
+ {0x1B, (uint8_t[]){0xAF}, 1, 0},
+ {0x1C, (uint8_t[]){0x00}, 1, 0},
+
+ {0x35, (uint8_t[]){0x26}, 1, 0},
+
+ {0x37, (uint8_t[]){0x09}, 1, 0},
+
+ {0x38, (uint8_t[]){0x04}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x3A, (uint8_t[]){0x01}, 1, 0},
+ {0x3C, (uint8_t[]){0x78}, 1, 0},
+ {0x3D, (uint8_t[]){0xFF}, 1, 0},
+ {0x3E, (uint8_t[]){0xFF}, 1, 0},
+ {0x3F, (uint8_t[]){0x7F}, 1, 0},
+
+ {0x40, (uint8_t[]){0x06}, 1, 0},
+ {0x41, (uint8_t[]){0xA0}, 1, 0},
+ {0x42, (uint8_t[]){0x81}, 1, 0},
+ {0x43, (uint8_t[]){0x1E}, 1, 0},
+ {0x44, (uint8_t[]){0x0D}, 1, 0},
+ {0x45, (uint8_t[]){0x28}, 1, 0},
+ //{0x4A, (uint8_t[]){0x35}, 1, 0},//bist
+
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x57, (uint8_t[]){0x69}, 1, 0},
+ {0x59, (uint8_t[]){0x0A}, 1, 0},
+ {0x5A, (uint8_t[]){0x2A}, 1, 0},
+ {0x5B, (uint8_t[]){0x17}, 1, 0},
+
+ {0x5D, (uint8_t[]){0x7F}, 1, 0},
+ {0x5E, (uint8_t[]){0x6A}, 1, 0},
+ {0x5F, (uint8_t[]){0x5B}, 1, 0},
+ {0x60, (uint8_t[]){0x4F}, 1, 0},
+ {0x61, (uint8_t[]){0x4A}, 1, 0},
+ {0x62, (uint8_t[]){0x3D}, 1, 0},
+ {0x63, (uint8_t[]){0x41}, 1, 0},
+ {0x64, (uint8_t[]){0x2A}, 1, 0},
+ {0x65, (uint8_t[]){0x44}, 1, 0},
+ {0x66, (uint8_t[]){0x43}, 1, 0},
+ {0x67, (uint8_t[]){0x44}, 1, 0},
+ {0x68, (uint8_t[]){0x62}, 1, 0},
+ {0x69, (uint8_t[]){0x52}, 1, 0},
+ {0x6A, (uint8_t[]){0x59}, 1, 0},
+ {0x6B, (uint8_t[]){0x4C}, 1, 0},
+ {0x6C, (uint8_t[]){0x48}, 1, 0},
+ {0x6D, (uint8_t[]){0x3A}, 1, 0},
+ {0x6E, (uint8_t[]){0x26}, 1, 0},
+ {0x6F, (uint8_t[]){0x00}, 1, 0},
+ {0x70, (uint8_t[]){0x7F}, 1, 0},
+ {0x71, (uint8_t[]){0x6A}, 1, 0},
+ {0x72, (uint8_t[]){0x5B}, 1, 0},
+ {0x73, (uint8_t[]){0x4F}, 1, 0},
+ {0x74, (uint8_t[]){0x4A}, 1, 0},
+ {0x75, (uint8_t[]){0x3D}, 1, 0},
+ {0x76, (uint8_t[]){0x41}, 1, 0},
+ {0x77, (uint8_t[]){0x2A}, 1, 0},
+ {0x78, (uint8_t[]){0x44}, 1, 0},
+ {0x79, (uint8_t[]){0x43}, 1, 0},
+ {0x7A, (uint8_t[]){0x44}, 1, 0},
+ {0x7B, (uint8_t[]){0x62}, 1, 0},
+ {0x7C, (uint8_t[]){0x52}, 1, 0},
+ {0x7D, (uint8_t[]){0x59}, 1, 0},
+ {0x7E, (uint8_t[]){0x4C}, 1, 0},
+ {0x7F, (uint8_t[]){0x48}, 1, 0},
+ {0x80, (uint8_t[]){0x3A}, 1, 0},
+ {0x81, (uint8_t[]){0x26}, 1, 0},
+ {0x82, (uint8_t[]){0x00}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x00, (uint8_t[]){0x42}, 1, 0},
+ {0x01, (uint8_t[]){0x42}, 1, 0},
+ {0x02, (uint8_t[]){0x40}, 1, 0},
+ {0x03, (uint8_t[]){0x40}, 1, 0},
+ {0x04, (uint8_t[]){0x5E}, 1, 0},
+ {0x05, (uint8_t[]){0x5E}, 1, 0},
+ {0x06, (uint8_t[]){0x5F}, 1, 0},
+ {0x07, (uint8_t[]){0x5F}, 1, 0},
+ {0x08, (uint8_t[]){0x5F}, 1, 0},
+ {0x09, (uint8_t[]){0x57}, 1, 0},
+ {0x0A, (uint8_t[]){0x57}, 1, 0},
+ {0x0B, (uint8_t[]){0x77}, 1, 0},
+ {0x0C, (uint8_t[]){0x77}, 1, 0},
+ {0x0D, (uint8_t[]){0x47}, 1, 0},
+ {0x0E, (uint8_t[]){0x47}, 1, 0},
+ {0x0F, (uint8_t[]){0x45}, 1, 0},
+ {0x10, (uint8_t[]){0x45}, 1, 0},
+ {0x11, (uint8_t[]){0x4B}, 1, 0},
+ {0x12, (uint8_t[]){0x4B}, 1, 0},
+ {0x13, (uint8_t[]){0x49}, 1, 0},
+ {0x14, (uint8_t[]){0x49}, 1, 0},
+ {0x15, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x16, (uint8_t[]){0x41}, 1, 0},
+ {0x17, (uint8_t[]){0x41}, 1, 0},
+ {0x18, (uint8_t[]){0x40}, 1, 0},
+ {0x19, (uint8_t[]){0x40}, 1, 0},
+ {0x1A, (uint8_t[]){0x5E}, 1, 0},
+ {0x1B, (uint8_t[]){0x5E}, 1, 0},
+ {0x1C, (uint8_t[]){0x5F}, 1, 0},
+ {0x1D, (uint8_t[]){0x5F}, 1, 0},
+ {0x1E, (uint8_t[]){0x5F}, 1, 0},
+ {0x1F, (uint8_t[]){0x57}, 1, 0},
+ {0x20, (uint8_t[]){0x57}, 1, 0},
+ {0x21, (uint8_t[]){0x77}, 1, 0},
+ {0x22, (uint8_t[]){0x77}, 1, 0},
+ {0x23, (uint8_t[]){0x46}, 1, 0},
+ {0x24, (uint8_t[]){0x46}, 1, 0},
+ {0x25, (uint8_t[]){0x44}, 1, 0},
+ {0x26, (uint8_t[]){0x44}, 1, 0},
+ {0x27, (uint8_t[]){0x4A}, 1, 0},
+ {0x28, (uint8_t[]){0x4A}, 1, 0},
+ {0x29, (uint8_t[]){0x48}, 1, 0},
+ {0x2A, (uint8_t[]){0x48}, 1, 0},
+ {0x2B, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x2C, (uint8_t[]){0x01}, 1, 0},
+ {0x2D, (uint8_t[]){0x01}, 1, 0},
+ {0x2E, (uint8_t[]){0x00}, 1, 0},
+ {0x2F, (uint8_t[]){0x00}, 1, 0},
+ {0x30, (uint8_t[]){0x1F}, 1, 0},
+ {0x31, (uint8_t[]){0x1F}, 1, 0},
+ {0x32, (uint8_t[]){0x1E}, 1, 0},
+ {0x33, (uint8_t[]){0x1E}, 1, 0},
+ {0x34, (uint8_t[]){0x1F}, 1, 0},
+ {0x35, (uint8_t[]){0x17}, 1, 0},
+ {0x36, (uint8_t[]){0x17}, 1, 0},
+ {0x37, (uint8_t[]){0x37}, 1, 0},
+ {0x38, (uint8_t[]){0x37}, 1, 0},
+ {0x39, (uint8_t[]){0x08}, 1, 0},
+ {0x3A, (uint8_t[]){0x08}, 1, 0},
+ {0x3B, (uint8_t[]){0x0A}, 1, 0},
+ {0x3C, (uint8_t[]){0x0A}, 1, 0},
+ {0x3D, (uint8_t[]){0x04}, 1, 0},
+ {0x3E, (uint8_t[]){0x04}, 1, 0},
+ {0x3F, (uint8_t[]){0x06}, 1, 0},
+ {0x40, (uint8_t[]){0x06}, 1, 0},
+ {0x41, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x42, (uint8_t[]){0x02}, 1, 0},
+ {0x43, (uint8_t[]){0x02}, 1, 0},
+ {0x44, (uint8_t[]){0x00}, 1, 0},
+ {0x45, (uint8_t[]){0x00}, 1, 0},
+ {0x46, (uint8_t[]){0x1F}, 1, 0},
+ {0x47, (uint8_t[]){0x1F}, 1, 0},
+ {0x48, (uint8_t[]){0x1E}, 1, 0},
+ {0x49, (uint8_t[]){0x1E}, 1, 0},
+ {0x4A, (uint8_t[]){0x1F}, 1, 0},
+ {0x4B, (uint8_t[]){0x17}, 1, 0},
+ {0x4C, (uint8_t[]){0x17}, 1, 0},
+ {0x4D, (uint8_t[]){0x37}, 1, 0},
+ {0x4E, (uint8_t[]){0x37}, 1, 0},
+ {0x4F, (uint8_t[]){0x09}, 1, 0},
+ {0x50, (uint8_t[]){0x09}, 1, 0},
+ {0x51, (uint8_t[]){0x0B}, 1, 0},
+ {0x52, (uint8_t[]){0x0B}, 1, 0},
+ {0x53, (uint8_t[]){0x05}, 1, 0},
+ {0x54, (uint8_t[]){0x05}, 1, 0},
+ {0x55, (uint8_t[]){0x07}, 1, 0},
+ {0x56, (uint8_t[]){0x07}, 1, 0},
+ {0x57, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x58, (uint8_t[]){0x40}, 1, 0},
+ {0x5B, (uint8_t[]){0x30}, 1, 0},
+ {0x5C, (uint8_t[]){0x00}, 1, 0},
+ {0x5D, (uint8_t[]){0x34}, 1, 0},
+ {0x5E, (uint8_t[]){0x05}, 1, 0},
+ {0x5F, (uint8_t[]){0x02}, 1, 0},
+ {0x63, (uint8_t[]){0x00}, 1, 0},
+ {0x64, (uint8_t[]){0x6A}, 1, 0},
+ {0x67, (uint8_t[]){0x73}, 1, 0},
+ {0x68, (uint8_t[]){0x07}, 1, 0},
+ {0x69, (uint8_t[]){0x08}, 1, 0},
+ {0x6A, (uint8_t[]){0x6A}, 1, 0},
+ {0x6B, (uint8_t[]){0x08}, 1, 0},
+
+ {0x6C, (uint8_t[]){0x00}, 1, 0},
+ {0x6D, (uint8_t[]){0x00}, 1, 0},
+ {0x6E, (uint8_t[]){0x00}, 1, 0},
+ {0x6F, (uint8_t[]){0x88}, 1, 0},
+
+ {0x75, (uint8_t[]){0xFF}, 1, 0},
+ {0x77, (uint8_t[]){0xDD}, 1, 0},
+ {0x78, (uint8_t[]){0x2C}, 1, 0},
+ {0x79, (uint8_t[]){0x15}, 1, 0},
+ {0x7A, (uint8_t[]){0x17}, 1, 0},
+ {0x7D, (uint8_t[]){0x14}, 1, 0},
+ {0x7E, (uint8_t[]){0x82}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x04}, 1, 0},
+ {0x00, (uint8_t[]){0x0E}, 1, 0},
+ {0x02, (uint8_t[]){0xB3}, 1, 0},
+ {0x09, (uint8_t[]){0x61}, 1, 0},
+ {0x0E, (uint8_t[]){0x48}, 1, 0},
+ {0x37, (uint8_t[]){0x58}, 1, 0}, // 鍏ㄥ織
+ {0x2B, (uint8_t[]){0x0F}, 1, 0}, // 鍏ㄥ織
+
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+
+ {0xE6, (uint8_t[]){0x02}, 1, 0},
+ {0xE7, (uint8_t[]){0x0C}, 1, 0},
+
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+
+ {0x29, (uint8_t[]){0x00}, 1, 20},
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/README.md
new file mode 100644
index 0000000..98a8081
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/README.md
@@ -0,0 +1,11 @@
+# Waveshare ESP32-P4-WIFI6-Touch-LCD-3.5
+
+**ESP32-P4-WIFI6-Touch-LCD-3.5** is the ESP32-P4 +ESP32-C6 screen development board launched by waveshare.
+
+## Configuration
+
+Configuration in `menuconfig`.
+
+Selection Board Type `Xiaozhi Assistant --> Board Type`
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-3.5
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.h
new file mode 100644
index 0000000..749b601
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.h
@@ -0,0 +1,45 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_53
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_35
+
+#define DISPLAY_WIDTH (320)
+#define DISPLAY_HEIGHT (480)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_28
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define TOUCH_RST_PIN GPIO_NUM_29
+#define TOUCH_INT_PIN GPIO_NUM_50
+#define LCD_SPI_MOSI_PIN GPIO_NUM_20
+#define LCD_SPI_CLK_PIN GPIO_NUM_21
+#define LCD_SPI_CS_PIN GPIO_NUM_23
+#define LCD_SPI_DC_PIN GPIO_NUM_26
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.json
new file mode 100644
index 0000000..50f6408
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/config.json
@@ -0,0 +1,17 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32p4",
+ "builds": [
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-3.5",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_5=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/esp32-p4-wifi6-touch-lcd-3.5.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/esp32-p4-wifi6-touch-lcd-3.5.cc
new file mode 100644
index 0000000..f042611
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd-3.5/esp32-p4-wifi6-touch-lcd-3.5.cc
@@ -0,0 +1,191 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "display/lcd_display.h"
+#include "button.h"
+#include "esp_video.h"
+#include "esp_video_init.h"
+#include "esp_lcd_panel_ops.h"
+#include <driver/spi_master.h>
+#include "esp_lcd_st7796.h"
+#include "config.h"
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lvgl_port.h>
+#include "esp_lcd_touch_ft5x06.h"
+
+#define TAG "WaveshareEsp32p4"
+
+class WaveshareEsp32p4 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay *display_;
+ EspVideo* camera_ = nullptr;
+
+
+ esp_err_t i2c_device_probe(uint8_t addr) {
+ return i2c_master_probe(i2c_bus_, addr, 100);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+ }
+
+ void InitializeLCD() {
+ esp_lcd_panel_io_handle_t io = NULL;
+ esp_lcd_panel_handle_t disp_panel = NULL;
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = LCD_SPI_CLK_PIN;
+ buscfg.mosi_io_num = LCD_SPI_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.quadwp_io_num = GPIO_NUM_NC;
+ buscfg.quadhd_io_num = GPIO_NUM_NC;
+ buscfg.max_transfer_sz = (DISPLAY_WIDTH * DISPLAY_HEIGHT);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.dc_gpio_num = LCD_SPI_DC_PIN;
+ io_config.cs_gpio_num = LCD_SPI_CS_PIN;
+ io_config.pclk_hz = (80 * 1000 * 1000);
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ io_config.spi_mode = 3;
+ io_config.trans_queue_depth = 10;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI2_HOST, &io_config, &io));
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .color_space = ESP_LCD_COLOR_SPACE_BGR,
+ .bits_per_pixel = 16,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(io, &lcd_dev_config, &disp_panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(disp_panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(disp_panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(disp_panel, true));
+ esp_lcd_panel_disp_on_off(disp_panel, true);
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(disp_panel, true, false));
+ display_ = new SpiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = TOUCH_RST_PIN,
+ .int_gpio_num = TOUCH_INT_PIN,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 1,
+ .mirror_x = 1,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {};
+ tp_io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS;
+ tp_io_config.control_phase_bytes = 1;
+ tp_io_config.dc_bit_offset = 0;
+ tp_io_config.lcd_cmd_bits = 8;
+ tp_io_config.flags.disable_control_phase = 1;
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
+ lvgl_port_touch_cfg_t touch_cfg = {};
+ touch_cfg.disp = lv_display_get_default();
+ touch_cfg.handle = tp;
+ lvgl_port_add_touch(&touch_cfg);
+ }
+ void InitializeCamera() {
+ esp_video_init_csi_config_t base_csi_config = {
+ .sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+ .freq = 400000,
+ },
+ .reset_pin = GPIO_NUM_NC,
+ .pwdn_pin = GPIO_NUM_NC,
+ };
+
+ esp_video_init_config_t cam_config = {
+ .csi = &base_csi_config,
+ };
+
+ camera_ = new EspVideo(cam_config);
+ }
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ auto state = app.GetDeviceState();
+ if (state == kDeviceStateStarting ||
+ state == kDeviceStateConnecting ||
+ state == kDeviceStateWifiConfiguring) {
+ EnterWifiConfigMode();
+ return;
+ }
+ esp_restart();
+ });
+ Application::GetInstance().StartListening();
+ }
+
+public:
+ WaveshareEsp32p4() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeLCD();
+ InitializeTouch();
+ InitializeCamera();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_1,
+ 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,
+ true,
+ false);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override {
+ return display_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+};
+
+DECLARE_BOARD(WaveshareEsp32p4);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/README.md
new file mode 100644
index 0000000..3f5f1d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/README.md
@@ -0,0 +1,18 @@
+# Waveshare ESP32-P4-WIFI6-Touch-LCD
+
+
+**ESP32-P4-WIFI6-Touch-LCD** series is the ESP32-P4 +ESP32-C6 screen development board launched by waveshare
+
+## Configuration
+
+Configuration in `menuconfig`.
+
+Selection Board Type `Xiaozhi Assistant --> Board Type`
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-4B
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-4.3
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-7B
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-3.4C
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-4C
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-7
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-8
+- Waveshare ESP32-P4-WIFI6-Touch-LCD-10.1
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.h
new file mode 100644
index 0000000..057f3d5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.h
@@ -0,0 +1,98 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_53
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_35
+
+#if CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B
+#define DISPLAY_WIDTH (720)
+#define DISPLAY_HEIGHT (720)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (480)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3
+#define DISPLAY_WIDTH (480)
+#define DISPLAY_HEIGHT (800)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (500)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B
+#define DISPLAY_WIDTH (1024)
+#define DISPLAY_HEIGHT (600)
+#define PIN_NUM_LCD_RST GPIO_NUM_33
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_32
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (900)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C
+#define DISPLAY_WIDTH (800)
+#define DISPLAY_HEIGHT (800)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (1500)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C
+#define DISPLAY_WIDTH (720)
+#define DISPLAY_HEIGHT (720)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (1500)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7
+#define DISPLAY_WIDTH (720)
+#define DISPLAY_HEIGHT (1280)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (1000)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8
+#define DISPLAY_WIDTH (800)
+#define DISPLAY_HEIGHT (1280)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (1500)
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1
+#define DISPLAY_WIDTH (800)
+#define DISPLAY_HEIGHT (1280)
+#define PIN_NUM_LCD_RST GPIO_NUM_27
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_26
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#define LCD_MIPI_DSI_LANE_BITRATE_MBPS (1500)
+#endif
+
+#define LCD_BIT_PER_PIXEL (16)
+#define DELAY_TIME_MS (3000)
+#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes
+
+#define MIPI_DSI_PHY_PWR_LDO_CHAN (3)
+#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.json
new file mode 100644
index 0000000..971934a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/config.json
@@ -0,0 +1,118 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32p4",
+ "builds": [
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-4b",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-4.3",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-7b",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-3.4c",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-4c",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-7",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-8",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ },
+ {
+ "name": "esp32-p4-wifi6-touch-lcd-10.1",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=0",
+ "CONFIG_BOOTLOADER_CACHE_32BIT_ADDR_QUAD_FLASH=y",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_CAMERA_OV5647=y",
+ "CONFIG_CAMERA_OV5647_AUTO_DETECT_MIPI_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5647_MIPI_RAW8_800X800_50FPS=y",
+ "CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/esp32-p4-wifi6-touch-lcd.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/esp32-p4-wifi6-touch-lcd.cc
new file mode 100644
index 0000000..b493aac
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/esp32-p4-wifi6-touch-lcd.cc
@@ -0,0 +1,458 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "display/lcd_display.h"
+// #include "display/no_display.h"
+#include "button.h"
+
+#include "esp_video.h"
+#include "esp_video_init.h"
+#include "esp_cam_sensor_xclk.h"
+
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_ldo_regulator.h"
+
+#if CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B
+#include "esp_lcd_st7703.h"
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3
+#include "esp_lcd_st7701.h"
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B
+#include "esp_lcd_ek79007.h"
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C || CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C \
+ || CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8 || CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1
+#include "esp_lcd_jd9365.h"
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7
+#include "esp_lcd_ili9881c.h"
+#endif
+
+#include "config.h"
+#include "lcd_init_cmds.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lvgl_port.h>
+#include "esp_lcd_touch_gt911.h"
+#define TAG "WaveshareEsp32p4"
+
+class WaveshareEsp32p4 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay *display_;
+ EspVideo* camera_ = nullptr;
+
+
+ esp_err_t i2c_device_probe(uint8_t addr) {
+ return i2c_master_probe(i2c_bus_, addr, 100);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+ }
+
+ static esp_err_t bsp_enable_dsi_phy_power(void) {
+#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0
+ // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
+ static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
+ esp_ldo_channel_config_t ldo_cfg = {
+ .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN,
+ .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
+ };
+ esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan);
+ ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
+#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0
+
+ return ESP_OK;
+ }
+
+ void InitializeLCD() {
+ bsp_enable_dsi_phy_power();
+ esp_lcd_panel_io_handle_t io = NULL;
+ esp_lcd_panel_handle_t disp_panel = NULL;
+
+ esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
+ esp_lcd_dsi_bus_config_t bus_config = {
+ .bus_id = 0,
+ .num_data_lanes = 2,
+ .lane_bit_rate_mbps = LCD_MIPI_DSI_LANE_BITRATE_MBPS,
+ };
+ esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
+
+ ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
+ // we use DBI interface to send LCD commands and parameters
+ esp_lcd_dbi_io_config_t dbi_config = {
+ .virtual_channel = 0,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ };
+ esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
+#if CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 46,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = 720,
+ .v_size = 720,
+ .hsync_pulse_width = 20,
+ .hsync_back_porch = 80,
+ .hsync_front_porch = 80,
+ .vsync_pulse_width = 4,
+ .vsync_back_porch = 12,
+ .vsync_front_porch = 30,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ st7703_vendor_config_t vendor_config = {
+
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ },
+ .flags = {
+ .use_mipi_interface = 1,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_st7703(io, &lcd_dev_config, &disp_panel);
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 30,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = 480,
+ .v_size = 800,
+ .hsync_pulse_width = 12,
+ .hsync_back_porch = 42,
+ .hsync_front_porch = 42,
+ .vsync_pulse_width = 8,
+ .vsync_back_porch = 2,
+ .vsync_front_porch = 60,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ st7701_vendor_config_t vendor_config = {
+ .init_cmds = vendor_specific_init_default,
+ .init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(st7701_lcd_init_cmd_t),
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ },
+ .flags = {
+ .use_mipi_interface = 1,
+ },
+
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_st7701(io, &lcd_dev_config, &disp_panel);
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 52,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = 1024,
+ .v_size = 600,
+ .hsync_pulse_width = 10,
+ .hsync_back_porch = 160,
+ .hsync_front_porch = 160,
+ .vsync_pulse_width = 1,
+ .vsync_back_porch = 23,
+ .vsync_front_porch = 12,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ ek79007_vendor_config_t vendor_config = {
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_ek79007(io, &lcd_dev_config, &disp_panel);
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C || CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 46,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = DISPLAY_WIDTH,
+ .v_size = DISPLAY_HEIGHT,
+ .hsync_pulse_width = 20,
+ .hsync_back_porch = 20,
+ .hsync_front_porch = 40,
+ .vsync_pulse_width = 4,
+ .vsync_back_porch = 12,
+ .vsync_front_porch = 24,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ jd9365_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ .lane_num = 2,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel);
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8 || CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 52,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = DISPLAY_WIDTH,
+ .v_size = DISPLAY_HEIGHT,
+ .hsync_pulse_width = 20,
+ .hsync_back_porch = 20,
+ .hsync_front_porch = 40,
+ .vsync_pulse_width = 4,
+ .vsync_back_porch = 10,
+ .vsync_front_porch = 30,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ jd9365_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ .lane_num = 2,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_jd9365(io, &lcd_dev_config, &disp_panel);
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 80,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = DISPLAY_WIDTH,
+ .v_size = DISPLAY_HEIGHT,
+ .hsync_pulse_width = 50,
+ .hsync_back_porch = 239,
+ .hsync_front_porch = 33,
+ .vsync_pulse_width = 30,
+ .vsync_back_porch = 20,
+ .vsync_front_porch = 2,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ ili9881c_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ .lane_num = 2,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_ili9881c(io, &lcd_dev_config, &disp_panel);
+#endif
+ esp_lcd_panel_reset(disp_panel);
+ esp_lcd_panel_init(disp_panel);
+
+ display_ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ if (ESP_OK == i2c_device_probe(ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS)) {
+ ESP_LOGI(TAG, "Touch panel found at address 0x%02X", ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS);
+ } else if (ESP_OK == i2c_device_probe(ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP)) {
+ ESP_LOGI(TAG, "Touch panel found at address 0x%02X", ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP);
+ tp_io_config.dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP;
+ } else {
+ ESP_LOGE(TAG, "Touch panel not found on I2C bus");
+ ESP_LOGE(TAG, "Tried addresses: 0x%02X and 0x%02X",
+ ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS_BACKUP);
+ return;
+ }
+
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+ void InitializeCamera() {
+ esp_video_init_csi_config_t base_csi_config = {
+ .sccb_config = {
+ .init_sccb = false,
+ .i2c_handle = i2c_bus_,
+ .freq = 400000,
+ },
+ .reset_pin = GPIO_NUM_NC,
+ .pwdn_pin = GPIO_NUM_NC,
+ };
+
+ esp_video_init_config_t cam_config = {
+ .csi = &base_csi_config,
+ };
+
+ camera_ = new EspVideo(cam_config);
+ }
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ WaveshareEsp32p4() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeLCD();
+ InitializeTouch();
+ InitializeCamera();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override {
+ return display_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+};
+
+DECLARE_BOARD(WaveshareEsp32p4);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/lcd_init_cmds.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/lcd_init_cmds.h
new file mode 100644
index 0000000..38b0f31
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-p4-wifi6-touch-lcd/lcd_init_cmds.h
@@ -0,0 +1,1124 @@
+#ifdef CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4B
+// Leave it blank
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4_3
+static const st7701_lcd_init_cmd_t vendor_specific_init_default[] = {
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x13}, 5, 0},
+ {0xEF, (uint8_t[]){0x08}, 1, 0},
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
+ {0xC0, (uint8_t[]){0x63, 0x00}, 2, 0},
+ {0xC1, (uint8_t[]){0x0D, 0x02}, 2, 0},
+ {0xC2, (uint8_t[]){0x17, 0x08}, 2, 0},
+ {0xCC, (uint8_t[]){0x10}, 1, 0},
+ {0xB0, (uint8_t[]){0x40, 0xC9, 0x94, 0x0E, 0x10, 0x05, 0x0B, 0x09, 0x08, 0x26, 0x04, 0x52, 0x10, 0x69, 0x6B, 0x69}, 16, 0},
+
+ {0xB1, (uint8_t[]){0x40, 0xD2, 0x98, 0x0C, 0x92, 0x07, 0x09, 0x08, 0x07, 0x25, 0x02, 0x0E, 0x0C, 0x6E, 0x78, 0x55}, 16, 0},
+
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
+ {0xB0, (uint8_t[]){0x5D}, 1, 0},
+ {0xB1, (uint8_t[]){0x4E}, 1, 0},
+ {0xB2, (uint8_t[]){0x87}, 1, 0},
+ {0xB3, (uint8_t[]){0x80}, 1, 0},
+ {0xB5, (uint8_t[]){0x4E}, 1, 0},
+ {0xB7, (uint8_t[]){0x85}, 1, 0},
+ {0xB8, (uint8_t[]){0x21}, 1, 0},
+ {0xB9, (uint8_t[]){0x10, 0x1F}, 2, 0},
+ {0xBB, (uint8_t[]){0x03}, 1, 0},
+ {0xBC, (uint8_t[]){0x00}, 1, 0},
+
+ {0xC1, (uint8_t[]){0x78}, 1, 0},
+ {0xC2, (uint8_t[]){0x78}, 1, 0},
+ {0xD0, (uint8_t[]){0x88}, 1, 0},
+ {0xE0, (uint8_t[]){0x00, 0x3A, 0x02}, 3, 0},
+ {0xE1, (uint8_t[]){0x04, 0xA0, 0x00, 0xA0, 0x05, 0xA0, 0x00, 0xA0, 0x00, 0x40, 0x40}, 11, 0},
+
+ {0xE2, (uint8_t[]){0x30, 0x00, 0x40, 0x40, 0x32, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00, 0xA0, 0x00}, 13, 0},
+
+ {0xE3, (uint8_t[]){0x00, 0x00, 0x33, 0x33}, 4, 0},
+ {0xE4, (uint8_t[]){0x44, 0x44}, 2, 0},
+ {0xE5, (uint8_t[]){0x09, 0x2E, 0xA0, 0xA0, 0x0B, 0x30, 0xA0, 0xA0, 0x05, 0x2A, 0xA0, 0xA0, 0x07, 0x2C, 0xA0, 0xA0}, 16, 0},
+
+ {0xE6, (uint8_t[]){0x00, 0x00, 0x33, 0x33}, 4, 0},
+ {0xE7, (uint8_t[]){0x44, 0x44}, 2, 0},
+ {0xE8, (uint8_t[]){0x08, 0x2D, 0xA0, 0xA0, 0x0A, 0x2F, 0xA0, 0xA0, 0x04, 0x29, 0xA0, 0xA0, 0x06, 0x2B, 0xA0, 0xA0}, 16, 0},
+
+ {0xEB, (uint8_t[]){0x00, 0x00, 0x4E, 0x4E, 0x00, 0x00, 0x00}, 7, 0},
+ {0xEC, (uint8_t[]){0x08, 0x01}, 2, 0},
+
+ {0xED, (uint8_t[]){0xB0, 0x2B, 0x98, 0xA4, 0x56, 0x7F, 0xFF, 0xFF, 0xFF, 0xFF, 0xF7, 0x65, 0x4A, 0x89, 0xB2, 0x0B}, 16, 0},
+
+ {0xEF, (uint8_t[]){0x08, 0x08, 0x08, 0x45, 0x3F, 0x54}, 6, 0},
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x29, (uint8_t[]){0x00}, 0, 0},
+};
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7B
+// Leave it blank
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_3_4C
+static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+
+ {0xE1, (uint8_t[]){0x93}, 1, 0},
+ {0xE2, (uint8_t[]){0x65}, 1, 0},
+ {0xE3, (uint8_t[]){0xF8}, 1, 0},
+ {0x80, (uint8_t[]){0x01}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x01}, 1, 0},
+
+ {0x00, (uint8_t[]){0x00}, 1, 0},
+ {0x01, (uint8_t[]){0x41}, 1, 0},
+ {0x03, (uint8_t[]){0x10}, 1, 0},
+ {0x04, (uint8_t[]){0x44}, 1, 0},
+
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0xD0}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1A, (uint8_t[]){0x00}, 1, 0},
+ {0x1B, (uint8_t[]){0xD0}, 1, 0},
+ {0x1C, (uint8_t[]){0x00}, 1, 0},
+
+ {0x24, (uint8_t[]){0xFE}, 1, 0},
+ {0x35, (uint8_t[]){0x26}, 1, 0},
+
+ {0x37, (uint8_t[]){0x09}, 1, 0},
+
+ {0x38, (uint8_t[]){0x04}, 1, 0},
+ {0x39, (uint8_t[]){0x08}, 1, 0},
+ {0x3A, (uint8_t[]){0x0A}, 1, 0},
+ {0x3C, (uint8_t[]){0x78}, 1, 0},
+ {0x3D, (uint8_t[]){0xFF}, 1, 0},
+ {0x3E, (uint8_t[]){0xFF}, 1, 0},
+ {0x3F, (uint8_t[]){0xFF}, 1, 0},
+
+ {0x40, (uint8_t[]){0x00}, 1, 0},
+ {0x41, (uint8_t[]){0x64}, 1, 0},
+ {0x42, (uint8_t[]){0xC7}, 1, 0},
+ {0x43, (uint8_t[]){0x18}, 1, 0},
+ {0x44, (uint8_t[]){0x0B}, 1, 0},
+ {0x45, (uint8_t[]){0x14}, 1, 0},
+
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x57, (uint8_t[]){0x49}, 1, 0},
+ {0x59, (uint8_t[]){0x0A}, 1, 0},
+ {0x5A, (uint8_t[]){0x1B}, 1, 0},
+ {0x5B, (uint8_t[]){0x19}, 1, 0},
+
+ {0x5D, (uint8_t[]){0x7F}, 1, 0},
+ {0x5E, (uint8_t[]){0x56}, 1, 0},
+ {0x5F, (uint8_t[]){0x43}, 1, 0},
+ {0x60, (uint8_t[]){0x37}, 1, 0},
+ {0x61, (uint8_t[]){0x33}, 1, 0},
+ {0x62, (uint8_t[]){0x25}, 1, 0},
+ {0x63, (uint8_t[]){0x2A}, 1, 0},
+ {0x64, (uint8_t[]){0x16}, 1, 0},
+ {0x65, (uint8_t[]){0x30}, 1, 0},
+ {0x66, (uint8_t[]){0x2F}, 1, 0},
+ {0x67, (uint8_t[]){0x32}, 1, 0},
+ {0x68, (uint8_t[]){0x53}, 1, 0},
+ {0x69, (uint8_t[]){0x43}, 1, 0},
+ {0x6A, (uint8_t[]){0x4C}, 1, 0},
+ {0x6B, (uint8_t[]){0x40}, 1, 0},
+ {0x6C, (uint8_t[]){0x3D}, 1, 0},
+ {0x6D, (uint8_t[]){0x31}, 1, 0},
+ {0x6E, (uint8_t[]){0x20}, 1, 0},
+ {0x6F, (uint8_t[]){0x0F}, 1, 0},
+
+ {0x70, (uint8_t[]){0x7F}, 1, 0},
+ {0x71, (uint8_t[]){0x56}, 1, 0},
+ {0x72, (uint8_t[]){0x43}, 1, 0},
+ {0x73, (uint8_t[]){0x37}, 1, 0},
+ {0x74, (uint8_t[]){0x33}, 1, 0},
+ {0x75, (uint8_t[]){0x25}, 1, 0},
+ {0x76, (uint8_t[]){0x2A}, 1, 0},
+ {0x77, (uint8_t[]){0x16}, 1, 0},
+ {0x78, (uint8_t[]){0x30}, 1, 0},
+ {0x79, (uint8_t[]){0x2F}, 1, 0},
+ {0x7A, (uint8_t[]){0x32}, 1, 0},
+ {0x7B, (uint8_t[]){0x53}, 1, 0},
+ {0x7C, (uint8_t[]){0x43}, 1, 0},
+ {0x7D, (uint8_t[]){0x4C}, 1, 0},
+ {0x7E, (uint8_t[]){0x40}, 1, 0},
+ {0x7F, (uint8_t[]){0x3D}, 1, 0},
+ {0x80, (uint8_t[]){0x31}, 1, 0},
+ {0x81, (uint8_t[]){0x20}, 1, 0},
+ {0x82, (uint8_t[]){0x0F}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x00, (uint8_t[]){0x5F}, 1, 0},
+ {0x01, (uint8_t[]){0x5F}, 1, 0},
+ {0x02, (uint8_t[]){0x5E}, 1, 0},
+ {0x03, (uint8_t[]){0x5E}, 1, 0},
+ {0x04, (uint8_t[]){0x50}, 1, 0},
+ {0x05, (uint8_t[]){0x48}, 1, 0},
+ {0x06, (uint8_t[]){0x48}, 1, 0},
+ {0x07, (uint8_t[]){0x4A}, 1, 0},
+ {0x08, (uint8_t[]){0x4A}, 1, 0},
+ {0x09, (uint8_t[]){0x44}, 1, 0},
+ {0x0A, (uint8_t[]){0x44}, 1, 0},
+ {0x0B, (uint8_t[]){0x46}, 1, 0},
+ {0x0C, (uint8_t[]){0x46}, 1, 0},
+ {0x0D, (uint8_t[]){0x5F}, 1, 0},
+ {0x0E, (uint8_t[]){0x5F}, 1, 0},
+ {0x0F, (uint8_t[]){0x57}, 1, 0},
+ {0x10, (uint8_t[]){0x57}, 1, 0},
+ {0x11, (uint8_t[]){0x77}, 1, 0},
+ {0x12, (uint8_t[]){0x77}, 1, 0},
+ {0x13, (uint8_t[]){0x40}, 1, 0},
+ {0x14, (uint8_t[]){0x42}, 1, 0},
+ {0x15, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x16, (uint8_t[]){0x5F}, 1, 0},
+ {0x17, (uint8_t[]){0x5F}, 1, 0},
+ {0x18, (uint8_t[]){0x5E}, 1, 0},
+ {0x19, (uint8_t[]){0x5E}, 1, 0},
+ {0x1A, (uint8_t[]){0x50}, 1, 0},
+ {0x1B, (uint8_t[]){0x49}, 1, 0},
+ {0x1C, (uint8_t[]){0x49}, 1, 0},
+ {0x1D, (uint8_t[]){0x4B}, 1, 0},
+ {0x1E, (uint8_t[]){0x4B}, 1, 0},
+ {0x1F, (uint8_t[]){0x45}, 1, 0},
+ {0x20, (uint8_t[]){0x45}, 1, 0},
+ {0x21, (uint8_t[]){0x47}, 1, 0},
+ {0x22, (uint8_t[]){0x47}, 1, 0},
+ {0x23, (uint8_t[]){0x5F}, 1, 0},
+ {0x24, (uint8_t[]){0x5F}, 1, 0},
+ {0x25, (uint8_t[]){0x57}, 1, 0},
+ {0x26, (uint8_t[]){0x57}, 1, 0},
+ {0x27, (uint8_t[]){0x77}, 1, 0},
+ {0x28, (uint8_t[]){0x77}, 1, 0},
+ {0x29, (uint8_t[]){0x41}, 1, 0},
+ {0x2A, (uint8_t[]){0x43}, 1, 0},
+ {0x2B, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x2C, (uint8_t[]){0x1E}, 1, 0},
+ {0x2D, (uint8_t[]){0x1E}, 1, 0},
+ {0x2E, (uint8_t[]){0x1F}, 1, 0},
+ {0x2F, (uint8_t[]){0x1F}, 1, 0},
+ {0x30, (uint8_t[]){0x10}, 1, 0},
+ {0x31, (uint8_t[]){0x07}, 1, 0},
+ {0x32, (uint8_t[]){0x07}, 1, 0},
+ {0x33, (uint8_t[]){0x05}, 1, 0},
+ {0x34, (uint8_t[]){0x05}, 1, 0},
+ {0x35, (uint8_t[]){0x0B}, 1, 0},
+ {0x36, (uint8_t[]){0x0B}, 1, 0},
+ {0x37, (uint8_t[]){0x09}, 1, 0},
+ {0x38, (uint8_t[]){0x09}, 1, 0},
+ {0x39, (uint8_t[]){0x1F}, 1, 0},
+ {0x3A, (uint8_t[]){0x1F}, 1, 0},
+ {0x3B, (uint8_t[]){0x17}, 1, 0},
+ {0x3C, (uint8_t[]){0x17}, 1, 0},
+ {0x3D, (uint8_t[]){0x17}, 1, 0},
+ {0x3E, (uint8_t[]){0x17}, 1, 0},
+ {0x3F, (uint8_t[]){0x03}, 1, 0},
+ {0x40, (uint8_t[]){0x01}, 1, 0},
+ {0x41, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x42, (uint8_t[]){0x1E}, 1, 0},
+ {0x43, (uint8_t[]){0x1E}, 1, 0},
+ {0x44, (uint8_t[]){0x1F}, 1, 0},
+ {0x45, (uint8_t[]){0x1F}, 1, 0},
+ {0x46, (uint8_t[]){0x10}, 1, 0},
+ {0x47, (uint8_t[]){0x06}, 1, 0},
+ {0x48, (uint8_t[]){0x06}, 1, 0},
+ {0x49, (uint8_t[]){0x04}, 1, 0},
+ {0x4A, (uint8_t[]){0x04}, 1, 0},
+ {0x4B, (uint8_t[]){0x0A}, 1, 0},
+ {0x4C, (uint8_t[]){0x0A}, 1, 0},
+ {0x4D, (uint8_t[]){0x08}, 1, 0},
+ {0x4E, (uint8_t[]){0x08}, 1, 0},
+ {0x4F, (uint8_t[]){0x1F}, 1, 0},
+ {0x50, (uint8_t[]){0x1F}, 1, 0},
+ {0x51, (uint8_t[]){0x17}, 1, 0},
+ {0x52, (uint8_t[]){0x17}, 1, 0},
+ {0x53, (uint8_t[]){0x17}, 1, 0},
+ {0x54, (uint8_t[]){0x17}, 1, 0},
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x56, (uint8_t[]){0x00}, 1, 0},
+ {0x57, (uint8_t[]){0x1F}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x58, (uint8_t[]){0x40}, 1, 0},
+ {0x59, (uint8_t[]){0x00}, 1, 0},
+ {0x5A, (uint8_t[]){0x00}, 1, 0},
+ {0x5B, (uint8_t[]){0x30}, 1, 0},
+ {0x5C, (uint8_t[]){0x01}, 1, 0},
+ {0x5D, (uint8_t[]){0x30}, 1, 0},
+ {0x5E, (uint8_t[]){0x01}, 1, 0},
+ {0x5F, (uint8_t[]){0x02}, 1, 0},
+ {0x60, (uint8_t[]){0x30}, 1, 0},
+ {0x61, (uint8_t[]){0x03}, 1, 0},
+ {0x62, (uint8_t[]){0x04}, 1, 0},
+ {0x63, (uint8_t[]){0x04}, 1, 0},
+ {0x64, (uint8_t[]){0xA6}, 1, 0},
+ {0x65, (uint8_t[]){0x43}, 1, 0},
+ {0x66, (uint8_t[]){0x30}, 1, 0},
+ {0x67, (uint8_t[]){0x73}, 1, 0},
+ {0x68, (uint8_t[]){0x05}, 1, 0},
+ {0x69, (uint8_t[]){0x04}, 1, 0},
+ {0x6A, (uint8_t[]){0x7F}, 1, 0},
+ {0x6B, (uint8_t[]){0x08}, 1, 0},
+ {0x6C, (uint8_t[]){0x00}, 1, 0},
+ {0x6D, (uint8_t[]){0x04}, 1, 0},
+ {0x6E, (uint8_t[]){0x04}, 1, 0},
+ {0x6F, (uint8_t[]){0x88}, 1, 0},
+
+ {0x75, (uint8_t[]){0xD9}, 1, 0},
+ {0x76, (uint8_t[]){0x00}, 1, 0},
+ {0x77, (uint8_t[]){0x33}, 1, 0},
+ {0x78, (uint8_t[]){0x43}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+
+ {0x29, (uint8_t[]){0x00}, 1, 20},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+};
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_4C
+static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+
+ {0xE1, (uint8_t[]){0x93}, 1, 0},
+ {0xE2, (uint8_t[]){0x65}, 1, 0},
+ {0xE3, (uint8_t[]){0xF8}, 1, 0},
+ {0x80, (uint8_t[]){0x01}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x01}, 1, 0},
+
+ {0x00, (uint8_t[]){0x00}, 1, 0},
+ {0x01, (uint8_t[]){0x41}, 1, 0},
+ {0x03, (uint8_t[]){0x10}, 1, 0},
+ {0x04, (uint8_t[]){0x44}, 1, 0},
+
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0xD0}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1A, (uint8_t[]){0x00}, 1, 0},
+ {0x1B, (uint8_t[]){0xD0}, 1, 0},
+ {0x1C, (uint8_t[]){0x00}, 1, 0},
+
+ {0x24, (uint8_t[]){0xFE}, 1, 0},
+ {0x35, (uint8_t[]){0x26}, 1, 0},
+
+ {0x37, (uint8_t[]){0x09}, 1, 0},
+
+ {0x38, (uint8_t[]){0x04}, 1, 0},
+ {0x39, (uint8_t[]){0x08}, 1, 0},
+ {0x3A, (uint8_t[]){0x0A}, 1, 0},
+ {0x3C, (uint8_t[]){0x78}, 1, 0},
+ {0x3D, (uint8_t[]){0xFF}, 1, 0},
+ {0x3E, (uint8_t[]){0xFF}, 1, 0},
+ {0x3F, (uint8_t[]){0xFF}, 1, 0},
+
+ {0x40, (uint8_t[]){0x04}, 1, 0},
+ {0x41, (uint8_t[]){0x64}, 1, 0},
+ {0x42, (uint8_t[]){0xC7}, 1, 0},
+ {0x43, (uint8_t[]){0x18}, 1, 0},
+ {0x44, (uint8_t[]){0x0B}, 1, 0},
+ {0x45, (uint8_t[]){0x14}, 1, 0},
+
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x57, (uint8_t[]){0x49}, 1, 0},
+ {0x59, (uint8_t[]){0x0A}, 1, 0},
+ {0x5A, (uint8_t[]){0x1B}, 1, 0},
+ {0x5B, (uint8_t[]){0x19}, 1, 0},
+
+ {0x5D, (uint8_t[]){0x7F}, 1, 0},
+ {0x5E, (uint8_t[]){0x56}, 1, 0},
+ {0x5F, (uint8_t[]){0x43}, 1, 0},
+ {0x60, (uint8_t[]){0x37}, 1, 0},
+ {0x61, (uint8_t[]){0x33}, 1, 0},
+ {0x62, (uint8_t[]){0x25}, 1, 0},
+ {0x63, (uint8_t[]){0x2A}, 1, 0},
+ {0x64, (uint8_t[]){0x16}, 1, 0},
+ {0x65, (uint8_t[]){0x30}, 1, 0},
+ {0x66, (uint8_t[]){0x2F}, 1, 0},
+ {0x67, (uint8_t[]){0x32}, 1, 0},
+ {0x68, (uint8_t[]){0x53}, 1, 0},
+ {0x69, (uint8_t[]){0x43}, 1, 0},
+ {0x6A, (uint8_t[]){0x4C}, 1, 0},
+ {0x6B, (uint8_t[]){0x40}, 1, 0},
+ {0x6C, (uint8_t[]){0x3D}, 1, 0},
+ {0x6D, (uint8_t[]){0x31}, 1, 0},
+ {0x6E, (uint8_t[]){0x20}, 1, 0},
+ {0x6F, (uint8_t[]){0x0F}, 1, 0},
+
+ {0x70, (uint8_t[]){0x7F}, 1, 0},
+ {0x71, (uint8_t[]){0x56}, 1, 0},
+ {0x72, (uint8_t[]){0x43}, 1, 0},
+ {0x73, (uint8_t[]){0x37}, 1, 0},
+ {0x74, (uint8_t[]){0x33}, 1, 0},
+ {0x75, (uint8_t[]){0x25}, 1, 0},
+ {0x76, (uint8_t[]){0x2A}, 1, 0},
+ {0x77, (uint8_t[]){0x16}, 1, 0},
+ {0x78, (uint8_t[]){0x30}, 1, 0},
+ {0x79, (uint8_t[]){0x2F}, 1, 0},
+ {0x7A, (uint8_t[]){0x32}, 1, 0},
+ {0x7B, (uint8_t[]){0x53}, 1, 0},
+ {0x7C, (uint8_t[]){0x43}, 1, 0},
+ {0x7D, (uint8_t[]){0x4C}, 1, 0},
+ {0x7E, (uint8_t[]){0x40}, 1, 0},
+ {0x7F, (uint8_t[]){0x3D}, 1, 0},
+ {0x80, (uint8_t[]){0x31}, 1, 0},
+ {0x81, (uint8_t[]){0x20}, 1, 0},
+ {0x82, (uint8_t[]){0x0F}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x00, (uint8_t[]){0x5F}, 1, 0},
+ {0x01, (uint8_t[]){0x5F}, 1, 0},
+ {0x02, (uint8_t[]){0x5E}, 1, 0},
+ {0x03, (uint8_t[]){0x5E}, 1, 0},
+ {0x04, (uint8_t[]){0x50}, 1, 0},
+ {0x05, (uint8_t[]){0x48}, 1, 0},
+ {0x06, (uint8_t[]){0x48}, 1, 0},
+ {0x07, (uint8_t[]){0x4A}, 1, 0},
+ {0x08, (uint8_t[]){0x4A}, 1, 0},
+ {0x09, (uint8_t[]){0x44}, 1, 0},
+ {0x0A, (uint8_t[]){0x44}, 1, 0},
+ {0x0B, (uint8_t[]){0x46}, 1, 0},
+ {0x0C, (uint8_t[]){0x46}, 1, 0},
+ {0x0D, (uint8_t[]){0x5F}, 1, 0},
+ {0x0E, (uint8_t[]){0x5F}, 1, 0},
+ {0x0F, (uint8_t[]){0x57}, 1, 0},
+ {0x10, (uint8_t[]){0x57}, 1, 0},
+ {0x11, (uint8_t[]){0x77}, 1, 0},
+ {0x12, (uint8_t[]){0x77}, 1, 0},
+ {0x13, (uint8_t[]){0x40}, 1, 0},
+ {0x14, (uint8_t[]){0x42}, 1, 0},
+ {0x15, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x16, (uint8_t[]){0x5F}, 1, 0},
+ {0x17, (uint8_t[]){0x5F}, 1, 0},
+ {0x18, (uint8_t[]){0x5E}, 1, 0},
+ {0x19, (uint8_t[]){0x5E}, 1, 0},
+ {0x1A, (uint8_t[]){0x50}, 1, 0},
+ {0x1B, (uint8_t[]){0x49}, 1, 0},
+ {0x1C, (uint8_t[]){0x49}, 1, 0},
+ {0x1D, (uint8_t[]){0x4B}, 1, 0},
+ {0x1E, (uint8_t[]){0x4B}, 1, 0},
+ {0x1F, (uint8_t[]){0x45}, 1, 0},
+ {0x20, (uint8_t[]){0x45}, 1, 0},
+ {0x21, (uint8_t[]){0x47}, 1, 0},
+ {0x22, (uint8_t[]){0x47}, 1, 0},
+ {0x23, (uint8_t[]){0x5F}, 1, 0},
+ {0x24, (uint8_t[]){0x5F}, 1, 0},
+ {0x25, (uint8_t[]){0x57}, 1, 0},
+ {0x26, (uint8_t[]){0x57}, 1, 0},
+ {0x27, (uint8_t[]){0x77}, 1, 0},
+ {0x28, (uint8_t[]){0x77}, 1, 0},
+ {0x29, (uint8_t[]){0x41}, 1, 0},
+ {0x2A, (uint8_t[]){0x43}, 1, 0},
+ {0x2B, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x2C, (uint8_t[]){0x1E}, 1, 0},
+ {0x2D, (uint8_t[]){0x1E}, 1, 0},
+ {0x2E, (uint8_t[]){0x1F}, 1, 0},
+ {0x2F, (uint8_t[]){0x1F}, 1, 0},
+ {0x30, (uint8_t[]){0x10}, 1, 0},
+ {0x31, (uint8_t[]){0x07}, 1, 0},
+ {0x32, (uint8_t[]){0x07}, 1, 0},
+ {0x33, (uint8_t[]){0x05}, 1, 0},
+ {0x34, (uint8_t[]){0x05}, 1, 0},
+ {0x35, (uint8_t[]){0x0B}, 1, 0},
+ {0x36, (uint8_t[]){0x0B}, 1, 0},
+ {0x37, (uint8_t[]){0x09}, 1, 0},
+ {0x38, (uint8_t[]){0x09}, 1, 0},
+ {0x39, (uint8_t[]){0x1F}, 1, 0},
+ {0x3A, (uint8_t[]){0x1F}, 1, 0},
+ {0x3B, (uint8_t[]){0x17}, 1, 0},
+ {0x3C, (uint8_t[]){0x17}, 1, 0},
+ {0x3D, (uint8_t[]){0x17}, 1, 0},
+ {0x3E, (uint8_t[]){0x17}, 1, 0},
+ {0x3F, (uint8_t[]){0x03}, 1, 0},
+ {0x40, (uint8_t[]){0x01}, 1, 0},
+ {0x41, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x42, (uint8_t[]){0x1E}, 1, 0},
+ {0x43, (uint8_t[]){0x1E}, 1, 0},
+ {0x44, (uint8_t[]){0x1F}, 1, 0},
+ {0x45, (uint8_t[]){0x1F}, 1, 0},
+ {0x46, (uint8_t[]){0x10}, 1, 0},
+ {0x47, (uint8_t[]){0x06}, 1, 0},
+ {0x48, (uint8_t[]){0x06}, 1, 0},
+ {0x49, (uint8_t[]){0x04}, 1, 0},
+ {0x4A, (uint8_t[]){0x04}, 1, 0},
+ {0x4B, (uint8_t[]){0x0A}, 1, 0},
+ {0x4C, (uint8_t[]){0x0A}, 1, 0},
+ {0x4D, (uint8_t[]){0x08}, 1, 0},
+ {0x4E, (uint8_t[]){0x08}, 1, 0},
+ {0x4F, (uint8_t[]){0x1F}, 1, 0},
+ {0x50, (uint8_t[]){0x1F}, 1, 0},
+ {0x51, (uint8_t[]){0x17}, 1, 0},
+ {0x52, (uint8_t[]){0x17}, 1, 0},
+ {0x53, (uint8_t[]){0x17}, 1, 0},
+ {0x54, (uint8_t[]){0x17}, 1, 0},
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x56, (uint8_t[]){0x00}, 1, 0},
+ {0x57, (uint8_t[]){0x1F}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x58, (uint8_t[]){0x40}, 1, 0},
+ {0x59, (uint8_t[]){0x00}, 1, 0},
+ {0x5A, (uint8_t[]){0x00}, 1, 0},
+ {0x5B, (uint8_t[]){0x30}, 1, 0},
+ {0x5C, (uint8_t[]){0x01}, 1, 0},
+ {0x5D, (uint8_t[]){0x30}, 1, 0},
+ {0x5E, (uint8_t[]){0x01}, 1, 0},
+ {0x5F, (uint8_t[]){0x02}, 1, 0},
+ {0x60, (uint8_t[]){0x30}, 1, 0},
+ {0x61, (uint8_t[]){0x03}, 1, 0},
+ {0x62, (uint8_t[]){0x04}, 1, 0},
+ {0x63, (uint8_t[]){0x04}, 1, 0},
+ {0x64, (uint8_t[]){0xA6}, 1, 0},
+ {0x65, (uint8_t[]){0x43}, 1, 0},
+ {0x66, (uint8_t[]){0x30}, 1, 0},
+ {0x67, (uint8_t[]){0x73}, 1, 0},
+ {0x68, (uint8_t[]){0x05}, 1, 0},
+ {0x69, (uint8_t[]){0x04}, 1, 0},
+ {0x6A, (uint8_t[]){0x7F}, 1, 0},
+ {0x6B, (uint8_t[]){0x08}, 1, 0},
+ {0x6C, (uint8_t[]){0x00}, 1, 0},
+ {0x6D, (uint8_t[]){0x04}, 1, 0},
+ {0x6E, (uint8_t[]){0x04}, 1, 0},
+ {0x6F, (uint8_t[]){0x88}, 1, 0},
+
+ {0x75, (uint8_t[]){0xD9}, 1, 0},
+ {0x76, (uint8_t[]){0x00}, 1, 0},
+ {0x77, (uint8_t[]){0x33}, 1, 0},
+ {0x78, (uint8_t[]){0x43}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+
+ {0x29, (uint8_t[]){0x00}, 1, 20},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+};
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_7
+static const ili9881c_lcd_init_cmd_t lcd_init_cmds[] = {
+ // {cmd, { data }, data_size, delay_ms}
+ /**** CMD_Page 3 ****/
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x03}, 3, 0},
+ // {0x01, (uint8_t []){0x00}, 1, 0},
+ {0x01, (uint8_t[]){0x00}, 1, 0},
+ {0x02, (uint8_t[]){0x00}, 1, 0},
+ {0x03, (uint8_t[]){0x73}, 1, 0},
+ {0x04, (uint8_t[]){0x00}, 1, 0},
+ {0x05, (uint8_t[]){0x00}, 1, 0},
+ {0x06, (uint8_t[]){0x0A}, 1, 0},
+ {0x07, (uint8_t[]){0x00}, 1, 0},
+ {0x08, (uint8_t[]){0x00}, 1, 0},
+ {0x09, (uint8_t[]){0x61}, 1, 0},
+ {0x0A, (uint8_t[]){0x00}, 1, 0},
+ {0x0B, (uint8_t[]){0x00}, 1, 0},
+ {0x0C, (uint8_t[]){0x01}, 1, 0},
+ {0x0D, (uint8_t[]){0x00}, 1, 0},
+ {0x0E, (uint8_t[]){0x00}, 1, 0},
+ {0x0F, (uint8_t[]){0x61}, 1, 0},
+ {0x10, (uint8_t[]){0x61}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 1, 0},
+ {0x12, (uint8_t[]){0x00}, 1, 0},
+ {0x13, (uint8_t[]){0x00}, 1, 0},
+ {0x14, (uint8_t[]){0x00}, 1, 0},
+ {0x15, (uint8_t[]){0x00}, 1, 0},
+ {0x16, (uint8_t[]){0x00}, 1, 0},
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0x00}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1A, (uint8_t[]){0x00}, 1, 0},
+ {0x1B, (uint8_t[]){0x00}, 1, 0},
+ {0x1C, (uint8_t[]){0x00}, 1, 0},
+ {0x1D, (uint8_t[]){0x00}, 1, 0},
+ {0x1E, (uint8_t[]){0x40}, 1, 0},
+ {0x1F, (uint8_t[]){0x80}, 1, 0},
+ {0x20, (uint8_t[]){0x06}, 1, 0},
+ {0x21, (uint8_t[]){0x01}, 1, 0},
+ {0x22, (uint8_t[]){0x00}, 1, 0},
+ {0x23, (uint8_t[]){0x00}, 1, 0},
+ {0x24, (uint8_t[]){0x00}, 1, 0},
+ {0x25, (uint8_t[]){0x00}, 1, 0},
+ {0x26, (uint8_t[]){0x00}, 1, 0},
+ {0x27, (uint8_t[]){0x00}, 1, 0},
+ {0x28, (uint8_t[]){0x33}, 1, 0},
+ {0x29, (uint8_t[]){0x03}, 1, 0},
+ {0x2A, (uint8_t[]){0x00}, 1, 0},
+ {0x2B, (uint8_t[]){0x00}, 1, 0},
+ {0x2C, (uint8_t[]){0x00}, 1, 0},
+ {0x2D, (uint8_t[]){0x00}, 1, 0},
+ {0x2E, (uint8_t[]){0x00}, 1, 0},
+ {0x2F, (uint8_t[]){0x00}, 1, 0},
+ {0x30, (uint8_t[]){0x00}, 1, 0},
+ {0x31, (uint8_t[]){0x00}, 1, 0},
+ {0x32, (uint8_t[]){0x00}, 1, 0},
+ {0x33, (uint8_t[]){0x00}, 1, 0},
+ {0x34, (uint8_t[]){0x04}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x36, (uint8_t[]){0x00}, 1, 0},
+ {0x37, (uint8_t[]){0x00}, 1, 0},
+ {0x38, (uint8_t[]){0x3C}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x3A, (uint8_t[]){0x00}, 1, 0},
+ {0x3B, (uint8_t[]){0x00}, 1, 0},
+ {0x3C, (uint8_t[]){0x00}, 1, 0},
+ {0x3D, (uint8_t[]){0x00}, 1, 0},
+ {0x3E, (uint8_t[]){0x00}, 1, 0},
+ {0x3F, (uint8_t[]){0x00}, 1, 0},
+ {0x40, (uint8_t[]){0x00}, 1, 0},
+ {0x41, (uint8_t[]){0x00}, 1, 0},
+ {0x42, (uint8_t[]){0x00}, 1, 0},
+ {0x43, (uint8_t[]){0x00}, 1, 0},
+ {0x44, (uint8_t[]){0x00}, 1, 0},
+ {0x50, (uint8_t[]){0x10}, 1, 0},
+ {0x51, (uint8_t[]){0x32}, 1, 0},
+ {0x52, (uint8_t[]){0x54}, 1, 0},
+ {0x53, (uint8_t[]){0x76}, 1, 0},
+ {0x54, (uint8_t[]){0x98}, 1, 0},
+ {0x55, (uint8_t[]){0xBA}, 1, 0},
+ {0x56, (uint8_t[]){0x10}, 1, 0},
+ {0x57, (uint8_t[]){0x32}, 1, 0},
+ {0x58, (uint8_t[]){0x54}, 1, 0},
+ {0x59, (uint8_t[]){0x76}, 1, 0},
+ {0x5A, (uint8_t[]){0x98}, 1, 0},
+ {0x5B, (uint8_t[]){0xBA}, 1, 0},
+ {0x5C, (uint8_t[]){0xDC}, 1, 0},
+ {0x5D, (uint8_t[]){0xFE}, 1, 0},
+ {0x5E, (uint8_t[]){0x00}, 1, 0},
+ {0x5F, (uint8_t[]){0x0E}, 1, 0},
+ {0x60, (uint8_t[]){0x0F}, 1, 0},
+ {0x61, (uint8_t[]){0x0C}, 1, 0},
+ {0x62, (uint8_t[]){0x0D}, 1, 0},
+ {0x63, (uint8_t[]){0x06}, 1, 0},
+ {0x64, (uint8_t[]){0x07}, 1, 0},
+ {0x65, (uint8_t[]){0x02}, 1, 0},
+ {0x66, (uint8_t[]){0x02}, 1, 0},
+ {0x67, (uint8_t[]){0x02}, 1, 0},
+ {0x68, (uint8_t[]){0x02}, 1, 0},
+ {0x69, (uint8_t[]){0x01}, 1, 0},
+ {0x6A, (uint8_t[]){0x00}, 1, 0},
+ {0x6B, (uint8_t[]){0x02}, 1, 0},
+ {0x6C, (uint8_t[]){0x15}, 1, 0},
+ {0x6D, (uint8_t[]){0x14}, 1, 0},
+ {0x6E, (uint8_t[]){0x02}, 1, 0},
+ {0x6F, (uint8_t[]){0x02}, 1, 0},
+ {0x70, (uint8_t[]){0x02}, 1, 0},
+ {0x71, (uint8_t[]){0x02}, 1, 0},
+ {0x72, (uint8_t[]){0x02}, 1, 0},
+ {0x73, (uint8_t[]){0x02}, 1, 0},
+ {0x74, (uint8_t[]){0x02}, 1, 0},
+ {0x75, (uint8_t[]){0x0E}, 1, 0},
+ {0x76, (uint8_t[]){0x0F}, 1, 0},
+ {0x77, (uint8_t[]){0x0C}, 1, 0},
+ {0x78, (uint8_t[]){0x0D}, 1, 0},
+ {0x79, (uint8_t[]){0x06}, 1, 0},
+ {0x7A, (uint8_t[]){0x07}, 1, 0},
+ {0x7B, (uint8_t[]){0x02}, 1, 0},
+ {0x7C, (uint8_t[]){0x02}, 1, 0},
+ {0x7D, (uint8_t[]){0x02}, 1, 0},
+ {0x7E, (uint8_t[]){0x02}, 1, 0},
+ {0x7F, (uint8_t[]){0x01}, 1, 0},
+ {0x80, (uint8_t[]){0x00}, 1, 0},
+ {0x81, (uint8_t[]){0x02}, 1, 0},
+ {0x82, (uint8_t[]){0x14}, 1, 0},
+ {0x83, (uint8_t[]){0x15}, 1, 0},
+ {0x84, (uint8_t[]){0x02}, 1, 0},
+ {0x85, (uint8_t[]){0x02}, 1, 0},
+ {0x86, (uint8_t[]){0x02}, 1, 0},
+ {0x87, (uint8_t[]){0x02}, 1, 0},
+ {0x88, (uint8_t[]){0x02}, 1, 0},
+ {0x89, (uint8_t[]){0x02}, 1, 0},
+ {0x8A, (uint8_t[]){0x02}, 1, 0},
+
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x04}, 3, 0},
+ {0x38, (uint8_t[]){0x01}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x6C, (uint8_t[]){0x15}, 1, 0},
+ {0x6E, (uint8_t[]){0x2A}, 1, 0},
+ {0x6F, (uint8_t[]){0x33}, 1, 0},
+ {0x3A, (uint8_t[]){0x94}, 1, 0},
+ {0x8D, (uint8_t[]){0x14}, 1, 0},
+ {0x87, (uint8_t[]){0xBA}, 1, 0},
+ {0x26, (uint8_t[]){0x76}, 1, 0},
+ {0xB2, (uint8_t[]){0xD1}, 1, 0},
+ {0xB5, (uint8_t[]){0x06}, 1, 0},
+ {0X3B, (uint8_t[]){0X98}, 1, 0},
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x01}, 3, 0},
+ {0x22, (uint8_t[]){0x0A}, 1, 0},
+ {0x31, (uint8_t[]){0x00}, 1, 0},
+ {0x53, (uint8_t[]){0x71}, 1, 0},
+ {0x55, (uint8_t[]){0x8F}, 1, 0},
+ {0x40, (uint8_t[]){0x33}, 1, 0},
+ {0x50, (uint8_t[]){0x96}, 1, 0},
+ {0x51, (uint8_t[]){0x96}, 1, 0},
+ {0x60, (uint8_t[]){0x23}, 1, 0},
+ {0xA0, (uint8_t[]){0x08}, 1, 0},
+ {0xA1, (uint8_t[]){0x1D}, 1, 0},
+ {0xA2, (uint8_t[]){0x2A}, 1, 0},
+ {0xA3, (uint8_t[]){0x10}, 1, 0},
+ {0xA4, (uint8_t[]){0x15}, 1, 0},
+ {0xA5, (uint8_t[]){0x28}, 1, 0},
+ {0xA6, (uint8_t[]){0x1C}, 1, 0},
+ {0xA7, (uint8_t[]){0x1D}, 1, 0},
+ {0xA8, (uint8_t[]){0x7E}, 1, 0},
+ {0xA9, (uint8_t[]){0x1D}, 1, 0},
+ {0xAA, (uint8_t[]){0x29}, 1, 0},
+ {0xAB, (uint8_t[]){0x6B}, 1, 0},
+ {0xAC, (uint8_t[]){0x1A}, 1, 0},
+ {0xAD, (uint8_t[]){0x18}, 1, 0},
+ {0xAE, (uint8_t[]){0x4B}, 1, 0},
+ {0xAF, (uint8_t[]){0x20}, 1, 0},
+ {0xB0, (uint8_t[]){0x27}, 1, 0},
+ {0xB1, (uint8_t[]){0x50}, 1, 0},
+ {0xB2, (uint8_t[]){0x64}, 1, 0},
+ {0xB3, (uint8_t[]){0x39}, 1, 0},
+ {0xC0, (uint8_t[]){0x08}, 1, 0},
+ {0xC1, (uint8_t[]){0x1D}, 1, 0},
+ {0xC2, (uint8_t[]){0x2A}, 1, 0},
+ {0xC3, (uint8_t[]){0x10}, 1, 0},
+ {0xC4, (uint8_t[]){0x15}, 1, 0},
+ {0xC5, (uint8_t[]){0x28}, 1, 0},
+ {0xC6, (uint8_t[]){0x1C}, 1, 0},
+ {0xC7, (uint8_t[]){0x1D}, 1, 0},
+ {0xC8, (uint8_t[]){0x7E}, 1, 0},
+ {0xC9, (uint8_t[]){0x1D}, 1, 0},
+ {0xCA, (uint8_t[]){0x29}, 1, 0},
+ {0xCB, (uint8_t[]){0x6B}, 1, 0},
+ {0xCC, (uint8_t[]){0x1A}, 1, 0},
+ {0xCD, (uint8_t[]){0x18}, 1, 0},
+ {0xCE, (uint8_t[]){0x4B}, 1, 0},
+ {0xCF, (uint8_t[]){0x20}, 1, 0},
+ {0xD0, (uint8_t[]){0x27}, 1, 0},
+ {0xD1, (uint8_t[]){0x50}, 1, 0},
+ {0xD2, (uint8_t[]){0x64}, 1, 0},
+ {0xD3, (uint8_t[]){0x39}, 1, 0},
+
+ {0xFF, (uint8_t[]){0x98, 0x81, 0x00}, 3, 0},
+ {0x3A, (uint8_t[]){0x77}, 1, 0},
+ {0x36, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 0, 150},
+
+ {0x29, (uint8_t[]){0x00}, 0, 20},
+
+ //============ Gamma END===========
+};
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_8
+static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+ {0xE1, (uint8_t[]){0x93}, 1, 0},
+ {0xE2, (uint8_t[]){0x65}, 1, 0},
+ {0xE3, (uint8_t[]){0xF8}, 1, 0},
+ {0x80, (uint8_t[]){0x01}, 1, 0}, // 0X03锛�4-LANE;0X02锛�3-LANE;0X01:2-LANE
+
+ {0xE0, (uint8_t[]){0x01}, 1, 0},
+ {0x00, (uint8_t[]){0x00}, 1, 0},
+ {0x01, (uint8_t[]){0x4E}, 1, 0},
+ {0x03, (uint8_t[]){0x00}, 1, 0},
+ {0x04, (uint8_t[]){0x65}, 1, 0},
+
+ {0x0C, (uint8_t[]){0x74}, 1, 0},
+
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0xB7}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1A, (uint8_t[]){0x00}, 1, 0},
+ {0x1B, (uint8_t[]){0xB7}, 1, 0},
+ {0x1C, (uint8_t[]){0x00}, 1, 0},
+
+ {0x24, (uint8_t[]){0xFE}, 1, 0},
+
+ {0x37, (uint8_t[]){0x19}, 1, 0},
+
+ {0x38, (uint8_t[]){0x05}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x3A, (uint8_t[]){0x01}, 1, 0},
+ {0x3B, (uint8_t[]){0x01}, 1, 0},
+ {0x3C, (uint8_t[]){0x70}, 1, 0},
+ {0x3D, (uint8_t[]){0xFF}, 1, 0},
+ {0x3E, (uint8_t[]){0xFF}, 1, 0},
+ {0x3F, (uint8_t[]){0xFF}, 1, 0},
+
+ {0x40, (uint8_t[]){0x06}, 1, 0},
+ {0x41, (uint8_t[]){0xA0}, 1, 0},
+ {0x43, (uint8_t[]){0x1E}, 1, 0},
+ {0x44, (uint8_t[]){0x0F}, 1, 0},
+ {0x45, (uint8_t[]){0x28}, 1, 0},
+ {0x4B, (uint8_t[]){0x04}, 1, 0},
+
+ //{0x4A, (uint8_t[]){0x35}, 1, 0},//bist
+
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x56, (uint8_t[]){0x01}, 1, 0},
+ {0x57, (uint8_t[]){0xA9}, 1, 0},
+ {0x58, (uint8_t[]){0x0A}, 1, 0},
+ {0x59, (uint8_t[]){0x0A}, 1, 0},
+ {0x5A, (uint8_t[]){0x37}, 1, 0},
+ {0x5B, (uint8_t[]){0x19}, 1, 0},
+
+ {0x5D, (uint8_t[]){0x78}, 1, 0},
+ {0x5E, (uint8_t[]){0x63}, 1, 0},
+ {0x5F, (uint8_t[]){0x54}, 1, 0},
+ {0x60, (uint8_t[]){0x49}, 1, 0},
+ {0x61, (uint8_t[]){0x45}, 1, 0},
+ {0x62, (uint8_t[]){0x38}, 1, 0},
+ {0x63, (uint8_t[]){0x3D}, 1, 0},
+ {0x64, (uint8_t[]){0x28}, 1, 0},
+ {0x65, (uint8_t[]){0x43}, 1, 0},
+ {0x66, (uint8_t[]){0x41}, 1, 0},
+ {0x67, (uint8_t[]){0x43}, 1, 0},
+ {0x68, (uint8_t[]){0x62}, 1, 0},
+ {0x69, (uint8_t[]){0x50}, 1, 0},
+ {0x6A, (uint8_t[]){0x57}, 1, 0},
+ {0x6B, (uint8_t[]){0x49}, 1, 0},
+ {0x6C, (uint8_t[]){0x44}, 1, 0},
+ {0x6D, (uint8_t[]){0x37}, 1, 0},
+ {0x6E, (uint8_t[]){0x23}, 1, 0},
+ {0x6F, (uint8_t[]){0x10}, 1, 0},
+ {0x70, (uint8_t[]){0x78}, 1, 0},
+ {0x71, (uint8_t[]){0x63}, 1, 0},
+ {0x72, (uint8_t[]){0x54}, 1, 0},
+ {0x73, (uint8_t[]){0x49}, 1, 0},
+ {0x74, (uint8_t[]){0x45}, 1, 0},
+ {0x75, (uint8_t[]){0x38}, 1, 0},
+ {0x76, (uint8_t[]){0x3D}, 1, 0},
+ {0x77, (uint8_t[]){0x28}, 1, 0},
+ {0x78, (uint8_t[]){0x43}, 1, 0},
+ {0x79, (uint8_t[]){0x41}, 1, 0},
+ {0x7A, (uint8_t[]){0x43}, 1, 0},
+ {0x7B, (uint8_t[]){0x62}, 1, 0},
+ {0x7C, (uint8_t[]){0x50}, 1, 0},
+ {0x7D, (uint8_t[]){0x57}, 1, 0},
+ {0x7E, (uint8_t[]){0x49}, 1, 0},
+ {0x7F, (uint8_t[]){0x44}, 1, 0},
+ {0x80, (uint8_t[]){0x37}, 1, 0},
+ {0x81, (uint8_t[]){0x23}, 1, 0},
+ {0x82, (uint8_t[]){0x10}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x00, (uint8_t[]){0x47}, 1, 0},
+ {0x01, (uint8_t[]){0x47}, 1, 0},
+ {0x02, (uint8_t[]){0x45}, 1, 0},
+ {0x03, (uint8_t[]){0x45}, 1, 0},
+ {0x04, (uint8_t[]){0x4B}, 1, 0},
+ {0x05, (uint8_t[]){0x4B}, 1, 0},
+ {0x06, (uint8_t[]){0x49}, 1, 0},
+ {0x07, (uint8_t[]){0x49}, 1, 0},
+ {0x08, (uint8_t[]){0x41}, 1, 0},
+ {0x09, (uint8_t[]){0x1F}, 1, 0},
+ {0x0A, (uint8_t[]){0x1F}, 1, 0},
+ {0x0B, (uint8_t[]){0x1F}, 1, 0},
+ {0x0C, (uint8_t[]){0x1F}, 1, 0},
+ {0x0D, (uint8_t[]){0x1F}, 1, 0},
+ {0x0E, (uint8_t[]){0x1F}, 1, 0},
+ {0x0F, (uint8_t[]){0x5F}, 1, 0},
+ {0x10, (uint8_t[]){0x5F}, 1, 0},
+ {0x11, (uint8_t[]){0x57}, 1, 0},
+ {0x12, (uint8_t[]){0x77}, 1, 0},
+ {0x13, (uint8_t[]){0x35}, 1, 0},
+ {0x14, (uint8_t[]){0x1F}, 1, 0},
+ {0x15, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x16, (uint8_t[]){0x46}, 1, 0},
+ {0x17, (uint8_t[]){0x46}, 1, 0},
+ {0x18, (uint8_t[]){0x44}, 1, 0},
+ {0x19, (uint8_t[]){0x44}, 1, 0},
+ {0x1A, (uint8_t[]){0x4A}, 1, 0},
+ {0x1B, (uint8_t[]){0x4A}, 1, 0},
+ {0x1C, (uint8_t[]){0x48}, 1, 0},
+ {0x1D, (uint8_t[]){0x48}, 1, 0},
+ {0x1E, (uint8_t[]){0x40}, 1, 0},
+ {0x1F, (uint8_t[]){0x1F}, 1, 0},
+ {0x20, (uint8_t[]){0x1F}, 1, 0},
+ {0x21, (uint8_t[]){0x1F}, 1, 0},
+ {0x22, (uint8_t[]){0x1F}, 1, 0},
+ {0x23, (uint8_t[]){0x1F}, 1, 0},
+ {0x24, (uint8_t[]){0x1F}, 1, 0},
+ {0x25, (uint8_t[]){0x5F}, 1, 0},
+ {0x26, (uint8_t[]){0x5F}, 1, 0},
+ {0x27, (uint8_t[]){0x57}, 1, 0},
+ {0x28, (uint8_t[]){0x77}, 1, 0},
+ {0x29, (uint8_t[]){0x35}, 1, 0},
+ {0x2A, (uint8_t[]){0x1F}, 1, 0},
+ {0x2B, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x58, (uint8_t[]){0x40}, 1, 0},
+ {0x59, (uint8_t[]){0x00}, 1, 0},
+ {0x5A, (uint8_t[]){0x00}, 1, 0},
+ {0x5B, (uint8_t[]){0x10}, 1, 0},
+ {0x5C, (uint8_t[]){0x06}, 1, 0},
+ {0x5D, (uint8_t[]){0x40}, 1, 0},
+ {0x5E, (uint8_t[]){0x01}, 1, 0},
+ {0x5F, (uint8_t[]){0x02}, 1, 0},
+ {0x60, (uint8_t[]){0x30}, 1, 0},
+ {0x61, (uint8_t[]){0x01}, 1, 0},
+ {0x62, (uint8_t[]){0x02}, 1, 0},
+ {0x63, (uint8_t[]){0x03}, 1, 0},
+ {0x64, (uint8_t[]){0x6B}, 1, 0},
+ {0x65, (uint8_t[]){0x05}, 1, 0},
+ {0x66, (uint8_t[]){0x0C}, 1, 0},
+ {0x67, (uint8_t[]){0x73}, 1, 0},
+ {0x68, (uint8_t[]){0x09}, 1, 0},
+ {0x69, (uint8_t[]){0x03}, 1, 0},
+ {0x6A, (uint8_t[]){0x56}, 1, 0},
+ {0x6B, (uint8_t[]){0x08}, 1, 0},
+ {0x6C, (uint8_t[]){0x00}, 1, 0},
+ {0x6D, (uint8_t[]){0x04}, 1, 0},
+ {0x6E, (uint8_t[]){0x04}, 1, 0},
+ {0x6F, (uint8_t[]){0x88}, 1, 0},
+ {0x70, (uint8_t[]){0x00}, 1, 0},
+ {0x71, (uint8_t[]){0x00}, 1, 0},
+ {0x72, (uint8_t[]){0x06}, 1, 0},
+ {0x73, (uint8_t[]){0x7B}, 1, 0},
+ {0x74, (uint8_t[]){0x00}, 1, 0},
+ {0x75, (uint8_t[]){0xF8}, 1, 0},
+ {0x76, (uint8_t[]){0x00}, 1, 0},
+ {0x77, (uint8_t[]){0xD5}, 1, 0},
+ {0x78, (uint8_t[]){0x2E}, 1, 0},
+ {0x79, (uint8_t[]){0x12}, 1, 0},
+ {0x7A, (uint8_t[]){0x03}, 1, 0},
+ {0x7B, (uint8_t[]){0x00}, 1, 0},
+ {0x7C, (uint8_t[]){0x00}, 1, 0},
+ {0x7D, (uint8_t[]){0x03}, 1, 0},
+ {0x7E, (uint8_t[]){0x7B}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x04}, 1, 0},
+ {0x00, (uint8_t[]){0x0E}, 1, 0},
+ {0x02, (uint8_t[]){0xB3}, 1, 0},
+ {0x09, (uint8_t[]){0x60}, 1, 0},
+ {0x0E, (uint8_t[]){0x2A}, 1, 0},
+ {0x36, (uint8_t[]){0x59}, 1, 0},
+ {0x37, (uint8_t[]){0x58}, 1, 0}, // A133
+ {0x2B, (uint8_t[]){0x0F}, 1, 0}, // A133
+
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+
+ {0x29, (uint8_t[]){0x00}, 1, 20},
+
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+};
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_P4_WIFI6_TOUCH_LCD_10_1
+static const jd9365_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+ {0xE1, (uint8_t[]){0x93}, 1, 0},
+ {0xE2, (uint8_t[]){0x65}, 1, 0},
+ {0xE3, (uint8_t[]){0xF8}, 1, 0},
+ {0x80, (uint8_t[]){0x01}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x01}, 1, 0},
+ {0x00, (uint8_t[]){0x00}, 1, 0},
+ {0x01, (uint8_t[]){0x38}, 1, 0},
+ {0x03, (uint8_t[]){0x10}, 1, 0},
+ {0x04, (uint8_t[]){0x38}, 1, 0},
+
+ {0x0C, (uint8_t[]){0x74}, 1, 0},
+
+ {0x17, (uint8_t[]){0x00}, 1, 0},
+ {0x18, (uint8_t[]){0xAF}, 1, 0},
+ {0x19, (uint8_t[]){0x00}, 1, 0},
+ {0x1A, (uint8_t[]){0x00}, 1, 0},
+ {0x1B, (uint8_t[]){0xAF}, 1, 0},
+ {0x1C, (uint8_t[]){0x00}, 1, 0},
+
+ {0x35, (uint8_t[]){0x26}, 1, 0},
+
+ {0x37, (uint8_t[]){0x09}, 1, 0},
+
+ {0x38, (uint8_t[]){0x04}, 1, 0},
+ {0x39, (uint8_t[]){0x00}, 1, 0},
+ {0x3A, (uint8_t[]){0x01}, 1, 0},
+ {0x3C, (uint8_t[]){0x78}, 1, 0},
+ {0x3D, (uint8_t[]){0xFF}, 1, 0},
+ {0x3E, (uint8_t[]){0xFF}, 1, 0},
+ {0x3F, (uint8_t[]){0x7F}, 1, 0},
+
+ {0x40, (uint8_t[]){0x06}, 1, 0},
+ {0x41, (uint8_t[]){0xA0}, 1, 0},
+ {0x42, (uint8_t[]){0x81}, 1, 0},
+ {0x43, (uint8_t[]){0x1E}, 1, 0},
+ {0x44, (uint8_t[]){0x0D}, 1, 0},
+ {0x45, (uint8_t[]){0x28}, 1, 0},
+ //{0x4A, (uint8_t[]){0x35}, 1, 0},//bist
+
+ {0x55, (uint8_t[]){0x02}, 1, 0},
+ {0x57, (uint8_t[]){0x69}, 1, 0},
+ {0x59, (uint8_t[]){0x0A}, 1, 0},
+ {0x5A, (uint8_t[]){0x2A}, 1, 0},
+ {0x5B, (uint8_t[]){0x17}, 1, 0},
+
+ {0x5D, (uint8_t[]){0x7F}, 1, 0},
+ {0x5E, (uint8_t[]){0x6A}, 1, 0},
+ {0x5F, (uint8_t[]){0x5B}, 1, 0},
+ {0x60, (uint8_t[]){0x4F}, 1, 0},
+ {0x61, (uint8_t[]){0x4A}, 1, 0},
+ {0x62, (uint8_t[]){0x3D}, 1, 0},
+ {0x63, (uint8_t[]){0x41}, 1, 0},
+ {0x64, (uint8_t[]){0x2A}, 1, 0},
+ {0x65, (uint8_t[]){0x44}, 1, 0},
+ {0x66, (uint8_t[]){0x43}, 1, 0},
+ {0x67, (uint8_t[]){0x44}, 1, 0},
+ {0x68, (uint8_t[]){0x62}, 1, 0},
+ {0x69, (uint8_t[]){0x52}, 1, 0},
+ {0x6A, (uint8_t[]){0x59}, 1, 0},
+ {0x6B, (uint8_t[]){0x4C}, 1, 0},
+ {0x6C, (uint8_t[]){0x48}, 1, 0},
+ {0x6D, (uint8_t[]){0x3A}, 1, 0},
+ {0x6E, (uint8_t[]){0x26}, 1, 0},
+ {0x6F, (uint8_t[]){0x00}, 1, 0},
+ {0x70, (uint8_t[]){0x7F}, 1, 0},
+ {0x71, (uint8_t[]){0x6A}, 1, 0},
+ {0x72, (uint8_t[]){0x5B}, 1, 0},
+ {0x73, (uint8_t[]){0x4F}, 1, 0},
+ {0x74, (uint8_t[]){0x4A}, 1, 0},
+ {0x75, (uint8_t[]){0x3D}, 1, 0},
+ {0x76, (uint8_t[]){0x41}, 1, 0},
+ {0x77, (uint8_t[]){0x2A}, 1, 0},
+ {0x78, (uint8_t[]){0x44}, 1, 0},
+ {0x79, (uint8_t[]){0x43}, 1, 0},
+ {0x7A, (uint8_t[]){0x44}, 1, 0},
+ {0x7B, (uint8_t[]){0x62}, 1, 0},
+ {0x7C, (uint8_t[]){0x52}, 1, 0},
+ {0x7D, (uint8_t[]){0x59}, 1, 0},
+ {0x7E, (uint8_t[]){0x4C}, 1, 0},
+ {0x7F, (uint8_t[]){0x48}, 1, 0},
+ {0x80, (uint8_t[]){0x3A}, 1, 0},
+ {0x81, (uint8_t[]){0x26}, 1, 0},
+ {0x82, (uint8_t[]){0x00}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x02}, 1, 0},
+ {0x00, (uint8_t[]){0x42}, 1, 0},
+ {0x01, (uint8_t[]){0x42}, 1, 0},
+ {0x02, (uint8_t[]){0x40}, 1, 0},
+ {0x03, (uint8_t[]){0x40}, 1, 0},
+ {0x04, (uint8_t[]){0x5E}, 1, 0},
+ {0x05, (uint8_t[]){0x5E}, 1, 0},
+ {0x06, (uint8_t[]){0x5F}, 1, 0},
+ {0x07, (uint8_t[]){0x5F}, 1, 0},
+ {0x08, (uint8_t[]){0x5F}, 1, 0},
+ {0x09, (uint8_t[]){0x57}, 1, 0},
+ {0x0A, (uint8_t[]){0x57}, 1, 0},
+ {0x0B, (uint8_t[]){0x77}, 1, 0},
+ {0x0C, (uint8_t[]){0x77}, 1, 0},
+ {0x0D, (uint8_t[]){0x47}, 1, 0},
+ {0x0E, (uint8_t[]){0x47}, 1, 0},
+ {0x0F, (uint8_t[]){0x45}, 1, 0},
+ {0x10, (uint8_t[]){0x45}, 1, 0},
+ {0x11, (uint8_t[]){0x4B}, 1, 0},
+ {0x12, (uint8_t[]){0x4B}, 1, 0},
+ {0x13, (uint8_t[]){0x49}, 1, 0},
+ {0x14, (uint8_t[]){0x49}, 1, 0},
+ {0x15, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x16, (uint8_t[]){0x41}, 1, 0},
+ {0x17, (uint8_t[]){0x41}, 1, 0},
+ {0x18, (uint8_t[]){0x40}, 1, 0},
+ {0x19, (uint8_t[]){0x40}, 1, 0},
+ {0x1A, (uint8_t[]){0x5E}, 1, 0},
+ {0x1B, (uint8_t[]){0x5E}, 1, 0},
+ {0x1C, (uint8_t[]){0x5F}, 1, 0},
+ {0x1D, (uint8_t[]){0x5F}, 1, 0},
+ {0x1E, (uint8_t[]){0x5F}, 1, 0},
+ {0x1F, (uint8_t[]){0x57}, 1, 0},
+ {0x20, (uint8_t[]){0x57}, 1, 0},
+ {0x21, (uint8_t[]){0x77}, 1, 0},
+ {0x22, (uint8_t[]){0x77}, 1, 0},
+ {0x23, (uint8_t[]){0x46}, 1, 0},
+ {0x24, (uint8_t[]){0x46}, 1, 0},
+ {0x25, (uint8_t[]){0x44}, 1, 0},
+ {0x26, (uint8_t[]){0x44}, 1, 0},
+ {0x27, (uint8_t[]){0x4A}, 1, 0},
+ {0x28, (uint8_t[]){0x4A}, 1, 0},
+ {0x29, (uint8_t[]){0x48}, 1, 0},
+ {0x2A, (uint8_t[]){0x48}, 1, 0},
+ {0x2B, (uint8_t[]){0x5F}, 1, 0},
+
+ {0x2C, (uint8_t[]){0x01}, 1, 0},
+ {0x2D, (uint8_t[]){0x01}, 1, 0},
+ {0x2E, (uint8_t[]){0x00}, 1, 0},
+ {0x2F, (uint8_t[]){0x00}, 1, 0},
+ {0x30, (uint8_t[]){0x1F}, 1, 0},
+ {0x31, (uint8_t[]){0x1F}, 1, 0},
+ {0x32, (uint8_t[]){0x1E}, 1, 0},
+ {0x33, (uint8_t[]){0x1E}, 1, 0},
+ {0x34, (uint8_t[]){0x1F}, 1, 0},
+ {0x35, (uint8_t[]){0x17}, 1, 0},
+ {0x36, (uint8_t[]){0x17}, 1, 0},
+ {0x37, (uint8_t[]){0x37}, 1, 0},
+ {0x38, (uint8_t[]){0x37}, 1, 0},
+ {0x39, (uint8_t[]){0x08}, 1, 0},
+ {0x3A, (uint8_t[]){0x08}, 1, 0},
+ {0x3B, (uint8_t[]){0x0A}, 1, 0},
+ {0x3C, (uint8_t[]){0x0A}, 1, 0},
+ {0x3D, (uint8_t[]){0x04}, 1, 0},
+ {0x3E, (uint8_t[]){0x04}, 1, 0},
+ {0x3F, (uint8_t[]){0x06}, 1, 0},
+ {0x40, (uint8_t[]){0x06}, 1, 0},
+ {0x41, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x42, (uint8_t[]){0x02}, 1, 0},
+ {0x43, (uint8_t[]){0x02}, 1, 0},
+ {0x44, (uint8_t[]){0x00}, 1, 0},
+ {0x45, (uint8_t[]){0x00}, 1, 0},
+ {0x46, (uint8_t[]){0x1F}, 1, 0},
+ {0x47, (uint8_t[]){0x1F}, 1, 0},
+ {0x48, (uint8_t[]){0x1E}, 1, 0},
+ {0x49, (uint8_t[]){0x1E}, 1, 0},
+ {0x4A, (uint8_t[]){0x1F}, 1, 0},
+ {0x4B, (uint8_t[]){0x17}, 1, 0},
+ {0x4C, (uint8_t[]){0x17}, 1, 0},
+ {0x4D, (uint8_t[]){0x37}, 1, 0},
+ {0x4E, (uint8_t[]){0x37}, 1, 0},
+ {0x4F, (uint8_t[]){0x09}, 1, 0},
+ {0x50, (uint8_t[]){0x09}, 1, 0},
+ {0x51, (uint8_t[]){0x0B}, 1, 0},
+ {0x52, (uint8_t[]){0x0B}, 1, 0},
+ {0x53, (uint8_t[]){0x05}, 1, 0},
+ {0x54, (uint8_t[]){0x05}, 1, 0},
+ {0x55, (uint8_t[]){0x07}, 1, 0},
+ {0x56, (uint8_t[]){0x07}, 1, 0},
+ {0x57, (uint8_t[]){0x1F}, 1, 0},
+
+ {0x58, (uint8_t[]){0x40}, 1, 0},
+ {0x5B, (uint8_t[]){0x30}, 1, 0},
+ {0x5C, (uint8_t[]){0x00}, 1, 0},
+ {0x5D, (uint8_t[]){0x34}, 1, 0},
+ {0x5E, (uint8_t[]){0x05}, 1, 0},
+ {0x5F, (uint8_t[]){0x02}, 1, 0},
+ {0x63, (uint8_t[]){0x00}, 1, 0},
+ {0x64, (uint8_t[]){0x6A}, 1, 0},
+ {0x67, (uint8_t[]){0x73}, 1, 0},
+ {0x68, (uint8_t[]){0x07}, 1, 0},
+ {0x69, (uint8_t[]){0x08}, 1, 0},
+ {0x6A, (uint8_t[]){0x6A}, 1, 0},
+ {0x6B, (uint8_t[]){0x08}, 1, 0},
+
+ {0x6C, (uint8_t[]){0x00}, 1, 0},
+ {0x6D, (uint8_t[]){0x00}, 1, 0},
+ {0x6E, (uint8_t[]){0x00}, 1, 0},
+ {0x6F, (uint8_t[]){0x88}, 1, 0},
+
+ {0x75, (uint8_t[]){0xFF}, 1, 0},
+ {0x77, (uint8_t[]){0xDD}, 1, 0},
+ {0x78, (uint8_t[]){0x2C}, 1, 0},
+ {0x79, (uint8_t[]){0x15}, 1, 0},
+ {0x7A, (uint8_t[]){0x17}, 1, 0},
+ {0x7D, (uint8_t[]){0x14}, 1, 0},
+ {0x7E, (uint8_t[]){0x82}, 1, 0},
+
+ {0xE0, (uint8_t[]){0x04}, 1, 0},
+ {0x00, (uint8_t[]){0x0E}, 1, 0},
+ {0x02, (uint8_t[]){0xB3}, 1, 0},
+ {0x09, (uint8_t[]){0x61}, 1, 0},
+ {0x0E, (uint8_t[]){0x48}, 1, 0},
+ {0x37, (uint8_t[]){0x58}, 1, 0}, // 鍏ㄥ織
+ {0x2B, (uint8_t[]){0x0F}, 1, 0}, // 鍏ㄥ織
+
+ {0xE0, (uint8_t[]){0x00}, 1, 0},
+
+ {0xE6, (uint8_t[]){0x02}, 1, 0},
+ {0xE7, (uint8_t[]){0x0C}, 1, 0},
+
+ {0x11, (uint8_t[]){0x00}, 1, 120},
+
+ {0x29, (uint8_t[]){0x00}, 1, 20},
+};
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/README.md
new file mode 100644
index 0000000..dff4acc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-AUDIO-Board
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-AUDIO-Board.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.h
new file mode 100644
index 0000000..958cb89
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.h
@@ -0,0 +1,95 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define BUILTIN_LED_GPIO GPIO_NUM_38
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define I2C_SCL_IO GPIO_NUM_10
+#define I2C_SDA_IO GPIO_NUM_11
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000
+
+#define DISPLAY_SDA_PIN I2C_SDA_IO
+#define DISPLAY_SCL_PIN I2C_SCL_IO
+
+#define DISPLAY_MISO_PIN GPIO_NUM_8
+#define DISPLAY_MOSI_PIN GPIO_NUM_9
+#define DISPLAY_SCLK_PIN GPIO_NUM_4
+#define DISPLAY_CS_PIN GPIO_NUM_3
+#define DISPLAY_DC_PIN GPIO_NUM_7
+#define DISPLAY_RESET_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_5
+
+#define DISPLAY_SPI_SCLK_HZ (20 * 1000 * 1000)
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_43
+#define CAMERA_PIN_SIOD GPIO_NUM_NC
+#define CAMERA_PIN_SIOC GPIO_NUM_NC
+
+#define CAMERA_PIN_D7 GPIO_NUM_48
+#define CAMERA_PIN_D6 GPIO_NUM_47
+#define CAMERA_PIN_D5 GPIO_NUM_46
+#define CAMERA_PIN_D4 GPIO_NUM_45
+#define CAMERA_PIN_D3 GPIO_NUM_39
+#define CAMERA_PIN_D2 GPIO_NUM_18
+#define CAMERA_PIN_D1 GPIO_NUM_17
+#define CAMERA_PIN_D0 GPIO_NUM_2
+#define CAMERA_PIN_VSYNC GPIO_NUM_21
+#define CAMERA_PIN_HREF GPIO_NUM_1
+#define CAMERA_PIN_PCLK GPIO_NUM_44
+
+#define XCLK_FREQ_HZ 20000000
+
+
+
+
+#ifdef CONFIG_AUDIO_BOARD_LCD_JD9853
+#define LCD_TYPE_JD9853_SERIAL
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 172
+
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_INVERT_COLOR true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#ifdef CONFIG_AUDIO_BOARD_LCD_ST7789
+#define LCD_TYPE_ST7789_SERIAL
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_INVERT_COLOR true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+
+
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.json
new file mode 100644
index 0000000..8e9b7a6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/config.json
@@ -0,0 +1,20 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-audio-board",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y",
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_640X480_6FPS=y",
+ "CONFIG_CAMERA_OV2640_DVP_IF_FORMAT_INDEX_DEFAULT=1",
+ "CONFIG_CAMERA_OV5640=y",
+ "CONFIG_CAMERA_OV5640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5640_DVP_YUV422_800X600_10FPS=y",
+ "CONFIG_CAMERA_OV5640_DVP_IF_FORMAT_INDEX_DEFAULT=0"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp32-s3-audio_board.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp32-s3-audio_board.cc
new file mode 100644
index 0000000..e983fb4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp32-s3-audio_board.cc
@@ -0,0 +1,239 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_st77916.h>
+#include <esp_timer.h>
+#include "esp_io_expander_tca95xx_16bit.h"
+#include "esp_video.h"
+#include "led/circular_strip.h"
+#include "esp_lcd_jd9853.h"
+
+#define TAG "waveshare_s3_audio_board"
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x0BULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+class CustomBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ EspVideo* camera_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = I2C_SDA_IO,
+ .scl_io_num = I2C_SCL_IO,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeTca9555(void)
+ {
+ esp_err_t ret = esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error"); // 鎵撳嵃寮曡剼鐘舵��
+
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_8|IO_EXPANDER_PIN_NUM_5|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍑�
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_8, 1); // 鍚敤鍠囧彮鍔熸斁
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, false); // 澶嶄綅鎽勫儚澶�
+ vTaskDelay(pdMS_TO_TICKS(5));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, true);
+ vTaskDelay(pdMS_TO_TICKS(5));
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK_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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR));
+
+ 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 InitializeJd9853Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖JD9853
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_new_panel_jd9853(panel_io, &panel_config, &panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true));
+ ESP_ERROR_CHECK(esp_lcd_panel_set_gap(panel, 0, 34));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, true, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, true));
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAMERA_PIN_D0,
+ [1] = CAMERA_PIN_D1,
+ [2] = CAMERA_PIN_D2,
+ [3] = CAMERA_PIN_D3,
+ [4] = CAMERA_PIN_D4,
+ [5] = CAMERA_PIN_D5,
+ [6] = CAMERA_PIN_D6,
+ [7] = CAMERA_PIN_D7,
+ },
+ .vsync_io = CAMERA_PIN_VSYNC,
+ .de_io = CAMERA_PIN_HREF,
+ .pclk_io = CAMERA_PIN_PCLK,
+ .xclk_io = CAMERA_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false, // 涓嶅垵濮嬪寲鏂扮殑 SCCB锛屼娇鐢ㄧ幇鏈夌殑 I2C 鎬荤嚎
+ .i2c_handle = i2c_bus_, // 浣跨敤鐜版湁鐨� I2C 鎬荤嚎鍙ユ焺
+ .freq = 100000, // 100kHz
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAMERA_PIN_RESET,
+ .pwdn_pin = CAMERA_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = 12000000,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+
+ }
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeTca9555();
+ InitializeSpi();
+ InitializeButtons();
+ #ifdef LCD_TYPE_JD9853_SERIAL
+ InitializeJd9853Display();
+ #else
+ InitializeSt7789Display();
+ #endif
+ InitializeCamera();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual Led* GetLed() override {
+ static CircularStrip led(BUILTIN_LED_GPIO, 6);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, BACKLIGHT_INVERT);
+ return &backlight;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.c b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.c
new file mode 100644
index 0000000..dea68f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.c
@@ -0,0 +1,460 @@
+#include <stdio.h>
+#include "esp_lcd_jd9853.h"
+/*
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+
+#include <stdlib.h>
+#include <sys/cdefs.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "esp_lcd_panel_interface.h"
+#include "esp_lcd_panel_io.h"
+#include "esp_lcd_panel_vendor.h"
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_panel_commands.h"
+#include "driver/gpio.h"
+#include "esp_log.h"
+#include "esp_check.h"
+
+static const char *TAG = "JD9853";
+
+static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel);
+static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel);
+static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel);
+static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data);
+static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data);
+static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y);
+static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes);
+static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap);
+static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool off);
+
+typedef struct
+{
+ esp_lcd_panel_t base;
+ esp_lcd_panel_io_handle_t io;
+ int reset_gpio_num;
+ bool reset_level;
+ int x_gap;
+ int y_gap;
+ uint8_t fb_bits_per_pixel;
+ uint8_t madctl_val; // save current value of LCD_CMD_MADCTL register
+ uint8_t colmod_val; // save current value of LCD_CMD_COLMOD register
+ const jd9853_lcd_init_cmd_t *init_cmds;
+ uint16_t init_cmds_size;
+} jd9853_panel_t;
+
+esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel)
+{
+ esp_err_t ret = ESP_OK;
+ jd9853_panel_t *jd9853 = NULL;
+ gpio_config_t io_conf = {0};
+
+ ESP_GOTO_ON_FALSE(io && panel_dev_config && ret_panel, ESP_ERR_INVALID_ARG, err, TAG, "invalid argument");
+ jd9853 = (jd9853_panel_t *)calloc(1, sizeof(jd9853_panel_t));
+ ESP_GOTO_ON_FALSE(jd9853, ESP_ERR_NO_MEM, err, TAG, "no mem for jd9853 panel");
+
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << panel_dev_config->reset_gpio_num;
+ ESP_GOTO_ON_ERROR(gpio_config(&io_conf), err, TAG, "configure GPIO for RST line failed");
+ }
+
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
+ switch (panel_dev_config->color_space)
+ {
+ case ESP_LCD_COLOR_SPACE_RGB:
+ jd9853->madctl_val = 0;
+ break;
+ case ESP_LCD_COLOR_SPACE_BGR:
+ jd9853->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported color space");
+ break;
+ }
+#else
+ switch (panel_dev_config->rgb_endian)
+ {
+ case LCD_RGB_ENDIAN_RGB:
+ jd9853->madctl_val = 0;
+ break;
+ case LCD_RGB_ENDIAN_BGR:
+ jd9853->madctl_val |= LCD_CMD_BGR_BIT;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported rgb endian");
+ break;
+ }
+#endif
+
+ switch (panel_dev_config->bits_per_pixel)
+ {
+ case 16: // RGB565
+ jd9853->colmod_val = 0x55;
+ jd9853->fb_bits_per_pixel = 16;
+ break;
+ case 18: // RGB666
+ jd9853->colmod_val = 0x66;
+ // each color component (R/G/B) should occupy the 6 high bits of a byte, which means 3 full bytes are required for a pixel
+ jd9853->fb_bits_per_pixel = 24;
+ break;
+ default:
+ ESP_GOTO_ON_FALSE(false, ESP_ERR_NOT_SUPPORTED, err, TAG, "unsupported pixel width");
+ break;
+ }
+
+ jd9853->io = io;
+ jd9853->reset_gpio_num = panel_dev_config->reset_gpio_num;
+ jd9853->reset_level = panel_dev_config->flags.reset_active_high;
+ if (panel_dev_config->vendor_config)
+ {
+ jd9853->init_cmds = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds;
+ jd9853->init_cmds_size = ((jd9853_vendor_config_t *)panel_dev_config->vendor_config)->init_cmds_size;
+ }
+ jd9853->base.del = panel_jd9853_del;
+ jd9853->base.reset = panel_jd9853_reset;
+ jd9853->base.init = panel_jd9853_init;
+ jd9853->base.draw_bitmap = panel_jd9853_draw_bitmap;
+ jd9853->base.invert_color = panel_jd9853_invert_color;
+ jd9853->base.set_gap = panel_jd9853_set_gap;
+ jd9853->base.mirror = panel_jd9853_mirror;
+ jd9853->base.swap_xy = panel_jd9853_swap_xy;
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
+ jd9853->base.disp_off = panel_jd9853_disp_on_off;
+#else
+ jd9853->base.disp_on_off = panel_jd9853_disp_on_off;
+#endif
+ *ret_panel = &(jd9853->base);
+ ESP_LOGD(TAG, "new jd9853 panel @%p", jd9853);
+
+ // ESP_LOGI(TAG, "LCD panel create success, version: %d.%d.%d", ESP_LCD_jd9853_VER_MAJOR, ESP_LCD_jd9853_VER_MINOR,
+ // ESP_LCD_jd9853_VER_PATCH);
+
+ return ESP_OK;
+
+err:
+ if (jd9853)
+ {
+ if (panel_dev_config->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(panel_dev_config->reset_gpio_num);
+ }
+ free(jd9853);
+ }
+ return ret;
+}
+
+static esp_err_t panel_jd9853_del(esp_lcd_panel_t *panel)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+
+ if (jd9853->reset_gpio_num >= 0)
+ {
+ gpio_reset_pin(jd9853->reset_gpio_num);
+ }
+ ESP_LOGD(TAG, "del jd9853 panel @%p", jd9853);
+ free(jd9853);
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_reset(esp_lcd_panel_t *panel)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+
+ // perform hardware reset
+ if (jd9853->reset_gpio_num >= 0)
+ {
+ gpio_set_level(jd9853->reset_gpio_num, jd9853->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ gpio_set_level(jd9853->reset_gpio_num, !jd9853->reset_level);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ else
+ { // perform software reset
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SWRESET, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(20)); // spec, wait at least 5ms before sending new command
+ }
+
+ return ESP_OK;
+}
+
+typedef struct
+{
+ uint8_t cmd;
+ uint8_t data[16];
+ uint8_t data_bytes; // Length of data in above data array; 0xFF = end of cmds.
+} lcd_init_cmd_t;
+
+// static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = {
+// // {cmd, { data }, data_size, delay_ms}
+// /* Power contorl B, power control = 0, DC_ENA = 1 */
+// {0xCF, (uint8_t []){0x00, 0xAA, 0XE0}, 3, 0},
+// /* Power on sequence control,
+// * cp1 keeps 1 frame, 1st frame enable
+// * vcl = 0, ddvdh=3, vgh=1, vgl=2
+// * DDVDH_ENH=1
+// */
+// {0xED, (uint8_t []){0x67, 0x03, 0X12, 0X81}, 4, 0},
+// /* Driver timing control A,
+// * non-overlap=default +1
+// * EQ=default - 1, CR=default
+// * pre-charge=default - 1
+// */
+// {0xE8, (uint8_t []){0x8A, 0x01, 0x78}, 3, 0},
+// /* Power control A, Vcore=1.6V, DDVDH=5.6V */
+// {0xCB, (uint8_t []){0x39, 0x2C, 0x00, 0x34, 0x02}, 5, 0},
+// /* Pump ratio control, DDVDH=2xVCl */
+// {0xF7, (uint8_t []){0x20}, 1, 0},
+
+// {0xF7, (uint8_t []){0x20}, 1, 0},
+// /* Driver timing control, all=0 unit */
+// {0xEA, (uint8_t []){0x00, 0x00}, 2, 0},
+// /* Power control 1, GVDD=4.75V */
+// {0xC0, (uint8_t []){0x23}, 1, 0},
+// /* Power control 2, DDVDH=VCl*2, VGH=VCl*7, VGL=-VCl*3 */
+// {0xC1, (uint8_t []){0x11}, 1, 0},
+// /* VCOM control 1, VCOMH=4.025V, VCOML=-0.950V */
+// {0xC5, (uint8_t []){0x43, 0x4C}, 2, 0},
+// /* VCOM control 2, VCOMH=VMH-2, VCOML=VML-2 */
+// {0xC7, (uint8_t []){0xA0}, 1, 0},
+// /* Frame rate control, f=fosc, 70Hz fps */
+// {0xB1, (uint8_t []){0x00, 0x1B}, 2, 0},
+// /* Enable 3G, disabled */
+// {0xF2, (uint8_t []){0x00}, 1, 0},
+// /* Gamma set, curve 1 */
+// {0x26, (uint8_t []){0x01}, 1, 0},
+// /* Positive gamma correction */
+// {0xE0, (uint8_t []){0x1F, 0x36, 0x36, 0x3A, 0x0C, 0x05, 0x4F, 0X87, 0x3C, 0x08, 0x11, 0x35, 0x19, 0x13, 0x00}, 15, 0},
+// /* Negative gamma correction */
+// {0xE1, (uint8_t []){0x00, 0x09, 0x09, 0x05, 0x13, 0x0A, 0x30, 0x78, 0x43, 0x07, 0x0E, 0x0A, 0x26, 0x2C, 0x1F}, 15, 0},
+// /* Entry mode set, Low vol detect disabled, normal display */
+// {0xB7, (uint8_t []){0x07}, 1, 0},
+// /* Display function control */
+// {0xB6, (uint8_t []){0x08, 0x82, 0x27}, 3, 0},
+// };
+
+static const jd9853_lcd_init_cmd_t vendor_specific_init_default[] = {
+ {0x11, (uint8_t []){ 0x00 }, 0, 120},
+ {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0},
+ {0xDF, (uint8_t[]){0x98, 0x53}, 2, 0},
+ {0xB2, (uint8_t[]){0x23}, 1, 0},
+ {0xB7, (uint8_t[]){0x00, 0x47, 0x00, 0x6F}, 4, 0},
+ {0xBB, (uint8_t[]){0x1C, 0x1A, 0x55, 0x73, 0x63, 0xF0}, 6, 0},
+ {0xC0, (uint8_t[]){0x44, 0xA4}, 2, 0},
+ {0xC1, (uint8_t[]){0x16}, 1, 0},
+ {0xC3, (uint8_t[]){0x7D, 0x07, 0x14, 0x06, 0xCF, 0x71, 0x72, 0x77}, 8, 0},
+ {0xC4, (uint8_t[]){0x00, 0x00, 0xA0, 0x79, 0x0B, 0x0A, 0x16, 0x79, 0x0B, 0x0A, 0x16, 0x82}, 12, 0}, // 00=60Hz 06=57Hz 08=51Hz, LN=320 Line
+ {0xC8, (uint8_t[]){0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00, 0x3F, 0x32, 0x29, 0x29, 0x27, 0x2B, 0x27, 0x28, 0x28, 0x26, 0x25, 0x17, 0x12, 0x0D, 0x04, 0x00}, 32, 0}, // SET_R_GAMMA
+ {0xD0, (uint8_t[]){0x04, 0x06, 0x6B, 0x0F, 0x00}, 5, 0},
+ {0xD7, (uint8_t[]){0x00, 0x30}, 2, 0},
+ {0xE6, (uint8_t[]){0x14}, 1, 0},
+ {0xDE, (uint8_t[]){0x01}, 1, 0},
+ {0xB7, (uint8_t[]){0x03, 0x13, 0xEF, 0x35, 0x35}, 5, 0},
+ {0xC1, (uint8_t[]){0x14, 0x15, 0xC0}, 3, 0},
+ {0xC2, (uint8_t[]){0x06, 0x3A}, 2, 0},
+ {0xC4, (uint8_t[]){0x72, 0x12}, 2, 0},
+ {0xBE, (uint8_t[]){0x00}, 1, 0},
+ {0xDE, (uint8_t[]){0x02}, 1, 0},
+ {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0},
+ {0xE5, (uint8_t[]){0x01, 0x02, 0x00}, 3, 0},
+ {0xDE, (uint8_t[]){0x00}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x3A, (uint8_t[]){0x05}, 1, 0}, // 06=RGB666锛�05=RGB565
+ {0x2A, (uint8_t[]){0x00, 0x22, 0x00, 0xCD}, 4, 0}, // Start_X=34, End_X=205
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0x3F}, 4, 0}, // Start_Y=0, End_Y=319
+ {0xDE, (uint8_t[]){0x02}, 1, 0},
+ {0xE5, (uint8_t[]){0x00, 0x02, 0x00}, 3, 0},
+ {0xDE, (uint8_t[]){0x00}, 1, 0},
+ {0x29, (uint8_t []){ 0x00 }, 0, 0},
+};
+
+static esp_err_t panel_jd9853_init(esp_lcd_panel_t *panel)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+
+ // LCD goes into sleep mode and display will be turned off after power on reset, exit sleep mode first
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_SLPOUT, NULL, 0), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){
+ jd9853->madctl_val,
+ },
+ 1),
+ TAG, "send command failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_COLMOD, (uint8_t[]){
+ jd9853->colmod_val,
+ },
+ 1),
+ TAG, "send command failed");
+
+ const jd9853_lcd_init_cmd_t *init_cmds = NULL;
+ uint16_t init_cmds_size = 0;
+ if (jd9853->init_cmds)
+ {
+ init_cmds = jd9853->init_cmds;
+ init_cmds_size = jd9853->init_cmds_size;
+ }
+ else
+ {
+ init_cmds = vendor_specific_init_default;
+ init_cmds_size = sizeof(vendor_specific_init_default) / sizeof(jd9853_lcd_init_cmd_t);
+ }
+
+ bool is_cmd_overwritten = false;
+ for (int i = 0; i < init_cmds_size; i++)
+ {
+ // Check if the command has been used or conflicts with the internal
+ switch (init_cmds[i].cmd)
+ {
+ case LCD_CMD_MADCTL:
+ is_cmd_overwritten = true;
+ jd9853->madctl_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ case LCD_CMD_COLMOD:
+ is_cmd_overwritten = true;
+ jd9853->colmod_val = ((uint8_t *)init_cmds[i].data)[0];
+ break;
+ default:
+ is_cmd_overwritten = false;
+ break;
+ }
+
+ if (is_cmd_overwritten)
+ {
+ ESP_LOGW(TAG, "The %02Xh command has been used and will be overwritten by external initialization sequence", init_cmds[i].cmd);
+ }
+
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, init_cmds[i].cmd, init_cmds[i].data, init_cmds[i].data_bytes), TAG, "send command failed");
+ vTaskDelay(pdMS_TO_TICKS(init_cmds[i].delay_ms));
+ }
+ ESP_LOGD(TAG, "send init commands success");
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_draw_bitmap(esp_lcd_panel_t *panel, int x_start, int y_start, int x_end, int y_end, const void *color_data)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ assert((x_start < x_end) && (y_start < y_end) && "start position must be smaller than end position");
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+
+ x_start += jd9853->x_gap;
+ x_end += jd9853->x_gap;
+ y_start += jd9853->y_gap;
+ y_end += jd9853->y_gap;
+
+ // define an area of frame memory where MCU can access
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_CASET, (uint8_t[]){
+ (x_start >> 8) & 0xFF,
+ x_start & 0xFF,
+ ((x_end - 1) >> 8) & 0xFF,
+ (x_end - 1) & 0xFF,
+ },
+ 4),
+ TAG, "send command failed");
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_RASET, (uint8_t[]){
+ (y_start >> 8) & 0xFF,
+ y_start & 0xFF,
+ ((y_end - 1) >> 8) & 0xFF,
+ (y_end - 1) & 0xFF,
+ },
+ 4),
+ TAG, "send command failed");
+ // transfer frame buffer
+ size_t len = (x_end - x_start) * (y_end - y_start) * jd9853->fb_bits_per_pixel / 8;
+ esp_lcd_panel_io_tx_color(io, LCD_CMD_RAMWR, color_data, len);
+
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_invert_color(esp_lcd_panel_t *panel, bool invert_color_data)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+ int command = 0;
+ if (invert_color_data)
+ {
+ command = LCD_CMD_INVON;
+ }
+ else
+ {
+ command = LCD_CMD_INVOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_mirror(esp_lcd_panel_t *panel, bool mirror_x, bool mirror_y)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+ if (mirror_x)
+ {
+ jd9853->madctl_val |= LCD_CMD_MX_BIT;
+ }
+ else
+ {
+ jd9853->madctl_val &= ~LCD_CMD_MX_BIT;
+ }
+ if (mirror_y)
+ {
+ jd9853->madctl_val |= LCD_CMD_MY_BIT;
+ }
+ else
+ {
+ jd9853->madctl_val &= ~LCD_CMD_MY_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_swap_xy(esp_lcd_panel_t *panel, bool swap_axes)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+ if (swap_axes)
+ {
+ jd9853->madctl_val |= LCD_CMD_MV_BIT;
+ }
+ else
+ {
+ jd9853->madctl_val &= ~LCD_CMD_MV_BIT;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, LCD_CMD_MADCTL, (uint8_t[]){jd9853->madctl_val}, 1), TAG, "send command failed");
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_set_gap(esp_lcd_panel_t *panel, int x_gap, int y_gap)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ jd9853->x_gap = x_gap;
+ jd9853->y_gap = y_gap;
+ return ESP_OK;
+}
+
+static esp_err_t panel_jd9853_disp_on_off(esp_lcd_panel_t *panel, bool on_off)
+{
+ jd9853_panel_t *jd9853 = __containerof(panel, jd9853_panel_t, base);
+ esp_lcd_panel_io_handle_t io = jd9853->io;
+ int command = 0;
+
+#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(5, 0, 0)
+ on_off = !on_off;
+#endif
+
+ if (on_off)
+ {
+ command = LCD_CMD_DISPON;
+ }
+ else
+ {
+ command = LCD_CMD_DISPOFF;
+ }
+ ESP_RETURN_ON_ERROR(esp_lcd_panel_io_tx_param(io, command, NULL, 0), TAG, "send command failed");
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.h
new file mode 100644
index 0000000..487e749
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-audio-board/esp_lcd_jd9853.h
@@ -0,0 +1,102 @@
+/*
+ * SPDX-FileCopyrightText: 2022-2023 Espressif Systems (Shanghai) CO LTD
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ */
+/**
+ * @file
+ * @brief ESP LCD: jd9853
+ */
+
+#pragma once
+
+#include "hal/spi_ll.h"
+#include "esp_lcd_panel_vendor.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief LCD panel initialization commands.
+ *
+ */
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} jd9853_lcd_init_cmd_t;
+
+/**
+ * @brief LCD panel vendor configuration.
+ *
+ * @note This structure needs to be passed to the `vendor_config` field in `esp_lcd_panel_dev_config_t`.
+ *
+ */
+typedef struct {
+ const jd9853_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+} jd9853_vendor_config_t;
+
+/**
+ * @brief Create LCD panel for model jd9853
+ *
+ * @note Vendor specific initialization can be different between manufacturers, should consult the LCD supplier for initialization sequence code.
+ *
+ * @param[in] io LCD panel IO handle
+ * @param[in] panel_dev_config general panel device configuration
+ * @param[out] ret_panel Returned LCD panel handle
+ * @return
+ * - ESP_ERR_INVALID_ARG if parameter is invalid
+ * - ESP_ERR_NO_MEM if out of memory
+ * - ESP_OK on success
+ */
+esp_err_t esp_lcd_new_panel_jd9853(const esp_lcd_panel_io_handle_t io, const esp_lcd_panel_dev_config_t *panel_dev_config, esp_lcd_panel_handle_t *ret_panel);
+
+/**
+ * @brief LCD panel bus configuration structure
+ *
+ * @param[in] sclk SPI clock pin number
+ * @param[in] mosi SPI MOSI pin number
+ * @param[in] max_trans_sz Maximum transfer size in bytes
+ *
+ */
+#define JD9853_PANEL_BUS_SPI_CONFIG(sclk, mosi, max_trans_sz) \
+ { \
+ .sclk_io_num = sclk, \
+ .mosi_io_num = mosi, \
+ .miso_io_num = -1, \
+ .quadhd_io_num = -1, \
+ .quadwp_io_num = -1, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+/**
+ * @brief LCD panel IO configuration structure
+ *
+ * @param[in] cs SPI chip select pin number
+ * @param[in] dc SPI data/command pin number
+ * @param[in] cb Callback function when SPI transfer is done
+ * @param[in] cb_ctx Callback function context
+ *
+ */
+#define JD9853_PANEL_IO_SPI_CONFIG(cs, dc, callback, callback_ctx) \
+ { \
+ .cs_gpio_num = cs, \
+ .dc_gpio_num = dc, \
+ .spi_mode = 0, \
+ .pclk_hz = 40 * 1000 * 1000, \
+ .trans_queue_depth = 10, \
+ .on_color_trans_done = callback, \
+ .user_ctx = callback_ctx, \
+ .lcd_cmd_bits = 8, \
+ .lcd_param_bits = 8, \
+ }
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/README.md
new file mode 100644
index 0000000..caaa761
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/README.md
@@ -0,0 +1,6 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-CAM-XXXX
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-CAM-OV5640.htm
+https://www.waveshare.net/shop/ESP32-S3-CAM-OV3660.htm
+https://www.waveshare.net/shop/ESP32-S3-CAM-GC2145.htm
+https://www.waveshare.net/shop/ESP32-S3-CAM-GC0308.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.h
new file mode 100644
index 0000000..5576bc8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.h
@@ -0,0 +1,115 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_11
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_13
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_14
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define I2C_SCL_IO GPIO_NUM_7
+#define I2C_SDA_IO GPIO_NUM_8
+
+#define BSP_IO_EXPANDER_I2C_ADDRESS CUSTOM_IO_EXPANDER_I2C_CH32V003_ADDRESS
+
+#define DISPLAY_SDA_PIN I2C_SDA_IO
+#define DISPLAY_SCL_PIN I2C_SCL_IO
+
+#define DISPLAY_MISO_PIN GPIO_NUM_2
+#define DISPLAY_MOSI_PIN GPIO_NUM_1
+#define DISPLAY_SCLK_PIN GPIO_NUM_5
+#define DISPLAY_CS_PIN GPIO_NUM_6
+#define DISPLAY_DC_PIN GPIO_NUM_3
+#define DISPLAY_RESET_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_SPI_SCLK_HZ (40 * 1000 * 1000)
+
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN GPIO_NUM_NC
+#define CAMERA_PIN_RESET GPIO_NUM_NC
+#define CAMERA_PIN_XCLK GPIO_NUM_38
+#define CAMERA_PIN_SIOD GPIO_NUM_NC
+#define CAMERA_PIN_SIOC GPIO_NUM_NC
+
+#define CAMERA_PIN_D7 GPIO_NUM_21
+#define CAMERA_PIN_D6 GPIO_NUM_39
+#define CAMERA_PIN_D5 GPIO_NUM_40
+#define CAMERA_PIN_D4 GPIO_NUM_42
+#define CAMERA_PIN_D3 GPIO_NUM_46
+#define CAMERA_PIN_D2 GPIO_NUM_48
+#define CAMERA_PIN_D1 GPIO_NUM_47
+#define CAMERA_PIN_D0 GPIO_NUM_45
+#define CAMERA_PIN_VSYNC GPIO_NUM_17
+#define CAMERA_PIN_HREF GPIO_NUM_18
+#define CAMERA_PIN_PCLK GPIO_NUM_41
+
+#define XCLK_FREQ_HZ 20000000
+
+
+
+#ifdef CONFIG_BSP_LCD_SIZE_2INCH
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_INVERT_COLOR true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#ifdef CONFIG_BSP_LCD_SIZE_2_8INCH
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 320
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_INVERT_COLOR true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#ifdef CONFIG_BSP_LCD_SIZE_3_5INCH
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 480
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_INVERT_COLOR true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#ifdef CONFIG_BSP_LCD_SIZE_1_83INCH
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 284
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_INVERT_COLOR true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#endif
+
+#endif // _BOARD_CONFIG_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.json
new file mode 100644
index 0000000..14a539a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/config.json
@@ -0,0 +1,38 @@
+{
+"manufacturer": "waveshare",
+"target": "esp32s3",
+"builds": [
+ {
+ "name": "esp32-s3-cam-2",
+ "sdkconfig_append": [
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX=y",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_BSP_LCD_SIZE_2INCH=y"
+ ]
+ },
+ {
+ "name": "esp32-s3-cam-2.8",
+ "sdkconfig_append": [
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX=y",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_BSP_LCD_SIZE_2_8INCH=y"
+ ]
+ },
+ {
+ "name": "esp32-s3-cam-3.5",
+ "sdkconfig_append": [
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX=y",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_BSP_LCD_SIZE_3_5INCH=y"
+ ]
+ },
+ {
+ "name": "esp32-s3-cam-1.83",
+ "sdkconfig_append": [
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_CAM_XXXX=y",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_BSP_LCD_SIZE_1_83INCH=y"
+ ]
+ }
+]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/esp32-s3-cam-xxxx.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/esp32-s3-cam-xxxx.cc
new file mode 100644
index 0000000..615ed93
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-cam/esp32-s3-cam-xxxx.cc
@@ -0,0 +1,282 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <wifi_station.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_timer.h>
+#include "esp32_camera.h"
+
+#include "esp_lcd_panel_vendor.h"
+#include "custom_io_expander_ch32v003.h"
+#include "esp_lcd_st7796.h"
+#include "esp_ota_ops.h"
+
+#define TAG "waveshare_s3_cam_xxxx"
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x0BULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+void switch_to_main(void)
+{
+ const esp_partition_t *factory_part =
+ esp_partition_find_first(ESP_PARTITION_TYPE_APP,
+ ESP_PARTITION_SUBTYPE_APP_FACTORY,
+ NULL);
+ if (!factory_part) {
+ ESP_LOGE("APP_SWITCH", "鏈壘鍒� factory 鍒嗗尯");
+ return;
+ }
+
+ esp_err_t err = esp_ota_set_boot_partition(factory_part);
+ if (err == ESP_OK) {
+ ESP_LOGI("APP_SWITCH", "宸茶缃� factory 涓哄惎鍔ㄥ垎鍖猴紝閲嶅惎鍥炲埌涓荤▼搴�");
+ esp_restart();
+ } else {
+ ESP_LOGE("APP_SWITCH", "璁剧疆鍚姩鍒嗗尯澶辫触: %s", esp_err_to_name(err));
+ }
+}
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_io_expander_handle_t io_handle)
+ : Backlight(), io_handle_(io_handle) {}
+
+protected:
+ esp_io_expander_handle_t io_handle_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ if (brightness > 100) brightness = 100;
+ int flipped_brightness = 100 - brightness;
+
+ custom_io_expander_set_pwm(io_handle_, flipped_brightness * 255 / 100);
+ }
+};
+
+
+class CustomBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ Esp32Camera* camera_;
+ esp_io_expander_handle_t io_expander = NULL;
+ CustomBacklight *backlight_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .sda_io_num = I2C_SDA_IO,
+ .scl_io_num = I2C_SCL_IO,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void Initialize_Expander(void)
+ {
+
+ custom_io_expander_new_i2c_ch32v003(i2c_bus_, BSP_IO_EXPANDER_I2C_ADDRESS, &io_expander);
+ esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT);
+
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 , 1);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 , 0);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 , 1);
+ vTaskDelay(pdMS_TO_TICKS(10));
+
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_4 | IO_EXPANDER_PIN_NUM_6 , 1);
+
+ //custom_io_expander_set_pwm(io_expander,100);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCLK_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 InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel,true));
+ 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);
+ backlight_ = new CustomBacklight(io_expander);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeSt7796Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = DISPLAY_SPI_SCLK_HZ;
+ 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));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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_BGR;
+ panel_config.bits_per_pixel = 16;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7796(panel_io, &panel_config, &panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel,true));
+ 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);
+ backlight_ = new CustomBacklight(io_expander);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnLongPress([this]() {
+ auto& app = Application::GetInstance();
+ switch_to_main();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+
+ void InitializeCamera() {
+ camera_config_t camera_config = {
+ .pin_pwdn = CAMERA_PIN_PWDN,
+ .pin_reset = CAMERA_PIN_RESET,
+ .pin_xclk = CAMERA_PIN_XCLK,
+ .pin_sccb_sda = -1, // Use initialized I2C
+ .pin_sccb_scl = -1,
+ .pin_d7 = CAMERA_PIN_D7,
+ .pin_d6 = CAMERA_PIN_D6,
+ .pin_d5 = CAMERA_PIN_D5,
+ .pin_d4 = CAMERA_PIN_D4,
+ .pin_d3 = CAMERA_PIN_D3,
+ .pin_d2 = CAMERA_PIN_D2,
+ .pin_d1 = CAMERA_PIN_D1,
+ .pin_d0 = CAMERA_PIN_D0,
+ .pin_vsync = CAMERA_PIN_VSYNC,
+ .pin_href = CAMERA_PIN_HREF,
+ .pin_pclk = CAMERA_PIN_PCLK,
+
+ .xclk_freq_hz = XCLK_FREQ_HZ,
+ .ledc_timer = LEDC_TIMER_0,
+ .ledc_channel = LEDC_CHANNEL_0,
+
+ .pixel_format = PIXFORMAT_RGB565,
+ .frame_size = FRAMESIZE_QVGA,
+ .jpeg_quality = 12,
+ .fb_count = 2,
+ .fb_location = CAMERA_FB_IN_PSRAM,
+ .grab_mode = CAMERA_GRAB_WHEN_EMPTY,
+ .sccb_i2c_port = (i2c_port_t)1,
+ };
+
+ camera_ = new Esp32Camera(camera_config);
+ if (camera_ != nullptr) {
+ camera_->SetVFlip(true);
+ }
+ }
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ Initialize_Expander();
+ InitializeSpi();
+ InitializeButtons();
+ #ifdef CONFIG_BSP_LCD_SIZE_2INCH
+ InitializeSt7789Display();
+ #elif CONFIG_BSP_LCD_SIZE_2_8INCH
+ InitializeSt7789Display();
+ #elif CONFIG_BSP_LCD_SIZE_1_83INCH
+ InitializeSt7789Display();
+ #elif CONFIG_BSP_LCD_SIZE_3_5INCH
+ InitializeSt7796Display();
+ #endif
+
+ InitializeCamera();
+
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/README.md
new file mode 100644
index 0000000..4aaebb8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/README.md
@@ -0,0 +1,55 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-ePaper-1.54](https://www.waveshare.net/shop/ESP32-S3-ePaper-1.54.htm)
+
+```bash
+esptool.py flash_id
+V1: 4MB Flash, 2MB PSRAM
+V2: 8MB Flash, 8MB PSRAM
+```
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-ePaper-1.54_v2
+```
+
+**缂栬瘧**
+
+```bash
+python ./scripts/release.py --name esp32-s3-epaper-1.54-v1 waveshare/esp32-s3-epaper-1.54
+python ./scripts/release.py --name esp32-s3-epaper-1.54-v2 waveshare/esp32-s3-epaper-1.54
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.cc
new file mode 100644
index 0000000..812d19f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.cc
@@ -0,0 +1,59 @@
+#include <stdio.h>
+#include <driver/gpio.h>
+#include <freertos/FreeRTOS.h>
+#include "board_power_bsp.h"
+
+void BoardPowerBsp::PowerLedTask(void *arg) {
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << GPIO_NUM_3);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+ for (;;) {
+ gpio_set_level(GPIO_NUM_3, 0);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ gpio_set_level(GPIO_NUM_3, 1);
+ // 涓嶉渶瑕侀绻侀棯鐑侊紝闂撮殧鎸囩ず鏁堟灉鏇村ソ
+ vTaskDelay(pdMS_TO_TICKS(4800));
+ }
+}
+
+BoardPowerBsp::BoardPowerBsp(int epdPowerPin, int audioPowerPin, int vbatPowerPin) : epdPowerPin_(epdPowerPin), audioPowerPin_(audioPowerPin), vbatPowerPin_(vbatPowerPin) {
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << epdPowerPin_) | (0x1ULL << audioPowerPin_) | (0x1ULL << vbatPowerPin_);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+ xTaskCreatePinnedToCore(PowerLedTask, "PowerLedTask", 3 * 1024, NULL, 2, NULL, 0);
+}
+
+BoardPowerBsp::~BoardPowerBsp() {
+}
+
+void BoardPowerBsp::PowerEpdOn() {
+ gpio_set_level((gpio_num_t) epdPowerPin_, 0);
+}
+
+void BoardPowerBsp::PowerEpdOff() {
+ gpio_set_level((gpio_num_t) epdPowerPin_, 1);
+}
+
+void BoardPowerBsp::PowerAudioOn() {
+ gpio_set_level((gpio_num_t) audioPowerPin_, 0);
+}
+
+void BoardPowerBsp::PowerAudioOff() {
+ gpio_set_level((gpio_num_t) audioPowerPin_, 1);
+}
+
+void BoardPowerBsp::VbatPowerOn() {
+ gpio_set_level((gpio_num_t) vbatPowerPin_, 1);
+}
+
+void BoardPowerBsp::VbatPowerOff() {
+ gpio_set_level((gpio_num_t) vbatPowerPin_, 0);
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.h
new file mode 100644
index 0000000..1301168
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/board_power_bsp.h
@@ -0,0 +1,23 @@
+#ifndef __BOARD_POWER_BSP_H__
+#define __BOARD_POWER_BSP_H__
+
+class BoardPowerBsp {
+private:
+ const int epdPowerPin_;
+ const int audioPowerPin_;
+ const int vbatPowerPin_;
+
+ static void PowerLedTask(void *arg);
+
+public:
+ BoardPowerBsp(int epdPowerPin, int audioPowerPin, int vbatPowerPin);
+ ~BoardPowerBsp();
+ void PowerEpdOn();
+ void PowerEpdOff();
+ void PowerAudioOn();
+ void PowerAudioOff();
+ void VbatPowerOn();
+ void VbatPowerOff();
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.h
new file mode 100644
index 0000000..898f0ed
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.h
@@ -0,0 +1,50 @@
+#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_14
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_38
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VBAT_PWR_GPIO GPIO_NUM_18
+
+/*EPD port Init*/
+#define EPD_SPI_NUM SPI3_HOST
+
+#define EPD_DC_PIN GPIO_NUM_10
+#define EPD_CS_PIN GPIO_NUM_11
+#define EPD_SCK_PIN GPIO_NUM_12
+#define EPD_MOSI_PIN GPIO_NUM_13
+#define EPD_RST_PIN GPIO_NUM_9
+#define EPD_BUSY_PIN GPIO_NUM_8
+
+#define EXAMPLE_LCD_WIDTH 200
+#define EXAMPLE_LCD_HEIGHT 200
+
+/*DEV POWER init*/
+#define EPD_PWR_PIN GPIO_NUM_6
+#define Audio_PWR_PIN GPIO_NUM_42
+#define VBAT_PWR_PIN GPIO_NUM_17
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.json
new file mode 100644
index 0000000..a298dad
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/config.json
@@ -0,0 +1,23 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-epaper-1.54-v2",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
+ "CONFIG_USE_MULTILINE_CHAT_MESSAGE=y"
+ ]
+ },
+ {
+ "name": "esp32-s3-epaper-1.54-v1",
+ "sdkconfig_append": [
+ "CONFIG_SPIRAM_MODE_QUAD=y",
+ "CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/4m.csv\"",
+ "CONFIG_USE_MULTILINE_CHAT_MESSAGE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.cc
new file mode 100644
index 0000000..45cc610
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.cc
@@ -0,0 +1,403 @@
+#include <stdio.h>
+#include <esp_lcd_panel_io.h>
+#include <freertos/FreeRTOS.h>
+#include <vector>
+#include <esp_log.h>
+#include "custom_lcd_display.h"
+#include "board.h"
+#include "config.h"
+#include "esp_lvgl_port.h"
+#include "settings.h"
+
+#define TAG "CustomLcdDisplay"
+
+#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
+#define BUFF_SIZE (EXAMPLE_LCD_WIDTH * EXAMPLE_LCD_HEIGHT * BYTES_PER_PIXEL)
+
+const uint8_t WF_Full_1IN54[159] =
+{
+ 0x80,0x48,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x40,0x48,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x80,0x48,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x40,0x48,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0xA,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x8,0x1,0x0,0x8,0x1,0x0,0x2,
+ 0xA,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
+ 0x22,0x17,0x41,0x0,0x32,0x20
+};
+
+const uint8_t WF_PARTIAL_1IN54_0[159] =
+{
+ 0x0,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x80,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x40,0x40,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x80,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0xF,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x1,0x1,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x0,0x0,0x0,0x0,0x0,0x0,0x0,
+ 0x22,0x22,0x22,0x22,0x22,0x22,0x0,0x0,0x0,
+ 0x02,0x17,0x41,0xB0,0x32,0x28,
+};
+
+void CustomLcdDisplay::lvgl_flush_cb(lv_display_t *disp, const lv_area_t *area, uint8_t *color_p) {
+ assert(disp != NULL);
+ CustomLcdDisplay *driver = (CustomLcdDisplay *) lv_display_get_user_data(disp);
+ uint16_t *buffer = (uint16_t *) color_p;
+ driver->EPD_Clear();
+ for (int y = area->y1; y <= area->y2; y++) {
+ for (int x = area->x1; x <= area->x2; x++) {
+ uint8_t color = (*buffer < 0x7fff) ? DRIVER_COLOR_BLACK : DRIVER_COLOR_WHITE;
+ driver->EPD_DrawColorPixel(x, y, color);
+ buffer++;
+ }
+ }
+ driver->EPD_DisplayPart();
+ lv_disp_flush_ready(disp);
+}
+
+CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy, custom_lcd_spi_t _lcd_spi_data) :
+ LcdDisplay(panel_io, panel, width, height),
+ lcd_spi_data(_lcd_spi_data),
+ Width(width), Height(height) {
+
+ ESP_LOGI(TAG, "Initialize SPI");
+ spi_port_init();
+ spi_gpio_init();
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 2;
+ port_cfg.timer_period_ms = 50;
+ lvgl_port_init(&port_cfg);
+ lvgl_port_lock(0);
+
+ buffer = (uint8_t *) heap_caps_malloc(lcd_spi_data.buffer_len, MALLOC_CAP_SPIRAM);
+ assert(buffer);
+ display_ = lv_display_create(width, height); /* 浠ユ按骞冲拰鍨傜洿鍒嗚鲸鐜囷紙鍍忕礌锛夎繘琛屽熀鏈垵濮嬪寲 */
+ lv_display_set_flush_cb(display_, lvgl_flush_cb);
+ lv_display_set_user_data(display_, this);
+
+ uint8_t *buffer_1 = NULL;
+ buffer_1 = (uint8_t *) heap_caps_malloc(BUFF_SIZE, MALLOC_CAP_SPIRAM);
+ assert(buffer_1);
+ lv_display_set_buffers(display_, buffer_1, NULL, BUFF_SIZE, LV_DISPLAY_RENDER_MODE_FULL);
+
+ ESP_LOGI(TAG, "EPD init");
+ EPD_Init();
+ EPD_Clear();
+ EPD_Display();
+ EPD_DisplayPartBaseImage();
+ EPD_Init_Partial(); // 灞�閮ㄥ埛鏂板垵濮嬪寲
+
+ lvgl_port_unlock();
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ // Note: SetupUI() should be called by Application::Initialize(), not in constructor
+ // to ensure lvgl objects are created after the display is fully initialized.
+}
+
+CustomLcdDisplay::~CustomLcdDisplay() {
+
+}
+
+void CustomLcdDisplay::spi_gpio_init() {
+ int rst = lcd_spi_data.rst;
+ int cs = lcd_spi_data.cs;
+ int dc = lcd_spi_data.dc;
+ int busy = lcd_spi_data.busy;
+
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << rst) | (0x1ULL << dc) | (0x1ULL << cs);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+
+ gpio_conf.mode = GPIO_MODE_INPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << busy);
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+
+ set_rst_1();
+}
+
+void CustomLcdDisplay::spi_port_init() {
+ int mosi = lcd_spi_data.mosi;
+ int scl = lcd_spi_data.scl;
+ int spi_host = lcd_spi_data.spi_host;
+ esp_err_t ret;
+ spi_bus_config_t buscfg = {};
+ buscfg.miso_io_num = -1;
+ buscfg.mosi_io_num = mosi;
+ buscfg.sclk_io_num = scl;
+ buscfg.quadwp_io_num = -1;
+ buscfg.quadhd_io_num = -1;
+ buscfg.max_transfer_sz = Width * Height;
+
+ spi_device_interface_config_t devcfg = {};
+ devcfg.spics_io_num = -1;
+ devcfg.clock_speed_hz = 40 * 1000 * 1000; // Clock out at 10 MHz
+ devcfg.mode = 0; // SPI mode 0
+ devcfg.queue_size = 7; // We want to be able to queue 7 transactions at a time
+
+ ret = spi_bus_initialize((spi_host_device_t) spi_host, &buscfg, SPI_DMA_CH_AUTO);
+ ESP_ERROR_CHECK(ret);
+ ret = spi_bus_add_device((spi_host_device_t) spi_host, &devcfg, &spi);
+ ESP_ERROR_CHECK(ret);
+}
+
+void CustomLcdDisplay::read_busy() {
+ int busy = lcd_spi_data.busy;
+ while (gpio_get_level((gpio_num_t) busy) == 1) {
+ vTaskDelay(pdMS_TO_TICKS(5)); // LOW: idle, HIGH: busy
+ }
+}
+
+void CustomLcdDisplay::SPI_SendByte(uint8_t data) {
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t));
+ t.length = 8;
+ t.tx_buffer = &data;
+ ret = spi_device_polling_transmit(spi, &t); // Transmit!
+ assert(ret == ESP_OK); // Should have had no issues.
+}
+
+void CustomLcdDisplay::EPD_SendData(uint8_t data) {
+ set_dc_1();
+ set_cs_0();
+ SPI_SendByte(data);
+ set_cs_1();
+}
+
+void CustomLcdDisplay::EPD_SendCommand(uint8_t command) {
+ set_dc_0();
+ set_cs_0();
+ SPI_SendByte(command);
+ set_cs_1();
+}
+
+void CustomLcdDisplay::writeBytes(uint8_t *buffer, int len) {
+ set_dc_1();
+ set_cs_0();
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t));
+ t.length = 8 * len;
+ t.tx_buffer = buffer;
+ ret = spi_device_polling_transmit(spi, &t); // Transmit!
+ assert(ret == ESP_OK);
+ set_cs_1();
+}
+
+void CustomLcdDisplay::writeBytes(const uint8_t *buffer, int len) {
+ set_dc_1();
+ set_cs_0();
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t));
+ t.length = 8 * len;
+ t.tx_buffer = buffer;
+ ret = spi_device_polling_transmit(spi, &t); // Transmit!
+ assert(ret == ESP_OK);
+ set_cs_1();
+}
+
+void CustomLcdDisplay::EPD_SetWindows(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend) {
+ EPD_SendCommand(0x44); // SET_RAM_X_ADDRESS_START_END_POSITION
+ EPD_SendData((Xstart >> 3) & 0xFF);
+ EPD_SendData((Xend >> 3) & 0xFF);
+
+ EPD_SendCommand(0x45); // SET_RAM_Y_ADDRESS_START_END_POSITION
+ EPD_SendData(Ystart & 0xFF);
+ EPD_SendData((Ystart >> 8) & 0xFF);
+ EPD_SendData(Yend & 0xFF);
+ EPD_SendData((Yend >> 8) & 0xFF);
+}
+
+void CustomLcdDisplay::EPD_SetCursor(uint16_t Xstart, uint16_t Ystart) {
+ EPD_SendCommand(0x4E); // SET_RAM_X_ADDRESS_COUNTER
+ EPD_SendData(Xstart & 0xFF);
+
+ EPD_SendCommand(0x4F); // SET_RAM_Y_ADDRESS_COUNTER
+ EPD_SendData(Ystart & 0xFF);
+ EPD_SendData((Ystart >> 8) & 0xFF);
+}
+
+void CustomLcdDisplay::EPD_SetLut(const uint8_t *lut) {
+ EPD_SendCommand(0x32);
+ writeBytes(lut, 153);
+ read_busy();
+
+ EPD_SendCommand(0x3f);
+ EPD_SendData(lut[153]);
+
+ EPD_SendCommand(0x03);
+ EPD_SendData(lut[154]);
+
+ EPD_SendCommand(0x04);
+ EPD_SendData(lut[155]);
+ EPD_SendData(lut[156]);
+ EPD_SendData(lut[157]);
+
+ EPD_SendCommand(0x2c);
+ EPD_SendData(lut[158]);
+}
+
+void CustomLcdDisplay::EPD_TurnOnDisplay() {
+ EPD_SendCommand(0x22);
+ EPD_SendData(0xc7);
+ EPD_SendCommand(0x20);
+ read_busy();
+}
+
+void CustomLcdDisplay::EPD_TurnOnDisplayPart() {
+ EPD_SendCommand(0x22);
+ EPD_SendData(0xcf);
+ EPD_SendCommand(0x20);
+ read_busy();
+}
+
+void CustomLcdDisplay::EPD_Init() {
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+ set_rst_0();
+ vTaskDelay(pdMS_TO_TICKS(20));
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+
+ read_busy();
+ EPD_SendCommand(0x12); // SWRESET
+ read_busy();
+
+ EPD_SendCommand(0x01); // Driver output control
+ EPD_SendData(0xC7);
+ EPD_SendData(0x00);
+ EPD_SendData(0x01);
+
+ EPD_SendCommand(0x11); // data entry mode
+ EPD_SendData(0x01);
+
+ EPD_SetWindows(0, Width - 1, Height - 1, 0);
+
+ EPD_SendCommand(0x3C); // BorderWavefrom
+ EPD_SendData(0x01);
+
+ EPD_SendCommand(0x18);
+ EPD_SendData(0x80);
+
+ EPD_SendCommand(0x22); // Load Temperature and waveform setting.
+ EPD_SendData(0XB1);
+ EPD_SendCommand(0x20);
+
+ EPD_SetCursor(0, Height - 1);
+ read_busy();
+
+ EPD_SetLut(WF_Full_1IN54);
+}
+
+void CustomLcdDisplay::EPD_Clear() {
+ int buffer_len = lcd_spi_data.buffer_len;
+ memset(buffer, 0xff, buffer_len);
+}
+
+void CustomLcdDisplay::EPD_Display() {
+ int buffer_len = lcd_spi_data.buffer_len;
+ EPD_SendCommand(0x24);
+ assert(buffer);
+ writeBytes(buffer, buffer_len);
+ EPD_TurnOnDisplay();
+}
+
+void CustomLcdDisplay::EPD_DisplayPartBaseImage() {
+ int buffer_len = lcd_spi_data.buffer_len;
+ EPD_SendCommand(0x24);
+ assert(buffer);
+ writeBytes(buffer, buffer_len);
+ EPD_SendCommand(0x26);
+ writeBytes(buffer, buffer_len);
+ EPD_TurnOnDisplay();
+}
+
+void CustomLcdDisplay::EPD_Init_Partial() {
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+ set_rst_0();
+ vTaskDelay(pdMS_TO_TICKS(20));
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+
+ read_busy();
+
+ EPD_SetLut(WF_PARTIAL_1IN54_0);
+
+ EPD_SendCommand(0x37);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData(0x40);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+
+ EPD_SendCommand(0x3C); // BorderWavefrom
+ EPD_SendData(0x80);
+
+ EPD_SendCommand(0x22);
+ EPD_SendData(0xc0);
+ EPD_SendCommand(0x20);
+ read_busy();
+}
+
+void CustomLcdDisplay::EPD_DisplayPart() {
+ EPD_SendCommand(0x24);
+ assert(buffer);
+ writeBytes(buffer, 5000);
+ EPD_TurnOnDisplayPart();
+}
+
+void CustomLcdDisplay::EPD_DrawColorPixel(uint16_t x, uint16_t y, uint8_t color) {
+ if (x >= Width || y >= Height) {
+ ESP_LOGE("EPD", "Out of bounds pixel: (%d,%d)", x, y);
+ return;
+ }
+
+ uint16_t index = y * 25 + (x >> 3);
+ uint8_t bit = 7 - (x & 0x07);
+ if (color == DRIVER_COLOR_WHITE) {
+ buffer[index] |= (0x01 << bit);
+ } else {
+ buffer[index] &= ~(0x01 << bit);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.h
new file mode 100644
index 0000000..7a8f03e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/custom_lcd_display.h
@@ -0,0 +1,75 @@
+#ifndef __CUSTOM_LCD_DISPLAY_H__
+#define __CUSTOM_LCD_DISPLAY_H__
+
+#include <driver/gpio.h>
+#include "lcd_display.h"
+
+/* Display color */
+typedef enum {
+ DRIVER_COLOR_WHITE = 0xff,
+ DRIVER_COLOR_BLACK = 0x00,
+ FONT_BACKGROUND = DRIVER_COLOR_WHITE,
+}COLOR_IMAGE;
+
+typedef struct {
+ uint8_t cs;
+ uint8_t dc;
+ uint8_t rst;
+ uint8_t busy;
+ uint8_t mosi;
+ uint8_t scl;
+ int spi_host;
+ int buffer_len;
+}custom_lcd_spi_t;
+
+
+class CustomLcdDisplay : public LcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy,custom_lcd_spi_t _lcd_spi_data);
+ ~CustomLcdDisplay();
+
+ void EPD_Init(); /* 澧ㄦ按灞忓垵濮嬪寲 */
+ void EPD_Clear(); /* 娓呯┖灞忓箷 */
+ void EPD_Display(); /* 鍒穊uffer鍒板ⅷ姘村睆 */
+
+ /*蹇�熷埛鏂�*/
+ void EPD_DisplayPartBaseImage();
+ void EPD_Init_Partial();
+ void EPD_DisplayPart();
+ void EPD_DrawColorPixel(uint16_t x, uint16_t y,uint8_t color);
+
+private:
+ const custom_lcd_spi_t lcd_spi_data;
+ const int Width;
+ const int Height;
+ spi_device_handle_t spi;
+ uint8_t *buffer = NULL;
+
+ static void lvgl_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p);
+
+ void spi_gpio_init();
+ void spi_port_init();
+ void read_busy();
+
+ void set_cs_1(){gpio_set_level((gpio_num_t)lcd_spi_data.cs,1);}
+ void set_cs_0(){gpio_set_level((gpio_num_t)lcd_spi_data.cs,0);}
+ void set_dc_1(){gpio_set_level((gpio_num_t)lcd_spi_data.dc,1);}
+ void set_dc_0(){gpio_set_level((gpio_num_t)lcd_spi_data.dc,0);}
+ void set_rst_1(){gpio_set_level((gpio_num_t)lcd_spi_data.rst,1);}
+ void set_rst_0(){gpio_set_level((gpio_num_t)lcd_spi_data.rst,0);}
+
+ void SPI_SendByte(uint8_t data);
+ void EPD_SendData(uint8_t data);
+ void EPD_SendCommand(uint8_t command);
+ void writeBytes(uint8_t *buffer,int len);
+ void writeBytes(const uint8_t *buffer, int len);
+ void EPD_SetWindows(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend);
+ void EPD_SetCursor(uint16_t Xstart, uint16_t Ystart);
+ void EPD_SetLut(const uint8_t *lut);
+ void EPD_TurnOnDisplay();
+ void EPD_TurnOnDisplayPart();
+};
+
+#endif // __CUSTOM_LCD_DISPLAY_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc
new file mode 100644
index 0000000..f801d27
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-1.54/waveshare-s3-epaper-1.54.cc
@@ -0,0 +1,172 @@
+#include <stdio.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include "application.h"
+#include "button.h"
+#include "codecs/es8311_audio_codec.h"
+#include "config.h"
+#include "wifi_board.h"
+#include "board_power_bsp.h"
+#include "custom_lcd_display.h"
+#include "lvgl.h"
+#include "mcp_server.h"
+
+#define TAG "waveshare_epaper_1_54"
+
+class CustomBoard : public WifiBoard {
+ private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button pwr_button_;
+ CustomLcdDisplay *display_;
+ BoardPowerBsp *power_;
+ adc_oneshot_unit_handle_t adc1_handle;
+ adc_cali_handle_t cali_handle;
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = (i2c_port_t) 0;
+ i2c_bus_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ pwr_button_.OnLongPress([this]() {
+ GetDisplay()->SetChatMessage("system", "OFF");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ power_->PowerAudioOff();
+ power_->PowerEpdOff();
+ power_->VbatPowerOff();
+ });
+ }
+
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.network", "閲嶆柊閰嶇綉", PropertyList(), [this](const PropertyList &) -> ReturnValue {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+ void InitializeLcdDisplay() {
+ custom_lcd_spi_t lcd_spi_data = {};
+ lcd_spi_data.cs = EPD_CS_PIN;
+ lcd_spi_data.dc = EPD_DC_PIN;
+ lcd_spi_data.rst = EPD_RST_PIN;
+ lcd_spi_data.busy = EPD_BUSY_PIN;
+ lcd_spi_data.mosi = EPD_MOSI_PIN;
+ lcd_spi_data.scl = EPD_SCK_PIN;
+ lcd_spi_data.spi_host = EPD_SPI_NUM;
+ lcd_spi_data.buffer_len = 5000;
+ display_ = new CustomLcdDisplay(NULL, NULL, EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, lcd_spi_data);
+ }
+
+ void Power_Init() {
+ power_ = new BoardPowerBsp(EPD_PWR_PIN, Audio_PWR_PIN, VBAT_PWR_PIN);
+ power_->VbatPowerOn();
+ power_->PowerAudioOn();
+ power_->PowerEpdOn();
+ do {
+ vTaskDelay(pdMS_TO_TICKS(10));
+ } while (!gpio_get_level(VBAT_PWR_GPIO));
+ }
+
+ uint16_t BatterygetVoltage(void) {
+ static bool initialized = false;
+ static adc_oneshot_unit_handle_t adc_handle;
+ static adc_cali_handle_t cali_handle = NULL;
+ if (!initialized) {
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ };
+ adc_oneshot_new_unit(&init_config, &adc_handle);
+
+ adc_oneshot_chan_cfg_t ch_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_3, &ch_config);
+
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = ADC_UNIT_1,
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) {
+ initialized = true;
+ }
+ }
+
+ if (initialized) {
+ int raw_value = 0;
+ int raw_voltage = 0;
+ int voltage = 0; // mV
+ adc_oneshot_read(adc_handle, ADC_CHANNEL_3, &raw_value);
+ adc_cali_raw_to_voltage(cali_handle, raw_value, &raw_voltage);
+ voltage = raw_voltage * 2;
+ // ESP_LOGI(TAG, "voltage: %dmV", voltage);
+ return (uint16_t)voltage;
+ }
+
+ return 0;
+ }
+
+ uint8_t BatterygetPercent() {
+ int voltage = 0;
+ for (uint8_t i = 0; i < 10; i++) {
+ voltage += BatterygetVoltage();
+ }
+
+ voltage /= 10;
+ int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000;
+ percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent;
+ // ESP_LOGI(TAG, "voltage: %dmV, percentage: %d%%", voltage, percent);
+ return (uint8_t)percent;
+ }
+
+ public:
+ CustomBoard() : boot_button_(BOOT_BUTTON_GPIO), pwr_button_(VBAT_PWR_GPIO) {
+ Power_Init();
+ InitializeI2c();
+ InitializeButtons();
+ InitializeTools();
+ InitializeLcdDisplay();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Es8311AudioCodec audio_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 bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ charging = false;
+ discharging = !charging;
+ level = (int)BatterygetPercent();
+
+ return true;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/README.md
new file mode 100644
index 0000000..2f951f3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/README.md
@@ -0,0 +1,48 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-ePaper-3.97](https://www.waveshare.net/shop/ESP32-S3-ePaper-3.97.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-ePaper-3.97
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.h
new file mode 100644
index 0000000..dd6dfa7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.h
@@ -0,0 +1,44 @@
+#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_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_47
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_21
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_39
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VBAT_PWR_GPIO GPIO_NUM_1
+
+#define EPD_SPI_NUM SPI3_HOST
+
+#define EPD_DC_PIN GPIO_NUM_9
+#define EPD_CS_PIN GPIO_NUM_10
+#define EPD_SCK_PIN GPIO_NUM_11
+#define EPD_MOSI_PIN GPIO_NUM_12
+#define EPD_RST_PIN GPIO_NUM_46
+#define EPD_BUSY_PIN GPIO_NUM_3
+
+#define EXAMPLE_LCD_WIDTH 800
+#define EXAMPLE_LCD_HEIGHT 480
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.json
new file mode 100644
index 0000000..08bf71a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-epaper-3.97",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.cc
new file mode 100644
index 0000000..ef1355b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.cc
@@ -0,0 +1,393 @@
+#include "custom_lcd_display.h"
+#include <esp_lcd_panel_io.h>
+#include <esp_log.h>
+#include <freertos/FreeRTOS.h>
+#include <stdio.h>
+#include <vector>
+#include "board.h"
+#include "config.h"
+#include "esp_lvgl_port.h"
+#include "settings.h"
+
+#define TAG "CustomEpdDisplay"
+
+#define BYTES_PER_PIXEL (LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565))
+#define BUFF_SIZE (EXAMPLE_LCD_WIDTH * EXAMPLE_LCD_HEIGHT * BYTES_PER_PIXEL)
+
+void CustomEpdDisplay::lvgl_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_p) {
+ assert(disp != NULL);
+ CustomEpdDisplay* driver = (CustomEpdDisplay*)lv_display_get_user_data(disp);
+ uint16_t* buffer = (uint16_t*)color_p;
+ driver->EPD_Clear();
+ for (int y = area->y1; y <= area->y2; y++) {
+ for (int x = area->x1; x <= area->x2; x++) {
+ uint8_t color = (*buffer < 0x7fff) ? DRIVER_COLOR_BLACK : DRIVER_COLOR_WHITE;
+ driver->EPD_DrawColorPixel(x, y, color);
+ buffer++;
+ }
+ }
+ driver->EPD_DisplayPart();
+ lv_disp_flush_ready(disp);
+}
+
+CustomEpdDisplay::CustomEpdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y, bool mirror_x,
+ bool mirror_y, bool swap_xy, custom_epd_spi_t _epd_spi_data)
+ : LcdDisplay(panel_io, panel, width, height),
+ epd_spi_data(_epd_spi_data),
+ Width(width),
+ Height(height) {
+ ESP_LOGI(TAG, "Initialize SPI");
+ spi_port_init();
+ spi_gpio_init();
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 2;
+ port_cfg.timer_period_ms = 50;
+ lvgl_port_init(&port_cfg);
+ lvgl_port_lock(0);
+
+ buffer = (uint8_t*)heap_caps_malloc(epd_spi_data.buffer_len, MALLOC_CAP_SPIRAM);
+ assert(buffer);
+ display_ = lv_display_create(width, height); /* 浠ユ按骞冲拰鍨傜洿鍒嗚鲸鐜囷紙鍍忕礌锛夎繘琛屽熀鏈垵濮嬪寲 */
+ lv_display_set_flush_cb(display_, lvgl_flush_cb);
+ lv_display_set_user_data(display_, this);
+
+ uint8_t* buffer_1 = NULL;
+ buffer_1 = (uint8_t*)heap_caps_malloc(BUFF_SIZE, MALLOC_CAP_SPIRAM);
+ assert(buffer_1);
+ lv_display_set_buffers(display_, buffer_1, NULL, BUFF_SIZE, LV_DISPLAY_RENDER_MODE_FULL);
+
+ ESP_LOGI(TAG, "EPD init");
+ EPD_Init();
+ ESP_LOGI(TAG, "EPD Clear");
+ EPD_Clear();
+ EPD_Display();
+
+ lvgl_port_unlock();
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ ESP_LOGI(TAG, "ui start");
+
+ SetupUI();
+}
+
+CustomEpdDisplay::~CustomEpdDisplay() {}
+
+void CustomEpdDisplay::spi_gpio_init() {
+ int rst = epd_spi_data.rst;
+ int cs = epd_spi_data.cs;
+ int dc = epd_spi_data.dc;
+ int busy = epd_spi_data.busy;
+
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << rst) | (0x1ULL << dc) | (0x1ULL << cs);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+
+ gpio_conf.mode = GPIO_MODE_INPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << busy);
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+
+ set_rst_1();
+}
+
+void CustomEpdDisplay::spi_port_init() {
+ int mosi = epd_spi_data.mosi;
+ int scl = epd_spi_data.scl;
+ int spi_host = epd_spi_data.spi_host;
+ esp_err_t ret;
+ spi_bus_config_t buscfg = {};
+ buscfg.miso_io_num = -1;
+ buscfg.mosi_io_num = mosi;
+ buscfg.sclk_io_num = scl;
+ buscfg.quadwp_io_num = -1;
+ buscfg.quadhd_io_num = -1;
+ buscfg.max_transfer_sz = 4096;
+
+ spi_device_interface_config_t devcfg = {};
+ devcfg.spics_io_num = -1;
+ devcfg.clock_speed_hz = 20 * 1000 * 1000; // Clock out at 10 MHz
+ devcfg.mode = 0; // SPI mode 0
+ devcfg.queue_size = 7; // We want to be able to queue 7 transactions at a time
+
+ ret = spi_bus_initialize((spi_host_device_t)spi_host, &buscfg, SPI_DMA_CH_AUTO);
+ ESP_ERROR_CHECK(ret);
+ ret = spi_bus_add_device((spi_host_device_t)spi_host, &devcfg, &spi);
+ ESP_ERROR_CHECK(ret);
+}
+
+void CustomEpdDisplay::read_busy() {
+ int busy = epd_spi_data.busy;
+ while (gpio_get_level((gpio_num_t)busy) == 1) {
+ vTaskDelay(pdMS_TO_TICKS(5)); // LOW: idle, HIGH: busy
+ }
+}
+
+void CustomEpdDisplay::SPI_SendByte(uint8_t data) {
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t));
+ t.length = 8;
+ t.tx_buffer = &data;
+ ret = spi_device_polling_transmit(spi, &t); // Transmit!
+ assert(ret == ESP_OK); // Should have had no issues.
+}
+
+void CustomEpdDisplay::EPD_SendData(uint8_t data) {
+ set_dc_1();
+ set_cs_0();
+ SPI_SendByte(data);
+ set_cs_1();
+}
+
+void CustomEpdDisplay::EPD_SendCommand(uint8_t command) {
+ set_dc_0();
+ set_cs_0();
+ SPI_SendByte(command);
+ set_cs_1();
+}
+
+void CustomEpdDisplay::writeBytes(uint8_t* buffer, int len) {
+ set_dc_1();
+ set_cs_0();
+
+ const int MAX_SPI_TRANSFER = 4096;
+
+ int remaining = len;
+ int offset = 0;
+
+ while (remaining > 0) {
+ int chunk_size = (remaining > MAX_SPI_TRANSFER) ? MAX_SPI_TRANSFER : remaining;
+
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t));
+ t.length = 8 * chunk_size;
+ t.tx_buffer = buffer + offset;
+ ret = spi_device_polling_transmit(spi, &t); // Transmit!
+
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "SPI transmit failed at offset %d, chunk %d: %s", offset, chunk_size,
+ esp_err_to_name(ret));
+ break;
+ }
+
+ remaining -= chunk_size;
+ offset += chunk_size;
+ }
+
+ set_cs_1();
+}
+
+void CustomEpdDisplay::writeBytes(const uint8_t* buffer, int len) {
+ set_dc_1();
+ set_cs_0();
+
+ const int MAX_SPI_TRANSFER = 4096;
+
+ int remaining = len;
+ int offset = 0;
+
+ while (remaining > 0) {
+ int chunk_size = (remaining > MAX_SPI_TRANSFER) ? MAX_SPI_TRANSFER : remaining;
+
+ esp_err_t ret;
+ spi_transaction_t t;
+ memset(&t, 0, sizeof(t));
+ t.length = 8 * chunk_size;
+ t.tx_buffer = buffer + offset;
+ ret = spi_device_polling_transmit(spi, &t); // Transmit!
+
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "SPI transmit failed at offset %d, chunk %d: %s", offset, chunk_size,
+ esp_err_to_name(ret));
+ break;
+ }
+
+ remaining -= chunk_size;
+ offset += chunk_size;
+ }
+
+ set_cs_1();
+}
+
+void CustomEpdDisplay::EPD_SetWindows(uint16_t Xstart, uint16_t Ystart, uint16_t Xend,
+ uint16_t Yend) {
+ EPD_SendCommand(0x44); // SET_RAM_X_ADDRESS_START_END_POSITION
+ EPD_SendData((Xstart * 8) & 0xFF);
+ EPD_SendData(((Xstart * 8) >> 8) & 0xFF);
+ EPD_SendData((Xend * 8) & 0xFF);
+ EPD_SendData(((Xend * 8) >> 8) & 0xFF);
+
+ EPD_SendCommand(0x45); // SET_RAM_Y_ADDRESS_START_END_POSITION
+ EPD_SendData(Yend & 0xFF);
+ EPD_SendData((Yend >> 8) & 0xFF);
+ EPD_SendData(Ystart & 0xFF);
+ EPD_SendData((Ystart >> 8) & 0xFF);
+}
+
+void CustomEpdDisplay::EPD_SetCursor(uint16_t Xstart, uint16_t Ystart) {
+ EPD_SendCommand(0x4E);
+ EPD_SendData((Xstart * 8) & 0xFF);
+ EPD_SendData(((Xstart * 8) >> 8) & 0xFF);
+
+ EPD_SendCommand(0x4F);
+ EPD_SendData(Ystart & 0xFF);
+ EPD_SendData((Ystart >> 8) & 0xFF);
+}
+
+void CustomEpdDisplay::EPD_TurnOnDisplay() {
+ EPD_SendCommand(0x22);
+ EPD_SendData(0xF7);
+ EPD_SendCommand(0x20);
+ read_busy();
+}
+
+void CustomEpdDisplay::EPD_TurnOnDisplayPart() {
+ EPD_SendCommand(0x22);
+ EPD_SendData(0xFF);
+ EPD_SendCommand(0x20);
+ read_busy();
+}
+
+void CustomEpdDisplay::EPD_Init() {
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+ set_rst_0();
+ vTaskDelay(pdMS_TO_TICKS(2));
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+
+ read_busy();
+ EPD_SendCommand(0x12); // SWRESET
+ read_busy();
+
+ EPD_SendCommand(0x18);
+ EPD_SendData(0x80);
+
+ EPD_SendCommand(0x0C); // Driver output control
+ EPD_SendData(0xAE);
+ EPD_SendData(0xC7);
+ EPD_SendData(0xC3);
+ EPD_SendData(0xC0);
+ EPD_SendData(0x80);
+
+ EPD_SendCommand(0x01); // Driver output control
+ EPD_SendData((Height - 1) % 256);
+ EPD_SendData((Height - 1) / 256);
+ EPD_SendData(0x02);
+
+ EPD_SendCommand(0x3C); // BorderWavefrom
+ EPD_SendData(0x01);
+
+ EPD_SendCommand(0x11); // data entry mode
+ EPD_SendData(0x01);
+
+ EPD_SendCommand(0x44); // set Ram-X address start/end position
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendData((Width - 1) % 256);
+ EPD_SendData((Width - 1) / 256);
+
+ EPD_SendCommand(0x45); // set Ram-Y address start/end position
+ EPD_SendData((Height - 1) % 256);
+ EPD_SendData((Height - 1) / 256);
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+
+ EPD_SendCommand(0x4E); // set RAM x address count to 0;
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+ EPD_SendCommand(0x4F); // set RAM y address count to 0X199;
+ EPD_SendData(0x00);
+ EPD_SendData(0x00);
+
+ read_busy();
+}
+
+void CustomEpdDisplay::EPD_Clear() {
+ int buffer_len = epd_spi_data.buffer_len;
+ memset(buffer, 0xff, buffer_len);
+}
+
+void CustomEpdDisplay::EPD_Display() {
+ int buffer_len = epd_spi_data.buffer_len;
+ EPD_SendCommand(0x24);
+ assert(buffer);
+ writeBytes(buffer, buffer_len);
+ EPD_TurnOnDisplay();
+}
+
+void CustomEpdDisplay::EPD_DisplayPartBaseImage() {
+ int buffer_len = epd_spi_data.buffer_len;
+ EPD_SendCommand(0x24);
+ assert(buffer);
+ writeBytes(buffer, buffer_len);
+ EPD_SendCommand(0x26);
+ writeBytes(buffer, buffer_len);
+ EPD_TurnOnDisplay();
+}
+
+void CustomEpdDisplay::EPD_Init_Partial() {
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+ set_rst_0();
+ vTaskDelay(pdMS_TO_TICKS(2));
+ set_rst_1();
+ vTaskDelay(pdMS_TO_TICKS(50));
+
+ read_busy();
+
+ EPD_SendCommand(0x18);
+ EPD_SendData(0x80);
+
+ EPD_SendCommand(0x3C);
+ EPD_SendData(0x80);
+
+ EPD_SetWindows(0, Width - 1, Height - 1, 0);
+
+ EPD_SetCursor(0, Height - 1);
+
+ read_busy();
+}
+
+void CustomEpdDisplay::EPD_DisplayPart() {
+ EPD_SendCommand(0x24);
+ assert(buffer);
+ writeBytes(buffer, 48000);
+ EPD_TurnOnDisplayPart();
+}
+
+void CustomEpdDisplay::EPD_Sleep() {
+ EPD_SendCommand(0x10); // enter deep sleep
+ EPD_SendData(0x01);
+ vTaskDelay(pdMS_TO_TICKS(10));
+ set_rst_0();
+ set_cs_0();
+ set_dc_0();
+}
+
+void CustomEpdDisplay::EPD_DrawColorPixel(uint16_t x, uint16_t y, uint8_t color) {
+ if (x >= Width || y >= Height) {
+ ESP_LOGE("EPD", "Out of bounds pixel: (%d,%d)", x, y);
+ return;
+ }
+
+ uint16_t index = y * Width / 8 + (x >> 3);
+ uint8_t bit = 7 - (x & 0x07);
+ if (color == DRIVER_COLOR_WHITE) {
+ buffer[index] |= (0x01 << bit);
+ } else {
+ buffer[index] &= ~(0x01 << bit);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.h
new file mode 100644
index 0000000..fe4913f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/custom_lcd_display.h
@@ -0,0 +1,74 @@
+#ifndef __CUSTOM_LCD_DISPLAY_H__
+#define __CUSTOM_LCD_DISPLAY_H__
+
+#include <driver/gpio.h>
+#include "lcd_display.h"
+
+/* Display color */
+typedef enum {
+ DRIVER_COLOR_WHITE = 0xff,
+ DRIVER_COLOR_BLACK = 0x00,
+ FONT_BACKGROUND = DRIVER_COLOR_WHITE,
+} COLOR_IMAGE;
+
+typedef struct {
+ uint8_t cs;
+ uint8_t dc;
+ uint8_t rst;
+ uint8_t busy;
+ uint8_t mosi;
+ uint8_t scl;
+ int spi_host;
+ int buffer_len;
+} custom_epd_spi_t;
+
+class CustomEpdDisplay : public LcdDisplay {
+public:
+ CustomEpdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width,
+ int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y,
+ bool swap_xy, custom_epd_spi_t _epd_spi_data);
+ ~CustomEpdDisplay();
+
+ void EPD_Init(); /* 澧ㄦ按灞忓垵濮嬪寲 */
+ void EPD_Clear(); /* 娓呯┖灞忓箷 */
+ void EPD_Display(); /* 鍒穊uffer鍒板ⅷ姘村睆 */
+ void EPD_Sleep();
+
+ /*蹇�熷埛鏂�*/
+ void EPD_DisplayPartBaseImage();
+ void EPD_Init_Partial();
+ void EPD_DisplayPart();
+ void EPD_DrawColorPixel(uint16_t x, uint16_t y, uint8_t color);
+
+private:
+ const custom_epd_spi_t epd_spi_data;
+ const int Width;
+ const int Height;
+ spi_device_handle_t spi;
+ uint8_t* buffer = NULL;
+
+ static void lvgl_flush_cb(lv_display_t* disp, const lv_area_t* area, uint8_t* color_p);
+
+ void spi_gpio_init();
+ void spi_port_init();
+ void read_busy();
+
+ void set_cs_1() { gpio_set_level((gpio_num_t)epd_spi_data.cs, 1); }
+ void set_cs_0() { gpio_set_level((gpio_num_t)epd_spi_data.cs, 0); }
+ void set_dc_1() { gpio_set_level((gpio_num_t)epd_spi_data.dc, 1); }
+ void set_dc_0() { gpio_set_level((gpio_num_t)epd_spi_data.dc, 0); }
+ void set_rst_1() { gpio_set_level((gpio_num_t)epd_spi_data.rst, 1); }
+ void set_rst_0() { gpio_set_level((gpio_num_t)epd_spi_data.rst, 0); }
+
+ void SPI_SendByte(uint8_t data);
+ void EPD_SendData(uint8_t data);
+ void EPD_SendCommand(uint8_t command);
+ void writeBytes(uint8_t* buffer, int len);
+ void writeBytes(const uint8_t* buffer, int len);
+ void EPD_SetWindows(uint16_t Xstart, uint16_t Ystart, uint16_t Xend, uint16_t Yend);
+ void EPD_SetCursor(uint16_t Xstart, uint16_t Ystart);
+ void EPD_TurnOnDisplay();
+ void EPD_TurnOnDisplayPart();
+};
+
+#endif // __CUSTOM_LCD_DISPLAY_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/waveshare-s3-epaper-3.97.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/waveshare-s3-epaper-3.97.cc
new file mode 100644
index 0000000..64ce477
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-epaper-3.97/waveshare-s3-epaper-3.97.cc
@@ -0,0 +1,178 @@
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include <stdio.h>
+#include "application.h"
+#include "button.h"
+#include "codecs/es8311_audio_codec.h"
+#include "config.h"
+#include "custom_lcd_display.h"
+#include "lvgl.h"
+#include "mcp_server.h"
+#include "wifi_board.h"
+
+#include "axp2101.h"
+#include "codecs/box_audio_codec.h"
+#include "i2c_device.h"
+#include "power_save_timer.h"
+
+#define TAG "waveshare_epaper_3_97"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+ // Set ALDO2 to 3.3V
+ WriteReg(0x93, (3300 - 500) / 100);
+ // Set ALDO3 to 3.3V
+ WriteReg(0x94, (3300 - 500) / 100);
+
+ // Enable ALDO1銆丄LDO2銆丄LDO2
+ WriteReg(0x90, 0x07);
+
+ WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA,
+ // 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+class WaveshareEsp32s3ePaper3inch97 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ Button pwr_button_;
+ CustomEpdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 100, 300);
+ power_save_timer_->OnShutdownRequest([this]() {
+ GetDisplay()->SetChatMessage("system", "OFF");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = (i2c_port_t)0;
+ i2c_bus_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode
+ // without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ pwr_button_.OnClick([this]() {
+ GetDisplay()->SetChatMessage("system", "OFF");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ pmic_->PowerOff();
+ });
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeTools() {
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.network", "閲嶆柊閰嶇綉", PropertyList(),
+ [this](const PropertyList&) -> ReturnValue {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+ void InitializeEpdDisplay() {
+ custom_epd_spi_t epd_spi_data = {};
+ epd_spi_data.cs = EPD_CS_PIN;
+ epd_spi_data.dc = EPD_DC_PIN;
+ epd_spi_data.rst = EPD_RST_PIN;
+ epd_spi_data.busy = EPD_BUSY_PIN;
+ epd_spi_data.mosi = EPD_MOSI_PIN;
+ epd_spi_data.scl = EPD_SCK_PIN;
+ epd_spi_data.spi_host = EPD_SPI_NUM;
+ epd_spi_data.buffer_len = 48000;
+ display_ = new CustomEpdDisplay(NULL, NULL, EXAMPLE_LCD_WIDTH, EXAMPLE_LCD_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, epd_spi_data);
+ }
+
+public:
+ WaveshareEsp32s3ePaper3inch97()
+ : boot_button_(BOOT_BUTTON_GPIO), pwr_button_(VBAT_PWR_GPIO, 1) {
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeAxp2101();
+ InitializeButtons();
+ InitializeTools();
+ InitializeEpdDisplay();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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 bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual Display* GetDisplay() override { return display_; }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3ePaper3inch97);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/README.md
new file mode 100644
index 0000000..4fa0058
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/README.md
@@ -0,0 +1,60 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-LCD-0.85](https://www.waveshare.net/shop/ESP32-S3-LCD-0.85.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32C6**
+
+```bash
+idf.py set-target esp32S3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-LCD-0.85
+```
+
+**淇敼 flash 澶у皬锛�**
+
+```
+Serial flasher config -> Flash size -> 8 MB
+```
+
+**淇敼鍒嗗尯琛細**
+
+```
+Partition Table -> Custom partition CSV file -> partitions/v2/8m.csv
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.h
new file mode 100644
index 0000000..8a0e96b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.h
@@ -0,0 +1,56 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define PWR_BUTTON_GPIO GPIO_NUM_5
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_4
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_SPI_MODE 3
+#define DISPLAY_CS_PIN GPIO_NUM_21
+#define DISPLAY_MOSI_PIN GPIO_NUM_39
+#define DISPLAY_MISO_PIN GPIO_NUM_NC
+#define DISPLAY_CLK_PIN GPIO_NUM_38
+#define DISPLAY_DC_PIN GPIO_NUM_45
+#define DISPLAY_RST_PIN GPIO_NUM_40
+
+#define TOOUCH_INT_PIN GPIO_NUM_48
+#define TOOUCH_RST_PIN GPIO_NUM_47
+
+#define BATTERY_EN_PIN GPIO_NUM_2
+#define BATTERY_ADC_PIN GPIO_NUM_1
+#define BATTERY_CHARGING_PIN GPIO_NUM_3
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_INVERT_COLOR true
+
+#define DISPLAY_OFFSET_X 2
+#define DISPLAY_OFFSET_Y 1
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.json
new file mode 100644
index 0000000..6520b37
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/config.json
@@ -0,0 +1,15 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-lcd-0.85",
+ "sdkconfig_append": [
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/esp32-s3-lcd-0.85.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/esp32-s3-lcd-0.85.cc
new file mode 100644
index 0000000..6af8334
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/esp32-s3-lcd-0.85.cc
@@ -0,0 +1,311 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <wifi_station.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_timer.h>
+#include "power_save_timer.h"
+#include "assets/lang_config.h"
+#include <esp_lvgl_port.h>
+#include "driver/gpio.h"
+#include "iot_button.h"
+#include "power_manager.h"
+#include "led/circular_strip.h"
+#include "led_strip_control.h"
+
+#define TAG "waveshare_lcd_0_85"
+
+
+class CustomButton: public Button{
+public:
+ void OnPressDownDel(void) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_down_ = NULL;
+ iot_button_unregister_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr);
+ }
+ void OnPressUpDel(void) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_up_ = NULL;
+ iot_button_unregister_cb(button_handle_, BUTTON_PRESS_UP, nullptr);
+ }
+};
+
+
+class CustomBoard : public WifiBoard {
+private:
+ CustomButton pwr_button_;
+ CustomButton volume_up_button_;
+ CustomButton volume_down_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ CircularStrip* led_strip_;
+ LcdDisplay* display_;
+ PowerManager* power_manager_ = nullptr;
+ PowerSaveTimer* power_save_timer_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, BATTERY_ADC_PIN, BATTERY_EN_PIN);
+ power_manager_->PowerON();
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ ESP_LOGI(TAG, "Enabling sleep mode");
+ auto display = GetDisplay();
+ display->SetChatMessage("system", "");
+ display->SetEmotion("sleepy");
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ auto display = GetDisplay();
+ display->SetChatMessage("system", "");
+ display->SetEmotion("neutral");
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ power_manager_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = DISPLAY_MISO_PIN;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLed() {
+ led_strip_ = new CircularStrip(BUILTIN_LED_GPIO, 8);
+ new LedStripControl(led_strip_);
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+
+ pwr_button_.OnPressUp([this]() {
+ pwr_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ });
+ pwr_button_.OnLongPress([this]() {
+ printf("Power button long press\n");
+ if (power_manager_ != nullptr){
+ power_manager_->PowerOff();
+ }
+ });
+
+ pwr_button_.OnDoubleClick([this]() {
+ static uint8_t brightness_last = 0;
+ auto backlight = Board::GetInstance().GetBacklight();
+ if (backlight->brightness() == 0) {
+ brightness_last = 0;
+ if (brightness_last == 0) {
+ backlight->SetBrightness(50, true);
+ } else {
+ backlight->SetBrightness(brightness_last, true);
+ }
+ } else {
+ brightness_last = backlight->brightness();
+ backlight->SetBrightness(0);
+ }
+ });
+ pwr_button_.OnMultipleClick([this]() {
+ EnterWifiConfigMode();
+ }, 3);
+ pwr_button_.OnPressUpDel();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = Board::GetInstance().GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ Board::GetInstance().GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = Board::GetInstance().GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ Board::GetInstance().GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+#if CONFIG_USE_DEVICE_AEC
+ volume_down_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "Reboot the device and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ CustomBoard() :
+ pwr_button_(PWR_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeLed();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ true);
+ return &audio_codec;
+ }
+
+ virtual Led* GetLed() override {
+ return led_strip_;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.cc
new file mode 100644
index 0000000..e2b0b83
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.cc
@@ -0,0 +1,130 @@
+#include "led_strip_control.h"
+#include "settings.h"
+#include "mcp_server.h"
+#include <esp_log.h>
+
+#define TAG "LedStripControl"
+
+
+int LedStripControl::LevelToBrightness(int level) const {
+ if (level < 0) level = 0;
+ if (level > 8) level = 8;
+ return (1 << level) - 1; // 2^n - 1
+}
+
+StripColor LedStripControl::RGBToColor(int red, int green, int blue) {
+ if (red < 0) red = 0;
+ if (red > 255) red = 255;
+ if (green < 0) green = 0;
+ if (green > 255) green = 255;
+ if (blue < 0) blue = 0;
+ if (blue > 255) blue = 255;
+ return {static_cast<uint8_t>(red), static_cast<uint8_t>(green), static_cast<uint8_t>(blue)};
+}
+
+LedStripControl::LedStripControl(CircularStrip* led_strip)
+ : led_strip_(led_strip) {
+ // 浠庤缃腑璇诲彇浜害绛夌骇
+ Settings settings("led_strip");
+ brightness_level_ = settings.GetInt("brightness", 4); // 榛樿绛夌骇4
+ led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
+
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.led_strip.get_brightness",
+ "Get the brightness of the led strip (0-8)",
+ PropertyList(), [this](const PropertyList& properties) -> ReturnValue {
+ return brightness_level_;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_brightness",
+ "Set the brightness of the led strip (0-8)",
+ PropertyList({
+ Property("level", kPropertyTypeInteger, 0, 8)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI(TAG, "Set LedStrip brightness level to %d", level);
+ brightness_level_ = level;
+ led_strip_->SetBrightness(LevelToBrightness(brightness_level_), 4);
+
+ // 淇濆瓨璁剧疆
+ Settings settings("led_strip", true);
+ settings.SetInt("brightness", brightness_level_);
+
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_single_color",
+ "Set the color of a single led.",
+ PropertyList({
+ Property("index", kPropertyTypeInteger, 0, 7),
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int index = properties["index"].value<int>();
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ ESP_LOGI(TAG, "Set led strip single color %d to %d, %d, %d",
+ index, red, green, blue);
+ led_strip_->SetSingleColor(index, RGBToColor(green, red, blue));
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.set_all_color",
+ "Set the color of all leds.",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ ESP_LOGI(TAG, "Set led strip all color to %d, %d, %d",
+ red, green, blue);
+ led_strip_->SetAllColor(RGBToColor(green, red, blue));
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.blink",
+ "Blink the led strip. (闂儊)",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255),
+ Property("interval", kPropertyTypeInteger, 0, 1000)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ int interval = properties["interval"].value<int>();
+ ESP_LOGI(TAG, "Blink led strip with color %d, %d, %d, interval %dms",
+ red, green, blue, interval);
+ led_strip_->Blink(RGBToColor(green, red, blue), interval);
+ return true;
+ });
+
+ mcp_server.AddTool("self.led_strip.scroll",
+ "Scroll the led strip. (璺戦┈鐏�)",
+ PropertyList({
+ Property("red", kPropertyTypeInteger, 0, 255),
+ Property("green", kPropertyTypeInteger, 0, 255),
+ Property("blue", kPropertyTypeInteger, 0, 255),
+ Property("length", kPropertyTypeInteger, 1, 7),
+ Property("interval", kPropertyTypeInteger, 0, 1000)
+ }), [this](const PropertyList& properties) -> ReturnValue {
+ int red = properties["red"].value<int>();
+ int green = properties["green"].value<int>();
+ int blue = properties["blue"].value<int>();
+ int interval = properties["interval"].value<int>();
+ int length = properties["length"].value<int>();
+ ESP_LOGI(TAG, "Scroll led strip with color %d, %d, %d, length %d, interval %dms",
+ red, green, blue, length, interval);
+ StripColor low = RGBToColor(4, 4, 4);
+ StripColor high = RGBToColor(green, red, blue);
+ led_strip_->Scroll(low, high, length, interval);
+ return true;
+ });
+
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.h
new file mode 100644
index 0000000..0e99bb3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/led_strip_control.h
@@ -0,0 +1,18 @@
+#ifndef LED_STRIP_CONTROL_H
+#define LED_STRIP_CONTROL_H
+
+#include "led/circular_strip.h"
+
+class LedStripControl {
+private:
+ CircularStrip* led_strip_;
+ int brightness_level_; // 浜害绛夌骇 (0-8)
+
+ int LevelToBrightness(int level) const; // 灏嗙瓑绾ц浆鎹负瀹為檯浜害鍊�
+ StripColor RGBToColor(int red, int green, int blue);
+
+public:
+ explicit LedStripControl(CircularStrip* led_strip);
+};
+
+#endif // LED_STRIP_CONTROL_H
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/power_manager.h
new file mode 100644
index 0000000..fd63cc0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-lcd-0.85/power_manager.h
@@ -0,0 +1,212 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include <esp_log.h>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include "esp_adc/adc_oneshot.h"
+#include "esp_adc/adc_cali.h"
+#include "esp_adc/adc_cali_scheme.h"
+
+#include <math.h>
+
+class PowerManager
+{
+private:
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ gpio_num_t bat_adc_pin_ = GPIO_NUM_NC;
+ gpio_num_t bat_power_pin_ = GPIO_NUM_NC;
+ adc_oneshot_unit_handle_t adc_handle_ = NULL;
+ adc_cali_handle_t adc_cali_handle_ = NULL;
+ adc_channel_t adc_channel_;
+ bool do_calibration = false;
+ bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
+ {
+ adc_cali_handle_t handle = NULL;
+ esp_err_t ret = ESP_FAIL;
+ bool calibrated = false;
+
+ if (!calibrated)
+ {
+ ESP_LOGI("power_manager", "calibration scheme version is %s", "Curve Fitting");
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = unit,
+ .chan = channel,
+ .atten = atten,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
+ };
+ ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
+ if (ret == ESP_OK)
+ {
+ calibrated = true;
+ }
+ }
+
+ *out_handle = handle;
+ if (ret == ESP_OK)
+ {
+ ESP_LOGI("power_manager", "Calibration Success");
+ }
+ else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated)
+ {
+ ESP_LOGW("power_manager", "eFuse not burnt, skip software calibration");
+ }
+ else
+ {
+ ESP_LOGE("power_manager", "Invalid arg or no memory");
+ }
+ return calibrated;
+ }
+
+public:
+ PowerManager(gpio_num_t charging_pin, gpio_num_t bat_adc_pin, gpio_num_t bat_power_pin) : charging_pin_(charging_pin),
+ bat_adc_pin_(bat_adc_pin),
+ bat_power_pin_(bat_power_pin)
+ {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ if (charging_pin_ != GPIO_NUM_NC)
+ {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = 1ULL << charging_pin_;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+ }
+ // 鍒濆鍖栫數姹犱娇鑳藉紩鑴�
+ if (bat_power_pin_ != GPIO_NUM_NC)
+ {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << bat_power_pin_;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+ // 鍒濆鍖朼dc
+ if (bat_adc_pin_ != GPIO_NUM_NC)
+ {
+ adc_oneshot_unit_init_cfg_t init_config = {};
+ init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
+ if (bat_adc_pin_ >= GPIO_NUM_1 && bat_adc_pin_ <= GPIO_NUM_10)
+ {
+ init_config.unit_id = ADC_UNIT_1;
+ adc_channel_ = (adc_channel_t)((int)bat_adc_pin_ - 1);
+ }
+ else if (bat_adc_pin_ >= GPIO_NUM_11 && bat_adc_pin_ <= GPIO_NUM_20)
+ {
+ init_config.unit_id = ADC_UNIT_2;
+ adc_channel_ = (adc_channel_t)((int)bat_adc_pin_ - 11);
+ }
+ else
+ {
+ return;
+ }
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+ adc_oneshot_chan_cfg_t config = {};
+ config.bitwidth = ADC_BITWIDTH_DEFAULT;
+ config.atten = ADC_ATTEN_DB_12;
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &config));
+ do_calibration = adc_calibration_init(init_config.unit_id, adc_channel_, config.atten, &adc_cali_handle_);
+ }
+ }
+
+ ~PowerManager()
+ {
+ if (adc_handle_)
+ {
+ ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_));
+ }
+ // if (adc_cali_handle_)
+ // {
+ // ESP_ERROR_CHECK(adc_cali_delete_scheme_line_fitting(adc_cali_handle_));
+ // }
+ }
+
+ int GetBatteryLevel(void)
+ {
+
+ int adc_raw = 0;
+ int voltage_int = 0;
+ const float voltage_float_threshold = 0.1f;
+ float voltage_float = 0.0f;
+ static float last_voltage_float = 0.0f;
+
+ static int last_battery_level = 0;
+
+ if (adc_handle_ != nullptr)
+ {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_raw));
+ if (do_calibration)
+ {
+ ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_int));
+ voltage_float = (voltage_int / 1000.0f) * 3.0;
+
+ if (fabs(voltage_float - last_voltage_float) >= voltage_float_threshold)
+ {
+ last_voltage_float = voltage_float;
+ if (voltage_float < 3.52) {
+ last_battery_level = 1;
+ } else if (voltage_float < 3.64) {
+ last_battery_level = 20;
+ } else if (voltage_float < 3.76) {
+ last_battery_level = 40;
+ } else if (voltage_float < 3.88) {
+ last_battery_level = 60;
+ } else if (voltage_float < 4.0) {
+ last_battery_level = 80;
+ } else {
+ last_battery_level = 100;
+ }
+ }
+ return last_battery_level;
+ }
+ }
+ return 100;
+ }
+
+ bool IsCharging(void)
+ {
+ if (charging_pin_ != GPIO_NUM_NC)
+ {
+ return gpio_get_level(charging_pin_) == 0 ? true : false;
+ }
+ return false;
+ }
+
+ bool IsDischarging(void)
+ {
+ if (charging_pin_ != GPIO_NUM_NC)
+ {
+ return gpio_get_level(charging_pin_) == 1;
+ }
+ return true;
+ }
+
+ bool IsChargingDone(void)
+ {
+ if (GetBatteryLevel() == 100)
+ {
+ return true;
+ }
+ return false;
+ }
+ void PowerOff(void)
+ {
+ if (bat_power_pin_ != GPIO_NUM_NC)
+ {
+ gpio_set_level(bat_power_pin_, 0);
+ }
+ }
+ void PowerON(void)
+ {
+ if (bat_power_pin_ != GPIO_NUM_NC)
+ {
+ gpio_set_level(bat_power_pin_, 1);
+ }
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/README.md
new file mode 100644
index 0000000..d7359ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/README.md
@@ -0,0 +1,48 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-RLCD-4.2](https://www.waveshare.net/shop/ESP32-S3-RLCD-4.2.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-RLCD-4.2
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.h
new file mode 100644
index 0000000..47b6c71
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.h
@@ -0,0 +1,46 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define ESP32_I2C_HOST I2C_NUM_0
+#define ESP32_LCD_HOST SPI3_HOST
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_13
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+
+#define RLCD_DC_PIN GPIO_NUM_5
+#define RLCD_CS_PIN GPIO_NUM_40
+#define RLCD_SCK_PIN GPIO_NUM_11
+#define RLCD_MOSI_PIN GPIO_NUM_12
+#define RLCD_RST_PIN GPIO_NUM_41
+#define RLCD_TE_PIN GPIO_NUM_6
+
+#define RLCD_WIDTH 400
+#define RLCD_HEIGHT 300
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.json
new file mode 100644
index 0000000..e441937
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-rlcd-4.2",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.cc
new file mode 100644
index 0000000..ba5d81d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.cc
@@ -0,0 +1,338 @@
+#include <vector>
+#include <cstring>
+#include <freertos/FreeRTOS.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include "custom_lcd_display.h"
+#include "lcd_display.h"
+#include "esp_lvgl_port.h"
+#include "assets/lang_config.h"
+#include "settings.h"
+#include "config.h"
+#include "board.h"
+
+void CustomLcdDisplay::Lvgl_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p)
+{
+ assert(disp != NULL);
+ CustomLcdDisplay *Disp = (CustomLcdDisplay *)lv_display_get_user_data(disp);
+ uint16_t *buffer = (uint16_t *)color_p;
+ for(int y = area->y1; y <= area->y2; y++)
+ {
+ for(int x = area->x1; x <= area->x2; x++)
+ {
+ uint8_t color = (*buffer < 0x7fff) ? ColorBlack : ColorWhite;
+ Disp->RLCD_SetPixel(x,y,color);
+ buffer++;
+ }
+ }
+ Disp->RLCD_Display();
+ lv_disp_flush_ready(disp);
+}
+
+CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io,
+esp_lcd_panel_handle_t panel,
+int width,
+int height,
+int offset_x,
+int offset_y,
+bool mirror_x,
+bool mirror_y,
+bool swap_xy,
+spi_display_config_t spiconfig,
+spi_host_device_t spi_host) : LcdDisplay(panel_io, panel, width, height),
+mosi_(spiconfig.mosi),
+scl_(spiconfig.scl),
+dc_(spiconfig.dc),
+cs_(spiconfig.cs),
+rst_(spiconfig.rst),
+width_(width),
+height_(height)
+{
+ ESP_LOGI(TAG, "Initialize SPI");
+ esp_err_t ret;
+ spi_bus_config_t buscfg = {};
+ int transfer = width_ * height_;
+ buscfg.miso_io_num = -1;
+ buscfg.mosi_io_num = mosi_;
+ buscfg.sclk_io_num = scl_;
+ buscfg.quadwp_io_num = -1;
+ buscfg.quadhd_io_num = -1;
+ buscfg.max_transfer_sz = transfer;
+ ret = spi_bus_initialize(spi_host, &buscfg, SPI_DMA_CH_AUTO);
+ ESP_ERROR_CHECK(ret);
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.dc_gpio_num = dc_;
+ io_config.cs_gpio_num = cs_;
+ io_config.pclk_hz = 40 * 1000 * 1000;
+ io_config.lcd_cmd_bits = 8;
+ io_config.lcd_param_bits = 8;
+ io_config.spi_mode = 0;
+ io_config.trans_queue_depth = 7;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)spi_host, &io_config, &io_handle));
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << rst_);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+ Set_ResetIOLevel(1);
+
+ DisplayLen = transfer >> 3; //(1byte 8ipex)
+ DispBuffer = (uint8_t *) heap_caps_malloc(DisplayLen, MALLOC_CAP_SPIRAM);
+ assert(DispBuffer);
+ PixelIndexLUT = (uint16_t (*)[300])heap_caps_malloc(transfer * sizeof(uint16_t), MALLOC_CAP_SPIRAM);
+ PixelBitLUT = (uint8_t (*)[300])heap_caps_malloc(transfer * sizeof(uint8_t), MALLOC_CAP_SPIRAM);
+ assert(PixelIndexLUT);
+ assert(PixelBitLUT);
+ if(width_ == 400) {
+ InitLandscapeLUT();
+ } else {
+ InitPortraitLUT();
+ }
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 2;
+ port_cfg.timer_period_ms = 50;
+ lvgl_port_init(&port_cfg);
+ lvgl_port_lock(0);
+
+ display_ = lv_display_create(width, height); /* 浠ユ按骞冲拰鍨傜洿鍒嗚鲸鐜囷紙鍍忕礌锛夎繘琛屽熀鏈垵濮嬪寲 */
+ lv_display_set_flush_cb(display_, Lvgl_flush_cb);
+ lv_display_set_user_data(display_, this);
+ size_t lvgl_buffer_size = LV_COLOR_FORMAT_GET_SIZE(LV_COLOR_FORMAT_RGB565) * transfer;
+ uint8_t *lvgl_buffer1 = (uint8_t *) heap_caps_malloc(lvgl_buffer_size, MALLOC_CAP_SPIRAM);
+ assert(lvgl_buffer1);
+ lv_display_set_buffers(display_, lvgl_buffer1, NULL, lvgl_buffer_size, LV_DISPLAY_RENDER_MODE_PARTIAL);
+
+ ESP_LOGI(TAG, "RLCD init");
+ RLCD_Init();
+
+ lvgl_port_unlock();
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ // Note: SetupUI() should be called by Application::Initialize(), not in constructor
+ // to ensure lvgl objects are created after the display is fully initialized.
+}
+
+CustomLcdDisplay::~CustomLcdDisplay() {
+}
+
+void CustomLcdDisplay::InitPortraitLUT() {
+ uint16_t W4 = width_ >> 2;
+ for (uint16_t y = 0; y < height_; y++)
+ {
+ uint16_t byte_y = y >> 1;
+ uint8_t local_y = y & 1;
+ for (uint16_t x = 0; x < width_; x++)
+ {
+ uint16_t byte_x = x >> 2;
+ uint8_t local_x = x & 3;
+
+ uint32_t index = byte_y * W4 + byte_x;
+ uint8_t bit = 7 - ((local_x << 1) | local_y);
+
+ PixelIndexLUT[x][y] = index;
+ PixelBitLUT [x][y] = (1 << bit);
+ }
+ }
+}
+
+void CustomLcdDisplay::InitLandscapeLUT() {
+ uint16_t H4 = height_ >> 2;
+ for (uint16_t y = 0; y < height_; y++)
+ {
+ uint16_t inv_y = height_ - 1 - y;
+ uint16_t block_y = inv_y >> 2;
+ uint8_t local_y = inv_y & 3;
+ for (uint16_t x = 0; x < width_; x++)
+ {
+ uint16_t byte_x = x >> 1;
+ uint8_t local_x = x & 1;
+
+ uint32_t index = byte_x * H4 + block_y;
+ uint8_t bit = 7 - ((local_y << 1) | local_x);
+
+ PixelIndexLUT[x][y] = index;
+ PixelBitLUT [x][y] = (1 << bit);
+ }
+ }
+}
+
+void CustomLcdDisplay::Set_ResetIOLevel(uint8_t level) {
+ gpio_set_level((gpio_num_t) rst_, level ? 1 : 0);
+}
+
+void CustomLcdDisplay::RLCD_SendCommand(uint8_t Reg) {
+ ESP_ERROR_CHECK(esp_lcd_panel_io_tx_param(io_handle, Reg, NULL, 0));
+}
+
+void CustomLcdDisplay::RLCD_SendData(uint8_t Data) {
+ ESP_ERROR_CHECK(esp_lcd_panel_io_tx_param(io_handle, -1, &Data, 1));
+}
+
+void CustomLcdDisplay::RLCD_Sendbuffera(uint8_t *Data, int len) {
+ ESP_ERROR_CHECK(esp_lcd_panel_io_tx_color(io_handle, -1, Data, len));
+}
+
+void CustomLcdDisplay::RLCD_Reset(void) {
+ Set_ResetIOLevel(1);
+ vTaskDelay(pdMS_TO_TICKS(50));
+ Set_ResetIOLevel(0);
+ vTaskDelay(pdMS_TO_TICKS(20));
+ Set_ResetIOLevel(1);
+ vTaskDelay(pdMS_TO_TICKS(50));
+}
+
+void CustomLcdDisplay::RLCD_ColorClear(uint8_t color) {
+ memset(DispBuffer, color, DisplayLen);
+}
+
+void CustomLcdDisplay::RLCD_Init() {
+ RLCD_Reset();
+
+ RLCD_SendCommand(0xD6); // NVM Load Control
+ RLCD_SendData(0x17);
+ RLCD_SendData(0x02);
+
+ RLCD_SendCommand(0xD1); //Booster Enable
+ RLCD_SendData(0x01);
+
+ RLCD_SendCommand(0xC0); //Gate Voltage Control
+ RLCD_SendData(0x11);
+ RLCD_SendData(0x04);
+
+ RLCD_SendCommand(0xC1); //VSHP Setting
+ RLCD_SendData(0x69);
+ RLCD_SendData(0x69);
+ RLCD_SendData(0x69);
+ RLCD_SendData(0x69);
+
+ RLCD_SendCommand(0xC2);
+ RLCD_SendData(0x19);
+ RLCD_SendData(0x19);
+ RLCD_SendData(0x19);
+ RLCD_SendData(0x19);
+
+ RLCD_SendCommand(0xC4);
+ RLCD_SendData(0x4B);
+ RLCD_SendData(0x4B);
+ RLCD_SendData(0x4B);
+ RLCD_SendData(0x4B);
+
+ RLCD_SendCommand(0xC5);
+ RLCD_SendData(0x19);
+ RLCD_SendData(0x19);
+ RLCD_SendData(0x19);
+ RLCD_SendData(0x19);
+
+ RLCD_SendCommand(0xD8);
+ RLCD_SendData(0x80);
+ RLCD_SendData(0xE9);
+
+ RLCD_SendCommand(0xB2);
+ RLCD_SendData(0x02);
+
+ RLCD_SendCommand(0xB3);
+ RLCD_SendData(0xE5);
+ RLCD_SendData(0xF6);
+ RLCD_SendData(0x05);
+ RLCD_SendData(0x46);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x76);
+ RLCD_SendData(0x45);
+
+ RLCD_SendCommand(0xB4);
+ RLCD_SendData(0x05);
+ RLCD_SendData(0x46);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x77);
+ RLCD_SendData(0x76);
+ RLCD_SendData(0x45);
+
+ RLCD_SendCommand(0x62);
+ RLCD_SendData(0x32);
+ RLCD_SendData(0x03);
+ RLCD_SendData(0x1F);
+
+ RLCD_SendCommand(0xB7);
+ RLCD_SendData(0x13);
+
+ RLCD_SendCommand(0xB0);
+ RLCD_SendData(0x64);
+
+ RLCD_SendCommand(0x11);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ RLCD_SendCommand(0xC9);
+ RLCD_SendData(0x00);
+
+ RLCD_SendCommand(0x36);
+ RLCD_SendData(0x48);
+
+ RLCD_SendCommand(0x3A);
+ RLCD_SendData(0x11);
+
+ RLCD_SendCommand(0xB9);
+ RLCD_SendData(0x20);
+
+ RLCD_SendCommand(0xB8);
+ RLCD_SendData(0x29);
+
+ RLCD_SendCommand(0x21);
+
+ RLCD_SendCommand(0x2A);
+ RLCD_SendData(0x12);
+ RLCD_SendData(0x2A);
+
+ RLCD_SendCommand(0x2B);
+ RLCD_SendData(0x00);
+ RLCD_SendData(0xC7);
+
+ RLCD_SendCommand(0x35);
+ RLCD_SendData(0x00);
+
+ RLCD_SendCommand(0xD0);
+ RLCD_SendData(0xFF);
+
+ RLCD_SendCommand(0x38);
+ RLCD_SendCommand(0x29);
+
+ RLCD_ColorClear(ColorWhite);
+}
+
+void CustomLcdDisplay::RLCD_SetPixel(uint16_t x, uint16_t y, uint8_t color) {
+ uint32_t idx = PixelIndexLUT[x][y];
+ uint8_t mask = PixelBitLUT[x][y];
+
+ uint8_t *p = &DispBuffer[idx];
+
+ if (color)
+ *p |= mask;
+ else
+ *p &= ~mask;
+}
+
+void CustomLcdDisplay::RLCD_Display() {
+ RLCD_SendCommand(0x2A); // Column Address Set
+ RLCD_SendData(0x12);
+ RLCD_SendData(0x2A);
+
+ RLCD_SendCommand(0x2B); // Page Address Set
+ RLCD_SendData(0x00);
+ RLCD_SendData(0xC7);
+
+ RLCD_SendCommand(0x2c); // Page Address Set
+
+ RLCD_Sendbuffera(DispBuffer,DisplayLen);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.h
new file mode 100644
index 0000000..7ca91cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/custom_lcd_display.h
@@ -0,0 +1,57 @@
+#ifndef __CUSTOM_LCD_DISPLAY_H__
+#define __CUSTOM_LCD_DISPLAY_H__
+
+#include <driver/gpio.h>
+#include "lcd_display.h"
+
+enum ColorSelection {
+ ColorBlack = 0,
+ ColorWhite = 0xff
+};
+
+typedef struct {
+ uint8_t mosi;
+ uint8_t scl;
+ uint8_t dc;
+ uint8_t cs;
+ uint8_t rst;
+} spi_display_config_t;
+
+class CustomLcdDisplay : public LcdDisplay {
+private:
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ uint32_t i2c_data_pdMS_TICKS = 0;
+ uint32_t i2c_done_pdMS_TICKS = 0;
+ const char *TAG = "CustomDisplay";
+ int mosi_;
+ int scl_;
+ int dc_;
+ int cs_;
+ int rst_;
+ int width_;
+ int height_;
+ uint8_t *DispBuffer = NULL;
+ int DisplayLen;
+ uint16_t (*PixelIndexLUT)[300];
+ uint8_t (*PixelBitLUT )[300];
+ void InitPortraitLUT();
+ void InitLandscapeLUT();
+ void Set_ResetIOLevel(uint8_t level);
+ void RLCD_SendCommand(uint8_t Reg);
+ void RLCD_SendData(uint8_t Data);
+ void RLCD_Sendbuffera(uint8_t *Data, int len);
+ void RLCD_Reset(void);
+ static void Lvgl_flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p);
+
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy,spi_display_config_t spiconfig,spi_host_device_t spi_host = SPI3_HOST);
+ ~CustomLcdDisplay();
+ void RLCD_Init();
+ void RLCD_ColorClear(uint8_t color);
+ void RLCD_Display();
+ void RLCD_SetPixel(uint16_t x, uint16_t y, uint8_t color);
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/waveshare-s3-rlcd-4.2.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/waveshare-s3-rlcd-4.2.cc
new file mode 100644
index 0000000..faa89ca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-rlcd-4.2/waveshare-s3-rlcd-4.2.cc
@@ -0,0 +1,170 @@
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_log.h>
+#include "custom_lcd_display.h"
+#include "wifi_board.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "codecs/box_audio_codec.h"
+#include "wifi_station.h"
+#include "mcp_server.h"
+#include "lvgl.h"
+#include "custom_lcd_display.h"
+
+#define TAG "waveshare_rlcd_4_2"
+
+class CustomBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ CustomLcdDisplay *display_;
+ adc_oneshot_unit_handle_t adc1_handle;
+ adc_cali_handle_t cali_handle;
+ bool vbat_status = 0;
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = ESP32_I2C_HOST;
+ i2c_bus_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeTools() {
+ auto& mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.network", "閲嶆柊閰嶇綉", PropertyList(),
+ [this](const PropertyList&) -> ReturnValue {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+ void InitializeLcdDisplay() {
+ spi_display_config_t spi_config = {};
+ spi_config.mosi = RLCD_MOSI_PIN;
+ spi_config.scl = RLCD_SCK_PIN;
+ spi_config.dc = RLCD_DC_PIN;
+ spi_config.cs = RLCD_CS_PIN;
+ spi_config.rst = RLCD_RST_PIN;
+ display_ = new CustomLcdDisplay(NULL, NULL, RLCD_WIDTH,RLCD_HEIGHT,DISPLAY_OFFSET_X,DISPLAY_OFFSET_Y,DISPLAY_MIRROR_X,DISPLAY_MIRROR_Y,DISPLAY_SWAP_XY,spi_config);
+ }
+
+ uint16_t BatterygetVoltage(void) {
+ static bool initialized = false;
+ static adc_oneshot_unit_handle_t adc_handle;
+ static adc_cali_handle_t cali_handle = NULL;
+ if (!initialized) {
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ };
+ adc_oneshot_new_unit(&init_config, &adc_handle);
+
+ adc_oneshot_chan_cfg_t ch_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_3, &ch_config);
+
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = ADC_UNIT_1,
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ if (adc_cali_create_scheme_curve_fitting(&cali_config, &cali_handle) == ESP_OK) {
+ initialized = true;
+ }
+ }
+
+ if (initialized) {
+ int raw_value = 0;
+ int raw_voltage = 0;
+ int voltage = 0; // mV
+ adc_oneshot_read(adc_handle, ADC_CHANNEL_3, &raw_value);
+ adc_cali_raw_to_voltage(cali_handle, raw_value, &raw_voltage);
+ voltage = raw_voltage * 3;
+ // ESP_LOGI(TAG, "voltage: %dmV", voltage);
+ return (uint16_t)voltage;
+ }
+
+ return 0;
+ }
+
+ uint8_t BatterygetPercent() {
+ int voltage = 0;
+ for (uint8_t i = 0; i < 10; i++) {
+ voltage += BatterygetVoltage();
+ }
+
+ voltage /= 10;
+ int percent = (-1 * voltage * voltage + 9016 * voltage - 19189000) / 10000;
+ percent = (percent > 100) ? 100 : (percent < 0) ? 0 : percent;
+ // ESP_LOGI(TAG, "voltage: %dmV, percentage: %d%%", voltage, percent);
+ return (uint8_t)percent;
+ }
+
+public:
+ CustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeButtons();
+ InitializeTools();
+ InitializeLcdDisplay();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ charging = false;
+ discharging = !charging;
+ level = (int)BatterygetPercent();
+
+ return true;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/README.md
new file mode 100644
index 0000000..82eecd7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/README.md
@@ -0,0 +1,48 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-Touch-AMOLED-1.32](https://www.waveshare.net/shop/ESP32-S3-Touch-AMOLED-1.32.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-Touch-AMOLED-1.32
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.h
new file mode 100644
index 0000000..6534a6f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.h
@@ -0,0 +1,47 @@
+#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_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_41
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_40
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_17
+#define PWR_EN_GPIO GPIO_NUM_18
+
+
+/*disp-qspi-lcd*/
+#define LCD_CS GPIO_NUM_10
+#define LCD_PCLK GPIO_NUM_11
+#define LCD_D0 GPIO_NUM_12
+#define LCD_D1 GPIO_NUM_13
+#define LCD_D2 GPIO_NUM_14
+#define LCD_D3 GPIO_NUM_15
+#define LCD_RST GPIO_NUM_8
+#define LCD_LIGHT (-1)
+
+#define EXAMPLE_LCD_H_RES 466
+#define EXAMPLE_LCD_V_RES 466
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.json
new file mode 100644
index 0000000..785d43d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-amoled-1.32",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc
new file mode 100644
index 0000000..031eaa0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.32/esp32-s3-touch-amoled-1.32.cc
@@ -0,0 +1,223 @@
+#include "application.h"
+#include "button.h"
+#include "codecs/es8311_audio_codec.h"
+#include "config.h"
+#include "wifi_board.h"
+
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+#include "lvgl.h"
+#include "mcp_server.h"
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#define TAG "waveshare_s3_amoled_1_32"
+
+static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xFE, (uint8_t[]) {0x00}, 1, 0},
+ {0xC4, (uint8_t[]) {0x80}, 1, 0},
+ {0x3A, (uint8_t[]) {0x55}, 1, 0}, // 0x55 for RGB565, 0x77 for RGB888
+ {0x35, (uint8_t[]) {0x00}, 1, 0},
+ {0x53, (uint8_t[]) {0x20}, 1, 0},
+ {0x51, (uint8_t[]) {0xFF}, 1, 0}, // Brightness
+ {0x63, (uint8_t[]) {0xFF}, 1, 0},
+ {0x2A, (uint8_t[]) {0x00, 0x06, 0x01, 0xD7}, 4, 0},
+ {0x2B, (uint8_t[]) {0x00, 0x00, 0x01, 0xD1}, 4, 0},
+ {0x11, (uint8_t[]) {0x00}, 0, 100},
+ {0x29, (uint8_t[]) {0x00}, 0, 0},
+};
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+ private:
+ esp_lcd_panel_io_handle_t io_handle_;
+
+ public:
+ static void my_draw_event_cb(lv_event_t *e) {
+ lv_area_t *area = (lv_area_t *) lv_event_get_param(e);
+
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // Round area for better performance
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ void SetMIRROR_XY(uint8_t mirror) {
+ uint32_t lcd_cmd = 0x36;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = mirror;
+ esp_lcd_panel_io_tx_param(io_handle_, lcd_cmd, ¶m, 1);
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) :
+ SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy),
+ io_handle_(io_handle) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ SetMIRROR_XY(0xC0); // Rotate 180 degrees - this is safe as it only operates on hardware
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_display_add_event_cb(display_, my_draw_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ lv_obj_invalidate(lv_screen_active());
+ }
+};
+
+class CustomBoard : public WifiBoard {
+ private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button pwr_button_;
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ CustomLcdDisplay *display_;
+ lv_indev_t *touch_indev = NULL;
+ i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = I2C_NUM_0;
+ i2c_bus_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void SetDispbacklight(uint8_t backlight) {
+ uint32_t lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = backlight;
+ esp_lcd_panel_io_tx_param(io_handle, lcd_cmd, ¶m, 1);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ pwr_button_.OnLongPress([this]() {
+ GetDisplay()->SetChatMessage("system", "OFF");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ gpio_set_level(PWR_EN_GPIO, 0);
+ });
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.data0_io_num = LCD_D0;
+ buscfg.data1_io_num = LCD_D1;
+ buscfg.sclk_io_num = LCD_PCLK;
+ buscfg.data2_io_num = LCD_D2;
+ buscfg.data3_io_num = LCD_D3;
+ buscfg.max_transfer_sz = EXAMPLE_LCD_H_RES * EXAMPLE_LCD_V_RES * sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS;
+ io_config.dc_gpio_num = -1;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 1000 * 1000;
+ io_config.trans_queue_depth = 8;
+ io_config.on_color_trans_done = NULL;
+ io_config.user_ctx = NULL;
+ io_config.lcd_cmd_bits = 32;
+ io_config.lcd_param_bits = 8;
+ io_config.flags.quad_mode = true;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle));
+
+ sh8601_vendor_config_t vendor_config = {};
+ vendor_config.init_cmds = lcd_init_cmds;
+ vendor_config.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]);
+ vendor_config.flags.use_qspi_interface = 1;
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &panel_handle));
+ esp_lcd_panel_set_gap(panel_handle, 0x06, 0x00);
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+
+ display_ = new CustomLcdDisplay(io_handle, panel_handle, EXAMPLE_LCD_H_RES, EXAMPLE_LCD_V_RES, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.setbacklight", "璁剧疆灞忓箷浜害", PropertyList({Property("level", kPropertyTypeInteger, 0, 255)}), [this](const PropertyList &properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI("setbacklight", "%d", level);
+ SetDispbacklight(level);
+ return true;
+ });
+
+ mcp_server.AddTool("self.disp.network", "閲嶆柊閰嶇綉", PropertyList(), [this](const PropertyList &) -> ReturnValue {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+ void CheckPowerKeyState() {
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = (0x1ULL << PWR_EN_GPIO);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+ gpio_set_level(PWR_EN_GPIO, 1);
+ do {
+ vTaskDelay(pdMS_TO_TICKS(10));
+ } while (!gpio_get_level(PWR_BUTTON_GPIO));
+ }
+
+ public:
+ CustomBoard() : boot_button_(BOOT_BUTTON_GPIO), pwr_button_(PWR_BUTTON_GPIO) {
+ CheckPowerKeyState();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec *GetAudioCodec() override {
+ static Es8311AudioCodec audio_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_;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/README.md
new file mode 100644
index 0000000..24223c4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/README.md
@@ -0,0 +1,48 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-Touch-AMOLED-1.32](https://www.waveshare.net/shop/ESP32-S3-Touch-AMOLED-1.43C.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-Touch-AMOLED-1.43C
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.h
new file mode 100644
index 0000000..7eb128b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.h
@@ -0,0 +1,49 @@
+#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_INPUT_REFERENCE true
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_40
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_42
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_41
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define LCD_D0_PIN GPIO_NUM_9
+#define LCD_D1_PIN GPIO_NUM_10
+#define LCD_D2_PIN GPIO_NUM_11
+#define LCD_D3_PIN GPIO_NUM_12
+#define LCD_CS_PIN GPIO_NUM_15
+#define LCD_SCK_PIN GPIO_NUM_14
+#define LCD_RST_PIN GPIO_NUM_13
+#define LCD_TE_PIN GPIO_NUM_8
+
+#define TP_RST_PIN GPIO_NUM_16
+#define TP_INT_PIN GPIO_NUM_17
+
+
+#define LCD_WIDTH 466
+#define LCD_HEIGHT 466
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.json
new file mode 100644
index 0000000..a2e0cca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-amoled-1.43c",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/esp32-s3-touch-amoled-1.43c.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/esp32-s3-touch-amoled-1.43c.cc
new file mode 100644
index 0000000..e383d63
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.43c/esp32-s3-touch-amoled-1.43c.cc
@@ -0,0 +1,203 @@
+#include "application.h"
+#include "button.h"
+#include "codecs/box_audio_codec.h"
+#include "config.h"
+#include "wifi_board.h"
+
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+#include "lvgl.h"
+#include "mcp_server.h"
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+
+#define TAG "waveshare_s3_amoled_1_43c"
+
+static const sh8601_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xFE, (uint8_t []){0x00}, 1, 0},
+ {0xC4, (uint8_t []){0x80}, 1, 0},
+ {0x3A, (uint8_t []){0x55}, 1, 0},
+ {0x35, (uint8_t []){0x00}, 1, 0},
+ {0x53, (uint8_t []){0x20}, 1, 0},
+ {0x51, (uint8_t []){0xFF}, 1, 0},
+ {0x36, (uint8_t []){0xC0}, 1, 0},
+ {0x63, (uint8_t []){0xFF}, 1, 0},
+ {0x2A, (uint8_t []){0x00,0x06,0x01,0xD7}, 4, 0},
+ {0x2B, (uint8_t []){0x00,0x00,0x01,0xD1}, 4, 0},
+ {0x11, (uint8_t []){0x00}, 0, 100},
+ {0x29, (uint8_t []){0x00}, 0, 0},
+};
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+ private:
+ esp_lcd_panel_io_handle_t io_handle_;
+
+ public:
+ static void my_draw_event_cb(lv_event_t *e) {
+ lv_area_t *area = (lv_area_t *) lv_event_get_param(e);
+
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // Round area for better performance
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ void SetMIRROR_XY(uint8_t mirror) {
+ uint32_t lcd_cmd = 0x36;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = mirror;
+ esp_lcd_panel_io_tx_param(io_handle_, lcd_cmd, ¶m, 1);
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) :
+ SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy),
+ io_handle_(io_handle) {
+ SetMIRROR_XY(0xC0); // Rotate 180 degrees - this is safe as it only operates on hardware
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+ DisplayLockGuard lock(this);
+ lv_display_add_event_cb(display_, my_draw_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ lv_obj_invalidate(lv_screen_active());
+ }
+};
+
+class CustomBoard : public WifiBoard {
+ private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_panel_io_handle_t io_handle = NULL;
+ CustomLcdDisplay *display_;
+ lv_indev_t *touch_indev = NULL;
+ i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
+
+ void InitializeI2c() {
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = I2C_NUM_0;
+ i2c_bus_cfg.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void SetDispbacklight(uint8_t backlight) {
+ uint32_t lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= 0x02 << 24;
+ uint8_t param = backlight;
+ esp_lcd_panel_io_tx_param(io_handle, lcd_cmd, ¶m, 1);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto &app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.data0_io_num = LCD_D0_PIN;
+ buscfg.data1_io_num = LCD_D1_PIN;
+ buscfg.sclk_io_num = LCD_SCK_PIN;
+ buscfg.data2_io_num = LCD_D2_PIN;
+ buscfg.data3_io_num = LCD_D3_PIN;
+ buscfg.max_transfer_sz = LCD_WIDTH * LCD_HEIGHT * sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = LCD_CS_PIN;
+ io_config.dc_gpio_num = -1;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 40 * 1000 * 1000;
+ io_config.trans_queue_depth = 8;
+ io_config.on_color_trans_done = NULL;
+ io_config.user_ctx = NULL;
+ io_config.lcd_cmd_bits = 32;
+ io_config.lcd_param_bits = 8;
+ io_config.flags.quad_mode = true;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &io_handle));
+
+ sh8601_vendor_config_t vendor_config = {};
+ vendor_config.init_cmds = lcd_init_cmds;
+ vendor_config.init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]);
+ vendor_config.flags.use_qspi_interface = 1;
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = LCD_RST_PIN;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(io_handle, &panel_config, &panel_handle));
+ esp_lcd_panel_set_gap(panel_handle, 0x08, 0x00);
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+
+ display_ = new CustomLcdDisplay(io_handle, panel_handle, LCD_WIDTH, LCD_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.disp.setbacklight", "璁剧疆灞忓箷浜害", PropertyList({Property("level", kPropertyTypeInteger, 0, 255)}), [this](const PropertyList &properties) -> ReturnValue {
+ int level = properties["level"].value<int>();
+ ESP_LOGI("setbacklight", "%d", level);
+ SetDispbacklight(level);
+ return true;
+ });
+
+ mcp_server.AddTool("self.disp.network", "閲嶆柊閰嶇綉", PropertyList(), [this](const PropertyList &) -> ReturnValue {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+ public:
+ CustomBoard() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display *GetDisplay() override {
+ return display_;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/README.md
new file mode 100644
index 0000000..5f99392
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/README.md
@@ -0,0 +1,15 @@
+# Waveshare ESP32-S3-Touch-AMOLED-1.75
+
+
+[ESP32-S3-Touch-AMOLED-1.75](https://www.waveshare.com/esp32-s3-touch-amoled-1.75.htm) ESP32-S3-Touch-AMOLED-1.75 is a high performance, highly integrated microcontroller development board designed by Waveshare.\
+In the smaller form, the 1.75-inch capacitive HD AMOLED screen, highly integrated power management chip, six-axis sensor (three-axis accelerometer and three-axis gyroscope), RTC, low-power audio codec chip and echo cancellation circuit are mounted on the board, which is convenient for development and embedding into the product.
+
+
+## images
+
+| [ESP32-S3-Touch-AMOLED-1.75](https://www.waveshare.com/esp32-s3-touch-amoled-1.75.htm) |
+|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| <img style="width: 150px; height: auto; display: block; margin: 0 auto;" src= "https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/e/s/esp32-s3-touch-amoled-1.75-1.jpg"> |
+| <img style="width: 150px; height: auto; display: block; margin: 0 auto;" src= "https://www.waveshare.com/media/catalog/product/cache/1/image/560x560/9df78eab33525d08d6e5fb8d27136e95/e/s/esp32-s3-touch-amoled-1.75-b-3.jpg"> |
+| <img style="width: 150px; height: auto; display: block; margin: 0 auto;" src= "https://www.waveshare.com/media/catalog/product/cache/1/image/560x560/9df78eab33525d08d6e5fb8d27136e95/e/s/esp32-s3-touch-amoled-1.75-b-1.jpg"> |
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.h
new file mode 100644
index 0000000..9bfe026
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.h
@@ -0,0 +1,57 @@
+#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_INPUT_REFERENCE true
+#if CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_39
+#define PIN_NUM_TOUCH_RST GPIO_NUM_40
+#define PIN_NUM_TOUCH_INT GPIO_NUM_11
+#elif CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_1
+#define PIN_NUM_TOUCH_RST GPIO_NUM_2
+#define PIN_NUM_TOUCH_INT GPIO_NUM_11
+#endif
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12
+#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_38
+#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4
+#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5
+#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6
+#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7
+
+#define DISPLAY_WIDTH 466
+#define DISPLAY_HEIGHT 466
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.json
new file mode 100644
index 0000000..86d8785
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/config.json
@@ -0,0 +1,22 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-amoled-1.75",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75=y",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ },
+ {
+ "name": "esp32-s3-touch-amoled-1.75c",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75C=y",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc
new file mode 100644
index 0000000..ed24fda
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.75/esp32-s3-touch-amoled-1.75.cc
@@ -0,0 +1,370 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_co5300.h"
+
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "esp_io_expander_tca9554.h"
+#include "settings.h"
+
+#include <esp_lcd_touch_cst9217.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32s3TouchAMOLED1inch75"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x01);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x03ULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const co5300_lcd_init_cmd_t vendor_specific_init[] = {
+ // set display to qspi mode
+ {0xFE, (uint8_t[]){0x20}, 1, 0},
+ {0x19, (uint8_t[]){0x10}, 1, 0},
+ {0x1C, (uint8_t[]){0xA0}, 1, 0},
+
+ {0xFE, (uint8_t[]){0x00}, 1, 0},
+ {0xC4, (uint8_t[]){0x80}, 1, 0},
+ {0x3A, (uint8_t[]){0x55}, 1, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x53, (uint8_t[]){0x20}, 1, 0},
+ {0x51, (uint8_t[]){0xFF}, 1, 0},
+ {0x63, (uint8_t[]){0xFF}, 1, 0},
+ {0x2A, (uint8_t[]){0x00, 0x06, 0x01, 0xD7}, 4, 0},
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xD1}, 4, 600},
+ {0x11, NULL, 0, 600},
+ {0x29, NULL, 0, 0},
+};
+
+// 鍦╳aveshare_amoled_1_75绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ static void rounder_event_cb(lv_event_t* e) {
+ lv_area_t* area = (lv_area_t* )lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // round the start of coordinate down to the nearest 2M number
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ // round the end of coordinate up to the nearest 2N+1 number
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0);
+ lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
+
+protected:
+ esp_lcd_panel_io_handle_t panel_io_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ auto display = Board::GetInstance().GetDisplay();
+ DisplayLockGuard lock(display);
+ uint8_t data[1] = {((uint8_t)((255* brightness) / 100))};
+ int lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
+ esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
+ }
+};
+
+class WaveshareEsp32s3TouchAMOLED1inch75 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ CustomLcdDisplay* display_;
+ CustomBacklight* backlight_;
+ esp_io_expander_handle_t io_expander = NULL;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness(); });
+ power_save_timer_->OnShutdownRequest([this](){
+ pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander);
+ if (ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4, IO_EXPANDER_INPUT);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK;
+ buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0;
+ buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1;
+ buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2;
+ buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t);
+ buscfg.flags = SPICOMMON_BUSFLAG_QUAD;
+ 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();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = CO5300_PANEL_IO_QSPI_CONFIG(
+ EXAMPLE_PIN_NUM_LCD_CS,
+ nullptr,
+ nullptr);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const co5300_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(co5300_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ }};
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void* )&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_co5300(panel_io, &panel_config, &panel));
+ esp_lcd_panel_set_gap(panel, 0x06, 0);
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(panel_io);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH - 1,
+ .y_max = DISPLAY_HEIGHT - 1,
+ .rst_gpio_num = PIN_NUM_TOUCH_RST,
+ .int_gpio_num = PIN_NUM_TOUCH_INT,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 1,
+ .mirror_y = 1,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_CST9217_CONFIG();
+ tp_io_config.scl_speed_hz = 400* 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst9217(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32s3TouchAMOLED1inch75() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+#if CONFIG_BOARD_TYPE_WAVESHARE_ESP32_S3_TOUCH_AMOLED_1_75
+ InitializeTca9554();
+#endif
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeDisplay();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ return backlight_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging)
+ {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED1inch75);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.h
new file mode 100644
index 0000000..c904053
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.h
@@ -0,0 +1,41 @@
+#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_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12
+#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11
+#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4
+#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5
+#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6
+#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7
+#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_NC
+#define DISPLAY_WIDTH 368
+#define DISPLAY_HEIGHT 448
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.json
new file mode 100644
index 0000000..a9c4e7b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/config.json
@@ -0,0 +1,12 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-amoled-1.8",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc
new file mode 100644
index 0000000..90317f0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc
@@ -0,0 +1,352 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "esp_io_expander_tca9554.h"
+#include "settings.h"
+
+#include <esp_lcd_touch_ft5x06.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32s3TouchAMOLED1inch8"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x01);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x03ULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x44, (uint8_t[]){0x01, 0xD1}, 2, 0},
+ {0x35, (uint8_t[]){0x00}, 1, 0},
+ {0x53, (uint8_t[]){0x20}, 1, 10},
+ {0x2A, (uint8_t[]){0x00, 0x00, 0x01, 0x6F}, 4, 0},
+ {0x2B, (uint8_t[]){0x00, 0x00, 0x01, 0xBF}, 4, 0},
+ {0x51, (uint8_t[]){0x00}, 1, 10},
+ {0x29, (uint8_t[]){0x00}, 0, 10}
+};
+
+// 鍦╳aveshare_amoled_1_8绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
+
+protected:
+ esp_lcd_panel_io_handle_t panel_io_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ auto display = Board::GetInstance().GetDisplay();
+ DisplayLockGuard lock(display);
+ uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))};
+ int lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
+ esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
+ }
+};
+
+class WaveshareEsp32s3TouchAMOLED1inch8 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ CustomLcdDisplay* display_;
+ CustomBacklight* backlight_;
+ esp_io_expander_handle_t io_expander = NULL;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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 InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(codec_i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 |IO_EXPANDER_PIN_NUM_2, IO_EXPANDER_OUTPUT);
+ ret |= esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4, IO_EXPANDER_INPUT);
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 1);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 0);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1|IO_EXPANDER_PIN_NUM_2, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(codec_i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = GPIO_NUM_11;
+ buscfg.data0_io_num = GPIO_NUM_4;
+ buscfg.data1_io_num = GPIO_NUM_5;
+ buscfg.data2_io_num = GPIO_NUM_6;
+ buscfg.data3_io_num = GPIO_NUM_7;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
+ buscfg.flags = SPICOMMON_BUSFLAG_QUAD;
+ 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 InitializeSH8601Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG(
+ EXAMPLE_PIN_NUM_LCD_CS,
+ nullptr,
+ nullptr
+ );
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const sh8601_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t),
+ .flags ={
+ .use_qspi_interface = 1,
+ }
+ };
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = GPIO_NUM_NC;
+ panel_config.flags.reset_active_high = 1,
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void *)&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(panel_io);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_21,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(codec_i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32s3TouchAMOLED1inch8() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeTca9554();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeSH8601Display();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ 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 {
+ return backlight_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED1inch8);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/README.md
new file mode 100644
index 0000000..aa8bbe8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/README.md
@@ -0,0 +1,11 @@
+# Waveshare ESP32-S3-Touch-AMOLED-2.06
+
+
+[ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) is a high-performance, wearable watch-style development board designed by Waveshare. Based on the ESP32-S3R8 microcontroller, it integrates a 2.06inch AMOLED capacitive touch display, 6-axis IMU, RTC chip, audio codec chip, power management IC, and so on. Comes with a custom-designed case with a smartwatch-like appearance, making it ideal for prototyping and functional verification of wearable applications.
+
+## images
+
+| [ESP32-S3-Touch-AMOLED-2.06](https://www.waveshare.com/esp32-s3-touch-amoled-2.06.htm) |
+|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
+| <img style="width: 150px; height: auto; display: block; margin: 0 auto;" src= "https://www.waveshare.com/media/catalog/product/cache/1/image/800x800/9df78eab33525d08d6e5fb8d27136e95/e/s/esp32-s3-touch-amoled-2.06-1.jpg"> |
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.h
new file mode 100644
index 0000000..6ad75a7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.h
@@ -0,0 +1,43 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_41
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_42
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_40
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12
+#define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11
+#define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4
+#define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5
+#define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6
+#define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7
+#define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_8
+#define DISPLAY_WIDTH 410
+#define DISPLAY_HEIGHT 502
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.json
new file mode 100644
index 0000000..7393071
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-amoled-2.06",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc
new file mode 100644
index 0000000..8dafd1d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-amoled-2.06/esp32-s3-touch-amoled-2.06.cc
@@ -0,0 +1,363 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_sh8601.h"
+
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "settings.h"
+
+#include <esp_lcd_touch_ft5x06.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32s3TouchAMOLED2inch06"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+ WriteReg(0x93, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x03);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x03ULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const sh8601_lcd_init_cmd_t vendor_specific_init[] = {
+ // set display to qspi mode
+ {0x11, (uint8_t []){0x00}, 0, 120},
+ {0xC4, (uint8_t []){0x80}, 1, 0},
+ {0x44, (uint8_t []){0x01, 0xD1}, 2, 0},
+ {0x35, (uint8_t []){0x00}, 1, 0},
+ {0x53, (uint8_t []){0x20}, 1, 10},
+ {0x63, (uint8_t []){0xFF}, 1, 10},
+ {0x51, (uint8_t []){0x00}, 1, 10},
+ {0x2A, (uint8_t []){0x00,0x16,0x01,0xAF}, 4, 0},
+ {0x2B, (uint8_t []){0x00,0x00,0x01,0xF5}, 4, 0},
+ {0x29, (uint8_t []){0x00}, 0, 10},
+ {0x51, (uint8_t []){0xFF}, 1, 0},
+};
+
+// 鍦╳aveshare_amoled_2_06绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ static void rounder_event_cb(lv_event_t* e) {
+ lv_area_t* area = (lv_area_t* )lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+
+ uint16_t y1 = area->y1;
+ uint16_t y2 = area->y2;
+
+ // round the start of coordinate down to the nearest 2M number
+ area->x1 = (x1 >> 1) << 1;
+ area->y1 = (y1 >> 1) << 1;
+ // round the end of coordinate up to the nearest 2N+1 number
+ area->x2 = ((x2 >> 1) << 1) + 1;
+ area->y2 = ((y2 >> 1) << 1) + 1;
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES* 0.1, 0);
+ lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES* 0.1, 0);
+ lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ }
+};
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {}
+
+protected:
+ esp_lcd_panel_io_handle_t panel_io_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ auto display = Board::GetInstance().GetDisplay();
+ DisplayLockGuard lock(display);
+ uint8_t data[1] = {((uint8_t)((255* brightness) / 100))};
+ int lcd_cmd = 0x51;
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_WRITE_CMD << 24;
+ esp_lcd_panel_io_tx_param(panel_io_, lcd_cmd, &data, sizeof(data));
+ }
+};
+
+class WaveshareEsp32s3TouchAMOLED2inch06 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ CustomLcdDisplay* display_;
+ CustomBacklight* backlight_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness(); });
+ power_save_timer_->OnShutdownRequest([this](){
+ pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.sclk_io_num = EXAMPLE_PIN_NUM_LCD_PCLK;
+ buscfg.data0_io_num = EXAMPLE_PIN_NUM_LCD_DATA0;
+ buscfg.data1_io_num = EXAMPLE_PIN_NUM_LCD_DATA1;
+ buscfg.data2_io_num = EXAMPLE_PIN_NUM_LCD_DATA2;
+ buscfg.data3_io_num = EXAMPLE_PIN_NUM_LCD_DATA3;
+ buscfg.max_transfer_sz = DISPLAY_WIDTH* DISPLAY_HEIGHT* sizeof(uint16_t);
+ buscfg.flags = SPICOMMON_BUSFLAG_QUAD;
+ 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();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeSH8601Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = SH8601_PANEL_IO_QSPI_CONFIG(
+ EXAMPLE_PIN_NUM_LCD_CS,
+ nullptr,
+ nullptr);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ const sh8601_vendor_config_t vendor_config = {
+ .init_cmds = &vendor_specific_init[0],
+ .init_cmds_size = sizeof(vendor_specific_init) / sizeof(sh8601_lcd_init_cmd_t),
+ .flags = {
+ .use_qspi_interface = 1,
+ }};
+
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = EXAMPLE_PIN_NUM_LCD_RST;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = (void* )&vendor_config;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_sh8601(panel_io, &panel_config, &panel));
+ esp_lcd_panel_set_gap(panel, 0x16, 0);
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, false);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ backlight_ = new CustomBacklight(panel_io);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH - 1,
+ .y_max = DISPLAY_HEIGHT - 1,
+ .rst_gpio_num = GPIO_NUM_9,
+ .int_gpio_num = GPIO_NUM_38,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400* 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32s3TouchAMOLED2inch06() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeSH8601Display();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ return backlight_;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging)
+ {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3TouchAMOLED2inch06);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/README.md
new file mode 100644
index 0000000..0919b9d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/README.md
@@ -0,0 +1,4 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-Touch-LCD-1.46銆丒SP32-S3-Touch-LCD-1.46B
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.46.htm
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.46B.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.h
new file mode 100644
index 0000000..b1bd2d9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.h
@@ -0,0 +1,70 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_6
+#define PWR_Control_PIN GPIO_NUM_7
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38
+
+#define I2C_SCL_IO GPIO_NUM_10
+#define I2C_SDA_IO GPIO_NUM_11
+
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+
+#define DISPLAY_WIDTH 412
+#define DISPLAY_HEIGHT 412
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define QSPI_LCD_H_RES (412)
+#define QSPI_LCD_V_RES (412)
+#define QSPI_LCD_BIT_PER_PIXEL (16)
+
+#define QSPI_LCD_HOST SPI2_HOST
+#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40
+#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21
+#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46
+#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45
+#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42
+#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41
+#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC
+#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define TP_PORT (I2C_NUM_1)
+#define TP_PIN_NUM_SDA (I2C_SDA_IO)
+#define TP_PIN_NUM_SCL (I2C_SCL_IO)
+#define TP_PIN_NUM_RST (GPIO_NUM_NC)
+#define TP_PIN_NUM_INT (GPIO_NUM_4)
+
+#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define TAIJIPI_SPD2010_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
+ { \
+ .data0_io_num = d0, \
+ .data1_io_num = d1, \
+ .sclk_io_num = sclk, \
+ .data2_io_num = d2, \
+ .data3_io_num = d3, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.json
new file mode 100644
index 0000000..b5006f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/config.json
@@ -0,0 +1,10 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-1.46",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc
new file mode 100644
index 0000000..ea8b1b4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc
@@ -0,0 +1,245 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_spd2010.h>
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+#include "lcd_display.h"
+#include <iot_button.h>
+
+#define TAG "waveshare_lcd_1_46"
+
+// 鍦╳aveshare_lcd_1_46绫讳箣鍓嶆坊鍔犳柊鐨勬樉绀虹被
+class CustomLcdDisplay : public SpiLcdDisplay {
+public:
+ static void rounder_event_cb(lv_event_t * e) {
+ lv_area_t * area = (lv_area_t *)lv_event_get_param(e);
+ uint16_t x1 = area->x1;
+ uint16_t x2 = area->x2;
+
+ area->x1 = (x1 >> 2) << 2; // round the start of coordinate down to the nearest 4M number
+ area->x2 = ((x2 >> 2) << 2) + 3; // round the end of coordinate up to the nearest 4N+3 number
+ }
+
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ // Note: UI customization should be done in SetupUI(), not in constructor
+ // to ensure lvgl objects are created before accessing them
+ }
+
+ virtual void SetupUI() override {
+ // Call parent SetupUI() first to create all lvgl objects
+ SpiLcdDisplay::SetupUI();
+
+ DisplayLockGuard lock(this);
+ lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL);
+ }
+};
+
+class CustomBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ button_handle_t boot_btn, pwr_btn;
+ button_driver_t* boot_btn_driver_ = nullptr;
+ button_driver_t* pwr_btn_driver_ = nullptr;
+ static CustomBoard* instance_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = I2C_SDA_IO,
+ .scl_io_num = I2C_SCL_IO,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+
+ // uint32_t input_level_mask = 0;
+ // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍏�
+ // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 鑾峰彇寮曡剼 EXIO0 鍜� EXIO1 鐨勭數骞崇姸鎬�,瀛樻斁鍦� input_level_mask 涓�
+
+ // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO2 鍜� EXIO3 妯″紡涓鸿緭鍑�
+ // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 灏嗗紩鑴氱數骞宠缃负 1
+ // ret = esp_io_expander_print_state(io_expander); // 鎵撳嵃寮曡剼鐘舵��
+
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍑�
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+
+ const spi_bus_config_t bus_config = TAIJIPI_SPD2010_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK,
+ QSPI_PIN_NUM_LCD_DATA0,
+ QSPI_PIN_NUM_LCD_DATA1,
+ QSPI_PIN_NUM_LCD_DATA2,
+ QSPI_PIN_NUM_LCD_DATA3,
+ QSPI_LCD_H_RES * 80 * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSpd2010Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGI(TAG, "Install panel IO");
+
+ const esp_lcd_panel_io_spi_config_t io_config = SPD2010_PANEL_IO_QSPI_CONFIG(QSPI_PIN_NUM_LCD_CS, NULL, NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io));
+
+ ESP_LOGI(TAG, "Install SPD2010 panel driver");
+
+ spd2010_vendor_config_t vendor_config = {
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = QSPI_PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h`
+ .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18)
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_spd2010(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_disp_on_off(panel, true);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtonsCustom() {
+ gpio_reset_pin(BOOT_BUTTON_GPIO);
+ gpio_set_direction(BOOT_BUTTON_GPIO, GPIO_MODE_INPUT);
+ gpio_reset_pin(PWR_BUTTON_GPIO);
+ gpio_set_direction(PWR_BUTTON_GPIO, GPIO_MODE_INPUT);
+ gpio_reset_pin(PWR_Control_PIN);
+ gpio_set_direction(PWR_Control_PIN, GPIO_MODE_OUTPUT);
+ // gpio_set_level(PWR_Control_PIN, false);
+ gpio_set_level(PWR_Control_PIN, true);
+ }
+
+ void InitializeButtons() {
+ instance_ = this;
+ InitializeButtonsCustom();
+
+ // Boot Button
+ button_config_t boot_btn_config = {
+ .long_press_time = 2000,
+ .short_press_time = 0
+ };
+ boot_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ boot_btn_driver_->enable_power_save = false;
+ boot_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !gpio_get_level(BOOT_BUTTON_GPIO);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&boot_btn_config, boot_btn_driver_, &boot_btn));
+ iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<CustomBoard*>(usr_data);
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }, this);
+ iot_button_register_cb(boot_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ // 闀挎寜鏃犲鐞�
+ }, this);
+
+ // Power Button
+ button_config_t pwr_btn_config = {
+ .long_press_time = 5000,
+ .short_press_time = 0
+ };
+ pwr_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ pwr_btn_driver_->enable_power_save = false;
+ pwr_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !gpio_get_level(PWR_BUTTON_GPIO);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&pwr_btn_config, pwr_btn_driver_, &pwr_btn));
+ iot_button_register_cb(pwr_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ // 鐭寜鏃犲鐞�
+ }, this);
+ iot_button_register_cb(pwr_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<CustomBoard*>(usr_data);
+ if(self->GetBacklight()->brightness() > 0) {
+ self->GetBacklight()->SetBrightness(0);
+ gpio_set_level(PWR_Control_PIN, false);
+ }
+ else {
+ self->GetBacklight()->RestoreBrightness();
+ gpio_set_level(PWR_Control_PIN, true);
+ }
+ }, this);
+ }
+
+public:
+ CustomBoard() {
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeSpi();
+ InitializeSpd2010Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_LEFT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH
+
+ 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(CustomBoard);
+
+CustomBoard* CustomBoard::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/README.md
new file mode 100644
index 0000000..bb15826
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/README.md
@@ -0,0 +1,49 @@
+# 浜у搧閾炬帴
+
+[寰洩鐢靛瓙 ESP32-S3-Touch-LCD-1.54](https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.54.htm)
+[寰洩鐢靛瓙 ESP32-S3-LCD-1.54](https://www.waveshare.net/shop/ESP32-S3-LCD-1.54.htm)
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+Xiaozhi Assistant -> Board Type -> Waveshare ESP32-S3-Touch-LCD-1.54
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.h
new file mode 100644
index 0000000..1625b26
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.h
@@ -0,0 +1,56 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define PWR_BUTTON_GPIO GPIO_NUM_5
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_4
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_0
+
+#define DISPLAY_SPI_MODE 3
+#define DISPLAY_CS_PIN GPIO_NUM_21
+#define DISPLAY_MOSI_PIN GPIO_NUM_39
+#define DISPLAY_MISO_PIN GPIO_NUM_NC
+#define DISPLAY_CLK_PIN GPIO_NUM_38
+#define DISPLAY_DC_PIN GPIO_NUM_45
+#define DISPLAY_RST_PIN GPIO_NUM_40
+
+#define TOOUCH_INT_PIN GPIO_NUM_48
+#define TOOUCH_RST_PIN GPIO_NUM_47
+
+#define BATTERY_EN_PIN GPIO_NUM_2
+#define BATTERY_ADC_PIN GPIO_NUM_1
+#define BATTERY_CHARGING_PIN GPIO_NUM_3
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_INVERT_COLOR true
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.json
new file mode 100644
index 0000000..ec065ca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-1.54",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/esp32-s3-touch-lcd-1.54.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/esp32-s3-touch-lcd-1.54.cc
new file mode 100644
index 0000000..bbb1876
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/esp32-s3-touch-lcd-1.54.cc
@@ -0,0 +1,355 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <wifi_station.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <esp_timer.h>
+
+#include "power_save_timer.h"
+#include "assets/lang_config.h"
+
+#include <esp_lcd_touch_cst816s.h>
+#include <esp_lvgl_port.h>
+#include "driver/gpio.h"
+
+#include "iot_button.h"
+
+#include "power_manager.h"
+#include "esp_ota_ops.h"
+
+#define TAG "waveshare_esp32_s3_touch_lcd_1_54"
+
+class CustomButton: public Button{
+public:
+ void OnPressDownDel(void) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_down_ = NULL;
+ iot_button_unregister_cb(button_handle_, BUTTON_PRESS_DOWN, nullptr);
+ }
+ void OnPressUpDel(void) {
+ if (button_handle_ == nullptr) {
+ return;
+ }
+ on_press_up_ = NULL;
+ iot_button_unregister_cb(button_handle_, BUTTON_PRESS_UP, nullptr);
+ }
+};
+
+
+
+class CustomBoard : public WifiBoard {
+private:
+ CustomButton pwr_button_;
+ CustomButton volume_up_button_;
+ CustomButton volume_down_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ PowerManager* power_manager_ = nullptr;
+ PowerSaveTimer* power_save_timer_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(BATTERY_CHARGING_PIN, BATTERY_ADC_PIN, BATTERY_EN_PIN);
+ power_manager_->PowerON();
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ auto display = GetDisplay();
+ display->SetChatMessage("system", "");
+ display->SetEmotion("sleepy");
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ auto display = GetDisplay();
+ display->SetChatMessage("system", "");
+ display->SetEmotion("neutral");
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ power_manager_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = DISPLAY_MISO_PIN;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeTouch()
+ {
+ esp_err_t ret = ESP_OK;
+ esp_lcd_touch_handle_t touch_handle;
+
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS,
+ .on_color_trans_done = 0,
+ .user_ctx = 0,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 0,
+ .flags =
+ {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 1,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = TOOUCH_RST_PIN,
+ .int_gpio_num = TOOUCH_INT_PIN,
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ ESP_LOGI(TAG, "Initialize touch controller CST816");
+ ret = esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &touch_handle);
+ if (ret == ESP_OK)
+ {
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = touch_handle,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+
+ pwr_button_.OnPressUp([this]() {
+ pwr_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ });
+ pwr_button_.OnLongPress([this]() {
+ printf("Power button long press\n");
+ // gpio_set_level(BATTERY_EN_PIN, 0);
+ if (power_manager_ != nullptr){
+ power_manager_->PowerOff();
+ }
+ });
+
+ pwr_button_.OnDoubleClick([this]() {
+ static uint8_t brightness_last = 0;
+ auto backlight = Board::GetInstance().GetBacklight();
+ if (backlight->brightness() == 0) {
+ brightness_last = 0;
+ if (brightness_last == 0) {
+ backlight->SetBrightness(50, true);
+ } else {
+ backlight->SetBrightness(brightness_last, true);
+ }
+ } else {
+ brightness_last = backlight->brightness();
+ backlight->SetBrightness(0);
+ }
+ });
+ pwr_button_.OnMultipleClick([this]() {
+ EnterWifiConfigMode();
+ }, 3);
+ pwr_button_.OnPressUpDel();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = Board::GetInstance().GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ Board::GetInstance().GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = Board::GetInstance().GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ Board::GetInstance().GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ volume_down_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "Reboot the device and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ CustomBoard() :
+ pwr_button_(PWR_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ true);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/power_manager.h
new file mode 100644
index 0000000..fd63cc0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.54/power_manager.h
@@ -0,0 +1,212 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include <esp_log.h>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include "esp_adc/adc_oneshot.h"
+#include "esp_adc/adc_cali.h"
+#include "esp_adc/adc_cali_scheme.h"
+
+#include <math.h>
+
+class PowerManager
+{
+private:
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ gpio_num_t bat_adc_pin_ = GPIO_NUM_NC;
+ gpio_num_t bat_power_pin_ = GPIO_NUM_NC;
+ adc_oneshot_unit_handle_t adc_handle_ = NULL;
+ adc_cali_handle_t adc_cali_handle_ = NULL;
+ adc_channel_t adc_channel_;
+ bool do_calibration = false;
+ bool adc_calibration_init(adc_unit_t unit, adc_channel_t channel, adc_atten_t atten, adc_cali_handle_t *out_handle)
+ {
+ adc_cali_handle_t handle = NULL;
+ esp_err_t ret = ESP_FAIL;
+ bool calibrated = false;
+
+ if (!calibrated)
+ {
+ ESP_LOGI("power_manager", "calibration scheme version is %s", "Curve Fitting");
+ adc_cali_curve_fitting_config_t cali_config = {
+ .unit_id = unit,
+ .chan = channel,
+ .atten = atten,
+ .bitwidth = ADC_BITWIDTH_DEFAULT,
+ };
+ ret = adc_cali_create_scheme_curve_fitting(&cali_config, &handle);
+ if (ret == ESP_OK)
+ {
+ calibrated = true;
+ }
+ }
+
+ *out_handle = handle;
+ if (ret == ESP_OK)
+ {
+ ESP_LOGI("power_manager", "Calibration Success");
+ }
+ else if (ret == ESP_ERR_NOT_SUPPORTED || !calibrated)
+ {
+ ESP_LOGW("power_manager", "eFuse not burnt, skip software calibration");
+ }
+ else
+ {
+ ESP_LOGE("power_manager", "Invalid arg or no memory");
+ }
+ return calibrated;
+ }
+
+public:
+ PowerManager(gpio_num_t charging_pin, gpio_num_t bat_adc_pin, gpio_num_t bat_power_pin) : charging_pin_(charging_pin),
+ bat_adc_pin_(bat_adc_pin),
+ bat_power_pin_(bat_power_pin)
+ {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ if (charging_pin_ != GPIO_NUM_NC)
+ {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = 1ULL << charging_pin_;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ gpio_config(&io_conf);
+ }
+ // 鍒濆鍖栫數姹犱娇鑳藉紩鑴�
+ if (bat_power_pin_ != GPIO_NUM_NC)
+ {
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = 1ULL << bat_power_pin_;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+ }
+
+ // 鍒濆鍖朼dc
+ if (bat_adc_pin_ != GPIO_NUM_NC)
+ {
+ adc_oneshot_unit_init_cfg_t init_config = {};
+ init_config.ulp_mode = ADC_ULP_MODE_DISABLE;
+ if (bat_adc_pin_ >= GPIO_NUM_1 && bat_adc_pin_ <= GPIO_NUM_10)
+ {
+ init_config.unit_id = ADC_UNIT_1;
+ adc_channel_ = (adc_channel_t)((int)bat_adc_pin_ - 1);
+ }
+ else if (bat_adc_pin_ >= GPIO_NUM_11 && bat_adc_pin_ <= GPIO_NUM_20)
+ {
+ init_config.unit_id = ADC_UNIT_2;
+ adc_channel_ = (adc_channel_t)((int)bat_adc_pin_ - 11);
+ }
+ else
+ {
+ return;
+ }
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+ adc_oneshot_chan_cfg_t config = {};
+ config.bitwidth = ADC_BITWIDTH_DEFAULT;
+ config.atten = ADC_ATTEN_DB_12;
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, adc_channel_, &config));
+ do_calibration = adc_calibration_init(init_config.unit_id, adc_channel_, config.atten, &adc_cali_handle_);
+ }
+ }
+
+ ~PowerManager()
+ {
+ if (adc_handle_)
+ {
+ ESP_ERROR_CHECK(adc_oneshot_del_unit(adc_handle_));
+ }
+ // if (adc_cali_handle_)
+ // {
+ // ESP_ERROR_CHECK(adc_cali_delete_scheme_line_fitting(adc_cali_handle_));
+ // }
+ }
+
+ int GetBatteryLevel(void)
+ {
+
+ int adc_raw = 0;
+ int voltage_int = 0;
+ const float voltage_float_threshold = 0.1f;
+ float voltage_float = 0.0f;
+ static float last_voltage_float = 0.0f;
+
+ static int last_battery_level = 0;
+
+ if (adc_handle_ != nullptr)
+ {
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, adc_channel_, &adc_raw));
+ if (do_calibration)
+ {
+ ESP_ERROR_CHECK(adc_cali_raw_to_voltage(adc_cali_handle_, adc_raw, &voltage_int));
+ voltage_float = (voltage_int / 1000.0f) * 3.0;
+
+ if (fabs(voltage_float - last_voltage_float) >= voltage_float_threshold)
+ {
+ last_voltage_float = voltage_float;
+ if (voltage_float < 3.52) {
+ last_battery_level = 1;
+ } else if (voltage_float < 3.64) {
+ last_battery_level = 20;
+ } else if (voltage_float < 3.76) {
+ last_battery_level = 40;
+ } else if (voltage_float < 3.88) {
+ last_battery_level = 60;
+ } else if (voltage_float < 4.0) {
+ last_battery_level = 80;
+ } else {
+ last_battery_level = 100;
+ }
+ }
+ return last_battery_level;
+ }
+ }
+ return 100;
+ }
+
+ bool IsCharging(void)
+ {
+ if (charging_pin_ != GPIO_NUM_NC)
+ {
+ return gpio_get_level(charging_pin_) == 0 ? true : false;
+ }
+ return false;
+ }
+
+ bool IsDischarging(void)
+ {
+ if (charging_pin_ != GPIO_NUM_NC)
+ {
+ return gpio_get_level(charging_pin_) == 1;
+ }
+ return true;
+ }
+
+ bool IsChargingDone(void)
+ {
+ if (GetBatteryLevel() == 100)
+ {
+ return true;
+ }
+ return false;
+ }
+ void PowerOff(void)
+ {
+ if (bat_power_pin_ != GPIO_NUM_NC)
+ {
+ gpio_set_level(bat_power_pin_, 0);
+ }
+ }
+ void PowerON(void)
+ {
+ if (bat_power_pin_ != GPIO_NUM_NC)
+ {
+ gpio_set_level(bat_power_pin_, 1);
+ }
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/README.md
new file mode 100644
index 0000000..f2d4377
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/README.md
@@ -0,0 +1,4 @@
+# Waveshare ESP32-S3-Touch-LCD-1.83
+
+
+[ESP32-S3-Touch-LCD-1.83](https://www.waveshare.com/esp32-s3-touch-lcd-1.83.htm) ESP32-S3-Touch-LCD-1.83 is a high-performance and highly integrated microcontroller development board designed by Waveshare. With a compact board size, it is equipped with a 1.83-inch capacitive LCD screen, a highly integrated power management chip, six-axis sensors (three-axis accelerometer and three-axis gyroscope), RTC and low-power audio codec chip, etc., making it convenient for development and easy to be embedded in products.
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.h
new file mode 100644
index 0000000..97bba44
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.h
@@ -0,0 +1,47 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_45
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_46
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_41
+
+#define DISPLAY_SPI_MODE 3
+#define DISPLAY_CS_PIN GPIO_NUM_5
+#define DISPLAY_MOSI_PIN GPIO_NUM_7
+#define DISPLAY_MISO_PIN GPIO_NUM_NC
+#define DISPLAY_CLK_PIN GPIO_NUM_6
+#define DISPLAY_DC_PIN GPIO_NUM_4
+#define DISPLAY_RST_PIN GPIO_NUM_38
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 284
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_40
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.json
new file mode 100644
index 0000000..0528ca3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-1.83",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc
new file mode 100644
index 0000000..f0d4b2e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.83/esp32-s3-touch-lcd-1.83.cc
@@ -0,0 +1,274 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "settings.h"
+
+#include <esp_lcd_touch_cst816s.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#define TAG "WaveshareEsp32s3TouchLCD1inch83"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x01);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+class WaveshareEsp32s3TouchLCD1inch83 : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ Display* display_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness(); });
+ power_save_timer_->OnShutdownRequest([this](){
+ pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = 0;
+ io_config.pclk_hz = 24 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ 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_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ esp_lcd_panel_disp_on_off(panel, true);
+ 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 InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH - 1,
+ .y_max = DISPLAY_HEIGHT - 1,
+ .rst_gpio_num = GPIO_NUM_39,
+ .int_gpio_num = GPIO_NUM_13,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_CST816S_ADDRESS,
+ .on_color_trans_done = 0,
+ .user_ctx = 0,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 0,
+ .flags =
+ {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 1,
+ },
+ };
+ tp_io_config.scl_speed_hz = 400* 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_cst816s(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32s3TouchLCD1inch83() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeDisplay();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging)
+ {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3TouchLCD1inch83);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/README.md
new file mode 100644
index 0000000..df8a905
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-Touch-LCD-1.85
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.85.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.h
new file mode 100644
index 0000000..7eff4c6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.h
@@ -0,0 +1,69 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_6
+#define PWR_Control_PIN GPIO_NUM_7
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38
+
+#define I2C_SCL_IO GPIO_NUM_10
+#define I2C_SDA_IO GPIO_NUM_11
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+
+#define DISPLAY_WIDTH 360
+#define DISPLAY_HEIGHT 360
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define QSPI_LCD_H_RES (360)
+#define QSPI_LCD_V_RES (360)
+#define QSPI_LCD_BIT_PER_PIXEL (16)
+
+#define QSPI_LCD_HOST SPI2_HOST
+#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40
+#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21
+#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46
+#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45
+#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42
+#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41
+#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC
+#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define TP_PORT (I2C_NUM_1)
+#define TP_PIN_NUM_SDA (GPIO_NUM_1)
+#define TP_PIN_NUM_SCL (GPIO_NUM_3)
+#define TP_PIN_NUM_RST (GPIO_NUM_NC)
+#define TP_PIN_NUM_INT (GPIO_NUM_4)
+
+#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
+ { \
+ .data0_io_num = d0, \
+ .data1_io_num = d1, \
+ .sclk_io_num = sclk, \
+ .data2_io_num = d2, \
+ .data3_io_num = d3, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.json
new file mode 100644
index 0000000..92485aa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/config.json
@@ -0,0 +1,10 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-1.85",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc
new file mode 100644
index 0000000..16d4e9a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc
@@ -0,0 +1,447 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_st77916.h>
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+
+#define TAG "waveshare_lcd_1_85"
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x0BULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const st77916_lcd_init_cmd_t vendor_specific_init_new[] = {
+ {0xF0, (uint8_t []){0x28}, 1, 0},
+ {0xF2, (uint8_t []){0x28}, 1, 0},
+ {0x73, (uint8_t []){0xF0}, 1, 0},
+ {0x7C, (uint8_t []){0xD1}, 1, 0},
+ {0x83, (uint8_t []){0xE0}, 1, 0},
+ {0x84, (uint8_t []){0x61}, 1, 0},
+ {0xF2, (uint8_t []){0x82}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x01}, 1, 0},
+ {0xF1, (uint8_t []){0x01}, 1, 0},
+ {0xB0, (uint8_t []){0x56}, 1, 0},
+ {0xB1, (uint8_t []){0x4D}, 1, 0},
+ {0xB2, (uint8_t []){0x24}, 1, 0},
+ {0xB4, (uint8_t []){0x87}, 1, 0},
+ {0xB5, (uint8_t []){0x44}, 1, 0},
+ {0xB6, (uint8_t []){0x8B}, 1, 0},
+ {0xB7, (uint8_t []){0x40}, 1, 0},
+ {0xB8, (uint8_t []){0x86}, 1, 0},
+ {0xBA, (uint8_t []){0x00}, 1, 0},
+ {0xBB, (uint8_t []){0x08}, 1, 0},
+ {0xBC, (uint8_t []){0x08}, 1, 0},
+ {0xBD, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x80}, 1, 0},
+ {0xC1, (uint8_t []){0x10}, 1, 0},
+ {0xC2, (uint8_t []){0x37}, 1, 0},
+ {0xC3, (uint8_t []){0x80}, 1, 0},
+ {0xC4, (uint8_t []){0x10}, 1, 0},
+ {0xC5, (uint8_t []){0x37}, 1, 0},
+ {0xC6, (uint8_t []){0xA9}, 1, 0},
+ {0xC7, (uint8_t []){0x41}, 1, 0},
+ {0xC8, (uint8_t []){0x01}, 1, 0},
+ {0xC9, (uint8_t []){0xA9}, 1, 0},
+ {0xCA, (uint8_t []){0x41}, 1, 0},
+ {0xCB, (uint8_t []){0x01}, 1, 0},
+ {0xD0, (uint8_t []){0x91}, 1, 0},
+ {0xD1, (uint8_t []){0x68}, 1, 0},
+ {0xD2, (uint8_t []){0x68}, 1, 0},
+ {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0},
+ {0xDD, (uint8_t []){0x4F}, 1, 0},
+ {0xDE, (uint8_t []){0x4F}, 1, 0},
+ {0xF1, (uint8_t []){0x10}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0},
+ {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0},
+ {0xF0, (uint8_t []){0x10}, 1, 0},
+ {0xF3, (uint8_t []){0x10}, 1, 0},
+ {0xE0, (uint8_t []){0x07}, 1, 0},
+ {0xE1, (uint8_t []){0x00}, 1, 0},
+ {0xE2, (uint8_t []){0x00}, 1, 0},
+ {0xE3, (uint8_t []){0x00}, 1, 0},
+ {0xE4, (uint8_t []){0xE0}, 1, 0},
+ {0xE5, (uint8_t []){0x06}, 1, 0},
+ {0xE6, (uint8_t []){0x21}, 1, 0},
+ {0xE7, (uint8_t []){0x01}, 1, 0},
+ {0xE8, (uint8_t []){0x05}, 1, 0},
+ {0xE9, (uint8_t []){0x02}, 1, 0},
+ {0xEA, (uint8_t []){0xDA}, 1, 0},
+ {0xEB, (uint8_t []){0x00}, 1, 0},
+ {0xEC, (uint8_t []){0x00}, 1, 0},
+ {0xED, (uint8_t []){0x0F}, 1, 0},
+ {0xEE, (uint8_t []){0x00}, 1, 0},
+ {0xEF, (uint8_t []){0x00}, 1, 0},
+ {0xF8, (uint8_t []){0x00}, 1, 0},
+ {0xF9, (uint8_t []){0x00}, 1, 0},
+ {0xFA, (uint8_t []){0x00}, 1, 0},
+ {0xFB, (uint8_t []){0x00}, 1, 0},
+ {0xFC, (uint8_t []){0x00}, 1, 0},
+ {0xFD, (uint8_t []){0x00}, 1, 0},
+ {0xFE, (uint8_t []){0x00}, 1, 0},
+ {0xFF, (uint8_t []){0x00}, 1, 0},
+ {0x60, (uint8_t []){0x40}, 1, 0},
+ {0x61, (uint8_t []){0x04}, 1, 0},
+ {0x62, (uint8_t []){0x00}, 1, 0},
+ {0x63, (uint8_t []){0x42}, 1, 0},
+ {0x64, (uint8_t []){0xD9}, 1, 0},
+ {0x65, (uint8_t []){0x00}, 1, 0},
+ {0x66, (uint8_t []){0x00}, 1, 0},
+ {0x67, (uint8_t []){0x00}, 1, 0},
+ {0x68, (uint8_t []){0x00}, 1, 0},
+ {0x69, (uint8_t []){0x00}, 1, 0},
+ {0x6A, (uint8_t []){0x00}, 1, 0},
+ {0x6B, (uint8_t []){0x00}, 1, 0},
+ {0x70, (uint8_t []){0x40}, 1, 0},
+ {0x71, (uint8_t []){0x03}, 1, 0},
+ {0x72, (uint8_t []){0x00}, 1, 0},
+ {0x73, (uint8_t []){0x42}, 1, 0},
+ {0x74, (uint8_t []){0xD8}, 1, 0},
+ {0x75, (uint8_t []){0x00}, 1, 0},
+ {0x76, (uint8_t []){0x00}, 1, 0},
+ {0x77, (uint8_t []){0x00}, 1, 0},
+ {0x78, (uint8_t []){0x00}, 1, 0},
+ {0x79, (uint8_t []){0x00}, 1, 0},
+ {0x7A, (uint8_t []){0x00}, 1, 0},
+ {0x7B, (uint8_t []){0x00}, 1, 0},
+ {0x80, (uint8_t []){0x48}, 1, 0},
+ {0x81, (uint8_t []){0x00}, 1, 0},
+ {0x82, (uint8_t []){0x06}, 1, 0},
+ {0x83, (uint8_t []){0x02}, 1, 0},
+ {0x84, (uint8_t []){0xD6}, 1, 0},
+ {0x85, (uint8_t []){0x04}, 1, 0},
+ {0x86, (uint8_t []){0x00}, 1, 0},
+ {0x87, (uint8_t []){0x00}, 1, 0},
+ {0x88, (uint8_t []){0x48}, 1, 0},
+ {0x89, (uint8_t []){0x00}, 1, 0},
+ {0x8A, (uint8_t []){0x08}, 1, 0},
+ {0x8B, (uint8_t []){0x02}, 1, 0},
+ {0x8C, (uint8_t []){0xD8}, 1, 0},
+ {0x8D, (uint8_t []){0x04}, 1, 0},
+ {0x8E, (uint8_t []){0x00}, 1, 0},
+ {0x8F, (uint8_t []){0x00}, 1, 0},
+ {0x90, (uint8_t []){0x48}, 1, 0},
+ {0x91, (uint8_t []){0x00}, 1, 0},
+ {0x92, (uint8_t []){0x0A}, 1, 0},
+ {0x93, (uint8_t []){0x02}, 1, 0},
+ {0x94, (uint8_t []){0xDA}, 1, 0},
+ {0x95, (uint8_t []){0x04}, 1, 0},
+ {0x96, (uint8_t []){0x00}, 1, 0},
+ {0x97, (uint8_t []){0x00}, 1, 0},
+ {0x98, (uint8_t []){0x48}, 1, 0},
+ {0x99, (uint8_t []){0x00}, 1, 0},
+ {0x9A, (uint8_t []){0x0C}, 1, 0},
+ {0x9B, (uint8_t []){0x02}, 1, 0},
+ {0x9C, (uint8_t []){0xDC}, 1, 0},
+ {0x9D, (uint8_t []){0x04}, 1, 0},
+ {0x9E, (uint8_t []){0x00}, 1, 0},
+ {0x9F, (uint8_t []){0x00}, 1, 0},
+ {0xA0, (uint8_t []){0x48}, 1, 0},
+ {0xA1, (uint8_t []){0x00}, 1, 0},
+ {0xA2, (uint8_t []){0x05}, 1, 0},
+ {0xA3, (uint8_t []){0x02}, 1, 0},
+ {0xA4, (uint8_t []){0xD5}, 1, 0},
+ {0xA5, (uint8_t []){0x04}, 1, 0},
+ {0xA6, (uint8_t []){0x00}, 1, 0},
+ {0xA7, (uint8_t []){0x00}, 1, 0},
+ {0xA8, (uint8_t []){0x48}, 1, 0},
+ {0xA9, (uint8_t []){0x00}, 1, 0},
+ {0xAA, (uint8_t []){0x07}, 1, 0},
+ {0xAB, (uint8_t []){0x02}, 1, 0},
+ {0xAC, (uint8_t []){0xD7}, 1, 0},
+ {0xAD, (uint8_t []){0x04}, 1, 0},
+ {0xAE, (uint8_t []){0x00}, 1, 0},
+ {0xAF, (uint8_t []){0x00}, 1, 0},
+ {0xB0, (uint8_t []){0x48}, 1, 0},
+ {0xB1, (uint8_t []){0x00}, 1, 0},
+ {0xB2, (uint8_t []){0x09}, 1, 0},
+ {0xB3, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0xD9}, 1, 0},
+ {0xB5, (uint8_t []){0x04}, 1, 0},
+ {0xB6, (uint8_t []){0x00}, 1, 0},
+ {0xB7, (uint8_t []){0x00}, 1, 0},
+
+ {0xB8, (uint8_t []){0x48}, 1, 0},
+ {0xB9, (uint8_t []){0x00}, 1, 0},
+ {0xBA, (uint8_t []){0x0B}, 1, 0},
+ {0xBB, (uint8_t []){0x02}, 1, 0},
+ {0xBC, (uint8_t []){0xDB}, 1, 0},
+ {0xBD, (uint8_t []){0x04}, 1, 0},
+ {0xBE, (uint8_t []){0x00}, 1, 0},
+ {0xBF, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x10}, 1, 0},
+ {0xC1, (uint8_t []){0x47}, 1, 0},
+ {0xC2, (uint8_t []){0x56}, 1, 0},
+ {0xC3, (uint8_t []){0x65}, 1, 0},
+ {0xC4, (uint8_t []){0x74}, 1, 0},
+ {0xC5, (uint8_t []){0x88}, 1, 0},
+ {0xC6, (uint8_t []){0x99}, 1, 0},
+ {0xC7, (uint8_t []){0x01}, 1, 0},
+ {0xC8, (uint8_t []){0xBB}, 1, 0},
+ {0xC9, (uint8_t []){0xAA}, 1, 0},
+ {0xD0, (uint8_t []){0x10}, 1, 0},
+ {0xD1, (uint8_t []){0x47}, 1, 0},
+ {0xD2, (uint8_t []){0x56}, 1, 0},
+ {0xD3, (uint8_t []){0x65}, 1, 0},
+ {0xD4, (uint8_t []){0x74}, 1, 0},
+ {0xD5, (uint8_t []){0x88}, 1, 0},
+ {0xD6, (uint8_t []){0x99}, 1, 0},
+ {0xD7, (uint8_t []){0x01}, 1, 0},
+ {0xD8, (uint8_t []){0xBB}, 1, 0},
+ {0xD9, (uint8_t []){0xAA}, 1, 0},
+ {0xF3, (uint8_t []){0x01}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0x21, (uint8_t []){0x00}, 1, 0},
+ {0x11, (uint8_t []){0x00}, 1, 120},
+ {0x29, (uint8_t []){0x00}, 1, 0},
+};
+class CustomBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ button_handle_t boot_btn, pwr_btn;
+ button_driver_t* boot_btn_driver_ = nullptr;
+ button_driver_t* pwr_btn_driver_ = nullptr;
+ static CustomBoard* instance_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = I2C_SDA_IO,
+ .scl_io_num = I2C_SCL_IO,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+
+ // uint32_t input_level_mask = 0;
+ // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍏�
+ // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 鑾峰彇寮曡剼 EXIO0 鍜� EXIO1 鐨勭數骞崇姸鎬�,瀛樻斁鍦� input_level_mask 涓�
+
+ // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO2 鍜� EXIO3 妯″紡涓鸿緭鍑�
+ // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 灏嗗紩鑴氱數骞宠缃负 1
+ // ret = esp_io_expander_print_state(io_expander); // 鎵撳嵃寮曡剼鐘舵��
+
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍑�
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+
+ const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK,
+ QSPI_PIN_NUM_LCD_DATA0,
+ QSPI_PIN_NUM_LCD_DATA1,
+ QSPI_PIN_NUM_LCD_DATA2,
+ QSPI_PIN_NUM_LCD_DATA3,
+ QSPI_LCD_H_RES * 80 * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));
+ }
+
+ void Initializest77916Display() {
+
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGI(TAG, "Install panel IO");
+
+ esp_lcd_panel_io_spi_config_t io_config = {
+ .cs_gpio_num = QSPI_PIN_NUM_LCD_CS,
+ .dc_gpio_num = -1,
+ .spi_mode = 0,
+ .pclk_hz = 3 * 1000 * 1000,
+ .trans_queue_depth = 10,
+ .on_color_trans_done = NULL,
+ .user_ctx = NULL,
+ .lcd_cmd_bits = 32,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .octal_mode = 0,
+ .quad_mode = 1,
+ .sio_mode = 0,
+ .lsb_first = 0,
+ .cs_high_active = 0,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io));
+
+ ESP_LOGI(TAG, "Install ST77916 panel driver");
+
+ st77916_vendor_config_t vendor_config = {
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+
+ printf("-------------------------------------- Version selection -------------------------------------- \r\n");
+ esp_err_t ret;
+ int lcd_cmd = 0x04;
+ uint8_t register_data[4];
+ size_t param_size = sizeof(register_data);
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_READ_CMD << 24; // Use the read opcode instead of write
+ ret = esp_lcd_panel_io_rx_param(panel_io, lcd_cmd, register_data, param_size);
+ if (ret == ESP_OK) {
+ printf("Register 0x04 data: %02x %02x %02x %02x\n", register_data[0], register_data[1], register_data[2], register_data[3]);
+ } else {
+ printf("Failed to read register 0x04, error code: %d\n", ret);
+ }
+ // panel_io_spi_del(io_handle);
+ io_config.pclk_hz = 80 * 1000 * 1000;
+ if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io) != ESP_OK){
+ printf("Failed to set LCD communication parameters -- SPI\r\n");
+ return ;
+ }
+ printf("LCD communication parameters are set successfully -- SPI\r\n");
+
+ // Check register values and configure accordingly
+ if (register_data[0] == 0x00 && register_data[1] == 0x7F && register_data[2] == 0x7F && register_data[3] == 0x7F) {
+ // Handle the case where the register data matches this pattern
+ printf("Vendor-specific initialization for case 1.\n");
+ }
+ else if (register_data[0] == 0x00 && register_data[1] == 0x02 && register_data[2] == 0x7F && register_data[3] == 0x7F) {
+ // Provide vendor-specific initialization commands if register data matches this pattern
+ vendor_config.init_cmds = vendor_specific_init_new;
+ vendor_config.init_cmds_size = sizeof(vendor_specific_init_new) / sizeof(st77916_lcd_init_cmd_t);
+ printf("Vendor-specific initialization for case 2.\n");
+ }
+ printf("------------------------------------- End of version selection------------------------------------- \r\n");
+
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = QSPI_PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h`
+ .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18)
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_disp_on_off(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 InitializeButtonsCustom() {
+ gpio_reset_pin(BOOT_BUTTON_GPIO);
+ gpio_set_direction(BOOT_BUTTON_GPIO, GPIO_MODE_INPUT);
+ gpio_reset_pin(PWR_BUTTON_GPIO);
+ gpio_set_direction(PWR_BUTTON_GPIO, GPIO_MODE_INPUT);
+ gpio_reset_pin(PWR_Control_PIN);
+ gpio_set_direction(PWR_Control_PIN, GPIO_MODE_OUTPUT);
+ // gpio_set_level(PWR_Control_PIN, false);
+ gpio_set_level(PWR_Control_PIN, true);
+ }
+ void InitializeButtons() {
+ instance_ = this;
+ InitializeButtonsCustom();
+
+ // Boot Button
+ button_config_t boot_btn_config = {
+ .long_press_time = 2000,
+ .short_press_time = 0
+ };
+ boot_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ boot_btn_driver_->enable_power_save = false;
+ boot_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !gpio_get_level(BOOT_BUTTON_GPIO);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&boot_btn_config, boot_btn_driver_, &boot_btn));
+ iot_button_register_cb(boot_btn, BUTTON_SINGLE_CLICK, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<CustomBoard*>(usr_data);
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ self->EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ }, this);
+
+ // Power Button
+ button_config_t pwr_btn_config = {
+ .long_press_time = 5000,
+ .short_press_time = 0
+ };
+ pwr_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t));
+ pwr_btn_driver_->enable_power_save = false;
+ pwr_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t {
+ return !gpio_get_level(PWR_BUTTON_GPIO);
+ };
+ ESP_ERROR_CHECK(iot_button_create(&pwr_btn_config, pwr_btn_driver_, &pwr_btn));
+ iot_button_register_cb(pwr_btn, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) {
+ auto self = static_cast<CustomBoard*>(usr_data);
+ if(self->GetBacklight()->brightness() > 0) {
+ self->GetBacklight()->SetBrightness(0);
+ gpio_set_level(PWR_Control_PIN, false);
+ }
+ else {
+ self->GetBacklight()->RestoreBrightness();
+ gpio_set_level(PWR_Control_PIN, true);
+ }
+ }, this);
+ }
+
+public:
+ CustomBoard() {
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeSpi();
+ Initializest77916Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_BOTH, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH
+
+ 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(CustomBoard);
+
+CustomBoard* CustomBoard::instance_ = nullptr;
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/README.md
new file mode 100644
index 0000000..7668dec
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-Touch-LCD-1.85C
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-1.85C.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.h
new file mode 100644
index 0000000..d7d8cf0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.h
@@ -0,0 +1,90 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#ifdef CONFIG_VERSION_1_0
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_2
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_15
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_47
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_48
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_38
+
+#endif
+
+#ifdef CONFIG_VERSION_2_0
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_38
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_39
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_47
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_15
+#define AUDIO_INPUT_REFERENCE true
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#endif
+
+
+
+#define I2C_SCL_IO GPIO_NUM_10
+#define I2C_SDA_IO GPIO_NUM_11
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+
+#define DISPLAY_WIDTH 360
+#define DISPLAY_HEIGHT 360
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define QSPI_LCD_H_RES (360)
+#define QSPI_LCD_V_RES (360)
+#define QSPI_LCD_BIT_PER_PIXEL (16)
+
+#define QSPI_LCD_HOST SPI2_HOST
+#define QSPI_PIN_NUM_LCD_PCLK GPIO_NUM_40
+#define QSPI_PIN_NUM_LCD_CS GPIO_NUM_21
+#define QSPI_PIN_NUM_LCD_DATA0 GPIO_NUM_46
+#define QSPI_PIN_NUM_LCD_DATA1 GPIO_NUM_45
+#define QSPI_PIN_NUM_LCD_DATA2 GPIO_NUM_42
+#define QSPI_PIN_NUM_LCD_DATA3 GPIO_NUM_41
+#define QSPI_PIN_NUM_LCD_RST GPIO_NUM_NC
+#define QSPI_PIN_NUM_LCD_BL GPIO_NUM_5
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define TP_PORT (I2C_NUM_1)
+#define TP_PIN_NUM_SDA (I2C_SDA_IO)
+#define TP_PIN_NUM_SCL (I2C_SCL_IO)
+#define TP_PIN_NUM_RST (GPIO_NUM_NC)
+#define TP_PIN_NUM_INT (GPIO_NUM_4)
+
+#define DISPLAY_BACKLIGHT_PIN QSPI_PIN_NUM_LCD_BL
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(sclk, d0, d1, d2, d3, max_trans_sz) \
+ { \
+ .data0_io_num = d0, \
+ .data1_io_num = d1, \
+ .sclk_io_num = sclk, \
+ .data2_io_num = d2, \
+ .data3_io_num = d3, \
+ .max_transfer_sz = max_trans_sz, \
+ }
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.json
new file mode 100644
index 0000000..39b6ca0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/config.json
@@ -0,0 +1,10 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-1.85c",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc
new file mode 100644
index 0000000..b73abb3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc
@@ -0,0 +1,407 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_st77916.h>
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+
+#define TAG "waveshare_lcd_1_85c"
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x0BULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const st77916_lcd_init_cmd_t vendor_specific_init_new[] = {
+ {0xF0, (uint8_t []){0x28}, 1, 0},
+ {0xF2, (uint8_t []){0x28}, 1, 0},
+ {0x73, (uint8_t []){0xF0}, 1, 0},
+ {0x7C, (uint8_t []){0xD1}, 1, 0},
+ {0x83, (uint8_t []){0xE0}, 1, 0},
+ {0x84, (uint8_t []){0x61}, 1, 0},
+ {0xF2, (uint8_t []){0x82}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x01}, 1, 0},
+ {0xF1, (uint8_t []){0x01}, 1, 0},
+ {0xB0, (uint8_t []){0x56}, 1, 0},
+ {0xB1, (uint8_t []){0x4D}, 1, 0},
+ {0xB2, (uint8_t []){0x24}, 1, 0},
+ {0xB4, (uint8_t []){0x87}, 1, 0},
+ {0xB5, (uint8_t []){0x44}, 1, 0},
+ {0xB6, (uint8_t []){0x8B}, 1, 0},
+ {0xB7, (uint8_t []){0x40}, 1, 0},
+ {0xB8, (uint8_t []){0x86}, 1, 0},
+ {0xBA, (uint8_t []){0x00}, 1, 0},
+ {0xBB, (uint8_t []){0x08}, 1, 0},
+ {0xBC, (uint8_t []){0x08}, 1, 0},
+ {0xBD, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x80}, 1, 0},
+ {0xC1, (uint8_t []){0x10}, 1, 0},
+ {0xC2, (uint8_t []){0x37}, 1, 0},
+ {0xC3, (uint8_t []){0x80}, 1, 0},
+ {0xC4, (uint8_t []){0x10}, 1, 0},
+ {0xC5, (uint8_t []){0x37}, 1, 0},
+ {0xC6, (uint8_t []){0xA9}, 1, 0},
+ {0xC7, (uint8_t []){0x41}, 1, 0},
+ {0xC8, (uint8_t []){0x01}, 1, 0},
+ {0xC9, (uint8_t []){0xA9}, 1, 0},
+ {0xCA, (uint8_t []){0x41}, 1, 0},
+ {0xCB, (uint8_t []){0x01}, 1, 0},
+ {0xD0, (uint8_t []){0x91}, 1, 0},
+ {0xD1, (uint8_t []){0x68}, 1, 0},
+ {0xD2, (uint8_t []){0x68}, 1, 0},
+ {0xF5, (uint8_t []){0x00, 0xA5}, 2, 0},
+ {0xDD, (uint8_t []){0x4F}, 1, 0},
+ {0xDE, (uint8_t []){0x4F}, 1, 0},
+ {0xF1, (uint8_t []){0x10}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0xF0, (uint8_t []){0x02}, 1, 0},
+ {0xE0, (uint8_t []){0xF0, 0x0A, 0x10, 0x09, 0x09, 0x36, 0x35, 0x33, 0x4A, 0x29, 0x15, 0x15, 0x2E, 0x34}, 14, 0},
+ {0xE1, (uint8_t []){0xF0, 0x0A, 0x0F, 0x08, 0x08, 0x05, 0x34, 0x33, 0x4A, 0x39, 0x15, 0x15, 0x2D, 0x33}, 14, 0},
+ {0xF0, (uint8_t []){0x10}, 1, 0},
+ {0xF3, (uint8_t []){0x10}, 1, 0},
+ {0xE0, (uint8_t []){0x07}, 1, 0},
+ {0xE1, (uint8_t []){0x00}, 1, 0},
+ {0xE2, (uint8_t []){0x00}, 1, 0},
+ {0xE3, (uint8_t []){0x00}, 1, 0},
+ {0xE4, (uint8_t []){0xE0}, 1, 0},
+ {0xE5, (uint8_t []){0x06}, 1, 0},
+ {0xE6, (uint8_t []){0x21}, 1, 0},
+ {0xE7, (uint8_t []){0x01}, 1, 0},
+ {0xE8, (uint8_t []){0x05}, 1, 0},
+ {0xE9, (uint8_t []){0x02}, 1, 0},
+ {0xEA, (uint8_t []){0xDA}, 1, 0},
+ {0xEB, (uint8_t []){0x00}, 1, 0},
+ {0xEC, (uint8_t []){0x00}, 1, 0},
+ {0xED, (uint8_t []){0x0F}, 1, 0},
+ {0xEE, (uint8_t []){0x00}, 1, 0},
+ {0xEF, (uint8_t []){0x00}, 1, 0},
+ {0xF8, (uint8_t []){0x00}, 1, 0},
+ {0xF9, (uint8_t []){0x00}, 1, 0},
+ {0xFA, (uint8_t []){0x00}, 1, 0},
+ {0xFB, (uint8_t []){0x00}, 1, 0},
+ {0xFC, (uint8_t []){0x00}, 1, 0},
+ {0xFD, (uint8_t []){0x00}, 1, 0},
+ {0xFE, (uint8_t []){0x00}, 1, 0},
+ {0xFF, (uint8_t []){0x00}, 1, 0},
+ {0x60, (uint8_t []){0x40}, 1, 0},
+ {0x61, (uint8_t []){0x04}, 1, 0},
+ {0x62, (uint8_t []){0x00}, 1, 0},
+ {0x63, (uint8_t []){0x42}, 1, 0},
+ {0x64, (uint8_t []){0xD9}, 1, 0},
+ {0x65, (uint8_t []){0x00}, 1, 0},
+ {0x66, (uint8_t []){0x00}, 1, 0},
+ {0x67, (uint8_t []){0x00}, 1, 0},
+ {0x68, (uint8_t []){0x00}, 1, 0},
+ {0x69, (uint8_t []){0x00}, 1, 0},
+ {0x6A, (uint8_t []){0x00}, 1, 0},
+ {0x6B, (uint8_t []){0x00}, 1, 0},
+ {0x70, (uint8_t []){0x40}, 1, 0},
+ {0x71, (uint8_t []){0x03}, 1, 0},
+ {0x72, (uint8_t []){0x00}, 1, 0},
+ {0x73, (uint8_t []){0x42}, 1, 0},
+ {0x74, (uint8_t []){0xD8}, 1, 0},
+ {0x75, (uint8_t []){0x00}, 1, 0},
+ {0x76, (uint8_t []){0x00}, 1, 0},
+ {0x77, (uint8_t []){0x00}, 1, 0},
+ {0x78, (uint8_t []){0x00}, 1, 0},
+ {0x79, (uint8_t []){0x00}, 1, 0},
+ {0x7A, (uint8_t []){0x00}, 1, 0},
+ {0x7B, (uint8_t []){0x00}, 1, 0},
+ {0x80, (uint8_t []){0x48}, 1, 0},
+ {0x81, (uint8_t []){0x00}, 1, 0},
+ {0x82, (uint8_t []){0x06}, 1, 0},
+ {0x83, (uint8_t []){0x02}, 1, 0},
+ {0x84, (uint8_t []){0xD6}, 1, 0},
+ {0x85, (uint8_t []){0x04}, 1, 0},
+ {0x86, (uint8_t []){0x00}, 1, 0},
+ {0x87, (uint8_t []){0x00}, 1, 0},
+ {0x88, (uint8_t []){0x48}, 1, 0},
+ {0x89, (uint8_t []){0x00}, 1, 0},
+ {0x8A, (uint8_t []){0x08}, 1, 0},
+ {0x8B, (uint8_t []){0x02}, 1, 0},
+ {0x8C, (uint8_t []){0xD8}, 1, 0},
+ {0x8D, (uint8_t []){0x04}, 1, 0},
+ {0x8E, (uint8_t []){0x00}, 1, 0},
+ {0x8F, (uint8_t []){0x00}, 1, 0},
+ {0x90, (uint8_t []){0x48}, 1, 0},
+ {0x91, (uint8_t []){0x00}, 1, 0},
+ {0x92, (uint8_t []){0x0A}, 1, 0},
+ {0x93, (uint8_t []){0x02}, 1, 0},
+ {0x94, (uint8_t []){0xDA}, 1, 0},
+ {0x95, (uint8_t []){0x04}, 1, 0},
+ {0x96, (uint8_t []){0x00}, 1, 0},
+ {0x97, (uint8_t []){0x00}, 1, 0},
+ {0x98, (uint8_t []){0x48}, 1, 0},
+ {0x99, (uint8_t []){0x00}, 1, 0},
+ {0x9A, (uint8_t []){0x0C}, 1, 0},
+ {0x9B, (uint8_t []){0x02}, 1, 0},
+ {0x9C, (uint8_t []){0xDC}, 1, 0},
+ {0x9D, (uint8_t []){0x04}, 1, 0},
+ {0x9E, (uint8_t []){0x00}, 1, 0},
+ {0x9F, (uint8_t []){0x00}, 1, 0},
+ {0xA0, (uint8_t []){0x48}, 1, 0},
+ {0xA1, (uint8_t []){0x00}, 1, 0},
+ {0xA2, (uint8_t []){0x05}, 1, 0},
+ {0xA3, (uint8_t []){0x02}, 1, 0},
+ {0xA4, (uint8_t []){0xD5}, 1, 0},
+ {0xA5, (uint8_t []){0x04}, 1, 0},
+ {0xA6, (uint8_t []){0x00}, 1, 0},
+ {0xA7, (uint8_t []){0x00}, 1, 0},
+ {0xA8, (uint8_t []){0x48}, 1, 0},
+ {0xA9, (uint8_t []){0x00}, 1, 0},
+ {0xAA, (uint8_t []){0x07}, 1, 0},
+ {0xAB, (uint8_t []){0x02}, 1, 0},
+ {0xAC, (uint8_t []){0xD7}, 1, 0},
+ {0xAD, (uint8_t []){0x04}, 1, 0},
+ {0xAE, (uint8_t []){0x00}, 1, 0},
+ {0xAF, (uint8_t []){0x00}, 1, 0},
+ {0xB0, (uint8_t []){0x48}, 1, 0},
+ {0xB1, (uint8_t []){0x00}, 1, 0},
+ {0xB2, (uint8_t []){0x09}, 1, 0},
+ {0xB3, (uint8_t []){0x02}, 1, 0},
+ {0xB4, (uint8_t []){0xD9}, 1, 0},
+ {0xB5, (uint8_t []){0x04}, 1, 0},
+ {0xB6, (uint8_t []){0x00}, 1, 0},
+ {0xB7, (uint8_t []){0x00}, 1, 0},
+
+ {0xB8, (uint8_t []){0x48}, 1, 0},
+ {0xB9, (uint8_t []){0x00}, 1, 0},
+ {0xBA, (uint8_t []){0x0B}, 1, 0},
+ {0xBB, (uint8_t []){0x02}, 1, 0},
+ {0xBC, (uint8_t []){0xDB}, 1, 0},
+ {0xBD, (uint8_t []){0x04}, 1, 0},
+ {0xBE, (uint8_t []){0x00}, 1, 0},
+ {0xBF, (uint8_t []){0x00}, 1, 0},
+ {0xC0, (uint8_t []){0x10}, 1, 0},
+ {0xC1, (uint8_t []){0x47}, 1, 0},
+ {0xC2, (uint8_t []){0x56}, 1, 0},
+ {0xC3, (uint8_t []){0x65}, 1, 0},
+ {0xC4, (uint8_t []){0x74}, 1, 0},
+ {0xC5, (uint8_t []){0x88}, 1, 0},
+ {0xC6, (uint8_t []){0x99}, 1, 0},
+ {0xC7, (uint8_t []){0x01}, 1, 0},
+ {0xC8, (uint8_t []){0xBB}, 1, 0},
+ {0xC9, (uint8_t []){0xAA}, 1, 0},
+ {0xD0, (uint8_t []){0x10}, 1, 0},
+ {0xD1, (uint8_t []){0x47}, 1, 0},
+ {0xD2, (uint8_t []){0x56}, 1, 0},
+ {0xD3, (uint8_t []){0x65}, 1, 0},
+ {0xD4, (uint8_t []){0x74}, 1, 0},
+ {0xD5, (uint8_t []){0x88}, 1, 0},
+ {0xD6, (uint8_t []){0x99}, 1, 0},
+ {0xD7, (uint8_t []){0x01}, 1, 0},
+ {0xD8, (uint8_t []){0xBB}, 1, 0},
+ {0xD9, (uint8_t []){0xAA}, 1, 0},
+ {0xF3, (uint8_t []){0x01}, 1, 0},
+ {0xF0, (uint8_t []){0x00}, 1, 0},
+ {0x21, (uint8_t []){0x00}, 1, 0},
+ {0x11, (uint8_t []){0x00}, 1, 120},
+ {0x29, (uint8_t []){0x00}, 1, 0},
+};
+
+class CustomBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = I2C_SDA_IO,
+ .scl_io_num = I2C_SCL_IO,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void)
+ {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+
+ // uint32_t input_level_mask = 0;
+ // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_INPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍏�
+ // ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, &input_level_mask); // 鑾峰彇寮曡剼 EXIO0 鍜� EXIO1 鐨勭數骞崇姸鎬�,瀛樻斁鍦� input_level_mask 涓�
+
+ // ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO2 鍜� EXIO3 妯″紡涓鸿緭鍑�
+ // ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_2 | IO_EXPANDER_PIN_NUM_3, 1); // 灏嗗紩鑴氱數骞宠缃负 1
+ // ret = esp_io_expander_print_state(io_expander); // 鎵撳嵃寮曡剼鐘舵��
+
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT); // 璁剧疆寮曡剼 EXIO0 鍜� EXIO1 妯″紡涓鸿緭鍑�
+ ESP_ERROR_CHECK(ret);
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(300));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 1); // 澶嶄綅 LCD 涓� TouchPad
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+
+ const spi_bus_config_t bus_config = TAIJIPI_ST77916_PANEL_BUS_QSPI_CONFIG(QSPI_PIN_NUM_LCD_PCLK,
+ QSPI_PIN_NUM_LCD_DATA0,
+ QSPI_PIN_NUM_LCD_DATA1,
+ QSPI_PIN_NUM_LCD_DATA2,
+ QSPI_PIN_NUM_LCD_DATA3,
+ QSPI_LCD_H_RES * 80 * sizeof(uint16_t));
+ ESP_ERROR_CHECK(spi_bus_initialize(QSPI_LCD_HOST, &bus_config, SPI_DMA_CH_AUTO));
+ }
+
+ void Initializest77916Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+
+ ESP_LOGI(TAG, "Install panel IO");
+
+ esp_lcd_panel_io_spi_config_t io_config = {
+ .cs_gpio_num = QSPI_PIN_NUM_LCD_CS,
+ .dc_gpio_num = -1,
+ .spi_mode = 0,
+ .pclk_hz = 3 * 1000 * 1000,
+ .trans_queue_depth = 10,
+ .on_color_trans_done = NULL,
+ .user_ctx = NULL,
+ .lcd_cmd_bits = 32,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .octal_mode = 0,
+ .quad_mode = 1,
+ .sio_mode = 0,
+ .lsb_first = 0,
+ .cs_high_active = 0,
+ },
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io));
+
+ ESP_LOGI(TAG, "Install ST77916 panel driver");
+
+ st77916_vendor_config_t vendor_config = {
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+
+ printf("-------------------------------------- Version selection -------------------------------------- \r\n");
+ esp_err_t ret;
+ int lcd_cmd = 0x04;
+ uint8_t register_data[4];
+ size_t param_size = sizeof(register_data);
+ lcd_cmd &= 0xff;
+ lcd_cmd <<= 8;
+ lcd_cmd |= LCD_OPCODE_READ_CMD << 24; // Use the read opcode instead of write
+ ret = esp_lcd_panel_io_rx_param(panel_io, lcd_cmd, register_data, param_size);
+ if (ret == ESP_OK) {
+ printf("Register 0x04 data: %02x %02x %02x %02x\n", register_data[0], register_data[1], register_data[2], register_data[3]);
+ } else {
+ printf("Failed to read register 0x04, error code: %d\n", ret);
+ }
+ // panel_io_spi_del(io_handle);
+ io_config.pclk_hz = 80 * 1000 * 1000;
+ if (esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)QSPI_LCD_HOST, &io_config, &panel_io) != ESP_OK) {
+ printf("Failed to set LCD communication parameters -- SPI\r\n");
+ return ;
+ }
+ printf("LCD communication parameters are set successfully -- SPI\r\n");
+
+ // Check register values and configure accordingly
+ if (register_data[0] == 0x00 && register_data[1] == 0x7F && register_data[2] == 0x7F && register_data[3] == 0x7F) {
+ // Handle the case where the register data matches this pattern
+ printf("Vendor-specific initialization for case 1.\n");
+ }
+ else if (register_data[0] == 0x00 && register_data[1] == 0x02 && register_data[2] == 0x7F && register_data[3] == 0x7F) {
+ // Provide vendor-specific initialization commands if register data matches this pattern
+ vendor_config.init_cmds = vendor_specific_init_new;
+ vendor_config.init_cmds_size = sizeof(vendor_specific_init_new) / sizeof(st77916_lcd_init_cmd_t);
+ printf("Vendor-specific initialization for case 2.\n");
+ }
+ printf("------------------------------------- End of version selection------------------------------------- \r\n");
+
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = QSPI_PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, // Implemented by LCD command `36h`
+ .bits_per_pixel = QSPI_LCD_BIT_PER_PIXEL, // Implemented by LCD command `3Ah` (16/18)
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st77916(panel_io, &panel_config, &panel));
+
+ esp_lcd_panel_reset(panel);
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_disp_on_off(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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeSpi();
+ Initializest77916Display();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ #ifdef CONFIG_VERSION_1_0
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, I2S_STD_SLOT_LEFT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN, I2S_STD_SLOT_RIGHT); // I2S_STD_SLOT_LEFT / I2S_STD_SLOT_RIGHT / I2S_STD_SLOT_BOTH
+
+ return &audio_codec;
+ }
+ #endif
+
+ #ifdef CONFIG_VERSION_2_0
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(i2c_bus_, 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, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+ #endif
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/README.md
new file mode 100644
index 0000000..657331d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-Touch-LCD-3.49
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.49.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.h
new file mode 100644
index 0000000..e2e9b79
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.h
@@ -0,0 +1,62 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+#include "lvgl.h"
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_7
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_46
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#define Dev_Touch_I2C_SDA_PIN GPIO_NUM_17
+#define Dev_Touch_I2C_SCL_PIN GPIO_NUM_18
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define I2C_Touch_ADDRESS 0x3b
+#define I2C_Touch_SDA_PIN GPIO_NUM_17
+#define I2C_Touch_SCL_PIN GPIO_NUM_18
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_16
+
+#define LCD_CS GPIO_NUM_9
+#define LCD_PCLK GPIO_NUM_10
+#define LCD_D0 GPIO_NUM_11
+#define LCD_D1 GPIO_NUM_12
+#define LCD_D2 GPIO_NUM_13
+#define LCD_D3 GPIO_NUM_14
+#define LCD_RST GPIO_NUM_21
+#define LCD_LIGHT (-1)
+
+#define DISPLAY_WIDTH 172
+#define DISPLAY_HEIGHT 640
+#define LVGL_DMA_BUFF_LEN (DISPLAY_WIDTH * 64 * 2)
+#define LVGL_SPIRAM_BUFF_LEN (DISPLAY_WIDTH * DISPLAY_HEIGHT * 2)
+
+#define DISPLAY_ROTATION_90 false
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_8
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.json
new file mode 100644
index 0000000..f140886
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-3.49",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.cc
new file mode 100644
index 0000000..14eeecc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.cc
@@ -0,0 +1,147 @@
+#include "custom_lcd_display.h"
+
+#include "lcd_display.h"
+
+#include <vector>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_lvgl_port.h>
+#include "assets/lang_config.h"
+#include <cstring>
+#include "settings.h"
+
+#include "esp_lcd_panel_io.h"
+
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/semphr.h"
+
+#include "config.h"
+
+#include "board.h"
+
+#define TAG "CustomLcdDisplay"
+
+static SemaphoreHandle_t trans_done_sem = NULL;
+static uint16_t *trans_buf_1;
+
+#if (DISPLAY_ROTATION_90 == true)
+static uint16_t *dest_map;
+#endif
+
+
+bool CustomLcdDisplay::lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx) {
+ BaseType_t taskAwake = pdFALSE;
+ lv_display_t *disp_drv = (lv_display_t *)user_ctx;
+ assert(disp_drv != NULL);
+ if (trans_done_sem) {
+ xSemaphoreGiveFromISR(trans_done_sem, &taskAwake);
+ }
+ return false;
+}
+
+void CustomLcdDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map) {
+ assert(drv != NULL);
+ esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_user_data(drv);
+ assert(panel_handle != NULL);
+
+ lv_draw_sw_rgb565_swap(color_map, lv_area_get_width(area) * lv_area_get_height(area));
+
+#if (DISPLAY_ROTATION_90 == true)
+ lv_display_rotation_t rotation = lv_display_get_rotation(drv);
+ lv_area_t rotated_area;
+ if(rotation != LV_DISPLAY_ROTATION_0) {
+ lv_color_format_t cf = lv_display_get_color_format(drv);
+ /*Calculate the position of the rotated area*/
+ rotated_area = *area;
+ lv_display_rotate_area(drv, &rotated_area);
+ /*Calculate the source stride (bytes in a line) from the width of the area*/
+ uint32_t src_stride = lv_draw_buf_width_to_stride(lv_area_get_width(area), cf);
+ /*Calculate the stride of the destination (rotated) area too*/
+ uint32_t dest_stride = lv_draw_buf_width_to_stride(lv_area_get_width(&rotated_area), cf);
+ /*Have a buffer to store the rotated area and perform the rotation*/
+
+ int32_t src_w = lv_area_get_width(area);
+ int32_t src_h = lv_area_get_height(area);
+ lv_draw_sw_rotate(color_map, dest_map, src_w, src_h, src_stride, dest_stride, rotation, cf);
+ /*Use the rotated area and rotated buffer from now on*/
+ area = &rotated_area;
+ }
+#endif
+ const int flush_coun = (LVGL_SPIRAM_BUFF_LEN / LVGL_DMA_BUFF_LEN);
+ const int offgap = (DISPLAY_HEIGHT / flush_coun);
+ const int dmalen = (LVGL_DMA_BUFF_LEN / 2);
+ int offsetx1 = 0;
+ int offsety1 = 0;
+ int offsetx2 = DISPLAY_WIDTH;
+ int offsety2 = offgap;
+#if (DISPLAY_ROTATION_90 == true)
+ uint16_t *map = (uint16_t*)dest_map;
+#else
+ uint16_t *map = (uint16_t*)color_map;
+#endif
+ xSemaphoreGive(trans_done_sem);
+
+ for(int i = 0; i<flush_coun; i++) {
+ xSemaphoreTake(trans_done_sem,portMAX_DELAY);
+ memcpy(trans_buf_1,map,LVGL_DMA_BUFF_LEN);
+ esp_lcd_panel_draw_bitmap(panel_handle, offsetx1, offsety1, offsetx2, offsety2, trans_buf_1);
+ offsety1 += offgap;
+ offsety2 += offgap;
+ map += dmalen;
+ }
+ xSemaphoreTake(trans_done_sem,portMAX_DELAY);
+ lv_disp_flush_ready(drv);
+}
+
+
+CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy)
+ : LcdDisplay(panel_io, panel, width, height) {
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+ ESP_LOGI(TAG, "Initialize LVGL port");
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 2;
+ port_cfg.timer_period_ms = 50;
+ lvgl_port_init(&port_cfg);
+ trans_done_sem = xSemaphoreCreateBinary();
+ trans_buf_1 = (uint16_t *)heap_caps_malloc(LVGL_DMA_BUFF_LEN, MALLOC_CAP_DMA);
+
+ uint32_t buffer_size = 0;
+ lv_color_t *buf1 = NULL;
+ lvgl_port_lock(0);
+ uint8_t color_bytes = lv_color_format_get_size(LV_COLOR_FORMAT_RGB565);
+ display_ = lv_display_create(width_, height_);
+ lv_display_set_flush_cb(display_, lvgl_port_flush_callback);
+ buffer_size = width_ * height_;
+ buf1 = (lv_color_t *)heap_caps_aligned_alloc(1, buffer_size * color_bytes, MALLOC_CAP_SPIRAM);
+#if (DISPLAY_ROTATION_90 == true)
+ dest_map = (uint16_t *)heap_caps_malloc(buffer_size * color_bytes, MALLOC_CAP_SPIRAM);
+ lv_display_set_rotation(display_, LV_DISPLAY_ROTATION_90);
+#endif
+ lv_display_set_buffers(display_, buf1, NULL, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_FULL);
+ lv_display_set_user_data(display_, panel_);
+ lvgl_port_unlock();
+
+ esp_lcd_panel_io_callbacks_t cbs = {
+ .on_color_trans_done = lvgl_port_flush_io_ready_callback,
+ };
+ /* Register done callback */
+ esp_lcd_panel_io_register_event_callbacks(panel_io_, &cbs, display_);
+
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ if (offset_x != 0 || offset_y != 0) {
+ lv_display_set_offset(display_, offset_x, offset_y);
+ }
+
+ // Note: SetupUI() should be called by Application::Initialize(), not in constructor
+ // to ensure lvgl objects are created after the display is fully initialized.
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.h
new file mode 100644
index 0000000..d8b8fcc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/custom_lcd_display.h
@@ -0,0 +1,17 @@
+#ifndef __CUSTOM_LCD_DISPLAY_H__
+#define __CUSTOM_LCD_DISPLAY_H__
+
+#include "lcd_display.h"
+
+// // SPI LCD鏄剧ず鍣�
+class CustomLcdDisplay : public LcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy);
+private:
+ static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
+ static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map);
+};
+
+#endif // __CUSTOM_LCD_DISPLAY_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc
new file mode 100644
index 0000000..870fed9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.49/waveshare-s3-touch-lcd-3.49.cc
@@ -0,0 +1,253 @@
+#include "wifi_board.h"
+#include "codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+
+#include "esp_lcd_axs15231b.h"
+
+#include "custom_lcd_display.h"
+
+#include <lvgl.h>
+
+#define TAG "waveshare_lcd_3_39"
+
+
+static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0x11, (uint8_t []){0x00}, 0, 100},
+ {0x29, (uint8_t []){0x00}, 0, 100},
+};
+
+class CustomBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ Button pwr_button_;
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ i2c_master_dev_handle_t disp_touch_dev_handle = NULL;
+ lv_indev_t *touch_indev = NULL; //touch
+ bool is_PwrControlEn = false;
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void) {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_OUTPUT);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_7 | IO_EXPANDER_PIN_NUM_6, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.data0_io_num = LCD_D0;
+ buscfg.data1_io_num = LCD_D1;
+ buscfg.data2_io_num = LCD_D2;
+ buscfg.data3_io_num = LCD_D3;
+ buscfg.sclk_io_num = LCD_PCLK;
+ buscfg.max_transfer_sz = LVGL_DMA_BUFF_LEN;
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // RESET PIN INIT
+ gpio_config_t gpio_conf = {};
+ gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ gpio_conf.mode = GPIO_MODE_OUTPUT;
+ gpio_conf.pin_bit_mask = ((uint64_t)0x01<<LCD_RST);
+ gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_conf.pull_up_en = GPIO_PULLUP_ENABLE;
+ ESP_ERROR_CHECK_WITHOUT_ABORT(gpio_config(&gpio_conf));
+
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = AXS15231B_PANEL_IO_QSPI_CONFIG(
+ LCD_CS,
+ NULL,
+ NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGI(TAG, "Install LCD driver");
+ const axs15231b_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = -1,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = (void *)&vendor_config,
+ };
+ esp_lcd_new_panel_axs15231b(panel_io, &panel_config, &panel);
+
+ gpio_set_level(LCD_RST,1);
+ vTaskDelay(pdMS_TO_TICKS(30));
+ gpio_set_level(LCD_RST,0);
+ vTaskDelay(pdMS_TO_TICKS(250));
+ gpio_set_level(LCD_RST,1);
+ vTaskDelay(pdMS_TO_TICKS(30));
+ esp_lcd_panel_init(panel);
+
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ pwr_button_.OnLongPress([this]() {
+ if(is_PwrControlEn) {
+ is_PwrControlEn = false;
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0);
+ }
+ });
+
+ pwr_button_.OnPressUp([this]() {
+ if(!is_PwrControlEn) {
+ is_PwrControlEn = true;
+ }
+ });
+ }
+
+ void InitializeTouch() {
+ i2c_master_bus_handle_t touch_i2c_bus_;
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {};
+ i2c_bus_cfg.i2c_port = (i2c_port_t)I2C_NUM_1;
+ i2c_bus_cfg.sda_io_num = I2C_Touch_SDA_PIN;
+ i2c_bus_cfg.scl_io_num = I2C_Touch_SCL_PIN;
+ i2c_bus_cfg.clk_source = I2C_CLK_SRC_DEFAULT;
+ i2c_bus_cfg.glitch_ignore_cnt = 7;
+ i2c_bus_cfg.intr_priority = 0;
+ i2c_bus_cfg.trans_queue_depth = 0;
+ i2c_bus_cfg.flags.enable_internal_pullup = 1;
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &touch_i2c_bus_));
+
+ i2c_device_config_t dev_cfg = {
+ .dev_addr_length = I2C_ADDR_BIT_LEN_7,
+ .device_address = I2C_Touch_ADDRESS,
+ .scl_speed_hz = 300000,
+ };
+ ESP_ERROR_CHECK(i2c_master_bus_add_device(touch_i2c_bus_, &dev_cfg, &disp_touch_dev_handle));
+
+ touch_indev = lv_indev_create();
+ lv_indev_set_type(touch_indev, LV_INDEV_TYPE_POINTER);
+ lv_indev_set_read_cb(touch_indev, TouchInputReadCallback);
+ lv_indev_set_user_data(touch_indev, disp_touch_dev_handle);
+ }
+
+ static void TouchInputReadCallback(lv_indev_t * indev, lv_indev_data_t *indevData) {
+ i2c_master_dev_handle_t i2c_dev = (i2c_master_dev_handle_t)lv_indev_get_user_data(indev);
+ uint8_t read_touchpad_cmd[11] = {0xb5, 0xab, 0xa5, 0x5a, 0x0, 0x0, 0x0, 0x0e,0x0, 0x0, 0x0};
+ uint8_t buff[32] = {0};
+ i2c_master_transmit_receive(i2c_dev,read_touchpad_cmd,11,buff,32,1000);
+ uint16_t pointX;
+ uint16_t pointY;
+ pointX = (((uint16_t)buff[2] & 0x0f) << 8) | (uint16_t)buff[3];
+ pointY = (((uint16_t)buff[4] & 0x0f) << 8) | (uint16_t)buff[5];
+ if (buff[1]>0 && buff[1]<5) {
+ indevData->state = LV_INDEV_STATE_PRESSED;
+ if(pointX > DISPLAY_WIDTH) pointX = DISPLAY_WIDTH;
+ if(pointY > DISPLAY_HEIGHT) pointY = DISPLAY_HEIGHT;
+ indevData->point.x = pointY;
+ indevData->point.y = (DISPLAY_HEIGHT-pointX);
+ ESP_LOGE("Touch","(%ld,%ld)",indevData->point.x,indevData->point.y);
+ } else {
+ indevData->state = LV_INDEV_STATE_RELEASED;
+ }
+ }
+
+ void GetPwrCurrentState() {
+ if(gpio_get_level(PWR_BUTTON_GPIO)) {
+ is_PwrControlEn = true;
+ }
+ }
+
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ pwr_button_(PWR_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeButtons();
+ InitializeTouch();
+ GetPwrCurrentState();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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(CustomBoard);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/README.md
new file mode 100644
index 0000000..78b6949
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-Touch-LCD-3.5
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.5.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.h
new file mode 100644
index 0000000..7852904
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.h
@@ -0,0 +1,69 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_12
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_15
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SPI_MODE 0
+#define DISPLAY_CS_PIN GPIO_NUM_NC
+#define DISPLAY_MOSI_PIN GPIO_NUM_1
+#define DISPLAY_MISO_PIN GPIO_NUM_2
+#define DISPLAY_CLK_PIN GPIO_NUM_5
+#define DISPLAY_DC_PIN GPIO_NUM_3
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+
+
+
+#define DISPLAY_WIDTH 480
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_INVERT_COLOR true
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#define CAM_PIN_PWDN GPIO_NUM_NC
+#define CAM_PIN_RESET GPIO_NUM_NC
+#define CAM_PIN_VSYNC GPIO_NUM_17
+#define CAM_PIN_HREF GPIO_NUM_18
+#define CAM_PIN_PCLK GPIO_NUM_41
+#define CAM_PIN_XCLK GPIO_NUM_38
+#define CAM_PIN_SIOD GPIO_NUM_NC
+#define CAM_PIN_SIOC GPIO_NUM_NC
+#define CAM_PIN_D0 GPIO_NUM_45
+#define CAM_PIN_D1 GPIO_NUM_47
+#define CAM_PIN_D2 GPIO_NUM_48
+#define CAM_PIN_D3 GPIO_NUM_46
+#define CAM_PIN_D4 GPIO_NUM_42
+#define CAM_PIN_D5 GPIO_NUM_40
+#define CAM_PIN_D6 GPIO_NUM_39
+#define CAM_PIN_D7 GPIO_NUM_21
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.json
new file mode 100644
index 0000000..cc1c55d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/config.json
@@ -0,0 +1,20 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-3.5",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y",
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_640X480_6FPS=y",
+ "CONFIG_CAMERA_OV2640_DVP_IF_FORMAT_INDEX_DEFAULT=1",
+ "CONFIG_CAMERA_OV5640=y",
+ "CONFIG_CAMERA_OV5640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5640_DVP_YUV422_800X600_10FPS=y",
+ "CONFIG_CAMERA_OV5640_DVP_IF_FORMAT_INDEX_DEFAULT=0"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc
new file mode 100644
index 0000000..7caf2b4
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5/esp32-s3-touch-lcd-3.5.cc
@@ -0,0 +1,382 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+
+#include "axp2101.h"
+#include "power_save_timer.h"
+
+#include <esp_lcd_touch_ft5x06.h>
+#include <esp_lvgl_port.h>
+
+#include "esp_video.h"
+
+#define TAG "waveshare_lcd_3_5"
+
+class Pmic : public Axp2101 {
+ public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ WriteReg(0x96, (1500 - 500) / 100);
+ WriteReg(0x97, (2800 - 500) / 100);
+
+ // Enable ALDO1 BLDO1 BLDO2
+ WriteReg(0x90, 0x31);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+ };
+
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} st7796_lcd_init_cmd_t;
+
+typedef struct {
+ const st7796_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+} st7796_vendor_config_t;
+
+st7796_lcd_init_cmd_t st7796_lcd_init_cmds[] = {
+ {0x11, (uint8_t []){ 0x00 }, 0, 120},
+
+ // {0x36, (uint8_t []){ 0x08 }, 1, 0},
+
+ {0x3A, (uint8_t []){ 0x05 }, 1, 0},
+ {0xF0, (uint8_t []){ 0xC3 }, 1, 0},
+ {0xF0, (uint8_t []){ 0x96 }, 1, 0},
+ {0xB4, (uint8_t []){ 0x01 }, 1, 0},
+ {0xB7, (uint8_t []){ 0xC6 }, 1, 0},
+ {0xC0, (uint8_t []){ 0x80, 0x45 }, 2, 0},
+ {0xC1, (uint8_t []){ 0x13 }, 1, 0},
+ {0xC2, (uint8_t []){ 0xA7 }, 1, 0},
+ {0xC5, (uint8_t []){ 0x0A }, 1, 0},
+ {0xE8, (uint8_t []){ 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 8, 0},
+ {0xE0, (uint8_t []){ 0xD0, 0x08, 0x0F, 0x06, 0x06, 0x33, 0x30, 0x33, 0x47, 0x17, 0x13, 0x13, 0x2B, 0x31}, 14, 0},
+ {0xE1, (uint8_t []){ 0xD0, 0x0A, 0x11, 0x0B, 0x09, 0x07, 0x2F, 0x33, 0x47, 0x38, 0x15, 0x16, 0x2C, 0x32},14, 0},
+ {0xF0, (uint8_t []){ 0x3C }, 1, 0},
+ {0xF0, (uint8_t []){ 0x69 }, 1, 120},
+ {0x21, (uint8_t []){ 0x00 }, 0, 0},
+ {0x29, (uint8_t []){ 0x00 }, 0, 0},
+};
+
+class CustomBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ Pmic* pmic_ = nullptr;
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ EspVideo* camera_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void)
+ {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = DISPLAY_MISO_PIN;
+ buscfg.sclk_io_num = DISPLAY_CLK_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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+ void InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAM_PIN_D0,
+ [1] = CAM_PIN_D1,
+ [2] = CAM_PIN_D2,
+ [3] = CAM_PIN_D3,
+ [4] = CAM_PIN_D4,
+ [5] = CAM_PIN_D5,
+ [6] = CAM_PIN_D6,
+ [7] = CAM_PIN_D7,
+ },
+ .vsync_io = CAM_PIN_VSYNC,
+ .de_io = CAM_PIN_HREF,
+ .pclk_io = CAM_PIN_PCLK,
+ .xclk_io = CAM_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false, // 涓嶅垵濮嬪寲鏂扮殑 SCCB锛屼娇鐢ㄧ幇鏈夌殑 I2C 鎬荤嚎
+ .i2c_handle = i2c_bus_, // 浣跨敤鐜版湁鐨� I2C 鎬荤嚎鍙ユ焺
+ .freq = 100000, // 100kHz
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAM_PIN_RESET,
+ .pwdn_pin = CAM_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = 12000000,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+
+ }
+
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 1,
+ .mirror_x = 1,
+ .mirror_y = 1,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 40 * 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(SPI3_HOST, &io_config, &panel_io));
+
+ st7796_vendor_config_t st7796_vendor_config = {
+ .init_cmds = st7796_lcd_init_cmds,
+ .init_cmds_size = sizeof(st7796_lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t),
+ };
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &st7796_vendor_config;
+
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ // 瑙e喅閮ㄥ垎寮�鏈洪粦灞忕殑闂
+ if (esp_reset_reason() == ESP_RST_POWERON) {
+ fflush(stdout);
+ esp_restart();
+ }
+ InitializeTouch();
+ InitializeButtons();
+ InitializeCamera();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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;
+ }
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/README.md
new file mode 100644
index 0000000..aaadeae
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-S3-Touch-LCD-3.5B
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-S3-Touch-LCD-3.5B.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.h
new file mode 100644
index 0000000..c44d734
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.h
@@ -0,0 +1,78 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+#include "lvgl.h"
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_44
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_15
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_16
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_7
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SPI_MODE 0
+#define DISPLAY_CS_PIN GPIO_NUM_12
+#define DISPLAY_CLK_PIN GPIO_NUM_5
+#define DISPLAY_DATA0_PIN GPIO_NUM_1
+#define DISPLAY_DATA1_PIN GPIO_NUM_2
+#define DISPLAY_DATA2_PIN GPIO_NUM_3
+#define DISPLAY_DATA3_PIN GPIO_NUM_4
+
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+
+
+
+#define DISPLAY_WIDTH 480
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_TRANS_SIZE (DISPLAY_WIDTH * 10)
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_RGB
+#define DISPLAY_INVERT_COLOR false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define LV_DISPLAY_ROTATION LV_DISPLAY_ROTATION_90
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_6
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define PMIC_ENABLE 0
+#define TOUCH_ENABLE 1
+
+#define CAM_PIN_PWDN GPIO_NUM_NC
+#define CAM_PIN_RESET GPIO_NUM_NC
+#define CAM_PIN_VSYNC GPIO_NUM_17
+#define CAM_PIN_HREF GPIO_NUM_18
+#define CAM_PIN_PCLK GPIO_NUM_41
+#define CAM_PIN_XCLK GPIO_NUM_38
+#define CAM_PIN_SIOD GPIO_NUM_NC
+#define CAM_PIN_SIOC GPIO_NUM_NC
+#define CAM_PIN_D0 GPIO_NUM_45
+#define CAM_PIN_D1 GPIO_NUM_47
+#define CAM_PIN_D2 GPIO_NUM_48
+#define CAM_PIN_D3 GPIO_NUM_46
+#define CAM_PIN_D4 GPIO_NUM_42
+#define CAM_PIN_D5 GPIO_NUM_40
+#define CAM_PIN_D6 GPIO_NUM_39
+#define CAM_PIN_D7 GPIO_NUM_21
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.json
new file mode 100644
index 0000000..c5c8083
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/config.json
@@ -0,0 +1,20 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-3.5b",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y",
+ "CONFIG_CAMERA_OV2640=y",
+ "CONFIG_CAMERA_OV2640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV2640_DVP_YUV422_640X480_6FPS=y",
+ "CONFIG_CAMERA_OV2640_DVP_IF_FORMAT_INDEX_DEFAULT=1",
+ "CONFIG_CAMERA_OV5640=y",
+ "CONFIG_CAMERA_OV5640_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
+ "CONFIG_CAMERA_OV5640_DVP_YUV422_800X600_10FPS=y",
+ "CONFIG_CAMERA_OV5640_DVP_IF_FORMAT_INDEX_DEFAULT=0"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.cc
new file mode 100644
index 0000000..366bf0b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.cc
@@ -0,0 +1,286 @@
+
+#include "config.h"
+#include "custom_lcd_display.h"
+#include "lcd_display.h"
+#include "assets/lang_config.h"
+#include "settings.h"
+#include "board.h"
+
+#include <vector>
+#include <cstring>
+
+#include <esp_lcd_panel_io.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <freertos/semphr.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_lvgl_port.h>
+
+#define TAG "CustomLcdDisplay"
+
+
+static SemaphoreHandle_t trans_done_sem = NULL;
+static uint16_t *trans_act;
+static uint16_t *trans_buf_1;
+static uint16_t *trans_buf_2;
+
+bool CustomLcdDisplay::lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx)
+{
+ BaseType_t taskAwake = pdFALSE;
+ lv_display_t *disp_drv = (lv_display_t *)user_ctx;
+ assert(disp_drv != NULL);
+ if (trans_done_sem) {
+ xSemaphoreGiveFromISR(trans_done_sem, &taskAwake);
+ }
+ return false;
+}
+
+void CustomLcdDisplay::lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map)
+{
+ assert(drv != NULL);
+ esp_lcd_panel_handle_t panel_handle = (esp_lcd_panel_handle_t)lv_display_get_driver_data(drv);
+ assert(panel_handle != NULL);
+
+ size_t len = lv_area_get_size(area);
+ lv_draw_sw_rgb565_swap(color_map, len);
+
+ const int x_start = area->x1;
+ const int x_end = area->x2;
+ const int y_start = area->y1;
+ const int y_end = area->y2;
+ const int width = x_end - x_start + 1;
+ const int height = y_end - y_start + 1;
+
+ int32_t hor_res = lv_display_get_horizontal_resolution(drv);
+ int32_t ver_res = lv_display_get_vertical_resolution(drv);
+
+ // printf("hor_res: %ld, ver_res: %ld\r\n", hor_res, ver_res);
+ // printf("x_start: %d, x_end: %d, y_start: %d, y_end: %d, width: %d, height: %d\r\n", x_start, x_end, y_start, y_end, width, height);
+ uint16_t *from = (uint16_t *)color_map;
+ uint16_t *to = NULL;
+
+ if (DISPLAY_TRANS_SIZE > 0) {
+ assert(trans_buf_1 != NULL);
+
+ int x_draw_start = 0;
+ int x_draw_end = 0;
+ int y_draw_start = 0;
+ int y_draw_end = 0;
+ int trans_count = 0;
+
+ trans_act = trans_buf_1;
+ lv_display_rotation_t rotate = LV_DISPLAY_ROTATION;
+
+ int x_start_tmp = 0;
+ int x_end_tmp = 0;
+ int max_width = 0;
+ int trans_width = 0;
+
+ int y_start_tmp = 0;
+ int y_end_tmp = 0;
+ int max_height = 0;
+ int trans_height = 0;
+
+ if (LV_DISPLAY_ROTATION_270 == rotate || LV_DISPLAY_ROTATION_90 == rotate) {
+ max_width = ((DISPLAY_TRANS_SIZE / height) > width) ? (width) : (DISPLAY_TRANS_SIZE / height);
+ trans_count = width / max_width + (width % max_width ? (1) : (0));
+
+ x_start_tmp = x_start;
+ x_end_tmp = x_end;
+ } else {
+ max_height = ((DISPLAY_TRANS_SIZE / width) > height) ? (height) : (DISPLAY_TRANS_SIZE / width);
+ trans_count = height / max_height + (height % max_height ? (1) : (0));
+
+ y_start_tmp = y_start;
+ y_end_tmp = y_end;
+ }
+
+ for (int i = 0; i < trans_count; i++) {
+
+ if (LV_DISPLAY_ROTATION_90 == rotate) {
+ trans_width = (x_end - x_start_tmp + 1) > max_width ? max_width : (x_end - x_start_tmp + 1);
+ x_end_tmp = (x_end - x_start_tmp + 1) > max_width ? (x_start_tmp + max_width - 1) : x_end;
+ } else if (LV_DISPLAY_ROTATION_270 == rotate) {
+ trans_width = (x_end_tmp - x_start + 1) > max_width ? max_width : (x_end_tmp - x_start + 1);
+ x_start_tmp = (x_end_tmp - x_start + 1) > max_width ? (x_end_tmp - trans_width + 1) : x_start;
+ } else if (LV_DISPLAY_ROTATION_0 == rotate) {
+ trans_height = (y_end - y_start_tmp + 1) > max_height ? max_height : (y_end - y_start_tmp + 1);
+ y_end_tmp = (y_end - y_start_tmp + 1) > max_height ? (y_start_tmp + max_height - 1) : y_end;
+ } else {
+ trans_height = (y_end_tmp - y_start + 1) > max_height ? max_height : (y_end_tmp - y_start + 1);
+ y_start_tmp = (y_end_tmp - y_start + 1) > max_height ? (y_end_tmp - max_height + 1) : y_start;
+ }
+
+ trans_act = (trans_act == trans_buf_1) ? (trans_buf_2) : (trans_buf_1);
+ to = trans_act;
+
+ switch (rotate) {
+ case LV_DISPLAY_ROTATION_90:
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < trans_width; x++) {
+ *(to + x * height + (height - y - 1)) = *(from + y * width + x_start_tmp + x);
+ }
+ }
+ x_draw_start = ver_res - y_end - 1;
+ x_draw_end = ver_res - y_start - 1;
+ y_draw_start = x_start_tmp;
+ y_draw_end = x_end_tmp;
+ break;
+ case LV_DISPLAY_ROTATION_270:
+ for (int y = 0; y < height; y++) {
+ for (int x = 0; x < trans_width; x++) {
+ *(to + (trans_width - x - 1) * height + y) = *(from + y * width + x_start_tmp + x);
+ }
+ }
+ x_draw_start = y_start;
+ x_draw_end = y_end;
+ y_draw_start = hor_res - x_end_tmp - 1;
+ y_draw_end = hor_res - x_start_tmp - 1;
+ break;
+ case LV_DISPLAY_ROTATION_180:
+ for (int y = 0; y < trans_height; y++) {
+ for (int x = 0; x < width; x++) {
+ *(to + (trans_height - y - 1)*width + (width - x - 1)) = *(from + y_start_tmp * width + y * (width) + x);
+ }
+ }
+ x_draw_start = hor_res - x_end - 1;
+ x_draw_end = hor_res - x_start - 1;
+ y_draw_start = ver_res - y_end_tmp - 1;
+ y_draw_end = ver_res - y_start_tmp - 1;
+ break;
+ case LV_DISPLAY_ROTATION_0:
+ for (int y = 0; y < trans_height; y++) {
+ for (int x = 0; x < width; x++) {
+ *(to + y * (width) + x) = *(from + y_start_tmp * width + y * (width) + x);
+ }
+ }
+ x_draw_start = x_start;
+ x_draw_end = x_end;
+ y_draw_start = y_start_tmp;
+ y_draw_end = y_end_tmp;
+ break;
+ default:
+ break;
+ }
+
+ if (0 == i) {
+ // if (disp_ctx->draw_wait_cb) {
+ // disp_ctx->draw_wait_cb(disp_ctx->panel_handle->user_data);
+ // }
+ xSemaphoreGive(trans_done_sem);
+ }
+
+ xSemaphoreTake(trans_done_sem, portMAX_DELAY);
+ // printf("i: %d, x_draw_start: %d, x_draw_end: %d, y_draw_start: %d, y_draw_end: %d\r\n", i, x_draw_start, x_draw_end, y_draw_start, y_draw_end);
+ esp_lcd_panel_draw_bitmap(panel_handle, x_draw_start, y_draw_start, x_draw_end + 1, y_draw_end + 1, to);
+
+ if (LV_DISPLAY_ROTATION_90 == rotate) {
+ x_start_tmp += max_width;
+ } else if (LV_DISPLAY_ROTATION_270 == rotate) {
+ x_end_tmp -= max_width;
+ } if (LV_DISPLAY_ROTATION_0 == rotate) {
+ y_start_tmp += max_height;
+ } else {
+ y_end_tmp -= max_height;
+ }
+ }
+ } else {
+ esp_lcd_panel_draw_bitmap(panel_handle, x_start, y_start, x_end + 1, y_end + 1, color_map);
+ }
+ lv_disp_flush_ready(drv);
+}
+
+CustomLcdDisplay::CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : LcdDisplay(panel_io, panel, width, height) {
+ // width_ = width;
+ // height_ = height;
+
+ // draw white
+ std::vector<uint16_t> buffer(width_, 0xFFFF);
+ for (int y = 0; y < height_; y++) {
+ esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+ ESP_LOGI(TAG, "Initialize LVGL port");
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 1;
+ port_cfg.timer_period_ms = 50;
+ lvgl_port_init(&port_cfg);
+ trans_done_sem = xSemaphoreCreateCounting(1, 0);
+ trans_buf_1 = (uint16_t *)heap_caps_malloc(DISPLAY_TRANS_SIZE * sizeof(uint16_t), MALLOC_CAP_DMA);
+ trans_buf_2 = (uint16_t *)heap_caps_malloc(DISPLAY_TRANS_SIZE * sizeof(uint16_t), MALLOC_CAP_DMA);
+#if 0
+ ESP_LOGI(TAG, "Adding LCD screen");
+ const lvgl_port_display_cfg_t display_cfg = {
+ .io_handle = panel_io_,
+ .panel_handle = panel_,
+ .control_handle = nullptr,
+ .buffer_size = static_cast<uint32_t>(width_ * height_),
+ .double_buffer = false,
+ .trans_size = 0,
+ .hres = static_cast<uint32_t>(width_),
+ .vres = static_cast<uint32_t>(height_),
+ .monochrome = false,
+ .rotation = {
+ .swap_xy = swap_xy,
+ .mirror_x = mirror_x,
+ .mirror_y = mirror_y,
+ },
+ .color_format = LV_COLOR_FORMAT_RGB565,
+ .flags = {
+ .buff_dma = 0,
+ .buff_spiram = 1,
+ .sw_rotate = 0,
+ .swap_bytes = 1,
+ .full_refresh = 1,
+ .direct_mode = 0,
+ },
+ };
+
+ display_ = lvgl_port_add_disp(&display_cfg);
+ lv_display_set_flush_cb(display_, lvgl_port_flush_callback);
+#else
+
+ uint32_t buffer_size = 0;
+ lv_color_t *buf1 = NULL;
+ lvgl_port_lock(0);
+ uint8_t color_bytes = lv_color_format_get_size(LV_COLOR_FORMAT_RGB565);
+ display_ = lv_display_create(width_, height_);
+ lv_display_set_flush_cb(display_, lvgl_port_flush_callback);
+ buffer_size = width_ * height_;
+ buf1 = (lv_color_t *)heap_caps_aligned_alloc(1, buffer_size * color_bytes, MALLOC_CAP_SPIRAM);
+ lv_display_set_buffers(display_, buf1, NULL, buffer_size * color_bytes, LV_DISPLAY_RENDER_MODE_FULL);
+ lv_display_set_driver_data(display_, panel_);
+ lvgl_port_unlock();
+
+#endif
+
+ esp_lcd_panel_io_callbacks_t cbs = {
+ .on_color_trans_done = lvgl_port_flush_io_ready_callback,
+ };
+ /* Register done callback */
+ esp_lcd_panel_io_register_event_callbacks(panel_io_, &cbs, display_);
+
+ esp_lcd_panel_disp_on_off(panel_, false);
+
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ if (offset_x != 0 || offset_y != 0) {
+ lv_display_set_offset(display_, offset_x, offset_y);
+ }
+
+ // Note: SetupUI() should be called by Application::Initialize(), not in constructor
+ // to ensure lvgl objects are created after the display is fully initialized.
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.h
new file mode 100644
index 0000000..c3a9900
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/custom_lcd_display.h
@@ -0,0 +1,19 @@
+#ifndef __CUSTOM_LCD_DISPLAY_H__
+#define __CUSTOM_LCD_DISPLAY_H__
+
+#include "lcd_display.h"
+
+// // SPI LCD鏄剧ず鍣�
+class CustomLcdDisplay : public LcdDisplay {
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy);
+private:
+ static bool lvgl_port_flush_io_ready_callback(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_io_event_data_t *edata, void *user_ctx);
+ static void lvgl_port_flush_callback(lv_display_t *drv, const lv_area_t *area, uint8_t *color_map);
+};
+
+
+
+#endif // __CUSTOM_LCD_DISPLAY_H__
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc
new file mode 100644
index 0000000..fc2d360
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-3.5b/waveshare-s3-touch-lcd-3.5b.cc
@@ -0,0 +1,365 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+
+#include "axp2101.h"
+#include "power_save_timer.h"
+
+#include "esp_lcd_axs15231b.h"
+
+#include "custom_lcd_display.h"
+
+#include <esp_lcd_touch_ft5x06.h>
+
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+#include "esp_video.h"
+
+#define TAG "waveshare_lcd_3_5b"
+
+static const axs15231b_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x5A, 0xA5}, 8, 0},
+ {0xA0, (uint8_t[]){0xC0, 0x10, 0x00, 0x02, 0x00, 0x00, 0x04, 0x3F, 0x20, 0x05, 0x3F, 0x3F, 0x00, 0x00, 0x00, 0x00, 0x00}, 17, 0},
+ {0xA2, (uint8_t[]){0x30, 0x3C, 0x24, 0x14, 0xD0, 0x20, 0xFF, 0xE0, 0x40, 0x19, 0x80, 0x80, 0x80, 0x20, 0xf9, 0x10, 0x02, 0xff, 0xff, 0xF0, 0x90, 0x01, 0x32, 0xA0, 0x91, 0xE0, 0x20, 0x7F, 0xFF, 0x00, 0x5A}, 31, 0},
+ {0xD0, (uint8_t[]){0xE0, 0x40, 0x51, 0x24, 0x08, 0x05, 0x10, 0x01, 0x20, 0x15, 0x42, 0xC2, 0x22, 0x22, 0xAA, 0x03, 0x10, 0x12, 0x60, 0x14, 0x1E, 0x51, 0x15, 0x00, 0x8A, 0x20, 0x00, 0x03, 0x3A, 0x12}, 30, 0},
+ {0xA3, (uint8_t[]){0xA0, 0x06, 0xAa, 0x00, 0x08, 0x02, 0x0A, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, 0x55, 0x55}, 22, 0},
+ {0xC1, (uint8_t[]){0x31, 0x04, 0x02, 0x02, 0x71, 0x05, 0x24, 0x55, 0x02, 0x00, 0x41, 0x00, 0x53, 0xFF, 0xFF, 0xFF, 0x4F, 0x52, 0x00, 0x4F, 0x52, 0x00, 0x45, 0x3B, 0x0B, 0x02, 0x0d, 0x00, 0xFF, 0x40}, 30, 0},
+ {0xC3, (uint8_t[]){0x00, 0x00, 0x00, 0x50, 0x03, 0x00, 0x00, 0x00, 0x01, 0x80, 0x01}, 11, 0},
+ {0xC4, (uint8_t[]){0x00, 0x24, 0x33, 0x80, 0x00, 0xea, 0x64, 0x32, 0xC8, 0x64, 0xC8, 0x32, 0x90, 0x90, 0x11, 0x06, 0xDC, 0xFA, 0x00, 0x00, 0x80, 0xFE, 0x10, 0x10, 0x00, 0x0A, 0x0A, 0x44, 0x50}, 29, 0},
+ {0xC5, (uint8_t[]){0x18, 0x00, 0x00, 0x03, 0xFE, 0x3A, 0x4A, 0x20, 0x30, 0x10, 0x88, 0xDE, 0x0D, 0x08, 0x0F, 0x0F, 0x01, 0x3A, 0x4A, 0x20, 0x10, 0x10, 0x00}, 23, 0},
+ {0xC6, (uint8_t[]){0x05, 0x0A, 0x05, 0x0A, 0x00, 0xE0, 0x2E, 0x0B, 0x12, 0x22, 0x12, 0x22, 0x01, 0x03, 0x00, 0x3F, 0x6A, 0x18, 0xC8, 0x22}, 20, 0},
+ {0xC7, (uint8_t[]){0x50, 0x32, 0x28, 0x00, 0xa2, 0x80, 0x8f, 0x00, 0x80, 0xff, 0x07, 0x11, 0x9c, 0x67, 0xff, 0x24, 0x0c, 0x0d, 0x0e, 0x0f}, 20, 0},
+ {0xC9, (uint8_t[]){0x33, 0x44, 0x44, 0x01}, 4, 0},
+ {0xCF, (uint8_t[]){0x2C, 0x1E, 0x88, 0x58, 0x13, 0x18, 0x56, 0x18, 0x1E, 0x68, 0x88, 0x00, 0x65, 0x09, 0x22, 0xC4, 0x0C, 0x77, 0x22, 0x44, 0xAA, 0x55, 0x08, 0x08, 0x12, 0xA0, 0x08}, 27, 0},
+ {0xD5, (uint8_t[]){0x40, 0x8E, 0x8D, 0x01, 0x35, 0x04, 0x92, 0x74, 0x04, 0x92, 0x74, 0x04, 0x08, 0x6A, 0x04, 0x46, 0x03, 0x03, 0x03, 0x03, 0x82, 0x01, 0x03, 0x00, 0xE0, 0x51, 0xA1, 0x00, 0x00, 0x00}, 30, 0},
+ {0xD6, (uint8_t[]){0x10, 0x32, 0x54, 0x76, 0x98, 0xBA, 0xDC, 0xFE, 0x93, 0x00, 0x01, 0x83, 0x07, 0x07, 0x00, 0x07, 0x07, 0x00, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x00, 0x84, 0x00, 0x20, 0x01, 0x00}, 30, 0},
+ {0xD7, (uint8_t[]){0x03, 0x01, 0x0b, 0x09, 0x0f, 0x0d, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19, 0x40, 0x8E, 0x04, 0x00, 0x20, 0xA0, 0x1F}, 19, 0},
+ {0xD8, (uint8_t[]){0x02, 0x00, 0x0a, 0x08, 0x0e, 0x0c, 0x1E, 0x1F, 0x18, 0x1d, 0x1f, 0x19}, 12, 0},
+ {0xD9, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0},
+ {0xDD, (uint8_t[]){0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F, 0x1F}, 12, 0},
+ {0xDF, (uint8_t[]){0x44, 0x73, 0x4B, 0x69, 0x00, 0x0A, 0x02, 0x90}, 8, 0},
+ {0xE0, (uint8_t[]){0x3B, 0x28, 0x10, 0x16, 0x0c, 0x06, 0x11, 0x28, 0x5c, 0x21, 0x0D, 0x35, 0x13, 0x2C, 0x33, 0x28, 0x0D}, 17, 0},
+ {0xE1, (uint8_t[]){0x37, 0x28, 0x10, 0x16, 0x0b, 0x06, 0x11, 0x28, 0x5C, 0x21, 0x0D, 0x35, 0x14, 0x2C, 0x33, 0x28, 0x0F}, 17, 0},
+ {0xE2, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0},
+ {0xE3, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x35, 0x44, 0x32, 0x0C, 0x14, 0x14, 0x36, 0x32, 0x2F, 0x0F}, 17, 0},
+ {0xE4, (uint8_t[]){0x3B, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0D}, 17, 0},
+ {0xE5, (uint8_t[]){0x37, 0x07, 0x12, 0x18, 0x0E, 0x0D, 0x17, 0x39, 0x44, 0x2E, 0x0C, 0x14, 0x14, 0x36, 0x3A, 0x2F, 0x0F}, 17, 0},
+ {0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x82, 0xAF, 0xAA, 0xAA, 0x80, 0x10, 0x30, 0x40, 0x40, 0x20, 0xFF, 0x60, 0x30}, 16, 0},
+ {0xA4, (uint8_t[]){0x85, 0x85, 0x95, 0x85}, 4, 0},
+ {0xBB, (uint8_t[]){0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, 8, 0},
+ {0x13, (uint8_t[]){0x00}, 0, 0},
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x2C, (uint8_t[]){0x00, 0x00, 0x00, 0x00}, 4, 0},
+ {0x2a, (uint8_t[]){0x00, 0x00, 0x01, 0x3f}, 4, 0},
+ {0x2b, (uint8_t[]){0x00, 0x00, 0x01, 0xdf}, 4, 0}};
+
+class Pmic : public Axp2101 {
+ public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ WriteReg(0x96, (1500 - 500) / 100);
+ WriteReg(0x97, (2800 - 500) / 100);
+
+ // Enable ALDO1 BLDO1 BLDO2
+ WriteReg(0x90, 0x31);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+ };
+
+class CustomBoard : public WifiBoard {
+private:
+ Button boot_button_;
+ Pmic* pmic_ = nullptr;
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ EspVideo* camera_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void)
+ {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander);
+ if(ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, IO_EXPANDER_OUTPUT);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1, 0);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.data0_io_num = DISPLAY_DATA0_PIN;
+ buscfg.data1_io_num = DISPLAY_DATA1_PIN;
+ buscfg.data2_io_num = DISPLAY_DATA2_PIN;
+ buscfg.data3_io_num = DISPLAY_DATA3_PIN;
+ buscfg.sclk_io_num = DISPLAY_CLK_PIN;
+ buscfg.max_transfer_sz = DISPLAY_TRANS_SIZE * sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeCamera() {
+ static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
+ .data_width = CAM_CTLR_DATA_WIDTH_8,
+ .data_io = {
+ [0] = CAM_PIN_D0,
+ [1] = CAM_PIN_D1,
+ [2] = CAM_PIN_D2,
+ [3] = CAM_PIN_D3,
+ [4] = CAM_PIN_D4,
+ [5] = CAM_PIN_D5,
+ [6] = CAM_PIN_D6,
+ [7] = CAM_PIN_D7,
+ },
+ .vsync_io = CAM_PIN_VSYNC,
+ .de_io = CAM_PIN_HREF,
+ .pclk_io = CAM_PIN_PCLK,
+ .xclk_io = CAM_PIN_XCLK,
+ };
+
+ esp_video_init_sccb_config_t sccb_config = {
+ .init_sccb = false, // 涓嶅垵濮嬪寲鏂扮殑 SCCB锛屼娇鐢ㄧ幇鏈夌殑 I2C 鎬荤嚎
+ .i2c_handle = i2c_bus_, // 浣跨敤鐜版湁鐨� I2C 鎬荤嚎鍙ユ焺
+ .freq = 100000, // 100kHz
+ };
+
+ esp_video_init_dvp_config_t dvp_config = {
+ .sccb_config = sccb_config,
+ .reset_pin = CAM_PIN_RESET,
+ .pwdn_pin = CAM_PIN_PWDN,
+ .dvp_pin = dvp_pin_config,
+ .xclk_freq = 12000000,
+ };
+
+ esp_video_init_config_t video_config = {
+ .dvp = &dvp_config,
+ };
+
+ camera_ = new EspVideo(video_config);
+
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = AXS15231B_PANEL_IO_QSPI_CONFIG(
+ DISPLAY_CS_PIN,
+ NULL,
+ NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGI(TAG, "Install LCD driver");
+ const axs15231b_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds, // Uncomment these line if use custom initialization commands
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .flags = {
+ .use_qspi_interface = 1,
+ },
+ };
+ esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = DISPLAY_RST_PIN,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .vendor_config = (void *)&vendor_config,
+ };
+ esp_lcd_new_panel_axs15231b(panel_io, &panel_config, &panel);
+
+ esp_lcd_panel_reset(panel);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
+ // esp_lcd_panel_disp_on_off(panel, false);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+
+ display_ = new CustomLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 1,
+ .mirror_x = 1,
+ .mirror_y = 1,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = ESP_LCD_TOUCH_IO_I2C_AXS15231B_CONFIG();
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_axs15231b(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+public:
+ CustomBoard() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+
+ InitializeI2c();
+ InitializeTca9554();
+
+ InitializeAxp2101();
+#if PMIC_ENABLE
+ InitializePowerSaveTimer();
+#endif
+ InitializeSpi();
+ InitializeLcdDisplay();
+#if TOUCH_ENABLE
+ InitializeTouch();
+#endif
+ InitializeButtons();
+ InitializeCamera();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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;
+ }
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+
+#if PMIC_ENABLE
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+#endif
+};
+
+DECLARE_BOARD(CustomBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/README.md
new file mode 100755
index 0000000..a3066bc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/README.md
@@ -0,0 +1,43 @@
+# 浣跨敤璇存槑
+
+* [ESP32-S3-Touch-LCD-4.3C docs](https://www.waveshare.com/esp32-s3-touch-lcd-4.3c.htm)
+
+## 蹇�熶綋楠�
+
+涓嬭浇缂栬瘧濂界殑 [鍥轰欢](https://files.waveshare.com/wiki/ESP32-S3-Touch-LCD-4.3C/ESP32-S3-Touch-LCD-4.3C-Xiaozhi.bin)
+
+```shell
+esptool.py --chip esp32s3 -p /dev/ttyACM0 -b 460800 --before=default_reset --after=hard_reset write_flash --flash_mode dio --flash_freq 80m --flash_size 16MB 0x00 ESP32-S3-Touch-LCD-4.3C-Xiaozhi.bin
+```
+
+## 鍩虹浣跨敤
+
+* idf version: v5.5-dev
+
+1. 璁剧疆缂栬瘧鐩爣涓� esp32s3
+
+```shell
+idf.py set-target esp32s3
+```
+
+2. 淇敼閰嶇疆
+
+```shell
+cp main/boards/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c sdkconfig
+```
+
+3. 缂栬瘧鐑у綍绋嬪簭
+
+```shell
+idf.py build flash monitor
+```
+
+## log
+
+@2025/05/17 娴嬭瘯闂
+
+1. 杩斿洖搴旂敤鐣岄潰鏃讹紝闇�瑕佸瓨鍦ㄦ鍒嗗尯锛屽惁鍒欐棤鏁�
+2.
+3.
+
+## TODO
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.h
new file mode 100755
index 0000000..5c8b0f9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.h
@@ -0,0 +1,89 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include "custom_io_expander_ch32v003.h"
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define BSP_I2S_SCLK (GPIO_NUM_44)
+#define BSP_I2S_MCLK (GPIO_NUM_6)
+#define BSP_I2S_LCLK (GPIO_NUM_16)
+#define BSP_I2S_DOUT (GPIO_NUM_15) // To Codec ES8311
+#define BSP_I2S_DSIN (GPIO_NUM_43) // From ADC ES7210
+
+#define BSP_POWER_AMP_IO (IO_EXPANDER_PIN_NUM_3)
+#define BSP_PA_PIN (GPIO_NUM_NC)
+
+#define BSP_I2C_SCL (GPIO_NUM_9)
+#define BSP_I2C_SDA (GPIO_NUM_8)
+
+#define BSP_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define BSP_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define BSP_IO_EXPANDER_I2C_ADDRESS CUSTOM_IO_EXPANDER_I2C_CH32V003_ADDRESS
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+/* Display */
+#define BSP_LCD_VSYNC (GPIO_NUM_3)
+#define BSP_LCD_HSYNC (GPIO_NUM_46)
+#define BSP_LCD_DE (GPIO_NUM_5)
+#define BSP_LCD_PCLK (GPIO_NUM_7)
+#define BSP_LCD_DISP (GPIO_NUM_NC)
+
+// Blue data signals
+#define BSP_LCD_DATA0 (GPIO_NUM_14) ///< B3
+#define BSP_LCD_DATA1 (GPIO_NUM_38) ///< B4
+#define BSP_LCD_DATA2 (GPIO_NUM_18) ///< B5
+#define BSP_LCD_DATA3 (GPIO_NUM_17) ///< B6
+#define BSP_LCD_DATA4 (GPIO_NUM_10) ///< B7
+
+// Green data signals
+#define BSP_LCD_DATA5 (GPIO_NUM_39) ///< G2
+#define BSP_LCD_DATA6 (GPIO_NUM_0) ///< G3
+#define BSP_LCD_DATA7 (GPIO_NUM_45) ///< G4
+#define BSP_LCD_DATA8 (GPIO_NUM_48) ///< G5
+#define BSP_LCD_DATA9 (GPIO_NUM_47) ///< G6
+#define BSP_LCD_DATA10 (GPIO_NUM_21) ///< G7
+
+// Red data signals
+#define BSP_LCD_DATA11 (GPIO_NUM_1) ///< R3
+#define BSP_LCD_DATA12 (GPIO_NUM_2) ///< R4
+#define BSP_LCD_DATA13 (GPIO_NUM_42) ///< R5
+#define BSP_LCD_DATA14 (GPIO_NUM_41) ///< R6
+#define BSP_LCD_DATA15 (GPIO_NUM_40) ///< R7
+
+#define BSP_LCD_BACKLIGHT (IO_EXPANDER_PIN_NUM_2)
+#define BSP_LCD_RST (GPIO_NUM_NC)
+#define BSP_LCD_TOUCH_RST (IO_EXPANDER_PIN_NUM_1)
+#define BSP_LCD_TOUCH_INT (GPIO_NUM_4)
+
+/* LCD display color format */
+#define BSP_LCD_COLOR_FORMAT (ESP_LCD_COLOR_FORMAT_RGB565)
+/* LCD display color bytes endianess */
+#define BSP_LCD_BIGENDIAN (1)
+/* LCD display color bits */
+#define BSP_LCD_BITS_PER_PIXEL (16)
+#define BSP_LCD_BIT_PER_PIXEL (18)
+#define BSP_RGB_DATA_WIDTH (16)
+
+/* LCD display definition */
+#define BSP_LCD_H_RES (800)
+#define BSP_LCD_V_RES (480)
+
+
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.json
new file mode 100755
index 0000000..5f837ff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/config.json
@@ -0,0 +1,15 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-4.3c",
+ "sdkconfig_append": [
+ "CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_4_3C=y",
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y",
+ "CONFIG_SPIRAM_RODATA=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/esp32_s3_touch_lcd_4.3c.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/esp32_s3_touch_lcd_4.3c.cc
new file mode 100755
index 0000000..f9a41a0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/esp32_s3_touch_lcd_4.3c.cc
@@ -0,0 +1,252 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+
+#include <esp_lcd_touch_gt911.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_rgb.h>
+
+#define TAG "WaveshareEsp32s3TouchLCD43c"
+
+class CustomBacklight : public Backlight {
+public:
+ CustomBacklight(esp_io_expander_handle_t io_handle)
+ : Backlight(), io_handle_(io_handle) {}
+
+protected:
+ esp_io_expander_handle_t io_handle_;
+
+ virtual void SetBrightnessImpl(uint8_t brightness) override {
+ if (brightness > 100) brightness = 100;
+ int flipped_brightness = 100 - brightness;
+
+ custom_io_expander_set_pwm(io_handle_, flipped_brightness * 255 / 100);
+ }
+};
+
+
+class WaveshareEsp32s3TouchLCD43c : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ LcdDisplay* display_;
+ esp_io_expander_handle_t io_expander = NULL;
+ PowerSaveTimer* power_save_timer_;
+ CustomBacklight *backlight_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();});
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeGpio() {
+ // Zero-initialize the GPIO configuration structure
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE; // Disable interrupts for this pin
+ io_conf.pin_bit_mask = 1ULL << BSP_LCD_TOUCH_INT; // Select the GPIO pin using a bitmask
+ io_conf.mode = GPIO_MODE_OUTPUT; // Set pin as output
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE; // Disable pull-up
+ gpio_config(&io_conf); // Apply the configuration
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_0,
+ .sda_io_num = BSP_I2C_SDA,
+ .scl_io_num = BSP_I2C_SCL,
+ .clk_source = I2C_CLK_SRC_DEFAULT,
+ .glitch_ignore_cnt = 7,
+ .trans_queue_depth = 0,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeCustomio(void) {
+ custom_io_expander_new_i2c_ch32v003(i2c_bus_, BSP_IO_EXPANDER_I2C_ADDRESS, &io_expander);
+ esp_io_expander_set_dir(io_expander, BSP_POWER_AMP_IO | BSP_LCD_BACKLIGHT | BSP_LCD_TOUCH_RST , IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(io_expander, BSP_POWER_AMP_IO | BSP_LCD_BACKLIGHT | BSP_LCD_TOUCH_RST , 1);
+
+ esp_io_expander_set_level(io_expander, BSP_LCD_TOUCH_RST, 0);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ gpio_set_level(BSP_LCD_TOUCH_INT, 0);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_io_expander_set_level(io_expander, BSP_LCD_TOUCH_RST, 1);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ }
+
+ void InitializeRGB() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel_handle = nullptr;
+
+ esp_lcd_rgb_panel_config_t rgb_config = {
+ .clk_src = LCD_CLK_SRC_DEFAULT,
+ .timings = {
+ .pclk_hz = 16 * 1000 * 1000,
+ .h_res = BSP_LCD_H_RES,
+ .v_res = BSP_LCD_V_RES,
+ .hsync_pulse_width = 4,
+ .hsync_back_porch = 4,
+ .hsync_front_porch = 8,
+ .vsync_pulse_width = 4,
+ .vsync_back_porch = 4,
+ .vsync_front_porch = 8,
+ .flags = {
+ .pclk_active_neg = true
+ }
+ },
+ .data_width = 16,
+ .bits_per_pixel = 16,
+ .num_fbs = 2,
+ .bounce_buffer_size_px = BSP_LCD_H_RES * 10,
+ .psram_trans_align = 64,
+ .hsync_gpio_num = BSP_LCD_HSYNC,
+ .vsync_gpio_num = BSP_LCD_VSYNC,
+ .de_gpio_num = BSP_LCD_DE,
+ .pclk_gpio_num = BSP_LCD_PCLK,
+ .disp_gpio_num = BSP_LCD_DISP,
+ .data_gpio_nums = {
+ BSP_LCD_DATA0, BSP_LCD_DATA1, BSP_LCD_DATA2, BSP_LCD_DATA3,
+ BSP_LCD_DATA4, BSP_LCD_DATA5, BSP_LCD_DATA6, BSP_LCD_DATA7,
+ BSP_LCD_DATA8, BSP_LCD_DATA9, BSP_LCD_DATA10, BSP_LCD_DATA11,
+ BSP_LCD_DATA12, BSP_LCD_DATA13, BSP_LCD_DATA14, BSP_LCD_DATA15
+ },
+ .flags = {
+ .fb_in_psram = 1,
+ },
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_rgb_panel(&rgb_config, &panel_handle));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle));
+
+ display_ = new RgbLcdDisplay(panel_io, panel_handle,
+ BSP_LCD_H_RES, BSP_LCD_V_RES, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+
+ backlight_ = new CustomBacklight(io_expander);
+ backlight_->RestoreBrightness();
+ }
+
+ void InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = BSP_LCD_H_RES - 1,
+ .y_max = BSP_LCD_V_RES - 1,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+
+ esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle);
+
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ // Initialization tool
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "Reboot the device and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32s3TouchLCD43c() {
+ InitializePowerSaveTimer();
+ InitializeGpio();
+ InitializeCodecI2c();
+ InitializeCustomio();
+ InitializeRGB();
+ InitializeTouch();
+ InitializeTools();
+ GetBacklight()->SetBrightness(100);
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ AUDIO_INPUT_SAMPLE_RATE,
+ AUDIO_OUTPUT_SAMPLE_RATE,
+ BSP_I2S_MCLK,
+ BSP_I2S_SCLK,
+ BSP_I2S_LCLK,
+ BSP_I2S_DOUT,
+ BSP_I2S_DSIN,
+ BSP_PA_PIN,
+ BSP_CODEC_ES8311_ADDR,
+ BSP_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Backlight *GetBacklight() override {
+ return backlight_;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3TouchLCD43c);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c
new file mode 100755
index 0000000..2f73cb6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4.3c/sdkconfig.4_3c
@@ -0,0 +1,3306 @@
+#
+# Automatically generated file. DO NOT EDIT.
+# Espressif IoT Development Framework (ESP-IDF) 5.5.0 Project Configuration
+#
+CONFIG_SOC_ADC_SUPPORTED=y
+CONFIG_SOC_UART_SUPPORTED=y
+CONFIG_SOC_PCNT_SUPPORTED=y
+CONFIG_SOC_PHY_SUPPORTED=y
+CONFIG_SOC_WIFI_SUPPORTED=y
+CONFIG_SOC_TWAI_SUPPORTED=y
+CONFIG_SOC_GDMA_SUPPORTED=y
+CONFIG_SOC_UHCI_SUPPORTED=y
+CONFIG_SOC_AHB_GDMA_SUPPORTED=y
+CONFIG_SOC_GPTIMER_SUPPORTED=y
+CONFIG_SOC_LCDCAM_SUPPORTED=y
+CONFIG_SOC_LCDCAM_I80_LCD_SUPPORTED=y
+CONFIG_SOC_LCDCAM_RGB_LCD_SUPPORTED=y
+CONFIG_SOC_MCPWM_SUPPORTED=y
+CONFIG_SOC_DEDICATED_GPIO_SUPPORTED=y
+CONFIG_SOC_CACHE_SUPPORT_WRAP=y
+CONFIG_SOC_ULP_SUPPORTED=y
+CONFIG_SOC_ULP_FSM_SUPPORTED=y
+CONFIG_SOC_RISCV_COPROC_SUPPORTED=y
+CONFIG_SOC_BT_SUPPORTED=y
+CONFIG_SOC_USB_OTG_SUPPORTED=y
+CONFIG_SOC_USB_SERIAL_JTAG_SUPPORTED=y
+CONFIG_SOC_CCOMP_TIMER_SUPPORTED=y
+CONFIG_SOC_ASYNC_MEMCPY_SUPPORTED=y
+CONFIG_SOC_SUPPORTS_SECURE_DL_MODE=y
+CONFIG_SOC_EFUSE_KEY_PURPOSE_FIELD=y
+CONFIG_SOC_EFUSE_SUPPORTED=y
+CONFIG_SOC_SDMMC_HOST_SUPPORTED=y
+CONFIG_SOC_RTC_FAST_MEM_SUPPORTED=y
+CONFIG_SOC_RTC_SLOW_MEM_SUPPORTED=y
+CONFIG_SOC_RTC_MEM_SUPPORTED=y
+CONFIG_SOC_PSRAM_DMA_CAPABLE=y
+CONFIG_SOC_XT_WDT_SUPPORTED=y
+CONFIG_SOC_I2S_SUPPORTED=y
+CONFIG_SOC_RMT_SUPPORTED=y
+CONFIG_SOC_SDM_SUPPORTED=y
+CONFIG_SOC_GPSPI_SUPPORTED=y
+CONFIG_SOC_LEDC_SUPPORTED=y
+CONFIG_SOC_I2C_SUPPORTED=y
+CONFIG_SOC_SYSTIMER_SUPPORTED=y
+CONFIG_SOC_SUPPORT_COEXISTENCE=y
+CONFIG_SOC_TEMP_SENSOR_SUPPORTED=y
+CONFIG_SOC_AES_SUPPORTED=y
+CONFIG_SOC_MPI_SUPPORTED=y
+CONFIG_SOC_SHA_SUPPORTED=y
+CONFIG_SOC_HMAC_SUPPORTED=y
+CONFIG_SOC_DIG_SIGN_SUPPORTED=y
+CONFIG_SOC_FLASH_ENC_SUPPORTED=y
+CONFIG_SOC_SECURE_BOOT_SUPPORTED=y
+CONFIG_SOC_MEMPROT_SUPPORTED=y
+CONFIG_SOC_TOUCH_SENSOR_SUPPORTED=y
+CONFIG_SOC_BOD_SUPPORTED=y
+CONFIG_SOC_CLK_TREE_SUPPORTED=y
+CONFIG_SOC_MPU_SUPPORTED=y
+CONFIG_SOC_WDT_SUPPORTED=y
+CONFIG_SOC_SPI_FLASH_SUPPORTED=y
+CONFIG_SOC_RNG_SUPPORTED=y
+CONFIG_SOC_LIGHT_SLEEP_SUPPORTED=y
+CONFIG_SOC_DEEP_SLEEP_SUPPORTED=y
+CONFIG_SOC_LP_PERIPH_SHARE_INTERRUPT=y
+CONFIG_SOC_PM_SUPPORTED=y
+CONFIG_SOC_SIMD_INSTRUCTION_SUPPORTED=y
+CONFIG_SOC_XTAL_SUPPORT_40M=y
+CONFIG_SOC_APPCPU_HAS_CLOCK_GATING_BUG=y
+CONFIG_SOC_ADC_RTC_CTRL_SUPPORTED=y
+CONFIG_SOC_ADC_DIG_CTRL_SUPPORTED=y
+CONFIG_SOC_ADC_ARBITER_SUPPORTED=y
+CONFIG_SOC_ADC_DIG_IIR_FILTER_SUPPORTED=y
+CONFIG_SOC_ADC_MONITOR_SUPPORTED=y
+CONFIG_SOC_ADC_DMA_SUPPORTED=y
+CONFIG_SOC_ADC_PERIPH_NUM=2
+CONFIG_SOC_ADC_MAX_CHANNEL_NUM=10
+CONFIG_SOC_ADC_ATTEN_NUM=4
+CONFIG_SOC_ADC_DIGI_CONTROLLER_NUM=2
+CONFIG_SOC_ADC_PATT_LEN_MAX=24
+CONFIG_SOC_ADC_DIGI_MIN_BITWIDTH=12
+CONFIG_SOC_ADC_DIGI_MAX_BITWIDTH=12
+CONFIG_SOC_ADC_DIGI_RESULT_BYTES=4
+CONFIG_SOC_ADC_DIGI_DATA_BYTES_PER_CONV=4
+CONFIG_SOC_ADC_DIGI_IIR_FILTER_NUM=2
+CONFIG_SOC_ADC_DIGI_MONITOR_NUM=2
+CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_HIGH=83333
+CONFIG_SOC_ADC_SAMPLE_FREQ_THRES_LOW=611
+CONFIG_SOC_ADC_RTC_MIN_BITWIDTH=12
+CONFIG_SOC_ADC_RTC_MAX_BITWIDTH=12
+CONFIG_SOC_ADC_CALIBRATION_V1_SUPPORTED=y
+CONFIG_SOC_ADC_SELF_HW_CALI_SUPPORTED=y
+CONFIG_SOC_ADC_SHARED_POWER=y
+CONFIG_SOC_APB_BACKUP_DMA=y
+CONFIG_SOC_BROWNOUT_RESET_SUPPORTED=y
+CONFIG_SOC_CACHE_WRITEBACK_SUPPORTED=y
+CONFIG_SOC_CACHE_FREEZE_SUPPORTED=y
+CONFIG_SOC_CACHE_ACS_INVALID_STATE_ON_PANIC=y
+CONFIG_SOC_CPU_CORES_NUM=2
+CONFIG_SOC_CPU_INTR_NUM=32
+CONFIG_SOC_CPU_HAS_FPU=y
+CONFIG_SOC_HP_CPU_HAS_MULTIPLE_CORES=y
+CONFIG_SOC_CPU_BREAKPOINTS_NUM=2
+CONFIG_SOC_CPU_WATCHPOINTS_NUM=2
+CONFIG_SOC_CPU_WATCHPOINT_MAX_REGION_SIZE=0x40
+CONFIG_SOC_SIMD_PREFERRED_DATA_ALIGNMENT=16
+CONFIG_SOC_DS_SIGNATURE_MAX_BIT_LEN=4096
+CONFIG_SOC_DS_KEY_PARAM_MD_IV_LENGTH=16
+CONFIG_SOC_DS_KEY_CHECK_MAX_WAIT_US=1100
+CONFIG_SOC_AHB_GDMA_VERSION=1
+CONFIG_SOC_GDMA_NUM_GROUPS_MAX=1
+CONFIG_SOC_GDMA_PAIRS_PER_GROUP=5
+CONFIG_SOC_GDMA_PAIRS_PER_GROUP_MAX=5
+CONFIG_SOC_AHB_GDMA_SUPPORT_PSRAM=y
+CONFIG_SOC_GPIO_PORT=1
+CONFIG_SOC_GPIO_PIN_COUNT=49
+CONFIG_SOC_GPIO_SUPPORT_PIN_GLITCH_FILTER=y
+CONFIG_SOC_GPIO_FILTER_CLK_SUPPORT_APB=y
+CONFIG_SOC_GPIO_SUPPORT_RTC_INDEPENDENT=y
+CONFIG_SOC_GPIO_SUPPORT_FORCE_HOLD=y
+CONFIG_SOC_GPIO_VALID_GPIO_MASK=0x1FFFFFFFFFFFF
+CONFIG_SOC_GPIO_IN_RANGE_MAX=48
+CONFIG_SOC_GPIO_OUT_RANGE_MAX=48
+CONFIG_SOC_GPIO_VALID_DIGITAL_IO_PAD_MASK=0x0001FFFFFC000000
+CONFIG_SOC_GPIO_CLOCKOUT_BY_IO_MUX=y
+CONFIG_SOC_GPIO_CLOCKOUT_CHANNEL_NUM=3
+CONFIG_SOC_GPIO_SUPPORT_HOLD_IO_IN_DSLP=y
+CONFIG_SOC_DEDIC_GPIO_OUT_CHANNELS_NUM=8
+CONFIG_SOC_DEDIC_GPIO_IN_CHANNELS_NUM=8
+CONFIG_SOC_DEDIC_GPIO_OUT_AUTO_ENABLE=y
+CONFIG_SOC_I2C_NUM=2
+CONFIG_SOC_HP_I2C_NUM=2
+CONFIG_SOC_I2C_FIFO_LEN=32
+CONFIG_SOC_I2C_CMD_REG_NUM=8
+CONFIG_SOC_I2C_SUPPORT_SLAVE=y
+CONFIG_SOC_I2C_SUPPORT_HW_CLR_BUS=y
+CONFIG_SOC_I2C_SUPPORT_XTAL=y
+CONFIG_SOC_I2C_SUPPORT_RTC=y
+CONFIG_SOC_I2C_SUPPORT_10BIT_ADDR=y
+CONFIG_SOC_I2C_SLAVE_SUPPORT_BROADCAST=y
+CONFIG_SOC_I2C_SLAVE_SUPPORT_I2CRAM_ACCESS=y
+CONFIG_SOC_I2C_SLAVE_CAN_GET_STRETCH_CAUSE=y
+CONFIG_SOC_I2S_NUM=2
+CONFIG_SOC_I2S_HW_VERSION_2=y
+CONFIG_SOC_I2S_SUPPORTS_XTAL=y
+CONFIG_SOC_I2S_SUPPORTS_PLL_F160M=y
+CONFIG_SOC_I2S_SUPPORTS_PCM=y
+CONFIG_SOC_I2S_SUPPORTS_PDM=y
+CONFIG_SOC_I2S_SUPPORTS_PDM_TX=y
+CONFIG_SOC_I2S_SUPPORTS_PCM2PDM=y
+CONFIG_SOC_I2S_SUPPORTS_PDM_RX=y
+CONFIG_SOC_I2S_SUPPORTS_PDM2PCM=y
+CONFIG_SOC_I2S_PDM_MAX_TX_LINES=2
+CONFIG_SOC_I2S_PDM_MAX_RX_LINES=4
+CONFIG_SOC_I2S_SUPPORTS_TDM=y
+CONFIG_SOC_LEDC_SUPPORT_APB_CLOCK=y
+CONFIG_SOC_LEDC_SUPPORT_XTAL_CLOCK=y
+CONFIG_SOC_LEDC_TIMER_NUM=4
+CONFIG_SOC_LEDC_CHANNEL_NUM=8
+CONFIG_SOC_LEDC_TIMER_BIT_WIDTH=14
+CONFIG_SOC_LEDC_SUPPORT_FADE_STOP=y
+CONFIG_SOC_MCPWM_GROUPS=2
+CONFIG_SOC_MCPWM_TIMERS_PER_GROUP=3
+CONFIG_SOC_MCPWM_OPERATORS_PER_GROUP=3
+CONFIG_SOC_MCPWM_COMPARATORS_PER_OPERATOR=2
+CONFIG_SOC_MCPWM_GENERATORS_PER_OPERATOR=2
+CONFIG_SOC_MCPWM_TRIGGERS_PER_OPERATOR=2
+CONFIG_SOC_MCPWM_GPIO_FAULTS_PER_GROUP=3
+CONFIG_SOC_MCPWM_CAPTURE_TIMERS_PER_GROUP=y
+CONFIG_SOC_MCPWM_CAPTURE_CHANNELS_PER_TIMER=3
+CONFIG_SOC_MCPWM_GPIO_SYNCHROS_PER_GROUP=3
+CONFIG_SOC_MCPWM_SWSYNC_CAN_PROPAGATE=y
+CONFIG_SOC_MMU_LINEAR_ADDRESS_REGION_NUM=1
+CONFIG_SOC_MMU_PERIPH_NUM=1
+CONFIG_SOC_MPU_MIN_REGION_SIZE=0x20000000
+CONFIG_SOC_MPU_REGIONS_MAX_NUM=8
+CONFIG_SOC_PCNT_GROUPS=1
+CONFIG_SOC_PCNT_UNITS_PER_GROUP=4
+CONFIG_SOC_PCNT_CHANNELS_PER_UNIT=2
+CONFIG_SOC_PCNT_THRES_POINT_PER_UNIT=2
+CONFIG_SOC_RMT_GROUPS=1
+CONFIG_SOC_RMT_TX_CANDIDATES_PER_GROUP=4
+CONFIG_SOC_RMT_RX_CANDIDATES_PER_GROUP=4
+CONFIG_SOC_RMT_CHANNELS_PER_GROUP=8
+CONFIG_SOC_RMT_MEM_WORDS_PER_CHANNEL=48
+CONFIG_SOC_RMT_SUPPORT_RX_PINGPONG=y
+CONFIG_SOC_RMT_SUPPORT_RX_DEMODULATION=y
+CONFIG_SOC_RMT_SUPPORT_TX_ASYNC_STOP=y
+CONFIG_SOC_RMT_SUPPORT_TX_LOOP_COUNT=y
+CONFIG_SOC_RMT_SUPPORT_TX_LOOP_AUTO_STOP=y
+CONFIG_SOC_RMT_SUPPORT_TX_SYNCHRO=y
+CONFIG_SOC_RMT_SUPPORT_TX_CARRIER_DATA_ONLY=y
+CONFIG_SOC_RMT_SUPPORT_XTAL=y
+CONFIG_SOC_RMT_SUPPORT_RC_FAST=y
+CONFIG_SOC_RMT_SUPPORT_APB=y
+CONFIG_SOC_RMT_SUPPORT_DMA=y
+CONFIG_SOC_LCD_I80_SUPPORTED=y
+CONFIG_SOC_LCD_RGB_SUPPORTED=y
+CONFIG_SOC_LCD_I80_BUSES=1
+CONFIG_SOC_LCD_RGB_PANELS=1
+CONFIG_SOC_LCD_I80_BUS_WIDTH=16
+CONFIG_SOC_LCD_RGB_DATA_WIDTH=16
+CONFIG_SOC_LCD_SUPPORT_RGB_YUV_CONV=y
+CONFIG_SOC_LCDCAM_I80_NUM_BUSES=1
+CONFIG_SOC_LCDCAM_I80_BUS_WIDTH=16
+CONFIG_SOC_LCDCAM_RGB_NUM_PANELS=1
+CONFIG_SOC_LCDCAM_RGB_DATA_WIDTH=16
+CONFIG_SOC_RTC_CNTL_CPU_PD_DMA_BUS_WIDTH=128
+CONFIG_SOC_RTC_CNTL_CPU_PD_REG_FILE_NUM=549
+CONFIG_SOC_RTC_CNTL_TAGMEM_PD_DMA_BUS_WIDTH=128
+CONFIG_SOC_RTCIO_PIN_COUNT=22
+CONFIG_SOC_RTCIO_INPUT_OUTPUT_SUPPORTED=y
+CONFIG_SOC_RTCIO_HOLD_SUPPORTED=y
+CONFIG_SOC_RTCIO_WAKE_SUPPORTED=y
+CONFIG_SOC_LP_IO_CLOCK_IS_INDEPENDENT=y
+CONFIG_SOC_SDM_GROUPS=1
+CONFIG_SOC_SDM_CHANNELS_PER_GROUP=8
+CONFIG_SOC_SDM_CLK_SUPPORT_APB=y
+CONFIG_SOC_SPI_PERIPH_NUM=3
+CONFIG_SOC_SPI_MAX_CS_NUM=6
+CONFIG_SOC_SPI_MAXIMUM_BUFFER_SIZE=64
+CONFIG_SOC_SPI_SUPPORT_DDRCLK=y
+CONFIG_SOC_SPI_SLAVE_SUPPORT_SEG_TRANS=y
+CONFIG_SOC_SPI_SUPPORT_CD_SIG=y
+CONFIG_SOC_SPI_SUPPORT_CONTINUOUS_TRANS=y
+CONFIG_SOC_SPI_SUPPORT_SLAVE_HD_VER2=y
+CONFIG_SOC_SPI_SUPPORT_CLK_APB=y
+CONFIG_SOC_SPI_SUPPORT_CLK_XTAL=y
+CONFIG_SOC_SPI_PERIPH_SUPPORT_CONTROL_DUMMY_OUT=y
+CONFIG_SOC_MEMSPI_IS_INDEPENDENT=y
+CONFIG_SOC_SPI_MAX_PRE_DIVIDER=16
+CONFIG_SOC_SPI_SUPPORT_OCT=y
+CONFIG_SOC_SPI_SCT_SUPPORTED=y
+CONFIG_SOC_SPI_SCT_REG_NUM=14
+CONFIG_SOC_SPI_SCT_BUFFER_NUM_MAX=y
+CONFIG_SOC_SPI_SCT_CONF_BITLEN_MAX=0x3FFFA
+CONFIG_SOC_MEMSPI_SRC_FREQ_120M_SUPPORTED=y
+CONFIG_SOC_MEMSPI_SRC_FREQ_80M_SUPPORTED=y
+CONFIG_SOC_MEMSPI_SRC_FREQ_40M_SUPPORTED=y
+CONFIG_SOC_MEMSPI_SRC_FREQ_20M_SUPPORTED=y
+CONFIG_SOC_SPIRAM_SUPPORTED=y
+CONFIG_SOC_SPIRAM_XIP_SUPPORTED=y
+CONFIG_SOC_SYSTIMER_COUNTER_NUM=2
+CONFIG_SOC_SYSTIMER_ALARM_NUM=3
+CONFIG_SOC_SYSTIMER_BIT_WIDTH_LO=32
+CONFIG_SOC_SYSTIMER_BIT_WIDTH_HI=20
+CONFIG_SOC_SYSTIMER_FIXED_DIVIDER=y
+CONFIG_SOC_SYSTIMER_INT_LEVEL=y
+CONFIG_SOC_SYSTIMER_ALARM_MISS_COMPENSATE=y
+CONFIG_SOC_TIMER_GROUPS=2
+CONFIG_SOC_TIMER_GROUP_TIMERS_PER_GROUP=2
+CONFIG_SOC_TIMER_GROUP_COUNTER_BIT_WIDTH=54
+CONFIG_SOC_TIMER_GROUP_SUPPORT_XTAL=y
+CONFIG_SOC_TIMER_GROUP_SUPPORT_APB=y
+CONFIG_SOC_TIMER_GROUP_TOTAL_TIMERS=4
+CONFIG_SOC_LP_TIMER_BIT_WIDTH_LO=32
+CONFIG_SOC_LP_TIMER_BIT_WIDTH_HI=16
+CONFIG_SOC_TOUCH_SENSOR_VERSION=2
+CONFIG_SOC_TOUCH_SENSOR_NUM=15
+CONFIG_SOC_TOUCH_MIN_CHAN_ID=1
+CONFIG_SOC_TOUCH_MAX_CHAN_ID=14
+CONFIG_SOC_TOUCH_SUPPORT_BENCHMARK=y
+CONFIG_SOC_TOUCH_SUPPORT_SLEEP_WAKEUP=y
+CONFIG_SOC_TOUCH_SUPPORT_WATERPROOF=y
+CONFIG_SOC_TOUCH_SUPPORT_PROX_SENSING=y
+CONFIG_SOC_TOUCH_SUPPORT_DENOISE_CHAN=y
+CONFIG_SOC_TOUCH_PROXIMITY_CHANNEL_NUM=3
+CONFIG_SOC_TOUCH_PROXIMITY_MEAS_DONE_SUPPORTED=y
+CONFIG_SOC_TOUCH_SAMPLE_CFG_NUM=1
+CONFIG_SOC_TWAI_CONTROLLER_NUM=1
+CONFIG_SOC_TWAI_MASK_FILTER_NUM=1
+CONFIG_SOC_TWAI_CLK_SUPPORT_APB=y
+CONFIG_SOC_TWAI_BRP_MIN=2
+CONFIG_SOC_TWAI_BRP_MAX=16384
+CONFIG_SOC_TWAI_SUPPORTS_RX_STATUS=y
+CONFIG_SOC_UART_NUM=3
+CONFIG_SOC_UART_HP_NUM=3
+CONFIG_SOC_UART_FIFO_LEN=128
+CONFIG_SOC_UART_BITRATE_MAX=5000000
+CONFIG_SOC_UART_SUPPORT_FSM_TX_WAIT_SEND=y
+CONFIG_SOC_UART_SUPPORT_WAKEUP_INT=y
+CONFIG_SOC_UART_SUPPORT_APB_CLK=y
+CONFIG_SOC_UART_SUPPORT_RTC_CLK=y
+CONFIG_SOC_UART_SUPPORT_XTAL_CLK=y
+CONFIG_SOC_UART_WAKEUP_SUPPORT_ACTIVE_THRESH_MODE=y
+CONFIG_SOC_UHCI_NUM=1
+CONFIG_SOC_USB_OTG_PERIPH_NUM=1
+CONFIG_SOC_SHA_DMA_MAX_BUFFER_SIZE=3968
+CONFIG_SOC_SHA_SUPPORT_DMA=y
+CONFIG_SOC_SHA_SUPPORT_RESUME=y
+CONFIG_SOC_SHA_GDMA=y
+CONFIG_SOC_SHA_SUPPORT_SHA1=y
+CONFIG_SOC_SHA_SUPPORT_SHA224=y
+CONFIG_SOC_SHA_SUPPORT_SHA256=y
+CONFIG_SOC_SHA_SUPPORT_SHA384=y
+CONFIG_SOC_SHA_SUPPORT_SHA512=y
+CONFIG_SOC_SHA_SUPPORT_SHA512_224=y
+CONFIG_SOC_SHA_SUPPORT_SHA512_256=y
+CONFIG_SOC_SHA_SUPPORT_SHA512_T=y
+CONFIG_SOC_MPI_MEM_BLOCKS_NUM=4
+CONFIG_SOC_MPI_OPERATIONS_NUM=3
+CONFIG_SOC_RSA_MAX_BIT_LEN=4096
+CONFIG_SOC_AES_SUPPORT_DMA=y
+CONFIG_SOC_AES_GDMA=y
+CONFIG_SOC_AES_SUPPORT_AES_128=y
+CONFIG_SOC_AES_SUPPORT_AES_256=y
+CONFIG_SOC_PM_SUPPORT_EXT0_WAKEUP=y
+CONFIG_SOC_PM_SUPPORT_EXT1_WAKEUP=y
+CONFIG_SOC_PM_SUPPORT_EXT_WAKEUP=y
+CONFIG_SOC_PM_SUPPORT_WIFI_WAKEUP=y
+CONFIG_SOC_PM_SUPPORT_BT_WAKEUP=y
+CONFIG_SOC_PM_SUPPORT_TOUCH_SENSOR_WAKEUP=y
+CONFIG_SOC_PM_SUPPORT_CPU_PD=y
+CONFIG_SOC_PM_SUPPORT_TAGMEM_PD=y
+CONFIG_SOC_PM_SUPPORT_RTC_PERIPH_PD=y
+CONFIG_SOC_PM_SUPPORT_RC_FAST_PD=y
+CONFIG_SOC_PM_SUPPORT_VDDSDIO_PD=y
+CONFIG_SOC_PM_SUPPORT_MAC_BB_PD=y
+CONFIG_SOC_PM_SUPPORT_MODEM_PD=y
+CONFIG_SOC_CONFIGURABLE_VDDSDIO_SUPPORTED=y
+CONFIG_SOC_PM_SUPPORT_DEEPSLEEP_CHECK_STUB_ONLY=y
+CONFIG_SOC_PM_CPU_RETENTION_BY_RTCCNTL=y
+CONFIG_SOC_PM_MODEM_RETENTION_BY_BACKUPDMA=y
+CONFIG_SOC_PM_MODEM_PD_BY_SW=y
+CONFIG_SOC_CLK_RC_FAST_D256_SUPPORTED=y
+CONFIG_SOC_RTC_SLOW_CLK_SUPPORT_RC_FAST_D256=y
+CONFIG_SOC_CLK_RC_FAST_SUPPORT_CALIBRATION=y
+CONFIG_SOC_CLK_XTAL32K_SUPPORTED=y
+CONFIG_SOC_CLK_LP_FAST_SUPPORT_XTAL_D2=y
+CONFIG_SOC_EFUSE_DIS_DOWNLOAD_ICACHE=y
+CONFIG_SOC_EFUSE_DIS_DOWNLOAD_DCACHE=y
+CONFIG_SOC_EFUSE_HARD_DIS_JTAG=y
+CONFIG_SOC_EFUSE_DIS_USB_JTAG=y
+CONFIG_SOC_EFUSE_SOFT_DIS_JTAG=y
+CONFIG_SOC_EFUSE_DIS_DIRECT_BOOT=y
+CONFIG_SOC_EFUSE_DIS_ICACHE=y
+CONFIG_SOC_EFUSE_BLOCK9_KEY_PURPOSE_QUIRK=y
+CONFIG_SOC_SECURE_BOOT_V2_RSA=y
+CONFIG_SOC_EFUSE_SECURE_BOOT_KEY_DIGESTS=3
+CONFIG_SOC_EFUSE_REVOKE_BOOT_KEY_DIGESTS=y
+CONFIG_SOC_SUPPORT_SECURE_BOOT_REVOKE_KEY=y
+CONFIG_SOC_FLASH_ENCRYPTED_XTS_AES_BLOCK_MAX=64
+CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES=y
+CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_OPTIONS=y
+CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_128=y
+CONFIG_SOC_FLASH_ENCRYPTION_XTS_AES_256=y
+CONFIG_SOC_MEMPROT_CPU_PREFETCH_PAD_SIZE=16
+CONFIG_SOC_MEMPROT_MEM_ALIGN_SIZE=256
+CONFIG_SOC_PHY_DIG_REGS_MEM_SIZE=21
+CONFIG_SOC_MAC_BB_PD_MEM_SIZE=192
+CONFIG_SOC_WIFI_LIGHT_SLEEP_CLK_WIDTH=12
+CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_WAIT_IDLE=y
+CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_SUSPEND=y
+CONFIG_SOC_SPI_MEM_SUPPORT_AUTO_RESUME=y
+CONFIG_SOC_SPI_MEM_SUPPORT_SW_SUSPEND=y
+CONFIG_SOC_SPI_MEM_SUPPORT_FLASH_OPI_MODE=y
+CONFIG_SOC_SPI_MEM_SUPPORT_TIMING_TUNING=y
+CONFIG_SOC_SPI_MEM_SUPPORT_CONFIG_GPIO_BY_EFUSE=y
+CONFIG_SOC_SPI_MEM_SUPPORT_WRAP=y
+CONFIG_SOC_MEMSPI_TIMING_TUNING_BY_MSPI_DELAY=y
+CONFIG_SOC_MEMSPI_CORE_CLK_SHARED_WITH_PSRAM=y
+CONFIG_SOC_SPI_MEM_SUPPORT_CACHE_32BIT_ADDR_MAP=y
+CONFIG_SOC_COEX_HW_PTI=y
+CONFIG_SOC_EXTERNAL_COEX_LEADER_TX_LINE=y
+CONFIG_SOC_SDMMC_USE_GPIO_MATRIX=y
+CONFIG_SOC_SDMMC_NUM_SLOTS=2
+CONFIG_SOC_SDMMC_SUPPORT_XTAL_CLOCK=y
+CONFIG_SOC_SDMMC_DELAY_PHASE_NUM=4
+CONFIG_SOC_TEMPERATURE_SENSOR_SUPPORT_FAST_RC=y
+CONFIG_SOC_WIFI_HW_TSF=y
+CONFIG_SOC_WIFI_FTM_SUPPORT=y
+CONFIG_SOC_WIFI_GCMP_SUPPORT=y
+CONFIG_SOC_WIFI_WAPI_SUPPORT=y
+CONFIG_SOC_WIFI_CSI_SUPPORT=y
+CONFIG_SOC_WIFI_MESH_SUPPORT=y
+CONFIG_SOC_WIFI_SUPPORT_VARIABLE_BEACON_WINDOW=y
+CONFIG_SOC_WIFI_PHY_NEEDS_USB_WORKAROUND=y
+CONFIG_SOC_BLE_SUPPORTED=y
+CONFIG_SOC_BLE_MESH_SUPPORTED=y
+CONFIG_SOC_BLE_50_SUPPORTED=y
+CONFIG_SOC_BLE_DEVICE_PRIVACY_SUPPORTED=y
+CONFIG_SOC_BLUFI_SUPPORTED=y
+CONFIG_SOC_ULP_HAS_ADC=y
+CONFIG_SOC_PHY_COMBO_MODULE=y
+CONFIG_IDF_CMAKE=y
+CONFIG_IDF_TOOLCHAIN="gcc"
+CONFIG_IDF_TOOLCHAIN_GCC=y
+CONFIG_IDF_TARGET_ARCH_XTENSA=y
+CONFIG_IDF_TARGET_ARCH="xtensa"
+CONFIG_IDF_TARGET="esp32s3"
+CONFIG_IDF_INIT_VERSION="5.5.0"
+CONFIG_IDF_TARGET_ESP32S3=y
+CONFIG_IDF_FIRMWARE_CHIP_ID=0x0009
+
+#
+# Build type
+#
+CONFIG_APP_BUILD_TYPE_APP_2NDBOOT=y
+# CONFIG_APP_BUILD_TYPE_RAM is not set
+CONFIG_APP_BUILD_GENERATE_BINARIES=y
+CONFIG_APP_BUILD_BOOTLOADER=y
+CONFIG_APP_BUILD_USE_FLASH_SECTIONS=y
+# CONFIG_APP_REPRODUCIBLE_BUILD is not set
+# CONFIG_APP_NO_BLOBS is not set
+# end of Build type
+
+#
+# Bootloader config
+#
+
+#
+# Bootloader manager
+#
+CONFIG_BOOTLOADER_COMPILE_TIME_DATE=y
+CONFIG_BOOTLOADER_PROJECT_VER=1
+# end of Bootloader manager
+
+#
+# Application Rollback
+#
+CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
+# CONFIG_BOOTLOADER_APP_ANTI_ROLLBACK is not set
+# end of Application Rollback
+
+#
+# Bootloader Rollback
+#
+# end of Bootloader Rollback
+
+CONFIG_BOOTLOADER_OFFSET_IN_FLASH=0x0
+# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_SIZE is not set
+# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_DEBUG is not set
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y
+# CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_NONE is not set
+
+#
+# Log
+#
+CONFIG_BOOTLOADER_LOG_VERSION_1=y
+CONFIG_BOOTLOADER_LOG_VERSION=1
+CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
+# CONFIG_BOOTLOADER_LOG_LEVEL_ERROR is not set
+# CONFIG_BOOTLOADER_LOG_LEVEL_WARN is not set
+# CONFIG_BOOTLOADER_LOG_LEVEL_INFO is not set
+# CONFIG_BOOTLOADER_LOG_LEVEL_DEBUG is not set
+# CONFIG_BOOTLOADER_LOG_LEVEL_VERBOSE is not set
+CONFIG_BOOTLOADER_LOG_LEVEL=0
+
+#
+# Format
+#
+# CONFIG_BOOTLOADER_LOG_COLORS is not set
+CONFIG_BOOTLOADER_LOG_TIMESTAMP_SOURCE_CPU_TICKS=y
+# end of Format
+
+#
+# Settings
+#
+CONFIG_BOOTLOADER_LOG_MODE_TEXT_EN=y
+CONFIG_BOOTLOADER_LOG_MODE_TEXT=y
+# end of Settings
+# end of Log
+
+#
+# Serial Flash Configurations
+#
+# CONFIG_BOOTLOADER_FLASH_DC_AWARE is not set
+CONFIG_BOOTLOADER_FLASH_XMC_SUPPORT=y
+# end of Serial Flash Configurations
+
+# CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_8V is not set
+CONFIG_BOOTLOADER_VDDSDIO_BOOST_1_9V=y
+# CONFIG_BOOTLOADER_FACTORY_RESET is not set
+# CONFIG_BOOTLOADER_APP_TEST is not set
+CONFIG_BOOTLOADER_REGION_PROTECTION_ENABLE=y
+CONFIG_BOOTLOADER_WDT_ENABLE=y
+# CONFIG_BOOTLOADER_WDT_DISABLE_IN_USER_CODE is not set
+CONFIG_BOOTLOADER_WDT_TIME_MS=9000
+CONFIG_BOOTLOADER_SKIP_VALIDATE_IN_DEEP_SLEEP=y
+CONFIG_BOOTLOADER_SKIP_VALIDATE_ON_POWER_ON=y
+CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y
+CONFIG_BOOTLOADER_RESERVE_RTC_SIZE=0x10
+# CONFIG_BOOTLOADER_CUSTOM_RESERVE_RTC is not set
+CONFIG_BOOTLOADER_RESERVE_RTC_MEM=y
+# end of Bootloader config
+
+#
+# Security features
+#
+CONFIG_SECURE_BOOT_V2_RSA_SUPPORTED=y
+CONFIG_SECURE_BOOT_V2_PREFERRED=y
+# CONFIG_SECURE_SIGNED_APPS_NO_SECURE_BOOT is not set
+# CONFIG_SECURE_BOOT is not set
+# CONFIG_SECURE_FLASH_ENC_ENABLED is not set
+CONFIG_SECURE_ROM_DL_MODE_ENABLED=y
+# end of Security features
+
+#
+# Application manager
+#
+CONFIG_APP_COMPILE_TIME_DATE=y
+# CONFIG_APP_EXCLUDE_PROJECT_VER_VAR is not set
+# CONFIG_APP_EXCLUDE_PROJECT_NAME_VAR is not set
+# CONFIG_APP_PROJECT_VER_FROM_CONFIG is not set
+CONFIG_APP_RETRIEVE_LEN_ELF_SHA=9
+# end of Application manager
+
+CONFIG_ESP_ROM_HAS_CRC_LE=y
+CONFIG_ESP_ROM_HAS_CRC_BE=y
+CONFIG_ESP_ROM_HAS_MZ_CRC32=y
+CONFIG_ESP_ROM_HAS_JPEG_DECODE=y
+CONFIG_ESP_ROM_UART_CLK_IS_XTAL=y
+CONFIG_ESP_ROM_HAS_RETARGETABLE_LOCKING=y
+CONFIG_ESP_ROM_USB_OTG_NUM=3
+CONFIG_ESP_ROM_USB_SERIAL_DEVICE_NUM=4
+CONFIG_ESP_ROM_HAS_ERASE_0_REGION_BUG=y
+CONFIG_ESP_ROM_HAS_ENCRYPTED_WRITES_USING_LEGACY_DRV=y
+CONFIG_ESP_ROM_GET_CLK_FREQ=y
+CONFIG_ESP_ROM_HAS_HAL_WDT=y
+CONFIG_ESP_ROM_NEEDS_SWSETUP_WORKAROUND=y
+CONFIG_ESP_ROM_HAS_LAYOUT_TABLE=y
+CONFIG_ESP_ROM_HAS_SPI_FLASH=y
+CONFIG_ESP_ROM_HAS_SPI_FLASH_MMAP=y
+CONFIG_ESP_ROM_HAS_ETS_PRINTF_BUG=y
+CONFIG_ESP_ROM_HAS_NEWLIB=y
+CONFIG_ESP_ROM_HAS_NEWLIB_NANO_FORMAT=y
+CONFIG_ESP_ROM_HAS_NEWLIB_32BIT_TIME=y
+CONFIG_ESP_ROM_NEEDS_SET_CACHE_MMU_SIZE=y
+CONFIG_ESP_ROM_RAM_APP_NEEDS_MMU_INIT=y
+CONFIG_ESP_ROM_HAS_FLASH_COUNT_PAGES_BUG=y
+CONFIG_ESP_ROM_HAS_CACHE_SUSPEND_WAITI_BUG=y
+CONFIG_ESP_ROM_HAS_CACHE_WRITEBACK_BUG=y
+CONFIG_ESP_ROM_HAS_SW_FLOAT=y
+CONFIG_ESP_ROM_HAS_VERSION=y
+CONFIG_ESP_ROM_SUPPORT_DEEP_SLEEP_WAKEUP_STUB=y
+CONFIG_ESP_ROM_HAS_OUTPUT_PUTC_FUNC=y
+CONFIG_ESP_ROM_CONSOLE_OUTPUT_SECONDARY=y
+
+#
+# Boot ROM Behavior
+#
+CONFIG_BOOT_ROM_LOG_ALWAYS_ON=y
+# CONFIG_BOOT_ROM_LOG_ALWAYS_OFF is not set
+# CONFIG_BOOT_ROM_LOG_ON_GPIO_HIGH is not set
+# CONFIG_BOOT_ROM_LOG_ON_GPIO_LOW is not set
+# end of Boot ROM Behavior
+
+#
+# Serial flasher config
+#
+# CONFIG_ESPTOOLPY_NO_STUB is not set
+# CONFIG_ESPTOOLPY_OCT_FLASH is not set
+CONFIG_ESPTOOLPY_FLASH_MODE_AUTO_DETECT=y
+CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
+# CONFIG_ESPTOOLPY_FLASHMODE_QOUT is not set
+# CONFIG_ESPTOOLPY_FLASHMODE_DIO is not set
+# CONFIG_ESPTOOLPY_FLASHMODE_DOUT is not set
+CONFIG_ESPTOOLPY_FLASH_SAMPLE_MODE_STR=y
+CONFIG_ESPTOOLPY_FLASHMODE="dio"
+CONFIG_ESPTOOLPY_FLASHFREQ_120M=y
+# CONFIG_ESPTOOLPY_FLASHFREQ_80M is not set
+# CONFIG_ESPTOOLPY_FLASHFREQ_40M is not set
+# CONFIG_ESPTOOLPY_FLASHFREQ_20M is not set
+CONFIG_ESPTOOLPY_FLASHFREQ="80m"
+# CONFIG_ESPTOOLPY_FLASHSIZE_1MB is not set
+# CONFIG_ESPTOOLPY_FLASHSIZE_2MB is not set
+# CONFIG_ESPTOOLPY_FLASHSIZE_4MB is not set
+# CONFIG_ESPTOOLPY_FLASHSIZE_8MB is not set
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+# CONFIG_ESPTOOLPY_FLASHSIZE_32MB is not set
+# CONFIG_ESPTOOLPY_FLASHSIZE_64MB is not set
+# CONFIG_ESPTOOLPY_FLASHSIZE_128MB is not set
+CONFIG_ESPTOOLPY_FLASHSIZE="16MB"
+# CONFIG_ESPTOOLPY_HEADER_FLASHSIZE_UPDATE is not set
+CONFIG_ESPTOOLPY_BEFORE_RESET=y
+# CONFIG_ESPTOOLPY_BEFORE_NORESET is not set
+CONFIG_ESPTOOLPY_BEFORE="default_reset"
+CONFIG_ESPTOOLPY_AFTER_RESET=y
+# CONFIG_ESPTOOLPY_AFTER_NORESET is not set
+CONFIG_ESPTOOLPY_AFTER="hard_reset"
+CONFIG_ESPTOOLPY_MONITOR_BAUD=115200
+# end of Serial flasher config
+
+#
+# Partition Table
+#
+# CONFIG_PARTITION_TABLE_SINGLE_APP is not set
+# CONFIG_PARTITION_TABLE_SINGLE_APP_LARGE is not set
+# CONFIG_PARTITION_TABLE_TWO_OTA is not set
+# CONFIG_PARTITION_TABLE_TWO_OTA_LARGE is not set
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv"
+CONFIG_PARTITION_TABLE_FILENAME="partitions/v2/16m.csv"
+CONFIG_PARTITION_TABLE_OFFSET=0x8000
+CONFIG_PARTITION_TABLE_MD5=y
+# end of Partition Table
+
+#
+# Xiaozhi Assistant
+#
+CONFIG_OTA_URL="https://api.tenclass.net/xiaozhi/ota/"
+# CONFIG_FLASH_NONE_ASSETS is not set
+CONFIG_FLASH_DEFAULT_ASSETS=y
+# CONFIG_FLASH_CUSTOM_ASSETS is not set
+CONFIG_LANGUAGE_ZH_CN=y
+# CONFIG_LANGUAGE_ZH_TW is not set
+# CONFIG_LANGUAGE_EN_US is not set
+# CONFIG_LANGUAGE_JA_JP is not set
+# CONFIG_LANGUAGE_KO_KR is not set
+# CONFIG_LANGUAGE_VI_VN is not set
+# CONFIG_LANGUAGE_TH_TH is not set
+# CONFIG_LANGUAGE_DE_DE is not set
+# CONFIG_LANGUAGE_FR_FR is not set
+# CONFIG_LANGUAGE_ES_ES is not set
+# CONFIG_LANGUAGE_IT_IT is not set
+# CONFIG_LANGUAGE_RU_RU is not set
+# CONFIG_LANGUAGE_AR_SA is not set
+# CONFIG_LANGUAGE_HI_IN is not set
+# CONFIG_LANGUAGE_PT_PT is not set
+# CONFIG_LANGUAGE_PL_PL is not set
+# CONFIG_LANGUAGE_CS_CZ is not set
+# CONFIG_LANGUAGE_FI_FI is not set
+# CONFIG_LANGUAGE_TR_TR is not set
+# CONFIG_LANGUAGE_ID_ID is not set
+# CONFIG_LANGUAGE_UK_UA is not set
+# CONFIG_LANGUAGE_RO_RO is not set
+# CONFIG_LANGUAGE_BG_BG is not set
+# CONFIG_LANGUAGE_CA_ES is not set
+# CONFIG_LANGUAGE_DA_DK is not set
+# CONFIG_LANGUAGE_EL_GR is not set
+# CONFIG_LANGUAGE_FA_IR is not set
+# CONFIG_LANGUAGE_FIL_PH is not set
+# CONFIG_LANGUAGE_HE_IL is not set
+# CONFIG_LANGUAGE_HR_HR is not set
+# CONFIG_LANGUAGE_HU_HU is not set
+# CONFIG_LANGUAGE_MS_MY is not set
+# CONFIG_LANGUAGE_NB_NO is not set
+# CONFIG_LANGUAGE_NL_NL is not set
+# CONFIG_LANGUAGE_SK_SK is not set
+# CONFIG_LANGUAGE_SL_SI is not set
+# CONFIG_LANGUAGE_SV_SE is not set
+# CONFIG_LANGUAGE_SR_RS is not set
+# CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI is not set
+# CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_LCD is not set
+# CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM is not set
+# CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307 is not set
+# CONFIG_BOARD_TYPE_ESP_KORVO2_V3 is not set
+# CONFIG_BOARD_TYPE_ESP_SPARKBOT is not set
+# CONFIG_BOARD_TYPE_ESP_SPOT_S3 is not set
+# CONFIG_BOARD_TYPE_ESP_BOX_3 is not set
+# CONFIG_BOARD_TYPE_ESP_BOX is not set
+# CONFIG_BOARD_TYPE_ESP_BOX_LITE is not set
+# CONFIG_BOARD_TYPE_ECHOEAR is not set
+# CONFIG_BOARD_TYPE_KEVIN_BOX_2 is not set
+# CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV is not set
+# CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV is not set
+# CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD is not set
+# CONFIG_BOARD_TYPE_LICHUANG_DEV_S3 is not set
+# CONFIG_BOARD_TYPE_DF_K10 is not set
+# CONFIG_BOARD_TYPE_DF_S3_AI_CAM is not set
+# CONFIG_BOARD_TYPE_MAGICLICK_S3_2P4 is not set
+# CONFIG_BOARD_TYPE_MAGICLICK_S3_2P5 is not set
+# CONFIG_BOARD_TYPE_M5STACK_CORE_S3 is not set
+# CONFIG_BOARD_TYPE_M5STACK_ATOM_S3_ECHO_BASE is not set
+# CONFIG_BOARD_TYPE_M5STACK_ATOM_S3R_ECHO_BASE is not set
+# CONFIG_BOARD_TYPE_M5STACK_ATOM_S3R_CAM_M12_ECHO_BASE is not set
+# CONFIG_BOARD_TYPE_M5STACK_ATOM_ECHOS3R is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_AUDIO_BOARD is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_8 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_2_06 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_83 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_85C is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_85 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_46 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_32 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_5 is not set
+# CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_5B is not set
+CONFIG_BOARD_TYPE_ESP32S3_Touch_LCD_4_3C=y
+# CONFIG_BOARD_TYPE_TUDOUZI is not set
+# CONFIG_BOARD_TYPE_LILYGO_T_CIRCLE_S3 is not set
+# CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_0_V1_1 is not set
+# CONFIG_BOARD_TYPE_LILYGO_T_CAMERAPLUS_S3_V1_2 is not set
+# CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA is not set
+# CONFIG_BOARD_TYPE_LILYGO_T_DISPLAY_S3_PRO_MVSRLORA_NO_BATTERY is not set
+# CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3 is not set
+# CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3 is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3 is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0 is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI is not set
+# CONFIG_BOARD_TYPE_ATK_DNESP32S3M_4G is not set
+# CONFIG_BOARD_TYPE_DU_CHATX is not set
+# CONFIG_BOARD_TYPE_TAIJI_PI_S3 is not set
+# CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_85TFT_WIFI is not set
+# CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_85TFT_ML307 is not set
+# CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_96OLED_WIFI is not set
+# CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_96OLED_ML307 is not set
+# CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_WIFI is not set
+# CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307 is not set
+# CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER is not set
+# CONFIG_BOARD_TYPE_DOIT_S3_AIBOX is not set
+# CONFIG_BOARD_TYPE_MIXGO_NOVA is not set
+# CONFIG_BOARD_TYPE_GENJUTECH_S3_1_54TFT is not set
+# CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board is not set
+# CONFIG_BOARD_TYPE_ESP_S3_LCD_EV_Board_2 is not set
+# CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_WIFI is not set
+# CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307 is not set
+# CONFIG_BOARD_TYPE_MINSI_K08_DUAL is not set
+# CONFIG_BOARD_TYPE_SPOTPEAR_ESP32_S3_1_54_MUMA is not set
+# CONFIG_BOARD_TYPE_SPOTPEAR_ESP32_S3_1_28_BOX is not set
+# CONFIG_BOARD_TYPE_OTTO_ROBOT is not set
+# CONFIG_BOARD_TYPE_ELECTRON_BOT is not set
+# CONFIG_BOARD_TYPE_JIUCHUAN is not set
+# CONFIG_BOARD_TYPE_LABPLUS_MPYTHON_V3 is not set
+# CONFIG_BOARD_TYPE_LABPLUS_LEDONG_V2 is not set
+# CONFIG_BOARD_TYPE_YUNLIAO_S3 is not set
+# CONFIG_BOARD_TYPE_AIPI_LITE is not set
+CONFIG_USE_DEFAULT_MESSAGE_STYLE=y
+# CONFIG_USE_WECHAT_MESSAGE_STYLE is not set
+# CONFIG_WAKE_WORD_DISABLED is not set
+CONFIG_USE_AFE_WAKE_WORD=y
+# CONFIG_USE_CUSTOM_WAKE_WORD is not set
+CONFIG_SEND_WAKE_WORD_DATA=y
+CONFIG_USE_AUDIO_PROCESSOR=y
+CONFIG_USE_DEVICE_AEC=y
+# CONFIG_USE_SERVER_AEC is not set
+# CONFIG_USE_AUDIO_DEBUGGER is not set
+# CONFIG_USE_ACOUSTIC_WIFI_PROVISIONING is not set
+# CONFIG_RECEIVE_CUSTOM_MESSAGE is not set
+
+#
+# Camera Configuration
+#
+
+#
+# Warning: Please read the help text before modifying these settings.
+#
+# CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE is not set
+# CONFIG_XIAOZHI_ENABLE_CAMERA_ENDIANNESS_SWAP is not set
+# CONFIG_XIAOZHI_ENABLE_ROTATE_CAMERA_IMAGE is not set
+# end of Camera Configuration
+# end of Xiaozhi Assistant
+
+#
+# ESP Speech Recognition
+#
+CONFIG_MODEL_IN_FLASH=y
+# CONFIG_MODEL_IN_SDCARD is not set
+CONFIG_AFE_INTERFACE_V1=y
+CONFIG_SR_NSN_WEBRTC=y
+# CONFIG_SR_NSN_NSNET2 is not set
+CONFIG_SR_VADN_WEBRTC=y
+# CONFIG_SR_VADN_VADNET1_MEDIUM is not set
+
+#
+# Load Multiple Wake Words (WakeNet9s)
+#
+# CONFIG_SR_WN_WN9S_HILEXIN is not set
+# CONFIG_SR_WN_WN9S_HIESP is not set
+# CONFIG_SR_WN_WN9S_NIHAOXIAOZHI is not set
+# CONFIG_SR_WN_WN9S_HIJASON is not set
+# end of Load Multiple Wake Words (WakeNet9s)
+
+#
+# Load Multiple Wake Words (WakeNet9)
+#
+# CONFIG_SR_WN_WN9_HILEXIN is not set
+# CONFIG_SR_WN_WN9_NIHAOMIAOBAN_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOAITONGXUE is not set
+# CONFIG_SR_WN_WN9L_XIAOAITONGXUE is not set
+CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
+# CONFIG_SR_WN_WN9L_NIHAOXIAOZHI_TTS3 is not set
+# CONFIG_SR_WN_WN9_ALEXA is not set
+# CONFIG_SR_WN_WN9_HIESP is not set
+# CONFIG_SR_WN_WN9_JARVIS_TTS is not set
+# CONFIG_SR_WN_WN9_COMPUTER_TTS is not set
+# CONFIG_SR_WN_WN9_HEYWILLOW_TTS is not set
+# CONFIG_SR_WN_WN9_HIMFIVE is not set
+# CONFIG_SR_WN_WN9_SOPHIA_TTS is not set
+# CONFIG_SR_WN_WN9_HEYWANDA_TTS is not set
+# CONFIG_SR_WN_WN9_HIJOLLY_TTS2 is not set
+# CONFIG_SR_WN_WN9_HIFAIRY_TTS2 is not set
+# CONFIG_SR_WN_WN9_HEYPRINTER_TTS is not set
+# CONFIG_SR_WN_WN9_MYCROFT_TTS is not set
+# CONFIG_SR_WN_WN9_HIJOY_TTS is not set
+# CONFIG_SR_WN_WN9_HIJASON_TTS2 is not set
+# CONFIG_SR_WN_WN9_ASTROLABE_TTS is not set
+# CONFIG_SR_WN_WN9_HEYILY_TTS2 is not set
+# CONFIG_SR_WN_WN9_BLUECHIP_TTS2 is not set
+# CONFIG_SR_WN_WN9_HIANDY_TTS2 is not set
+# CONFIG_SR_WN_WN9_HEYIVY_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOLONGXIAOLONG_TTS is not set
+# CONFIG_SR_WN_WN9_HIMIAOMIAO_TTS is not set
+# CONFIG_SR_WN_WN9_MIAOMIAOTONGXUE_TTS is not set
+# CONFIG_SR_WN_WN9_NIHAOXIAOXIN_TTS is not set
+# CONFIG_SR_WN_WN9_XIAOMEITONGXUE_TTS is not set
+# CONFIG_SR_WN_WN9_HILILI_TTS is not set
+# CONFIG_SR_WN_WN9_HITELLY_TTS is not set
+# CONFIG_SR_WN_WN9_XIAOBINXIAOBIN_TTS is not set
+# CONFIG_SR_WN_WN9_HAIXIAOWU_TTS is not set
+# CONFIG_SR_WN_WN9_XIAOYAXIAOYA_TTS2 is not set
+# CONFIG_SR_WN_WN9_LINAIBAN_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOSUROU_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOYUTONGXUE_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOMINGTONGXUE_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOKANGTONGXUE_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOJIANXIAOJIAN_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOTEXIAOTE_TTS2 is not set
+# CONFIG_SR_WN_WN9_NIHAOXIAOYI_TTS2 is not set
+# CONFIG_SR_WN_WN9_NIHAOBAIYING_TTS2 is not set
+# CONFIG_SR_WN_WN9_NIHAODONGDONG_TTS2 is not set
+# CONFIG_SR_WN_WN9_HIWALLE_TTS2 is not set
+# CONFIG_SR_WN_WN9_XIAOLUXIAOLU_TTS2 is not set
+# CONFIG_SR_WN_WN9_NIHAOXIAOAN_TTS2 is not set
+# CONFIG_SR_WN_WN9_NI3HAO3XIAO3MAI4_TTS2 is not set
+# CONFIG_SR_WN_WN9_NI3HAO3XIAO3RUI4_TTS3 is not set
+# end of Load Multiple Wake Words (WakeNet9)
+
+CONFIG_SR_MN_CN_NONE=y
+# CONFIG_SR_MN_CN_MULTINET5_RECOGNITION_QUANT8 is not set
+# CONFIG_SR_MN_CN_MULTINET6_QUANT is not set
+# CONFIG_SR_MN_CN_MULTINET6_AC_QUANT is not set
+# CONFIG_SR_MN_CN_MULTINET7_QUANT is not set
+# CONFIG_SR_MN_CN_MULTINET7_AC_QUANT is not set
+CONFIG_SR_MN_EN_NONE=y
+# CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION_QUANT8 is not set
+# CONFIG_SR_MN_EN_MULTINET6_QUANT is not set
+# CONFIG_SR_MN_EN_MULTINET7_QUANT is not set
+# end of ESP Speech Recognition
+
+#
+# Compiler options
+#
+# CONFIG_COMPILER_OPTIMIZATION_DEBUG is not set
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+# CONFIG_COMPILER_OPTIMIZATION_PERF is not set
+# CONFIG_COMPILER_OPTIMIZATION_NONE is not set
+CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_ENABLE=y
+# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_SILENT is not set
+# CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE is not set
+CONFIG_COMPILER_ASSERT_NDEBUG_EVALUATE=y
+CONFIG_COMPILER_FLOAT_LIB_FROM_GCCLIB=y
+CONFIG_COMPILER_OPTIMIZATION_ASSERTION_LEVEL=2
+# CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT is not set
+CONFIG_COMPILER_HIDE_PATHS_MACROS=y
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
+CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
+CONFIG_COMPILER_CXX_RTTI=y
+CONFIG_COMPILER_STACK_CHECK_MODE_NONE=y
+# CONFIG_COMPILER_STACK_CHECK_MODE_NORM is not set
+# CONFIG_COMPILER_STACK_CHECK_MODE_STRONG is not set
+# CONFIG_COMPILER_STACK_CHECK_MODE_ALL is not set
+# CONFIG_COMPILER_NO_MERGE_CONSTANTS is not set
+# CONFIG_COMPILER_WARN_WRITE_STRINGS is not set
+CONFIG_COMPILER_DISABLE_DEFAULT_ERRORS=y
+# CONFIG_COMPILER_DISABLE_GCC12_WARNINGS is not set
+# CONFIG_COMPILER_DISABLE_GCC13_WARNINGS is not set
+# CONFIG_COMPILER_DISABLE_GCC14_WARNINGS is not set
+# CONFIG_COMPILER_DUMP_RTL_FILES is not set
+CONFIG_COMPILER_RT_LIB_GCCLIB=y
+CONFIG_COMPILER_RT_LIB_NAME="gcc"
+CONFIG_COMPILER_ORPHAN_SECTIONS_WARNING=y
+# CONFIG_COMPILER_ORPHAN_SECTIONS_PLACE is not set
+# CONFIG_COMPILER_STATIC_ANALYZER is not set
+# end of Compiler options
+
+#
+# Component config
+#
+
+#
+# Application Level Tracing
+#
+# CONFIG_APPTRACE_DEST_JTAG is not set
+CONFIG_APPTRACE_DEST_NONE=y
+# CONFIG_APPTRACE_DEST_UART1 is not set
+# CONFIG_APPTRACE_DEST_UART2 is not set
+# CONFIG_APPTRACE_DEST_USB_CDC is not set
+CONFIG_APPTRACE_DEST_UART_NONE=y
+CONFIG_APPTRACE_UART_TASK_PRIO=1
+CONFIG_APPTRACE_LOCK_ENABLE=y
+# end of Application Level Tracing
+
+#
+# Bluetooth
+#
+# CONFIG_BT_ENABLED is not set
+
+#
+# Common Options
+#
+# CONFIG_BT_BLE_LOG_SPI_OUT_ENABLED is not set
+# end of Common Options
+# end of Bluetooth
+
+#
+# Console Library
+#
+# CONFIG_CONSOLE_SORTED_HELP is not set
+# end of Console Library
+
+#
+# Driver Configurations
+#
+
+#
+# Legacy TWAI Driver Configurations
+#
+# CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK is not set
+CONFIG_TWAI_ERRATA_FIX_LISTEN_ONLY_DOM=y
+# end of Legacy TWAI Driver Configurations
+
+#
+# Legacy ADC Driver Configuration
+#
+# CONFIG_ADC_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_ADC_SKIP_LEGACY_CONFLICT_CHECK is not set
+
+#
+# Legacy ADC Calibration Configuration
+#
+# CONFIG_ADC_CALI_SUPPRESS_DEPRECATE_WARN is not set
+# end of Legacy ADC Calibration Configuration
+# end of Legacy ADC Driver Configuration
+
+#
+# Legacy MCPWM Driver Configurations
+#
+# CONFIG_MCPWM_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_MCPWM_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy MCPWM Driver Configurations
+
+#
+# Legacy Timer Group Driver Configurations
+#
+# CONFIG_GPTIMER_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_GPTIMER_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy Timer Group Driver Configurations
+
+#
+# Legacy RMT Driver Configurations
+#
+# CONFIG_RMT_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_RMT_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy RMT Driver Configurations
+
+#
+# Legacy I2S Driver Configurations
+#
+# CONFIG_I2S_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_I2S_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy I2S Driver Configurations
+
+#
+# Legacy I2C Driver Configurations
+#
+# CONFIG_I2C_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy I2C Driver Configurations
+
+#
+# Legacy PCNT Driver Configurations
+#
+# CONFIG_PCNT_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_PCNT_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy PCNT Driver Configurations
+
+#
+# Legacy SDM Driver Configurations
+#
+# CONFIG_SDM_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_SDM_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy SDM Driver Configurations
+
+#
+# Legacy Temperature Sensor Driver Configurations
+#
+# CONFIG_TEMP_SENSOR_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_TEMP_SENSOR_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy Temperature Sensor Driver Configurations
+
+#
+# Legacy Touch Sensor Driver Configurations
+#
+# CONFIG_TOUCH_SUPPRESS_DEPRECATE_WARN is not set
+# CONFIG_TOUCH_SKIP_LEGACY_CONFLICT_CHECK is not set
+# end of Legacy Touch Sensor Driver Configurations
+# end of Driver Configurations
+
+#
+# eFuse Bit Manager
+#
+# CONFIG_EFUSE_CUSTOM_TABLE is not set
+# CONFIG_EFUSE_VIRTUAL is not set
+CONFIG_EFUSE_MAX_BLK_LEN=256
+# end of eFuse Bit Manager
+
+#
+# ESP-TLS
+#
+CONFIG_ESP_TLS_USING_MBEDTLS=y
+# CONFIG_ESP_TLS_USE_SECURE_ELEMENT is not set
+CONFIG_ESP_TLS_USE_DS_PERIPHERAL=y
+# CONFIG_ESP_TLS_CLIENT_SESSION_TICKETS is not set
+# CONFIG_ESP_TLS_SERVER_SESSION_TICKETS is not set
+# CONFIG_ESP_TLS_SERVER_CERT_SELECT_HOOK is not set
+# CONFIG_ESP_TLS_SERVER_MIN_AUTH_MODE_OPTIONAL is not set
+# CONFIG_ESP_TLS_PSK_VERIFICATION is not set
+# CONFIG_ESP_TLS_INSECURE is not set
+# end of ESP-TLS
+
+#
+# ADC and ADC Calibration
+#
+# CONFIG_ADC_ONESHOT_CTRL_FUNC_IN_IRAM is not set
+# CONFIG_ADC_CONTINUOUS_ISR_IRAM_SAFE is not set
+# CONFIG_ADC_CONTINUOUS_FORCE_USE_ADC2_ON_C3_S3 is not set
+# CONFIG_ADC_ENABLE_DEBUG_LOG is not set
+# end of ADC and ADC Calibration
+
+#
+# Wireless Coexistence
+#
+CONFIG_ESP_COEX_ENABLED=y
+# CONFIG_ESP_COEX_EXTERNAL_COEXIST_ENABLE is not set
+# CONFIG_ESP_COEX_GPIO_DEBUG is not set
+# end of Wireless Coexistence
+
+#
+# Common ESP-related
+#
+CONFIG_ESP_ERR_TO_NAME_LOOKUP=y
+# end of Common ESP-related
+
+#
+# ESP-Driver:GPIO Configurations
+#
+# CONFIG_GPIO_CTRL_FUNC_IN_IRAM is not set
+# end of ESP-Driver:GPIO Configurations
+
+#
+# ESP-Driver:GPTimer Configurations
+#
+CONFIG_GPTIMER_ISR_HANDLER_IN_IRAM=y
+# CONFIG_GPTIMER_CTRL_FUNC_IN_IRAM is not set
+# CONFIG_GPTIMER_ISR_CACHE_SAFE is not set
+CONFIG_GPTIMER_OBJ_CACHE_SAFE=y
+# CONFIG_GPTIMER_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:GPTimer Configurations
+
+#
+# ESP-Driver:I2C Configurations
+#
+# CONFIG_I2C_ISR_IRAM_SAFE is not set
+# CONFIG_I2C_ENABLE_DEBUG_LOG is not set
+# CONFIG_I2C_ENABLE_SLAVE_DRIVER_VERSION_2 is not set
+CONFIG_I2C_MASTER_ISR_HANDLER_IN_IRAM=y
+# end of ESP-Driver:I2C Configurations
+
+#
+# ESP-Driver:I2S Configurations
+#
+# CONFIG_I2S_ISR_IRAM_SAFE is not set
+# CONFIG_I2S_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:I2S Configurations
+
+#
+# ESP-Driver:LEDC Configurations
+#
+# CONFIG_LEDC_CTRL_FUNC_IN_IRAM is not set
+# end of ESP-Driver:LEDC Configurations
+
+#
+# ESP-Driver:MCPWM Configurations
+#
+CONFIG_MCPWM_ISR_HANDLER_IN_IRAM=y
+# CONFIG_MCPWM_ISR_CACHE_SAFE is not set
+# CONFIG_MCPWM_CTRL_FUNC_IN_IRAM is not set
+CONFIG_MCPWM_OBJ_CACHE_SAFE=y
+# CONFIG_MCPWM_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:MCPWM Configurations
+
+#
+# ESP-Driver:PCNT Configurations
+#
+# CONFIG_PCNT_CTRL_FUNC_IN_IRAM is not set
+# CONFIG_PCNT_ISR_IRAM_SAFE is not set
+# CONFIG_PCNT_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:PCNT Configurations
+
+#
+# ESP-Driver:RMT Configurations
+#
+CONFIG_RMT_ENCODER_FUNC_IN_IRAM=y
+CONFIG_RMT_TX_ISR_HANDLER_IN_IRAM=y
+CONFIG_RMT_RX_ISR_HANDLER_IN_IRAM=y
+# CONFIG_RMT_RECV_FUNC_IN_IRAM is not set
+# CONFIG_RMT_TX_ISR_CACHE_SAFE is not set
+# CONFIG_RMT_RX_ISR_CACHE_SAFE is not set
+CONFIG_RMT_OBJ_CACHE_SAFE=y
+# CONFIG_RMT_ENABLE_DEBUG_LOG is not set
+# CONFIG_RMT_ISR_IRAM_SAFE is not set
+# end of ESP-Driver:RMT Configurations
+
+#
+# ESP-Driver:Sigma Delta Modulator Configurations
+#
+# CONFIG_SDM_CTRL_FUNC_IN_IRAM is not set
+# CONFIG_SDM_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:Sigma Delta Modulator Configurations
+
+#
+# ESP-Driver:SPI Configurations
+#
+# CONFIG_SPI_MASTER_IN_IRAM is not set
+CONFIG_SPI_MASTER_ISR_IN_IRAM=y
+# CONFIG_SPI_SLAVE_IN_IRAM is not set
+CONFIG_SPI_SLAVE_ISR_IN_IRAM=y
+# end of ESP-Driver:SPI Configurations
+
+#
+# ESP-Driver:Touch Sensor Configurations
+#
+# CONFIG_TOUCH_CTRL_FUNC_IN_IRAM is not set
+# CONFIG_TOUCH_ISR_IRAM_SAFE is not set
+# CONFIG_TOUCH_ENABLE_DEBUG_LOG is not set
+# CONFIG_TOUCH_SKIP_FSM_CHECK is not set
+# end of ESP-Driver:Touch Sensor Configurations
+
+#
+# ESP-Driver:Temperature Sensor Configurations
+#
+# CONFIG_TEMP_SENSOR_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:Temperature Sensor Configurations
+
+#
+# ESP-Driver:TWAI Configurations
+#
+# CONFIG_TWAI_ISR_IN_IRAM is not set
+# CONFIG_TWAI_ISR_CACHE_SAFE is not set
+# CONFIG_TWAI_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:TWAI Configurations
+
+#
+# ESP-Driver:UART Configurations
+#
+CONFIG_UART_ISR_IN_IRAM=y
+# end of ESP-Driver:UART Configurations
+
+#
+# ESP-Driver:UHCI Configurations
+#
+# CONFIG_UHCI_ISR_HANDLER_IN_IRAM is not set
+# CONFIG_UHCI_ISR_CACHE_SAFE is not set
+# CONFIG_UHCI_ENABLE_DEBUG_LOG is not set
+# end of ESP-Driver:UHCI Configurations
+
+#
+# ESP-Driver:USB Serial/JTAG Configuration
+#
+CONFIG_USJ_ENABLE_USB_SERIAL_JTAG=y
+# end of ESP-Driver:USB Serial/JTAG Configuration
+
+#
+# Ethernet
+#
+CONFIG_ETH_ENABLED=y
+CONFIG_ETH_USE_SPI_ETHERNET=y
+# CONFIG_ETH_SPI_ETHERNET_DM9051 is not set
+# CONFIG_ETH_SPI_ETHERNET_W5500 is not set
+# CONFIG_ETH_SPI_ETHERNET_KSZ8851SNL is not set
+# CONFIG_ETH_USE_OPENETH is not set
+# CONFIG_ETH_TRANSMIT_MUTEX is not set
+# end of Ethernet
+
+#
+# Event Loop Library
+#
+# CONFIG_ESP_EVENT_LOOP_PROFILING is not set
+CONFIG_ESP_EVENT_POST_FROM_ISR=y
+CONFIG_ESP_EVENT_POST_FROM_IRAM_ISR=y
+# end of Event Loop Library
+
+#
+# GDB Stub
+#
+CONFIG_ESP_GDBSTUB_ENABLED=y
+# CONFIG_ESP_SYSTEM_GDBSTUB_RUNTIME is not set
+CONFIG_ESP_GDBSTUB_SUPPORT_TASKS=y
+CONFIG_ESP_GDBSTUB_MAX_TASKS=32
+# end of GDB Stub
+
+#
+# ESP HID
+#
+CONFIG_ESPHID_TASK_SIZE_BT=2048
+CONFIG_ESPHID_TASK_SIZE_BLE=4096
+# end of ESP HID
+
+#
+# ESP HTTP client
+#
+CONFIG_ESP_HTTP_CLIENT_ENABLE_HTTPS=y
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_BASIC_AUTH is not set
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_DIGEST_AUTH is not set
+# CONFIG_ESP_HTTP_CLIENT_ENABLE_CUSTOM_TRANSPORT is not set
+CONFIG_ESP_HTTP_CLIENT_EVENT_POST_TIMEOUT=2000
+# end of ESP HTTP client
+
+#
+# HTTP Server
+#
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048
+CONFIG_HTTPD_MAX_URI_LEN=2048
+CONFIG_HTTPD_ERR_RESP_NO_DELAY=y
+CONFIG_HTTPD_PURGE_BUF_LEN=32
+# CONFIG_HTTPD_LOG_PURGE_DATA is not set
+# CONFIG_HTTPD_WS_SUPPORT is not set
+# CONFIG_HTTPD_QUEUE_WORK_BLOCKING is not set
+CONFIG_HTTPD_SERVER_EVENT_POST_TIMEOUT=2000
+# end of HTTP Server
+
+#
+# ESP HTTPS OTA
+#
+# CONFIG_ESP_HTTPS_OTA_DECRYPT_CB is not set
+# CONFIG_ESP_HTTPS_OTA_ALLOW_HTTP is not set
+CONFIG_ESP_HTTPS_OTA_EVENT_POST_TIMEOUT=2000
+# end of ESP HTTPS OTA
+
+#
+# ESP HTTPS server
+#
+# CONFIG_ESP_HTTPS_SERVER_ENABLE is not set
+CONFIG_ESP_HTTPS_SERVER_EVENT_POST_TIMEOUT=2000
+# CONFIG_ESP_HTTPS_SERVER_CERT_SELECT_HOOK is not set
+# end of ESP HTTPS server
+
+#
+# Hardware Settings
+#
+
+#
+# Chip revision
+#
+CONFIG_ESP32S3_REV_MIN_0=y
+# CONFIG_ESP32S3_REV_MIN_1 is not set
+# CONFIG_ESP32S3_REV_MIN_2 is not set
+CONFIG_ESP32S3_REV_MIN_FULL=0
+CONFIG_ESP_REV_MIN_FULL=0
+
+#
+# Maximum Supported ESP32-S3 Revision (Rev v0.99)
+#
+CONFIG_ESP32S3_REV_MAX_FULL=99
+CONFIG_ESP_REV_MAX_FULL=99
+CONFIG_ESP_EFUSE_BLOCK_REV_MIN_FULL=0
+CONFIG_ESP_EFUSE_BLOCK_REV_MAX_FULL=199
+
+#
+# Maximum Supported ESP32-S3 eFuse Block Revision (eFuse Block Rev v1.99)
+#
+# end of Chip revision
+
+#
+# MAC Config
+#
+CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_STA=y
+CONFIG_ESP_MAC_ADDR_UNIVERSE_WIFI_AP=y
+CONFIG_ESP_MAC_ADDR_UNIVERSE_BT=y
+CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH=y
+CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES_FOUR=y
+CONFIG_ESP_MAC_UNIVERSAL_MAC_ADDRESSES=4
+# CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_TWO is not set
+CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES_FOUR=y
+CONFIG_ESP32S3_UNIVERSAL_MAC_ADDRESSES=4
+# CONFIG_ESP_MAC_USE_CUSTOM_MAC_AS_BASE_MAC is not set
+# end of MAC Config
+
+#
+# Sleep Config
+#
+CONFIG_ESP_SLEEP_FLASH_LEAKAGE_WORKAROUND=y
+CONFIG_ESP_SLEEP_PSRAM_LEAKAGE_WORKAROUND=y
+CONFIG_ESP_SLEEP_MSPI_NEED_ALL_IO_PU=y
+CONFIG_ESP_SLEEP_RTC_BUS_ISO_WORKAROUND=y
+CONFIG_ESP_SLEEP_GPIO_RESET_WORKAROUND=y
+CONFIG_ESP_SLEEP_WAIT_FLASH_READY_EXTRA_DELAY=2000
+# CONFIG_ESP_SLEEP_CACHE_SAFE_ASSERTION is not set
+# CONFIG_ESP_SLEEP_DEBUG is not set
+CONFIG_ESP_SLEEP_GPIO_ENABLE_INTERNAL_RESISTORS=y
+# end of Sleep Config
+
+#
+# RTC Clock Config
+#
+CONFIG_RTC_CLK_SRC_INT_RC=y
+# CONFIG_RTC_CLK_SRC_EXT_CRYS is not set
+# CONFIG_RTC_CLK_SRC_EXT_OSC is not set
+# CONFIG_RTC_CLK_SRC_INT_8MD256 is not set
+CONFIG_RTC_CLK_CAL_CYCLES=1024
+# end of RTC Clock Config
+
+#
+# Peripheral Control
+#
+CONFIG_ESP_PERIPH_CTRL_FUNC_IN_IRAM=y
+CONFIG_ESP_REGI2C_CTRL_FUNC_IN_IRAM=y
+# end of Peripheral Control
+
+#
+# GDMA Configurations
+#
+CONFIG_GDMA_CTRL_FUNC_IN_IRAM=y
+CONFIG_GDMA_ISR_HANDLER_IN_IRAM=y
+CONFIG_GDMA_OBJ_DRAM_SAFE=y
+# CONFIG_GDMA_ENABLE_DEBUG_LOG is not set
+# CONFIG_GDMA_ISR_IRAM_SAFE is not set
+# end of GDMA Configurations
+
+#
+# Main XTAL Config
+#
+CONFIG_XTAL_FREQ_40=y
+CONFIG_XTAL_FREQ=40
+# end of Main XTAL Config
+
+#
+# Power Supplier
+#
+
+#
+# Brownout Detector
+#
+CONFIG_ESP_BROWNOUT_DET=y
+CONFIG_ESP_BROWNOUT_DET_LVL_SEL_7=y
+# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_6 is not set
+# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_5 is not set
+# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_4 is not set
+# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_3 is not set
+# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_2 is not set
+# CONFIG_ESP_BROWNOUT_DET_LVL_SEL_1 is not set
+CONFIG_ESP_BROWNOUT_DET_LVL=7
+CONFIG_ESP_BROWNOUT_USE_INTR=y
+# end of Brownout Detector
+# end of Power Supplier
+
+CONFIG_ESP_SPI_BUS_LOCK_ISR_FUNCS_IN_IRAM=y
+CONFIG_ESP_INTR_IN_IRAM=y
+# end of Hardware Settings
+
+#
+# ESP-Driver:LCD Controller Configurations
+#
+# CONFIG_LCD_ENABLE_DEBUG_LOG is not set
+# CONFIG_LCD_RGB_ISR_IRAM_SAFE is not set
+# CONFIG_LCD_RGB_RESTART_IN_VSYNC is not set
+# end of ESP-Driver:LCD Controller Configurations
+
+#
+# ESP-MM: Memory Management Configurations
+#
+# CONFIG_ESP_MM_CACHE_MSYNC_C2M_CHUNKED_OPS is not set
+# end of ESP-MM: Memory Management Configurations
+
+#
+# ESP NETIF Adapter
+#
+CONFIG_ESP_NETIF_IP_LOST_TIMER_INTERVAL=120
+# CONFIG_ESP_NETIF_PROVIDE_CUSTOM_IMPLEMENTATION is not set
+CONFIG_ESP_NETIF_TCPIP_LWIP=y
+# CONFIG_ESP_NETIF_LOOPBACK is not set
+CONFIG_ESP_NETIF_USES_TCPIP_WITH_BSD_API=y
+CONFIG_ESP_NETIF_REPORT_DATA_TRAFFIC=y
+# CONFIG_ESP_NETIF_RECEIVE_REPORT_ERRORS is not set
+# CONFIG_ESP_NETIF_L2_TAP is not set
+# CONFIG_ESP_NETIF_BRIDGE_EN is not set
+# CONFIG_ESP_NETIF_SET_DNS_PER_DEFAULT_NETIF is not set
+# end of ESP NETIF Adapter
+
+#
+# Partition API Configuration
+#
+# end of Partition API Configuration
+
+#
+# PHY
+#
+CONFIG_ESP_PHY_ENABLED=y
+CONFIG_ESP_PHY_CALIBRATION_AND_DATA_STORAGE=y
+# CONFIG_ESP_PHY_INIT_DATA_IN_PARTITION is not set
+CONFIG_ESP_PHY_MAX_WIFI_TX_POWER=20
+CONFIG_ESP_PHY_MAX_TX_POWER=20
+# CONFIG_ESP_PHY_REDUCE_TX_POWER is not set
+CONFIG_ESP_PHY_ENABLE_USB=y
+# CONFIG_ESP_PHY_ENABLE_CERT_TEST is not set
+CONFIG_ESP_PHY_RF_CAL_PARTIAL=y
+# CONFIG_ESP_PHY_RF_CAL_NONE is not set
+# CONFIG_ESP_PHY_RF_CAL_FULL is not set
+CONFIG_ESP_PHY_CALIBRATION_MODE=0
+# CONFIG_ESP_PHY_PLL_TRACK_DEBUG is not set
+# CONFIG_ESP_PHY_RECORD_USED_TIME is not set
+CONFIG_ESP_PHY_IRAM_OPT=y
+# end of PHY
+
+#
+# Power Management
+#
+CONFIG_PM_SLEEP_FUNC_IN_IRAM=y
+# CONFIG_PM_ENABLE is not set
+CONFIG_PM_SLP_IRAM_OPT=y
+CONFIG_PM_POWER_DOWN_CPU_IN_LIGHT_SLEEP=y
+CONFIG_PM_RESTORE_CACHE_TAGMEM_AFTER_LIGHT_SLEEP=y
+# end of Power Management
+
+#
+# ESP PSRAM
+#
+CONFIG_SPIRAM=y
+
+#
+# SPI RAM config
+#
+# CONFIG_SPIRAM_MODE_QUAD is not set
+CONFIG_SPIRAM_MODE_OCT=y
+CONFIG_SPIRAM_TYPE_AUTO=y
+# CONFIG_SPIRAM_TYPE_ESPPSRAM64 is not set
+CONFIG_SPIRAM_CLK_IO=30
+CONFIG_SPIRAM_CS_IO=26
+# CONFIG_SPIRAM_XIP_FROM_PSRAM is not set
+CONFIG_SPIRAM_FETCH_INSTRUCTIONS=y
+CONFIG_SPIRAM_RODATA=y
+CONFIG_SPIRAM_SPEED_120M=y
+# CONFIG_SPIRAM_SPEED_80M is not set
+# CONFIG_SPIRAM_SPEED_40M is not set
+# CONFIG_SPIRAM_TIMING_TUNING_POINT_VIA_TEMPERATURE_SENSOR is not set
+CONFIG_SPIRAM_SPEED=120
+# CONFIG_SPIRAM_ECC_ENABLE is not set
+CONFIG_SPIRAM_BOOT_HW_INIT=y
+CONFIG_SPIRAM_BOOT_INIT=y
+CONFIG_SPIRAM_PRE_CONFIGURE_MEMORY_PROTECTION=y
+# CONFIG_SPIRAM_IGNORE_NOTFOUND is not set
+# CONFIG_SPIRAM_USE_MEMMAP is not set
+# CONFIG_SPIRAM_USE_CAPS_ALLOC is not set
+CONFIG_SPIRAM_USE_MALLOC=y
+# CONFIG_SPIRAM_MEMTEST is not set
+CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=512
+# CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP is not set
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
+# CONFIG_SPIRAM_ALLOW_BSS_SEG_EXTERNAL_MEMORY is not set
+# CONFIG_SPIRAM_ALLOW_NOINIT_SEG_EXTERNAL_MEMORY is not set
+# end of SPI RAM config
+# end of ESP PSRAM
+
+#
+# ESP Ringbuf
+#
+# CONFIG_RINGBUF_PLACE_FUNCTIONS_INTO_FLASH is not set
+# end of ESP Ringbuf
+
+#
+# ESP-ROM
+#
+CONFIG_ESP_ROM_PRINT_IN_IRAM=y
+# end of ESP-ROM
+
+#
+# ESP Security Specific
+#
+# end of ESP Security Specific
+
+#
+# ESP System Settings
+#
+# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_80 is not set
+# CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_160 is not set
+CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
+CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ=240
+
+#
+# Cache config
+#
+# CONFIG_ESP32S3_INSTRUCTION_CACHE_16KB is not set
+CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
+CONFIG_ESP32S3_INSTRUCTION_CACHE_SIZE=0x8000
+# CONFIG_ESP32S3_INSTRUCTION_CACHE_4WAYS is not set
+CONFIG_ESP32S3_INSTRUCTION_CACHE_8WAYS=y
+CONFIG_ESP32S3_ICACHE_ASSOCIATED_WAYS=8
+CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_32B=y
+CONFIG_ESP32S3_INSTRUCTION_CACHE_LINE_SIZE=32
+# CONFIG_ESP32S3_DATA_CACHE_16KB is not set
+CONFIG_ESP32S3_DATA_CACHE_32KB=y
+# CONFIG_ESP32S3_DATA_CACHE_64KB is not set
+CONFIG_ESP32S3_DATA_CACHE_SIZE=0x8000
+# CONFIG_ESP32S3_DATA_CACHE_4WAYS is not set
+CONFIG_ESP32S3_DATA_CACHE_8WAYS=y
+CONFIG_ESP32S3_DCACHE_ASSOCIATED_WAYS=8
+# CONFIG_ESP32S3_DATA_CACHE_LINE_16B is not set
+# CONFIG_ESP32S3_DATA_CACHE_LINE_32B is not set
+CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
+CONFIG_ESP32S3_DATA_CACHE_LINE_SIZE=64
+# end of Cache config
+
+#
+# Memory
+#
+# CONFIG_ESP32S3_RTCDATA_IN_FAST_MEM is not set
+# CONFIG_ESP32S3_USE_FIXED_STATIC_RAM_SIZE is not set
+# end of Memory
+
+#
+# Trace memory
+#
+# CONFIG_ESP32S3_TRAX is not set
+CONFIG_ESP32S3_TRACEMEM_RESERVE_DRAM=0x0
+# end of Trace memory
+
+# CONFIG_ESP_SYSTEM_PANIC_PRINT_HALT is not set
+CONFIG_ESP_SYSTEM_PANIC_PRINT_REBOOT=y
+# CONFIG_ESP_SYSTEM_PANIC_SILENT_REBOOT is not set
+# CONFIG_ESP_SYSTEM_PANIC_GDBSTUB is not set
+CONFIG_ESP_SYSTEM_PANIC_REBOOT_DELAY_SECONDS=0
+CONFIG_ESP_SYSTEM_RTC_FAST_MEM_AS_HEAP_DEPCHECK=y
+CONFIG_ESP_SYSTEM_ALLOW_RTC_FAST_MEM_AS_HEAP=y
+
+#
+# Memory protection
+#
+CONFIG_ESP_SYSTEM_MEMPROT_FEATURE=y
+CONFIG_ESP_SYSTEM_MEMPROT_FEATURE_LOCK=y
+# end of Memory protection
+
+CONFIG_ESP_SYSTEM_EVENT_QUEUE_SIZE=32
+CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+CONFIG_ESP_MAIN_TASK_AFFINITY_CPU0=y
+# CONFIG_ESP_MAIN_TASK_AFFINITY_CPU1 is not set
+# CONFIG_ESP_MAIN_TASK_AFFINITY_NO_AFFINITY is not set
+CONFIG_ESP_MAIN_TASK_AFFINITY=0x0
+CONFIG_ESP_MINIMAL_SHARED_STACK_SIZE=2048
+CONFIG_ESP_CONSOLE_UART_DEFAULT=y
+# CONFIG_ESP_CONSOLE_USB_CDC is not set
+# CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG is not set
+# CONFIG_ESP_CONSOLE_UART_CUSTOM is not set
+# CONFIG_ESP_CONSOLE_NONE is not set
+# CONFIG_ESP_CONSOLE_SECONDARY_NONE is not set
+CONFIG_ESP_CONSOLE_SECONDARY_USB_SERIAL_JTAG=y
+CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG_ENABLED=y
+CONFIG_ESP_CONSOLE_UART=y
+CONFIG_ESP_CONSOLE_UART_NUM=0
+CONFIG_ESP_CONSOLE_ROM_SERIAL_PORT_NUM=0
+CONFIG_ESP_CONSOLE_UART_BAUDRATE=115200
+CONFIG_ESP_INT_WDT=y
+CONFIG_ESP_INT_WDT_TIMEOUT_MS=300
+CONFIG_ESP_INT_WDT_CHECK_CPU1=y
+CONFIG_ESP_TASK_WDT_EN=y
+CONFIG_ESP_TASK_WDT_INIT=y
+# CONFIG_ESP_TASK_WDT_PANIC is not set
+CONFIG_ESP_TASK_WDT_TIMEOUT_S=10
+CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
+CONFIG_ESP_TASK_WDT_CHECK_IDLE_TASK_CPU1=y
+# CONFIG_ESP_PANIC_HANDLER_IRAM is not set
+# CONFIG_ESP_DEBUG_STUBS_ENABLE is not set
+CONFIG_ESP_DEBUG_OCDAWARE=y
+CONFIG_ESP_SYSTEM_CHECK_INT_LEVEL_4=y
+CONFIG_ESP_SYSTEM_BBPLL_RECALIB=y
+# end of ESP System Settings
+
+#
+# IPC (Inter-Processor Call)
+#
+CONFIG_ESP_IPC_TASK_STACK_SIZE=1280
+CONFIG_ESP_IPC_USES_CALLERS_PRIORITY=y
+CONFIG_ESP_IPC_ISR_ENABLE=y
+# end of IPC (Inter-Processor Call)
+
+#
+# ESP Timer (High Resolution Timer)
+#
+CONFIG_ESP_TIMER_IN_IRAM=y
+# CONFIG_ESP_TIMER_PROFILING is not set
+CONFIG_ESP_TIME_FUNCS_USE_RTC_TIMER=y
+CONFIG_ESP_TIME_FUNCS_USE_ESP_TIMER=y
+CONFIG_ESP_TIMER_TASK_STACK_SIZE=3584
+CONFIG_ESP_TIMER_INTERRUPT_LEVEL=1
+# CONFIG_ESP_TIMER_SHOW_EXPERIMENTAL is not set
+CONFIG_ESP_TIMER_TASK_AFFINITY=0x0
+CONFIG_ESP_TIMER_TASK_AFFINITY_CPU0=y
+CONFIG_ESP_TIMER_ISR_AFFINITY_CPU0=y
+# CONFIG_ESP_TIMER_SUPPORTS_ISR_DISPATCH_METHOD is not set
+CONFIG_ESP_TIMER_IMPL_SYSTIMER=y
+# end of ESP Timer (High Resolution Timer)
+
+#
+# Wi-Fi
+#
+CONFIG_ESP_WIFI_ENABLED=y
+CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3
+CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6
+# CONFIG_ESP_WIFI_STATIC_TX_BUFFER is not set
+CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER=y
+CONFIG_ESP_WIFI_TX_BUFFER_TYPE=1
+CONFIG_ESP_WIFI_DYNAMIC_TX_BUFFER_NUM=32
+# CONFIG_ESP_WIFI_STATIC_RX_MGMT_BUFFER is not set
+CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER=y
+CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUF=1
+CONFIG_ESP_WIFI_RX_MGMT_BUF_NUM_DEF=5
+# CONFIG_ESP_WIFI_CSI_ENABLED is not set
+CONFIG_ESP_WIFI_AMPDU_TX_ENABLED=y
+CONFIG_ESP_WIFI_TX_BA_WIN=6
+CONFIG_ESP_WIFI_AMPDU_RX_ENABLED=y
+CONFIG_ESP_WIFI_RX_BA_WIN=3
+CONFIG_ESP_WIFI_NVS_ENABLED=y
+CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=y
+# CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1 is not set
+CONFIG_ESP_WIFI_SOFTAP_BEACON_MAX_LEN=752
+CONFIG_ESP_WIFI_MGMT_SBUF_NUM=32
+# CONFIG_ESP_WIFI_IRAM_OPT is not set
+# CONFIG_ESP_WIFI_EXTRA_IRAM_OPT is not set
+# CONFIG_ESP_WIFI_RX_IRAM_OPT is not set
+CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=y
+CONFIG_ESP_WIFI_ENABLE_SAE_PK=y
+CONFIG_ESP_WIFI_ENABLE_SAE_H2E=y
+CONFIG_ESP_WIFI_SOFTAP_SAE_SUPPORT=y
+CONFIG_ESP_WIFI_ENABLE_WPA3_OWE_STA=y
+# CONFIG_ESP_WIFI_SLP_IRAM_OPT is not set
+CONFIG_ESP_WIFI_SLP_DEFAULT_MIN_ACTIVE_TIME=50
+# CONFIG_ESP_WIFI_BSS_MAX_IDLE_SUPPORT is not set
+CONFIG_ESP_WIFI_SLP_DEFAULT_MAX_ACTIVE_TIME=10
+CONFIG_ESP_WIFI_SLP_DEFAULT_WAIT_BROADCAST_DATA_TIME=15
+# CONFIG_ESP_WIFI_FTM_ENABLE is not set
+CONFIG_ESP_WIFI_STA_DISCONNECTED_PM_ENABLE=y
+# CONFIG_ESP_WIFI_GCMP_SUPPORT is not set
+CONFIG_ESP_WIFI_GMAC_SUPPORT=y
+CONFIG_ESP_WIFI_SOFTAP_SUPPORT=y
+# CONFIG_ESP_WIFI_SLP_BEACON_LOST_OPT is not set
+CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=7
+CONFIG_ESP_WIFI_MBEDTLS_CRYPTO=y
+# CONFIG_ESP_WIFI_WAPI_PSK is not set
+# CONFIG_ESP_WIFI_SUITE_B_192 is not set
+# CONFIG_ESP_WIFI_11KV_SUPPORT is not set
+# CONFIG_ESP_WIFI_MBO_SUPPORT is not set
+# CONFIG_ESP_WIFI_ENABLE_ROAMING_APP is not set
+# CONFIG_ESP_WIFI_DPP_SUPPORT is not set
+# CONFIG_ESP_WIFI_11R_SUPPORT is not set
+# CONFIG_ESP_WIFI_WPS_SOFTAP_REGISTRAR is not set
+
+#
+# WPS Configuration Options
+#
+# CONFIG_ESP_WIFI_WPS_STRICT is not set
+# CONFIG_ESP_WIFI_WPS_PASSPHRASE is not set
+# end of WPS Configuration Options
+
+# CONFIG_ESP_WIFI_DEBUG_PRINT is not set
+# CONFIG_ESP_WIFI_TESTING_OPTIONS is not set
+# CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT is not set
+# end of Wi-Fi
+
+#
+# Core dump
+#
+# CONFIG_ESP_COREDUMP_ENABLE_TO_FLASH is not set
+# CONFIG_ESP_COREDUMP_ENABLE_TO_UART is not set
+CONFIG_ESP_COREDUMP_ENABLE_TO_NONE=y
+# end of Core dump
+
+#
+# FAT Filesystem support
+#
+CONFIG_FATFS_VOLUME_COUNT=2
+CONFIG_FATFS_LFN_NONE=y
+# CONFIG_FATFS_LFN_HEAP is not set
+# CONFIG_FATFS_LFN_STACK is not set
+# CONFIG_FATFS_SECTOR_512 is not set
+CONFIG_FATFS_SECTOR_4096=y
+# CONFIG_FATFS_CODEPAGE_DYNAMIC is not set
+CONFIG_FATFS_CODEPAGE_437=y
+# CONFIG_FATFS_CODEPAGE_720 is not set
+# CONFIG_FATFS_CODEPAGE_737 is not set
+# CONFIG_FATFS_CODEPAGE_771 is not set
+# CONFIG_FATFS_CODEPAGE_775 is not set
+# CONFIG_FATFS_CODEPAGE_850 is not set
+# CONFIG_FATFS_CODEPAGE_852 is not set
+# CONFIG_FATFS_CODEPAGE_855 is not set
+# CONFIG_FATFS_CODEPAGE_857 is not set
+# CONFIG_FATFS_CODEPAGE_860 is not set
+# CONFIG_FATFS_CODEPAGE_861 is not set
+# CONFIG_FATFS_CODEPAGE_862 is not set
+# CONFIG_FATFS_CODEPAGE_863 is not set
+# CONFIG_FATFS_CODEPAGE_864 is not set
+# CONFIG_FATFS_CODEPAGE_865 is not set
+# CONFIG_FATFS_CODEPAGE_866 is not set
+# CONFIG_FATFS_CODEPAGE_869 is not set
+# CONFIG_FATFS_CODEPAGE_932 is not set
+# CONFIG_FATFS_CODEPAGE_936 is not set
+# CONFIG_FATFS_CODEPAGE_949 is not set
+# CONFIG_FATFS_CODEPAGE_950 is not set
+CONFIG_FATFS_CODEPAGE=437
+CONFIG_FATFS_FS_LOCK=0
+CONFIG_FATFS_TIMEOUT_MS=10000
+CONFIG_FATFS_PER_FILE_CACHE=y
+CONFIG_FATFS_ALLOC_PREFER_EXTRAM=y
+# CONFIG_FATFS_USE_FASTSEEK is not set
+CONFIG_FATFS_USE_STRFUNC_NONE=y
+# CONFIG_FATFS_USE_STRFUNC_WITHOUT_CRLF_CONV is not set
+# CONFIG_FATFS_USE_STRFUNC_WITH_CRLF_CONV is not set
+CONFIG_FATFS_VFS_FSTAT_BLKSIZE=0
+# CONFIG_FATFS_IMMEDIATE_FSYNC is not set
+# CONFIG_FATFS_USE_LABEL is not set
+CONFIG_FATFS_LINK_LOCK=y
+# CONFIG_FATFS_USE_DYN_BUFFERS is not set
+
+#
+# File system free space calculation behavior
+#
+CONFIG_FATFS_DONT_TRUST_FREE_CLUSTER_CNT=0
+CONFIG_FATFS_DONT_TRUST_LAST_ALLOC=0
+# end of File system free space calculation behavior
+# end of FAT Filesystem support
+
+#
+# FreeRTOS
+#
+
+#
+# Kernel
+#
+# CONFIG_FREERTOS_SMP is not set
+# CONFIG_FREERTOS_UNICORE is not set
+CONFIG_FREERTOS_HZ=1000
+# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_NONE is not set
+# CONFIG_FREERTOS_CHECK_STACKOVERFLOW_PTRVAL is not set
+CONFIG_FREERTOS_CHECK_STACKOVERFLOW_CANARY=y
+CONFIG_FREERTOS_THREAD_LOCAL_STORAGE_POINTERS=1
+CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=1536
+# CONFIG_FREERTOS_USE_IDLE_HOOK is not set
+# CONFIG_FREERTOS_USE_TICK_HOOK is not set
+CONFIG_FREERTOS_MAX_TASK_NAME_LEN=16
+# CONFIG_FREERTOS_ENABLE_BACKWARD_COMPATIBILITY is not set
+CONFIG_FREERTOS_USE_TIMERS=y
+CONFIG_FREERTOS_TIMER_SERVICE_TASK_NAME="Tmr Svc"
+# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU0 is not set
+# CONFIG_FREERTOS_TIMER_TASK_AFFINITY_CPU1 is not set
+CONFIG_FREERTOS_TIMER_TASK_NO_AFFINITY=y
+CONFIG_FREERTOS_TIMER_SERVICE_TASK_CORE_AFFINITY=0x7FFFFFFF
+CONFIG_FREERTOS_TIMER_TASK_PRIORITY=1
+CONFIG_FREERTOS_TIMER_TASK_STACK_DEPTH=2048
+CONFIG_FREERTOS_TIMER_QUEUE_LENGTH=10
+CONFIG_FREERTOS_QUEUE_REGISTRY_SIZE=0
+CONFIG_FREERTOS_TASK_NOTIFICATION_ARRAY_ENTRIES=1
+CONFIG_FREERTOS_USE_TRACE_FACILITY=y
+CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
+# CONFIG_FREERTOS_USE_LIST_DATA_INTEGRITY_CHECK_BYTES is not set
+# CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID is not set
+CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
+CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U32=y
+# CONFIG_FREERTOS_RUN_TIME_COUNTER_TYPE_U64 is not set
+# CONFIG_FREERTOS_USE_APPLICATION_TASK_TAG is not set
+# end of Kernel
+
+#
+# Port
+#
+# CONFIG_FREERTOS_WATCHPOINT_END_OF_STACK is not set
+CONFIG_FREERTOS_TLSP_DELETION_CALLBACKS=y
+# CONFIG_FREERTOS_TASK_PRE_DELETION_HOOK is not set
+# CONFIG_FREERTOS_ENABLE_STATIC_TASK_CLEAN_UP is not set
+CONFIG_FREERTOS_CHECK_MUTEX_GIVEN_BY_OWNER=y
+CONFIG_FREERTOS_ISR_STACKSIZE=1536
+CONFIG_FREERTOS_INTERRUPT_BACKTRACE=y
+# CONFIG_FREERTOS_FPU_IN_ISR is not set
+CONFIG_FREERTOS_TICK_SUPPORT_SYSTIMER=y
+CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL1=y
+# CONFIG_FREERTOS_CORETIMER_SYSTIMER_LVL3 is not set
+CONFIG_FREERTOS_SYSTICK_USES_SYSTIMER=y
+CONFIG_FREERTOS_RUN_TIME_STATS_USING_ESP_TIMER=y
+# CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH is not set
+# CONFIG_FREERTOS_CHECK_PORT_CRITICAL_COMPLIANCE is not set
+# end of Port
+
+#
+# Extra
+#
+CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM=y
+# end of Extra
+
+CONFIG_FREERTOS_PORT=y
+CONFIG_FREERTOS_NO_AFFINITY=0x7FFFFFFF
+CONFIG_FREERTOS_SUPPORT_STATIC_ALLOCATION=y
+CONFIG_FREERTOS_DEBUG_OCDAWARE=y
+CONFIG_FREERTOS_ENABLE_TASK_SNAPSHOT=y
+CONFIG_FREERTOS_PLACE_SNAPSHOT_FUNS_INTO_FLASH=y
+CONFIG_FREERTOS_NUMBER_OF_CORES=2
+CONFIG_FREERTOS_IN_IRAM=y
+# end of FreeRTOS
+
+#
+# Hardware Abstraction Layer (HAL) and Low Level (LL)
+#
+CONFIG_HAL_ASSERTION_EQUALS_SYSTEM=y
+# CONFIG_HAL_ASSERTION_DISABLE is not set
+# CONFIG_HAL_ASSERTION_SILENT is not set
+# CONFIG_HAL_ASSERTION_ENABLE is not set
+CONFIG_HAL_DEFAULT_ASSERTION_LEVEL=2
+CONFIG_HAL_WDT_USE_ROM_IMPL=y
+# end of Hardware Abstraction Layer (HAL) and Low Level (LL)
+
+#
+# Heap memory debugging
+#
+CONFIG_HEAP_POISONING_DISABLED=y
+# CONFIG_HEAP_POISONING_LIGHT is not set
+# CONFIG_HEAP_POISONING_COMPREHENSIVE is not set
+CONFIG_HEAP_TRACING_OFF=y
+# CONFIG_HEAP_TRACING_STANDALONE is not set
+# CONFIG_HEAP_TRACING_TOHOST is not set
+# CONFIG_HEAP_USE_HOOKS is not set
+# CONFIG_HEAP_TASK_TRACKING is not set
+# CONFIG_HEAP_ABORT_WHEN_ALLOCATION_FAILS is not set
+# CONFIG_HEAP_PLACE_FUNCTION_INTO_FLASH is not set
+# end of Heap memory debugging
+
+#
+# Log
+#
+CONFIG_LOG_VERSION_1=y
+# CONFIG_LOG_VERSION_2 is not set
+CONFIG_LOG_VERSION=1
+
+#
+# Log Level
+#
+# CONFIG_LOG_DEFAULT_LEVEL_NONE is not set
+# CONFIG_LOG_DEFAULT_LEVEL_ERROR is not set
+# CONFIG_LOG_DEFAULT_LEVEL_WARN is not set
+CONFIG_LOG_DEFAULT_LEVEL_INFO=y
+# CONFIG_LOG_DEFAULT_LEVEL_DEBUG is not set
+# CONFIG_LOG_DEFAULT_LEVEL_VERBOSE is not set
+CONFIG_LOG_DEFAULT_LEVEL=3
+CONFIG_LOG_MAXIMUM_EQUALS_DEFAULT=y
+# CONFIG_LOG_MAXIMUM_LEVEL_DEBUG is not set
+# CONFIG_LOG_MAXIMUM_LEVEL_VERBOSE is not set
+CONFIG_LOG_MAXIMUM_LEVEL=3
+
+#
+# Level Settings
+#
+# CONFIG_LOG_MASTER_LEVEL is not set
+CONFIG_LOG_DYNAMIC_LEVEL_CONTROL=y
+# CONFIG_LOG_TAG_LEVEL_IMPL_NONE is not set
+# CONFIG_LOG_TAG_LEVEL_IMPL_LINKED_LIST is not set
+CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_AND_LINKED_LIST=y
+# CONFIG_LOG_TAG_LEVEL_CACHE_ARRAY is not set
+CONFIG_LOG_TAG_LEVEL_CACHE_BINARY_MIN_HEAP=y
+CONFIG_LOG_TAG_LEVEL_IMPL_CACHE_SIZE=31
+# end of Level Settings
+# end of Log Level
+
+#
+# Format
+#
+# CONFIG_LOG_COLORS is not set
+CONFIG_LOG_TIMESTAMP_SOURCE_RTOS=y
+# CONFIG_LOG_TIMESTAMP_SOURCE_SYSTEM is not set
+# end of Format
+
+#
+# Settings
+#
+CONFIG_LOG_MODE_TEXT_EN=y
+CONFIG_LOG_MODE_TEXT=y
+# end of Settings
+
+CONFIG_LOG_IN_IRAM=y
+# end of Log
+
+#
+# LWIP
+#
+CONFIG_LWIP_ENABLE=y
+CONFIG_LWIP_LOCAL_HOSTNAME="espressif"
+CONFIG_LWIP_TCPIP_TASK_PRIO=18
+# CONFIG_LWIP_TCPIP_CORE_LOCKING is not set
+# CONFIG_LWIP_CHECK_THREAD_SAFETY is not set
+CONFIG_LWIP_DNS_SUPPORT_MDNS_QUERIES=y
+# CONFIG_LWIP_L2_TO_L3_COPY is not set
+# CONFIG_LWIP_IRAM_OPTIMIZATION is not set
+# CONFIG_LWIP_EXTRA_IRAM_OPTIMIZATION is not set
+CONFIG_LWIP_TIMERS_ONDEMAND=y
+CONFIG_LWIP_ND6=y
+# CONFIG_LWIP_FORCE_ROUTER_FORWARDING is not set
+CONFIG_LWIP_MAX_SOCKETS=10
+# CONFIG_LWIP_USE_ONLY_LWIP_SELECT is not set
+# CONFIG_LWIP_SO_LINGER is not set
+CONFIG_LWIP_SO_REUSE=y
+CONFIG_LWIP_SO_REUSE_RXTOALL=y
+# CONFIG_LWIP_SO_RCVBUF is not set
+# CONFIG_LWIP_NETBUF_RECVINFO is not set
+CONFIG_LWIP_IP_DEFAULT_TTL=64
+CONFIG_LWIP_IP4_FRAG=y
+CONFIG_LWIP_IP6_FRAG=y
+# CONFIG_LWIP_IP4_REASSEMBLY is not set
+# CONFIG_LWIP_IP6_REASSEMBLY is not set
+CONFIG_LWIP_IP_REASS_MAX_PBUFS=10
+# CONFIG_LWIP_IP_FORWARD is not set
+# CONFIG_LWIP_STATS is not set
+CONFIG_LWIP_ESP_GRATUITOUS_ARP=y
+CONFIG_LWIP_GARP_TMR_INTERVAL=60
+CONFIG_LWIP_ESP_MLDV6_REPORT=y
+CONFIG_LWIP_MLDV6_TMR_INTERVAL=40
+CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16
+CONFIG_LWIP_DHCP_DOES_ARP_CHECK=y
+# CONFIG_LWIP_DHCP_DOES_ACD_CHECK is not set
+# CONFIG_LWIP_DHCP_DOES_NOT_CHECK_OFFERED_IP is not set
+# CONFIG_LWIP_DHCP_DISABLE_CLIENT_ID is not set
+CONFIG_LWIP_DHCP_DISABLE_VENDOR_CLASS_ID=y
+# CONFIG_LWIP_DHCP_RESTORE_LAST_IP is not set
+CONFIG_LWIP_DHCP_OPTIONS_LEN=68
+CONFIG_LWIP_NUM_NETIF_CLIENT_DATA=0
+CONFIG_LWIP_DHCP_COARSE_TIMER_SECS=1
+
+#
+# DHCP server
+#
+CONFIG_LWIP_DHCPS=y
+CONFIG_LWIP_DHCPS_LEASE_UNIT=60
+CONFIG_LWIP_DHCPS_MAX_STATION_NUM=8
+CONFIG_LWIP_DHCPS_STATIC_ENTRIES=y
+CONFIG_LWIP_DHCPS_ADD_DNS=y
+# end of DHCP server
+
+# CONFIG_LWIP_AUTOIP is not set
+CONFIG_LWIP_IPV4=y
+CONFIG_LWIP_IPV6=y
+# CONFIG_LWIP_IPV6_AUTOCONFIG is not set
+CONFIG_LWIP_IPV6_NUM_ADDRESSES=3
+# CONFIG_LWIP_IPV6_FORWARD is not set
+# CONFIG_LWIP_NETIF_STATUS_CALLBACK is not set
+CONFIG_LWIP_NETIF_LOOPBACK=y
+CONFIG_LWIP_LOOPBACK_MAX_PBUFS=8
+
+#
+# TCP
+#
+CONFIG_LWIP_MAX_ACTIVE_TCP=16
+CONFIG_LWIP_MAX_LISTENING_TCP=16
+CONFIG_LWIP_TCP_HIGH_SPEED_RETRANSMISSION=y
+CONFIG_LWIP_TCP_MAXRTX=12
+CONFIG_LWIP_TCP_SYNMAXRTX=12
+CONFIG_LWIP_TCP_MSS=1440
+CONFIG_LWIP_TCP_TMR_INTERVAL=250
+CONFIG_LWIP_TCP_MSL=60000
+CONFIG_LWIP_TCP_FIN_WAIT_TIMEOUT=20000
+CONFIG_LWIP_TCP_SND_BUF_DEFAULT=5760
+CONFIG_LWIP_TCP_WND_DEFAULT=5760
+CONFIG_LWIP_TCP_RECVMBOX_SIZE=6
+CONFIG_LWIP_TCP_ACCEPTMBOX_SIZE=6
+CONFIG_LWIP_TCP_QUEUE_OOSEQ=y
+CONFIG_LWIP_TCP_OOSEQ_TIMEOUT=6
+CONFIG_LWIP_TCP_OOSEQ_MAX_PBUFS=4
+# CONFIG_LWIP_TCP_SACK_OUT is not set
+CONFIG_LWIP_TCP_OVERSIZE_MSS=y
+# CONFIG_LWIP_TCP_OVERSIZE_QUARTER_MSS is not set
+# CONFIG_LWIP_TCP_OVERSIZE_DISABLE is not set
+CONFIG_LWIP_TCP_RTO_TIME=1500
+# end of TCP
+
+#
+# UDP
+#
+CONFIG_LWIP_MAX_UDP_PCBS=16
+CONFIG_LWIP_UDP_RECVMBOX_SIZE=6
+# end of UDP
+
+#
+# Checksums
+#
+# CONFIG_LWIP_CHECKSUM_CHECK_IP is not set
+# CONFIG_LWIP_CHECKSUM_CHECK_UDP is not set
+CONFIG_LWIP_CHECKSUM_CHECK_ICMP=y
+# end of Checksums
+
+CONFIG_LWIP_TCPIP_TASK_STACK_SIZE=3072
+CONFIG_LWIP_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
+# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU0 is not set
+# CONFIG_LWIP_TCPIP_TASK_AFFINITY_CPU1 is not set
+CONFIG_LWIP_TCPIP_TASK_AFFINITY=0x7FFFFFFF
+CONFIG_LWIP_IPV6_MEMP_NUM_ND6_QUEUE=3
+CONFIG_LWIP_IPV6_ND6_NUM_NEIGHBORS=5
+CONFIG_LWIP_IPV6_ND6_NUM_PREFIXES=5
+CONFIG_LWIP_IPV6_ND6_NUM_ROUTERS=3
+CONFIG_LWIP_IPV6_ND6_NUM_DESTINATIONS=10
+# CONFIG_LWIP_PPP_SUPPORT is not set
+# CONFIG_LWIP_SLIP_SUPPORT is not set
+
+#
+# ICMP
+#
+CONFIG_LWIP_ICMP=y
+# CONFIG_LWIP_MULTICAST_PING is not set
+# CONFIG_LWIP_BROADCAST_PING is not set
+# end of ICMP
+
+#
+# LWIP RAW API
+#
+CONFIG_LWIP_MAX_RAW_PCBS=16
+# end of LWIP RAW API
+
+#
+# SNTP
+#
+CONFIG_LWIP_SNTP_MAX_SERVERS=1
+# CONFIG_LWIP_DHCP_GET_NTP_SRV is not set
+CONFIG_LWIP_SNTP_UPDATE_DELAY=3600000
+CONFIG_LWIP_SNTP_STARTUP_DELAY=y
+CONFIG_LWIP_SNTP_MAXIMUM_STARTUP_DELAY=5000
+# end of SNTP
+
+#
+# DNS
+#
+CONFIG_LWIP_DNS_MAX_HOST_IP=1
+CONFIG_LWIP_DNS_MAX_SERVERS=3
+# CONFIG_LWIP_FALLBACK_DNS_SERVER_SUPPORT is not set
+# CONFIG_LWIP_DNS_SETSERVER_WITH_NETIF is not set
+# CONFIG_LWIP_USE_ESP_GETADDRINFO is not set
+# end of DNS
+
+CONFIG_LWIP_BRIDGEIF_MAX_PORTS=7
+CONFIG_LWIP_ESP_LWIP_ASSERT=y
+
+#
+# Hooks
+#
+# CONFIG_LWIP_HOOK_TCP_ISN_NONE is not set
+CONFIG_LWIP_HOOK_TCP_ISN_DEFAULT=y
+# CONFIG_LWIP_HOOK_TCP_ISN_CUSTOM is not set
+CONFIG_LWIP_HOOK_IP6_ROUTE_NONE=y
+# CONFIG_LWIP_HOOK_IP6_ROUTE_DEFAULT is not set
+# CONFIG_LWIP_HOOK_IP6_ROUTE_CUSTOM is not set
+CONFIG_LWIP_HOOK_ND6_GET_GW_NONE=y
+# CONFIG_LWIP_HOOK_ND6_GET_GW_DEFAULT is not set
+# CONFIG_LWIP_HOOK_ND6_GET_GW_CUSTOM is not set
+CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_NONE=y
+# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_DEFAULT is not set
+# CONFIG_LWIP_HOOK_IP6_SELECT_SRC_ADDR_CUSTOM is not set
+CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_NONE=y
+# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_DEFAULT is not set
+# CONFIG_LWIP_HOOK_DHCP_EXTRA_OPTION_CUSTOM is not set
+CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_NONE=y
+# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_DEFAULT is not set
+# CONFIG_LWIP_HOOK_NETCONN_EXT_RESOLVE_CUSTOM is not set
+CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_NONE=y
+# CONFIG_LWIP_HOOK_DNS_EXT_RESOLVE_CUSTOM is not set
+# CONFIG_LWIP_HOOK_IP6_INPUT_NONE is not set
+CONFIG_LWIP_HOOK_IP6_INPUT_DEFAULT=y
+# CONFIG_LWIP_HOOK_IP6_INPUT_CUSTOM is not set
+# end of Hooks
+
+# CONFIG_LWIP_DEBUG is not set
+# end of LWIP
+
+#
+# mbedTLS
+#
+# CONFIG_MBEDTLS_INTERNAL_MEM_ALLOC is not set
+CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
+# CONFIG_MBEDTLS_DEFAULT_MEM_ALLOC is not set
+# CONFIG_MBEDTLS_CUSTOM_MEM_ALLOC is not set
+CONFIG_MBEDTLS_ASYMMETRIC_CONTENT_LEN=y
+CONFIG_MBEDTLS_SSL_IN_CONTENT_LEN=16384
+CONFIG_MBEDTLS_SSL_OUT_CONTENT_LEN=4096
+CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
+CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
+CONFIG_MBEDTLS_DYNAMIC_FREE_CA_CERT=y
+# CONFIG_MBEDTLS_DEBUG is not set
+
+#
+# mbedTLS v3.x related
+#
+# CONFIG_MBEDTLS_SSL_VARIABLE_BUFFER_LENGTH is not set
+# CONFIG_MBEDTLS_X509_TRUSTED_CERT_CALLBACK is not set
+# CONFIG_MBEDTLS_SSL_CONTEXT_SERIALIZATION is not set
+# CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE is not set
+CONFIG_MBEDTLS_PKCS7_C=y
+# end of mbedTLS v3.x related
+
+#
+# Certificate Bundle
+#
+CONFIG_MBEDTLS_CERTIFICATE_BUNDLE=y
+CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_FULL=y
+# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_CMN is not set
+# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEFAULT_NONE is not set
+# CONFIG_MBEDTLS_CUSTOM_CERTIFICATE_BUNDLE is not set
+# CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_DEPRECATED_LIST is not set
+CONFIG_MBEDTLS_CERTIFICATE_BUNDLE_MAX_CERTS=200
+# end of Certificate Bundle
+
+# CONFIG_MBEDTLS_ECP_RESTARTABLE is not set
+CONFIG_MBEDTLS_CMAC_C=y
+CONFIG_MBEDTLS_HARDWARE_AES=y
+CONFIG_MBEDTLS_AES_USE_INTERRUPT=y
+CONFIG_MBEDTLS_AES_INTERRUPT_LEVEL=0
+CONFIG_MBEDTLS_GCM_SUPPORT_NON_AES_CIPHER=y
+CONFIG_MBEDTLS_HARDWARE_MPI=y
+# CONFIG_MBEDTLS_LARGE_KEY_SOFTWARE_MPI is not set
+CONFIG_MBEDTLS_MPI_USE_INTERRUPT=y
+CONFIG_MBEDTLS_MPI_INTERRUPT_LEVEL=0
+CONFIG_MBEDTLS_HARDWARE_SHA=y
+CONFIG_MBEDTLS_ROM_MD5=y
+# CONFIG_MBEDTLS_ATCA_HW_ECDSA_SIGN is not set
+# CONFIG_MBEDTLS_ATCA_HW_ECDSA_VERIFY is not set
+CONFIG_MBEDTLS_HAVE_TIME=y
+# CONFIG_MBEDTLS_PLATFORM_TIME_ALT is not set
+# CONFIG_MBEDTLS_HAVE_TIME_DATE is not set
+CONFIG_MBEDTLS_ECDSA_DETERMINISTIC=y
+CONFIG_MBEDTLS_SHA1_C=y
+CONFIG_MBEDTLS_SHA512_C=y
+# CONFIG_MBEDTLS_SHA3_C is not set
+CONFIG_MBEDTLS_TLS_SERVER_AND_CLIENT=y
+# CONFIG_MBEDTLS_TLS_SERVER_ONLY is not set
+# CONFIG_MBEDTLS_TLS_CLIENT_ONLY is not set
+# CONFIG_MBEDTLS_TLS_DISABLED is not set
+CONFIG_MBEDTLS_TLS_SERVER=y
+CONFIG_MBEDTLS_TLS_CLIENT=y
+CONFIG_MBEDTLS_TLS_ENABLED=y
+
+#
+# TLS Key Exchange Methods
+#
+# CONFIG_MBEDTLS_PSK_MODES is not set
+CONFIG_MBEDTLS_KEY_EXCHANGE_RSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ELLIPTIC_CURVE=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_RSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_ECDSA=y
+CONFIG_MBEDTLS_KEY_EXCHANGE_ECDH_RSA=y
+# end of TLS Key Exchange Methods
+
+# CONFIG_MBEDTLS_SSL_RENEGOTIATION is not set
+CONFIG_MBEDTLS_SSL_PROTO_TLS1_2=y
+# CONFIG_MBEDTLS_SSL_PROTO_GMTSSL1_1 is not set
+# CONFIG_MBEDTLS_SSL_PROTO_DTLS is not set
+CONFIG_MBEDTLS_SSL_ALPN=y
+CONFIG_MBEDTLS_CLIENT_SSL_SESSION_TICKETS=y
+CONFIG_MBEDTLS_SERVER_SSL_SESSION_TICKETS=y
+
+#
+# Symmetric Ciphers
+#
+CONFIG_MBEDTLS_AES_C=y
+# CONFIG_MBEDTLS_CAMELLIA_C is not set
+# CONFIG_MBEDTLS_DES_C is not set
+# CONFIG_MBEDTLS_BLOWFISH_C is not set
+# CONFIG_MBEDTLS_XTEA_C is not set
+CONFIG_MBEDTLS_CCM_C=y
+CONFIG_MBEDTLS_GCM_C=y
+# CONFIG_MBEDTLS_NIST_KW_C is not set
+# end of Symmetric Ciphers
+
+# CONFIG_MBEDTLS_RIPEMD160_C is not set
+
+#
+# Certificates
+#
+CONFIG_MBEDTLS_PEM_PARSE_C=y
+CONFIG_MBEDTLS_PEM_WRITE_C=y
+CONFIG_MBEDTLS_X509_CRL_PARSE_C=y
+CONFIG_MBEDTLS_X509_CSR_PARSE_C=y
+# end of Certificates
+
+CONFIG_MBEDTLS_ECP_C=y
+CONFIG_MBEDTLS_PK_PARSE_EC_EXTENDED=y
+CONFIG_MBEDTLS_PK_PARSE_EC_COMPRESSED=y
+# CONFIG_MBEDTLS_DHM_C is not set
+CONFIG_MBEDTLS_ECDH_C=y
+CONFIG_MBEDTLS_ECDSA_C=y
+# CONFIG_MBEDTLS_ECJPAKE_C is not set
+CONFIG_MBEDTLS_ECP_DP_SECP192R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP224R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP256R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP384R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP521R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP192K1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP224K1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_SECP256K1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_BP256R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_BP384R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_BP512R1_ENABLED=y
+CONFIG_MBEDTLS_ECP_DP_CURVE25519_ENABLED=y
+CONFIG_MBEDTLS_ECP_NIST_OPTIM=y
+# CONFIG_MBEDTLS_ECP_FIXED_POINT_OPTIM is not set
+# CONFIG_MBEDTLS_POLY1305_C is not set
+# CONFIG_MBEDTLS_CHACHA20_C is not set
+# CONFIG_MBEDTLS_HKDF_C is not set
+# CONFIG_MBEDTLS_THREADING_C is not set
+CONFIG_MBEDTLS_ERROR_STRINGS=y
+CONFIG_MBEDTLS_FS_IO=y
+# CONFIG_MBEDTLS_ALLOW_WEAK_CERTIFICATE_VERIFICATION is not set
+# end of mbedTLS
+
+#
+# ESP-MQTT Configurations
+#
+CONFIG_MQTT_PROTOCOL_311=y
+# CONFIG_MQTT_PROTOCOL_5 is not set
+CONFIG_MQTT_TRANSPORT_SSL=y
+CONFIG_MQTT_TRANSPORT_WEBSOCKET=y
+CONFIG_MQTT_TRANSPORT_WEBSOCKET_SECURE=y
+# CONFIG_MQTT_MSG_ID_INCREMENTAL is not set
+# CONFIG_MQTT_SKIP_PUBLISH_IF_DISCONNECTED is not set
+# CONFIG_MQTT_REPORT_DELETED_MESSAGES is not set
+# CONFIG_MQTT_USE_CUSTOM_CONFIG is not set
+# CONFIG_MQTT_TASK_CORE_SELECTION_ENABLED is not set
+# CONFIG_MQTT_CUSTOM_OUTBOX is not set
+# end of ESP-MQTT Configurations
+
+#
+# LibC
+#
+CONFIG_LIBC_NEWLIB=y
+# CONFIG_LIBC_PICOLIBC is not set
+CONFIG_LIBC_MISC_IN_IRAM=y
+CONFIG_LIBC_LOCKS_PLACE_IN_IRAM=y
+CONFIG_LIBC_STDOUT_LINE_ENDING_CRLF=y
+# CONFIG_LIBC_STDOUT_LINE_ENDING_LF is not set
+# CONFIG_LIBC_STDOUT_LINE_ENDING_CR is not set
+# CONFIG_LIBC_STDIN_LINE_ENDING_CRLF is not set
+# CONFIG_LIBC_STDIN_LINE_ENDING_LF is not set
+CONFIG_LIBC_STDIN_LINE_ENDING_CR=y
+CONFIG_LIBC_NEWLIB_NANO_FORMAT=y
+CONFIG_LIBC_TIME_SYSCALL_USE_RTC_HRT=y
+# CONFIG_LIBC_TIME_SYSCALL_USE_RTC is not set
+# CONFIG_LIBC_TIME_SYSCALL_USE_HRT is not set
+# CONFIG_LIBC_TIME_SYSCALL_USE_NONE is not set
+# end of LibC
+
+CONFIG_STDATOMIC_S32C1I_SPIRAM_WORKAROUND=y
+
+#
+# NVS
+#
+# CONFIG_NVS_ENCRYPTION is not set
+# CONFIG_NVS_ASSERT_ERROR_CHECK is not set
+# CONFIG_NVS_LEGACY_DUP_KEYS_COMPATIBILITY is not set
+# CONFIG_NVS_ALLOCATE_CACHE_IN_SPIRAM is not set
+# end of NVS
+
+#
+# OpenThread
+#
+# CONFIG_OPENTHREAD_ENABLED is not set
+
+#
+# OpenThread Spinel
+#
+# CONFIG_OPENTHREAD_SPINEL_ONLY is not set
+# end of OpenThread Spinel
+# end of OpenThread
+
+#
+# Protocomm
+#
+CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_0=y
+CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_1=y
+CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_VERSION_2=y
+CONFIG_ESP_PROTOCOMM_SUPPORT_SECURITY_PATCH_VERSION=y
+# end of Protocomm
+
+#
+# PThreads
+#
+CONFIG_PTHREAD_TASK_PRIO_DEFAULT=5
+CONFIG_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072
+CONFIG_PTHREAD_STACK_MIN=768
+CONFIG_PTHREAD_DEFAULT_CORE_NO_AFFINITY=y
+# CONFIG_PTHREAD_DEFAULT_CORE_0 is not set
+# CONFIG_PTHREAD_DEFAULT_CORE_1 is not set
+CONFIG_PTHREAD_TASK_CORE_DEFAULT=-1
+CONFIG_PTHREAD_TASK_NAME_DEFAULT="pthread"
+# end of PThreads
+
+#
+# MMU Config
+#
+CONFIG_MMU_PAGE_SIZE_64KB=y
+CONFIG_MMU_PAGE_MODE="64KB"
+CONFIG_MMU_PAGE_SIZE=0x10000
+# end of MMU Config
+
+#
+# Main Flash configuration
+#
+
+#
+# SPI Flash behavior when brownout
+#
+CONFIG_SPI_FLASH_BROWNOUT_RESET_XMC=y
+CONFIG_SPI_FLASH_BROWNOUT_RESET=y
+# end of SPI Flash behavior when brownout
+
+#
+# Optional and Experimental Features (READ DOCS FIRST)
+#
+
+#
+# Features here require specific hardware (READ DOCS FIRST!)
+#
+CONFIG_SPI_FLASH_UNDER_HIGH_FREQ=y
+# CONFIG_SPI_FLASH_HPM_ENA is not set
+CONFIG_SPI_FLASH_HPM_AUTO=y
+# CONFIG_SPI_FLASH_HPM_DIS is not set
+CONFIG_SPI_FLASH_HPM_ON=y
+CONFIG_SPI_FLASH_HPM_DC_AUTO=y
+# CONFIG_SPI_FLASH_HPM_DC_DISABLE is not set
+# CONFIG_SPI_FLASH_AUTO_SUSPEND is not set
+CONFIG_SPI_FLASH_SUSPEND_TSUS_VAL_US=50
+# CONFIG_SPI_FLASH_FORCE_ENABLE_XMC_C_SUSPEND is not set
+# CONFIG_SPI_FLASH_FORCE_ENABLE_C6_H2_SUSPEND is not set
+CONFIG_SPI_FLASH_PLACE_FUNCTIONS_IN_IRAM=y
+# end of Optional and Experimental Features (READ DOCS FIRST)
+# end of Main Flash configuration
+
+#
+# SPI Flash driver
+#
+# CONFIG_SPI_FLASH_VERIFY_WRITE is not set
+# CONFIG_SPI_FLASH_ENABLE_COUNTERS is not set
+CONFIG_SPI_FLASH_ROM_DRIVER_PATCH=y
+# CONFIG_SPI_FLASH_ROM_IMPL is not set
+CONFIG_SPI_FLASH_DANGEROUS_WRITE_ABORTS=y
+# CONFIG_SPI_FLASH_DANGEROUS_WRITE_FAILS is not set
+# CONFIG_SPI_FLASH_DANGEROUS_WRITE_ALLOWED is not set
+# CONFIG_SPI_FLASH_BYPASS_BLOCK_ERASE is not set
+CONFIG_SPI_FLASH_YIELD_DURING_ERASE=y
+CONFIG_SPI_FLASH_ERASE_YIELD_DURATION_MS=20
+CONFIG_SPI_FLASH_ERASE_YIELD_TICKS=1
+CONFIG_SPI_FLASH_WRITE_CHUNK_SIZE=8192
+# CONFIG_SPI_FLASH_SIZE_OVERRIDE is not set
+# CONFIG_SPI_FLASH_CHECK_ERASE_TIMEOUT_DISABLED is not set
+# CONFIG_SPI_FLASH_OVERRIDE_CHIP_DRIVER_LIST is not set
+
+#
+# Auto-detect flash chips
+#
+CONFIG_SPI_FLASH_VENDOR_XMC_SUPPORTED=y
+CONFIG_SPI_FLASH_VENDOR_GD_SUPPORTED=y
+CONFIG_SPI_FLASH_VENDOR_ISSI_SUPPORTED=y
+CONFIG_SPI_FLASH_VENDOR_MXIC_SUPPORTED=y
+CONFIG_SPI_FLASH_VENDOR_WINBOND_SUPPORTED=y
+CONFIG_SPI_FLASH_VENDOR_BOYA_SUPPORTED=y
+CONFIG_SPI_FLASH_VENDOR_TH_SUPPORTED=y
+CONFIG_SPI_FLASH_SUPPORT_ISSI_CHIP=y
+CONFIG_SPI_FLASH_SUPPORT_MXIC_CHIP=y
+CONFIG_SPI_FLASH_SUPPORT_GD_CHIP=y
+CONFIG_SPI_FLASH_SUPPORT_WINBOND_CHIP=y
+CONFIG_SPI_FLASH_SUPPORT_BOYA_CHIP=y
+CONFIG_SPI_FLASH_SUPPORT_TH_CHIP=y
+CONFIG_SPI_FLASH_SUPPORT_MXIC_OPI_CHIP=y
+# end of Auto-detect flash chips
+
+CONFIG_SPI_FLASH_ENABLE_ENCRYPTED_READ_WRITE=y
+# end of SPI Flash driver
+
+#
+# SPIFFS Configuration
+#
+CONFIG_SPIFFS_MAX_PARTITIONS=3
+
+#
+# SPIFFS Cache Configuration
+#
+CONFIG_SPIFFS_CACHE=y
+CONFIG_SPIFFS_CACHE_WR=y
+# CONFIG_SPIFFS_CACHE_STATS is not set
+# end of SPIFFS Cache Configuration
+
+CONFIG_SPIFFS_PAGE_CHECK=y
+CONFIG_SPIFFS_GC_MAX_RUNS=10
+# CONFIG_SPIFFS_GC_STATS is not set
+CONFIG_SPIFFS_PAGE_SIZE=256
+CONFIG_SPIFFS_OBJ_NAME_LEN=32
+# CONFIG_SPIFFS_FOLLOW_SYMLINKS is not set
+CONFIG_SPIFFS_USE_MAGIC=y
+CONFIG_SPIFFS_USE_MAGIC_LENGTH=y
+CONFIG_SPIFFS_META_LENGTH=4
+CONFIG_SPIFFS_USE_MTIME=y
+
+#
+# Debug Configuration
+#
+# CONFIG_SPIFFS_DBG is not set
+# CONFIG_SPIFFS_API_DBG is not set
+# CONFIG_SPIFFS_GC_DBG is not set
+# CONFIG_SPIFFS_CACHE_DBG is not set
+# CONFIG_SPIFFS_CHECK_DBG is not set
+# CONFIG_SPIFFS_TEST_VISUALISATION is not set
+# end of Debug Configuration
+# end of SPIFFS Configuration
+
+#
+# TCP Transport
+#
+
+#
+# Websocket
+#
+CONFIG_WS_TRANSPORT=y
+CONFIG_WS_BUFFER_SIZE=1024
+# CONFIG_WS_DYNAMIC_BUFFER is not set
+# end of Websocket
+# end of TCP Transport
+
+#
+# Ultra Low Power (ULP) Co-processor
+#
+# CONFIG_ULP_COPROC_ENABLED is not set
+
+#
+# ULP Debugging Options
+#
+# end of ULP Debugging Options
+# end of Ultra Low Power (ULP) Co-processor
+
+#
+# Unity unit testing library
+#
+CONFIG_UNITY_ENABLE_FLOAT=y
+CONFIG_UNITY_ENABLE_DOUBLE=y
+# CONFIG_UNITY_ENABLE_64BIT is not set
+# CONFIG_UNITY_ENABLE_COLOR is not set
+CONFIG_UNITY_ENABLE_IDF_TEST_RUNNER=y
+# CONFIG_UNITY_ENABLE_FIXTURE is not set
+# CONFIG_UNITY_ENABLE_BACKTRACE_ON_FAIL is not set
+# CONFIG_UNITY_TEST_ORDER_BY_FILE_PATH_AND_LINE is not set
+# end of Unity unit testing library
+
+#
+# USB-OTG
+#
+CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=256
+CONFIG_USB_HOST_HW_BUFFER_BIAS_BALANCED=y
+# CONFIG_USB_HOST_HW_BUFFER_BIAS_IN is not set
+# CONFIG_USB_HOST_HW_BUFFER_BIAS_PERIODIC_OUT is not set
+
+#
+# Hub Driver Configuration
+#
+
+#
+# Root Port configuration
+#
+CONFIG_USB_HOST_DEBOUNCE_DELAY_MS=250
+CONFIG_USB_HOST_RESET_HOLD_MS=30
+CONFIG_USB_HOST_RESET_RECOVERY_MS=30
+CONFIG_USB_HOST_SET_ADDR_RECOVERY_MS=10
+# end of Root Port configuration
+
+# CONFIG_USB_HOST_HUBS_SUPPORTED is not set
+# end of Hub Driver Configuration
+
+# CONFIG_USB_HOST_ENABLE_ENUM_FILTER_CALLBACK is not set
+CONFIG_USB_OTG_SUPPORTED=y
+# end of USB-OTG
+
+#
+# Virtual file system
+#
+CONFIG_VFS_SUPPORT_IO=y
+CONFIG_VFS_SUPPORT_DIR=y
+CONFIG_VFS_SUPPORT_SELECT=y
+CONFIG_VFS_SUPPRESS_SELECT_DEBUG_OUTPUT=y
+CONFIG_VFS_SELECT_IN_RAM=y
+CONFIG_VFS_SUPPORT_TERMIOS=y
+CONFIG_VFS_MAX_COUNT=8
+
+#
+# Host File System I/O (Semihosting)
+#
+CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS=1
+# end of Host File System I/O (Semihosting)
+
+CONFIG_VFS_INITIALIZE_DEV_NULL=y
+# end of Virtual file system
+
+#
+# Wear Levelling
+#
+# CONFIG_WL_SECTOR_SIZE_512 is not set
+CONFIG_WL_SECTOR_SIZE_4096=y
+CONFIG_WL_SECTOR_SIZE=4096
+# end of Wear Levelling
+
+#
+# Wi-Fi Provisioning Manager
+#
+CONFIG_WIFI_PROV_SCAN_MAX_ENTRIES=16
+CONFIG_WIFI_PROV_AUTOSTOP_TIMEOUT=30
+CONFIG_WIFI_PROV_STA_ALL_CHANNEL_SCAN=y
+# CONFIG_WIFI_PROV_STA_FAST_SCAN is not set
+# end of Wi-Fi Provisioning Manager
+
+#
+# ESP Emote Graphics Framework
+#
+# CONFIG_GFX_FONT_FREETYPE_SUPPORT is not set
+# end of ESP Emote Graphics Framework
+
+#
+# Animation Player Configuration
+#
+CONFIG_ANIM_PLAYER_DEFAULT_FPS=30
+# end of Animation Player Configuration
+
+#
+# ADC Battery Estimation
+#
+CONFIG_OCV_SOC_MODEL_1=y
+# CONFIG_OCV_SOC_MODEL_2 is not set
+CONFIG_ADC_FILTER_WINDOW_SIZE=10
+CONFIG_BATTERY_CAPACITY_LPF_COEFFICIENT=2
+CONFIG_BATTERY_STATE_SOFTWARE_ESTIMATION=y
+
+#
+# Software Estimation Configuration
+#
+CONFIG_SOFTWARE_ESTIMATION_SAMPLE_COUNT=10
+CONFIG_SOFTWARE_ESTIMATION_SAMPLE_INTERVAL=20000
+# end of Software Estimation Configuration
+# end of ADC Battery Estimation
+
+#
+# ADC Mic
+#
+CONFIG_ADC_MIC_APPLY_GAIN=3
+CONFIG_ADC_MIC_OFFSET=16380
+CONFIG_ADC_MIC_TASK_PRIORITY=1
+CONFIG_ADC_MIC_TASK_STACK_SIZE=3072
+CONFIG_ADC_MIC_TASK_CORE=-1
+# end of ADC Mic
+
+#
+# IoT Button
+#
+CONFIG_BUTTON_PERIOD_TIME_MS=5
+CONFIG_BUTTON_DEBOUNCE_TICKS=2
+CONFIG_BUTTON_SHORT_PRESS_TIME_MS=180
+CONFIG_BUTTON_LONG_PRESS_TIME_MS=1500
+CONFIG_BUTTON_LONG_PRESS_HOLD_SERIAL_TIME_MS=20
+CONFIG_ADC_BUTTON_MAX_CHANNEL=3
+CONFIG_ADC_BUTTON_MAX_BUTTON_PER_CHANNEL=8
+CONFIG_ADC_BUTTON_SAMPLE_TIMES=1
+# end of IoT Button
+
+#
+# CMake Utilities
+#
+# CONFIG_CU_RELINKER_ENABLE is not set
+# CONFIG_CU_DIAGNOSTICS_COLOR_NEVER is not set
+CONFIG_CU_DIAGNOSTICS_COLOR_ALWAYS=y
+# CONFIG_CU_DIAGNOSTICS_COLOR_AUTO is not set
+# CONFIG_CU_GCC_LTO_ENABLE is not set
+# CONFIG_CU_GCC_STRING_1BYTE_ALIGN is not set
+# end of CMake Utilities
+
+#
+# DSP Library
+#
+CONFIG_DSP_OPTIMIZATIONS_SUPPORTED=y
+# CONFIG_DSP_ANSI is not set
+CONFIG_DSP_OPTIMIZED=y
+CONFIG_DSP_OPTIMIZATION=1
+# CONFIG_DSP_MAX_FFT_SIZE_512 is not set
+# CONFIG_DSP_MAX_FFT_SIZE_1024 is not set
+# CONFIG_DSP_MAX_FFT_SIZE_2048 is not set
+CONFIG_DSP_MAX_FFT_SIZE_4096=y
+# CONFIG_DSP_MAX_FFT_SIZE_8192 is not set
+# CONFIG_DSP_MAX_FFT_SIZE_16384 is not set
+# CONFIG_DSP_MAX_FFT_SIZE_32768 is not set
+CONFIG_DSP_MAX_FFT_SIZE=4096
+# end of DSP Library
+
+#
+# Espressif Camera Sensors Configurations
+#
+
+#
+# Camera Sensor Configuration
+#
+
+#
+# Select and Set Camera Sensor
+#
+# CONFIG_CAMERA_BF3901 is not set
+# end of Select and Set Camera Sensor
+# end of Camera Sensor Configuration
+
+#
+# Camera XCLK Generator Configuration
+#
+CONFIG_CAMERA_XCLK_USE_LEDC=y
+# end of Camera XCLK Generator Configuration
+
+#
+# Camera Motor Control Configuration
+#
+# CONFIG_CAM_MOTOR_DW9714 is not set
+# end of Camera Motor Control Configuration
+
+#
+# Camera SPI Controller Configuration
+#
+CONFIG_CAM_CTLR_SPI_ISR_CACHE_SAFE=y
+# CONFIG_CAM_CTLR_SPI_DISABLE_BACKUP_BUFFER is not set
+# CONFIG_CAM_CTLR_SPI_DISABLE_AUTO_DECODE is not set
+CONFIG_CAM_CTLR_SPI_DECODE_TASK_STACK_SIZE=4096
+CONFIG_CAM_CTLR_SPI_DECODE_TASK_PRIORITY=10
+# end of Camera SPI Controller Configuration
+# end of Espressif Camera Sensors Configurations
+
+#
+# Audio Codec Device Configuration
+#
+# CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE is not set
+CONFIG_CODEC_ES8311_SUPPORT=y
+CONFIG_CODEC_ES7210_SUPPORT=y
+CONFIG_CODEC_ES7243_SUPPORT=y
+CONFIG_CODEC_ES7243E_SUPPORT=y
+CONFIG_CODEC_ES8156_SUPPORT=y
+CONFIG_CODEC_AW88298_SUPPORT=y
+CONFIG_CODEC_ES8389_SUPPORT=y
+CONFIG_CODEC_ES8374_SUPPORT=y
+CONFIG_CODEC_ES8388_SUPPORT=y
+CONFIG_CODEC_TAS5805M_SUPPORT=y
+# CONFIG_CODEC_ZL38063_SUPPORT is not set
+# CONFIG_CODEC_CJC8910_SUPPORT is not set
+# end of Audio Codec Device Configuration
+
+#
+# ESP LCD TOUCH
+#
+CONFIG_ESP_LCD_TOUCH_MAX_POINTS=5
+CONFIG_ESP_LCD_TOUCH_MAX_BUTTONS=1
+# end of ESP LCD TOUCH
+
+#
+# ESP LCD TOUCH - CST816S
+#
+# CONFIG_ESP_LCD_TOUCH_CST816S_DISABLE_READ_ID is not set
+# end of ESP LCD TOUCH - CST816S
+
+#
+# ESP LVGL PORT
+#
+# end of ESP LVGL PORT
+
+#
+# mmap file support format
+#
+CONFIG_MMAP_FILE_NAME_LENGTH=16
+# end of mmap file support format
+
+#
+# Espressif SCCB Configurations
+#
+CONFIG_ESP_SCCB_TRANS_TIMEOUT_DEFAULT=500
+# end of Espressif SCCB Configurations
+
+#
+# Espressif Video Configuration
+#
+CONFIG_ESP_VIDEO_CHECK_PARAMETERS=y
+# CONFIG_ESP_VIDEO_ENABLE_SPI_VIDEO_DEVICE is not set
+# CONFIG_ESP_VIDEO_ENABLE_USB_UVC_VIDEO_DEVICE is not set
+
+#
+# Video Data Preprocessing
+#
+# end of Video Data Preprocessing
+# end of Espressif Video Configuration
+
+#
+# IOT Knob
+#
+CONFIG_KNOB_PERIOD_TIME_MS=3
+CONFIG_KNOB_DEBOUNCE_TICKS=2
+CONFIG_KNOB_HIGH_LIMIT=1000
+CONFIG_KNOB_LOW_LIMIT=-1000
+# end of IOT Knob
+
+#
+# USB HOST UVC
+#
+# CONFIG_UVC_PRINTF_CONFIGURATION_DESCRIPTOR is not set
+CONFIG_UVC_INTERVAL_ARRAY_SIZE=3
+# end of USB HOST UVC
+
+#
+# LVGL configuration
+#
+CONFIG_LV_CONF_SKIP=y
+# CONFIG_LV_CONF_MINIMAL is not set
+
+#
+# Color Settings
+#
+# CONFIG_LV_COLOR_DEPTH_32 is not set
+# CONFIG_LV_COLOR_DEPTH_24 is not set
+CONFIG_LV_COLOR_DEPTH_16=y
+# CONFIG_LV_COLOR_DEPTH_8 is not set
+# CONFIG_LV_COLOR_DEPTH_1 is not set
+CONFIG_LV_COLOR_DEPTH=16
+# end of Color Settings
+
+#
+# Memory Settings
+#
+# CONFIG_LV_USE_BUILTIN_MALLOC is not set
+CONFIG_LV_USE_CLIB_MALLOC=y
+# CONFIG_LV_USE_MICROPYTHON_MALLOC is not set
+# CONFIG_LV_USE_RTTHREAD_MALLOC is not set
+# CONFIG_LV_USE_CUSTOM_MALLOC is not set
+# CONFIG_LV_USE_BUILTIN_STRING is not set
+CONFIG_LV_USE_CLIB_STRING=y
+# CONFIG_LV_USE_CUSTOM_STRING is not set
+# CONFIG_LV_USE_BUILTIN_SPRINTF is not set
+CONFIG_LV_USE_CLIB_SPRINTF=y
+# CONFIG_LV_USE_CUSTOM_SPRINTF is not set
+# end of Memory Settings
+
+#
+# HAL Settings
+#
+CONFIG_LV_DEF_REFR_PERIOD=33
+CONFIG_LV_DPI_DEF=130
+# end of HAL Settings
+
+#
+# Operating System (OS)
+#
+CONFIG_LV_OS_NONE=y
+# CONFIG_LV_OS_PTHREAD is not set
+# CONFIG_LV_OS_FREERTOS is not set
+# CONFIG_LV_OS_CMSIS_RTOS2 is not set
+# CONFIG_LV_OS_RTTHREAD is not set
+# CONFIG_LV_OS_WINDOWS is not set
+# CONFIG_LV_OS_MQX is not set
+# CONFIG_LV_OS_CUSTOM is not set
+# end of Operating System (OS)
+
+#
+# Rendering Configuration
+#
+CONFIG_LV_DRAW_BUF_STRIDE_ALIGN=1
+CONFIG_LV_DRAW_BUF_ALIGN=4
+CONFIG_LV_DRAW_LAYER_SIMPLE_BUF_SIZE=24576
+CONFIG_LV_DRAW_LAYER_MAX_MEMORY=0
+CONFIG_LV_DRAW_THREAD_STACK_SIZE=8192
+CONFIG_LV_DRAW_THREAD_PRIO=3
+CONFIG_LV_USE_DRAW_SW=y
+CONFIG_LV_DRAW_SW_SUPPORT_RGB565=y
+CONFIG_LV_DRAW_SW_SUPPORT_RGB565A8=y
+CONFIG_LV_DRAW_SW_SUPPORT_RGB888=y
+CONFIG_LV_DRAW_SW_SUPPORT_XRGB8888=y
+CONFIG_LV_DRAW_SW_SUPPORT_ARGB8888=y
+CONFIG_LV_DRAW_SW_SUPPORT_L8=y
+CONFIG_LV_DRAW_SW_SUPPORT_AL88=y
+CONFIG_LV_DRAW_SW_SUPPORT_A8=y
+CONFIG_LV_DRAW_SW_SUPPORT_I1=y
+CONFIG_LV_DRAW_SW_I1_LUM_THRESHOLD=127
+CONFIG_LV_DRAW_SW_DRAW_UNIT_CNT=1
+# CONFIG_LV_USE_DRAW_ARM2D_SYNC is not set
+# CONFIG_LV_USE_NATIVE_HELIUM_ASM is not set
+CONFIG_LV_DRAW_SW_COMPLEX=y
+# CONFIG_LV_USE_DRAW_SW_COMPLEX_GRADIENTS is not set
+CONFIG_LV_DRAW_SW_SHADOW_CACHE_SIZE=0
+CONFIG_LV_DRAW_SW_CIRCLE_CACHE_SIZE=4
+CONFIG_LV_DRAW_SW_ASM_NONE=y
+# CONFIG_LV_DRAW_SW_ASM_NEON is not set
+# CONFIG_LV_DRAW_SW_ASM_HELIUM is not set
+# CONFIG_LV_DRAW_SW_ASM_CUSTOM is not set
+CONFIG_LV_USE_DRAW_SW_ASM=0
+# CONFIG_LV_USE_DRAW_VGLITE is not set
+# CONFIG_LV_USE_PXP is not set
+# CONFIG_LV_USE_DRAW_G2D is not set
+# CONFIG_LV_USE_DRAW_DAVE2D is not set
+# CONFIG_LV_USE_DRAW_SDL is not set
+# CONFIG_LV_USE_DRAW_VG_LITE is not set
+# CONFIG_LV_USE_VECTOR_GRAPHIC is not set
+# CONFIG_LV_USE_DRAW_DMA2D is not set
+# end of Rendering Configuration
+
+#
+# Feature Configuration
+#
+
+#
+# Logging
+#
+# CONFIG_LV_USE_LOG is not set
+# end of Logging
+
+#
+# Asserts
+#
+CONFIG_LV_USE_ASSERT_NULL=y
+CONFIG_LV_USE_ASSERT_MALLOC=y
+CONFIG_LV_USE_ASSERT_STYLE=y
+# CONFIG_LV_USE_ASSERT_MEM_INTEGRITY is not set
+# CONFIG_LV_USE_ASSERT_OBJ is not set
+CONFIG_LV_ASSERT_HANDLER_INCLUDE="assert.h"
+# end of Asserts
+
+#
+# Debug
+#
+# CONFIG_LV_USE_REFR_DEBUG is not set
+# CONFIG_LV_USE_LAYER_DEBUG is not set
+# CONFIG_LV_USE_PARALLEL_DRAW_DEBUG is not set
+# end of Debug
+
+#
+# Others
+#
+# CONFIG_LV_ENABLE_GLOBAL_CUSTOM is not set
+CONFIG_LV_CACHE_DEF_SIZE=0
+CONFIG_LV_IMAGE_HEADER_CACHE_DEF_CNT=0
+CONFIG_LV_GRADIENT_MAX_STOPS=2
+CONFIG_LV_COLOR_MIX_ROUND_OFS=128
+# CONFIG_LV_OBJ_STYLE_CACHE is not set
+# CONFIG_LV_USE_OBJ_ID is not set
+# CONFIG_LV_USE_OBJ_NAME is not set
+# CONFIG_LV_USE_OBJ_PROPERTY is not set
+# end of Others
+# end of Feature Configuration
+
+#
+# Compiler Settings
+#
+# CONFIG_LV_BIG_ENDIAN_SYSTEM is not set
+CONFIG_LV_ATTRIBUTE_MEM_ALIGN_SIZE=1
+# CONFIG_LV_ATTRIBUTE_FAST_MEM_USE_IRAM is not set
+# CONFIG_LV_USE_FLOAT is not set
+# CONFIG_LV_USE_MATRIX is not set
+# CONFIG_LV_USE_PRIVATE_API is not set
+# end of Compiler Settings
+
+#
+# Font Usage
+#
+
+#
+# Enable built-in fonts
+#
+# CONFIG_LV_FONT_MONTSERRAT_8 is not set
+# CONFIG_LV_FONT_MONTSERRAT_10 is not set
+# CONFIG_LV_FONT_MONTSERRAT_12 is not set
+CONFIG_LV_FONT_MONTSERRAT_14=y
+# CONFIG_LV_FONT_MONTSERRAT_16 is not set
+# CONFIG_LV_FONT_MONTSERRAT_18 is not set
+# CONFIG_LV_FONT_MONTSERRAT_20 is not set
+# CONFIG_LV_FONT_MONTSERRAT_22 is not set
+# CONFIG_LV_FONT_MONTSERRAT_24 is not set
+# CONFIG_LV_FONT_MONTSERRAT_26 is not set
+# CONFIG_LV_FONT_MONTSERRAT_28 is not set
+# CONFIG_LV_FONT_MONTSERRAT_30 is not set
+# CONFIG_LV_FONT_MONTSERRAT_32 is not set
+# CONFIG_LV_FONT_MONTSERRAT_34 is not set
+# CONFIG_LV_FONT_MONTSERRAT_36 is not set
+# CONFIG_LV_FONT_MONTSERRAT_38 is not set
+# CONFIG_LV_FONT_MONTSERRAT_40 is not set
+# CONFIG_LV_FONT_MONTSERRAT_42 is not set
+# CONFIG_LV_FONT_MONTSERRAT_44 is not set
+# CONFIG_LV_FONT_MONTSERRAT_46 is not set
+# CONFIG_LV_FONT_MONTSERRAT_48 is not set
+# CONFIG_LV_FONT_MONTSERRAT_28_COMPRESSED is not set
+# CONFIG_LV_FONT_DEJAVU_16_PERSIAN_HEBREW is not set
+# CONFIG_LV_FONT_SIMSUN_14_CJK is not set
+# CONFIG_LV_FONT_SIMSUN_16_CJK is not set
+# CONFIG_LV_FONT_SOURCE_HAN_SANS_SC_14_CJK is not set
+# CONFIG_LV_FONT_SOURCE_HAN_SANS_SC_16_CJK is not set
+# CONFIG_LV_FONT_UNSCII_8 is not set
+# CONFIG_LV_FONT_UNSCII_16 is not set
+# end of Enable built-in fonts
+
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_8 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_10 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_12 is not set
+CONFIG_LV_FONT_DEFAULT_MONTSERRAT_14=y
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_16 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_18 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_20 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_22 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_24 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_26 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_30 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_32 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_34 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_36 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_38 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_40 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_42 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_44 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_46 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_48 is not set
+# CONFIG_LV_FONT_DEFAULT_MONTSERRAT_28_COMPRESSED is not set
+# CONFIG_LV_FONT_DEFAULT_DEJAVU_16_PERSIAN_HEBREW is not set
+# CONFIG_LV_FONT_DEFAULT_SIMSUN_14_CJK is not set
+# CONFIG_LV_FONT_DEFAULT_SIMSUN_16_CJK is not set
+# CONFIG_LV_FONT_DEFAULT_SOURCE_HAN_SANS_SC_14_CJK is not set
+# CONFIG_LV_FONT_DEFAULT_SOURCE_HAN_SANS_SC_16_CJK is not set
+# CONFIG_LV_FONT_DEFAULT_UNSCII_8 is not set
+# CONFIG_LV_FONT_DEFAULT_UNSCII_16 is not set
+CONFIG_LV_FONT_FMT_TXT_LARGE=y
+# CONFIG_LV_USE_FONT_COMPRESSED is not set
+# CONFIG_LV_USE_FONT_PLACEHOLDER is not set
+
+#
+# Enable static fonts
+#
+# end of Enable static fonts
+# end of Font Usage
+
+#
+# Text Settings
+#
+CONFIG_LV_TXT_ENC_UTF8=y
+# CONFIG_LV_TXT_ENC_ASCII is not set
+CONFIG_LV_TXT_BREAK_CHARS=" ,.;:-_)}"
+CONFIG_LV_TXT_LINE_BREAK_LONG_LEN=0
+CONFIG_LV_TXT_COLOR_CMD="#"
+# CONFIG_LV_USE_BIDI is not set
+# CONFIG_LV_USE_ARABIC_PERSIAN_CHARS is not set
+# end of Text Settings
+
+#
+# Widget Usage
+#
+CONFIG_LV_WIDGETS_HAS_DEFAULT_VALUE=y
+# CONFIG_LV_USE_ANIMIMG is not set
+CONFIG_LV_USE_ARC=y
+CONFIG_LV_USE_BAR=y
+CONFIG_LV_USE_BUTTON=y
+CONFIG_LV_USE_BUTTONMATRIX=y
+# CONFIG_LV_USE_CALENDAR is not set
+CONFIG_LV_USE_CANVAS=y
+# CONFIG_LV_USE_CHART is not set
+CONFIG_LV_USE_CHECKBOX=y
+CONFIG_LV_USE_DROPDOWN=y
+CONFIG_LV_USE_IMAGE=y
+CONFIG_LV_USE_IMAGEBUTTON=y
+# CONFIG_LV_USE_KEYBOARD is not set
+CONFIG_LV_USE_LABEL=y
+CONFIG_LV_LABEL_TEXT_SELECTION=y
+CONFIG_LV_LABEL_LONG_TXT_HINT=y
+CONFIG_LV_LABEL_WAIT_CHAR_COUNT=3
+# CONFIG_LV_USE_LED is not set
+CONFIG_LV_USE_LINE=y
+# CONFIG_LV_USE_LIST is not set
+# CONFIG_LV_USE_MENU is not set
+# CONFIG_LV_USE_MSGBOX is not set
+CONFIG_LV_USE_ROLLER=y
+CONFIG_LV_USE_SCALE=y
+CONFIG_LV_USE_SLIDER=y
+# CONFIG_LV_USE_SPAN is not set
+# CONFIG_LV_USE_SPINBOX is not set
+# CONFIG_LV_USE_SPINNER is not set
+CONFIG_LV_USE_SWITCH=y
+CONFIG_LV_USE_TEXTAREA=y
+CONFIG_LV_TEXTAREA_DEF_PWD_SHOW_TIME=1500
+CONFIG_LV_USE_TABLE=y
+# CONFIG_LV_USE_TABVIEW is not set
+# CONFIG_LV_USE_TILEVIEW is not set
+# CONFIG_LV_USE_WIN is not set
+# end of Widget Usage
+
+#
+# Themes
+#
+CONFIG_LV_USE_THEME_DEFAULT=y
+# CONFIG_LV_THEME_DEFAULT_DARK is not set
+CONFIG_LV_THEME_DEFAULT_GROW=y
+CONFIG_LV_THEME_DEFAULT_TRANSITION_TIME=80
+CONFIG_LV_USE_THEME_SIMPLE=y
+# CONFIG_LV_USE_THEME_MONO is not set
+# end of Themes
+
+#
+# Layouts
+#
+CONFIG_LV_USE_FLEX=y
+CONFIG_LV_USE_GRID=y
+# end of Layouts
+
+#
+# 3rd Party Libraries
+#
+CONFIG_LV_FS_DEFAULT_DRIVER_LETTER=0
+# CONFIG_LV_USE_FS_STDIO is not set
+# CONFIG_LV_USE_FS_POSIX is not set
+# CONFIG_LV_USE_FS_WIN32 is not set
+# CONFIG_LV_USE_FS_FATFS is not set
+# CONFIG_LV_USE_FS_MEMFS is not set
+# CONFIG_LV_USE_FS_LITTLEFS is not set
+# CONFIG_LV_USE_FS_ARDUINO_ESP_LITTLEFS is not set
+# CONFIG_LV_USE_FS_ARDUINO_SD is not set
+# CONFIG_LV_USE_FS_UEFI is not set
+CONFIG_LV_USE_LODEPNG=y
+# CONFIG_LV_USE_LIBPNG is not set
+# CONFIG_LV_USE_BMP is not set
+# CONFIG_LV_USE_TJPGD is not set
+# CONFIG_LV_USE_LIBJPEG_TURBO is not set
+# CONFIG_LV_USE_GIF is not set
+# CONFIG_LV_BIN_DECODER_RAM_LOAD is not set
+# CONFIG_LV_USE_RLE is not set
+# CONFIG_LV_USE_QRCODE is not set
+# CONFIG_LV_USE_BARCODE is not set
+# CONFIG_LV_USE_FREETYPE is not set
+# CONFIG_LV_USE_TINY_TTF is not set
+# CONFIG_LV_USE_RLOTTIE is not set
+# CONFIG_LV_USE_THORVG is not set
+# CONFIG_LV_USE_LZ4 is not set
+# CONFIG_LV_USE_FFMPEG is not set
+# end of 3rd Party Libraries
+
+#
+# Others
+#
+CONFIG_LV_USE_SNAPSHOT=y
+# CONFIG_LV_USE_SYSMON is not set
+# CONFIG_LV_USE_PROFILER is not set
+# CONFIG_LV_USE_MONKEY is not set
+# CONFIG_LV_USE_GRIDNAV is not set
+# CONFIG_LV_USE_FRAGMENT is not set
+CONFIG_LV_USE_IMGFONT=y
+CONFIG_LV_USE_OBSERVER=y
+# CONFIG_LV_USE_IME_PINYIN is not set
+# CONFIG_LV_USE_FILE_EXPLORER is not set
+# CONFIG_LV_USE_FONT_MANAGER is not set
+# CONFIG_LV_USE_TEST is not set
+# CONFIG_LV_USE_XML is not set
+# CONFIG_LV_USE_COLOR_FILTER is not set
+CONFIG_LVGL_VERSION_MAJOR=9
+CONFIG_LVGL_VERSION_MINOR=3
+CONFIG_LVGL_VERSION_PATCH=0
+# end of Others
+
+#
+# Devices
+#
+# CONFIG_LV_USE_SDL is not set
+# CONFIG_LV_USE_X11 is not set
+# CONFIG_LV_USE_WAYLAND is not set
+# CONFIG_LV_USE_LINUX_FBDEV is not set
+# CONFIG_LV_USE_NUTTX is not set
+# CONFIG_LV_USE_LINUX_DRM is not set
+# CONFIG_LV_USE_TFT_ESPI is not set
+# CONFIG_LV_USE_EVDEV is not set
+# CONFIG_LV_USE_LIBINPUT is not set
+# CONFIG_LV_USE_ST7735 is not set
+# CONFIG_LV_USE_ST7789 is not set
+# CONFIG_LV_USE_ST7796 is not set
+# CONFIG_LV_USE_ILI9341 is not set
+# CONFIG_LV_USE_GENERIC_MIPI is not set
+# CONFIG_LV_USE_RENESAS_GLCDC is not set
+# CONFIG_LV_USE_ST_LTDC is not set
+# CONFIG_LV_USE_FT81X is not set
+# CONFIG_LV_USE_UEFI is not set
+# CONFIG_LV_USE_OPENGLES is not set
+# CONFIG_LV_USE_QNX is not set
+# end of Devices
+
+#
+# Examples
+#
+# CONFIG_LV_BUILD_EXAMPLES is not set
+# end of Examples
+
+#
+# Demos
+#
+# CONFIG_LV_BUILD_DEMOS is not set
+# end of Demos
+# end of LVGL configuration
+
+#
+# SH1106 ESP-IDF Driver
+#
+# end of Component config
+
+CONFIG_IDF_EXPERIMENTAL_FEATURES=y
+
+# Deprecated options for backward compatibility
+# CONFIG_APP_BUILD_TYPE_ELF_RAM is not set
+# CONFIG_NO_BLOBS is not set
+CONFIG_APP_ROLLBACK_ENABLE=y
+# CONFIG_APP_ANTI_ROLLBACK is not set
+CONFIG_LOG_BOOTLOADER_LEVEL_NONE=y
+# CONFIG_LOG_BOOTLOADER_LEVEL_ERROR is not set
+# CONFIG_LOG_BOOTLOADER_LEVEL_WARN is not set
+# CONFIG_LOG_BOOTLOADER_LEVEL_INFO is not set
+# CONFIG_LOG_BOOTLOADER_LEVEL_DEBUG is not set
+# CONFIG_LOG_BOOTLOADER_LEVEL_VERBOSE is not set
+CONFIG_LOG_BOOTLOADER_LEVEL=0
+# CONFIG_FLASH_ENCRYPTION_ENABLED is not set
+CONFIG_FLASHMODE_QIO=y
+# CONFIG_FLASHMODE_QOUT is not set
+# CONFIG_FLASHMODE_DIO is not set
+# CONFIG_FLASHMODE_DOUT is not set
+CONFIG_MONITOR_BAUD=115200
+# CONFIG_OPTIMIZATION_LEVEL_DEBUG is not set
+# CONFIG_COMPILER_OPTIMIZATION_LEVEL_DEBUG is not set
+# CONFIG_COMPILER_OPTIMIZATION_DEFAULT is not set
+CONFIG_OPTIMIZATION_LEVEL_RELEASE=y
+CONFIG_COMPILER_OPTIMIZATION_LEVEL_RELEASE=y
+CONFIG_OPTIMIZATION_ASSERTIONS_ENABLED=y
+# CONFIG_OPTIMIZATION_ASSERTIONS_SILENT is not set
+# CONFIG_OPTIMIZATION_ASSERTIONS_DISABLED is not set
+CONFIG_OPTIMIZATION_ASSERTION_LEVEL=2
+CONFIG_CXX_EXCEPTIONS=y
+CONFIG_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
+CONFIG_STACK_CHECK_NONE=y
+# CONFIG_STACK_CHECK_NORM is not set
+# CONFIG_STACK_CHECK_STRONG is not set
+# CONFIG_STACK_CHECK_ALL is not set
+# CONFIG_WARN_WRITE_STRINGS is not set
+# CONFIG_ESP32_APPTRACE_DEST_TRAX is not set
+CONFIG_ESP32_APPTRACE_DEST_NONE=y
+CONFIG_ESP32_APPTRACE_LOCK_ENABLE=y
+# CONFIG_EXTERNAL_COEX_ENABLE is not set
+# CONFIG_ESP_WIFI_EXTERNAL_COEXIST_ENABLE is not set
+# CONFIG_GPTIMER_ISR_IRAM_SAFE is not set
+# CONFIG_MCPWM_ISR_IRAM_SAFE is not set
+# CONFIG_EVENT_LOOP_PROFILING is not set
+CONFIG_POST_EVENTS_FROM_ISR=y
+CONFIG_POST_EVENTS_FROM_IRAM_ISR=y
+CONFIG_GDBSTUB_SUPPORT_TASKS=y
+CONFIG_GDBSTUB_MAX_TASKS=32
+# CONFIG_OTA_ALLOW_HTTP is not set
+CONFIG_ESP32S3_DEEP_SLEEP_WAKEUP_DELAY=2000
+CONFIG_ESP_SLEEP_DEEP_SLEEP_WAKEUP_DELAY=2000
+CONFIG_ESP32S3_RTC_CLK_SRC_INT_RC=y
+# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_CRYS is not set
+# CONFIG_ESP32S3_RTC_CLK_SRC_EXT_OSC is not set
+# CONFIG_ESP32S3_RTC_CLK_SRC_INT_8MD256 is not set
+CONFIG_ESP32S3_RTC_CLK_CAL_CYCLES=1024
+CONFIG_PERIPH_CTRL_FUNC_IN_IRAM=y
+CONFIG_BROWNOUT_DET=y
+CONFIG_ESP32S3_BROWNOUT_DET=y
+CONFIG_BROWNOUT_DET_LVL_SEL_7=y
+CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_7=y
+# CONFIG_BROWNOUT_DET_LVL_SEL_6 is not set
+# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_6 is not set
+# CONFIG_BROWNOUT_DET_LVL_SEL_5 is not set
+# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_5 is not set
+# CONFIG_BROWNOUT_DET_LVL_SEL_4 is not set
+# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_4 is not set
+# CONFIG_BROWNOUT_DET_LVL_SEL_3 is not set
+# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_3 is not set
+# CONFIG_BROWNOUT_DET_LVL_SEL_2 is not set
+# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_2 is not set
+# CONFIG_BROWNOUT_DET_LVL_SEL_1 is not set
+# CONFIG_ESP32S3_BROWNOUT_DET_LVL_SEL_1 is not set
+CONFIG_BROWNOUT_DET_LVL=7
+CONFIG_ESP32S3_BROWNOUT_DET_LVL=7
+CONFIG_ESP_SYSTEM_BROWNOUT_INTR=y
+CONFIG_ESP32_PHY_CALIBRATION_AND_DATA_STORAGE=y
+# CONFIG_ESP32_PHY_INIT_DATA_IN_PARTITION is not set
+CONFIG_ESP32_PHY_MAX_WIFI_TX_POWER=20
+CONFIG_ESP32_PHY_MAX_TX_POWER=20
+# CONFIG_REDUCE_PHY_TX_POWER is not set
+# CONFIG_ESP32_REDUCE_PHY_TX_POWER is not set
+CONFIG_ESP_SYSTEM_PM_POWER_DOWN_CPU=y
+CONFIG_PM_POWER_DOWN_TAGMEM_IN_LIGHT_SLEEP=y
+CONFIG_ESP32S3_SPIRAM_SUPPORT=y
+CONFIG_DEFAULT_PSRAM_CLK_IO=30
+CONFIG_DEFAULT_PSRAM_CS_IO=26
+# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_80 is not set
+# CONFIG_ESP32S3_DEFAULT_CPU_FREQ_160 is not set
+CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240=y
+CONFIG_ESP32S3_DEFAULT_CPU_FREQ_MHZ=240
+CONFIG_SYSTEM_EVENT_QUEUE_SIZE=32
+CONFIG_SYSTEM_EVENT_TASK_STACK_SIZE=4096
+CONFIG_MAIN_TASK_STACK_SIZE=8192
+CONFIG_CONSOLE_UART_DEFAULT=y
+# CONFIG_CONSOLE_UART_CUSTOM is not set
+# CONFIG_CONSOLE_UART_NONE is not set
+# CONFIG_ESP_CONSOLE_UART_NONE is not set
+CONFIG_CONSOLE_UART=y
+CONFIG_CONSOLE_UART_NUM=0
+CONFIG_CONSOLE_UART_BAUDRATE=115200
+CONFIG_INT_WDT=y
+CONFIG_INT_WDT_TIMEOUT_MS=300
+CONFIG_INT_WDT_CHECK_CPU1=y
+CONFIG_TASK_WDT=y
+CONFIG_ESP_TASK_WDT=y
+# CONFIG_TASK_WDT_PANIC is not set
+CONFIG_TASK_WDT_TIMEOUT_S=10
+CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU0=y
+CONFIG_TASK_WDT_CHECK_IDLE_TASK_CPU1=y
+# CONFIG_ESP32_DEBUG_STUBS_ENABLE is not set
+CONFIG_ESP32S3_DEBUG_OCDAWARE=y
+CONFIG_IPC_TASK_STACK_SIZE=1280
+CONFIG_TIMER_TASK_STACK_SIZE=3584
+CONFIG_ESP32_WIFI_ENABLED=y
+CONFIG_ESP32_WIFI_STATIC_RX_BUFFER_NUM=3
+CONFIG_ESP32_WIFI_DYNAMIC_RX_BUFFER_NUM=6
+# CONFIG_ESP32_WIFI_STATIC_TX_BUFFER is not set
+CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER=y
+CONFIG_ESP32_WIFI_TX_BUFFER_TYPE=1
+CONFIG_ESP32_WIFI_DYNAMIC_TX_BUFFER_NUM=32
+# CONFIG_ESP32_WIFI_CSI_ENABLED is not set
+CONFIG_ESP32_WIFI_AMPDU_TX_ENABLED=y
+CONFIG_ESP32_WIFI_TX_BA_WIN=6
+CONFIG_ESP32_WIFI_AMPDU_RX_ENABLED=y
+CONFIG_ESP32_WIFI_RX_BA_WIN=3
+CONFIG_ESP32_WIFI_NVS_ENABLED=y
+CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_0=y
+# CONFIG_ESP32_WIFI_TASK_PINNED_TO_CORE_1 is not set
+CONFIG_ESP32_WIFI_SOFTAP_BEACON_MAX_LEN=752
+CONFIG_ESP32_WIFI_MGMT_SBUF_NUM=32
+# CONFIG_ESP32_WIFI_IRAM_OPT is not set
+# CONFIG_ESP32_WIFI_RX_IRAM_OPT is not set
+CONFIG_ESP32_WIFI_ENABLE_WPA3_SAE=y
+CONFIG_ESP32_WIFI_ENABLE_WPA3_OWE_STA=y
+CONFIG_WPA_MBEDTLS_CRYPTO=y
+# CONFIG_WPA_WAPI_PSK is not set
+# CONFIG_WPA_SUITE_B_192 is not set
+# CONFIG_WPA_11KV_SUPPORT is not set
+# CONFIG_WPA_MBO_SUPPORT is not set
+# CONFIG_WPA_DPP_SUPPORT is not set
+# CONFIG_WPA_11R_SUPPORT is not set
+# CONFIG_WPA_WPS_SOFTAP_REGISTRAR is not set
+# CONFIG_WPA_WPS_STRICT is not set
+# CONFIG_WPA_DEBUG_PRINT is not set
+# CONFIG_WPA_TESTING_OPTIONS is not set
+# CONFIG_ESP32_ENABLE_COREDUMP_TO_FLASH is not set
+# CONFIG_ESP32_ENABLE_COREDUMP_TO_UART is not set
+CONFIG_ESP32_ENABLE_COREDUMP_TO_NONE=y
+CONFIG_TIMER_TASK_PRIORITY=1
+CONFIG_TIMER_TASK_STACK_DEPTH=2048
+CONFIG_TIMER_QUEUE_LENGTH=10
+# CONFIG_ENABLE_STATIC_TASK_CLEAN_UP_HOOK is not set
+CONFIG_SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY=y
+# CONFIG_HAL_ASSERTION_SILIENT is not set
+# CONFIG_L2_TO_L3_COPY is not set
+CONFIG_ESP_GRATUITOUS_ARP=y
+CONFIG_GARP_TMR_INTERVAL=60
+CONFIG_TCPIP_RECVMBOX_SIZE=16
+CONFIG_TCP_MAXRTX=12
+CONFIG_TCP_SYNMAXRTX=12
+CONFIG_TCP_MSS=1440
+CONFIG_TCP_MSL=60000
+CONFIG_TCP_SND_BUF_DEFAULT=5760
+CONFIG_TCP_WND_DEFAULT=5760
+CONFIG_TCP_RECVMBOX_SIZE=6
+CONFIG_TCP_QUEUE_OOSEQ=y
+CONFIG_TCP_OVERSIZE_MSS=y
+# CONFIG_TCP_OVERSIZE_QUARTER_MSS is not set
+# CONFIG_TCP_OVERSIZE_DISABLE is not set
+CONFIG_UDP_RECVMBOX_SIZE=6
+CONFIG_TCPIP_TASK_STACK_SIZE=3072
+CONFIG_TCPIP_TASK_AFFINITY_NO_AFFINITY=y
+# CONFIG_TCPIP_TASK_AFFINITY_CPU0 is not set
+# CONFIG_TCPIP_TASK_AFFINITY_CPU1 is not set
+CONFIG_TCPIP_TASK_AFFINITY=0x7FFFFFFF
+# CONFIG_PPP_SUPPORT is not set
+CONFIG_NEWLIB_STDOUT_LINE_ENDING_CRLF=y
+# CONFIG_NEWLIB_STDOUT_LINE_ENDING_LF is not set
+# CONFIG_NEWLIB_STDOUT_LINE_ENDING_CR is not set
+# CONFIG_NEWLIB_STDIN_LINE_ENDING_CRLF is not set
+# CONFIG_NEWLIB_STDIN_LINE_ENDING_LF is not set
+CONFIG_NEWLIB_STDIN_LINE_ENDING_CR=y
+CONFIG_NEWLIB_NANO_FORMAT=y
+CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC_HRT=y
+CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_SYSTIMER=y
+CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC_FRC1=y
+# CONFIG_NEWLIB_TIME_SYSCALL_USE_RTC is not set
+# CONFIG_ESP32S3_TIME_SYSCALL_USE_RTC is not set
+# CONFIG_NEWLIB_TIME_SYSCALL_USE_HRT is not set
+# CONFIG_ESP32S3_TIME_SYSCALL_USE_SYSTIMER is not set
+# CONFIG_ESP32S3_TIME_SYSCALL_USE_FRC1 is not set
+# CONFIG_NEWLIB_TIME_SYSCALL_USE_NONE is not set
+# CONFIG_ESP32S3_TIME_SYSCALL_USE_NONE is not set
+CONFIG_ESP32_PTHREAD_TASK_PRIO_DEFAULT=5
+CONFIG_ESP32_PTHREAD_TASK_STACK_SIZE_DEFAULT=3072
+CONFIG_ESP32_PTHREAD_STACK_MIN=768
+CONFIG_ESP32_DEFAULT_PTHREAD_CORE_NO_AFFINITY=y
+# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_0 is not set
+# CONFIG_ESP32_DEFAULT_PTHREAD_CORE_1 is not set
+CONFIG_ESP32_PTHREAD_TASK_CORE_DEFAULT=-1
+CONFIG_ESP32_PTHREAD_TASK_NAME_DEFAULT="pthread"
+CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ABORTS=y
+# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_FAILS is not set
+# CONFIG_SPI_FLASH_WRITING_DANGEROUS_REGIONS_ALLOWED is not set
+CONFIG_SUPPRESS_SELECT_DEBUG_OUTPUT=y
+CONFIG_SUPPORT_TERMIOS=y
+CONFIG_SEMIHOSTFS_MAX_MOUNT_POINTS=1
+# End of deprecated options
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/README.md
new file mode 100644
index 0000000..81dc450
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/README.md
@@ -0,0 +1,12 @@
+# Waveshare ESP32-S3-Touch-LCD-4B
+
+
+[ESP32-S3-Touch-LCD-4B](https://www.waveshare.com/esp32-s3-touch-lcd-4b.htm) is waveshare electronics designed an intelligent 86 box based on ESP32-S3 module equipped with a 480*480 IPS capacitive touch screen
+
+
+## Configuration
+
+Configuration in `menuconfig`.
+
+Selection Board Type `Xiaozhi Assistant --> Board Type`
+- Waveshare ESP32-S3-Touch-LCD-4B
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.h
new file mode 100644
index 0000000..aba2eac
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.h
@@ -0,0 +1,65 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_7
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_16
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_15
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_6
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_47
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_48
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR
+
+#define I2C_ADDRESS ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+#define BSP_LCD_VSYNC (GPIO_NUM_3)
+#define BSP_LCD_HSYNC (GPIO_NUM_46)
+#define BSP_LCD_DE (GPIO_NUM_17)
+#define BSP_LCD_PCLK (GPIO_NUM_9)
+#define BSP_LCD_DISP (GPIO_NUM_NC)
+#define BSP_LCD_DATA0 (GPIO_NUM_40)
+#define BSP_LCD_DATA1 (GPIO_NUM_41)
+#define BSP_LCD_DATA2 (GPIO_NUM_42)
+#define BSP_LCD_DATA3 (GPIO_NUM_2)
+#define BSP_LCD_DATA4 (GPIO_NUM_1)
+#define BSP_LCD_DATA5 (GPIO_NUM_21)
+#define BSP_LCD_DATA6 (GPIO_NUM_8)
+#define BSP_LCD_DATA7 (GPIO_NUM_18)
+#define BSP_LCD_DATA8 (GPIO_NUM_45)
+#define BSP_LCD_DATA9 (GPIO_NUM_38)
+#define BSP_LCD_DATA10 (GPIO_NUM_39)
+#define BSP_LCD_DATA11 (GPIO_NUM_10)
+#define BSP_LCD_DATA12 (GPIO_NUM_11)
+#define BSP_LCD_DATA13 (GPIO_NUM_12)
+#define BSP_LCD_DATA14 (GPIO_NUM_13)
+#define BSP_LCD_DATA15 (GPIO_NUM_14)
+
+#define BSP_LCD_IO_SPI_CS (IO_EXPANDER_PIN_NUM_0)
+#define BSP_LCD_IO_SPI_SCL (IO_EXPANDER_PIN_NUM_2)
+#define BSP_LCD_IO_SPI_SDA (IO_EXPANDER_PIN_NUM_1)
+
+#define DISPLAY_WIDTH 480
+#define DISPLAY_HEIGHT 480
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_4
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.json
new file mode 100644
index 0000000..c203443
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/config.json
@@ -0,0 +1,13 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "esp32-s3-touch-lcd-4b",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc
new file mode 100644
index 0000000..3f5531f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-s3-touch-lcd-4b/esp32-s3-touch-lcd-4b.cc
@@ -0,0 +1,440 @@
+#include "wifi_board.h"
+#include "display/lcd_display.h"
+#include "esp_lcd_st7701.h"
+
+#include "codecs/box_audio_codec.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "axp2101.h"
+#include "i2c_device.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_master.h>
+#include "esp_io_expander_tca9554.h"
+#include "settings.h"
+
+#include <esp_lcd_touch_gt911.h>
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_io_additions.h>
+#include <esp_ota_ops.h>
+
+#define TAG "WaveshareEsp32s3TouchLCD4b"
+
+class Pmic : public Axp2101 {
+public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ // Enable ALDO1(MIC)
+ WriteReg(0x90, 0x01);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+};
+
+#define LCD_OPCODE_WRITE_CMD (0x02ULL)
+#define LCD_OPCODE_READ_CMD (0x03ULL)
+#define LCD_OPCODE_WRITE_COLOR (0x32ULL)
+
+static const st7701_lcd_init_cmd_t lcd_init_cmds[] = {
+// {cmd, { data }, data_size, delay_ms}
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x10}, 5, 0},
+ {0xC0, (uint8_t[]){0x3B, 0x00}, 2, 0},
+ {0xC1, (uint8_t[]){0x0D, 0x02}, 2, 0},
+ {0xC2, (uint8_t[]){0x21, 0x08}, 2, 0},
+ {0xCD, (uint8_t[]){0x08}, 1, 0},
+ {0xB0, (uint8_t[]){0x00, 0x11, 0x18, 0x0E, 0x11, 0x06, 0x07, 0x08, 0x07, 0x22, 0x04, 0x12, 0x0F, 0xAA, 0x31, 0x18}, 16, 0},
+ {0xB1, (uint8_t[]){0x00, 0x11, 0x19, 0x0E, 0x12, 0x07, 0x08, 0x08, 0x08, 0x22, 0x04, 0x11, 0x11, 0xA9, 0x32, 0x18}, 16, 0},
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x11}, 5, 0},
+ {0xB0, (uint8_t[]){0x60}, 1, 0},
+ {0xB1, (uint8_t[]){0x30}, 1, 0},
+ {0xB2, (uint8_t[]){0x87}, 1, 0},
+ {0xB3, (uint8_t[]){0x80}, 1, 0},
+ {0xB5, (uint8_t[]){0x49}, 1, 0},
+ {0xB7, (uint8_t[]){0x85}, 1, 0},
+ {0xB8, (uint8_t[]){0x21}, 1, 0},
+ {0xC1, (uint8_t[]){0x78}, 1, 0},
+ {0xC2, (uint8_t[]){0x78}, 1, 20},
+ {0xE0, (uint8_t[]){0x00, 0x1B, 0x02}, 3, 0},
+ {0xE1, (uint8_t[]){0x08, 0xA0, 0x00, 0x00, 0x07, 0xA0, 0x00, 0x00, 0x00, 0x44, 0x44}, 11, 0},
+ {0xE2, (uint8_t[]){0x11, 0x11, 0x44, 0x44, 0xED, 0xA0, 0x00, 0x00, 0xEC, 0xA0, 0x00, 0x00}, 12, 0},
+ {0xE3, (uint8_t[]){0x00, 0x00, 0x11, 0x11}, 4, 0},
+ {0xE4, (uint8_t[]){0x44, 0x44}, 2, 0},
+ {0xE5, (uint8_t[]){0x0A, 0xE9, 0xD8, 0xA0, 0x0C, 0xEB, 0xD8, 0xA0, 0x0E, 0xED, 0xD8, 0xA0, 0x10, 0xEF, 0xD8, 0xA0}, 16, 0},
+ {0xE6, (uint8_t[]){0x00, 0x00, 0x11, 0x11}, 4, 0},
+ {0xE7, (uint8_t[]){0x44, 0x44}, 2, 0},
+ {0xE8, (uint8_t[]){0x09, 0xE8, 0xD8, 0xA0, 0x0B, 0xEA, 0xD8, 0xA0, 0x0D, 0xEC, 0xD8, 0xA0, 0x0F, 0xEE, 0xD8, 0xA0}, 16, 0},
+ {0xEB, (uint8_t[]){0x02, 0x00, 0xE4, 0xE4, 0x88, 0x00, 0x40}, 7, 0},
+ {0xEC, (uint8_t[]){0x3C, 0x00}, 2, 0},
+ {0xED, (uint8_t[]){0xAB, 0x89, 0x76, 0x54, 0x02, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x20, 0x45, 0x67, 0x98, 0xBA}, 16, 0},
+ {0xFF, (uint8_t[]){0x77, 0x01, 0x00, 0x00, 0x00}, 5, 0},
+ {0x36, (uint8_t[]){0x00}, 1, 0},
+ {0x3A, (uint8_t[]){0x66}, 1, 0},
+ {0x21, (uint8_t[]){0x00}, 0, 120},
+ {0x29, (uint8_t[]){0x00}, 0, 0},
+};
+
+class WaveshareEsp32s3TouchLCD4b : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Pmic* pmic_ = nullptr;
+ Button boot_button_;
+ LcdDisplay* display_;
+ esp_io_expander_handle_t io_expander = NULL;
+ PowerSaveTimer* power_save_timer_;
+
+ uint32_t key_press_start;
+ bool key_pressed;
+ bool key_handled;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(70); });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness(); });
+ power_save_timer_->OnShutdownRequest([this](){
+ pmic_->PowerOff(); });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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,
+ .trans_queue_depth = 0,
+ .flags = {
+ .enable_internal_pullup = 1,
+ },
+ };
+ ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void) {
+ esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander);
+ esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_3|IO_EXPANDER_PIN_NUM_5 | IO_EXPANDER_PIN_NUM_6 , IO_EXPANDER_OUTPUT);
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_3, 1);
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_6, 0);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, 0);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_5, 1);
+ vTaskDelay(pdMS_TO_TICKS(200));
+ esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_4|IO_EXPANDER_PIN_NUM_6, IO_EXPANDER_INPUT);
+ }
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeRGB() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+
+ spi_line_config_t line_config = {
+ .cs_io_type = IO_TYPE_EXPANDER,
+ .cs_expander_pin = BSP_LCD_IO_SPI_CS,
+ .scl_io_type = IO_TYPE_EXPANDER,
+ .scl_expander_pin = BSP_LCD_IO_SPI_SCL,
+ .sda_io_type = IO_TYPE_EXPANDER,
+ .sda_expander_pin = BSP_LCD_IO_SPI_SDA,
+ .io_expander = io_expander,
+ };
+ esp_lcd_panel_io_3wire_spi_config_t io_config = ST7701_PANEL_IO_3WIRE_SPI_CONFIG(line_config, 0);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_3wire_spi(&io_config, &panel_io));
+ esp_lcd_panel_handle_t panel_handle = NULL;
+ esp_lcd_rgb_panel_config_t rgb_config = {
+ .clk_src = LCD_CLK_SRC_DEFAULT,
+ .timings = {
+ .pclk_hz = 16 * 1000 * 1000,
+ .h_res = DISPLAY_WIDTH,
+ .v_res = DISPLAY_HEIGHT,
+ .hsync_pulse_width = 10,
+ .hsync_back_porch = 10,
+ .hsync_front_porch = 20,
+ .vsync_pulse_width = 10,
+ .vsync_back_porch = 10,
+ .vsync_front_porch = 10,
+ .flags = {
+ .pclk_active_neg = false
+ }
+ },
+ .data_width = 16,
+ .bits_per_pixel = 16,
+ .num_fbs = 2,
+ .bounce_buffer_size_px = 480 * 20,
+ .psram_trans_align = 64,
+ .hsync_gpio_num = BSP_LCD_HSYNC,
+ .vsync_gpio_num = BSP_LCD_VSYNC,
+ .de_gpio_num = BSP_LCD_DE,
+ .pclk_gpio_num = BSP_LCD_PCLK,
+ .disp_gpio_num = BSP_LCD_DISP,
+ .data_gpio_nums = {
+ BSP_LCD_DATA0, BSP_LCD_DATA1, BSP_LCD_DATA2, BSP_LCD_DATA3,
+ BSP_LCD_DATA4, BSP_LCD_DATA5, BSP_LCD_DATA6, BSP_LCD_DATA7,
+ BSP_LCD_DATA8, BSP_LCD_DATA9, BSP_LCD_DATA10, BSP_LCD_DATA11,
+ BSP_LCD_DATA12, BSP_LCD_DATA13, BSP_LCD_DATA14, BSP_LCD_DATA15
+ },
+ .flags = {
+ .fb_in_psram = 1,
+ },
+ };
+
+ rgb_config.timings.h_res = DISPLAY_WIDTH;
+ rgb_config.timings.v_res = DISPLAY_HEIGHT;
+ st7701_vendor_config_t vendor_config = {
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(lcd_init_cmds[0]),
+ .rgb_config = &rgb_config,
+ .flags = {
+ .mirror_by_cmd = 0,
+ .auto_del_panel_io = 1,
+ }
+ };
+ const esp_lcd_panel_dev_config_t panel_config = {
+ .reset_gpio_num = GPIO_NUM_NC,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 18,
+ .vendor_config = &vendor_config,
+ };
+ ESP_ERROR_CHECK(esp_lcd_new_panel_st7701(panel_io, &panel_config, &panel_handle));
+ esp_lcd_panel_init(panel_handle);
+
+ display_ = new RgbLcdDisplay(panel_io, panel_handle,
+ DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ void InitializeTouch() {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH - 1,
+ .y_max = DISPLAY_HEIGHT - 1,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 0,
+ .mirror_x = 0,
+ .mirror_y = 0,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400* 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+ void CheckKeyState() {
+ if (!io_expander) return;
+
+ uint32_t current_level;
+ esp_err_t ret = esp_io_expander_get_level(io_expander, IO_EXPANDER_PIN_NUM_4, ¤t_level);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to read IO_EXPANDER_PIN_NUM_4 level");
+ return;
+ }
+
+ static uint32_t last_level = 0;
+ static uint64_t press_start_time_ms = 0;
+
+ if (current_level != last_level) {
+ last_level = current_level;
+
+ if (current_level > 0) {
+ press_start_time_ms = esp_timer_get_time() / 1000;
+ ESP_LOGD(TAG, "Button pressed, start time recorded");
+ } else {
+ uint64_t press_duration = (esp_timer_get_time() / 1000) - press_start_time_ms;
+ ESP_LOGI(TAG, "Button released after %llums", press_duration);
+
+ if (press_duration < 1000) {
+ ESP_LOGI(TAG, "Short press detected, switching to factory partition");
+
+ const esp_partition_t* factory_partition = esp_partition_find_first(
+ ESP_PARTITION_TYPE_APP,
+ ESP_PARTITION_SUBTYPE_APP_FACTORY,
+ nullptr
+ );
+ if (factory_partition) {
+ ESP_LOGI(TAG, "Found factory partition: %s", factory_partition->label);
+ ESP_ERROR_CHECK(esp_ota_set_boot_partition(factory_partition));
+ esp_restart();
+ } else {
+ ESP_LOGE(TAG, "Factory partition not found");
+ }
+ } else {
+ ESP_LOGI(TAG, "Long press detected (>1000ms), no action");
+ }
+ }
+ }
+ }
+
+ void InitializeKeyMonitor() {
+ key_press_start = 0;
+ key_pressed = false;
+ key_handled = false;
+
+ xTaskCreatePinnedToCore(
+ [](void* arg) {
+ auto* board = static_cast<WaveshareEsp32s3TouchLCD4b*>(arg);
+ while (true) {
+ board->CheckKeyState();
+ vTaskDelay(pdMS_TO_TICKS(20));
+ }
+ },
+ "key_monitor_task",
+ 4096,
+ this,
+ 5,
+ nullptr,
+ 0
+ );
+ }
+
+public:
+ WaveshareEsp32s3TouchLCD4b() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeTca9554();
+ InitializeAxp2101();
+ InitializeRGB();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ InitializeKeyMonitor(); // 鍚姩鎸夐敭鐩戝惉
+ GetBacklight()->SetBrightness(100);
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static BoxAudioCodec audio_codec(
+ i2c_bus_,
+ 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,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int &level, bool &charging, bool &discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging)
+ {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(WaveshareEsp32s3TouchLCD4b);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/README.md b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/README.md
new file mode 100644
index 0000000..6eb85df
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/README.md
@@ -0,0 +1,3 @@
+鏂板 寰洩 寮�鍙戞澘: ESP32-Touch-LCD-3.5
+浜у搧閾炬帴锛�
+https://www.waveshare.net/shop/ESP32-Touch-LCD-3.5.htm
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.h b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.h
new file mode 100644
index 0000000..c800733
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.h
@@ -0,0 +1,55 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+#include <driver/spi_master.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_3
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_2
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_34
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_NC
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_21
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_22
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_NC
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define PWR_BUTTON_GPIO GPIO_NUM_36
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SPI_MODE 0
+#define DISPLAY_CS_PIN GPIO_NUM_5
+#define DISPLAY_MOSI_PIN GPIO_NUM_23
+#define DISPLAY_MISO_PIN GPIO_NUM_19
+#define DISPLAY_CLK_PIN GPIO_NUM_18
+#define DISPLAY_DC_PIN GPIO_NUM_27
+#define DISPLAY_RST_PIN GPIO_NUM_NC
+
+
+
+#define DISPLAY_WIDTH 480
+#define DISPLAY_HEIGHT 320
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_RGB_ORDER LCD_RGB_ELEMENT_ORDER_BGR
+#define DISPLAY_INVERT_COLOR true
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_25
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.json b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.json
new file mode 100644
index 0000000..058d85f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/config.json
@@ -0,0 +1,9 @@
+{
+ "manufacturer": "waveshare",
+ "target": "esp32",
+ "builds": [
+ {
+ "name": "esp32-touch-lcd-3.5"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/esp32-touch-lcd-3.5.cc b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/esp32-touch-lcd-3.5.cc
new file mode 100644
index 0000000..aa87995
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/waveshare/esp32-touch-lcd-3.5/esp32-touch-lcd-3.5.cc
@@ -0,0 +1,341 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "mcp_server.h"
+
+#include <esp_log.h>
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <driver/ledc.h>
+#include <wifi_station.h>
+#include <esp_lcd_panel_vendor.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+#include <esp_timer.h>
+#include "esp_io_expander_tca9554.h"
+
+#include "axp2101.h"
+#include "power_save_timer.h"
+
+#include <esp_lcd_touch_ft5x06.h>
+#include <esp_lvgl_port.h>
+
+#define TAG "waveshare_lcd_3_5"
+
+class Pmic : public Axp2101 {
+ public:
+ Pmic(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : Axp2101(i2c_bus, addr) {
+ WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable
+ WriteReg(0x27, 0x10); // hold 4s to power off
+
+ // Disable All DCs but DC1
+ WriteReg(0x80, 0x01);
+ // Disable All LDOs
+ WriteReg(0x90, 0x00);
+ WriteReg(0x91, 0x00);
+
+ // Set DC1 to 3.3V
+ WriteReg(0x82, (3300 - 1500) / 100);
+
+ // Set ALDO1 to 3.3V
+ WriteReg(0x92, (3300 - 500) / 100);
+
+ WriteReg(0x96, (1500 - 500) / 100);
+ WriteReg(0x97, (2800 - 500) / 100);
+
+ // Enable ALDO1 BLDO1 BLDO2
+ WriteReg(0x90, 0x31);
+
+ WriteReg(0x64, 0x02); // CV charger voltage setting to 4.1V
+
+ WriteReg(0x61, 0x02); // set Main battery precharge current to 50mA
+ WriteReg(0x62, 0x08); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA )
+ WriteReg(0x63, 0x01); // set Main battery term charge current to 25mA
+ }
+ };
+
+typedef struct {
+ int cmd; /*<! The specific LCD command */
+ const void *data; /*<! Buffer that holds the command specific data */
+ size_t data_bytes; /*<! Size of `data` in memory, in bytes */
+ unsigned int delay_ms; /*<! Delay in milliseconds after this command */
+} st7796_lcd_init_cmd_t;
+
+typedef struct {
+ const st7796_lcd_init_cmd_t *init_cmds; /*!< Pointer to initialization commands array. Set to NULL if using default commands.
+ * The array should be declared as `static const` and positioned outside the function.
+ * Please refer to `vendor_specific_init_default` in source file.
+ */
+ uint16_t init_cmds_size; /*<! Number of commands in above array */
+} st7796_vendor_config_t;
+
+st7796_lcd_init_cmd_t st7796_lcd_init_cmds[] = {
+ {0x11, (uint8_t []){ 0x00 }, 0, 120},
+
+ // {0x36, (uint8_t []){ 0x08 }, 1, 0},
+
+ {0x3A, (uint8_t []){ 0x05 }, 1, 0},
+ {0xF0, (uint8_t []){ 0xC3 }, 1, 0},
+ {0xF0, (uint8_t []){ 0x96 }, 1, 0},
+ {0xB4, (uint8_t []){ 0x01 }, 1, 0},
+ {0xB7, (uint8_t []){ 0xC6 }, 1, 0},
+ {0xC0, (uint8_t []){ 0x80, 0x45 }, 2, 0},
+ {0xC1, (uint8_t []){ 0x13 }, 1, 0},
+ {0xC2, (uint8_t []){ 0xA7 }, 1, 0},
+ {0xC5, (uint8_t []){ 0x0A }, 1, 0},
+ {0xE8, (uint8_t []){ 0x40, 0x8A, 0x00, 0x00, 0x29, 0x19, 0xA5, 0x33}, 8, 0},
+ {0xE0, (uint8_t []){ 0xD0, 0x08, 0x0F, 0x06, 0x06, 0x33, 0x30, 0x33, 0x47, 0x17, 0x13, 0x13, 0x2B, 0x31}, 14, 0},
+ {0xE1, (uint8_t []){ 0xD0, 0x0A, 0x11, 0x0B, 0x09, 0x07, 0x2F, 0x33, 0x47, 0x38, 0x15, 0x16, 0x2C, 0x32},14, 0},
+ {0xF0, (uint8_t []){ 0x3C }, 1, 0},
+ {0xF0, (uint8_t []){ 0x69 }, 1, 120},
+ {0x21, (uint8_t []){ 0x00 }, 0, 0},
+ {0x29, (uint8_t []){ 0x00 }, 0, 0},
+};
+
+class WaveshareEsp32TouchLCD3inch5 : public WifiBoard {
+private:
+ Button boot_button_;
+ Button pwr_button_;
+ Pmic* pmic_ = nullptr;
+ i2c_master_bus_handle_t i2c_bus_;
+ esp_io_expander_handle_t io_expander = NULL;
+ LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(20);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ pmic_->PowerOff();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)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, &i2c_bus_));
+ }
+
+ void InitializeTca9554(void)
+ {
+ esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9554_ADDRESS_000, &io_expander);
+ if (ret != ESP_OK)
+ ESP_LOGE(TAG, "TCA9554 create returned error");
+ ret = esp_io_expander_set_dir(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, IO_EXPANDER_OUTPUT);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_1, 0);
+ ESP_ERROR_CHECK(ret);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ret = esp_io_expander_set_level(io_expander, IO_EXPANDER_PIN_NUM_0 | IO_EXPANDER_PIN_NUM_1 | IO_EXPANDER_PIN_NUM_2, 1);
+ ESP_ERROR_CHECK(ret);
+ }
+
+ void InitializeAxp2101() {
+ ESP_LOGI(TAG, "Init AXP2101");
+ pmic_ = new Pmic(i2c_bus_, 0x34);
+ }
+
+ void InitializeSpi() {
+ ESP_LOGI(TAG, "Initialize QSPI bus");
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_MOSI_PIN;
+ buscfg.miso_io_num = DISPLAY_MISO_PIN;
+ buscfg.sclk_io_num = DISPLAY_CLK_PIN;
+ buscfg.quadwp_io_num = GPIO_NUM_NC;
+ buscfg.quadhd_io_num = GPIO_NUM_NC;
+ buscfg.max_transfer_sz = 0;
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_NC,
+ .levels = {
+ .reset = 0,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = 1,
+ .mirror_x = 1,
+ .mirror_y = 1,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_FT5x06_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 8,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_ft5x06(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ void InitializeLcdDisplay() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGI(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS_PIN;
+ io_config.dc_gpio_num = DISPLAY_DC_PIN;
+ io_config.spi_mode = DISPLAY_SPI_MODE;
+ io_config.pclk_hz = 24 * 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));
+
+ st7796_vendor_config_t st7796_vendor_config = {
+ .init_cmds = st7796_lcd_init_cmds,
+ .init_cmds_size = sizeof(st7796_lcd_init_cmds) / sizeof(st7796_lcd_init_cmd_t),
+ };
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖
+ ESP_LOGI(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RST_PIN;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &st7796_vendor_config;
+
+ 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, DISPLAY_INVERT_COLOR);
+ 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 InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+#if CONFIG_USE_DEVICE_AEC
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ }
+ });
+#endif
+ }
+
+ // 鍒濆鍖栧伐鍏�
+ void InitializeTools() {
+ auto &mcp_server = McpServer::GetInstance();
+ mcp_server.AddTool("self.system.reconfigure_wifi",
+ "End this conversation and enter WiFi configuration mode.\n"
+ "**CAUTION** You must ask the user to confirm this action.",
+ PropertyList(), [this](const PropertyList& properties) {
+ EnterWifiConfigMode();
+ return true;
+ });
+ }
+
+public:
+ WaveshareEsp32TouchLCD3inch5() :
+ boot_button_(BOOT_BUTTON_GPIO),pwr_button_(PWR_BUTTON_GPIO, true) {
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeTca9554();
+ InitializeAxp2101();
+ InitializeSpi();
+ InitializeLcdDisplay();
+ InitializeTouch();
+ InitializeButtons();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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;
+ }
+ virtual bool GetBatteryLevel(int &level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = pmic_->IsCharging();
+ discharging = pmic_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+
+ level = pmic_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+
+};
+
+DECLARE_BOARD(WaveshareEsp32TouchLCD3inch5);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/README.md b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/README.md
new file mode 100644
index 0000000..381aa92
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/README.md
@@ -0,0 +1,45 @@
+## Wireless-Tag WTP4C5MP07S
+
+[Wireless-Tag WTP4C5MP07S](https://shop.wireless-tag.com/products/7inch-lcd-touch-screen-1024x600-mipi-smart-displays-wtp4c5mp07s-esp32-lcd-board-used-with-esp32-p4-and-esp32-c5-dev-board) product is a combo of
+* [Wireless-Tag WT99P4C5-S1](https://en.wireless-tag.com/product-item-66.html) ESP32-P4 development board and
+* 7 inch 1024x600 ZX7D00C1060M002A MIPI DSI LCD display
+
+<br>
+
+## Configuration
+
+### ESP32P4 Configuration
+
+* Set the compilation target to ESP32P4
+
+ idf.py set-target esp32p4
+
+* Open menuconfig
+
+ idf.py menuconfig
+
+* Select the board
+
+ Xiaozhi Assistant -> Board Type -> Wireless-Tag WTP4C5MP07S
+
+* Select PSRAM
+
+ Component config -> ESP PSRAM -> PSRAM config -> Try to allocate memories of WiFi and LWIP in SPIRAM firstly -> No
+
+* Select Wi-Fi slave target
+
+ Component config -> Wi-Fi Remote -> choose slave target -> esp32c5
+
+* Select Wi-Fi buffers
+
+ Component config -> Wi-Fi Remote -> Wi-Fi configuration -> Max number of WiFi static RX buffers -> 10
+ Component config -> Wi-Fi Remote -> Wi-Fi configuration -> Max number of WiFi dynamic RX buffers -> 24
+ Component config -> Wi-Fi Remote -> Wi-Fi configuration -> Max number of WiFi static TX buffers -> 10
+
+* Build
+
+ idf.py build
+
+### ESP32C5 Configuration
+
+* Flash the slave example from the esp-hosted-mcu library for the target chip ESP32C5. The esp-hosted-mcu version must match the one used in the xiaozhi-esp32 library.
diff --git a/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.h b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.h
new file mode 100644
index 0000000..b3ae81e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.h
@@ -0,0 +1,104 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_10
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_9
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_53
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_7
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_8
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_35
+
+#define DISPLAY_WIDTH 1024
+#define DISPLAY_HEIGHT 600
+
+#define LCD_BIT_PER_PIXEL (16)
+#define PIN_NUM_LCD_RST GPIO_NUM_23
+
+#define DELAY_TIME_MS (3000)
+#define LCD_MIPI_DSI_LANE_NUM (2) // 2 data lanes
+
+#define MIPI_DSI_PHY_PWR_LDO_CHAN (3)
+#define MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV (2500)
+
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+// SD Card configuration (disabled by default)
+// Enable one of the following by setting to 1 and set pins accordingly.
+// Note: SDMMC may conflict with ESP-Hosted SDIO. If using ESP-Hosted via SDIO,
+// prefer SDSPI mode for SD card or disable hosted SDIO.
+
+// SDMMC 1-bit/4-bit mode
+#ifndef SDCARD_SDMMC_ENABLED
+#define SDCARD_SDMMC_ENABLED 0
+#endif
+// SDMMC bus width: set to 1 or 4
+#ifndef SDCARD_SDMMC_BUS_WIDTH
+// Use 4-bit bus width when enabling SDMMC
+#define SDCARD_SDMMC_BUS_WIDTH 4
+#endif
+// SDMMC pin assignments (set to actual pins when enabling SDMMC)
+#ifndef SDCARD_SDMMC_CLK_PIN
+#define SDCARD_SDMMC_CLK_PIN GPIO_NUM_43 // BSP_SD_CLK
+#endif
+#ifndef SDCARD_SDMMC_CMD_PIN
+#define SDCARD_SDMMC_CMD_PIN GPIO_NUM_44 // BSP_SD_CMD
+#endif
+#ifndef SDCARD_SDMMC_D0_PIN
+#define SDCARD_SDMMC_D0_PIN GPIO_NUM_39 // BSP_SD_D0
+#endif
+#ifndef SDCARD_SDMMC_D1_PIN
+#define SDCARD_SDMMC_D1_PIN GPIO_NUM_40 // BSP_SD_D1
+#endif
+#ifndef SDCARD_SDMMC_D2_PIN
+#define SDCARD_SDMMC_D2_PIN GPIO_NUM_41 // BSP_SD_D2
+#endif
+#ifndef SDCARD_SDMMC_D3_PIN
+#define SDCARD_SDMMC_D3_PIN GPIO_NUM_42 // BSP_SD_D3
+#endif
+
+// SDSPI mode (uses SPI bus)
+#ifndef SDCARD_SDSPI_ENABLED
+#define SDCARD_SDSPI_ENABLED 1
+#endif
+#ifndef SDCARD_SPI_HOST
+#define SDCARD_SPI_HOST SPI3_HOST
+#endif
+#ifndef SDCARD_SPI_MOSI
+#define SDCARD_SPI_MOSI GPIO_NUM_44 // BSP_SD_SPI_MOSI
+#endif
+#ifndef SDCARD_SPI_MISO
+#define SDCARD_SPI_MISO GPIO_NUM_39 // BSP_SD_SPI_MISO
+#endif
+#ifndef SDCARD_SPI_SCLK
+#define SDCARD_SPI_SCLK GPIO_NUM_43 // BSP_SD_SPI_CLK
+#endif
+#ifndef SDCARD_SPI_CS
+#define SDCARD_SPI_CS GPIO_NUM_42 // BSP_SD_SPI_CS
+#endif
+
+#ifndef SDCARD_MOUNT_POINT
+#define SDCARD_MOUNT_POINT "/sdcard"
+#endif
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.json b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.json
new file mode 100644
index 0000000..272ecfd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/config.json
@@ -0,0 +1,16 @@
+{
+ "target": "esp32p4",
+ "builds": [
+ {
+ "name": "wireless-tag-wtp4c5mp07s",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=n",
+ "CONFIG_SLAVE_IDF_TARGET_ESP32C5=y",
+ "CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=n",
+ "CONFIG_WIFI_RMT_STATIC_RX_BUFFER_NUM=10",
+ "CONFIG_WIFI_RMT_DYNAMIC_RX_BUFFER_NUM=24",
+ "CONFIG_WIFI_RMT_STATIC_TX_BUFFER_NUM=10"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc
new file mode 100644
index 0000000..88fe91e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/wireless-tag-wtp4c5mp07s/wireless-tag-wtp4c5mp07s.cc
@@ -0,0 +1,306 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "application.h"
+#include "display/lcd_display.h"
+// #include "display/no_display.h"
+#include "button.h"
+#include "config.h"
+
+#include "esp_lcd_panel_ops.h"
+#include "esp_lcd_mipi_dsi.h"
+#include "esp_ldo_regulator.h"
+
+#include "esp_lcd_ek79007.h"
+
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lvgl_port.h>
+#include "esp_lcd_touch_gt911.h"
+
+#include <esp_vfs_fat.h>
+#include <sdmmc_cmd.h>
+#include <driver/sdmmc_host.h>
+#include <driver/sdspi_host.h>
+#include "sd_pwr_ctrl_by_on_chip_ldo.h"
+
+#define TAG "WirelessTagEsp32p47b"
+
+class WirelessTagEsp32p47b : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ LcdDisplay *display_;
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = I2C_NUM_1,
+ .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, &i2c_bus_));
+ }
+
+ static esp_err_t bsp_enable_dsi_phy_power(void) {
+#if MIPI_DSI_PHY_PWR_LDO_CHAN > 0
+ // Turn on the power for MIPI DSI PHY, so it can go from "No Power" state to "Shutdown" state
+ static esp_ldo_channel_handle_t phy_pwr_chan = NULL;
+ esp_ldo_channel_config_t ldo_cfg = {
+ .chan_id = MIPI_DSI_PHY_PWR_LDO_CHAN,
+ .voltage_mv = MIPI_DSI_PHY_PWR_LDO_VOLTAGE_MV,
+ };
+ esp_ldo_acquire_channel(&ldo_cfg, &phy_pwr_chan);
+ ESP_LOGI(TAG, "MIPI DSI PHY Powered on");
+#endif // BSP_MIPI_DSI_PHY_PWR_LDO_CHAN > 0
+
+ return ESP_OK;
+ }
+
+ void InitializeLCD() {
+ bsp_enable_dsi_phy_power();
+ esp_lcd_panel_io_handle_t io = NULL;
+ esp_lcd_panel_handle_t disp_panel = NULL;
+
+ esp_lcd_dsi_bus_handle_t mipi_dsi_bus = NULL;
+ esp_lcd_dsi_bus_config_t bus_config = {
+ .bus_id = 0,
+ .num_data_lanes = 2,
+ .lane_bit_rate_mbps = 900,
+ };
+ esp_lcd_new_dsi_bus(&bus_config, &mipi_dsi_bus);
+
+ ESP_LOGI(TAG, "Install MIPI DSI LCD control panel");
+ // we use DBI interface to send LCD commands and parameters
+ esp_lcd_dbi_io_config_t dbi_config = EK79007_PANEL_IO_DBI_CONFIG();
+ esp_lcd_new_panel_io_dbi(mipi_dsi_bus, &dbi_config, &io);
+
+ esp_lcd_dpi_panel_config_t dpi_config = {
+ .dpi_clk_src = MIPI_DSI_DPI_CLK_SRC_DEFAULT,
+ .dpi_clock_freq_mhz = 52,
+ .pixel_format = LCD_COLOR_PIXEL_FORMAT_RGB565,
+ .num_fbs = 1,
+ .video_timing = {
+ .h_size = 1024,
+ .v_size = 600,
+ .hsync_pulse_width = 10,
+ .hsync_back_porch = 160,
+ .hsync_front_porch = 160,
+ .vsync_pulse_width = 1,
+ .vsync_back_porch = 23,
+ .vsync_front_porch = 12,
+ },
+ .flags = {
+ .use_dma2d = true,
+ },
+ };
+ ek79007_vendor_config_t vendor_config = {
+ .mipi_config = {
+ .dsi_bus = mipi_dsi_bus,
+ .dpi_config = &dpi_config,
+ },
+ };
+
+ const esp_lcd_panel_dev_config_t lcd_dev_config = {
+ .reset_gpio_num = PIN_NUM_LCD_RST,
+ .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB,
+ .bits_per_pixel = 16,
+ .flags = {
+ .reset_active_high = true,
+ },
+ .vendor_config = &vendor_config,
+ };
+ esp_lcd_new_panel_ek79007(io, &lcd_dev_config, &disp_panel);
+ esp_lcd_panel_reset(disp_panel);
+ esp_lcd_panel_init(disp_panel);
+
+ display_ = new MipiLcdDisplay(io, disp_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT,
+ DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+#if 0
+ lv_display_t *disp = lv_display_get_default();
+ if (disp) {
+ lv_disp_set_rotation(disp, LV_DISPLAY_ROTATION_180);
+ ESP_LOGI(TAG, "Display rotated 180 degrees");
+ } else {
+ ESP_LOGE(TAG, "Failed to get default display for rotation");
+ }
+#endif
+ }
+
+ void InitializeTouch()
+ {
+ esp_lcd_touch_handle_t tp;
+ esp_lcd_touch_config_t tp_cfg = {
+ .x_max = DISPLAY_WIDTH,
+ .y_max = DISPLAY_HEIGHT,
+ .rst_gpio_num = GPIO_NUM_NC,
+ .int_gpio_num = GPIO_NUM_21,
+ .levels = {
+ .reset = 1,
+ .interrupt = 0,
+ },
+ .flags = {
+ .swap_xy = DISPLAY_SWAP_XY,
+ .mirror_x = DISPLAY_MIRROR_X,
+ .mirror_y = DISPLAY_MIRROR_Y,
+ },
+ };
+ esp_lcd_panel_io_handle_t tp_io_handle = NULL;
+ esp_lcd_panel_io_i2c_config_t tp_io_config = {
+ .dev_addr = ESP_LCD_TOUCH_IO_I2C_GT911_ADDRESS,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 0,
+ .lcd_cmd_bits = 16,
+ .flags =
+ {
+ .disable_control_phase = 1,
+ }
+ };
+ tp_io_config.scl_speed_hz = 400 * 1000;
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c(i2c_bus_, &tp_io_config, &tp_io_handle));
+ ESP_LOGI(TAG, "Initialize touch controller");
+ ESP_ERROR_CHECK(esp_lcd_touch_new_i2c_gt911(tp_io_handle, &tp_cfg, &tp));
+ const lvgl_port_touch_cfg_t touch_cfg = {
+ .disp = lv_display_get_default(),
+ .handle = tp,
+ };
+ lvgl_port_add_touch(&touch_cfg);
+ ESP_LOGI(TAG, "Touch panel initialized successfully");
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeSdCard() {
+#if SDCARD_SDMMC_ENABLED
+ sd_pwr_ctrl_handle_t sd_ldo = NULL;
+ sd_pwr_ctrl_ldo_config_t ldo_cfg = { .ldo_chan_id = 4 };
+ if (sd_pwr_ctrl_new_on_chip_ldo(&ldo_cfg, &sd_ldo) == ESP_OK) {
+ ESP_LOGI(TAG, "SD LDO channel 4 enabled");
+ } else {
+ ESP_LOGW(TAG, "Failed to enable SD LDO channel 4");
+ }
+ sdmmc_host_t host = SDMMC_HOST_DEFAULT();
+ sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
+ // Map pins via GPIO matrix if needed
+ slot_config.clk = SDCARD_SDMMC_CLK_PIN;
+ slot_config.cmd = SDCARD_SDMMC_CMD_PIN;
+ slot_config.d0 = SDCARD_SDMMC_D0_PIN;
+ slot_config.width = SDCARD_SDMMC_BUS_WIDTH;
+ if (SDCARD_SDMMC_BUS_WIDTH == 4) {
+ slot_config.d1 = SDCARD_SDMMC_D1_PIN;
+ slot_config.d2 = SDCARD_SDMMC_D2_PIN;
+ slot_config.d3 = SDCARD_SDMMC_D3_PIN;
+ }
+
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = false,
+ .max_files = 5,
+ .allocation_unit_size = 0,
+ .disk_status_check_enable = true,
+ };
+ sdmmc_card_t* card;
+ host.pwr_ctrl_handle = sd_ldo;
+ esp_err_t ret = esp_vfs_fat_sdmmc_mount(SDCARD_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
+ if (ret == ESP_OK) {
+ sdmmc_card_print_info(stdout, card);
+ ESP_LOGI(TAG, "SD card mounted at %s (SDMMC)", SDCARD_MOUNT_POINT);
+ } else {
+ ESP_LOGW(TAG, "Failed to mount SD card (SDMMC): %s", esp_err_to_name(ret));
+ }
+#elif SDCARD_SDSPI_ENABLED
+ sd_pwr_ctrl_handle_t sd_ldo = NULL;
+ sd_pwr_ctrl_ldo_config_t ldo_cfg = { .ldo_chan_id = 4 };
+ if (sd_pwr_ctrl_new_on_chip_ldo(&ldo_cfg, &sd_ldo) == ESP_OK) {
+ ESP_LOGI(TAG, "SD LDO channel 4 enabled");
+ } else {
+ ESP_LOGW(TAG, "Failed to enable SD LDO channel 4");
+ }
+ sdmmc_host_t host = SDSPI_HOST_DEFAULT();
+ spi_bus_config_t bus_cfg = {
+ .mosi_io_num = SDCARD_SPI_MOSI,
+ .miso_io_num = SDCARD_SPI_MISO,
+ .sclk_io_num = SDCARD_SPI_SCLK,
+ .quadwp_io_num = -1,
+ .quadhd_io_num = -1,
+ .max_transfer_sz = 4000,
+ };
+ ESP_ERROR_CHECK_WITHOUT_ABORT(spi_bus_initialize((spi_host_device_t)SDCARD_SPI_HOST, &bus_cfg, SPI_DMA_CH_AUTO));
+ sdspi_device_config_t slot_config = SDSPI_DEVICE_CONFIG_DEFAULT();
+ slot_config.gpio_cs = SDCARD_SPI_CS;
+ slot_config.host_id = (spi_host_device_t)SDCARD_SPI_HOST;
+
+ esp_vfs_fat_sdmmc_mount_config_t mount_config = {
+ .format_if_mount_failed = false,
+ .max_files = 5,
+ .allocation_unit_size = 0,
+ .disk_status_check_enable = true,
+ };
+ sdmmc_card_t* card;
+ host.pwr_ctrl_handle = sd_ldo;
+ esp_err_t ret = esp_vfs_fat_sdspi_mount(SDCARD_MOUNT_POINT, &host, &slot_config, &mount_config, &card);
+ if (ret == ESP_OK) {
+ sdmmc_card_print_info(stdout, card);
+ ESP_LOGI(TAG, "SD card mounted at %s (SDSPI)", SDCARD_MOUNT_POINT);
+ } else {
+ ESP_LOGW(TAG, "Failed to mount SD card (SDSPI): %s", esp_err_to_name(ret));
+ }
+#else
+ ESP_LOGI(TAG, "SD card disabled (enable SDCARD_SDMMC_ENABLED or SDCARD_SDSPI_ENABLED)");
+#endif
+ }
+
+public:
+ WirelessTagEsp32p47b() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeLCD();
+ InitializeTouch();
+ InitializeSdCard();
+ InitializeButtons();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_codec(
+ i2c_bus_,
+ I2C_NUM_1,
+ 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(WirelessTagEsp32p47b);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/README.md b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/README.md
new file mode 100644
index 0000000..393a294
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/README.md
@@ -0,0 +1,78 @@
+# 鏃犲悕绉戞妧鏄熸櫤 ABS 2.0
+
+## 绠�浠�
+鏃犲悕绉戞妧鏄熸櫤 ABS 2.0 鏄竴娆鹃珮鎬т环姣旂殑 AI 璇煶浜や簰寮�鍙戞澘銆傚畠鎼浇 1.54 瀵� LCD 灞忓箷锛岄厤澶囩嫭绔嬬殑鐗╃悊鎸夐敭锛屽苟閲囩敤 **ML307R 4G 閫氫俊妯$粍**锛岃浣犲湪娌℃湁 Wi-Fi 鐨勭幆澧冧笅涔熻兘闅忔椂闅忓湴涓庡ぇ妯″瀷瀵硅瘽銆�
+
+## 鏍稿績鐗规��
+- 鍙岀綉缁滈�氫俊锛氭敮鎸� Wi-Fi 涓� ML307R Cat.1 4G 鍙屾ā鍒囨崲锛岄�傞厤澶氬満鏅娇鐢�
+- 鏄剧ず绯荤粺锛�1.54 瀵� 240脳240 LCD 灞忓箷锛岃嚜瀹氫箟 UI 甯冨眬浼樺寲锛岄�傞厤 1.54 瀵告柟褰㈠睆鏄剧ず鏁堟灉
+- 鐗╃悊鎸夐敭浜や簰锛氱嫭绔� Boot 鍔熻兘閿�侀煶閲忓姞鍑忛敭锛屾敮鎸佸崟鍑汇�佸弻鍑汇�侀暱鎸夈�佷簲鍑诲闃舵搷浣�
+- 鎵╁睍鑳藉姏锛氬唴缃� Micro SD 鍗℃彃妲斤紝鏀寔鏈湴瀛樺偍鎵╁睍锛涘彲棰勭暀闇囧姩椹揪锛屽疄鐜版寜閿搷浣滆Е瑙夊弽棣�
+- 瀹屽杽鐨勭數婧愮鐞嗭細鏀寔鐢垫睜鐢甸噺 ADC 妫�娴嬨�佸厖鐢电姸鎬佸疄鏃剁洃娴嬨�佽嚜鍔ㄤ紤鐪犱笌娣卞害鐫$湢鑺傝兘鎺у埗
+- 鐢熸�佸吋瀹癸細瀹屽叏閫傞厤灏忔櫤 ESP32 椤圭洰鍥轰欢锛屾敮鎸� Qwen/DeepSeek 绛夊ぇ妯″瀷锛屽吋瀹� MCP 鍗忚璁惧鎺у埗
+- 鐢变簬纭欢宸紓锛孶I鏄剧ず涓婂搴曢儴琛ㄦ儏鍜屾枃鏈殑鏄剧ず浣嶇疆绋嶆湁鏀瑰姩
+
+## 涓庨摑鍚堥噾鐗堟湰锛圶INGZHI_METAL_1_54_WIFI锛夋牳蹇冨樊寮�
+| 鍔熻兘鐗规�� | xingzhi-abs-2.0 | 閾濆悎閲戠増鏈� |
+|----------|------------------|------------|
+| 浜や簰鏂瑰紡 | 鐙珛鐗╃悊鎸夐敭锛圔oot/闊抽噺+/闊抽噺-锛� | CST816 瑙︽懜鑺墖+瑙︽懜浜や簰 |
+| 澶栧3鏉愯川 | ABS 宸ョ▼濉戞枡 | 閾濆悎閲戞潗璐� |
+
+>### 鎸夐敭鎿嶄綔
+>- **寮�鏈�**: 鍏虫満鐘舵�侊紝闀挎寜鐢垫簮閿�3绉掑悗鑷姩寮�鏈�
+>- **鍏虫満**: 寮�鏈虹姸鎬侊紝闀挎寜鐢垫簮閿�5绉掑悗鑷姩鍏虫満
+>- **鍞ら啋/鎵撴柇**: 姝e父閫氳瘽/寰呮満鐘舵�佷笅锛屽崟鍑籅oot鍔熻兘閿紝鍙敜閱掕澶囨垨鎵撴柇姝e湪杩涜鐨勮闊冲璇�
+>- **閲嶆柊閰嶇綉**: 寮�鏈哄悗1绉掗挓鍐咃紝鍗曞嚮Boot鍔熻兘閿紝璁惧浼氳嚜鍔ㄩ噸鍚苟杩涘叆閰嶇綉鐣岄潰
+>- **鍒囨崲缃戠粶妯″紡**: 寮�鏈虹姸鎬佷笅锛屽弻鍑籅oot鍔熻兘閿紝鍙垏鎹i-Fi涓�4G缃戠粶妯″紡
+>- **SD鍗$姸鎬佹娴�**: 寮�鏈虹姸鎬佷笅锛屼簲鍑籅oot鍔熻兘閿紝鍙娴嬪苟鍦ㄥ睆骞曟樉绀篠D鍗℃寕杞界姸鎬�
+>- **澧炲姞闊抽噺**: 寮�鏈虹姸鎬佷笅锛屽崟鍑婚煶閲�+鎸夐敭锛岄煶閲忓鍔�10%锛涢暱鎸夐煶閲�+鎸夐敭2s锛岄煶閲忕洿鎺ラ�掑鑷�100%鏈�澶ч煶閲�
+>- **鍑忓皬闊抽噺**: 寮�鏈虹姸鎬佷笅锛屽崟鍑婚煶閲�-鎸夐敭锛岄煶閲忓噺灏�10%锛涢暱鎸夐煶閲�-鎸夐敭2s锛岄煶閲忕洿鎺ラ�掑噺鑷�0%闈欓煶
+
+>### 浼戠湢鎿嶄綔
+>- **娴呯潯鐪�**: 寮�鏈哄悗锛岀淮鎸佸緟鍛界姸鎬�60s鍚庯紝杩涘叆娴呯潯鐪狅紙灞忓箷浜害璋冩暣鍒�1%锛�
+>- **娣辩潯鐪�**: 寮�鏈哄悗锛岀淮鎸佸緟鍛界姸鎬�300s鍚庯紝鑷姩鍏虫満
+>- **鍞ら啋**: 娴呯潯鐪犵姸鎬佷笅锛屽崟鍑讳换鎰忔寜閿紝鍞ら啋璁惧锛堝睆骞曚寒搴﹀洖璋冿級
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `鏃犲悕绉戞妧鏄熸櫤ABS 2.0`
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.h
new file mode 100644
index 0000000..dd9a1a3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.h
@@ -0,0 +1,70 @@
+
+#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_INPUT_REFERENCE true
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_USE_PCA9557
+#define AUDIO_CODEC_I2C_PA_EN GPIO_NUM_21
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+// 鎸夐敭
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_45
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2
+
+// sd鍗�
+#define SD_DATA0 GPIO_NUM_39
+#define SD_CLK GPIO_NUM_38
+#define SD_CMD GPIO_NUM_40
+#define SD_CS GPIO_NUM_46
+#define SD_MOUNT_POINT "/sdcard"
+#define SD_SPI_HOST SPI2_HOST
+
+// 闇囧姩椹揪
+#define VIBRATING_MOTOR_PIN GPIO_NUM_3
+
+// 灞忓箷
+#define DISPLAY_SPI_HOST SPI3_HOST
+#define DISPLAY_SDA GPIO_NUM_10
+#define DISPLAY_SCL GPIO_NUM_9
+#define DISPLAY_DC GPIO_NUM_8
+#define DISPLAY_CS GPIO_NUM_14
+#define DISPLAY_RES GPIO_NUM_18
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+// 4g
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+// 鐢垫簮绠$悊
+#define POWER_USB_IN GPIO_NUM_1
+#define Power_Control GPIO_NUM_47 // 鐢垫簮鎺у埗寮曡剼
+#define Power_Dec GPIO_NUM_48 // 鐢垫簮閿娴嬪紩鑴�
+#define POWER_CBS_ADC_UNIT ADC_UNIT_1 // adc妫�娴嬪叕鍏眜nit GPIO1
+#define POWER_USBIN_ADC_CHANNEL ADC_CHANNEL_0 // 妫�娴媢sb鏄惁鎻掑叆 GPIO1
+#define POWER_BATTERY_ADC_CHANNEL ADC_CHANNEL_6 // 鐢垫睜鐢甸噺妫�娴� GPIO7
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.json
new file mode 100644
index 0000000..db87ba0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-abs-2.0",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/customlcddisplay.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/customlcddisplay.h
new file mode 100644
index 0000000..b0422dc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/customlcddisplay.h
@@ -0,0 +1,94 @@
+#ifndef _CUSTOMLCDDISPLAY_H_
+#define _CUSTOMLCDDISPLAY_H_
+
+#include "display/lcd_display.h"
+#include "lvgl_theme.h"
+
+LV_FONT_DECLARE(font_puhui_16_4);
+LV_FONT_DECLARE(font_awesome_16_4);
+
+class CustomLcdDisplay : public SpiLcdDisplay {
+private:
+ lv_timer_t* init_check_timer_ = nullptr;
+ bool init_custom_display_ = false;
+
+ bool CreateCustomLcdDisplayImpl() {
+ DisplayLockGuard lock(this);
+ LvglTheme* lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ if (lvgl_theme == nullptr) {
+ ESP_LOGE("CustomLcdDisplay", "lvgl_theme is null");
+ return false;
+ }
+
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+
+ if (emoji_box_ == nullptr) {
+ ESP_LOGE("CustomLcdDisplay", "emoji_box_ is null, skip align");
+ return false;
+ }
+ if (bottom_bar_ == nullptr) {
+ ESP_LOGE("CustomLcdDisplay", "bottom_bar_ is null, skip align");
+ return false;
+ }
+
+ auto screen = lv_screen_active();
+ lv_obj_set_scrollbar_mode(screen, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_align(emoji_box_, LV_ALIGN_CENTER, 0, -5);
+
+ lv_obj_set_size(bottom_bar_, LV_HOR_RES, text_font->line_height + lvgl_theme->spacing(8)+8);
+ lv_obj_align(bottom_bar_, LV_ALIGN_BOTTOM_MID, 0, -5);
+ ESP_LOGI("CustomLcdDisplay", "Custom UI initialized successfully");
+ return true;
+ }
+
+ static void InitCheckTimerCb(lv_timer_t* timer) {
+ CustomLcdDisplay* self = static_cast<CustomLcdDisplay*>(lv_timer_get_user_data(timer));
+ if (self == nullptr) {
+ lv_timer_del(timer);
+ return;
+ }
+
+ if (self->IsSetupUICalled() && !self->init_custom_display_) {
+ if (self->CreateCustomLcdDisplayImpl()) {
+ self->init_custom_display_ = true;
+ lv_timer_del(timer);
+ self->init_check_timer_ = nullptr;
+ ESP_LOGI("CustomLcdDisplay", "Init timer stopped, custom UI ready");
+ }
+ }
+ }
+
+public:
+ CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle,
+ width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ init_check_timer_ = lv_timer_create(InitCheckTimerCb, 50, this);
+ if (init_check_timer_ == nullptr) {
+ ESP_LOGE("CustomLcdDisplay", "Failed to create init check timer");
+ } else {
+ ESP_LOGI("CustomLcdDisplay", "Init check timer created (50ms interval)");
+ }
+ }
+
+ ~CustomLcdDisplay() override {
+ if (init_check_timer_ != nullptr) {
+ lv_timer_del(init_check_timer_);
+ init_check_timer_ = nullptr;
+ }
+ }
+
+ bool IsSetupUICalled() const {
+ return setup_ui_called_;
+ }
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/power_manager.h
new file mode 100644
index 0000000..a57a865
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/power_manager.h
@@ -0,0 +1,305 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include "sdkconfig.h"
+#include "button.h"
+#include "board.h"
+#include "config.h"
+#include "assets/lang_config.h"
+#include <esp_sleep.h>
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ esp_timer_handle_t power_timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 30;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ adc_oneshot_unit_handle_t adc_handle_;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ // power
+ bool pressed = false; // 鏄惁鎸変笅鐢垫簮閿�
+ int PowerControl_ticks_ = 0; // 寮�鏈烘椂闀�
+ int press_ticks_ = 0; // 鎸変笅鏃剁殑tick
+ bool is_first_boot = true; //
+ uint8_t PowerDec_level_ = 0; // 鐢垫簮閿數骞�
+ const int power_off_ticks_ = 20; // 鎸夐敭鎸変笅X/5绉掑噯澶囧叧鏈�;
+ bool new_charging_status = false; // 鎻掑叆usb鏃舵棤娉曡蒋浠跺叧鏈�
+ bool is_shutting_down_ = false; // 鏍囪鏄惁杩涘叆鍏虫満娴佺▼
+ int shutdown_delay_ticks_ = 0; // 鍏虫満寤惰繜璁℃暟
+ uint8_t shutdown_ticks = 5; // 5/5s鍚庡叧鏈� 棰勭暀鐨勫叧鏈烘搷浣�
+ bool shutdown_first_ = true;
+
+ // 杞欢鍏虫満閫昏緫
+ void PowrSwitch() {
+ PowerDec_level_ = gpio_get_level(Power_Dec);
+
+ // 纭繚寮�鏈哄悗鐢垫簮閿澗寮�鍐嶆娴嬪叧鏈�
+ if (PowerDec_level_ == 1) {
+ is_first_boot = false;
+ }
+
+ if (is_shutting_down_) {
+ shutdown_delay_ticks_++;
+ if (shutdown_delay_ticks_ >= shutdown_ticks) {
+ shutdown();
+ is_shutting_down_ = false;
+ shutdown_delay_ticks_ = 0;
+ }
+ return;
+ }
+
+ if (!is_first_boot) {
+ PowerControl_ticks_++;
+ if (PowerDec_level_ == 0 && pressed == false) {
+ press_ticks_ = PowerControl_ticks_;
+ pressed = true;
+ }
+ if ((press_ticks_ != 0) &&( PowerControl_ticks_ - press_ticks_ == power_off_ticks_ )&& (!new_charging_status)) {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ is_shutting_down_ = true;
+ shutdown_delay_ticks_ = 0;
+ }
+ if (PowerDec_level_ == 1 && press_ticks_!= 0) {
+ PowerDec_level_ = gpio_get_level(Power_Dec);
+ if (PowerDec_level_ == 1) {
+ pressed = false;
+ press_ticks_ = 0;
+ }
+ }
+ }
+ }
+
+ void CheckBatteryStatus() {
+ int usb_usb_adc_value0;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, POWER_USBIN_ADC_CHANNEL, &usb_usb_adc_value0));
+ new_charging_status = (1500 < usb_usb_adc_value0 && usb_usb_adc_value0 < 4000);
+ // ESP_LOGE("powercontrol", "USB ADC VALUE 1: %d", usb_usb_adc_value0);
+
+ if (new_charging_status != is_charging_) {
+ ReadBatteryAdcData();
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, POWER_BATTERY_ADC_CHANNEL, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1970, 0},
+ {2062, 20},
+ {2154, 40},
+ {2246, 60},
+ {2338, 80},
+ {2430, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栫數婧愰敭妫�娴嬪紩鑴�
+ gpio_config_t powerdecgpio_conf = {};
+ powerdecgpio_conf.intr_type = GPIO_INTR_DISABLE;
+ powerdecgpio_conf.mode = GPIO_MODE_INPUT;
+ powerdecgpio_conf.pin_bit_mask = (1ULL << Power_Dec);
+ powerdecgpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ powerdecgpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&powerdecgpio_conf);
+
+ // 鍒濆鍖栫數婧愭帶鍒跺紩鑴�
+ gpio_config_t powercontgpio_conf = {};
+ powercontgpio_conf.intr_type = GPIO_INTR_DISABLE;
+ powercontgpio_conf.mode = GPIO_MODE_OUTPUT;
+ powercontgpio_conf.pin_bit_mask = (1ULL << Power_Control);
+ powercontgpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ powercontgpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&powercontgpio_conf);
+ gpio_set_level(Power_Control, 1);
+ ESP_LOGI("powercontrol", "turnded on ...");
+
+ // 鍒涘缓鐢垫簮鎺у埗妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t power_timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->PowrSwitch();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "power_cotrol_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&power_timer_args, &power_timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(power_timer_handle_, 200000));
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = POWER_CBS_ADC_UNIT,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, POWER_BATTERY_ADC_CHANNEL, &chan_config)); // 鐢垫睜鐢甸噺
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, POWER_USBIN_ADC_CHANNEL, &chan_config)); // usb
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (power_timer_handle_) {
+ esp_timer_stop(power_timer_handle_);
+ esp_timer_delete(power_timer_handle_);
+ }
+ if (adc_handle_ != nullptr) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+
+ void shutdown() {
+ if (!new_charging_status && shutdown_first_) {
+ shutdown_first_ = false;
+ gpio_set_level(DISPLAY_BACKLIGHT_PIN, 0);
+ for (int i=1;i<15;i++) {
+ gpio_set_level(Power_Control, 1);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ gpio_set_level(Power_Control, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ESP_LOGI("PowerManager","瑙﹀彂寮�鍏虫満鎺у埗");
+ }
+ ESP_LOGI("PowerManager","鍏虫満澶辫触锛岃繘鍏ユ繁鐫$湢");
+ esp_deep_sleep_start();
+ } else {
+ ESP_LOGI("PowerManager","妫�娴嬪埌鎻掑叆usb锛屾棤娉曞叧鏈�");
+ }
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/xingzhi-abs-2.0.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/xingzhi-abs-2.0.cc
new file mode 100644
index 0000000..4a6c174
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-abs-2.0/xingzhi-abs-2.0.cc
@@ -0,0 +1,389 @@
+#include "dual_network_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "power_manager.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include "esp_private/sdmmc_common.h"
+#include <esp_vfs_fat.h>
+#include <driver/sdspi_host.h>
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "customlcddisplay.h"
+
+#define TAG "XINGZHI_ABS_2_0"
+
+typedef enum {
+ VIBRATE_EVENT_TRIGGER = 0,
+ VIBRATE_EVENT_NONE
+} VibrateEvent_t;
+
+class XINGZHI_ABS_2_0 : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ // SpiLcdDisplay* display_;
+ CustomLcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ esp_err_t err;
+ bool is_sdcard_found = false;
+ QueueHandle_t vibrate_event_queue_;
+ TaskHandle_t vibrate_task_handle_;
+
+ void VibrateMotor(uint32_t duration_ms) {
+ ESP_LOGI(TAG, "Vibrate Motor");
+ gpio_set_level(VIBRATING_MOTOR_PIN, 1);
+ vTaskDelay(pdMS_TO_TICKS(duration_ms));
+ gpio_set_level(VIBRATING_MOTOR_PIN, 0);
+ }
+
+ static void VibrateTask(void* param) {
+ XINGZHI_ABS_2_0* board = static_cast<XINGZHI_ABS_2_0*>(param);
+ VibrateEvent_t event;
+
+ while (1) {
+ if (xQueueReceive(board->vibrate_event_queue_, &event, portMAX_DELAY)) {
+ if (event == VIBRATE_EVENT_TRIGGER) {
+ board->VibrateMotor(50);
+ }
+ }
+ }
+ }
+
+ void TriggerVibrateEvent() {
+ VibrateEvent_t event = VIBRATE_EVENT_TRIGGER;
+ xQueueSend(vibrate_event_queue_, &event, 0);
+ }
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(POWER_USB_IN);//USB鏄惁鎻掑叆
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ ESP_LOGI(TAG, "Enabling sleep mode");
+ display_->SetChatMessage("system", "");
+ display_->SetEmotion("sleepy");
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ display_->SetChatMessage("system", "");
+ display_->SetEmotion("neutral");
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ power_manager_->shutdown();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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, &i2c_bus_));
+ for (uint8_t addr = 1; addr < 127; addr++) {
+ err = i2c_master_probe(i2c_bus_, addr, 100);
+ if (err == ESP_OK) {
+ ESP_LOGI(TAG, "Device found at address 0x%02X", addr);
+ }
+ }
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(DISPLAY_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(DISPLAY_SPI_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+
+ display_ = new CustomLcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
+ DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ // 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 InitializeSDcardSpi() {
+ spi_bus_config_t bus_cnf = {
+ .mosi_io_num = SD_CMD,
+ .miso_io_num = SD_DATA0,
+ .sclk_io_num = SD_CLK,
+ .quadwp_io_num = -1,
+ .quadhd_io_num = -1,
+ .max_transfer_sz = 400000,
+ };
+
+ esp_err_t err = spi_bus_initialize(SD_SPI_HOST, &bus_cnf, SPI_DMA_CH_AUTO);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "SPI鎬荤嚎鍒濆鍖栧け璐�: %s", esp_err_to_name(err));
+ return;
+ }
+
+ static sdspi_device_config_t slot_cnf = {
+ .host_id = SD_SPI_HOST,
+ .gpio_cs = SD_CS,
+ .gpio_cd = SDSPI_SLOT_NO_CD,
+ .gpio_wp = GPIO_NUM_NC,
+ .gpio_int = GPIO_NUM_NC,
+ };
+
+ esp_vfs_fat_sdmmc_mount_config_t mount_cnf = {
+ .format_if_mount_failed = false,
+ .max_files = 5,
+ .allocation_unit_size = 16 * 1024,
+ };
+
+ sdmmc_card_t* card = NULL;
+
+ sdmmc_host_t host = SDSPI_HOST_DEFAULT();
+ err = esp_vfs_fat_sdspi_mount(SD_MOUNT_POINT, &host, &slot_cnf, &mount_cnf, &card);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "SD鍗℃寕杞藉け璐�: %s", esp_err_to_name(err));
+ is_sdcard_found = false;
+ return;
+ } else if (err == ESP_OK) {
+ ESP_LOGI(TAG, "SD鍗℃寕杞芥垚鍔�");
+ is_sdcard_found = true;
+ }
+ // sdmmc_card_print_info(stdout, card); // 鎵撳嵃SD鍗′俊鎭�
+ }
+
+ void InitializePhysicalButtons() {
+ boot_button_.OnClick([this]() {
+ this->TriggerVibrateEvent();
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ this->TriggerVibrateEvent();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ boot_button_.OnMultipleClick(
+ [this]() {
+ ESP_LOGI(TAG, "Button OnFiveClick");
+ this->TriggerVibrateEvent();
+ if (is_sdcard_found) {
+ display_->SetChatMessage("system", "寮�鏈烘娴嬪埌SD鎸傝浇鎴愬姛");
+ } else {
+ display_->SetChatMessage("system", "寮�鏈烘娴嬪埌SD鎸傝浇澶辫触");
+ }
+ }, 5);
+
+ volume_up_button_.OnClick([this]() {
+ this->TriggerVibrateEvent();
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ this->TriggerVibrateEvent();
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ this->TriggerVibrateEvent();
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ this->TriggerVibrateEvent();
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeGpio() {
+ gpio_config_t zhengdong = {};
+ zhengdong.intr_type = GPIO_INTR_DISABLE;
+ zhengdong.mode = GPIO_MODE_OUTPUT;
+ zhengdong.pin_bit_mask = (1ULL << VIBRATING_MOTOR_PIN);
+ zhengdong.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ zhengdong.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&zhengdong);
+ gpio_set_level(VIBRATING_MOTOR_PIN, 0);
+ }
+
+ void InitializeVibrateTask() {
+ vibrate_event_queue_ = xQueueCreate(10, sizeof(VibrateEvent_t));
+ if (vibrate_event_queue_ == nullptr) {
+ ESP_LOGE(TAG, "鍒涘缓鎸姩浜嬩欢闃熷垪澶辫触");
+ return;
+ }
+
+ BaseType_t ret = xTaskCreate(
+ VibrateTask,
+ "vibrate_task",
+ 2048,
+ this,
+ tskIDLE_PRIORITY + 1,
+ &vibrate_task_handle_
+ );
+
+ if (ret != pdPASS) {
+ ESP_LOGE(TAG, "鍒涘缓鎸姩浠诲姟澶辫触");
+ } else {
+ ESP_LOGI(TAG, "鎸姩浠诲姟鍒濆鍖栨垚鍔�");
+ }
+ }
+
+public:
+ XINGZHI_ABS_2_0() :
+ DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO),
+ vibrate_event_queue_(nullptr),
+ vibrate_task_handle_(nullptr) {
+ InitializeGpio();
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializePhysicalButtons();
+ InitializeSDcardSpi();
+ InitializeVibrateTask();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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_I2C_PA_EN,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_ABS_2_0);
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.h
new file mode 100644
index 0000000..7388d39
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.h
@@ -0,0 +1,40 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA GPIO_NUM_10
+#define DISPLAY_SCL GPIO_NUM_9
+#define DISPLAY_DC GPIO_NUM_8
+#define DISPLAY_CS GPIO_NUM_14
+#define DISPLAY_RES GPIO_NUM_18
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.json
new file mode 100644
index 0000000..0305c46
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-cube-0.85tft-ml307",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc
new file mode 100644
index 0000000..0dac3b1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-ml307/xingzhi-cube-0.85tft-ml307.cc
@@ -0,0 +1,239 @@
+#include "ml307_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "../xingzhi-cube-1.54tft-wifi/power_manager.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#include <esp_lcd_nv3023.h>
+#include "settings.h"
+
+#define TAG "XINGZHI_CUBE_0_85TFT_ML307"
+
+static const nv3023_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xff, (uint8_t[]){0xa5}, 1, 0},
+ {0x3E, (uint8_t[]){0x09}, 1, 0},
+ {0x3A, (uint8_t[]){0x65}, 1, 0},
+ {0x82, (uint8_t[]){0x00}, 1, 0},
+ {0x98, (uint8_t[]){0x00}, 1, 0},
+ {0x63, (uint8_t[]){0x0f}, 1, 0},
+ {0x64, (uint8_t[]){0x0f}, 1, 0},
+ {0xB4, (uint8_t[]){0x34}, 1, 0},
+ {0xB5, (uint8_t[]){0x30}, 1, 0},
+ {0x83, (uint8_t[]){0x03}, 1, 0},
+ {0x86, (uint8_t[]){0x04}, 1, 0},
+ {0x87, (uint8_t[]){0x16}, 1, 0},
+ {0x88, (uint8_t[]){0x0A}, 1, 0},
+ {0x89, (uint8_t[]){0x27}, 1, 0},
+ {0x93, (uint8_t[]){0x63}, 1, 0},
+ {0x96, (uint8_t[]){0x81}, 1, 0},
+ {0xC3, (uint8_t[]){0x10}, 1, 0},
+ {0xE6, (uint8_t[]){0x00}, 1, 0},
+ {0x99, (uint8_t[]){0x01}, 1, 0},
+ {0x70, (uint8_t[]){0x09}, 1, 0},
+ {0x71, (uint8_t[]){0x1D}, 1, 0},
+ {0x72, (uint8_t[]){0x14}, 1, 0},
+ {0x73, (uint8_t[]){0x0a}, 1, 0},
+ {0x74, (uint8_t[]){0x11}, 1, 0},
+ {0x75, (uint8_t[]){0x16}, 1, 0},
+ {0x76, (uint8_t[]){0x38}, 1, 0},
+ {0x77, (uint8_t[]){0x0B}, 1, 0},
+ {0x78, (uint8_t[]){0x08}, 1, 0},
+ {0x79, (uint8_t[]){0x3E}, 1, 0},
+ {0x7a, (uint8_t[]){0x07}, 1, 0},
+ {0x7b, (uint8_t[]){0x0D}, 1, 0},
+ {0x7c, (uint8_t[]){0x16}, 1, 0},
+ {0x7d, (uint8_t[]){0x0F}, 1, 0},
+ {0x7e, (uint8_t[]){0x14}, 1, 0},
+ {0x7f, (uint8_t[]){0x05}, 1, 0},
+ {0xa0, (uint8_t[]){0x04}, 1, 0},
+ {0xa1, (uint8_t[]){0x28}, 1, 0},
+ {0xa2, (uint8_t[]){0x0c}, 1, 0},
+ {0xa3, (uint8_t[]){0x11}, 1, 0},
+ {0xa4, (uint8_t[]){0x0b}, 1, 0},
+ {0xa5, (uint8_t[]){0x23}, 1, 0},
+ {0xa6, (uint8_t[]){0x45}, 1, 0},
+ {0xa7, (uint8_t[]){0x07}, 1, 0},
+ {0xa8, (uint8_t[]){0x0a}, 1, 0},
+ {0xa9, (uint8_t[]){0x3b}, 1, 0},
+ {0xaa, (uint8_t[]){0x0d}, 1, 0},
+ {0xab, (uint8_t[]){0x18}, 1, 0},
+ {0xac, (uint8_t[]){0x14}, 1, 0},
+ {0xad, (uint8_t[]){0x0F}, 1, 0},
+ {0xae, (uint8_t[]){0x19}, 1, 0},
+ {0xaf, (uint8_t[]){0x08}, 1, 0},
+ {0xff, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x29, (uint8_t[]){0x00}, 0, 10}
+};
+
+class XINGZHI_CUBE_0_85TFT_ML307 : public Ml307Board {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ SpiLcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_21);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ buscfg.quadwp_io_num = GPIO_NUM_NC;
+ buscfg.quadhd_io_num = GPIO_NUM_NC;
+ buscfg.max_transfer_sz = DISPLAY_HEIGHT * 80 *sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeNv3023Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = NV3023_PANEL_IO_SPI_CONFIG(DISPLAY_CS, DISPLAY_DC, NULL, NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ nv3023_vendor_config_t vendor_config = { // Uncomment these lines if use custom initialization commands
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(nv3023_lcd_init_cmd_t),
+ };
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io_, &panel_config, &panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ 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 Initializegpio21_45() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ //gpio_num_t sp_45 = GPIO_NUM_45;
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << GPIO_NUM_45);
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ gpio_config(&io_conf);
+ gpio_set_level(GPIO_NUM_45, 0);
+ }
+
+public:
+ XINGZHI_CUBE_0_85TFT_ML307(): Ml307Board(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ Initializegpio21_45(); // 鍒濆鏃讹紝鎷夐珮21寮曡剼锛屼繚璇�4g妯″潡姝e父宸ヤ綔
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeNv3023Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ Ml307Board::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_CUBE_0_85TFT_ML307);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.h
new file mode 100644
index 0000000..8b2bf9d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.h
@@ -0,0 +1,37 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC
+
+#define DISPLAY_SDA GPIO_NUM_10
+#define DISPLAY_SCL GPIO_NUM_9
+#define DISPLAY_DC GPIO_NUM_8
+#define DISPLAY_CS GPIO_NUM_14
+#define DISPLAY_RES GPIO_NUM_18
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 128
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.json
new file mode 100644
index 0000000..867160f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-cube-0.85tft-wifi",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc
new file mode 100644
index 0000000..3b2a0b3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.85tft-wifi/xingzhi-cube-0.85tft-wifi.cc
@@ -0,0 +1,244 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "../xingzhi-cube-1.54tft-wifi/power_manager.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#include <esp_lcd_nv3023.h>
+#include "settings.h"
+
+#define TAG "XINGZHI_CUBE_0_85TFT_WIFI"
+
+static const nv3023_lcd_init_cmd_t lcd_init_cmds[] = {
+ {0xff, (uint8_t[]){0xa5}, 1, 0},
+ {0x3E, (uint8_t[]){0x09}, 1, 0},
+ {0x3A, (uint8_t[]){0x65}, 1, 0},
+ {0x82, (uint8_t[]){0x00}, 1, 0},
+ {0x98, (uint8_t[]){0x00}, 1, 0},
+ {0x63, (uint8_t[]){0x0f}, 1, 0},
+ {0x64, (uint8_t[]){0x0f}, 1, 0},
+ {0xB4, (uint8_t[]){0x34}, 1, 0},
+ {0xB5, (uint8_t[]){0x30}, 1, 0},
+ {0x83, (uint8_t[]){0x03}, 1, 0},
+ {0x86, (uint8_t[]){0x04}, 1, 0},
+ {0x87, (uint8_t[]){0x16}, 1, 0},
+ {0x88, (uint8_t[]){0x0A}, 1, 0},
+ {0x89, (uint8_t[]){0x27}, 1, 0},
+ {0x93, (uint8_t[]){0x63}, 1, 0},
+ {0x96, (uint8_t[]){0x81}, 1, 0},
+ {0xC3, (uint8_t[]){0x10}, 1, 0},
+ {0xE6, (uint8_t[]){0x00}, 1, 0},
+ {0x99, (uint8_t[]){0x01}, 1, 0},
+ {0x70, (uint8_t[]){0x09}, 1, 0},
+ {0x71, (uint8_t[]){0x1D}, 1, 0},
+ {0x72, (uint8_t[]){0x14}, 1, 0},
+ {0x73, (uint8_t[]){0x0a}, 1, 0},
+ {0x74, (uint8_t[]){0x11}, 1, 0},
+ {0x75, (uint8_t[]){0x16}, 1, 0},
+ {0x76, (uint8_t[]){0x38}, 1, 0},
+ {0x77, (uint8_t[]){0x0B}, 1, 0},
+ {0x78, (uint8_t[]){0x08}, 1, 0},
+ {0x79, (uint8_t[]){0x3E}, 1, 0},
+ {0x7a, (uint8_t[]){0x07}, 1, 0},
+ {0x7b, (uint8_t[]){0x0D}, 1, 0},
+ {0x7c, (uint8_t[]){0x16}, 1, 0},
+ {0x7d, (uint8_t[]){0x0F}, 1, 0},
+ {0x7e, (uint8_t[]){0x14}, 1, 0},
+ {0x7f, (uint8_t[]){0x05}, 1, 0},
+ {0xa0, (uint8_t[]){0x04}, 1, 0},
+ {0xa1, (uint8_t[]){0x28}, 1, 0},
+ {0xa2, (uint8_t[]){0x0c}, 1, 0},
+ {0xa3, (uint8_t[]){0x11}, 1, 0},
+ {0xa4, (uint8_t[]){0x0b}, 1, 0},
+ {0xa5, (uint8_t[]){0x23}, 1, 0},
+ {0xa6, (uint8_t[]){0x45}, 1, 0},
+ {0xa7, (uint8_t[]){0x07}, 1, 0},
+ {0xa8, (uint8_t[]){0x0a}, 1, 0},
+ {0xa9, (uint8_t[]){0x3b}, 1, 0},
+ {0xaa, (uint8_t[]){0x0d}, 1, 0},
+ {0xab, (uint8_t[]){0x18}, 1, 0},
+ {0xac, (uint8_t[]){0x14}, 1, 0},
+ {0xad, (uint8_t[]){0x0F}, 1, 0},
+ {0xae, (uint8_t[]){0x19}, 1, 0},
+ {0xaf, (uint8_t[]){0x08}, 1, 0},
+ {0xff, (uint8_t[]){0x00}, 1, 0},
+ {0x11, (uint8_t[]){0x00}, 0, 120},
+ {0x29, (uint8_t[]){0x00}, 0, 10}
+};
+
+class XINGZHI_CUBE_0_85TFT_WIFI : public WifiBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ SpiLcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_21);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ buscfg.quadwp_io_num = GPIO_NUM_NC;
+ buscfg.quadhd_io_num = GPIO_NUM_NC;
+ buscfg.max_transfer_sz = DISPLAY_HEIGHT * 80 *sizeof(uint16_t);
+ ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+ }
+
+ void InitializeNv3023Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = NV3023_PANEL_IO_SPI_CONFIG(DISPLAY_CS, DISPLAY_DC, NULL, NULL);
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi((esp_lcd_spi_bus_handle_t)SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ nv3023_vendor_config_t vendor_config = { // Uncomment these lines if use custom initialization commands
+ .init_cmds = lcd_init_cmds,
+ .init_cmds_size = sizeof(lcd_init_cmds) / sizeof(nv3023_lcd_init_cmd_t),
+ };
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_BGR;
+ panel_config.bits_per_pixel = 16;
+ panel_config.vendor_config = &vendor_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io_, &panel_config, &panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, false));
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ 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 Initializegpio21_45() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ //gpio_num_t sp_45 = GPIO_NUM_45;
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_OUTPUT;
+ io_conf.pin_bit_mask = (1ULL << GPIO_NUM_45);
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ io_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ gpio_config(&io_conf);
+ gpio_set_level(GPIO_NUM_45, 0);
+ }
+
+public:
+ XINGZHI_CUBE_0_85TFT_WIFI():
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ Initializegpio21_45(); // 鍒濆鏃讹紝鎷夐珮21寮曡剼锛屼繚璇�4g妯″潡姝e父宸ヤ綔
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeNv3023Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_CUBE_0_85TFT_WIFI);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.h
new file mode 100644
index 0000000..0f3f8ce
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.h
@@ -0,0 +1,30 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA_PIN GPIO_NUM_41
+#define DISPLAY_SCL_PIN GPIO_NUM_42
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.json
new file mode 100644
index 0000000..be5919c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-cube-0.96oled-ml307",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc
new file mode 100644
index 0000000..3ba9643
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc
@@ -0,0 +1,234 @@
+#include "dual_network_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/oled_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "../xingzhi-cube-1.54tft-wifi/power_manager.h"
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "XINGZHI_CUBE_0_96OLED_ML307"
+
+class XINGZHI_CUBE_0_96OLED_ML307 : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ Display* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_21);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+public:
+ XINGZHI_CUBE_0_96OLED_ML307() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_ML307);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.h
new file mode 100644
index 0000000..b353890
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.h
@@ -0,0 +1,27 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA_PIN GPIO_NUM_41
+#define DISPLAY_SCL_PIN GPIO_NUM_42
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.json
new file mode 100644
index 0000000..2cba4c6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-cube-0.96oled-wifi",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc
new file mode 100644
index 0000000..27bf76a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc
@@ -0,0 +1,225 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/oled_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_save_timer.h"
+#include "../xingzhi-cube-1.54tft-wifi/power_manager.h"
+
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include <esp_log.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "XINGZHI_CUBE_0_96OLED_WIFI"
+
+class XINGZHI_CUBE_0_96OLED_WIFI : public WifiBoard {
+private:
+ i2c_master_bus_handle_t display_i2c_bus_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ Display* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_21);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeDisplayI2c() {
+ i2c_master_bus_config_t bus_config = {
+ .i2c_port = (i2c_port_t)0,
+ .sda_io_num = DISPLAY_SDA_PIN,
+ .scl_io_num = DISPLAY_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(&bus_config, &display_i2c_bus_));
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(display_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+public:
+ XINGZHI_CUBE_0_96OLED_WIFI() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeDisplayI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_WIFI);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.h
new file mode 100644
index 0000000..9167578
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.h
@@ -0,0 +1,40 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA GPIO_NUM_10
+#define DISPLAY_SCL GPIO_NUM_9
+#define DISPLAY_DC GPIO_NUM_8
+#define DISPLAY_CS GPIO_NUM_14
+#define DISPLAY_RES GPIO_NUM_18
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.json
new file mode 100644
index 0000000..e3d5f50
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/config.json
@@ -0,0 +1,15 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-cube-1.54tft-ml307",
+ "sdkconfig_append": []
+ },
+ {
+ "name": "xingzhi-cube-1.54tft-ml307-wechatui",
+ "sdkconfig_append": [
+ "CONFIG_USE_WECHAT_MESSAGE_STYLE=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc
new file mode 100644
index 0000000..bd33932
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc
@@ -0,0 +1,212 @@
+#include "dual_network_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "../xingzhi-cube-1.54tft-wifi/power_manager.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "XINGZHI_CUBE_1_54TFT_ML307"
+
+class XINGZHI_CUBE_1_54TFT_ML307 : public DualNetworkBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ SpiLcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_21);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+
+ 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);
+ }
+
+public:
+ XINGZHI_CUBE_1_54TFT_ML307() :
+ DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_ML307);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.h
new file mode 100644
index 0000000..c1a998a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.h
@@ -0,0 +1,36 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA GPIO_NUM_10
+#define DISPLAY_SCL GPIO_NUM_9
+#define DISPLAY_DC GPIO_NUM_8
+#define DISPLAY_CS GPIO_NUM_14
+#define DISPLAY_RES GPIO_NUM_18
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.json
new file mode 100644
index 0000000..6cfa0d3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-cube-1.54tft-wifi",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h
new file mode 100644
index 0000000..8d238f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h
@@ -0,0 +1,186 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ adc_oneshot_unit_handle_t adc_handle_;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_6, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1970, 0},
+ {2062, 20},
+ {2154, 40},
+ {2246, 60},
+ {2338, 80},
+ {2430, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_2,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_6, &chan_config));
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc
new file mode 100644
index 0000000..e7c41df
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc
@@ -0,0 +1,201 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "XINGZHI_CUBE_1_54TFT_WIFI"
+
+class XINGZHI_CUBE_1_54TFT_WIFI : public WifiBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ SpiLcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_38);
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_21);
+ rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_21, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ rtc_gpio_set_level(GPIO_NUM_21, 0);
+ // 鍚敤淇濇寔鍔熻兘锛岀‘淇濈潯鐪犳湡闂寸數骞充笉鍙�
+ rtc_gpio_hold_en(GPIO_NUM_21);
+ esp_lcd_panel_disp_on_off(panel_, false); //鍏抽棴鏄剧ず
+ esp_deep_sleep_start();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+
+ 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);
+ }
+
+public:
+ XINGZHI_CUBE_1_54TFT_WIFI() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_WIFI);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/README.md b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/README.md
new file mode 100644
index 0000000..2ad3efd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/README.md
@@ -0,0 +1,61 @@
+# 鏃犲悕绉戞妧鏄熸櫤 1.54 METAL (wifi)
+
+## 绠�浠�
+鏃犲悕绉戞妧鏄熸櫤 1.54 METAL (wifi) 鏄槦鏅� 1.54 寮�妯$増鐨勫崌绾ф锛岄厤澶� 1.54 瀵� LCD 灞忓箷涓� CST816 瑙︽懜鑺墖銆傚畠鐢ㄨЕ鎽镐氦浜掓浛浠g墿鐞嗘寜閿紝骞跺皢澶栧3鍗囩骇涓洪摑鍚堥噾鏉愯川锛屽悓姝ヤ紭鍖栦簡浜や簰浣撻獙涓庝骇鍝佽川鎰熴�佹墜鎰熴��
+
+>### 鎸夐敭鎿嶄綔
+>- **寮�鏈�**: 鍏虫満鐘舵�侊紝闀挎寜鐢垫簮閿�3绉掑悗鑷姩寮�鏈猴紙鏃х増纭欢闀挎寜鐢垫簮閿�1s鍚庤嚜鍔ㄥ紑鏈猴級
+>- **鍏虫満**: 寮�鏈虹姸鎬侊紝闀挎寜鐢垫簮閿�5绉掑悗鑷姩鍏虫満锛堟棫鐗堢‖浠舵彃鍏sb鏃朵笉浼氳嚜鍔ㄥ叧鏈猴級
+>- **鍞ら啋/鎵撴柇**: 姝e父閫氳瘽鐜涓嬶紝鍗曞嚮涓棿瑙︽懜鎸夐敭
+>- **閲嶆柊閰嶇綉**: 寮�鏈哄悗锛�1绉掗挓鍐呭崟鍑讳腑闂磋Е鎽告寜閿紝浼氳嚜鍔ㄩ噸鍚苟杩涘叆閰嶇綉鐣岄潰
+>- **澧炲姞闊抽噺**: 寮�鏈虹姸鎬佷笅锛屽崟鍑诲彸渚цЕ鎽告寜閿紝闊抽噺澧炲姞銆傞暱鎸夊彸渚цЕ鎽告寜閿�2s锛岄煶閲忛�掑銆�
+>- **鍑忓皬闊抽噺**: 寮�鏈虹姸鎬佷笅锛屽崟鍑诲乏渚цЕ鎽告寜閿紝闊抽噺鍑忓皬銆傞暱鎸夊乏渚цЕ鎽告寜閿�2s锛岄煶閲忛�掑噺銆�
+
+>### 浼戠湢鎿嶄綔
+>- **娴呯潯鐪�**: 寮�鏈哄悗锛岀淮鎸佸緟鍛界姸鎬�60s鍚庯紝杩涘叆娴呯潯鐪狅紙灞忓箷浜害璋冩暣鍒�1%锛�
+>- **娣辩潯鐪�**: 寮�鏈哄悗锛岀淮鎸佸緟鍛界姸鎬�300s鍚庯紝杩涘叆娣辩潯鐪狅紙鑷姩鍏虫満锛�
+>- **鍞ら啋**: 娴呯潯鐪犵姸鎬佷笅锛屽崟鍑讳腑闂磋Е鎽告寜閿紝鍞ら啋璁惧锛堝睆骞曚寒搴﹀洖璋冿級
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `鏃犲悕绉戞妧鏄熸櫤1.54 METAL(wifi)`
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.h
new file mode 100644
index 0000000..11dab54
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.h
@@ -0,0 +1,53 @@
+
+#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_INPUT_REFERENCE true
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_17
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15
+
+#define AUDIO_CODEC_USE_PCA9557
+#define AUDIO_CODEC_I2C_PA_EN GPIO_NUM_21
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_41
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_42
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+// 鎸夐敭
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+
+// 灞忓箷
+#define DISPLAY_SPI_HOST SPI3_HOST
+#define DISPLAY_SDA GPIO_NUM_10
+#define DISPLAY_SCL GPIO_NUM_9
+#define DISPLAY_DC GPIO_NUM_8
+#define DISPLAY_CS GPIO_NUM_14
+#define DISPLAY_RES GPIO_NUM_18
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+// 鐢垫簮绠$悊
+#define POWER_USB_IN GPIO_NUM_1
+#define Power_Control GPIO_NUM_48 // 鐢垫簮鎺у埗寮曡剼
+#define Power_Dec GPIO_NUM_47 // 鐢垫簮閿娴嬪紩鑴�
+#define POWER_CBS_ADC_UNIT ADC_UNIT_1 // adc妫�娴嬪叕鍏眜nit GPIO1
+#define POWER_USBIN_ADC_CHANNEL ADC_CHANNEL_0 // 妫�娴媢sb鏄惁鎻掑叆 GPIO1
+#define POWER_BATTERY_ADC_CHANNEL ADC_CHANNEL_6 // 鐢垫睜鐢甸噺妫�娴� GPIO7
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.json
new file mode 100644
index 0000000..53bebb5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/config.json
@@ -0,0 +1,10 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "xingzhi-metal-1.54-wifi",
+ "sdkconfig_append": [
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc
new file mode 100644
index 0000000..cbe64a7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.cc
@@ -0,0 +1,213 @@
+#include "cst816x.h"
+#include "board.h"
+#include "application.h"
+#include "display.h"
+#include "assets/lang_config.h"
+#include "wifi_board.h"
+#include "power_save_timer.h"
+#include "codecs/es8311_audio_codec.h"
+#include <algorithm> // 鐢ㄤ簬std::max/std::min
+
+#define TAG "Cst816x"
+
+const Cst816x::TouchThresholdConfig& Cst816x::getThresholdConfig(int x, int y) {
+ for (const auto& config : TOUCH_THRESHOLD_TABLE) {
+ if (config.x == x && config.y == y) {
+ return config;
+ }
+ }
+ return DEFAULT_THRESHOLD;
+}
+
+Cst816x::Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ uint8_t chip_id = ReadReg(0xA7);
+ ESP_LOGI(TAG, "Get CST816x chip ID: 0x%02X", chip_id);
+ read_buffer_ = new uint8_t[6];
+}
+
+Cst816x::~Cst816x() {
+ if (read_buffer_ != nullptr) {
+ delete[] read_buffer_;
+ read_buffer_ = nullptr;
+ }
+}
+
+int64_t Cst816x::getCurrentTimeUs() {
+ struct timeval tv;
+ gettimeofday(&tv, nullptr);
+ return (int64_t)tv.tv_sec * 1000000L + tv.tv_usec;
+}
+
+void Cst816x::UpdateTouchPoint() {
+ ReadRegs(0x02, read_buffer_, 6);
+ tp_.num = read_buffer_[0] & 0x0F;
+ tp_.x = ((read_buffer_[1] & 0x0F) << 8) | read_buffer_[2];
+ tp_.y = ((read_buffer_[3] & 0x0F) << 8) | read_buffer_[4];
+ memset(read_buffer_, 0, 6);
+}
+
+void Cst816x::resetTouchCounters() {
+ is_touching_ = false;
+ touch_start_time_ = 0;
+ last_release_time_ = 0;
+ click_count_ = 0;
+ long_press_started_ = false;
+
+ is_volume_long_pressing_ = false;
+ volume_long_press_dir_ = 0;
+ last_volume_adjust_time_ = 0;
+}
+
+void Cst816x::touchpad_daemon(void* arg) {
+ Cst816x* cst816x = static_cast<Cst816x*>(arg);
+ auto& board = Board::GetInstance();
+ auto codec = board.GetAudioCodec();
+ auto display = board.GetDisplay();
+
+ while (1) {
+ cst816x->UpdateTouchPoint();
+ auto& tp = cst816x->GetTouchPoint();
+ int64_t current_time = cst816x->getCurrentTimeUs();
+
+ const auto& config = cst816x->getThresholdConfig(tp.x, tp.y);
+ if (tp.num > 0) {
+ ESP_LOGD(TAG, "Touch at (%d,%d) 鈫� SingleThresh:%lldms, DoubleWindow:%lldms, LongThresh:%lldms",
+ tp.x, tp.y,
+ config.single_click_thresh_us / 1000,
+ config.double_click_window_us / 1000,
+ config.long_press_thresh_us / 1000);
+ }
+
+ TouchEvent current_event;
+ bool event_detected = false;
+
+ if (tp.num > 0 && !cst816x->is_touching_) {
+ cst816x->is_touching_ = true;
+ cst816x->touch_start_time_ = current_time;
+ cst816x->long_press_started_ = false;
+ }
+ else if (tp.num > 0 && cst816x->is_touching_) {
+ if (!cst816x->long_press_started_ &&
+ (current_time - cst816x->touch_start_time_ >= config.long_press_thresh_us)) {
+ current_event = {TouchEventType::LONG_PRESS_START, tp.x, tp.y};
+ event_detected = true;
+ cst816x->long_press_started_ = true;
+ }
+ }
+ else if (tp.num == 0 && cst816x->is_touching_) {
+ cst816x->is_touching_ = false;
+ int64_t touch_duration = current_time - cst816x->touch_start_time_;
+ cst816x->last_release_time_ = current_time;
+ if (cst816x->long_press_started_) {
+ current_event = {TouchEventType::LONG_PRESS_END, tp.x, tp.y};
+ event_detected = true;
+ }
+ else if (touch_duration <= config.single_click_thresh_us) {
+ cst816x->click_count_++;
+ }
+ }
+ else if (tp.num == 0 && !cst816x->is_touching_) {
+ if (cst816x->click_count_ > 0 &&
+ (current_time - cst816x->last_release_time_ >= config.double_click_window_us)) {
+ if (cst816x->click_count_ == 2) {
+ current_event = {TouchEventType::DOUBLE_CLICK, tp.x, tp.y};
+ event_detected = true;
+ }
+ else if (cst816x->click_count_ == 1) {
+ current_event = {TouchEventType::SINGLE_CLICK, tp.x, tp.y};
+ event_detected = true;
+ }
+ cst816x->click_count_ = 0;
+ }
+ }
+
+ if (event_detected) {
+ if (current_event.y == 600 && (current_event.x == 20 || current_event.x == 40 || current_event.x == 60)) {
+ switch (current_event.type) {
+ case TouchEventType::SINGLE_CLICK:
+ if (current_event.x == 40) {
+ board.SetPowerSaveLevel(PowerSaveLevel::PERFORMANCE);
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ auto& wifi_board = static_cast<WifiBoard&>(Board::GetInstance());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ } else if (current_event.x == 20) { // 20,600 鍗曞嚮锛氶煶閲�+
+ int current_vol = codec->output_volume();
+ int new_vol = current_vol + 10;
+ new_vol = (new_vol >= ES8311_VOL_MAX) ? ES8311_VOL_MAX : new_vol;
+ ESP_LOGI(TAG, "current_vol, new_vol(%d, %d)", current_vol, new_vol);
+ codec->EnableOutput(true);
+ codec->SetOutputVolume(new_vol);
+ display->ShowNotification(Lang::Strings::VOLUME + std::to_string(new_vol));
+ } else if (current_event.x == 60) { // 60,600 鍗曞嚮锛氶煶閲�-
+ int current_vol = codec->output_volume();
+ int new_vol = current_vol - 10;
+ new_vol = (new_vol <= ES8311_VOL_MIN) ? ES8311_VOL_MIN : new_vol;
+ ESP_LOGI(TAG, "current_vol, new_vol(%d, %d)", current_vol, new_vol);
+ codec->EnableOutput(true);
+ codec->SetOutputVolume(new_vol);
+ display->ShowNotification(Lang::Strings::VOLUME + std::to_string(new_vol));
+ }
+ break;
+
+ case TouchEventType::DOUBLE_CLICK:
+ ESP_LOGI(TAG, "Double click detected at (%d, %d)", current_event.x, current_event.y);
+ break;
+
+ case TouchEventType::LONG_PRESS_START:
+ ESP_LOGI(TAG, "Long press started at (%d, %d) 鈫� Start volume adjust", current_event.x, current_event.y);
+ if (current_event.x == 20) {
+ cst816x->is_volume_long_pressing_ = true;
+ cst816x->volume_long_press_dir_ = 1;
+ cst816x->last_volume_adjust_time_ = current_time;
+ } else if (current_event.x == 60) {
+ cst816x->is_volume_long_pressing_ = true;
+ cst816x->volume_long_press_dir_ = -1;
+ cst816x->last_volume_adjust_time_ = current_time;
+ }
+ break;
+
+ case TouchEventType::LONG_PRESS_END:
+ ESP_LOGI(TAG, "Long press ended at (%d, %d) 鈫� Stop volume adjust", current_event.x, current_event.y);
+ if (current_event.x == 20 || current_event.x == 60) {
+ cst816x->is_volume_long_pressing_ = false;
+ cst816x->volume_long_press_dir_ = 0;
+ cst816x->last_volume_adjust_time_ = 0;
+ }
+ break;
+ }
+ }
+ }
+
+ if (cst816x->is_volume_long_pressing_) {
+ int64_t now = cst816x->getCurrentTimeUs();
+ if (now - cst816x->last_volume_adjust_time_ >= cst816x->VOL_ADJ_INTERVAL_US) {
+ int current_vol = codec->output_volume();
+ int new_vol = current_vol + (cst816x->volume_long_press_dir_ * cst816x->VOL_ADJ_STEP);
+
+ new_vol = std::max(ES8311_VOL_MIN, std::min(ES8311_VOL_MAX, new_vol));
+
+ if (new_vol != current_vol) {
+ codec->EnableOutput(true);
+ codec->SetOutputVolume(new_vol);
+ display->ShowNotification(Lang::Strings::VOLUME + std::to_string(new_vol));
+ cst816x->last_volume_adjust_time_ = now;
+ } else {
+ cst816x->is_volume_long_pressing_ = false;
+ cst816x->volume_long_press_dir_ = 0;
+ ESP_LOGI(TAG, "Volume reached limit (%d), stop adjusting", new_vol);
+ }
+ }
+ }
+
+ vTaskDelay(pdMS_TO_TICKS(40));
+ }
+}
+
+void Cst816x::InitCst816d() {
+ ESP_LOGI(TAG, "Init CST816x touch driver");
+ xTaskCreate(touchpad_daemon, "touch_daemon", 2048, this, 1, NULL);
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.h
new file mode 100644
index 0000000..7a8a3d3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/cst816x.h
@@ -0,0 +1,90 @@
+#ifndef _CST816X_H_
+#define _CST816X_H_
+
+#include "esp_log.h"
+#include "esp_err.h"
+#include "driver/i2c.h"
+#include "i2c_device.h"
+#include <driver/i2c_master.h>
+#include <sys/time.h>
+#include <array>
+
+#define ES8311_VOL_MIN 0
+#define ES8311_VOL_MAX 100
+
+enum class TouchEventType {
+ SINGLE_CLICK, // 鍗曞嚮浜嬩欢
+ DOUBLE_CLICK, // 鍙屽嚮浜嬩欢
+ LONG_PRESS_START,// 闀挎寜寮�濮嬩簨浠�
+ LONG_PRESS_END // 闀挎寜缁撴潫浜嬩欢
+};
+
+struct TouchEvent {
+ TouchEventType type;
+ int x;
+ int y;
+};
+
+class Cst816x : public I2cDevice {
+private:
+ struct TouchPoint_t {
+ int num = 0;
+ int x = -1;
+ int y = -1;
+ };
+
+ struct TouchThresholdConfig {
+ int x; // 鐩爣X鍧愭爣
+ int y; // 鐩爣Y鍧愭爣
+ int64_t single_click_thresh_us; // 鍗曞嚮鏈�澶ф椂闀匡紙us锛�
+ int64_t double_click_window_us; // 鍙屽嚮绐楀彛锛坲s锛�
+ int64_t long_press_thresh_us; // 闀挎寜闃堝�硷紙us锛�
+ };
+
+ const TouchThresholdConfig DEFAULT_THRESHOLD = {
+ .x = -1, .y = -1,
+ .single_click_thresh_us = 120000, // 150ms
+ .double_click_window_us = 240000, // 150ms
+ .long_press_thresh_us = 4000000 // 4000ms
+ };
+
+ const std::array<TouchThresholdConfig, 3> TOUCH_THRESHOLD_TABLE = {
+ {
+ {20, 600, 200000, 240000, 2000000}, // 闊抽噺+
+ {40, 600, 200000, 240000, 4000000}, // boot鎸夐敭
+ {60, 600, 200000, 240000, 2000000} // 闊抽噺-
+ }
+ };
+
+ const TouchThresholdConfig& getThresholdConfig(int x, int y);
+
+ uint8_t* read_buffer_ = nullptr;
+ TouchPoint_t tp_;
+
+ bool is_touching_ = false;
+ int64_t touch_start_time_ = 0; // 瑙︽懜寮�濮嬫椂闂达紙us锛�
+ int64_t last_release_time_ = 0; // 涓婃閲婃斁鏃堕棿锛坲s锛�
+ int click_count_ = 0; // 鍗曞嚮璁℃暟锛堝弻鍑绘娴嬬敤锛�
+ bool long_press_started_ = false; // 闀挎寜鏄惁宸茶Е鍙�
+
+ bool is_volume_long_pressing_ = false; // 鏄惁澶勪簬闊抽噺闀挎寜璋冩暣涓�
+ int volume_long_press_dir_ = 0; // 璋冩暣鏂瑰悜锛�1=閫掑锛�-1=閫掑噺
+ int64_t last_volume_adjust_time_ = 0; // 涓婃璋冩暣闊抽噺鐨勬椂闂达紙us锛�
+ const int64_t VOL_ADJ_INTERVAL_US = 200000; // 闊抽噺璋冩暣闂撮殧锛�100ms锛�
+ const int VOL_ADJ_STEP = 5; // 姣忔璋冩暣姝ラ暱
+
+ int64_t getCurrentTimeUs();
+
+public:
+ Cst816x(i2c_master_bus_handle_t i2c_bus, uint8_t addr);
+ ~Cst816x();
+
+ void InitCst816d();
+ void UpdateTouchPoint();
+ void resetTouchCounters();
+ static void touchpad_daemon(void* param);
+
+ const TouchPoint_t& GetTouchPoint() { return tp_; }
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/power_manager.h
new file mode 100644
index 0000000..5be6ee8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/power_manager.h
@@ -0,0 +1,314 @@
+#pragma once
+#include <vector>
+#include <functional>
+
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include "sdkconfig.h"
+#include "button.h"
+#include "board.h"
+#include "config.h"
+#include "assets/lang_config.h"
+#include <esp_sleep.h>
+
+class PowerManager {
+private:
+ esp_timer_handle_t timer_handle_;
+ esp_timer_handle_t power_timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 30;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ int ticks_ = 0;
+ adc_oneshot_unit_handle_t adc_handle_;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+
+ // power
+ bool pressed = false; // 鏄惁鎸変笅鐢垫簮閿�
+ int PowerControl_ticks_ = 0; // 寮�鏈烘椂闀�
+ int press_ticks_ = 0; // 鎸変笅鏃剁殑tick
+ bool is_first_boot = true; //
+ uint8_t PowerDec_level_ = 0; // 鐢垫簮閿數骞�
+ const int power_off_ticks_ = 20; // 鎸夐敭鎸変笅20/5绉掑噯澶囧叧鏈�;
+ bool new_charging_status = false; // 鎻掑叆usb鏃舵棤娉曡蒋浠跺叧鏈�
+ bool is_shutting_down_ = false; // 鏍囪鏄惁杩涘叆鍏虫満娴佺▼
+ int shutdown_delay_ticks_ = 0; // 鍏虫満寤惰繜璁℃暟
+ uint8_t shutdown_ticks = 5; // 5/5s鍚庡叧鏈� 棰勭暀鐨勫叧鏈烘搷浣�
+ bool shutdown_first_ = true;
+
+ // 鏃х増纭欢杞欢鍏虫満閫昏緫
+ void PowrSwitch() {
+ PowerDec_level_ = gpio_get_level(Power_Dec);
+
+ // 纭繚寮�鏈哄悗鐢垫簮閿澗寮�鍐嶆娴嬪叧鏈�
+ if (PowerDec_level_ == 1) {
+ is_first_boot = false;
+ }
+
+ if (is_shutting_down_) {
+ shutdown_delay_ticks_++;
+ if (shutdown_delay_ticks_ >= shutdown_ticks) {
+ shutdown();
+ is_shutting_down_ = false;
+ shutdown_delay_ticks_ = 0;
+ }
+ return;
+ }
+
+ if (!is_first_boot) {
+ PowerControl_ticks_++;
+ if (PowerDec_level_ == 0 && pressed == false) {
+ press_ticks_ = PowerControl_ticks_;
+ pressed = true;
+ }
+ if ((press_ticks_ != 0) &&( PowerControl_ticks_ - press_ticks_ == power_off_ticks_ )&& (!new_charging_status)) {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ is_shutting_down_ = true;
+ shutdown_delay_ticks_ = 0;
+ }
+ if (PowerDec_level_ == 1 && press_ticks_!= 0) {
+ PowerDec_level_ = gpio_get_level(Power_Dec);
+ if (PowerDec_level_ == 1) {
+ pressed = false;
+ press_ticks_ = 0;
+ }
+ }
+ }
+ }
+
+ void CheckBatteryStatus() {
+ int usb_usb_adc_value0;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, POWER_USBIN_ADC_CHANNEL, &usb_usb_adc_value0));
+ new_charging_status = (1500 < usb_usb_adc_value0 && usb_usb_adc_value0 < 4000);
+ // ESP_LOGE("powercontrol", "USB ADC VALUE 1: %d", usb_usb_adc_value0);
+
+ if (new_charging_status != is_charging_) {
+ ReadBatteryAdcData();
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, POWER_BATTERY_ADC_CHANNEL, &adc_value));
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += value;
+ }
+ average_adc /= adc_values_.size();
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {1970, 0},
+ {2062, 20},
+ {2154, 40},
+ {2246, 60},
+ {2338, 80},
+ {2430, 100}
+ };
+
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+
+ // Check low battery status
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+ // 鍒濆鍖栫數婧愰敭妫�娴嬪紩鑴�
+ gpio_config_t powerdecgpio_conf = {};
+ powerdecgpio_conf.intr_type = GPIO_INTR_DISABLE;
+ powerdecgpio_conf.mode = GPIO_MODE_INPUT;
+ powerdecgpio_conf.pin_bit_mask = (1ULL << Power_Dec);
+ powerdecgpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ powerdecgpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&powerdecgpio_conf);
+
+ // 鍒濆鍖栫數婧愭帶鍒跺紩鑴�
+ gpio_config_t powercontgpio_conf = {};
+ powercontgpio_conf.intr_type = GPIO_INTR_DISABLE;
+ powercontgpio_conf.mode = GPIO_MODE_OUTPUT;
+ powercontgpio_conf.pin_bit_mask = (1ULL << Power_Control);
+ powercontgpio_conf.pull_down_en = GPIO_PULLDOWN_ENABLE;
+ powercontgpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&powercontgpio_conf);
+ gpio_set_level(Power_Control, 1);
+ ESP_LOGI("powercontrol", "turnded on ...");
+
+ // 鍒涘缓鐢垫簮鎺у埗妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t power_timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->PowrSwitch();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "power_cotrol_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&power_timer_args, &power_timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(power_timer_handle_, 200000));
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = POWER_CBS_ADC_UNIT,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, POWER_BATTERY_ADC_CHANNEL, &chan_config)); // 鐢垫睜鐢甸噺
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, POWER_USBIN_ADC_CHANNEL, &chan_config)); // usb
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (power_timer_handle_) {
+ esp_timer_stop(power_timer_handle_);
+ esp_timer_delete(power_timer_handle_);
+ }
+ if (adc_handle_ != nullptr) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ uint8_t GetBatteryLevel() {
+ return battery_level_;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+
+ void shutdown() {
+ if (!new_charging_status && shutdown_first_)
+ {
+ shutdown_first_ = false; // 杩涘叆鍚庣疆 false 锛岄槻姝㈠啀娆¤繘鍏ュ叧鏈虹姸鎬�
+ gpio_config_t shutdown_gpio_conf = {};
+ shutdown_gpio_conf.intr_type = GPIO_INTR_DISABLE;
+ shutdown_gpio_conf.mode = GPIO_MODE_OUTPUT;
+ shutdown_gpio_conf.pin_bit_mask = (1ULL << Power_Dec);
+ shutdown_gpio_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ shutdown_gpio_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&shutdown_gpio_conf);
+ gpio_set_level(DISPLAY_BACKLIGHT_PIN, 0);
+ gpio_set_level(Power_Control, 0);
+ for (int i=1;i<15;i++) {
+ gpio_set_level(Power_Dec, 1);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ gpio_set_level(Power_Dec, 0);
+ vTaskDelay(pdMS_TO_TICKS(100));
+ ESP_LOGI("PowerManager","瑙﹀彂寮�鍏虫満鎺у埗");
+ }
+ ESP_LOGI("PowerManager","鍏虫満澶辫触锛岃繘鍏ユ繁鐫$湢");
+ esp_deep_sleep_start();
+ } else {
+ ESP_LOGI("PowerManager","妫�娴嬪埌鎻掑叆usb锛屾棤娉曞叧鏈�");
+ }
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc
new file mode 100644
index 0000000..c1467ca
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xingzhi-metal-1.54-wifi/xingzhi-metal-1.54-wifi.cc
@@ -0,0 +1,201 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "i2c_device.h"
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include "power_manager.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+#include "cst816x.h"
+
+#define TAG "XINGZHI_METAL_1_54_WIFI"
+
+class XINGZHI_METAL_1_54_WIFI : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ Button boot_button_;
+ SpiLcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ esp_err_t err;
+ bool is_device_found = false;
+ Cst816x *cst816d_;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(POWER_USB_IN);//USB鏄惁鎻掑叆
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ ESP_LOGI(TAG, "Enabling sleep mode");
+ display_->SetChatMessage("system", "");
+ display_->SetEmotion("sleepy");
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ display_->SetChatMessage("system", "");
+ display_->SetEmotion("neutral");
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ power_manager_->shutdown();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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, &i2c_bus_));
+ for (uint8_t addr = 1; addr < 127; addr++) {
+ err = i2c_master_probe(i2c_bus_, addr, 100);
+ if (err == ESP_OK) {
+ ESP_LOGI(TAG, "Device found at address 0x%02X", addr);
+ if (addr == 0x15) {
+ is_device_found = true;
+ }
+ }
+ }
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(DISPLAY_SPI_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(DISPLAY_SPI_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+
+ 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 Initialtouchtask() {
+ if (is_device_found) {
+ cst816d_ = new Cst816x(i2c_bus_, 0x15);
+ cst816d_->InitCst816d();
+ }
+ }
+
+public:
+ XINGZHI_METAL_1_54_WIFI() :
+ boot_button_(BOOT_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ Initialtouchtask();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8311AudioCodec audio_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_I2C_PA_EN,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ Cst816x *GetTouchpad() {
+ return cst816d_;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = power_manager_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XINGZHI_METAL_1_54_WIFI);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/README.md b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/README.md
new file mode 100644
index 0000000..9697085
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/README.md
@@ -0,0 +1,4 @@
+# 寮�婧愬湴鍧�
+
+https://oshwhub.com/tenclass01/xmini_c3_4g
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.h b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.h
new file mode 100644
index 0000000..8a38e0f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.h
@@ -0,0 +1,32 @@
+#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_7
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_13
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_21
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_20
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_3
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+#define ML307_TX_PIN GPIO_NUM_2
+#define ML307_RX_PIN GPIO_NUM_0
+#define ML307_DTR_PIN GPIO_NUM_1
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.json b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.json
new file mode 100644
index 0000000..b17fde6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/config.json
@@ -0,0 +1,15 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "xmini-c3-4g",
+ "sdkconfig_append": [
+ "CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
+ "CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc
new file mode 100644
index 0000000..634ebbe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-4g/xmini_c3_4g_board.cc
@@ -0,0 +1,203 @@
+#include "ml307_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/oled_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "settings.h"
+#include "config.h"
+#include "sleep_timer.h"
+#include "adc_battery_monitor.h"
+#include "press_to_talk_mcp_tool.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "XminiC3Board"
+
+class XminiC3Board : public Ml307Board {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ SleepTimer* sleep_timer_ = nullptr;
+ AdcBatteryMonitor* adc_battery_monitor_ = nullptr;
+ PressToTalkMcpTool* press_to_talk_tool_ = nullptr;
+
+ void InitializeBatteryMonitor() {
+ adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_4, 100000, 100000, GPIO_NUM_12);
+ adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ sleep_timer_->SetEnabled(false);
+ } else {
+ sleep_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ // Wake word detection will be disabled in light sleep mode
+ sleep_timer_ = new SleepTimer(30);
+ sleep_timer_->OnEnterLightSleepMode([this]() {
+ ESP_LOGI(TAG, "Enabling sleep mode");
+ // Show the standby screen
+ GetDisplay()->SetPowerSaveMode(true);
+ // Enable sleep mode, and sleep in 1 second after DTR is set to high
+ modem_->SetSleepMode(true, 1);
+ // Set the DTR pin to high to make the modem enter sleep mode
+ modem_->GetAtUart()->SetDtrPin(true);
+ });
+ sleep_timer_->OnExitLightSleepMode([this]() {
+ // Set the DTR pin to low to make the modem wake up
+ modem_->GetAtUart()->SetDtrPin(false);
+ // Hide the standby screen
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ sleep_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) {
+ app.ToggleChatState();
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StartListening();
+ }
+ });
+ boot_button_.OnPressUp([this]() {
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StopListening();
+ }
+ });
+ }
+
+ void InitializeTools() {
+ press_to_talk_tool_ = new PressToTalkMcpTool();
+ press_to_talk_tool_->Initialize();
+ }
+
+public:
+ XminiC3Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, ML307_DTR_PIN),
+ boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) {
+
+ InitializeBatteryMonitor();
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ 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 bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ charging = adc_battery_monitor_->IsCharging();
+ discharging = adc_battery_monitor_->IsDischarging();
+ level = adc_battery_monitor_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ sleep_timer_->WakeUp();
+ }
+ Ml307Board::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XminiC3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/README.md b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/README.md
new file mode 100644
index 0000000..87dfd43
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/README.md
@@ -0,0 +1,4 @@
+# 寮�婧愬湴鍧�
+
+https://oshwhub.com/tenclass01/xmini_c3
+
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.h b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.h
new file mode 100644
index 0000000..1f75de6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.h
@@ -0,0 +1,28 @@
+#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_8
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_5
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_4
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_10
+#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 BUILTIN_LED_GPIO GPIO_NUM_2
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.json b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.json
new file mode 100644
index 0000000..e830af0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "xmini-c3-v3",
+ "sdkconfig_append": [
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/xmini_c3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/xmini_c3_board.cc
new file mode 100644
index 0000000..af26764
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3-v3/xmini_c3_board.cc
@@ -0,0 +1,201 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/oled_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "settings.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "adc_battery_monitor.h"
+#include "press_to_talk_mcp_tool.h"
+
+#include <wifi_manager.h>
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "XminiC3Board"
+
+class XminiC3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ PowerSaveTimer* power_save_timer_ = nullptr;
+ AdcBatteryMonitor* adc_battery_monitor_ = nullptr;
+ PressToTalkMcpTool* press_to_talk_tool_ = nullptr;
+
+ void InitializePowerManager() {
+ adc_battery_monitor_ = new AdcBatteryMonitor(ADC_UNIT_1, ADC_CHANNEL_3, 100000, 100000, GPIO_NUM_12);
+ adc_battery_monitor_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(160, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ // Print I2C bus info
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT enters config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) {
+ app.ToggleChatState();
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ if (power_save_timer_) {
+ power_save_timer_->WakeUp();
+ }
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StartListening();
+ }
+ });
+ boot_button_.OnPressUp([this]() {
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StopListening();
+ }
+ });
+ }
+
+ void InitializeTools() {
+ press_to_talk_tool_ = new PressToTalkMcpTool();
+ press_to_talk_tool_->Initialize();
+ }
+
+public:
+ XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO, false, 0, 0, true) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeCodecI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializeTools();
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ 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 bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ charging = adc_battery_monitor_->IsCharging();
+ discharging = adc_battery_monitor_->IsDischarging();
+ level = adc_battery_monitor_->GetBatteryLevel();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XminiC3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.h b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.h
new file mode 100644
index 0000000..f37a035
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.h
@@ -0,0 +1,28 @@
+#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_6
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_7
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_11
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+
+#define BUILTIN_LED_GPIO GPIO_NUM_2
+#define BOOT_BUTTON_GPIO GPIO_NUM_9
+
+#define DISPLAY_WIDTH 128
+#define DISPLAY_HEIGHT 64
+#define DISPLAY_MIRROR_X true
+#define DISPLAY_MIRROR_Y true
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.json b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.json
new file mode 100644
index 0000000..0073568
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/config.json
@@ -0,0 +1,14 @@
+{
+ "target": "esp32c3",
+ "builds": [
+ {
+ "name": "xmini-c3",
+ "sdkconfig_append": [
+ "CONFIG_PM_ENABLE=y",
+ "CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
+ "CONFIG_USE_ESP_WAKE_WORD=y",
+ "CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/xmini_c3_board.cc b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/xmini_c3_board.cc
new file mode 100644
index 0000000..11d4a2e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/xmini-c3/xmini_c3_board.cc
@@ -0,0 +1,182 @@
+#include "wifi_board.h"
+#include "codecs/es8311_audio_codec.h"
+#include "display/oled_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "mcp_server.h"
+#include "settings.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "press_to_talk_mcp_tool.h"
+
+#include <esp_log.h>
+#include <esp_efuse_table.h>
+#include <driver/i2c_master.h>
+#include <esp_lcd_panel_ops.h>
+#include <esp_lcd_panel_vendor.h>
+
+#define TAG "XminiC3Board"
+
+class XminiC3Board : public WifiBoard {
+private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+ Display* display_ = nullptr;
+ Button boot_button_;
+ PowerSaveTimer* power_save_timer_ = nullptr;
+ PressToTalkMcpTool* press_to_talk_tool_ = nullptr;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(160, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeCodecI2c() {
+ // Initialize I2C peripheral
+ 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_));
+
+ // Print I2C bus info
+ if (i2c_master_probe(codec_i2c_bus_, 0x18, 1000) != ESP_OK) {
+ while (true) {
+ ESP_LOGE(TAG, "Failed to probe I2C bus, please check if you have installed the correct firmware");
+ vTaskDelay(1000 / portTICK_PERIOD_MS);
+ }
+ }
+ }
+
+ void InitializeSsd1306Display() {
+ // SSD1306 config
+ esp_lcd_panel_io_i2c_config_t io_config = {
+ .dev_addr = 0x3C,
+ .on_color_trans_done = nullptr,
+ .user_ctx = nullptr,
+ .control_phase_bytes = 1,
+ .dc_bit_offset = 6,
+ .lcd_cmd_bits = 8,
+ .lcd_param_bits = 8,
+ .flags = {
+ .dc_low_on_data = 0,
+ .disable_control_phase = 0,
+ },
+ .scl_speed_hz = 400 * 1000,
+ };
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2(codec_i2c_bus_, &io_config, &panel_io_));
+
+ ESP_LOGI(TAG, "Install SSD1306 driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = -1;
+ panel_config.bits_per_pixel = 1;
+
+ esp_lcd_panel_ssd1306_config_t ssd1306_config = {
+ .height = static_cast<uint8_t>(DISPLAY_HEIGHT),
+ };
+ panel_config.vendor_config = &ssd1306_config;
+
+ ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_));
+ ESP_LOGI(TAG, "SSD1306 driver installed");
+
+ // Reset the display
+ ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ if (esp_lcd_panel_init(panel_) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to initialize display");
+ display_ = new NoDisplay();
+ return;
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true));
+
+ display_ = new OledDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ if (!press_to_talk_tool_ || !press_to_talk_tool_->IsPressToTalkEnabled()) {
+ app.ToggleChatState();
+ }
+ });
+ boot_button_.OnPressDown([this]() {
+ if (power_save_timer_) {
+ power_save_timer_->WakeUp();
+ }
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StartListening();
+ }
+ });
+ boot_button_.OnPressUp([this]() {
+ if (press_to_talk_tool_ && press_to_talk_tool_->IsPressToTalkEnabled()) {
+ Application::GetInstance().StopListening();
+ }
+ });
+ }
+
+ void InitializeTools() {
+ press_to_talk_tool_ = new PressToTalkMcpTool();
+ press_to_talk_tool_->Initialize();
+ }
+
+public:
+ XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO) {
+ InitializeCodecI2c();
+ InitializeSsd1306Display();
+ InitializeButtons();
+ InitializePowerSaveTimer();
+ InitializeTools();
+
+ // 閬垮厤浣跨敤閿欒鐨勫浐浠讹紝鎶� EFUSE 鎿嶄綔鏀惧湪鏈�鍚�
+ // 鎶� ESP32C3 鐨� VDD SPI 寮曡剼浣滀负鏅�� GPIO 鍙d娇鐢�
+ esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO);
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ 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 void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(XminiC3Board);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/README.md b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/README.md
new file mode 100644
index 0000000..0fc4433
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/README.md
@@ -0,0 +1,98 @@
+# 灏忔櫤浜戣亰 S3
+
+## 绠�浠�
+
+灏忔櫤浜戣亰 S3 鏄皬鏅� AI 鐨勯瓟鏀归」鐩紝鏄涓� 2.8 瀵告姢鐪煎ぇ灞�+澶у瓧浣�+2000mah 澶х數姹犵殑閲忎骇鎴愬搧锛屽仛浜嗗ぇ閲忓垱鏂板拰浼樺寲銆�
+
+## 瀹樻柟鐗�
+
+瀹樻柟鐗堜唬鐮佸湪灏忔櫤 AI 涓婚」鐩腑缁存姢锛岃窡闅忎富椤圭洰鐨勪竴璧风増鏈洿鏂帮紝渚夸簬鐢ㄦ埛鑷鎵╁睍鍜岀涓夋柟鍥轰欢鎵╁睍銆傛敮鎸佽闊冲敜閱掋�佽闊虫墦鏂�丱TA銆�4G 鑷敱鍒囨崲绛夊姛鑳姐��
+
+> ### 鎸夐敭鎿嶄綔
+>
+> - **寮�鏈�**: 鍏虫満鐘舵�侊紝闀挎寜 1 绉掑悗閲婃斁鎸夐敭锛岃嚜鍔ㄥ紑鏈恒��
+> - **鍏虫満**: 寮�鏈虹姸鎬侊紝闀挎寜 1 绉掑悗閲婃斁鎸夐敭锛屾爣棰樻爮浼氭樉绀�'璇风◢鍊�'锛屽啀绛� 2 绉掕嚜鍔ㄥ叧鏈恒��
+> - **鍞ら啋/鎵撴柇**: 姝e父閫氳瘽鐜涓嬶紝鍗曞嚮鎸夐敭銆�
+> - **鍒囨崲 4G/Wifi**: 鍚姩杩囩▼鎴栬�呴厤缃戠晫闈紝1 绉掗挓鍐呭弻鍑绘寜閿紙闇�瀹夎 4G 妯″潡锛夈��
+> - **鍒囨崲璇煶(AEC)鎵撴柇妯″紡**: 姝e父鍚姩鍚庯紝鍦ㄧ┖闂叉棤瀵硅瘽妯″紡涓嬶紝1 绉掗挓鍐呭弻鍑绘寜閿紝寰幆鍒囨崲璇煶鎵撴柇妯″紡銆�
+> - **閲嶆柊閰嶇綉**: 寮�鏈虹姸鎬侊紝1 绉掗挓鍐呬笁鍑绘寜閿紝浼氳嚜鍔ㄩ噸鍚苟杩涘叆閰嶇綉鐣岄潰銆�
+
+> ### 璇煶鎸囦护
+>
+> - **鎵撳紑/鍏抽棴璇煶(AEC)鎵撴柇妯″紡**: 鍦ㄦ挱鏀鹃煶涔愭椂锛岄渶瑕佸叧闂闊虫墦鏂ā寮忥紝鍚﹀垯鍙兘浼氭墦鏂煶涔愭挱鏀俱��
+> - **鍒囨崲 IPS 灞忓箷鏄剧ず妯″紡**: 鏂扮増灏忔櫤浜戣亰 S3 鍗囩骇浜� IPS 灞忓箷锛岄渶瑕佸垏鎹㈠睆骞曟樉绀烘ā寮忓悗鎵嶈兘姝e父鏄剧ず锛屽彲浠ユ潵鍥炲垏鎹€��
+
+## 榄旀敼鐗�
+
+榄旀敼鐗堢敱浜庡簳灞傛敼鍔ㄥお澶э紝浠g爜鍗曠嫭缁存姢锛屽畾鏈熷悎骞朵富椤圭洰浠g爜銆�
+
+> ### 涓轰粈涔堟槸榄旀敼
+>
+> - 棣栦釜瀹炵幇寰俊浜岀淮鐮侀厤缃戙��
+> - 棣栦釜鏀寔鍗曟墜鏈洪厤缃戙��
+> - 棣栦釜鏀寔鎵簩缁寸爜璁块棶鎺у埗鍙般��
+> - 棣栧彂鏀寔绻佷綋銆佹棩鏂囥�佽嫳鏂囩増鐣岄潰銆�
+> - 棣栦釜鍏ㄨ闊虫搷鎺фā寮忋��
+> - 鐙鎻愪緵涓�閿埛鏈鸿剼鏈瓑澶氱鍒锋満鏂瑰紡銆�
+
+## 鐗堟湰鍖哄埆
+
+> | 鐗规�� | 瀹樻柟鐗� | 榄旀敼鐗� |
+> | -------------- | ------ | ------ |
+> | 璇煶鎵撴柇 | 鉁� | 鉁� |
+> | 4G 鍔熻兘 | 鉁� | 鉁� |
+> | 鑷姩鏇存柊鍥轰欢 | 鉁� | X |
+> | 绗笁鏂瑰浐浠舵敮鎸� | 鉁� | X |
+> | 澶╂皵寰呮満鐣岄潰 | X | 鉁� |
+> | 闂归挓鎻愰啋 | X | 鉁� |
+> | 缃戠粶闊充箰鎾斁 | X | 鉁� |
+> | 寰俊鎵爜閰嶇綉 | X | 鉁� |
+> | 鍗曟墜鏈洪厤缃� | X | 鉁� |
+> | 鎵爜璁块棶鎺у埗鍙� | X | 鉁� |
+> | 绻佹棩鑻辨枃鐣岄潰 | X | 鉁� |
+> | 澶氳瑷�鏀寔 | 闇�鑷缂栬瘧 | 鉁� |
+> | 澶栨帴钃濈墮闊崇/鑰虫満 | 鉁� | 鉁� |
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**鍏嬮殕宸ョ▼**
+
+```bash
+git clone https://github.com/78/xiaozhi-esp32.git
+```
+
+**杩涘叆宸ョ▼**
+
+```bash
+cd xiaozhi-esp32
+```
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙**
+
+```bash
+- `Xiaozhi Assistant` 鈫� `Board Type` 鈫� 閫夋嫨 `灏忔櫤浜戣亰-S3` 鈫� 閫夋嫨 `Enable Device-Side AEC`
+```
+
+**缂栬瘧**
+
+```ba
+idf.py build
+```
+
+**涓嬭浇骞舵墦寮�涓插彛缁堢**
+
+```bash
+idf.py build flash monitor
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.h b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.h
new file mode 100644
index 0000000..4f2b9d6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.h
@@ -0,0 +1,61 @@
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_REFERENCE true
+
+#define AUDIO_INPUT_SAMPLE_RATE 24000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_DEFAULT_OUTPUT_VOLUME 70
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
+
+#define AUDIO_CODEC_PA_PIN GPIO_NUM_17
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18
+#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
+
+#define BOOT_BUTTON_PIN GPIO_NUM_2
+#define BOOT_5V_PIN GPIO_NUM_3 //5V鍗囧帇杈撳嚭
+#define MON_BTLINK_PIN GPIO_NUM_4 //妫�娴婤T杩炴帴鐘舵��
+#define BOOT_4G5V_PIN GPIO_NUM_5 //4G妯″潡渚涚數
+#define BOOT_4GEN_PIN GPIO_NUM_6 //4G妯″潡浣胯兘
+#define MON_BATT_PIN GPIO_NUM_43 //妫�娴婸MU鐢垫睜鎸囩ず
+#define MON_BATT_CNT 70 //妫�娴婸MU鐢垫睜绉掓暟
+#define MON_USB_PIN GPIO_NUM_47 //妫�娴婾SB鎻掑叆
+
+
+#define ML307_RX_PIN GPIO_NUM_16
+#define ML307_TX_PIN GPIO_NUM_15
+
+#define DISPLAY_SPI_LCD_HOST SPI2_HOST
+#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000)
+#define DISPLAY_SPI_PIN_SCLK 42
+#define DISPLAY_SPI_PIN_MOSI 40
+#define DISPLAY_SPI_PIN_MISO -1
+#define DISPLAY_SPI_PIN_LCD_DC 41
+#define DISPLAY_SPI_PIN_LCD_RST 45
+#define DISPLAY_SPI_PIN_LCD_CS -1
+#define DISPLAY_PIN_TOUCH_CS -1
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define DISPLAY_WIDTH 320
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY true
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y true
+#define DISPLAY_INVERT_COLOR true
+#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+#define KEY_EXPIRE_MS 800
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.json b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.json
new file mode 100644
index 0000000..1261a68
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/config.json
@@ -0,0 +1,11 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "yunliao-s3",
+ "sdkconfig_append": [
+ "CONFIG_USE_DEVICE_AEC=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.cc b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.cc
new file mode 100644
index 0000000..d6cc2d1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.cc
@@ -0,0 +1,278 @@
+#include "power_manager.h"
+#include "esp_sleep.h"
+#include "driver/rtc_io.h"
+#include "esp_log.h"
+#include "config.h"
+#include <esp_sleep.h>
+#include "esp_log.h"
+#include "settings.h"
+
+#define TAG "PowerManager"
+
+static QueueHandle_t gpio_evt_queue = NULL;
+uint16_t battCnt;//闂伅娆℃暟
+int battLife = 70; //鐢甸噺
+
+// 涓柇鏈嶅姟绋嬪簭
+static void IRAM_ATTR batt_mon_isr_handler(void* arg) {
+ uint32_t gpio_num = (uint32_t) arg;
+ xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
+}
+
+// 娣诲姞浠诲姟澶勭悊鍑芥暟
+static void batt_mon_task(void* arg) {
+ uint32_t io_num;
+ while(1) {
+ if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
+ battCnt++;
+ }
+ }
+}
+
+static void calBattLife() {
+ // 璁$畻鐢甸噺
+ battLife = battCnt;
+
+ if (battLife > 100){
+ battLife = 100;
+ }
+ // ESP_LOGI(TAG, "Battery life:%d", (int)battLife);
+ // 閲嶇疆璁℃暟鍣�
+ battCnt = 0;
+}
+
+PowerManager::PowerManager(){
+ m_bt_task_handle = nullptr;
+ bt_link_callback_ = nullptr;
+}
+
+void PowerManager::Initialize(){
+ // 鍒濆鍖�5V鎺у埗寮曡剼
+ gpio_config_t io_conf_5v = {
+ .pin_bit_mask = 1<<BOOT_5V_PIN,
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&io_conf_5v));
+
+ // 鍒濆鍖�4G鎺у埗寮曡剼
+ gpio_config_t io_conf_4g = {
+ .pin_bit_mask = (1<<BOOT_4G5V_PIN) | (1<<BOOT_4GEN_PIN),
+ .mode = GPIO_MODE_OUTPUT,
+ .pull_up_en = GPIO_PULLUP_DISABLE,
+ .pull_down_en = GPIO_PULLDOWN_ENABLE,
+ .intr_type = GPIO_INTR_DISABLE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&io_conf_4g));
+
+ // 鐢垫睜鐢甸噺鐩戞祴寮曡剼閰嶇疆
+ gpio_config_t io_conf_batt_mon = {
+ .pin_bit_mask = 1ull<<MON_BATT_PIN,
+ .mode = GPIO_MODE_INPUT,
+ .pull_up_en = GPIO_PULLUP_ENABLE,
+ .pull_down_en = GPIO_PULLDOWN_DISABLE,
+ .intr_type = GPIO_INTR_POSEDGE,
+ };
+ ESP_ERROR_CHECK(gpio_config(&io_conf_batt_mon));
+ // 鍒涘缓鐢甸噺GPIO浜嬩欢闃熷垪
+ gpio_evt_queue = xQueueCreate(2, sizeof(uint32_t));
+ // 瀹夎鐢甸噺GPIO ISR鏈嶅姟
+ ESP_ERROR_CHECK(gpio_install_isr_service(0));
+ // 娣诲姞涓柇澶勭悊
+ ESP_ERROR_CHECK(gpio_isr_handler_add(MON_BATT_PIN, batt_mon_isr_handler, (void*)MON_BATT_PIN));
+ // 鍒涘缓鐩戞帶浠诲姟
+ xTaskCreate(&batt_mon_task, "batt_mon_task", 1024, NULL, 10, NULL);
+
+ // 鍒濆鍖栫洃娴嬪紩鑴�
+ gpio_config_t mon_conf = {};
+ mon_conf.pin_bit_mask = 1ULL << MON_USB_PIN;
+ mon_conf.mode = GPIO_MODE_INPUT;
+ mon_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ mon_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ gpio_config(&mon_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+}
+
+void PowerManager::CheckBatteryStatus(){
+ call_count_++;
+ if(call_count_ >= MON_BATT_CNT) {
+ calBattLife();
+ call_count_ = 0;
+ }
+
+ bool new_charging_status = IsCharging();
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (charging_callback_) {
+ charging_callback_(is_charging_);
+ }
+ }
+
+ bool new_discharging_status = IsDischarging();
+ if (new_discharging_status != is_discharging_) {
+ is_discharging_ = new_discharging_status;
+ if (discharging_callback_) {
+ discharging_callback_(is_discharging_);
+ }
+ }
+}
+
+bool PowerManager::IsCharging() {
+ return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone();
+}
+
+bool PowerManager::IsDischarging() {
+ return gpio_get_level(MON_USB_PIN) == 0;
+}
+
+bool PowerManager::IsChargingDone() {
+ return battLife >= 95;
+}
+
+int PowerManager::GetBatteryLevel() {
+ return battLife;
+}
+
+void PowerManager::OnChargingStatusChanged(std::function<void(bool)> callback) {
+ charging_callback_ = callback;
+}
+
+void PowerManager::OnChargingStatusDisChanged(std::function<void(bool)> callback) {
+ discharging_callback_ = callback;
+}
+
+void PowerManager::CheckStartup() {
+ Settings settings1("board", true);
+ if(settings1.GetInt("sleep_flag", 0) > 0){
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ if( gpio_get_level(BOOT_BUTTON_PIN) == 1) {
+ Sleep(); //杩涘叆浼戠湢妯″紡
+ }else{
+ settings1.SetInt("sleep_flag", 0);
+ }
+ }
+}
+
+void PowerManager::Start5V() {
+ gpio_set_level(BOOT_5V_PIN, 1);
+}
+
+void PowerManager::Shutdown5V() {
+ gpio_set_level(BOOT_5V_PIN, 0);
+}
+
+void PowerManager::Start4G() {
+ gpio_set_level(BOOT_4G5V_PIN, 1);
+}
+
+void PowerManager::Shutdown4G() {
+ gpio_set_level(BOOT_4G5V_PIN, 0);
+ gpio_set_level(ML307_RX_PIN,1);
+ gpio_set_level(ML307_TX_PIN,1);
+}
+
+void PowerManager::Enable4G() {
+ gpio_set_level(BOOT_4GEN_PIN, 1);
+}
+
+void PowerManager::Disable4G() {
+ gpio_set_level(BOOT_4GEN_PIN, 0);
+}
+
+void PowerManager::Sleep() {
+ ESP_LOGI(TAG, "Entering deep sleep");
+ Settings settings("board", true);
+ settings.SetInt("sleep_flag", 1);
+ Disable4G();
+ Shutdown4G();
+ Shutdown5V();
+
+ if(gpio_evt_queue) {
+ vQueueDelete(gpio_evt_queue);
+ gpio_evt_queue = NULL;
+ }
+ ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN));
+ ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0));
+ ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN));
+ ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN));
+ esp_deep_sleep_start();
+}
+
+void PowerManager::InitializeBtModul() {
+ if (MON_BTLINK_PIN == GPIO_NUM_NC) {
+ ESP_LOGW(TAG, "MON_BTLINK_PIN not configured, skipping GPIO polling setup");
+ return;
+ }
+
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.pin_bit_mask = (1ULL << MON_BTLINK_PIN);
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ if (gpio_config(&io_conf) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to configure GPIO %d", MON_BTLINK_PIN);
+ return;
+ }
+ BaseType_t ret = xTaskCreate(BtTask, "bt_task", 2048, this, 10, &m_bt_task_handle);
+ if (ret != pdPASS) {
+ ESP_LOGE(TAG, "Failed to create BT task");
+ return;
+ }
+}
+
+void PowerManager::DeinitBtModul() {
+ if (MON_BTLINK_PIN == GPIO_NUM_NC) {
+ return;
+ }
+
+ if (m_bt_task_handle != nullptr) {
+ vTaskDelete(m_bt_task_handle);
+ m_bt_task_handle = nullptr;
+ }
+
+ gpio_reset_pin(MON_BTLINK_PIN);
+}
+
+void PowerManager::BtTask(void *arg) {
+ PowerManager *instance = static_cast<PowerManager *>(arg);
+ int last_level = -1;
+ for (;;) {
+ vTaskDelay(pdMS_TO_TICKS(100));
+ int level = gpio_get_level(MON_BTLINK_PIN);
+ if (level != last_level) {
+ last_level = level;
+ if (level == 1) {
+ ESP_LOGI(TAG, "BTLINK %d high - BT connected", MON_BTLINK_PIN);
+ if (instance->bt_link_callback_) {
+ instance->bt_link_callback_(true);
+ }
+ } else {
+ ESP_LOGI(TAG, "BTLINK %d low - BT disconnected", MON_BTLINK_PIN);
+ if (instance->bt_link_callback_) {
+ instance->bt_link_callback_(false);
+ }
+ }
+ }
+ }
+ vTaskDelete(NULL);
+}
+
+void PowerManager::OnBtLinkStatusChanged(std::function<void(bool)> callback) {
+ bt_link_callback_ = callback;
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.h
new file mode 100644
index 0000000..47139d7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/power_manager.h
@@ -0,0 +1,45 @@
+#ifndef __POWERMANAGER_H__
+#define __POWERMANAGER_H__
+
+#include <functional>
+#include "driver/gpio.h"
+#include "freertos/FreeRTOS.h"
+#include "freertos/task.h"
+#include "freertos/queue.h"
+#include "freertos/timers.h"
+
+class PowerManager{
+public:
+ PowerManager();
+ void Initialize();
+ bool IsCharging();
+ bool IsDischarging();
+ bool IsChargingDone();
+ int GetBatteryLevel();
+ void CheckStartup();
+ void Start5V();
+ void Shutdown5V();
+ void Start4G();
+ void Shutdown4G();
+ void Enable4G();
+ void Disable4G();
+ void Sleep();
+ void CheckBatteryStatus();
+ void OnChargingStatusChanged(std::function<void(bool)> callback);
+ void OnChargingStatusDisChanged(std::function<void(bool)> callback);
+ void OnBtLinkStatusChanged(std::function<void(bool)> callback);
+ void InitializeBtModul();
+ void DeinitBtModul();
+private:
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> charging_callback_;
+ std::function<void(bool)> discharging_callback_;
+ std::function<void(bool)> bt_link_callback_;
+ int is_charging_ = -1;
+ int is_discharging_ = -1;
+ int call_count_ = 0;
+ TaskHandle_t m_bt_task_handle;
+ static void BtTask(void *arg);
+};
+
+#endif
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/yunliao_s3.cc b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/yunliao_s3.cc
new file mode 100644
index 0000000..56f57bf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/yunliao-s3/yunliao_s3.cc
@@ -0,0 +1,295 @@
+#include <esp_lcd_panel_vendor.h>
+#include <esp_log.h>
+#include "settings.h"
+#include "application.h"
+#include "assets/lang_config.h"
+#include "button.h"
+#include "codecs/es8388_audio_codec.h"
+#include "config.h"
+#include "display/lcd_display.h"
+#include "dual_network_board.h"
+#include "lvgl_theme.h"
+#include "power_manager.h"
+#include "power_save_timer.h"
+#include "mcp_server.h"
+
+#define TAG "YunliaoS3"
+
+class YunliaoDisplay : public SpiLcdDisplay {
+public:
+ YunliaoDisplay(esp_lcd_panel_io_handle_t io_handle,
+ esp_lcd_panel_handle_t panel_handle,
+ int width,
+ int height,
+ int offset_x,
+ int offset_y,
+ bool mirror_x,
+ bool mirror_y,
+ bool swap_xy)
+ : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
+ }
+
+ virtual void SetupUI() override {
+ SpiLcdDisplay::SetupUI();
+ auto& theme_manager = LvglThemeManager::GetInstance();
+ auto theme = theme_manager.GetTheme("dark");
+ if (theme != nullptr) {
+ SetTheme(theme);
+ }
+ }
+};
+
+class YunliaoS3 : public DualNetworkBoard {
+ private:
+ i2c_master_bus_handle_t codec_i2c_bus_;
+ Button boot_button_;
+ YunliaoDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+
+ void InitializePowerSaveTimer() {
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 600);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(10);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->OnShutdownRequest([this]() {
+ ESP_LOGI(TAG, "Shutting down");
+ power_manager_->Sleep();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ 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_PIN_MOSI;
+ buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO;
+ buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK;
+ 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(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ app.ToggleChatState();
+ });
+ boot_button_.OnDoubleClick([this]() {
+ ESP_LOGI(TAG, "Button OnDoubleClick");
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting ||
+ app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }else if(app.GetDeviceState() == kDeviceStateIdle){
+ bool enableAec = app.GetAecMode() == kAecOff;
+ SetAecMode(enableAec);
+ Settings settings("aec", true);
+ settings.SetInt("mode", enableAec);
+ }
+ });
+ boot_button_.OnMultipleClick(
+ [this]() {
+ ESP_LOGI(TAG, "Button OnThreeClick");
+ if (GetNetworkType() == NetworkType::WIFI) {
+ auto& wifi_board =
+ static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ }
+ }, 3);
+ boot_button_.OnLongPress([this]() {
+ ESP_LOGI(TAG, "Button LongPress to Sleep");
+ display_->SetStatus(Lang::Strings::PLEASE_WAIT);
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ power_manager_->Sleep();
+ });
+ }
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS;
+ io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC;
+ io_config.spi_mode = 3;
+ io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ;
+ 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(DISPLAY_SPI_LCD_HOST,
+ &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ Settings settings("display", false);
+ bool currentIpsMode = settings.GetBool("ips_mode", DISPLAY_INVERT_COLOR);
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST;
+ panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR;
+ 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, currentIpsMode);
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
+ display_ = new YunliaoDisplay(panel_io, panel, DISPLAY_WIDTH,
+ DISPLAY_HEIGHT, DISPLAY_OFFSET_X,
+ DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
+ DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ }
+ void InitializeTools(){
+ auto& mcp_server = McpServer::GetInstance();
+
+ mcp_server.AddTool("self.system.set_aec",
+ "Enable or disable voice interruption mode (AEC:Acoustic Echo Cancellation). When enabled, the device can detect voice interruptions and respond accordingly.",
+ PropertyList({
+ Property("enable", kPropertyTypeBoolean)
+ }), [this](const PropertyList& properties) {
+ bool enable = properties["enable"].value<bool>();
+ SetAecMode(enable);
+ Settings settings("aec", true);
+ settings.SetInt("mode", enable);
+ return true;
+ });
+
+ mcp_server.AddTool("self.system.switch_TFT",
+ "Switch TFT display mode between normal and inverted colors. This will toggle the IPS mode and reboot the device.",
+ PropertyList(), [this](const PropertyList& properties) {
+ SwitchTFT();
+ return true;
+ });
+ }
+ void SetAecMode(bool enable) {
+ AecMode newMode = enable ? kAecOnDeviceSide : kAecOff;
+ auto& app = Application::GetInstance();
+ app.StopListening();
+ app.SetDeviceState(kDeviceStateIdle);
+ app.SetAecMode(newMode);
+ }
+ void SwitchTFT() {
+ Settings settings("display", true);
+ bool currentIpsMode = settings.GetBool("ips_mode", false);
+ settings.SetBool("ips_mode", !currentIpsMode);
+ ESP_LOGI(TAG, "IPS mode toggled to %s", !currentIpsMode ? "enabled" : "disabled");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ auto& app = Application::GetInstance();
+ app.Reboot();
+ }
+
+ public:
+ YunliaoS3()
+ : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
+ boot_button_(BOOT_BUTTON_PIN),
+ power_manager_(new PowerManager()) {
+ power_manager_->Start5V();
+ power_manager_->Initialize();
+ InitializeI2c();
+ power_manager_->CheckStartup();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeSt7789Display();
+ power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
+ if (power_save_timer_) {
+ if (is_discharging) {
+ power_save_timer_->SetEnabled(true);
+ } else {
+ power_save_timer_->SetEnabled(false);
+ }
+ }
+ });
+ GetBacklight()->RestoreBrightness();
+ while (gpio_get_level(BOOT_BUTTON_PIN) == 0) {
+ vTaskDelay(pdMS_TO_TICKS(10));
+ }
+ InitializeButtons();
+ Settings settings("aec", false);
+ auto& app = Application::GetInstance();
+ app.SetAecMode(settings.GetInt("mode",kAecOnDeviceSide) == kAecOnDeviceSide ? kAecOnDeviceSide : kAecOff);
+ power_manager_->Start4G();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ power_manager_->Disable4G();
+ }else{
+ power_manager_->Enable4G();
+ }
+ power_manager_->OnBtLinkStatusChanged([this](bool is_connected) {
+ auto& app = Application::GetInstance();
+ if (is_connected) {
+ if (app.GetAecMode() != kAecOff) {
+ SetAecMode(false);
+ }
+ } else {
+ Settings settings("aec", false);
+ int storedMode = settings.GetInt("mode", kAecOnDeviceSide);
+ if (storedMode != kAecOff && app.GetAecMode() == kAecOff) {
+ SetAecMode(storedMode == kAecOnDeviceSide);
+ }
+ }
+ });
+ power_manager_->InitializeBtModul();
+ InitializeTools();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static Es8388AudioCodec 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_ES8388_ADDR, AUDIO_INPUT_REFERENCE);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging,
+ bool& discharging) override {
+ level = power_manager_->GetBatteryLevel();
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(YunliaoS3);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/README.md b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/README.md
new file mode 100644
index 0000000..a7f289b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/README.md
@@ -0,0 +1,45 @@
+# 浜у搧鐩稿叧浠嬬粛缃戝潃
+
+```http
+https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356
+```
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> zhengchen-1.54tft-ml307
+```
+
+```
+
+**缂栬瘧锛�**
+
+bash
+idf.py build
+```
+
+**涓嬭浇锛�**
+idf.py build flash monitor
+
+杩涜涓嬭浇鍜屾樉绀烘棩蹇�
+
+
+**鍥轰欢鐢熸垚锛�**
+
+```bash
+idf.py merge-bin
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.h
new file mode 100644
index 0000000..888a052
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.h
@@ -0,0 +1,42 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_10
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA GPIO_NUM_41
+#define DISPLAY_SCL GPIO_NUM_42
+#define DISPLAY_RES GPIO_NUM_45
+#define DISPLAY_DC GPIO_NUM_40
+#define DISPLAY_CS GPIO_NUM_21
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+#define ML307_RX_PIN GPIO_NUM_11
+#define ML307_TX_PIN GPIO_NUM_12
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.json b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.json
new file mode 100644
index 0000000..6e17be7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "zhengchen-1.54tft-ml307",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc
new file mode 100644
index 0000000..e056371
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-ml307/zhengchen-1.54tft-ml307.cc
@@ -0,0 +1,211 @@
+#include "dual_network_board.h"
+#include "codecs/no_audio_codec.h"
+#include "../zhengchen-1.54tft-wifi/zhengchen_lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "../zhengchen-1.54tft-wifi/power_manager.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "ZHENGCHEN_1_54TFT_ML307"
+
+class ZHENGCHEN_1_54TFT_ML307 : public DualNetworkBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ ZHENGCHEN_LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_9);
+ power_manager_->OnTemperatureChanged([this](float chip_temp) {
+ display_->UpdateHighTempWarning(chip_temp);
+ });
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ } else {
+ power_save_timer_->SetEnabled(true);
+ }
+ });
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_2);
+ rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_2, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ return;
+ }
+ }
+ app.ToggleChatState();
+ });
+
+ boot_button_.OnLongPress([this]() {
+ SwitchNetworkType();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+
+ display_ = new ZHENGCHEN_LcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
+ DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ display_->SetupHighTempWarningPopup();
+ }
+
+public:
+ ZHENGCHEN_1_54TFT_ML307() :
+ DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = std::max<uint32_t>(power_manager_->GetBatteryLevel(), 20);
+ return true;
+ }
+
+ virtual bool GetTemperature(float& esp32temp) override {
+ esp32temp = power_manager_->GetTemperature();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ DualNetworkBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(ZHENGCHEN_1_54TFT_ML307);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/README.md b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/README.md
new file mode 100644
index 0000000..20bd9a6
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/README.md
@@ -0,0 +1,45 @@
+# 浜у搧鐩稿叧浠嬬粛缃戝潃
+
+```http
+https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356
+```
+
+# 缂栬瘧閰嶇疆鍛戒护
+
+**閰嶇疆缂栬瘧鐩爣涓� ESP32S3锛�**
+
+```bash
+idf.py set-target esp32s3
+```
+
+**鎵撳紑 menuconfig锛�**
+
+```bash
+idf.py menuconfig
+```
+
+**閫夋嫨鏉垮瓙锛�**
+
+```
+Xiaozhi Assistant -> Board Type -> zhengchen-1.54tft-wifi
+```
+
+```
+
+**缂栬瘧锛�**
+
+bash
+idf.py build
+```
+
+**涓嬭浇锛�**
+idf.py build flash monitor
+
+杩涜涓嬭浇鍜屾樉绀烘棩蹇�
+
+
+**鍥轰欢鐢熸垚锛�**
+
+```bash
+idf.py merge-bin
+```
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.h
new file mode 100644
index 0000000..f7d971a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.h
@@ -0,0 +1,39 @@
+
+#ifndef _BOARD_CONFIG_H_
+#define _BOARD_CONFIG_H_
+
+#include <driver/gpio.h>
+
+#define AUDIO_INPUT_SAMPLE_RATE 16000
+#define AUDIO_OUTPUT_SAMPLE_RATE 24000
+#define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4
+#define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5
+#define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6
+#define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7
+#define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15
+#define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16
+
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_10
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39
+
+#define DISPLAY_SDA GPIO_NUM_41
+#define DISPLAY_SCL GPIO_NUM_42
+#define DISPLAY_RES GPIO_NUM_45
+#define DISPLAY_DC GPIO_NUM_40
+#define DISPLAY_CS GPIO_NUM_21
+
+#define DISPLAY_WIDTH 240
+#define DISPLAY_HEIGHT 240
+#define DISPLAY_SWAP_XY false
+#define DISPLAY_MIRROR_X false
+#define DISPLAY_MIRROR_Y false
+#define BACKLIGHT_INVERT false
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_20
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
+
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.json b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.json
new file mode 100644
index 0000000..3c17d11
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/config.json
@@ -0,0 +1,9 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "zhengchen-1.54tft-wifi",
+ "sdkconfig_append": []
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/power_manager.h
new file mode 100644
index 0000000..de6e82d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/power_manager.h
@@ -0,0 +1,238 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <driver/temperature_sensor.h>
+#include "application.h"
+#include "zhengchen_lcd_display.h"
+
+class PowerManager {
+private:
+ // 瀹氭椂鍣ㄥ彞鏌�
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ std::function<void(float)> on_temperature_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ float current_temperature_ = 0.0f;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+ const int kTemperatureReadInterval = 10; // 姣� 10 绉掕鍙栦竴娆℃俯搴�
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ temperature_sensor_handle_t temp_sensor_ = NULL;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+
+ // 鏂板锛氬懆鏈熸�ц鍙栨俯搴�
+ if (ticks_ % kTemperatureReadInterval == 0) {
+ ReadTemperature();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ // 璇诲彇 ADC 鍊�
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_7, &adc_value));
+
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += (value + 80);
+ }
+ average_adc /= adc_values_.size();
+
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2030, 0},
+ {2134, 20},
+ {2252, 40},
+ {2370, 60},
+ {2488, 80},
+ {2606, 100}
+ };
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+ // 妫�鏌ユ槸鍚﹁揪鍒颁綆鐢甸噺闃堝��
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+ void ReadTemperature() {
+ float temperature = 0.0f;
+ ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor_, &temperature));
+
+ if (abs(temperature - current_temperature_) >= 3.5f) { // 娓╁害鍙樺寲瓒呰繃3.5掳C鎵嶈Е鍙戝洖璋�
+ current_temperature_ = temperature;
+ if (on_temperature_changed_) {
+ on_temperature_changed_(current_temperature_);
+ }
+ ESP_LOGI("PowerManager", "Temperature updated: %.1f掳C", current_temperature_);
+ }
+ }
+
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_7, &chan_config));
+
+ // 鍒濆鍖栨俯搴︿紶鎰熷櫒
+ temperature_sensor_config_t temp_config = {
+ .range_min = 10,
+ .range_max = 80,
+ .clk_src = TEMPERATURE_SENSOR_CLK_SRC_DEFAULT
+ };
+ ESP_ERROR_CHECK(temperature_sensor_install(&temp_config, &temp_sensor_));
+ ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor_));
+ ESP_LOGI("PowerManager", "Temperature sensor initialized (new driver)");
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+
+ if (temp_sensor_) {
+ temperature_sensor_disable(temp_sensor_);
+ temperature_sensor_uninstall(temp_sensor_);
+ }
+
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ // 鑾峰彇鐢垫睜鐢甸噺
+ uint8_t GetBatteryLevel() {
+ // 杩斿洖鐢垫睜鐢甸噺
+ return battery_level_;
+ }
+
+ float GetTemperature() const { return current_temperature_; } // 鑾峰彇褰撳墠娓╁害
+
+ void OnTemperatureChanged(std::function<void(float)> callback) {
+ on_temperature_changed_ = callback;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc
new file mode 100644
index 0000000..5f7c305
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen-1.54tft-wifi.cc
@@ -0,0 +1,228 @@
+#include "wifi_board.h"
+#include "codecs/no_audio_codec.h"
+#include "zhengchen_lcd_display.h"
+#include "system_reset.h"
+#include "application.h"
+#include "button.h"
+#include "config.h"
+#include "power_save_timer.h"
+#include "led/single_led.h"
+#include "assets/lang_config.h"
+#include "power_manager.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+
+#include <driver/rtc_io.h>
+#include <esp_sleep.h>
+
+#define TAG "ZHENGCHEN_1_54TFT_WIFI"
+
+class ZHENGCHEN_1_54TFT_WIFI : public WifiBoard {
+private:
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ ZHENGCHEN_LcdDisplay* display_;
+ PowerSaveTimer* power_save_timer_;
+ PowerManager* power_manager_;
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ void InitializePowerManager() {
+ power_manager_ = new PowerManager(GPIO_NUM_9);
+ power_manager_->OnTemperatureChanged([this](float chip_temp) {
+ display_->UpdateHighTempWarning(chip_temp);
+ });
+
+ power_manager_->OnChargingStatusChanged([this](bool is_charging) {
+ if (is_charging) {
+ power_save_timer_->SetEnabled(false);
+ ESP_LOGI("PowerManager", "Charging started");
+ } else {
+ power_save_timer_->SetEnabled(true);
+ ESP_LOGI("PowerManager", "Charging stopped");
+ }
+ });
+
+ }
+
+ void InitializePowerSaveTimer() {
+ rtc_gpio_init(GPIO_NUM_2);
+ rtc_gpio_set_direction(GPIO_NUM_2, RTC_GPIO_MODE_OUTPUT_ONLY);
+ rtc_gpio_set_level(GPIO_NUM_2, 1);
+
+ power_save_timer_ = new PowerSaveTimer(-1, 60, 300);
+ power_save_timer_->OnEnterSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(true);
+ GetBacklight()->SetBrightness(1);
+ });
+ power_save_timer_->OnExitSleepMode([this]() {
+ GetDisplay()->SetPowerSaveMode(false);
+ GetBacklight()->RestoreBrightness();
+ });
+ power_save_timer_->SetEnabled(true);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = DISPLAY_SDA;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = DISPLAY_SCL;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+
+ boot_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ // 璁剧疆寮�鏈烘寜閽殑闀挎寜浜嬩欢锛堢洿鎺ヨ繘鍏ラ厤缃戞ā寮忥級
+ boot_button_.OnLongPress([this]() {
+ // 鍞ら啋鐢垫簮淇濆瓨瀹氭椂鍣�
+ power_save_timer_->WakeUp();
+ // 鑾峰彇搴旂敤绋嬪簭瀹炰緥
+ auto& app = Application::GetInstance();
+
+ // 杩涘叆閰嶇綉妯″紡
+ app.SetDeviceState(kDeviceStateWifiConfiguring);
+
+ // 閲嶇疆WiFi閰嶇疆浠ョ‘淇濊繘鍏ラ厤缃戞ā寮�
+ EnterWifiConfigMode();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ power_save_timer_->WakeUp();
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ power_save_timer_->WakeUp();
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = DISPLAY_CS;
+ io_config.dc_gpio_num = DISPLAY_DC;
+ io_config.spi_mode = 3;
+ 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(SPI3_HOST, &io_config, &panel_io_));
+
+ ESP_LOGD(TAG, "Install LCD driver");
+ esp_lcd_panel_dev_config_t panel_config = {};
+ panel_config.reset_gpio_num = DISPLAY_RES;
+ 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_ERROR_CHECK(esp_lcd_panel_reset(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_init(panel_));
+ ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY));
+ ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y));
+ ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true));
+
+ display_ = new ZHENGCHEN_LcdDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y,
+ DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
+ display_->SetupHighTempWarningPopup();
+ }
+
+ void InitializeTools() {
+ }
+
+public:
+ ZHENGCHEN_1_54TFT_WIFI() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializePowerManager();
+ InitializePowerSaveTimer();
+ InitializeSpi();
+ InitializeButtons();
+ InitializeSt7789Display();
+ InitializeTools();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ // 鑾峰彇闊抽缂栬В鐮佸櫒
+ virtual AudioCodec* GetAudioCodec() override {
+ // 闈欐�佸疄渚嬪寲NoAudioCodecSimplex绫�
+ static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE,
+ AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN);
+ // 杩斿洖闊抽缂栬В鐮佸櫒
+ 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;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ power_save_timer_->SetEnabled(discharging);
+ last_discharging = discharging;
+ }
+ level = std::max<uint32_t>(power_manager_->GetBatteryLevel(), 20);
+ return true;
+ }
+
+ virtual bool GetTemperature(float& esp32temp) override {
+ esp32temp = power_manager_->GetTemperature();
+ return true;
+ }
+
+ virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
+ if (level != PowerSaveLevel::LOW_POWER) {
+ power_save_timer_->WakeUp();
+ }
+ WifiBoard::SetPowerSaveLevel(level);
+ }
+};
+
+DECLARE_BOARD(ZHENGCHEN_1_54TFT_WIFI);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h
new file mode 100644
index 0000000..fa6222a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-1.54tft-wifi/zhengchen_lcd_display.h
@@ -0,0 +1,66 @@
+#ifndef ZHENGCHEN_LCD_DISPLAY_H
+#define ZHENGCHEN_LCD_DISPLAY_H
+
+#include "display/lcd_display.h"
+#include "lvgl_theme.h"
+#include <esp_lvgl_port.h>
+
+class ZHENGCHEN_LcdDisplay : public SpiLcdDisplay {
+protected:
+ lv_obj_t* high_temp_popup_ = nullptr; // 楂樻俯璀﹀憡寮圭獥
+ lv_obj_t* high_temp_label_ = nullptr; // 楂樻俯璀﹀憡鏍囩
+
+public:
+ // 缁ф壙鏋勯�犲嚱鏁�
+ using SpiLcdDisplay::SpiLcdDisplay;
+
+ void SetupHighTempWarningPopup() {
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ // 鍒涘缓楂樻俯璀﹀憡寮圭獥
+ high_temp_popup_ = lv_obj_create(lv_screen_active()); // 浣跨敤褰撳墠灞忓箷
+ lv_obj_set_scrollbar_mode(high_temp_popup_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_size(high_temp_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2);
+ lv_obj_align(high_temp_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
+ lv_obj_set_style_bg_color(high_temp_popup_, lv_palette_main(LV_PALETTE_RED), 0);
+ lv_obj_set_style_radius(high_temp_popup_, 10, 0);
+
+ // 鍒涘缓璀﹀憡鏍囩
+ high_temp_label_ = lv_label_create(high_temp_popup_);
+ lv_label_set_text(high_temp_label_, "璀﹀憡锛氭俯搴﹁繃楂�");
+ lv_obj_set_style_text_color(high_temp_label_, lv_color_white(), 0);
+ lv_obj_center(high_temp_label_);
+
+ // 榛樿闅愯棌
+ lv_obj_add_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
+ }
+
+ void UpdateHighTempWarning(float chip_temp, float threshold = 75.0f) {
+ if (high_temp_popup_ == nullptr) {
+ ESP_LOGW("ZHENGCHEN_LcdDisplay", "High temp popup not initialized!");
+ return;
+ }
+
+ if (chip_temp >= threshold) {
+ ShowHighTempWarning();
+ } else {
+ HideHighTempWarning();
+ }
+ }
+
+ void ShowHighTempWarning() {
+ if (high_temp_popup_ && lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) {
+ lv_obj_remove_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
+ }
+ }
+
+ void HideHighTempWarning() {
+ if (high_temp_popup_ && !lv_obj_has_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN)) {
+ lv_obj_add_flag(high_temp_popup_, LV_OBJ_FLAG_HIDDEN);
+ }
+ }
+};
+
+
+
+#endif // ZHENGCHEN_LCD_DISPLAY_H
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/README.md b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/README.md
new file mode 100644
index 0000000..d8f1fdd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/README.md
@@ -0,0 +1,50 @@
+# 浜у搧鐩稿叧浠嬬粛缃戝潃
+# 寰佽景绉戞妧 AI camera + 4G
+
+## 绠�浠�
+寰佽景绉戞妧 AI camera鏄皬鏅篈I鐨勯瓟鏀归」鐩紝鍋氫簡澶ч噺鍒涙柊鍜屼紭鍖栥��
+
+## 鍚堝苟鐗�
+鍚堝苟鐗堜唬鐮佸湪灏忔櫤AI涓婚」鐩腑缁存姢锛岃窡闅忎富椤圭洰鐨勪竴璧风増鏈洿鏂帮紝渚夸簬鐢ㄦ埛鑷鎵╁睍鍜岀涓夋柟鍥轰欢鎵╁睍銆傛敮鎸佽闊冲敜閱掋�佽闊虫墦鏂�丱TA绛夊姛鑳姐��
+
+## 榄旀敼鐗�
+榄旀敼鐗堢敱浜庡簳灞傛敼鍔ㄥお澶э紝浠g爜鍗曠嫭缁存姢锛屽畾鏈熷悎骞朵富椤圭洰浠g爜銆�
+
+https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356
+```
+銆愭窐瀹濄�� 銆屽皬鏅篈I甯︽憚鍍忓ご鏀寔璇嗙墿鍙岄害鍏嬮鎵撴柇 ESP32S3N16R8寮�鍙戞澘琛ㄦ儏鍖呫��
+https://e.tb.cn/h.hBc8Gcx9cUluJJO?tk=YW5C4LPixKg
+
+
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+鐢变簬姝ら」鐩渶瑕侀厤缃緝澶氱殑 sdkconfig 閫夐」锛屾帹鑽愪娇鐢ㄧ紪璇戣剼鏈紪璇戙��
+
+**缂栬瘧**
+
+```bash
+python ./scripts/release.py zhengchen-cam-ml307
+```
+
+濡傞渶鎵嬪姩缂栬瘧锛岃鍙傝�� `zhengchen-cam-ml307/config.json` 淇敼 menuconfig 瀵瑰簲閫夐」銆�
+
+**鐑у綍**
+
+```bash
+idf.py flash
+
+
+```
+
+MCP Tool锛�
+self.get_device_status
+self.audio_speaker.set_volume
+self.screen.set_brightness
+self.screen.set_theme
+self.gif.set_gif_mode
+self.display.set_mode
+self.camera.take_photo
+self.AEC.set_mode
+self.AEC.get_mode
+self.res.esp_restart
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.h
new file mode 100644
index 0000000..7861fb3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.h
@@ -0,0 +1,70 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_13
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
+
+#define AUDIO_CODEC_USE_PCA9557
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR 0x82
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_3
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_46
+
+#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_WIDTH_1 240
+#define DISPLAY_HEIGHT_1 320
+#define DISPLAY_MIRROR_X_1 false
+#define DISPLAY_MIRROR_Y_1 false
+#define DISPLAY_SWAP_XY_1 false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN -1
+#define CAMERA_PIN_RESET -1
+#define CAMERA_PIN_XCLK 17
+#define CAMERA_PIN_SIOD 1
+#define CAMERA_PIN_SIOC 2
+
+#define CAMERA_PIN_D7 15
+#define CAMERA_PIN_D6 11
+#define CAMERA_PIN_D5 9
+#define CAMERA_PIN_D4 8
+#define CAMERA_PIN_D3 6
+#define CAMERA_PIN_D2 5
+#define CAMERA_PIN_D1 4
+#define CAMERA_PIN_D0 7
+#define CAMERA_PIN_VSYNC 21
+#define CAMERA_PIN_HREF 18
+#define CAMERA_PIN_PCLK 16
+
+#define XCLK_FREQ_HZ 24000000
+
+#define ML307_RX_PIN GPIO_NUM_44
+#define ML307_TX_PIN GPIO_NUM_43
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.json b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.json
new file mode 100644
index 0000000..3dfed8c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "zhengchen-cam-ml307",
+ "sdkconfig_append": [
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=48",
+ "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32",
+ "CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=n",
+ "CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y",
+ "CONFIG_LV_USE_FONT_COMPRESSED=y",
+ "CONFIG_LV_USE_FONT_PLACEHOLDER=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/mcp_controller.cc b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/mcp_controller.cc
new file mode 100644
index 0000000..0f7f073
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/mcp_controller.cc
@@ -0,0 +1,94 @@
+#include <cJSON.h>
+#include <esp_log.h>
+
+#include <cstring>
+
+#include "application.h"
+#include "board.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "sdkconfig.h"
+#include "settings.h"
+#include "display.h"
+
+#define TAG "MCPController"
+
+class MCPController {
+public:
+ MCPController() {
+ RegisterMcpTools();
+ ESP_LOGI(TAG, "娉ㄥ唽MCP宸ュ叿");
+ }
+
+ void RegisterMcpTools() {
+ auto& mcp_server = McpServer::GetInstance();
+ ESP_LOGI(TAG, "寮�濮嬫敞鍐孧CP宸ュ叿...");
+
+ mcp_server.AddTool(
+ "self.AEC.set_mode",
+ "璁剧疆AEC瀵硅瘽鎵撴柇妯″紡銆傚綋鐢ㄦ埛鎰忓浘鍒囨崲瀵硅瘽鎵撴柇妯″紡鏃舵垨鑰呯敤鎴疯寰梐i瀵硅瘽瀹规槗琚墦鏂椂鎴栬�呯敤鎴疯寰楁棤娉曞疄鐜板璇濇墦鏂椂閮戒娇鐢ㄦ宸ュ叿銆俓n"
+ "鍙傛暟锛歕n"
+ " `mode`: 瀵硅瘽鎵撴柇妯″紡锛屽彲閫夊�煎彧鏈塦kAecOff`(鍏抽棴锛夊拰`kAecOnDeviceSide`锛堝紑鍚級\n"
+ "杩斿洖鍊硷細\n"
+ " 鍙嶉鐘舵�佷俊鎭紝涓嶉渶瑕佺‘璁わ紝绔嬪嵆鎾姤鐩稿叧鏁版嵁\n",
+ PropertyList({
+ Property("mode", kPropertyTypeString)
+ }),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto mode = properties["mode"].value<std::string>();
+ auto& app = Application::GetInstance();
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ if (mode == "kAecOff") {
+ app.SetAecMode(kAecOff);
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡宸插叧闂璡"}";
+ }else {
+ auto& board = Board::GetInstance();
+ app.SetAecMode(kAecOnDeviceSide);
+
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡宸插紑鍚痋"}";
+ }
+ }
+ );
+
+ mcp_server.AddTool(
+ "self.AEC.get_mode",
+ "鑾峰彇AEC瀵硅瘽鎵撴柇妯″紡鐘舵�併�傚綋鐢ㄦ埛鎰忓浘鑾峰彇瀵硅瘽鎵撴柇妯″紡鐘舵�佹椂浣跨敤姝ゅ伐鍏枫�俓n"
+ "杩斿洖鍊硷細\n"
+ " 鍙嶉鐘舵�佷俊鎭紝涓嶉渶瑕佺‘璁わ紝绔嬪嵆鎾姤鐩稿叧鏁版嵁\n",
+ PropertyList(),
+ [](const PropertyList&) -> ReturnValue {
+ auto& app = Application::GetInstance();
+ const bool is_currently_off = (app.GetAecMode() == kAecOff);
+ if (is_currently_off) {
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡澶勪簬鍏抽棴鐘舵�乗"}";
+ }else {
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡澶勪簬寮�鍚姸鎬乗"}";
+ }
+ }
+ );
+
+ mcp_server.AddTool(
+ "self.res.esp_restart",
+ "閲嶅惎璁惧銆傚綋鐢ㄦ埛鎰忓浘閲嶅惎璁惧鏃朵娇鐢ㄦ宸ュ叿銆俓n",
+ PropertyList(),
+ [](const PropertyList&) -> ReturnValue {
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ // Reboot the device
+ esp_restart();
+ return true;
+ }
+ );
+
+ ESP_LOGI(TAG, "MCP宸ュ叿娉ㄥ唽瀹屾垚");
+ }
+
+};
+
+static MCPController* g_mcp_controller = nullptr;
+
+void InitializeMCPController() {
+ if (g_mcp_controller == nullptr) {
+ g_mcp_controller = new MCPController();
+ ESP_LOGI(TAG, "娉ㄥ唽MCP宸ュ叿");
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/power_manager.h
new file mode 100644
index 0000000..f9e31fd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/power_manager.h
@@ -0,0 +1,237 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <driver/temperature_sensor.h>
+#include "application.h"
+
+class PowerManager {
+private:
+ // 瀹氭椂鍣ㄥ彞鏌�
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ std::function<void(float)> on_temperature_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ float current_temperature_ = 0.0f;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+ const int kTemperatureReadInterval = 10; // 姣� 10 绉掕鍙栦竴娆℃俯搴�
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ temperature_sensor_handle_t temp_sensor_ = NULL;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+
+ // 鏂板锛氬懆鏈熸�ц鍙栨俯搴�
+ if (ticks_ % kTemperatureReadInterval == 0) {
+ ReadTemperature();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ // 璇诲彇 ADC 鍊�
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_9, &adc_value));
+
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += (value + 80);
+ }
+ average_adc /= adc_values_.size();
+
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2030, 0},
+ {2134, 20},
+ {2252, 40},
+ {2370, 60},
+ {2488, 80},
+ {2606, 100}
+ };
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+ // 妫�鏌ユ槸鍚﹁揪鍒颁綆鐢甸噺闃堝��
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+ void ReadTemperature() {
+ float temperature = 0.0f;
+ ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor_, &temperature));
+
+ if (abs(temperature - current_temperature_) >= 3.5f) { // 娓╁害鍙樺寲瓒呰繃3.5掳C鎵嶈Е鍙戝洖璋�
+ current_temperature_ = temperature;
+ if (on_temperature_changed_) {
+ on_temperature_changed_(current_temperature_);
+ }
+ ESP_LOGI("PowerManager", "Temperature updated: %.1f掳C", current_temperature_);
+ }
+ }
+
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_9, &chan_config));
+
+ // 鍒濆鍖栨俯搴︿紶鎰熷櫒
+ temperature_sensor_config_t temp_config = {
+ .range_min = 10,
+ .range_max = 80,
+ .clk_src = TEMPERATURE_SENSOR_CLK_SRC_DEFAULT
+ };
+ ESP_ERROR_CHECK(temperature_sensor_install(&temp_config, &temp_sensor_));
+ ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor_));
+ ESP_LOGI("PowerManager", "Temperature sensor initialized (new driver)");
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+
+ if (temp_sensor_) {
+ temperature_sensor_disable(temp_sensor_);
+ temperature_sensor_uninstall(temp_sensor_);
+ }
+
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ // 鑾峰彇鐢垫睜鐢甸噺
+ uint8_t GetBatteryLevel() {
+ // 杩斿洖鐢垫睜鐢甸噺
+ return battery_level_;
+ }
+
+ float GetTemperature() const { return current_temperature_; } // 鑾峰彇褰撳墠娓╁害
+
+ void OnTemperatureChanged(std::function<void(float)> callback) {
+ on_temperature_changed_ = callback;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/zhengchen_cam_board_ml307.cc b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/zhengchen_cam_board_ml307.cc
new file mode 100644
index 0000000..9d6f0cc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam-ml307/zhengchen_cam_board_ml307.cc
@@ -0,0 +1,340 @@
+#include "dual_network_board.h"
+#include "audio/codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "esp32_camera.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <wifi_manager.h>
+#include "power_manager.h"
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+#include "settings.h"
+
+#define FIRST_BOOT_NS "boot_config"
+#define FIRST_BOOT_KEY "is_first"
+
+
+#define TAG "ZhengchenCamBoard_ML307"
+
+//鎺у埗鍣ㄥ垵濮嬪寲鍑芥暟澹版槑
+void InitializeMCPController();
+
+LV_FONT_DECLARE(font_puhui_20_4);
+LV_FONT_DECLARE(font_awesome_20_4);
+
+class Pca9557 : public I2cDevice {
+public:
+ Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x01, 0x03);
+ WriteReg(0x03, 0xf8);
+ }
+
+ void SetOutputState(uint8_t bit, uint8_t level) {
+ uint8_t data = ReadReg(0x01);
+ data = (data & ~(1 << bit)) | (level << bit);
+ WriteReg(0x01, data);
+ }
+};
+
+class CustomAudioCodec : public BoxAudioCodec {
+private:
+ Pca9557* pca9557_;
+
+public:
+ CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557)
+ : BoxAudioCodec(i2c_bus,
+ 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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE),
+ pca9557_(pca9557) {
+ }
+
+ virtual void EnableOutput(bool enable) override {
+ BoxAudioCodec::EnableOutput(enable);
+ if (enable) {
+ pca9557_->SetOutputState(1, 1);
+ } else {
+ pca9557_->SetOutputState(1, 0);
+ }
+ }
+};
+
+class ZhengchenCamBoard_ML307 : public DualNetworkBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ i2c_master_dev_handle_t pca9557_handle_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ LcdDisplay* display_ = nullptr;
+ Pca9557* pca9557_;
+ Esp32Camera* camera_;
+ PowerManager* power_manager_ = new PowerManager(GPIO_NUM_47);
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+
+ // Initialize PCA9557
+ pca9557_ = new Pca9557(i2c_bus_, 0x19);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_40;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_41;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ if (GetNetworkType() == NetworkType::WIFI) {
+ if (app.GetDeviceState() == kDeviceStateStarting && !WifiManager::GetInstance().IsConnected()) {
+ // cast to WifiBoard
+ auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
+ wifi_board.EnterWifiConfigMode();
+ }
+ }
+
+ app.ToggleChatState();
+ });
+
+ boot_button_.OnDoubleClick([this]() {
+ Settings settings(FIRST_BOOT_NS, true);
+ bool is_first_boot = settings.GetInt(FIRST_BOOT_KEY, 1) != 0;
+ if (is_first_boot) {
+ ESP_LOGI(TAG, "棣栨鍚姩锛屽惎鐢ㄥ弻鍑绘媿鐓у姛鑳�");
+ auto camera = GetCamera();
+ if (!camera->Capture()) {
+ ESP_LOGE(TAG, "Camera capture failed");
+ }
+ settings.SetInt(FIRST_BOOT_KEY, 0);
+
+
+ } else {
+ ESP_LOGI(TAG, "闈為娆″惎鍔紝绂佺敤鍙屽嚮鎷嶇収鍔熻兘");
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ GetAudioCodec()->SetOutputVolume(60);
+ }
+ }
+ });
+
+ boot_button_.OnLongPress([this]() {
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
+ SwitchNetworkType();
+ }
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ codec->SetOutputVolume(volume);
+
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_NC;
+ io_config.dc_gpio_num = GPIO_NUM_39;
+ io_config.spi_mode = 0;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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);
+ pca9557_->SetOutputState(0, 0);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, true);
+
+ Settings settings("lcd_display", true);
+ bool is_landscape = settings.GetInt("lcd_mode", 1) != 0;
+
+ if(is_landscape) {
+ // 妯睆妯″紡
+ 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);
+
+ } else {
+ // 绔栧睆妯″紡
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY_1);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X_1, DISPLAY_MIRROR_Y_1);
+ display_ = new SpiLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH_1, DISPLAY_HEIGHT_1, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X_1, DISPLAY_MIRROR_Y_1, DISPLAY_SWAP_XY_1);
+ }
+
+ }
+
+ void InitializeCamera() {
+ // Open camera power
+ pca9557_->SetOutputState(2, 0);
+
+ camera_config_t config = {};
+ config.ledc_channel = LEDC_CHANNEL_2; // LEDC閫氶亾閫夋嫨 鐢ㄤ簬鐢熸垚XCLK鏃堕挓 浣嗘槸S3涓嶇敤
+ config.ledc_timer = LEDC_TIMER_2; // LEDC timer閫夋嫨 鐢ㄤ簬鐢熸垚XCLK鏃堕挓 浣嗘槸S3涓嶇敤
+ config.pin_d0 = CAMERA_PIN_D0;
+ config.pin_d1 = CAMERA_PIN_D1;
+ config.pin_d2 = CAMERA_PIN_D2;
+ config.pin_d3 = CAMERA_PIN_D3;
+ config.pin_d4 = CAMERA_PIN_D4;
+ config.pin_d5 = CAMERA_PIN_D5;
+ config.pin_d6 = CAMERA_PIN_D6;
+ config.pin_d7 = CAMERA_PIN_D7;
+ config.pin_xclk = CAMERA_PIN_XCLK;
+ config.pin_pclk = CAMERA_PIN_PCLK;
+ config.pin_vsync = CAMERA_PIN_VSYNC;
+ config.pin_href = CAMERA_PIN_HREF;
+ config.pin_sccb_sda = -1; // 杩欓噷鍐�-1 琛ㄧず浣跨敤宸茬粡鍒濆鍖栫殑I2C鎺ュ彛
+ config.pin_sccb_scl = CAMERA_PIN_SIOC;
+ config.sccb_i2c_port = 1;
+ config.pin_pwdn = CAMERA_PIN_PWDN;
+ config.pin_reset = CAMERA_PIN_RESET;
+ config.xclk_freq_hz = XCLK_FREQ_HZ;
+ config.pixel_format = PIXFORMAT_RGB565;
+ config.frame_size = FRAMESIZE_VGA;
+ config.jpeg_quality = 9;
+ config.fb_count = 1;
+ config.fb_location = CAMERA_FB_IN_PSRAM;
+ config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
+
+ camera_ = new Esp32Camera(config);
+ }
+
+ void InitializeController() { InitializeMCPController(); }
+
+public:
+ ZhengchenCamBoard_ML307() :
+ DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ InitializeCamera();
+ InitializeController();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static CustomAudioCodec audio_codec(
+ i2c_bus_,
+ pca9557_);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ last_discharging = discharging;
+ }
+ level = std::max<uint32_t>(power_manager_->GetBatteryLevel(), 20);
+ return true;
+ }
+
+ virtual bool GetTemperature(float& esp32temp) override {
+ esp32temp = power_manager_->GetTemperature();
+ return true;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(ZhengchenCamBoard_ML307);
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/README.md b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/README.md
new file mode 100644
index 0000000..919e52b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/README.md
@@ -0,0 +1,49 @@
+# 浜у搧鐩稿叧浠嬬粛缃戝潃
+
+## 绠�浠�
+寰佽景绉戞妧 AI camera鏄皬鏅篈I鐨勯瓟鏀归」鐩紝鍋氫簡澶ч噺鍒涙柊鍜屼紭鍖栥��
+
+## 鍚堝苟鐗�
+鍚堝苟鐗堜唬鐮佸湪灏忔櫤AI涓婚」鐩腑缁存姢锛岃窡闅忎富椤圭洰鐨勪竴璧风増鏈洿鏂帮紝渚夸簬鐢ㄦ埛鑷鎵╁睍鍜岀涓夋柟鍥轰欢鎵╁睍銆傛敮鎸佽闊冲敜閱掋�佽闊虫墦鏂�丱TA绛夊姛鑳姐��
+
+## 榄旀敼鐗�
+榄旀敼鐗堢敱浜庡簳灞傛敼鍔ㄥお澶э紝浠g爜鍗曠嫭缁存姢锛屽畾鏈熷悎骞朵富椤圭洰浠g爜銆�
+
+https://e.tb.cn/h.6Gl2LC7rsrswQZp?tk=qFuaV9hzh0k CZ356
+```
+銆愭窐瀹濄�� 銆屽皬鏅篈I甯︽憚鍍忓ご鏀寔璇嗙墿鍙岄害鍏嬮鎵撴柇 ESP32S3N16R8寮�鍙戞澘琛ㄦ儏鍖呫��
+https://e.tb.cn/h.hBc8Gcx9cUluJJO?tk=YW5C4LPixKg
+
+
+
+## 閰嶇疆銆佺紪璇戝懡浠�
+
+鐢变簬姝ら」鐩渶瑕侀厤缃緝澶氱殑 sdkconfig 閫夐」锛屾帹鑽愪娇鐢ㄧ紪璇戣剼鏈紪璇戙��
+
+**缂栬瘧**
+
+```bash
+python ./scripts/release.py zhengchen-cam
+```
+
+濡傞渶鎵嬪姩缂栬瘧锛岃鍙傝�� `zhengchen-cam/config.json` 淇敼 menuconfig 瀵瑰簲閫夐」銆�
+
+**鐑у綍**
+
+```bash
+idf.py flash
+
+
+```
+
+MCP Tool锛�
+self.get_device_status
+self.audio_speaker.set_volume
+self.screen.set_brightness
+self.screen.set_theme
+self.gif.set_gif_mode
+self.display.set_mode
+self.camera.take_photo
+self.AEC.set_mode
+self.AEC.get_mode
+self.res.esp_restart
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.h
new file mode 100644
index 0000000..f601229
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.h
@@ -0,0 +1,68 @@
+#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_INPUT_REFERENCE true
+
+#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38
+#define AUDIO_I2S_GPIO_WS GPIO_NUM_13
+#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14
+#define AUDIO_I2S_GPIO_DIN GPIO_NUM_12
+#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45
+
+#define AUDIO_CODEC_USE_PCA9557
+#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1
+#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2
+#define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR
+#define AUDIO_CODEC_ES7210_ADDR 0x82
+
+#define BUILTIN_LED_GPIO GPIO_NUM_48
+#define BOOT_BUTTON_GPIO GPIO_NUM_0
+#define VOLUME_UP_BUTTON_GPIO GPIO_NUM_3
+#define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_46
+
+#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_WIDTH_1 240
+#define DISPLAY_HEIGHT_1 320
+#define DISPLAY_MIRROR_X_1 false
+#define DISPLAY_MIRROR_Y_1 false
+#define DISPLAY_SWAP_XY_1 false
+
+#define DISPLAY_OFFSET_X 0
+#define DISPLAY_OFFSET_Y 0
+
+#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42
+#define DISPLAY_BACKLIGHT_OUTPUT_INVERT true
+
+
+/* Camera pins */
+#define CAMERA_PIN_PWDN -1
+#define CAMERA_PIN_RESET -1
+#define CAMERA_PIN_XCLK 17
+#define CAMERA_PIN_SIOD 1
+#define CAMERA_PIN_SIOC 2
+
+#define CAMERA_PIN_D7 15
+#define CAMERA_PIN_D6 11
+#define CAMERA_PIN_D5 9
+#define CAMERA_PIN_D4 8
+#define CAMERA_PIN_D3 6
+#define CAMERA_PIN_D2 5
+#define CAMERA_PIN_D1 4
+#define CAMERA_PIN_D0 7
+#define CAMERA_PIN_VSYNC 21
+#define CAMERA_PIN_HREF 18
+#define CAMERA_PIN_PCLK 16
+
+#define XCLK_FREQ_HZ 24000000
+
+#endif // _BOARD_CONFIG_H_
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.json b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.json
new file mode 100644
index 0000000..e78f51e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/config.json
@@ -0,0 +1,17 @@
+{
+ "target": "esp32s3",
+ "builds": [
+ {
+ "name": "zhengchen-cam",
+ "sdkconfig_append": [
+ "CONFIG_USE_DEVICE_AEC=y",
+ "CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=48",
+ "CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=32",
+ "CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_0=n",
+ "CONFIG_ESP_WIFI_TASK_PINNED_TO_CORE_1=y",
+ "CONFIG_LV_USE_FONT_COMPRESSED=y",
+ "CONFIG_LV_USE_FONT_PLACEHOLDER=y"
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/mcp_controller.cc b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/mcp_controller.cc
new file mode 100644
index 0000000..0f7f073
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/mcp_controller.cc
@@ -0,0 +1,94 @@
+#include <cJSON.h>
+#include <esp_log.h>
+
+#include <cstring>
+
+#include "application.h"
+#include "board.h"
+#include "config.h"
+#include "mcp_server.h"
+#include "sdkconfig.h"
+#include "settings.h"
+#include "display.h"
+
+#define TAG "MCPController"
+
+class MCPController {
+public:
+ MCPController() {
+ RegisterMcpTools();
+ ESP_LOGI(TAG, "娉ㄥ唽MCP宸ュ叿");
+ }
+
+ void RegisterMcpTools() {
+ auto& mcp_server = McpServer::GetInstance();
+ ESP_LOGI(TAG, "寮�濮嬫敞鍐孧CP宸ュ叿...");
+
+ mcp_server.AddTool(
+ "self.AEC.set_mode",
+ "璁剧疆AEC瀵硅瘽鎵撴柇妯″紡銆傚綋鐢ㄦ埛鎰忓浘鍒囨崲瀵硅瘽鎵撴柇妯″紡鏃舵垨鑰呯敤鎴疯寰梐i瀵硅瘽瀹规槗琚墦鏂椂鎴栬�呯敤鎴疯寰楁棤娉曞疄鐜板璇濇墦鏂椂閮戒娇鐢ㄦ宸ュ叿銆俓n"
+ "鍙傛暟锛歕n"
+ " `mode`: 瀵硅瘽鎵撴柇妯″紡锛屽彲閫夊�煎彧鏈塦kAecOff`(鍏抽棴锛夊拰`kAecOnDeviceSide`锛堝紑鍚級\n"
+ "杩斿洖鍊硷細\n"
+ " 鍙嶉鐘舵�佷俊鎭紝涓嶉渶瑕佺‘璁わ紝绔嬪嵆鎾姤鐩稿叧鏁版嵁\n",
+ PropertyList({
+ Property("mode", kPropertyTypeString)
+ }),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto mode = properties["mode"].value<std::string>();
+ auto& app = Application::GetInstance();
+ vTaskDelay(pdMS_TO_TICKS(2000));
+ if (mode == "kAecOff") {
+ app.SetAecMode(kAecOff);
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡宸插叧闂璡"}";
+ }else {
+ auto& board = Board::GetInstance();
+ app.SetAecMode(kAecOnDeviceSide);
+
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡宸插紑鍚痋"}";
+ }
+ }
+ );
+
+ mcp_server.AddTool(
+ "self.AEC.get_mode",
+ "鑾峰彇AEC瀵硅瘽鎵撴柇妯″紡鐘舵�併�傚綋鐢ㄦ埛鎰忓浘鑾峰彇瀵硅瘽鎵撴柇妯″紡鐘舵�佹椂浣跨敤姝ゅ伐鍏枫�俓n"
+ "杩斿洖鍊硷細\n"
+ " 鍙嶉鐘舵�佷俊鎭紝涓嶉渶瑕佺‘璁わ紝绔嬪嵆鎾姤鐩稿叧鏁版嵁\n",
+ PropertyList(),
+ [](const PropertyList&) -> ReturnValue {
+ auto& app = Application::GetInstance();
+ const bool is_currently_off = (app.GetAecMode() == kAecOff);
+ if (is_currently_off) {
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡澶勪簬鍏抽棴鐘舵�乗"}";
+ }else {
+ return "{\"success\": true, \"message\": \"AEC瀵硅瘽鎵撴柇妯″紡澶勪簬寮�鍚姸鎬乗"}";
+ }
+ }
+ );
+
+ mcp_server.AddTool(
+ "self.res.esp_restart",
+ "閲嶅惎璁惧銆傚綋鐢ㄦ埛鎰忓浘閲嶅惎璁惧鏃朵娇鐢ㄦ宸ュ叿銆俓n",
+ PropertyList(),
+ [](const PropertyList&) -> ReturnValue {
+ vTaskDelay(pdMS_TO_TICKS(1000));
+ // Reboot the device
+ esp_restart();
+ return true;
+ }
+ );
+
+ ESP_LOGI(TAG, "MCP宸ュ叿娉ㄥ唽瀹屾垚");
+ }
+
+};
+
+static MCPController* g_mcp_controller = nullptr;
+
+void InitializeMCPController() {
+ if (g_mcp_controller == nullptr) {
+ g_mcp_controller = new MCPController();
+ ESP_LOGI(TAG, "娉ㄥ唽MCP宸ュ叿");
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/power_manager.h b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/power_manager.h
new file mode 100644
index 0000000..f9e31fd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/power_manager.h
@@ -0,0 +1,237 @@
+#pragma once
+#include <vector>
+#include <functional>
+#include <esp_log.h>
+#include <esp_timer.h>
+#include <driver/gpio.h>
+#include <esp_adc/adc_oneshot.h>
+#include <driver/temperature_sensor.h>
+#include "application.h"
+
+class PowerManager {
+private:
+ // 瀹氭椂鍣ㄥ彞鏌�
+ esp_timer_handle_t timer_handle_;
+ std::function<void(bool)> on_charging_status_changed_;
+ std::function<void(bool)> on_low_battery_status_changed_;
+ std::function<void(float)> on_temperature_changed_;
+
+ gpio_num_t charging_pin_ = GPIO_NUM_NC;
+ std::vector<uint16_t> adc_values_;
+ uint32_t battery_level_ = 0;
+ bool is_charging_ = false;
+ bool is_low_battery_ = false;
+ float current_temperature_ = 0.0f;
+ int ticks_ = 0;
+ const int kBatteryAdcInterval = 60;
+ const int kBatteryAdcDataCount = 3;
+ const int kLowBatteryLevel = 20;
+ const int kTemperatureReadInterval = 10; // 姣� 10 绉掕鍙栦竴娆℃俯搴�
+
+ adc_oneshot_unit_handle_t adc_handle_;
+ temperature_sensor_handle_t temp_sensor_ = NULL;
+
+ void CheckBatteryStatus() {
+ // Get charging status
+ bool new_charging_status = gpio_get_level(charging_pin_) == 1;
+ if (new_charging_status != is_charging_) {
+ is_charging_ = new_charging_status;
+ if (on_charging_status_changed_) {
+ on_charging_status_changed_(is_charging_);
+ }
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁涓嶈冻锛屽垯璇诲彇鐢垫睜鐢甸噺鏁版嵁
+ if (adc_values_.size() < kBatteryAdcDataCount) {
+ ReadBatteryAdcData();
+ return;
+ }
+
+ // 濡傛灉鐢垫睜鐢甸噺鏁版嵁鍏呰冻锛屽垯姣� kBatteryAdcInterval 涓� tick 璇诲彇涓�娆$數姹犵數閲忔暟鎹�
+ ticks_++;
+ if (ticks_ % kBatteryAdcInterval == 0) {
+ ReadBatteryAdcData();
+ }
+
+ // 鏂板锛氬懆鏈熸�ц鍙栨俯搴�
+ if (ticks_ % kTemperatureReadInterval == 0) {
+ ReadTemperature();
+ }
+ }
+
+ void ReadBatteryAdcData() {
+ // 璇诲彇 ADC 鍊�
+ int adc_value;
+ ESP_ERROR_CHECK(adc_oneshot_read(adc_handle_, ADC_CHANNEL_9, &adc_value));
+
+
+ // 灏� ADC 鍊兼坊鍔犲埌闃熷垪涓�
+ adc_values_.push_back(adc_value);
+ if (adc_values_.size() > kBatteryAdcDataCount) {
+ adc_values_.erase(adc_values_.begin());
+ }
+ uint32_t average_adc = 0;
+ for (auto value : adc_values_) {
+ average_adc += (value + 80);
+ }
+ average_adc /= adc_values_.size();
+
+
+ // 瀹氫箟鐢垫睜鐢甸噺鍖洪棿
+ const struct {
+ uint16_t adc;
+ uint8_t level;
+ } levels[] = {
+ {2030, 0},
+ {2134, 20},
+ {2252, 40},
+ {2370, 60},
+ {2488, 80},
+ {2606, 100}
+ };
+ // 浣庝簬鏈�浣庡�兼椂
+ if (average_adc < levels[0].adc) {
+ battery_level_ = 0;
+ }
+ // 楂樹簬鏈�楂樺�兼椂
+ else if (average_adc >= levels[5].adc) {
+ battery_level_ = 100;
+ } else {
+ // 绾挎�ф彃鍊艰绠椾腑闂村��
+ for (int i = 0; i < 5; i++) {
+ if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) {
+ float ratio = static_cast<float>(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc);
+ battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level);
+ break;
+ }
+ }
+ }
+ // 妫�鏌ユ槸鍚﹁揪鍒颁綆鐢甸噺闃堝��
+ if (adc_values_.size() >= kBatteryAdcDataCount) {
+ bool new_low_battery_status = battery_level_ <= kLowBatteryLevel;
+ if (new_low_battery_status != is_low_battery_) {
+ is_low_battery_ = new_low_battery_status;
+ if (on_low_battery_status_changed_) {
+ on_low_battery_status_changed_(is_low_battery_);
+ }
+ }
+ }
+
+ ESP_LOGI("PowerManager", "ADC value: %d average: %ld level: %ld", adc_value, average_adc, battery_level_);
+ }
+
+ void ReadTemperature() {
+ float temperature = 0.0f;
+ ESP_ERROR_CHECK(temperature_sensor_get_celsius(temp_sensor_, &temperature));
+
+ if (abs(temperature - current_temperature_) >= 3.5f) { // 娓╁害鍙樺寲瓒呰繃3.5掳C鎵嶈Е鍙戝洖璋�
+ current_temperature_ = temperature;
+ if (on_temperature_changed_) {
+ on_temperature_changed_(current_temperature_);
+ }
+ ESP_LOGI("PowerManager", "Temperature updated: %.1f掳C", current_temperature_);
+ }
+ }
+
+
+public:
+ PowerManager(gpio_num_t pin) : charging_pin_(pin) {
+
+ // 鍒濆鍖栧厖鐢靛紩鑴�
+ gpio_config_t io_conf = {};
+ io_conf.intr_type = GPIO_INTR_DISABLE;
+ io_conf.mode = GPIO_MODE_INPUT;
+ io_conf.pin_bit_mask = (1ULL << charging_pin_);
+ io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
+ io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
+ gpio_config(&io_conf);
+
+ // 鍒涘缓鐢垫睜鐢甸噺妫�鏌ュ畾鏃跺櫒
+ esp_timer_create_args_t timer_args = {
+ .callback = [](void* arg) {
+ PowerManager* self = static_cast<PowerManager*>(arg);
+ self->CheckBatteryStatus();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "battery_check_timer",
+ .skip_unhandled_events = true,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
+ ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
+
+ // 鍒濆鍖� ADC
+ adc_oneshot_unit_init_cfg_t init_config = {
+ .unit_id = ADC_UNIT_1,
+ .ulp_mode = ADC_ULP_MODE_DISABLE,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle_));
+
+ adc_oneshot_chan_cfg_t chan_config = {
+ .atten = ADC_ATTEN_DB_12,
+ .bitwidth = ADC_BITWIDTH_12,
+ };
+ ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle_, ADC_CHANNEL_9, &chan_config));
+
+ // 鍒濆鍖栨俯搴︿紶鎰熷櫒
+ temperature_sensor_config_t temp_config = {
+ .range_min = 10,
+ .range_max = 80,
+ .clk_src = TEMPERATURE_SENSOR_CLK_SRC_DEFAULT
+ };
+ ESP_ERROR_CHECK(temperature_sensor_install(&temp_config, &temp_sensor_));
+ ESP_ERROR_CHECK(temperature_sensor_enable(temp_sensor_));
+ ESP_LOGI("PowerManager", "Temperature sensor initialized (new driver)");
+ }
+
+ ~PowerManager() {
+ if (timer_handle_) {
+ esp_timer_stop(timer_handle_);
+ esp_timer_delete(timer_handle_);
+ }
+ if (adc_handle_) {
+ adc_oneshot_del_unit(adc_handle_);
+ }
+
+ if (temp_sensor_) {
+ temperature_sensor_disable(temp_sensor_);
+ temperature_sensor_uninstall(temp_sensor_);
+ }
+
+ }
+
+ bool IsCharging() {
+ // 濡傛灉鐢甸噺宸茬粡婊′簡锛屽垯涓嶅啀鏄剧ず鍏呯數涓�
+ if (battery_level_ == 100) {
+ return false;
+ }
+ return is_charging_;
+ }
+
+ bool IsDischarging() {
+ // 娌℃湁鍖哄垎鍏呯數鍜屾斁鐢碉紝鎵�浠ョ洿鎺ヨ繑鍥炵浉鍙嶇姸鎬�
+ return !is_charging_;
+ }
+
+ // 鑾峰彇鐢垫睜鐢甸噺
+ uint8_t GetBatteryLevel() {
+ // 杩斿洖鐢垫睜鐢甸噺
+ return battery_level_;
+ }
+
+ float GetTemperature() const { return current_temperature_; } // 鑾峰彇褰撳墠娓╁害
+
+ void OnTemperatureChanged(std::function<void(float)> callback) {
+ on_temperature_changed_ = callback;
+ }
+
+ void OnLowBatteryStatusChanged(std::function<void(bool)> callback) {
+ on_low_battery_status_changed_ = callback;
+ }
+
+ void OnChargingStatusChanged(std::function<void(bool)> callback) {
+ on_charging_status_changed_ = callback;
+ }
+};
diff --git a/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/zhengchen_cam_board.cc b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/zhengchen_cam_board.cc
new file mode 100644
index 0000000..c7bc9ef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/boards/zhengchen-cam/zhengchen_cam_board.cc
@@ -0,0 +1,333 @@
+#include "wifi_board.h"
+#include "audio/codecs/box_audio_codec.h"
+#include "display/lcd_display.h"
+#include "application.h"
+#include "button.h"
+#include "led/single_led.h"
+#include "config.h"
+#include "i2c_device.h"
+#include "esp32_camera.h"
+#include "assets/lang_config.h"
+
+#include <esp_log.h>
+#include <esp_lcd_panel_vendor.h>
+#include <driver/i2c_master.h>
+#include <driver/spi_common.h>
+#include <wifi_station.h>
+#include "power_manager.h"
+#include <esp_lvgl_port.h>
+#include <lvgl.h>
+#include "settings.h"
+
+#define FIRST_BOOT_NS "boot_config"
+#define FIRST_BOOT_KEY "is_first"
+
+
+#define TAG "ZhengchenCamBoard"
+
+//鎺у埗鍣ㄥ垵濮嬪寲鍑芥暟澹版槑
+void InitializeMCPController();
+
+LV_FONT_DECLARE(font_puhui_20_4);
+LV_FONT_DECLARE(font_awesome_20_4);
+
+class Pca9557 : public I2cDevice {
+public:
+ Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) {
+ WriteReg(0x01, 0x03);
+ WriteReg(0x03, 0xf8);
+ }
+
+ void SetOutputState(uint8_t bit, uint8_t level) {
+ uint8_t data = ReadReg(0x01);
+ data = (data & ~(1 << bit)) | (level << bit);
+ WriteReg(0x01, data);
+ }
+};
+
+class CustomAudioCodec : public BoxAudioCodec {
+private:
+ Pca9557* pca9557_;
+
+public:
+ CustomAudioCodec(i2c_master_bus_handle_t i2c_bus, Pca9557* pca9557)
+ : BoxAudioCodec(i2c_bus,
+ 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,
+ GPIO_NUM_NC,
+ AUDIO_CODEC_ES8311_ADDR,
+ AUDIO_CODEC_ES7210_ADDR,
+ AUDIO_INPUT_REFERENCE),
+ pca9557_(pca9557) {
+ }
+
+ virtual void EnableOutput(bool enable) override {
+ BoxAudioCodec::EnableOutput(enable);
+ if (enable) {
+ pca9557_->SetOutputState(1, 1);
+ } else {
+ pca9557_->SetOutputState(1, 0);
+ }
+ }
+};
+
+class ZhengchenCamBoard : public WifiBoard {
+private:
+ i2c_master_bus_handle_t i2c_bus_;
+ i2c_master_dev_handle_t pca9557_handle_;
+ Button boot_button_;
+ Button volume_up_button_;
+ Button volume_down_button_;
+ LcdDisplay* display_ = nullptr;
+ Pca9557* pca9557_;
+ Esp32Camera* camera_;
+ PowerManager* power_manager_ = new PowerManager(GPIO_NUM_47);
+
+ void InitializeI2c() {
+ // Initialize I2C peripheral
+ i2c_master_bus_config_t i2c_bus_cfg = {
+ .i2c_port = (i2c_port_t)1,
+ .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, &i2c_bus_));
+
+ // Initialize PCA9557
+ pca9557_ = new Pca9557(i2c_bus_, 0x19);
+ }
+
+ void InitializeSpi() {
+ spi_bus_config_t buscfg = {};
+ buscfg.mosi_io_num = GPIO_NUM_40;
+ buscfg.miso_io_num = GPIO_NUM_NC;
+ buscfg.sclk_io_num = GPIO_NUM_41;
+ 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(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO));
+ }
+
+ void InitializeButtons() {
+ boot_button_.OnClick([this]() {
+ auto& app = Application::GetInstance();
+ // During startup (before connected), pressing BOOT button enters Wi-Fi config mode without reboot
+ if (app.GetDeviceState() == kDeviceStateStarting) {
+ EnterWifiConfigMode();
+ return;
+ }
+ app.ToggleChatState();
+ });
+
+ boot_button_.OnDoubleClick([this]() {
+ Settings settings(FIRST_BOOT_NS, true);
+ bool is_first_boot = settings.GetInt(FIRST_BOOT_KEY, 1) != 0;
+ if (is_first_boot) {
+ ESP_LOGI(TAG, "棣栨鍚姩锛屽惎鐢ㄥ弻鍑绘媿鐓у姛鑳�");
+ auto camera = GetCamera();
+ if (!camera->Capture()) {
+ ESP_LOGE(TAG, "Camera capture failed");
+ }
+ settings.SetInt(FIRST_BOOT_KEY, 0);
+
+
+ } else {
+ ESP_LOGI(TAG, "闈為娆″惎鍔紝绂佺敤鍙屽嚮鎷嶇収鍔熻兘");
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ app.SetAecMode(app.GetAecMode() == kAecOff ? kAecOnDeviceSide : kAecOff);
+ GetAudioCodec()->SetOutputVolume(60);
+ }
+ }
+ });
+
+ boot_button_.OnLongPress([this]() {
+ EnterWifiConfigMode();
+ });
+
+ volume_up_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() + 10;
+ if (volume > 100) {
+ volume = 100;
+ }
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ codec->SetOutputVolume(volume);
+
+ });
+
+ volume_up_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(100);
+ GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME);
+ });
+
+ volume_down_button_.OnClick([this]() {
+ auto codec = GetAudioCodec();
+ auto volume = codec->output_volume() - 10;
+ if (volume < 0) {
+ volume = 0;
+ }
+ codec->SetOutputVolume(volume);
+ GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume/10));
+ });
+
+ volume_down_button_.OnLongPress([this]() {
+ GetAudioCodec()->SetOutputVolume(0);
+ GetDisplay()->ShowNotification(Lang::Strings::MUTED);
+ });
+ }
+
+ void InitializeSt7789Display() {
+ esp_lcd_panel_io_handle_t panel_io = nullptr;
+ esp_lcd_panel_handle_t panel = nullptr;
+ // 娑叉櫠灞忔帶鍒禝O鍒濆鍖�
+ ESP_LOGD(TAG, "Install panel IO");
+ esp_lcd_panel_io_spi_config_t io_config = {};
+ io_config.cs_gpio_num = GPIO_NUM_NC;
+ io_config.dc_gpio_num = GPIO_NUM_39;
+ io_config.spi_mode = 0;
+ 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(SPI3_HOST, &io_config, &panel_io));
+
+ // 鍒濆鍖栨恫鏅跺睆椹卞姩鑺墖ST7789
+ ESP_LOGD(TAG, "Install LCD driver");
+ 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);
+ pca9557_->SetOutputState(0, 0);
+
+ esp_lcd_panel_init(panel);
+ esp_lcd_panel_invert_color(panel, true);
+
+ Settings settings("lcd_display", true);
+ bool is_landscape = settings.GetInt("lcd_mode", 1) != 0;
+
+ if(is_landscape) {
+ // 妯睆妯″紡
+ 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);
+
+ } else {
+ // 绔栧睆妯″紡
+ esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY_1);
+ esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X_1, DISPLAY_MIRROR_Y_1);
+ display_ = new SpiLcdDisplay(panel_io, panel,
+ DISPLAY_WIDTH_1, DISPLAY_HEIGHT_1, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X_1, DISPLAY_MIRROR_Y_1, DISPLAY_SWAP_XY_1);
+ }
+
+ }
+
+ void InitializeCamera() {
+ // Open camera power
+ pca9557_->SetOutputState(2, 0);
+
+ camera_config_t config = {};
+ config.ledc_channel = LEDC_CHANNEL_2; // LEDC閫氶亾閫夋嫨 鐢ㄤ簬鐢熸垚XCLK鏃堕挓 浣嗘槸S3涓嶇敤
+ config.ledc_timer = LEDC_TIMER_2; // LEDC timer閫夋嫨 鐢ㄤ簬鐢熸垚XCLK鏃堕挓 浣嗘槸S3涓嶇敤
+ config.pin_d0 = CAMERA_PIN_D0;
+ config.pin_d1 = CAMERA_PIN_D1;
+ config.pin_d2 = CAMERA_PIN_D2;
+ config.pin_d3 = CAMERA_PIN_D3;
+ config.pin_d4 = CAMERA_PIN_D4;
+ config.pin_d5 = CAMERA_PIN_D5;
+ config.pin_d6 = CAMERA_PIN_D6;
+ config.pin_d7 = CAMERA_PIN_D7;
+ config.pin_xclk = CAMERA_PIN_XCLK;
+ config.pin_pclk = CAMERA_PIN_PCLK;
+ config.pin_vsync = CAMERA_PIN_VSYNC;
+ config.pin_href = CAMERA_PIN_HREF;
+ config.pin_sccb_sda = -1; // 杩欓噷鍐�-1 琛ㄧず浣跨敤宸茬粡鍒濆鍖栫殑I2C鎺ュ彛
+ config.pin_sccb_scl = CAMERA_PIN_SIOC;
+ config.sccb_i2c_port = 1;
+ config.pin_pwdn = CAMERA_PIN_PWDN;
+ config.pin_reset = CAMERA_PIN_RESET;
+ config.xclk_freq_hz = XCLK_FREQ_HZ;
+ config.pixel_format = PIXFORMAT_RGB565;
+ config.frame_size = FRAMESIZE_VGA;
+ config.jpeg_quality = 9;
+ config.fb_count = 1;
+ config.fb_location = CAMERA_FB_IN_PSRAM;
+ config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
+
+ camera_ = new Esp32Camera(config);
+ }
+
+ void InitializeController() { InitializeMCPController(); }
+
+public:
+ ZhengchenCamBoard() :
+ boot_button_(BOOT_BUTTON_GPIO),
+ volume_up_button_(VOLUME_UP_BUTTON_GPIO),
+ volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) {
+ InitializeI2c();
+ InitializeSpi();
+ InitializeSt7789Display();
+ InitializeButtons();
+ InitializeCamera();
+ InitializeController();
+ GetBacklight()->RestoreBrightness();
+ }
+
+ virtual AudioCodec* GetAudioCodec() override {
+ static CustomAudioCodec audio_codec(
+ i2c_bus_,
+ pca9557_);
+ return &audio_codec;
+ }
+
+ virtual Display* GetDisplay() override {
+ return display_;
+ }
+
+ virtual Led* GetLed() override {
+ static SingleLed led(BUILTIN_LED_GPIO);
+ return &led;
+ }
+
+ virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
+ static bool last_discharging = false;
+ charging = power_manager_->IsCharging();
+ discharging = power_manager_->IsDischarging();
+ if (discharging != last_discharging) {
+ last_discharging = discharging;
+ }
+ level = std::max<uint32_t>(power_manager_->GetBatteryLevel(), 20);
+ return true;
+ }
+
+ virtual bool GetTemperature(float& esp32temp) override {
+ esp32temp = power_manager_->GetTemperature();
+ return true;
+ }
+
+ virtual Backlight* GetBacklight() override {
+ static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
+ return &backlight;
+ }
+
+ virtual Camera* GetCamera() override {
+ return camera_;
+ }
+};
+
+DECLARE_BOARD(ZhengchenCamBoard);
diff --git a/xiaozhi-esp32-2.2.6/main/device_state.h b/xiaozhi-esp32-2.2.6/main/device_state.h
new file mode 100644
index 0000000..4ffafae
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/device_state.h
@@ -0,0 +1,18 @@
+#ifndef _DEVICE_STATE_H_
+#define _DEVICE_STATE_H_
+
+enum DeviceState {
+ kDeviceStateUnknown,
+ kDeviceStateStarting,
+ kDeviceStateWifiConfiguring,
+ kDeviceStateIdle,
+ kDeviceStateConnecting,
+ kDeviceStateListening,
+ kDeviceStateSpeaking,
+ kDeviceStateUpgrading,
+ kDeviceStateActivating,
+ kDeviceStateAudioTesting,
+ kDeviceStateFatalError
+};
+
+#endif // _DEVICE_STATE_H_
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/device_state_machine.cc b/xiaozhi-esp32-2.2.6/main/device_state_machine.cc
new file mode 100644
index 0000000..30581de
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/device_state_machine.cc
@@ -0,0 +1,161 @@
+#include "device_state_machine.h"
+
+#include <algorithm>
+#include <esp_log.h>
+
+static const char* TAG = "StateMachine";
+
+// State name strings for logging
+static const char* const STATE_STRINGS[] = {
+ "unknown",
+ "starting",
+ "wifi_configuring",
+ "idle",
+ "connecting",
+ "listening",
+ "speaking",
+ "upgrading",
+ "activating",
+ "audio_testing",
+ "fatal_error",
+ "invalid_state"
+};
+
+DeviceStateMachine::DeviceStateMachine() {
+}
+
+const char* DeviceStateMachine::GetStateName(DeviceState state) {
+ if (state >= 0 && state <= kDeviceStateFatalError) {
+ return STATE_STRINGS[state];
+ }
+ return STATE_STRINGS[kDeviceStateFatalError + 1];
+}
+
+bool DeviceStateMachine::IsValidTransition(DeviceState from, DeviceState to) const {
+ // Allow transition to the same state (no-op)
+ if (from == to) {
+ return true;
+ }
+
+ // Define valid state transitions based on the state diagram
+ switch (from) {
+ case kDeviceStateUnknown:
+ // Can only go to starting
+ return to == kDeviceStateStarting;
+
+ case kDeviceStateStarting:
+ // Can go to wifi configuring or activating
+ return to == kDeviceStateWifiConfiguring ||
+ to == kDeviceStateActivating;
+
+ case kDeviceStateWifiConfiguring:
+ // Can go to activating (after wifi connected) or audio testing
+ return to == kDeviceStateActivating ||
+ to == kDeviceStateAudioTesting;
+
+ case kDeviceStateAudioTesting:
+ // Can go back to wifi configuring
+ return to == kDeviceStateWifiConfiguring;
+
+ case kDeviceStateActivating:
+ // Can go to upgrading, idle, or back to wifi configuring (on error)
+ return to == kDeviceStateUpgrading ||
+ to == kDeviceStateIdle ||
+ to == kDeviceStateWifiConfiguring;
+
+ case kDeviceStateUpgrading:
+ // Can go to idle (upgrade failed) or activating
+ return to == kDeviceStateIdle ||
+ to == kDeviceStateActivating;
+
+ case kDeviceStateIdle:
+ // Can go to connecting, listening (manual mode), speaking, activating, upgrading, or wifi configuring
+ return to == kDeviceStateConnecting ||
+ to == kDeviceStateListening ||
+ to == kDeviceStateSpeaking ||
+ to == kDeviceStateActivating ||
+ to == kDeviceStateUpgrading ||
+ to == kDeviceStateWifiConfiguring;
+
+ case kDeviceStateConnecting:
+ // Can go to idle (failed) or listening (success)
+ return to == kDeviceStateIdle ||
+ to == kDeviceStateListening;
+
+ case kDeviceStateListening:
+ // Can go to speaking or idle
+ return to == kDeviceStateSpeaking ||
+ to == kDeviceStateIdle;
+
+ case kDeviceStateSpeaking:
+ // Can go to listening or idle
+ return to == kDeviceStateListening ||
+ to == kDeviceStateIdle;
+
+ case kDeviceStateFatalError:
+ // Cannot transition out of fatal error
+ return false;
+
+ default:
+ return false;
+ }
+}
+
+bool DeviceStateMachine::CanTransitionTo(DeviceState target) const {
+ return IsValidTransition(current_state_.load(), target);
+}
+
+bool DeviceStateMachine::TransitionTo(DeviceState new_state) {
+ DeviceState old_state = current_state_.load();
+
+ // No-op if already in the target state
+ if (old_state == new_state) {
+ return true;
+ }
+
+ // Validate transition
+ if (!IsValidTransition(old_state, new_state)) {
+ ESP_LOGW(TAG, "Invalid state transition: %s -> %s",
+ GetStateName(old_state), GetStateName(new_state));
+ return false;
+ }
+
+ // Perform transition
+ current_state_.store(new_state);
+ ESP_LOGI(TAG, "State: %s -> %s",
+ GetStateName(old_state), GetStateName(new_state));
+
+ // Notify callback
+ NotifyStateChange(old_state, new_state);
+ return true;
+}
+
+int DeviceStateMachine::AddStateChangeListener(StateCallback callback) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ int id = next_listener_id_++;
+ listeners_.emplace_back(id, std::move(callback));
+ return id;
+}
+
+void DeviceStateMachine::RemoveStateChangeListener(int listener_id) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ listeners_.erase(
+ std::remove_if(listeners_.begin(), listeners_.end(),
+ [listener_id](const auto& p) { return p.first == listener_id; }),
+ listeners_.end());
+}
+
+void DeviceStateMachine::NotifyStateChange(DeviceState old_state, DeviceState new_state) {
+ std::vector<StateCallback> callbacks_copy;
+ {
+ std::lock_guard<std::mutex> lock(mutex_);
+ callbacks_copy.reserve(listeners_.size());
+ for (const auto& [id, cb] : listeners_) {
+ callbacks_copy.push_back(cb);
+ }
+ }
+
+ for (const auto& cb : callbacks_copy) {
+ cb(old_state, new_state);
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/device_state_machine.h b/xiaozhi-esp32-2.2.6/main/device_state_machine.h
new file mode 100644
index 0000000..9566fde
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/device_state_machine.h
@@ -0,0 +1,83 @@
+#ifndef DEVICE_STATE_MACHINE_H
+#define DEVICE_STATE_MACHINE_H
+
+#include <atomic>
+#include <functional>
+#include <mutex>
+#include <vector>
+
+#include "device_state.h"
+
+/**
+ * DeviceStateMachine - Manages device state transitions with validation
+ *
+ * This class ensures strict state transition rules and provides a callback mechanism
+ * for components to react to state changes.
+ */
+class DeviceStateMachine {
+public:
+ DeviceStateMachine();
+ ~DeviceStateMachine() = default;
+
+ // Delete copy constructor and assignment operator
+ DeviceStateMachine(const DeviceStateMachine&) = delete;
+ DeviceStateMachine& operator=(const DeviceStateMachine&) = delete;
+
+ /**
+ * Get the current device state
+ */
+ DeviceState GetState() const { return current_state_.load(); }
+
+ /**
+ * Attempt to transition to a new state
+ * @param new_state The target state
+ * @return true if transition was successful, false if invalid transition
+ */
+ bool TransitionTo(DeviceState new_state);
+
+ /**
+ * Check if transition to target state is valid from current state
+ */
+ bool CanTransitionTo(DeviceState target) const;
+
+ /**
+ * State change callback type
+ * Parameters: old_state, new_state
+ */
+ using StateCallback = std::function<void(DeviceState, DeviceState)>;
+
+ /**
+ * Add a state change listener (observer pattern)
+ * Callback is invoked in the context of the caller of TransitionTo()
+ * @return listener id for removal
+ */
+ int AddStateChangeListener(StateCallback callback);
+
+ /**
+ * Remove a state change listener by id
+ */
+ void RemoveStateChangeListener(int listener_id);
+
+ /**
+ * Get state name string for logging
+ */
+ static const char* GetStateName(DeviceState state);
+
+private:
+ std::atomic<DeviceState> current_state_{kDeviceStateUnknown};
+ std::vector<std::pair<int, StateCallback>> listeners_;
+ int next_listener_id_{0};
+ std::mutex mutex_;
+
+ /**
+ * Check if transition from source to target is valid
+ */
+ bool IsValidTransition(DeviceState from, DeviceState to) const;
+
+ /**
+ * Notify callback of state change
+ */
+ void NotifyStateChange(DeviceState old_state, DeviceState new_state);
+};
+
+#endif // DEVICE_STATE_MACHINE_H
diff --git a/xiaozhi-esp32-2.2.6/main/display/display.cc b/xiaozhi-esp32-2.2.6/main/display/display.cc
new file mode 100644
index 0000000..50fc1c5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/display.cc
@@ -0,0 +1,60 @@
+#include <esp_log.h>
+#include <esp_err.h>
+#include <string>
+#include <cstdlib>
+#include <cstring>
+#include <font_awesome.h>
+
+#include "display.h"
+#include "board.h"
+#include "application.h"
+#include "audio_codec.h"
+#include "settings.h"
+#include "assets/lang_config.h"
+
+#define TAG "Display"
+
+Display::Display() {
+}
+
+Display::~Display() {
+}
+
+void Display::SetStatus(const char* status) {
+ ESP_LOGW(TAG, "SetStatus: %s", status);
+}
+
+void Display::ShowNotification(const std::string ¬ification, int duration_ms) {
+ ShowNotification(notification.c_str(), duration_ms);
+}
+
+void Display::ShowNotification(const char* notification, int duration_ms) {
+ ESP_LOGW(TAG, "ShowNotification: %s", notification);
+}
+
+void Display::UpdateStatusBar(bool update_all) {
+}
+
+
+void Display::SetEmotion(const char* emotion) {
+ ESP_LOGW(TAG, "SetEmotion: %s", emotion);
+}
+
+void Display::SetChatMessage(const char* role, const char* content) {
+ ESP_LOGW(TAG, "Role:%s", role);
+ ESP_LOGW(TAG, " %s", content);
+}
+
+void Display::ClearChatMessages() {
+ // Default empty implementation, override in subclasses if needed
+}
+
+void Display::SetTheme(Theme* theme) {
+ current_theme_ = theme;
+ Settings settings("display", true);
+ settings.SetString("theme", theme->name());
+}
+
+void Display::SetPowerSaveMode(bool on) {
+ ESP_LOGW(TAG, "SetPowerSaveMode: %d", on);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/display.h b/xiaozhi-esp32-2.2.6/main/display/display.h
new file mode 100644
index 0000000..9e73014
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/display.h
@@ -0,0 +1,87 @@
+#ifndef DISPLAY_H
+#define DISPLAY_H
+
+#include "emoji_collection.h"
+
+#ifndef CONFIG_USE_EMOTE_MESSAGE_STYLE
+#define HAVE_LVGL 1
+#include <lvgl.h>
+#endif
+
+#include <esp_timer.h>
+#include <esp_log.h>
+#include <esp_pm.h>
+
+#include <string>
+#include <chrono>
+
+class Theme {
+public:
+ Theme(const std::string& name) : name_(name) {}
+ virtual ~Theme() = default;
+
+ inline std::string name() const { return name_; }
+private:
+ std::string name_;
+};
+
+class Display {
+public:
+ Display();
+ virtual ~Display();
+
+ virtual void SetStatus(const char* status);
+ virtual void ShowNotification(const char* notification, int duration_ms = 3000);
+ virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
+ virtual void SetEmotion(const char* emotion);
+ virtual void SetChatMessage(const char* role, const char* content);
+ virtual void ClearChatMessages();
+ virtual void SetTheme(Theme* theme);
+ virtual Theme* GetTheme() { return current_theme_; }
+ virtual void UpdateStatusBar(bool update_all = false);
+ virtual void SetPowerSaveMode(bool on);
+ virtual void SetupUI() {
+ setup_ui_called_ = true;
+ }
+
+ inline int width() const { return width_; }
+ inline int height() const { return height_; }
+ inline bool IsSetupUICalled() const { return setup_ui_called_; }
+
+protected:
+ int width_ = 0;
+ int height_ = 0;
+ bool setup_ui_called_ = false; // Track if SetupUI() has been called
+
+ Theme* current_theme_ = nullptr;
+
+ friend class DisplayLockGuard;
+ virtual bool Lock(int timeout_ms = 0) = 0;
+ virtual void Unlock() = 0;
+};
+
+
+class DisplayLockGuard {
+public:
+ DisplayLockGuard(Display *display) : display_(display) {
+ if (!display_->Lock(30000)) {
+ ESP_LOGE("Display", "Failed to lock display");
+ }
+ }
+ ~DisplayLockGuard() {
+ display_->Unlock();
+ }
+
+private:
+ Display *display_;
+};
+
+class NoDisplay : public Display {
+private:
+ virtual bool Lock(int timeout_ms = 0) override {
+ return true;
+ }
+ virtual void Unlock() override {}
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/display/emote_display.cc b/xiaozhi-esp32-2.2.6/main/display/emote_display.cc
new file mode 100644
index 0000000..7ed920a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/emote_display.cc
@@ -0,0 +1,250 @@
+#include "emote_display.h"
+
+// Standard C++ headers
+#include <cstring>
+#include <memory>
+#include <unordered_map>
+#include <tuple>
+#include <algorithm>
+#include <cinttypes>
+
+// Standard C headers
+#include <sys/time.h>
+#include <time.h>
+
+// ESP-IDF headers
+#include <esp_log.h>
+#include <esp_lcd_panel_io.h>
+#include <esp_timer.h>
+#include <lvgl.h>
+
+// FreeRTOS headers
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+// Project headers
+#include "assets/lang_config.h"
+#include "assets.h"
+#include "board.h"
+#include "gfx.h"
+#include "expression_emote.h"
+
+
+namespace emote {
+
+// ============================================================================
+// Constants and Type Definitions
+// ============================================================================
+
+static const char* TAG = "EmoteDisplay";
+
+// ============================================================================
+// Forward Declarations
+// ============================================================================
+
+class EmoteDisplay;
+
+// ============================================================================
+// Helper Functions
+// ============================================================================
+
+static bool OnFlushIoReady(const esp_lcd_panel_io_handle_t panel_io,
+ esp_lcd_panel_io_event_data_t* const edata, void* user_ctx)
+{
+ emote_handle_t handle = static_cast<emote_handle_t>(user_ctx);
+ if (handle) {
+ emote_notify_flush_finished(handle);
+ }
+ return true;
+}
+
+// Flush callback for emote
+static void OnFlushCallback(int x_start, int y_start, int x_end, int y_end, const void* data, emote_handle_t handle)
+{
+ esp_lcd_panel_handle_t panel = (esp_lcd_panel_handle_t)emote_get_user_data(handle);
+ if (panel != nullptr) {
+ esp_lcd_panel_draw_bitmap(panel, x_start, y_start, x_end, y_end, data);
+ }
+}
+
+// ============================================================================
+// Graphics Initialization Functions
+// ============================================================================
+
+static emote_handle_t InitializeEmote(const esp_lcd_panel_handle_t panel, const int width, const int height)
+{
+ if (!panel) {
+ ESP_LOGE(TAG, "Invalid panel");
+ return nullptr;
+ }
+
+ emote_config_t emote_cfg = {
+ .flags = {
+ .swap = true,
+ .double_buffer = true,
+ .buff_dma = false,
+ },
+ .gfx_emote = {
+ .h_res = width,
+ .v_res = height,
+ .fps = 30,
+ },
+ .buffers = {
+ .buf_pixels = static_cast<size_t>(width * 16),
+ },
+ .task = {
+ .task_priority = 5,
+ .task_stack = 6 * 1024,
+ .task_affinity = 0,
+ .task_stack_in_ext = false,
+ },
+ .flush_cb = OnFlushCallback,
+ .user_data = (void*)panel,
+ };
+
+ emote_handle_t emote_handle = emote_init(&emote_cfg);
+ if (!emote_handle) {
+ ESP_LOGE(TAG, "Failed to initialize emote");
+ return nullptr;
+ }
+
+ return emote_handle;
+}
+
+// ============================================================================
+// EmoteDisplay Class Implementation
+// ============================================================================
+
+EmoteDisplay::EmoteDisplay(const esp_lcd_panel_handle_t panel, const esp_lcd_panel_io_handle_t panel_io,
+ const int width, const int height)
+{
+ emote_handle_ = InitializeEmote(panel, width, height);
+
+ const esp_lcd_panel_io_callbacks_t cbs = {
+ .on_color_trans_done = OnFlushIoReady,
+ };
+ esp_lcd_panel_io_register_event_callbacks(panel_io, &cbs, emote_handle_);
+}
+
+EmoteDisplay::~EmoteDisplay()
+{
+ if (emote_handle_) {
+ emote_deinit(emote_handle_);
+ emote_handle_ = nullptr;
+ }
+}
+
+void EmoteDisplay::SetEmotion(const char* const emotion)
+{
+ ESP_LOGI(TAG, "SetEmotion: %s", emotion);
+ if (emote_handle_ && emotion && strlen(emotion) > 0) {
+ emote_set_anim_emoji(emote_handle_, emotion);
+ }
+}
+
+void EmoteDisplay::SetChatMessage(const char* const role, const char* const content)
+{
+ ESP_LOGI(TAG, "SetChatMessage: %s, %s", role, content);
+ if (emote_handle_ && content && strlen(content) > 0) {
+ if ((std::strcmp(role, "system") == 0) && std::strstr(content, "xiaozhi.me")) {
+ size_t len = strlen(content);
+ char* new_content = new char[len + 1];
+ strcpy(new_content, content);
+ std::replace(new_content, new_content + len, static_cast<char>(0x0A), static_cast<char>(0x20));
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SYS, new_content);
+ delete[] new_content;
+ } else {
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SPEAK, content);
+ }
+ }
+}
+
+void EmoteDisplay::SetStatus(const char* const status)
+{
+ ESP_LOGI(TAG, "SetStatus: %s", status);
+ if (emote_handle_ && status && strlen(status) > 0) {
+ if (std::strcmp(status, Lang::Strings::LISTENING) == 0) {
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_LISTEN, NULL);
+ } else if (std::strcmp(status, Lang::Strings::STANDBY) == 0) {
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_IDLE, NULL);
+ } else if (std::strcmp(status, Lang::Strings::SPEAKING) == 0) {
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SPEAK, NULL);
+ } else if (std::strcmp(status, Lang::Strings::ERROR) == 0) {
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SET, NULL);
+ }
+ }
+}
+
+void EmoteDisplay::ShowNotification(const char* notification, int duration_ms)
+{
+ ESP_LOGI(TAG, "ShowNotification: %s", notification);
+ if (emote_handle_ && notification && strlen(notification) > 0) {
+ emote_set_event_msg(emote_handle_, EMOTE_MGR_EVT_SYS, notification);
+ }
+}
+
+void EmoteDisplay::UpdateStatusBar(bool update_all)
+{
+ ESP_LOGD(TAG, "UpdateStatusBar: %s", update_all ? "true" : "false");
+ if (!emote_handle_) {
+ return;
+ }
+}
+
+void EmoteDisplay::SetPowerSaveMode(bool on)
+{
+ ESP_LOGI(TAG, "SetPowerSaveMode: %s", on ? "ON" : "OFF");
+ if (!emote_handle_) {
+ return;
+ }
+}
+
+void EmoteDisplay::SetPreviewImage(const void* image)
+{
+ if (image) {
+ ESP_LOGI(TAG, "SetPreviewImage: Preview image not supported, using default icon");
+ }
+}
+
+void EmoteDisplay::SetTheme(Theme* const theme)
+{
+ ESP_LOGI(TAG, "SetTheme: %p", theme);
+}
+
+bool EmoteDisplay::Lock(const int timeout_ms)
+{
+ (void)timeout_ms;
+ return true;
+}
+
+void EmoteDisplay::Unlock()
+{
+}
+
+bool EmoteDisplay::StopAnimDialog()
+{
+ ESP_LOGI(TAG, "StopAnimDialog");
+ if (emote_handle_) {
+ return emote_stop_anim_dialog(emote_handle_);
+ }
+ return false;
+}
+
+bool EmoteDisplay::InsertAnimDialog(const char* emoji_name, uint32_t duration_ms)
+{
+ ESP_LOGI(TAG, "InsertAnimDialog: %s, %" PRIu32, emoji_name, duration_ms);
+ if (emote_handle_ && emoji_name) {
+ return emote_insert_anim_dialog(emote_handle_, emoji_name, duration_ms);
+ }
+ return false;
+}
+
+void EmoteDisplay::RefreshAll()
+{
+ if (emote_handle_) {
+ emote_notify_all_refresh(emote_handle_);
+ return;
+ }
+}
+
+} // namespace emote
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/display/emote_display.h b/xiaozhi-esp32-2.2.6/main/display/emote_display.h
new file mode 100644
index 0000000..20f1870
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/emote_display.h
@@ -0,0 +1,42 @@
+#pragma once
+
+#include "display.h"
+#include <memory>
+#include <string>
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include "expression_emote.h"
+
+namespace emote {
+
+class EmoteDisplay : public Display {
+public:
+ EmoteDisplay(esp_lcd_panel_handle_t panel, esp_lcd_panel_io_handle_t panel_io, int width, int height);
+ virtual ~EmoteDisplay();
+
+ virtual void SetEmotion(const char* emotion) override;
+ virtual void SetStatus(const char* status) override;
+ virtual void SetChatMessage(const char* role, const char* content) override;
+ virtual void SetTheme(Theme* theme) override;
+ virtual void ShowNotification(const char* notification, int duration_ms = 3000) override;
+ virtual void UpdateStatusBar(bool update_all = false) override;
+ virtual void SetPowerSaveMode(bool on) override;
+ virtual void SetPreviewImage(const void* image);
+
+ bool StopAnimDialog();
+ bool InsertAnimDialog(const char* emoji_name, uint32_t duration_ms);
+
+ void RefreshAll();
+
+ // Get emote handle for internal use
+ emote_handle_t GetEmoteHandle() const { return emote_handle_; }
+
+private:
+ virtual bool Lock(int timeout_ms = 0) override;
+ virtual void Unlock() override;
+
+ emote_handle_t emote_handle_ = nullptr;
+
+};
+
+} // namespace emote
diff --git a/xiaozhi-esp32-2.2.6/main/display/lcd_display.cc b/xiaozhi-esp32-2.2.6/main/display/lcd_display.cc
new file mode 100644
index 0000000..be236bb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lcd_display.cc
@@ -0,0 +1,1310 @@
+#include "lcd_display.h"
+#include "gif/lvgl_gif.h"
+#include "settings.h"
+#include "lvgl_theme.h"
+#include "assets/lang_config.h"
+
+#include <vector>
+#include <algorithm>
+#include <font_awesome.h>
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_lvgl_port.h>
+#include <esp_psram.h>
+#include <cstring>
+#include <src/misc/cache/lv_cache.h>
+
+#include "board.h"
+
+#define TAG "LcdDisplay"
+
+LV_FONT_DECLARE(BUILTIN_TEXT_FONT);
+LV_FONT_DECLARE(BUILTIN_ICON_FONT);
+LV_FONT_DECLARE(font_awesome_30_4);
+
+void LcdDisplay::InitializeLcdThemes() {
+ auto text_font = std::make_shared<LvglBuiltInFont>(&BUILTIN_TEXT_FONT);
+ auto icon_font = std::make_shared<LvglBuiltInFont>(&BUILTIN_ICON_FONT);
+ auto large_icon_font = std::make_shared<LvglBuiltInFont>(&font_awesome_30_4);
+
+ // light theme
+ auto light_theme = new LvglTheme("light");
+ light_theme->set_background_color(lv_color_hex(0xFFFFFF));
+ light_theme->set_text_color(lv_color_hex(0x000000));
+ light_theme->set_chat_background_color(lv_color_hex(0xE0E0E0));
+ light_theme->set_user_bubble_color(lv_color_hex(0x00FF00));
+ light_theme->set_assistant_bubble_color(lv_color_hex(0xDDDDDD));
+ light_theme->set_system_bubble_color(lv_color_hex(0xFFFFFF));
+ light_theme->set_system_text_color(lv_color_hex(0x000000));
+ light_theme->set_border_color(lv_color_hex(0x000000));
+ light_theme->set_low_battery_color(lv_color_hex(0x000000));
+ light_theme->set_text_font(text_font);
+ light_theme->set_icon_font(icon_font);
+ light_theme->set_large_icon_font(large_icon_font);
+
+ // dark theme
+ auto dark_theme = new LvglTheme("dark");
+ dark_theme->set_background_color(lv_color_hex(0x000000));
+ dark_theme->set_text_color(lv_color_hex(0xFFFFFF));
+ dark_theme->set_chat_background_color(lv_color_hex(0x1F1F1F));
+ dark_theme->set_user_bubble_color(lv_color_hex(0x00FF00));
+ dark_theme->set_assistant_bubble_color(lv_color_hex(0x222222));
+ dark_theme->set_system_bubble_color(lv_color_hex(0x000000));
+ dark_theme->set_system_text_color(lv_color_hex(0xFFFFFF));
+ dark_theme->set_border_color(lv_color_hex(0xFFFFFF));
+ dark_theme->set_low_battery_color(lv_color_hex(0xFF0000));
+ dark_theme->set_text_font(text_font);
+ dark_theme->set_icon_font(icon_font);
+ dark_theme->set_large_icon_font(large_icon_font);
+
+ auto& theme_manager = LvglThemeManager::GetInstance();
+ theme_manager.RegisterTheme("light", light_theme);
+ theme_manager.RegisterTheme("dark", dark_theme);
+}
+
+LcdDisplay::LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height)
+ : panel_io_(panel_io), panel_(panel) {
+ width_ = width;
+ height_ = height;
+
+ // Initialize LCD themes
+ InitializeLcdThemes();
+
+ // Load theme from settings
+ Settings settings("display", false);
+ std::string theme_name = settings.GetString("theme", "light");
+ current_theme_ = LvglThemeManager::GetInstance().GetTheme(theme_name);
+
+ // Create a timer to hide the preview image
+ esp_timer_create_args_t preview_timer_args = {
+ .callback = [](void* arg) {
+ LcdDisplay* display = static_cast<LcdDisplay*>(arg);
+ display->SetPreviewImage(nullptr);
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "preview_timer",
+ .skip_unhandled_events = false,
+ };
+ esp_timer_create(&preview_timer_args, &preview_timer_);
+}
+
+SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy)
+ : LcdDisplay(panel_io, panel, width, height) {
+
+ // draw white
+ std::vector<uint16_t> buffer(width_, 0xFFFF);
+ for (int y = 0; y < height_; y++) {
+ esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
+ }
+
+ // Set the display to on
+ ESP_LOGI(TAG, "Turning display on");
+ {
+ esp_err_t __err = esp_lcd_panel_disp_on_off(panel_, true);
+ if (__err == ESP_ERR_NOT_SUPPORTED) {
+ ESP_LOGW(TAG, "Panel does not support disp_on_off; assuming ON");
+ } else {
+ ESP_ERROR_CHECK(__err);
+ }
+ }
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+#if CONFIG_SPIRAM
+ // lv image cache, currently only PNG is supported
+ size_t psram_size_mb = esp_psram_get_size() / 1024 / 1024;
+ if (psram_size_mb >= 8) {
+ lv_image_cache_resize(2 * 1024 * 1024, true);
+ ESP_LOGI(TAG, "Use 2MB of PSRAM for image cache");
+ } else if (psram_size_mb >= 2) {
+ lv_image_cache_resize(512 * 1024, true);
+ ESP_LOGI(TAG, "Use 512KB of PSRAM for image cache");
+ }
+#endif
+
+ ESP_LOGI(TAG, "Initialize LVGL port");
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 1;
+#if CONFIG_SOC_CPU_CORES_NUM > 1
+ port_cfg.task_affinity = 1;
+#endif
+ lvgl_port_init(&port_cfg);
+
+ ESP_LOGI(TAG, "Adding LCD display");
+ const lvgl_port_display_cfg_t display_cfg = {
+ .io_handle = panel_io_,
+ .panel_handle = panel_,
+ .control_handle = nullptr,
+ .buffer_size = static_cast<uint32_t>(width_ * 20),
+ .double_buffer = false,
+ .trans_size = 0,
+ .hres = static_cast<uint32_t>(width_),
+ .vres = static_cast<uint32_t>(height_),
+ .monochrome = false,
+ .rotation = {
+ .swap_xy = swap_xy,
+ .mirror_x = mirror_x,
+ .mirror_y = mirror_y,
+ },
+ .color_format = LV_COLOR_FORMAT_RGB565,
+ .flags = {
+ .buff_dma = 1,
+ .buff_spiram = 0,
+ .sw_rotate = 0,
+ .swap_bytes = 1,
+ .full_refresh = 0,
+ .direct_mode = 0,
+ },
+ };
+
+ display_ = lvgl_port_add_disp(&display_cfg);
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ if (offset_x != 0 || offset_y != 0) {
+ lv_display_set_offset(display_, offset_x, offset_y);
+ }
+}
+
+
+// RGB LCD implementation
+RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy)
+ : LcdDisplay(panel_io, panel, width, height) {
+
+ // draw white
+ std::vector<uint16_t> buffer(width_, 0xFFFF);
+ for (int y = 0; y < height_; y++) {
+ esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data());
+ }
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+ ESP_LOGI(TAG, "Initialize LVGL port");
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 1;
+ port_cfg.timer_period_ms = 50;
+ lvgl_port_init(&port_cfg);
+
+ ESP_LOGI(TAG, "Adding LCD display");
+ const lvgl_port_display_cfg_t display_cfg = {
+ .io_handle = panel_io_,
+ .panel_handle = panel_,
+ .buffer_size = static_cast<uint32_t>(width_ * 20),
+ .double_buffer = true,
+ .hres = static_cast<uint32_t>(width_),
+ .vres = static_cast<uint32_t>(height_),
+ .rotation = {
+ .swap_xy = swap_xy,
+ .mirror_x = mirror_x,
+ .mirror_y = mirror_y,
+ },
+ .flags = {
+ .buff_dma = 1,
+ .swap_bytes = 0,
+ .full_refresh = 1,
+ .direct_mode = 1,
+ },
+ };
+
+ const lvgl_port_display_rgb_cfg_t rgb_cfg = {
+ .flags = {
+ .bb_mode = true,
+ .avoid_tearing = true,
+ }
+ };
+
+ display_ = lvgl_port_add_disp_rgb(&display_cfg, &rgb_cfg);
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add RGB display");
+ return;
+ }
+
+ if (offset_x != 0 || offset_y != 0) {
+ lv_display_set_offset(display_, offset_x, offset_y);
+ }
+}
+
+MipiLcdDisplay::MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy)
+ : LcdDisplay(panel_io, panel, width, height) {
+
+ ESP_LOGI(TAG, "Initialize LVGL library");
+ lv_init();
+
+ ESP_LOGI(TAG, "Initialize LVGL port");
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ lvgl_port_init(&port_cfg);
+
+ ESP_LOGI(TAG, "Adding LCD display");
+ const lvgl_port_display_cfg_t disp_cfg = {
+ .io_handle = panel_io,
+ .panel_handle = panel,
+ .control_handle = nullptr,
+ .buffer_size = static_cast<uint32_t>(width_ * 50),
+ .double_buffer = false,
+ .hres = static_cast<uint32_t>(width_),
+ .vres = static_cast<uint32_t>(height_),
+ .monochrome = false,
+ /* Rotation values must be same as used in esp_lcd for initial settings of the screen */
+ .rotation = {
+ .swap_xy = swap_xy,
+ .mirror_x = mirror_x,
+ .mirror_y = mirror_y,
+ },
+ .flags = {
+ .buff_dma = true,
+ .buff_spiram =false,
+ .sw_rotate = true,
+ },
+ };
+
+ const lvgl_port_display_dsi_cfg_t dpi_cfg = {
+ .flags = {
+ .avoid_tearing = false,
+ }
+ };
+ display_ = lvgl_port_add_disp_dsi(&disp_cfg, &dpi_cfg);
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ if (offset_x != 0 || offset_y != 0) {
+ lv_display_set_offset(display_, offset_x, offset_y);
+ }
+}
+
+LcdDisplay::~LcdDisplay() {
+ SetPreviewImage(nullptr);
+
+ // Clean up GIF controller
+ if (gif_controller_) {
+ gif_controller_->Stop();
+ gif_controller_.reset();
+ }
+
+ if (preview_timer_ != nullptr) {
+ esp_timer_stop(preview_timer_);
+ esp_timer_delete(preview_timer_);
+ }
+
+ if (preview_image_ != nullptr) {
+ lv_obj_del(preview_image_);
+ }
+ if (chat_message_label_ != nullptr) {
+ lv_obj_del(chat_message_label_);
+ }
+ if (emoji_label_ != nullptr) {
+ lv_obj_del(emoji_label_);
+ }
+ if (emoji_image_ != nullptr) {
+ lv_obj_del(emoji_image_);
+ }
+ if (emoji_box_ != nullptr) {
+ lv_obj_del(emoji_box_);
+ }
+ if (content_ != nullptr) {
+ lv_obj_del(content_);
+ }
+ if (bottom_bar_ != nullptr) {
+ lv_obj_del(bottom_bar_);
+ }
+ if (status_bar_ != nullptr) {
+ lv_obj_del(status_bar_);
+ }
+ if (top_bar_ != nullptr) {
+ lv_obj_del(top_bar_);
+ }
+ if (side_bar_ != nullptr) {
+ lv_obj_del(side_bar_);
+ }
+ if (container_ != nullptr) {
+ lv_obj_del(container_);
+ }
+ if (display_ != nullptr) {
+ lv_display_delete(display_);
+ }
+
+ if (panel_ != nullptr) {
+ esp_lcd_panel_del(panel_);
+ }
+ if (panel_io_ != nullptr) {
+ esp_lcd_panel_io_del(panel_io_);
+ }
+}
+
+bool LcdDisplay::Lock(int timeout_ms) {
+ return lvgl_port_lock(timeout_ms);
+}
+
+void LcdDisplay::Unlock() {
+ lvgl_port_unlock();
+}
+
+#if CONFIG_USE_WECHAT_MESSAGE_STYLE
+void LcdDisplay::SetupUI() {
+ // Prevent duplicate calls - if already called, return early
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetupUI() called multiple times, skipping duplicate call");
+ return;
+ }
+
+ Display::SetupUI(); // Mark SetupUI as called
+ DisplayLockGuard lock(this);
+
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+ auto large_icon_font = lvgl_theme->large_icon_font()->font();
+
+ auto screen = lv_screen_active();
+ lv_obj_set_style_text_font(screen, text_font, 0);
+ lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0);
+
+ /* Container */
+ container_ = lv_obj_create(screen);
+ lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
+ lv_obj_set_style_radius(container_, 0, 0);
+ lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_style_pad_all(container_, 0, 0);
+ lv_obj_set_style_border_width(container_, 0, 0);
+ lv_obj_set_style_pad_row(container_, 0, 0);
+ lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0);
+ lv_obj_set_style_border_color(container_, lvgl_theme->border_color(), 0);
+
+ /* Layer 1: Top bar - for status icons */
+ top_bar_ = lv_obj_create(container_);
+ lv_obj_set_size(top_bar_, LV_HOR_RES, LV_SIZE_CONTENT);
+ lv_obj_set_style_radius(top_bar_, 0, 0);
+ lv_obj_set_style_bg_opa(top_bar_, LV_OPA_50, 0); // 50% opacity background
+ lv_obj_set_style_bg_color(top_bar_, lvgl_theme->background_color(), 0);
+ lv_obj_set_style_border_width(top_bar_, 0, 0);
+ lv_obj_set_style_pad_all(top_bar_, 0, 0);
+ lv_obj_set_style_pad_top(top_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_style_pad_bottom(top_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_style_pad_left(top_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_style_pad_right(top_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_flex_flow(top_bar_, LV_FLEX_FLOW_ROW);
+ lv_obj_set_flex_align(top_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+ lv_obj_set_scrollbar_mode(top_bar_, LV_SCROLLBAR_MODE_OFF);
+
+ // Left icon
+ network_label_ = lv_label_create(top_bar_);
+ lv_label_set_text(network_label_, "");
+ lv_obj_set_style_text_font(network_label_, icon_font, 0);
+ lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0);
+
+ // Right icons container
+ lv_obj_t* right_icons = lv_obj_create(top_bar_);
+ lv_obj_set_size(right_icons, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
+ lv_obj_set_style_bg_opa(right_icons, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_border_width(right_icons, 0, 0);
+ lv_obj_set_style_pad_all(right_icons, 0, 0);
+ lv_obj_set_flex_flow(right_icons, LV_FLEX_FLOW_ROW);
+ lv_obj_set_flex_align(right_icons, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+
+ mute_label_ = lv_label_create(right_icons);
+ lv_label_set_text(mute_label_, "");
+ lv_obj_set_style_text_font(mute_label_, icon_font, 0);
+ lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0);
+
+ battery_label_ = lv_label_create(right_icons);
+ lv_label_set_text(battery_label_, "");
+ lv_obj_set_style_text_font(battery_label_, icon_font, 0);
+ lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_margin_left(battery_label_, lvgl_theme->spacing(2), 0);
+
+ /* Layer 2: Status bar - for center text labels */
+ status_bar_ = lv_obj_create(screen);
+ lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT);
+ lv_obj_set_style_radius(status_bar_, 0, 0);
+ lv_obj_set_style_bg_opa(status_bar_, LV_OPA_TRANSP, 0); // Transparent background
+ lv_obj_set_style_border_width(status_bar_, 0, 0);
+ lv_obj_set_style_pad_all(status_bar_, 0, 0);
+ lv_obj_set_style_pad_top(status_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_style_pad_bottom(status_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0); // Use absolute positioning
+ lv_obj_align(status_bar_, LV_ALIGN_TOP_MID, 0, 0); // Overlap with top_bar_
+
+ notification_label_ = lv_label_create(status_bar_);
+ lv_obj_set_width(notification_label_, LV_HOR_RES * 0.8);
+ lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0);
+ lv_label_set_text(notification_label_, "");
+ lv_obj_align(notification_label_, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
+
+ status_label_ = lv_label_create(status_bar_);
+ lv_obj_set_width(status_label_, LV_HOR_RES * 0.8);
+ lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0);
+ lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
+ lv_obj_align(status_label_, LV_ALIGN_CENTER, 0, 0);
+
+ /* Content - Chat area */
+ content_ = lv_obj_create(container_);
+ lv_obj_set_style_radius(content_, 0, 0);
+ lv_obj_set_width(content_, LV_HOR_RES);
+ lv_obj_set_flex_grow(content_, 1);
+ lv_obj_set_style_pad_all(content_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_style_border_width(content_, 0, 0);
+ lv_obj_set_style_bg_color(content_, lvgl_theme->chat_background_color(), 0); // Background for chat area
+
+ // Enable scrolling for chat content
+ lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_scroll_dir(content_, LV_DIR_VER);
+
+ // Create a flex container for chat messages
+ lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START, LV_FLEX_ALIGN_START);
+ lv_obj_set_style_pad_row(content_, lvgl_theme->spacing(4), 0); // Space between messages
+
+ // We'll create chat messages dynamically in SetChatMessage
+ chat_message_label_ = nullptr;
+
+ low_battery_popup_ = lv_obj_create(screen);
+ lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2);
+ lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -lvgl_theme->spacing(4));
+ lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0);
+ lv_obj_set_style_radius(low_battery_popup_, lvgl_theme->spacing(4), 0);
+ low_battery_label_ = lv_label_create(low_battery_popup_);
+ lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
+ lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
+ lv_obj_center(low_battery_label_);
+ lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
+
+ emoji_image_ = lv_img_create(screen);
+ lv_obj_align(emoji_image_, LV_ALIGN_TOP_MID, 0, text_font->line_height + lvgl_theme->spacing(8));
+
+ // Display AI logo while booting
+ emoji_label_ = lv_label_create(screen);
+ lv_obj_center(emoji_label_);
+ lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0);
+ lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
+ lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI);
+}
+#if CONFIG_IDF_TARGET_ESP32P4
+#define MAX_MESSAGES 40
+#else
+#define MAX_MESSAGES 20
+#endif
+void LcdDisplay::SetChatMessage(const char* role, const char* content) {
+ if (!setup_ui_called_) {
+ ESP_LOGW(TAG, "SetChatMessage('%s', '%s') called before SetupUI() - message will be lost!", role, content);
+ }
+ DisplayLockGuard lock(this);
+ if (content_ == nullptr) {
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetChatMessage('%s', '%s') failed: content_ is nullptr (SetupUI() was called but container not created)", role, content);
+ }
+ return;
+ }
+
+ // Check if message count exceeds limit
+ uint32_t child_count = lv_obj_get_child_cnt(content_);
+ if (child_count >= MAX_MESSAGES) {
+ // Delete the oldest message (first child object)
+ lv_obj_t* first_child = lv_obj_get_child(content_, 0);
+ if (first_child != nullptr) {
+ lv_obj_del(first_child);
+ // Refresh child count after deletion
+ child_count = lv_obj_get_child_cnt(content_);
+ }
+ // Scroll to the last message immediately (get last_child after deletion)
+ if (child_count > 0) {
+ lv_obj_t* last_child = lv_obj_get_child(content_, child_count - 1);
+ if (last_child != nullptr && lv_obj_is_valid(last_child)) {
+ lv_obj_scroll_to_view_recursive(last_child, LV_ANIM_OFF);
+ }
+ }
+ }
+
+ // Collapse system messages (if it's a system message, check if the last message is also a system message)
+ if (strcmp(role, "system") == 0) {
+ // Refresh child count to get accurate count after potential deletion above
+ child_count = lv_obj_get_child_cnt(content_);
+ if (child_count > 0) {
+ // Get the last message container
+ lv_obj_t* last_container = lv_obj_get_child(content_, child_count - 1);
+ if (last_container != nullptr && lv_obj_is_valid(last_container) && lv_obj_get_child_cnt(last_container) > 0) {
+ // Get the bubble inside the container
+ lv_obj_t* last_bubble = lv_obj_get_child(last_container, 0);
+ if (last_bubble != nullptr && lv_obj_is_valid(last_bubble)) {
+ // Check if bubble type is system message
+ void* bubble_type_ptr = lv_obj_get_user_data(last_bubble);
+ if (bubble_type_ptr != nullptr && strcmp((const char*)bubble_type_ptr, "system") == 0) {
+ // If the last message is also a system message, delete it
+ lv_obj_del(last_container);
+ }
+ }
+ }
+ }
+ } else {
+ // Hide the centered AI logo
+ lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
+ }
+
+ // Avoid empty message boxes
+ if(strlen(content) == 0) {
+ return;
+ }
+
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+
+ // Create a message bubble
+ lv_obj_t* msg_bubble = lv_obj_create(content_);
+ lv_obj_set_style_radius(msg_bubble, 8, 0);
+ lv_obj_set_scrollbar_mode(msg_bubble, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_style_border_width(msg_bubble, 0, 0);
+ lv_obj_set_style_pad_all(msg_bubble, lvgl_theme->spacing(4), 0);
+
+ // Create the message text
+ lv_obj_t* msg_text = lv_label_create(msg_bubble);
+ lv_label_set_text(msg_text, content);
+
+ // Calculate bubble width constraints
+ lv_coord_t max_width = LV_HOR_RES * 85 / 100 - 16; // 85% of screen width
+ lv_coord_t min_width = 20;
+
+ // Let LVGL calculate the natural text width first
+ lv_obj_set_width(msg_text, LV_SIZE_CONTENT);
+ lv_obj_update_layout(msg_text);
+ lv_coord_t text_width = lv_obj_get_width(msg_text);
+
+ // Ensure text width is not less than minimum width
+ if (text_width < min_width) {
+ text_width = min_width;
+ }
+
+ // Constrain to max width
+ lv_coord_t bubble_width = (text_width < max_width) ? text_width : max_width;
+
+ // Set message text width
+ lv_obj_set_width(msg_text, bubble_width);
+ lv_label_set_long_mode(msg_text, LV_LABEL_LONG_WRAP);
+
+ // Set bubble width
+ lv_obj_set_width(msg_bubble, bubble_width);
+ lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
+
+ // Set alignment and style based on message role
+ if (strcmp(role, "user") == 0) {
+ // User messages are right-aligned with green background
+ lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->user_bubble_color(), 0);
+ lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0);
+ // Set text color for contrast
+ lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0);
+
+ // Set custom attribute to mark bubble type
+ lv_obj_set_user_data(msg_bubble, (void*)"user");
+
+ // Set appropriate width for content
+ lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
+ lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
+
+ // Don't grow
+ lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
+ } else if (strcmp(role, "assistant") == 0) {
+ // Assistant messages are left-aligned with white background
+ lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->assistant_bubble_color(), 0);
+ lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0);
+ // Set text color for contrast
+ lv_obj_set_style_text_color(msg_text, lvgl_theme->text_color(), 0);
+
+ // Set custom attribute to mark bubble type
+ lv_obj_set_user_data(msg_bubble, (void*)"assistant");
+
+ // Set appropriate width for content
+ lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
+ lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
+
+ // Don't grow
+ lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
+ } else if (strcmp(role, "system") == 0) {
+ // System messages are center-aligned with light gray background
+ lv_obj_set_style_bg_color(msg_bubble, lvgl_theme->system_bubble_color(), 0);
+ lv_obj_set_style_bg_opa(msg_bubble, LV_OPA_70, 0);
+ // Set text color for contrast
+ lv_obj_set_style_text_color(msg_text, lvgl_theme->system_text_color(), 0);
+
+ // Set custom attribute to mark bubble type
+ lv_obj_set_user_data(msg_bubble, (void*)"system");
+
+ // Set appropriate width for content
+ lv_obj_set_width(msg_bubble, LV_SIZE_CONTENT);
+ lv_obj_set_height(msg_bubble, LV_SIZE_CONTENT);
+
+ // Don't grow
+ lv_obj_set_style_flex_grow(msg_bubble, 0, 0);
+ }
+
+ // Create a full-width container for user messages to ensure right alignment
+ if (strcmp(role, "user") == 0) {
+ // Create a full-width container
+ lv_obj_t* container = lv_obj_create(content_);
+ lv_obj_set_width(container, LV_HOR_RES);
+ lv_obj_set_height(container, LV_SIZE_CONTENT);
+
+ // Make container transparent and borderless
+ lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_border_width(container, 0, 0);
+ lv_obj_set_style_pad_all(container, 0, 0);
+
+ // Move the message bubble into this container
+ lv_obj_set_parent(msg_bubble, container);
+
+ // Right align the bubble in the container
+ lv_obj_align(msg_bubble, LV_ALIGN_RIGHT_MID, -25, 0);
+
+ // Auto-scroll to this container
+ lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
+ } else if (strcmp(role, "system") == 0) {
+ // Create full-width container for system messages to ensure center alignment
+ lv_obj_t* container = lv_obj_create(content_);
+ lv_obj_set_width(container, LV_HOR_RES);
+ lv_obj_set_height(container, LV_SIZE_CONTENT);
+
+ lv_obj_set_style_bg_opa(container, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_border_width(container, 0, 0);
+ lv_obj_set_style_pad_all(container, 0, 0);
+
+ lv_obj_set_parent(msg_bubble, container);
+ lv_obj_align(msg_bubble, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_scroll_to_view_recursive(container, LV_ANIM_ON);
+ } else {
+ // For assistant messages
+ // Left align assistant messages
+ lv_obj_align(msg_bubble, LV_ALIGN_LEFT_MID, 0, 0);
+
+ // Auto-scroll to the message bubble
+ lv_obj_scroll_to_view_recursive(msg_bubble, LV_ANIM_ON);
+ }
+
+ // Store reference to the latest message label
+ chat_message_label_ = msg_text;
+}
+
+void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
+ DisplayLockGuard lock(this);
+ if (content_ == nullptr) {
+ return;
+ }
+
+ if (image == nullptr) {
+ return;
+ }
+
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ // Create a message bubble for image preview
+ lv_obj_t* img_bubble = lv_obj_create(content_);
+ lv_obj_set_style_radius(img_bubble, 8, 0);
+ lv_obj_set_scrollbar_mode(img_bubble, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_style_border_width(img_bubble, 0, 0);
+ lv_obj_set_style_pad_all(img_bubble, lvgl_theme->spacing(4), 0);
+
+ // Set image bubble background color (similar to system message)
+ lv_obj_set_style_bg_color(img_bubble, lvgl_theme->assistant_bubble_color(), 0);
+ lv_obj_set_style_bg_opa(img_bubble, LV_OPA_70, 0);
+
+ // Set custom attribute to mark bubble type
+ lv_obj_set_user_data(img_bubble, (void*)"image");
+
+ // Create the image object inside the bubble
+ lv_obj_t* preview_image = lv_image_create(img_bubble);
+
+ // Calculate appropriate size for the image
+ lv_coord_t max_width = LV_HOR_RES * 70 / 100; // 70% of screen width
+ lv_coord_t max_height = LV_VER_RES * 50 / 100; // 50% of screen height
+
+ // Calculate zoom factor to fit within maximum dimensions
+ auto img_dsc = image->image_dsc();
+ lv_coord_t img_width = img_dsc->header.w;
+ lv_coord_t img_height = img_dsc->header.h;
+ if (img_width == 0 || img_height == 0) {
+ img_width = max_width;
+ img_height = max_height;
+ ESP_LOGW(TAG, "Invalid image dimensions: %ld x %ld, using default dimensions: %ld x %ld", img_width, img_height, max_width, max_height);
+ }
+
+ lv_coord_t zoom_w = (max_width * 256) / img_width;
+ lv_coord_t zoom_h = (max_height * 256) / img_height;
+ lv_coord_t zoom = (zoom_w < zoom_h) ? zoom_w : zoom_h;
+
+ // Ensure zoom doesn't exceed 256 (100%)
+ if (zoom > 256) zoom = 256;
+
+ // Set image properties
+ lv_image_set_src(preview_image, img_dsc);
+ lv_image_set_scale(preview_image, zoom);
+
+ // Add event handler to clean up LvglImage when image is deleted
+ // We need to transfer ownership of the unique_ptr to the event callback
+ LvglImage* raw_image = image.release(); // Release ownership of smart pointer
+ lv_obj_add_event_cb(preview_image, [](lv_event_t* e) {
+ LvglImage* img = (LvglImage*)lv_event_get_user_data(e);
+ if (img != nullptr) {
+ delete img; // Properly release memory by deleting LvglImage object
+ }
+ }, LV_EVENT_DELETE, (void*)raw_image);
+
+ // Calculate actual scaled image dimensions
+ lv_coord_t scaled_width = (img_width * zoom) / 256;
+ lv_coord_t scaled_height = (img_height * zoom) / 256;
+
+ // Set bubble size to be 16 pixels larger than the image (8 pixels on each side)
+ lv_obj_set_width(img_bubble, scaled_width + 16);
+ lv_obj_set_height(img_bubble, scaled_height + 16);
+
+ // Don't grow in flex layout
+ lv_obj_set_style_flex_grow(img_bubble, 0, 0);
+
+ // Center the image within the bubble
+ lv_obj_center(preview_image);
+
+ // Left align the image bubble like assistant messages
+ lv_obj_align(img_bubble, LV_ALIGN_LEFT_MID, 0, 0);
+
+ // Auto-scroll to the image bubble
+ lv_obj_scroll_to_view_recursive(img_bubble, LV_ANIM_ON);
+}
+
+void LcdDisplay::ClearChatMessages() {
+ DisplayLockGuard lock(this);
+ if (content_ == nullptr) {
+ return;
+ }
+
+ // Use lv_obj_clean to delete all children of content_ (chat message bubbles)
+ lv_obj_clean(content_);
+
+ // Reset chat_message_label_ as it has been deleted
+ chat_message_label_ = nullptr;
+
+ // Show the centered AI logo (emoji_label_) again
+ if (emoji_label_ != nullptr) {
+ lv_obj_remove_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
+ }
+
+ ESP_LOGI(TAG, "Chat messages cleared");
+}
+#else
+void LcdDisplay::SetupUI() {
+ // Prevent duplicate calls - if already called, return early
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetupUI() called multiple times, skipping duplicate call");
+ return;
+ }
+
+ Display::SetupUI(); // Mark SetupUI as called
+ DisplayLockGuard lock(this);
+ LvglTheme* lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+ auto large_icon_font = lvgl_theme->large_icon_font()->font();
+
+ auto screen = lv_screen_active();
+ lv_obj_set_style_text_font(screen, text_font, 0);
+ lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_bg_color(screen, lvgl_theme->background_color(), 0);
+
+ /* Container - used as background */
+ container_ = lv_obj_create(screen);
+ lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
+ lv_obj_set_style_radius(container_, 0, 0);
+ lv_obj_set_style_pad_all(container_, 0, 0);
+ lv_obj_set_style_border_width(container_, 0, 0);
+ lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0);
+ lv_obj_set_style_border_color(container_, lvgl_theme->border_color(), 0);
+
+ /* Bottom layer: emoji_box_ - centered display */
+ emoji_box_ = lv_obj_create(screen);
+ lv_obj_set_size(emoji_box_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
+ lv_obj_set_style_bg_opa(emoji_box_, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_pad_all(emoji_box_, 0, 0);
+ lv_obj_set_style_border_width(emoji_box_, 0, 0);
+ lv_obj_align(emoji_box_, LV_ALIGN_CENTER, 0, 0);
+
+ emoji_label_ = lv_label_create(emoji_box_);
+ lv_obj_set_style_text_font(emoji_label_, large_icon_font, 0);
+ lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
+ lv_label_set_text(emoji_label_, FONT_AWESOME_MICROCHIP_AI);
+
+ emoji_image_ = lv_img_create(emoji_box_);
+ lv_obj_center(emoji_image_);
+ lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
+
+ /* Middle layer: preview_image_ - centered display */
+ preview_image_ = lv_image_create(screen);
+ lv_obj_set_size(preview_image_, width_ / 2, height_ / 2);
+ lv_obj_align(preview_image_, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
+
+ /* Layer 1: Top bar - for status icons */
+ top_bar_ = lv_obj_create(screen);
+ lv_obj_set_size(top_bar_, LV_HOR_RES, LV_SIZE_CONTENT);
+ lv_obj_set_style_radius(top_bar_, 0, 0);
+ lv_obj_set_style_bg_opa(top_bar_, LV_OPA_50, 0); // 50% opacity background
+ lv_obj_set_style_bg_color(top_bar_, lvgl_theme->background_color(), 0);
+ lv_obj_set_style_border_width(top_bar_, 0, 0);
+ lv_obj_set_style_pad_all(top_bar_, 0, 0);
+ lv_obj_set_style_pad_top(top_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_style_pad_bottom(top_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_style_pad_left(top_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_style_pad_right(top_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_flex_flow(top_bar_, LV_FLEX_FLOW_ROW);
+ lv_obj_set_flex_align(top_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+ lv_obj_set_scrollbar_mode(top_bar_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_align(top_bar_, LV_ALIGN_TOP_MID, 0, 0);
+
+ // Left icon
+ network_label_ = lv_label_create(top_bar_);
+ lv_label_set_text(network_label_, "");
+ lv_obj_set_style_text_font(network_label_, icon_font, 0);
+ lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0);
+
+ // Right icons container
+ lv_obj_t* right_icons = lv_obj_create(top_bar_);
+ lv_obj_set_size(right_icons, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
+ lv_obj_set_style_bg_opa(right_icons, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_border_width(right_icons, 0, 0);
+ lv_obj_set_style_pad_all(right_icons, 0, 0);
+ lv_obj_set_flex_flow(right_icons, LV_FLEX_FLOW_ROW);
+ lv_obj_set_flex_align(right_icons, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+
+ mute_label_ = lv_label_create(right_icons);
+ lv_label_set_text(mute_label_, "");
+ lv_obj_set_style_text_font(mute_label_, icon_font, 0);
+ lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0);
+
+ battery_label_ = lv_label_create(right_icons);
+ lv_label_set_text(battery_label_, "");
+ lv_obj_set_style_text_font(battery_label_, icon_font, 0);
+ lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_margin_left(battery_label_, lvgl_theme->spacing(2), 0);
+
+ /* Layer 2: Status bar - for center text labels */
+ status_bar_ = lv_obj_create(screen);
+ lv_obj_set_size(status_bar_, LV_HOR_RES, LV_SIZE_CONTENT);
+ lv_obj_set_style_radius(status_bar_, 0, 0);
+ lv_obj_set_style_bg_opa(status_bar_, LV_OPA_TRANSP, 0); // Transparent background
+ lv_obj_set_style_border_width(status_bar_, 0, 0);
+ lv_obj_set_style_pad_all(status_bar_, 0, 0);
+ lv_obj_set_style_pad_top(status_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_style_pad_bottom(status_bar_, lvgl_theme->spacing(2), 0);
+ lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0); // Use absolute positioning
+ lv_obj_align(status_bar_, LV_ALIGN_TOP_MID, 0, 0); // Overlap with top_bar_
+
+ notification_label_ = lv_label_create(status_bar_);
+ lv_obj_set_width(notification_label_, LV_HOR_RES * 0.75);
+ lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0);
+ lv_label_set_text(notification_label_, "");
+ lv_obj_align(notification_label_, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
+
+ status_label_ = lv_label_create(status_bar_);
+ lv_obj_set_width(status_label_, LV_HOR_RES * 0.75);
+ lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0);
+ lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
+ lv_obj_align(status_label_, LV_ALIGN_CENTER, 0, 0);
+
+#if CONFIG_USE_MULTILINE_CHAT_MESSAGE
+ /* Bottom bar - auto height, grows upward with wrapped text */
+ bottom_bar_ = lv_obj_create(screen);
+ lv_obj_set_width(bottom_bar_, LV_HOR_RES);
+ lv_obj_set_height(bottom_bar_, LV_SIZE_CONTENT);
+ lv_obj_set_style_radius(bottom_bar_, 0, 0);
+ lv_obj_set_style_bg_color(bottom_bar_, lvgl_theme->background_color(), 0);
+ lv_obj_set_style_bg_opa(bottom_bar_, LV_OPA_50, 0);
+ lv_obj_set_style_text_color(bottom_bar_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_pad_all(bottom_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_style_border_width(bottom_bar_, 0, 0);
+ lv_obj_set_scrollbar_mode(bottom_bar_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_align(bottom_bar_, LV_ALIGN_BOTTOM_MID, 0, 0);
+
+ /* chat_message_label_ placed in bottom_bar_, multiline wrapped display */
+ chat_message_label_ = lv_label_create(bottom_bar_);
+ lv_label_set_text(chat_message_label_, "");
+ lv_obj_set_width(chat_message_label_, LV_HOR_RES - lvgl_theme->spacing(8));
+ lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP);
+ lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0);
+ lv_obj_align(chat_message_label_, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN); // Hide until there is content
+#else
+ /* Top layer: Bottom bar - fixed height at bottom */
+ bottom_bar_ = lv_obj_create(screen);
+ lv_obj_set_size(bottom_bar_, LV_HOR_RES, text_font->line_height + lvgl_theme->spacing(8));
+ lv_obj_set_style_radius(bottom_bar_, 0, 0);
+ lv_obj_set_style_bg_color(bottom_bar_, lvgl_theme->background_color(), 0);
+ lv_obj_set_style_text_color(bottom_bar_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_pad_all(bottom_bar_, 0, 0);
+ lv_obj_set_style_pad_left(bottom_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_style_pad_right(bottom_bar_, lvgl_theme->spacing(4), 0);
+ lv_obj_set_style_border_width(bottom_bar_, 0, 0);
+ lv_obj_set_scrollbar_mode(bottom_bar_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_align(bottom_bar_, LV_ALIGN_BOTTOM_MID, 0, 0);
+
+ /* chat_message_label_ placed in bottom_bar_, single-line horizontal scroll */
+ chat_message_label_ = lv_label_create(bottom_bar_);
+ lv_label_set_text(chat_message_label_, "");
+ lv_obj_set_width(chat_message_label_, LV_HOR_RES - lvgl_theme->spacing(8));
+ lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0);
+ lv_obj_align(chat_message_label_, LV_ALIGN_CENTER, 0, 0);
+
+ // Start scrolling after a delay (short text won't scroll)
+ static lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_delay(&a, 1000);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+ lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
+ lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
+ lv_obj_add_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN); // Hide until there is content
+#endif
+
+ low_battery_popup_ = lv_obj_create(screen);
+ lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2);
+ lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, -lvgl_theme->spacing(4));
+ lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0);
+ lv_obj_set_style_radius(low_battery_popup_, lvgl_theme->spacing(4), 0);
+
+ low_battery_label_ = lv_label_create(low_battery_popup_);
+ lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
+ lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
+ lv_obj_center(low_battery_label_);
+ lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
+}
+
+void LcdDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
+ DisplayLockGuard lock(this);
+ if (preview_image_ == nullptr) {
+ ESP_LOGE(TAG, "Preview image is not initialized");
+ return;
+ }
+
+ if (image == nullptr) {
+ esp_timer_stop(preview_timer_);
+ lv_obj_remove_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
+ preview_image_cached_.reset();
+ if (gif_controller_) {
+ gif_controller_->Start();
+ }
+ return;
+ }
+
+ preview_image_cached_ = std::move(image);
+ auto img_dsc = preview_image_cached_->image_dsc();
+ lv_image_set_src(preview_image_, img_dsc);
+ if (img_dsc->header.w > 0 && img_dsc->header.h > 0) {
+ // zoom factor 0.5
+ lv_image_set_scale(preview_image_, 128 * width_ / img_dsc->header.w);
+ }
+
+ // Hide emoji_box_
+ if (gif_controller_) {
+ gif_controller_->Stop();
+ }
+ lv_obj_add_flag(emoji_box_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_remove_flag(preview_image_, LV_OBJ_FLAG_HIDDEN);
+ esp_timer_stop(preview_timer_);
+ ESP_ERROR_CHECK(esp_timer_start_once(preview_timer_, PREVIEW_IMAGE_DURATION_MS * 1000));
+}
+
+void LcdDisplay::SetChatMessage(const char* role, const char* content) {
+ if (!setup_ui_called_) {
+ ESP_LOGW(TAG, "SetChatMessage('%s', '%s') called before SetupUI() - message will be lost!", role, content);
+ }
+ DisplayLockGuard lock(this);
+ if (chat_message_label_ == nullptr) {
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetChatMessage('%s', '%s') failed: chat_message_label_ is nullptr (SetupUI() was called but label not created)", role, content);
+ }
+ return;
+ }
+ lv_label_set_text(chat_message_label_, content);
+ // Show bottom_bar_ only when there is content (and subtitle is not globally hidden)
+ if (bottom_bar_ != nullptr) {
+ if (content == nullptr || content[0] == '\0') {
+ lv_obj_add_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN);
+ } else if (!hide_subtitle_) {
+ lv_obj_remove_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN);
+ }
+ }
+#if CONFIG_USE_MULTILINE_CHAT_MESSAGE
+ // Re-align bottom_bar_ after text change so it stays anchored to the bottom
+ // as its height adapts to the wrapped content.
+ if (bottom_bar_ != nullptr) {
+ lv_obj_align(bottom_bar_, LV_ALIGN_BOTTOM_MID, 0, 0);
+ }
+#endif
+}
+
+void LcdDisplay::ClearChatMessages() {
+ DisplayLockGuard lock(this);
+ // In non-wechat mode, just clear the chat message label and hide the bar
+ if (chat_message_label_ != nullptr) {
+ lv_label_set_text(chat_message_label_, "");
+ }
+ if (bottom_bar_ != nullptr) {
+ lv_obj_add_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN);
+ }
+}
+#endif
+
+void LcdDisplay::SetEmotion(const char* emotion) {
+ if (!setup_ui_called_) {
+ ESP_LOGW(TAG, "SetEmotion('%s') called before SetupUI() - emotion will not be displayed!", emotion);
+ }
+ if (emoji_image_ == nullptr) {
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetEmotion('%s') failed: emoji_image_ is nullptr (SetupUI() was called but emoji image not created)", emotion);
+ }
+ return;
+ }
+
+ auto emoji_collection = static_cast<LvglTheme*>(current_theme_)->emoji_collection();
+ auto image = emoji_collection != nullptr ? emoji_collection->GetEmojiImage(emotion) : nullptr;
+ if (image == nullptr) {
+ const char* utf8 = font_awesome_get_utf8(emotion);
+ if (utf8 != nullptr && emoji_label_ != nullptr) {
+ DisplayLockGuard lock(this);
+ if (gif_controller_) {
+ gif_controller_->Stop();
+ gif_controller_.reset();
+ }
+ lv_label_set_text(emoji_label_, utf8);
+ lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_remove_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
+ }
+ return;
+ }
+
+ DisplayLockGuard lock(this);
+ // Stop any running GIF animation in the same lock scope as setting new image
+ // to prevent LVGL from accessing freed image data between operations
+ if (gif_controller_) {
+ gif_controller_->Stop();
+ gif_controller_.reset();
+ }
+ if (image->IsGif()) {
+ // Create new GIF controller
+ gif_controller_ = std::make_unique<LvglGif>(image->image_dsc());
+
+ if (gif_controller_->IsLoaded()) {
+ // Set up frame update callback
+ gif_controller_->SetFrameCallback([this]() {
+ lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
+ });
+
+ // Set initial frame and start animation
+ lv_image_set_src(emoji_image_, gif_controller_->image_dsc());
+ gif_controller_->Start();
+
+ // Show GIF, hide others
+ lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
+ } else {
+ ESP_LOGE(TAG, "Failed to load GIF for emotion: %s", emotion);
+ gif_controller_.reset();
+ }
+ } else {
+ lv_image_set_src(emoji_image_, image->image_dsc());
+ lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_remove_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
+ }
+
+#if CONFIG_USE_WECHAT_MESSAGE_STYLE
+ // In WeChat message style, if emotion is neutral, don't display it
+ uint32_t child_count = lv_obj_get_child_cnt(content_);
+ if (strcmp(emotion, "neutral") == 0 && child_count > 0) {
+ // Stop GIF animation if running
+ if (gif_controller_) {
+ gif_controller_->Stop();
+ gif_controller_.reset();
+ }
+
+ lv_obj_add_flag(emoji_image_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(emoji_label_, LV_OBJ_FLAG_HIDDEN);
+ }
+#endif
+}
+
+void LcdDisplay::SetTheme(Theme* theme) {
+ DisplayLockGuard lock(this);
+
+ auto lvgl_theme = static_cast<LvglTheme*>(theme);
+
+ // Get the active screen
+ lv_obj_t* screen = lv_screen_active();
+
+ // Set font
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+ auto large_icon_font = lvgl_theme->large_icon_font()->font();
+
+ if (text_font->line_height >= 40) {
+ lv_obj_set_style_text_font(mute_label_, large_icon_font, 0);
+ lv_obj_set_style_text_font(battery_label_, large_icon_font, 0);
+ lv_obj_set_style_text_font(network_label_, large_icon_font, 0);
+ } else {
+ lv_obj_set_style_text_font(mute_label_, icon_font, 0);
+ lv_obj_set_style_text_font(battery_label_, icon_font, 0);
+ lv_obj_set_style_text_font(network_label_, icon_font, 0);
+ }
+
+ // Set parent text color
+ lv_obj_set_style_text_font(screen, text_font, 0);
+ lv_obj_set_style_text_color(screen, lvgl_theme->text_color(), 0);
+
+ // Set background image
+ if (lvgl_theme->background_image() != nullptr) {
+ lv_obj_set_style_bg_image_src(container_, lvgl_theme->background_image()->image_dsc(), 0);
+ } else {
+ lv_obj_set_style_bg_image_src(container_, nullptr, 0);
+ lv_obj_set_style_bg_color(container_, lvgl_theme->background_color(), 0);
+ }
+
+ // Update top bar background color with 50% opacity
+ if (top_bar_ != nullptr) {
+ lv_obj_set_style_bg_opa(top_bar_, LV_OPA_50, 0);
+ lv_obj_set_style_bg_color(top_bar_, lvgl_theme->background_color(), 0);
+ }
+
+ // Update status bar elements
+ lv_obj_set_style_text_color(network_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_text_color(status_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_text_color(notification_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_text_color(mute_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_text_color(battery_label_, lvgl_theme->text_color(), 0);
+ lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
+
+ // If we have the chat message style, update all message bubbles
+#if CONFIG_USE_WECHAT_MESSAGE_STYLE
+ // Set content background opacity
+ lv_obj_set_style_bg_opa(content_, LV_OPA_TRANSP, 0);
+
+ // Iterate through all children of content (message containers or bubbles)
+ uint32_t child_count = lv_obj_get_child_cnt(content_);
+ for (uint32_t i = 0; i < child_count; i++) {
+ lv_obj_t* obj = lv_obj_get_child(content_, i);
+ if (obj == nullptr) continue;
+
+ lv_obj_t* bubble = nullptr;
+
+ // Check if this object is a container or bubble
+ // If it's a container (user or system message), get its child as bubble
+ // If it's a bubble (assistant message), use it directly
+ if (lv_obj_get_child_cnt(obj) > 0) {
+ // Might be a container, check if it's a user or system message container
+ // User and system message containers are transparent
+ lv_opa_t bg_opa = lv_obj_get_style_bg_opa(obj, LV_PART_MAIN);
+ if (bg_opa == LV_OPA_TRANSP) {
+ // This is a user or system message container
+ bubble = lv_obj_get_child(obj, 0);
+ } else {
+ // This might be an assistant message bubble itself
+ bubble = obj;
+ }
+ } else {
+ // No child elements, might be other UI elements, skip
+ continue;
+ }
+
+ if (bubble == nullptr) continue;
+
+ // Use saved user data to identify bubble type
+ void* bubble_type_ptr = lv_obj_get_user_data(bubble);
+ if (bubble_type_ptr != nullptr) {
+ const char* bubble_type = static_cast<const char*>(bubble_type_ptr);
+
+ // Apply correct color based on bubble type
+ if (strcmp(bubble_type, "user") == 0) {
+ lv_obj_set_style_bg_color(bubble, lvgl_theme->user_bubble_color(), 0);
+ } else if (strcmp(bubble_type, "assistant") == 0) {
+ lv_obj_set_style_bg_color(bubble, lvgl_theme->assistant_bubble_color(), 0);
+ } else if (strcmp(bubble_type, "system") == 0) {
+ lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0);
+ } else if (strcmp(bubble_type, "image") == 0) {
+ lv_obj_set_style_bg_color(bubble, lvgl_theme->system_bubble_color(), 0);
+ }
+
+ // Update border color
+ lv_obj_set_style_border_color(bubble, lvgl_theme->border_color(), 0);
+
+ // Update text color for the message
+ if (lv_obj_get_child_cnt(bubble) > 0) {
+ lv_obj_t* text = lv_obj_get_child(bubble, 0);
+ if (text != nullptr) {
+ // Set text color based on bubble type
+ if (strcmp(bubble_type, "system") == 0) {
+ lv_obj_set_style_text_color(text, lvgl_theme->system_text_color(), 0);
+ } else {
+ lv_obj_set_style_text_color(text, lvgl_theme->text_color(), 0);
+ }
+ }
+ }
+ } else {
+ ESP_LOGW(TAG, "child[%lu] Bubble type is not found", i);
+ }
+ }
+#else
+ // Simple UI mode - just update the main chat message
+ if (chat_message_label_ != nullptr) {
+ lv_obj_set_style_text_color(chat_message_label_, lvgl_theme->text_color(), 0);
+ }
+
+ if (emoji_label_ != nullptr) {
+ lv_obj_set_style_text_color(emoji_label_, lvgl_theme->text_color(), 0);
+ }
+
+ // Update bottom bar background color with 50% opacity
+ if (bottom_bar_ != nullptr) {
+ lv_obj_set_style_bg_opa(bottom_bar_, LV_OPA_50, 0);
+ lv_obj_set_style_bg_color(bottom_bar_, lvgl_theme->background_color(), 0);
+ }
+#endif
+
+ // Update low battery popup
+ lv_obj_set_style_bg_color(low_battery_popup_, lvgl_theme->low_battery_color(), 0);
+
+ // No errors occurred. Save theme to settings
+ Display::SetTheme(lvgl_theme);
+}
+
+void LcdDisplay::SetHideSubtitle(bool hide) {
+ DisplayLockGuard lock(this);
+ hide_subtitle_ = hide;
+
+ // Immediately update UI visibility based on the setting
+ if (bottom_bar_ != nullptr) {
+ if (hide) {
+ lv_obj_add_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN);
+ } else {
+ // Only show if there is actual content to display
+ const char* text = (chat_message_label_ != nullptr) ? lv_label_get_text(chat_message_label_) : nullptr;
+ if (text != nullptr && text[0] != '\0') {
+ lv_obj_remove_flag(bottom_bar_, LV_OBJ_FLAG_HIDDEN);
+ }
+ }
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lcd_display.h b/xiaozhi-esp32-2.2.6/main/display/lcd_display.h
new file mode 100644
index 0000000..e5be55e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lcd_display.h
@@ -0,0 +1,85 @@
+#ifndef LCD_DISPLAY_H
+#define LCD_DISPLAY_H
+
+#include "lvgl_display.h"
+#include "gif/lvgl_gif.h"
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+#include <font_emoji.h>
+
+#include <atomic>
+#include <memory>
+
+#define PREVIEW_IMAGE_DURATION_MS 5000
+
+
+class LcdDisplay : public LvglDisplay {
+protected:
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ lv_draw_buf_t draw_buf_;
+ lv_obj_t* top_bar_ = nullptr;
+ lv_obj_t* status_bar_ = nullptr;
+ lv_obj_t* content_ = nullptr;
+ lv_obj_t* container_ = nullptr;
+ lv_obj_t* side_bar_ = nullptr;
+ lv_obj_t* bottom_bar_ = nullptr;
+ lv_obj_t* preview_image_ = nullptr;
+ lv_obj_t* emoji_label_ = nullptr;
+ lv_obj_t* emoji_image_ = nullptr;
+ std::unique_ptr<LvglGif> gif_controller_ = nullptr;
+ lv_obj_t* emoji_box_ = nullptr;
+ lv_obj_t* chat_message_label_ = nullptr;
+ esp_timer_handle_t preview_timer_ = nullptr;
+ std::unique_ptr<LvglImage> preview_image_cached_ = nullptr;
+ bool hide_subtitle_ = false; // Control whether to hide chat messages/subtitles
+
+ void InitializeLcdThemes();
+ virtual bool Lock(int timeout_ms = 0) override;
+ virtual void Unlock() override;
+
+protected:
+ // Add protected constructor
+ LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height);
+
+public:
+ ~LcdDisplay();
+ virtual void SetEmotion(const char* emotion) override;
+ virtual void SetChatMessage(const char* role, const char* content) override;
+ virtual void ClearChatMessages() override;
+ virtual void SetPreviewImage(std::unique_ptr<LvglImage> image) override;
+ virtual void SetupUI() override;
+ // Add theme switching function
+ virtual void SetTheme(Theme* theme) override;
+
+ // Set whether to hide chat messages/subtitles
+ void SetHideSubtitle(bool hide);
+};
+
+// SPI LCD display
+class SpiLcdDisplay : public LcdDisplay {
+public:
+ SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy);
+};
+
+// RGB LCD display
+class RgbLcdDisplay : public LcdDisplay {
+public:
+ RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy);
+};
+
+// MIPI LCD display
+class MipiLcdDisplay : public LcdDisplay {
+public:
+ MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, int offset_x, int offset_y,
+ bool mirror_x, bool mirror_y, bool swap_xy);
+};
+
+#endif // LCD_DISPLAY_H
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.cc b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.cc
new file mode 100644
index 0000000..c9a1a60
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.cc
@@ -0,0 +1,123 @@
+#include "emoji_collection.h"
+
+#include <esp_log.h>
+#include <unordered_map>
+#include <string>
+
+#define TAG "EmojiCollection"
+
+void EmojiCollection::AddEmoji(const std::string& name, LvglImage* image) {
+ emoji_collection_[name] = image;
+}
+
+const LvglImage* EmojiCollection::GetEmojiImage(const char* name) {
+ auto it = emoji_collection_.find(name);
+ if (it != emoji_collection_.end()) {
+ return it->second;
+ }
+
+ ESP_LOGW(TAG, "Emoji not found: %s", name);
+ return nullptr;
+}
+
+EmojiCollection::~EmojiCollection() {
+ for (auto it = emoji_collection_.begin(); it != emoji_collection_.end(); ++it) {
+ delete it->second;
+ }
+ emoji_collection_.clear();
+}
+
+// These are declared in xiaozhi-fonts/src/font_emoji_32.c
+extern const lv_image_dsc_t emoji_1f636_32; // neutral
+extern const lv_image_dsc_t emoji_1f642_32; // happy
+extern const lv_image_dsc_t emoji_1f606_32; // laughing
+extern const lv_image_dsc_t emoji_1f602_32; // funny
+extern const lv_image_dsc_t emoji_1f614_32; // sad
+extern const lv_image_dsc_t emoji_1f620_32; // angry
+extern const lv_image_dsc_t emoji_1f62d_32; // crying
+extern const lv_image_dsc_t emoji_1f60d_32; // loving
+extern const lv_image_dsc_t emoji_1f633_32; // embarrassed
+extern const lv_image_dsc_t emoji_1f62f_32; // surprised
+extern const lv_image_dsc_t emoji_1f631_32; // shocked
+extern const lv_image_dsc_t emoji_1f914_32; // thinking
+extern const lv_image_dsc_t emoji_1f609_32; // winking
+extern const lv_image_dsc_t emoji_1f60e_32; // cool
+extern const lv_image_dsc_t emoji_1f60c_32; // relaxed
+extern const lv_image_dsc_t emoji_1f924_32; // delicious
+extern const lv_image_dsc_t emoji_1f618_32; // kissy
+extern const lv_image_dsc_t emoji_1f60f_32; // confident
+extern const lv_image_dsc_t emoji_1f634_32; // sleepy
+extern const lv_image_dsc_t emoji_1f61c_32; // silly
+extern const lv_image_dsc_t emoji_1f644_32; // confused
+
+Twemoji32::Twemoji32() {
+ AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_32));
+ AddEmoji("happy", new LvglSourceImage(&emoji_1f642_32));
+ AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_32));
+ AddEmoji("funny", new LvglSourceImage(&emoji_1f602_32));
+ AddEmoji("sad", new LvglSourceImage(&emoji_1f614_32));
+ AddEmoji("angry", new LvglSourceImage(&emoji_1f620_32));
+ AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_32));
+ AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_32));
+ AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_32));
+ AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_32));
+ AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_32));
+ AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_32));
+ AddEmoji("winking", new LvglSourceImage(&emoji_1f609_32));
+ AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_32));
+ AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_32));
+ AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_32));
+ AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_32));
+ AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_32));
+ AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_32));
+ AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_32));
+ AddEmoji("confused", new LvglSourceImage(&emoji_1f644_32));
+}
+
+
+// These are declared in xiaozhi-fonts/src/font_emoji_64.c
+extern const lv_image_dsc_t emoji_1f636_64; // neutral
+extern const lv_image_dsc_t emoji_1f642_64; // happy
+extern const lv_image_dsc_t emoji_1f606_64; // laughing
+extern const lv_image_dsc_t emoji_1f602_64; // funny
+extern const lv_image_dsc_t emoji_1f614_64; // sad
+extern const lv_image_dsc_t emoji_1f620_64; // angry
+extern const lv_image_dsc_t emoji_1f62d_64; // crying
+extern const lv_image_dsc_t emoji_1f60d_64; // loving
+extern const lv_image_dsc_t emoji_1f633_64; // embarrassed
+extern const lv_image_dsc_t emoji_1f62f_64; // surprised
+extern const lv_image_dsc_t emoji_1f631_64; // shocked
+extern const lv_image_dsc_t emoji_1f914_64; // thinking
+extern const lv_image_dsc_t emoji_1f609_64; // winking
+extern const lv_image_dsc_t emoji_1f60e_64; // cool
+extern const lv_image_dsc_t emoji_1f60c_64; // relaxed
+extern const lv_image_dsc_t emoji_1f924_64; // delicious
+extern const lv_image_dsc_t emoji_1f618_64; // kissy
+extern const lv_image_dsc_t emoji_1f60f_64; // confident
+extern const lv_image_dsc_t emoji_1f634_64; // sleepy
+extern const lv_image_dsc_t emoji_1f61c_64; // silly
+extern const lv_image_dsc_t emoji_1f644_64; // confused
+
+Twemoji64::Twemoji64() {
+ AddEmoji("neutral", new LvglSourceImage(&emoji_1f636_64));
+ AddEmoji("happy", new LvglSourceImage(&emoji_1f642_64));
+ AddEmoji("laughing", new LvglSourceImage(&emoji_1f606_64));
+ AddEmoji("funny", new LvglSourceImage(&emoji_1f602_64));
+ AddEmoji("sad", new LvglSourceImage(&emoji_1f614_64));
+ AddEmoji("angry", new LvglSourceImage(&emoji_1f620_64));
+ AddEmoji("crying", new LvglSourceImage(&emoji_1f62d_64));
+ AddEmoji("loving", new LvglSourceImage(&emoji_1f60d_64));
+ AddEmoji("embarrassed", new LvglSourceImage(&emoji_1f633_64));
+ AddEmoji("surprised", new LvglSourceImage(&emoji_1f62f_64));
+ AddEmoji("shocked", new LvglSourceImage(&emoji_1f631_64));
+ AddEmoji("thinking", new LvglSourceImage(&emoji_1f914_64));
+ AddEmoji("winking", new LvglSourceImage(&emoji_1f609_64));
+ AddEmoji("cool", new LvglSourceImage(&emoji_1f60e_64));
+ AddEmoji("relaxed", new LvglSourceImage(&emoji_1f60c_64));
+ AddEmoji("delicious", new LvglSourceImage(&emoji_1f924_64));
+ AddEmoji("kissy", new LvglSourceImage(&emoji_1f618_64));
+ AddEmoji("confident", new LvglSourceImage(&emoji_1f60f_64));
+ AddEmoji("sleepy", new LvglSourceImage(&emoji_1f634_64));
+ AddEmoji("silly", new LvglSourceImage(&emoji_1f61c_64));
+ AddEmoji("confused", new LvglSourceImage(&emoji_1f644_64));
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.h
new file mode 100644
index 0000000..c9eac05
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/emoji_collection.h
@@ -0,0 +1,34 @@
+#ifndef EMOJI_COLLECTION_H
+#define EMOJI_COLLECTION_H
+
+#include "lvgl_image.h"
+
+#include <lvgl.h>
+
+#include <map>
+#include <string>
+#include <memory>
+
+
+// Define interface for emoji collection
+class EmojiCollection {
+public:
+ virtual void AddEmoji(const std::string& name, LvglImage* image);
+ virtual const LvglImage* GetEmojiImage(const char* name);
+ virtual ~EmojiCollection();
+
+private:
+ std::map<std::string, LvglImage*> emoji_collection_;
+};
+
+class Twemoji32 : public EmojiCollection {
+public:
+ Twemoji32();
+};
+
+class Twemoji64 : public EmojiCollection {
+public:
+ Twemoji64();
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/LICENSE.txt b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/LICENSE.txt
new file mode 100644
index 0000000..c53d519
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/LICENSE.txt
@@ -0,0 +1,2 @@
+All of the source code and documentation for gifdec is released into the
+public domain and provided without warranty of any kind.
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/README.md b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/README.md
new file mode 100644
index 0000000..935cfeb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/README.md
@@ -0,0 +1,17 @@
+# 璇存槑 / Description
+
+## 涓枃
+
+鏈洰褰曚唬鐮佺Щ妞嶈嚜 LVGL 鐨� GIF 绋嬪簭銆�
+
+涓昏淇鍜屾敼杩涳細
+- 淇浜嗛�忔槑鑳屾櫙闂
+- 鍏煎浜� 87a 鐗堟湰鐨� GIF 鏍煎紡
+
+## English
+
+The code in this directory is ported from LVGL's GIF program.
+
+Main fixes and improvements:
+- Fixed transparent background issues
+- Added compatibility for GIF 87a version format
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.c b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.c
new file mode 100644
index 0000000..cab83b9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.c
@@ -0,0 +1,821 @@
+#include "gifdec.h"
+
+#include <stdlib.h>
+#include <string.h>
+#include <stdbool.h>
+#include <esp_log.h>
+
+#define TAG "GIF"
+
+#define MIN(A, B) ((A) < (B) ? (A) : (B))
+#define MAX(A, B) ((A) > (B) ? (A) : (B))
+
+typedef struct Entry {
+ uint16_t length;
+ uint16_t prefix;
+ uint8_t suffix;
+} Entry;
+
+typedef struct Table {
+ int bulk;
+ int nentries;
+ Entry * entries;
+} Table;
+
+#if LV_GIF_CACHE_DECODE_DATA
+#define LZW_MAXBITS 12
+#define LZW_TABLE_SIZE (1 << LZW_MAXBITS)
+#define LZW_CACHE_SIZE (LZW_TABLE_SIZE * 4)
+#endif
+
+static gd_GIF * gif_open(gd_GIF * gif);
+static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file);
+static inline void f_gif_read(gd_GIF * gif, void * buf, size_t len);
+static inline int f_gif_seek(gd_GIF * gif, size_t pos, int k);
+static void f_gif_close(gd_GIF * gif);
+
+#if LV_USE_DRAW_SW_ASM == LV_DRAW_SW_ASM_HELIUM
+ #include "gifdec_mve.h"
+#endif
+
+static uint16_t
+read_num(gd_GIF * gif)
+{
+ uint8_t bytes[2];
+
+ f_gif_read(gif, bytes, 2);
+ return bytes[0] + (((uint16_t) bytes[1]) << 8);
+}
+
+gd_GIF *
+gd_open_gif_file(const char * fname)
+{
+ gd_GIF gif_base;
+ memset(&gif_base, 0, sizeof(gif_base));
+
+ bool res = f_gif_open(&gif_base, fname, true);
+ if(!res) return NULL;
+
+ return gif_open(&gif_base);
+}
+
+gd_GIF *
+gd_open_gif_data(const void * data)
+{
+ gd_GIF gif_base;
+ memset(&gif_base, 0, sizeof(gif_base));
+
+ bool res = f_gif_open(&gif_base, data, false);
+ if(!res) return NULL;
+
+ return gif_open(&gif_base);
+}
+
+static gd_GIF * gif_open(gd_GIF * gif_base)
+{
+ uint8_t sigver[3];
+ uint16_t width, height, depth;
+ uint8_t fdsz, bgidx, aspect;
+ uint8_t * bgcolor;
+ int gct_sz;
+ gd_GIF * gif = NULL;
+
+ /* Header */
+ f_gif_read(gif_base, sigver, 3);
+ if(memcmp(sigver, "GIF", 3) != 0) {
+ ESP_LOGW(TAG, "invalid signature");
+ goto fail;
+ }
+ /* Version */
+ f_gif_read(gif_base, sigver, 3);
+ if(memcmp(sigver, "89a", 3) != 0 && memcmp(sigver, "87a", 3) != 0) {
+ ESP_LOGW(TAG, "invalid version");
+ goto fail;
+ }
+ /* Width x Height */
+ width = read_num(gif_base);
+ height = read_num(gif_base);
+ /* FDSZ */
+ f_gif_read(gif_base, &fdsz, 1);
+ /* Presence of GCT */
+ if(!(fdsz & 0x80)) {
+ ESP_LOGW(TAG, "no global color table");
+ goto fail;
+ }
+ /* Color Space's Depth */
+ depth = ((fdsz >> 4) & 7) + 1;
+ /* Ignore Sort Flag. */
+ /* GCT Size */
+ gct_sz = 1 << ((fdsz & 0x07) + 1);
+ /* Background Color Index */
+ f_gif_read(gif_base, &bgidx, 1);
+ /* Aspect Ratio */
+ f_gif_read(gif_base, &aspect, 1);
+ /* Create gd_GIF Structure. */
+ if(0 == width || 0 == height){
+ ESP_LOGW(TAG, "Zero size image");
+ goto fail;
+ }
+#if LV_GIF_CACHE_DECODE_DATA
+ if(0 == (INT_MAX - sizeof(gd_GIF) - LZW_CACHE_SIZE) / width / height / 5){
+ ESP_LOGW(TAG, "Image dimensions are too large");
+ goto fail;
+ }
+ gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height + LZW_CACHE_SIZE);
+#else
+ if(0 == (INT_MAX - sizeof(gd_GIF)) / width / height / 5){
+ ESP_LOGW(TAG, "Image dimensions are too large");
+ goto fail;
+ }
+ gif = lv_malloc(sizeof(gd_GIF) + 5 * width * height);
+#endif
+ if(!gif) goto fail;
+ memcpy(gif, gif_base, sizeof(gd_GIF));
+ gif->width = width;
+ gif->height = height;
+ gif->depth = depth;
+ /* Read GCT */
+ gif->gct.size = gct_sz;
+ f_gif_read(gif, gif->gct.colors, 3 * gif->gct.size);
+ gif->palette = &gif->gct;
+ gif->bgindex = bgidx;
+ gif->canvas = (uint8_t *) &gif[1];
+ gif->frame = &gif->canvas[4 * width * height];
+ if(gif->bgindex) {
+ memset(gif->frame, gif->bgindex, gif->width * gif->height);
+ }
+ bgcolor = &gif->palette->colors[gif->bgindex * 3];
+ #if LV_GIF_CACHE_DECODE_DATA
+ gif->lzw_cache = gif->frame + width * height;
+ #endif
+
+#ifdef GIFDEC_FILL_BG
+ GIFDEC_FILL_BG(gif->canvas, gif->width * gif->height, 1, gif->width * gif->height, bgcolor, 0x00);
+#else
+ for(int i = 0; i < gif->width * gif->height; i++) {
+ gif->canvas[i * 4 + 0] = *(bgcolor + 2);
+ gif->canvas[i * 4 + 1] = *(bgcolor + 1);
+ gif->canvas[i * 4 + 2] = *(bgcolor + 0);
+ gif->canvas[i * 4 + 3] = 0x00; // 鍒濆鍖栦负閫忔槑锛岃绗竴甯ф牴鎹嚜宸辩殑閫忔槑搴﹁缃潵娓叉煋
+ }
+#endif
+ gif->anim_start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ gif->loop_count = -1;
+ goto ok;
+fail:
+ f_gif_close(gif_base);
+ok:
+ return gif;
+}
+
+static void
+discard_sub_blocks(gd_GIF * gif)
+{
+ uint8_t size;
+
+ do {
+ f_gif_read(gif, &size, 1);
+ f_gif_seek(gif, size, LV_FS_SEEK_CUR);
+ } while(size);
+}
+
+static void
+read_plain_text_ext(gd_GIF * gif)
+{
+ if(gif->plain_text) {
+ uint16_t tx, ty, tw, th;
+ uint8_t cw, ch, fg, bg;
+ size_t sub_block;
+ f_gif_seek(gif, 1, LV_FS_SEEK_CUR); /* block size = 12 */
+ tx = read_num(gif);
+ ty = read_num(gif);
+ tw = read_num(gif);
+ th = read_num(gif);
+ f_gif_read(gif, &cw, 1);
+ f_gif_read(gif, &ch, 1);
+ f_gif_read(gif, &fg, 1);
+ f_gif_read(gif, &bg, 1);
+ sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ gif->plain_text(gif, tx, ty, tw, th, cw, ch, fg, bg);
+ f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
+ }
+ else {
+ /* Discard plain text metadata. */
+ f_gif_seek(gif, 13, LV_FS_SEEK_CUR);
+ }
+ /* Discard plain text sub-blocks. */
+ discard_sub_blocks(gif);
+}
+
+static void
+read_graphic_control_ext(gd_GIF * gif)
+{
+ uint8_t rdit;
+
+ /* Discard block size (always 0x04). */
+ f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
+ f_gif_read(gif, &rdit, 1);
+ gif->gce.disposal = (rdit >> 2) & 3;
+ gif->gce.input = rdit & 2;
+ gif->gce.transparency = rdit & 1;
+ gif->gce.delay = read_num(gif);
+ f_gif_read(gif, &gif->gce.tindex, 1);
+ /* Skip block terminator. */
+ f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
+}
+
+static void
+read_comment_ext(gd_GIF * gif)
+{
+ if(gif->comment) {
+ size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ gif->comment(gif);
+ f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
+ }
+ /* Discard comment sub-blocks. */
+ discard_sub_blocks(gif);
+}
+
+static void
+read_application_ext(gd_GIF * gif)
+{
+ char app_id[8];
+ char app_auth_code[3];
+ uint16_t loop_count;
+
+ /* Discard block size (always 0x0B). */
+ f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
+ /* Application Identifier. */
+ f_gif_read(gif, app_id, 8);
+ /* Application Authentication Code. */
+ f_gif_read(gif, app_auth_code, 3);
+ if(!strncmp(app_id, "NETSCAPE", sizeof(app_id))) {
+ /* Discard block size (0x03) and constant byte (0x01). */
+ f_gif_seek(gif, 2, LV_FS_SEEK_CUR);
+ loop_count = read_num(gif);
+ if(gif->loop_count < 0) {
+ if(loop_count == 0) {
+ gif->loop_count = 0;
+ }
+ else {
+ gif->loop_count = loop_count + 1;
+ }
+ }
+ /* Skip block terminator. */
+ f_gif_seek(gif, 1, LV_FS_SEEK_CUR);
+ }
+ else if(gif->application) {
+ size_t sub_block = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ gif->application(gif, app_id, app_auth_code);
+ f_gif_seek(gif, sub_block, LV_FS_SEEK_SET);
+ discard_sub_blocks(gif);
+ }
+ else {
+ discard_sub_blocks(gif);
+ }
+}
+
+static void
+read_ext(gd_GIF * gif)
+{
+ uint8_t label;
+
+ f_gif_read(gif, &label, 1);
+ switch(label) {
+ case 0x01:
+ read_plain_text_ext(gif);
+ break;
+ case 0xF9:
+ read_graphic_control_ext(gif);
+ break;
+ case 0xFE:
+ read_comment_ext(gif);
+ break;
+ case 0xFF:
+ read_application_ext(gif);
+ break;
+ default:
+ ESP_LOGW(TAG, "unknown extension: %02X\n", label);
+ }
+}
+
+static uint16_t
+get_key(gd_GIF *gif, int key_size, uint8_t *sub_len, uint8_t *shift, uint8_t *byte)
+{
+ int bits_read;
+ int rpad;
+ int frag_size;
+ uint16_t key;
+
+ key = 0;
+ for (bits_read = 0; bits_read < key_size; bits_read += frag_size) {
+ rpad = (*shift + bits_read) % 8;
+ if (rpad == 0) {
+ /* Update byte. */
+ if (*sub_len == 0) {
+ f_gif_read(gif, sub_len, 1); /* Must be nonzero! */
+ if (*sub_len == 0) return 0x1000;
+ }
+ f_gif_read(gif, byte, 1);
+ (*sub_len)--;
+ }
+ frag_size = MIN(key_size - bits_read, 8 - rpad);
+ key |= ((uint16_t) ((*byte) >> rpad)) << bits_read;
+ }
+ /* Clear extra bits to the left. */
+ key &= (1 << key_size) - 1;
+ *shift = (*shift + key_size) % 8;
+ return key;
+}
+
+#if LV_GIF_CACHE_DECODE_DATA
+/* Decompress image pixels.
+ * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */
+static int
+read_image_data(gd_GIF *gif, int interlace)
+{
+ uint8_t sub_len, shift, byte;
+ int ret = 0;
+ int key_size;
+ int y, pass, linesize;
+ uint8_t *ptr = NULL;
+ uint8_t *ptr_row_start = NULL;
+ uint8_t *ptr_base = NULL;
+ size_t start, end;
+ uint16_t key, clear_code, stop_code, curr_code;
+ int frm_off, frm_size,curr_size,top_slot,new_codes,slot;
+ /* The first value of the value sequence corresponding to key */
+ int first_value;
+ int last_key;
+ uint8_t *sp = NULL;
+ uint8_t *p_stack = NULL;
+ uint8_t *p_suffix = NULL;
+ uint16_t *p_prefix = NULL;
+
+ /* get initial key size and clear code, stop code */
+ f_gif_read(gif, &byte, 1);
+ key_size = (int) byte;
+ clear_code = 1 << key_size;
+ stop_code = clear_code + 1;
+ key = 0;
+
+ start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ discard_sub_blocks(gif);
+ end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ f_gif_seek(gif, start, LV_FS_SEEK_SET);
+
+ linesize = gif->width;
+ ptr_base = &gif->frame[gif->fy * linesize + gif->fx];
+ ptr_row_start = ptr_base;
+ ptr = ptr_row_start;
+ sub_len = shift = 0;
+ /* decoder */
+ pass = 0;
+ y = 0;
+ p_stack = gif->lzw_cache;
+ p_suffix = gif->lzw_cache + LZW_TABLE_SIZE;
+ p_prefix = (uint16_t*)(gif->lzw_cache + LZW_TABLE_SIZE * 2);
+ frm_off = 0;
+ frm_size = gif->fw * gif->fh;
+ curr_size = key_size + 1;
+ top_slot = 1 << curr_size;
+ new_codes = clear_code + 2;
+ slot = new_codes;
+ first_value = -1;
+ last_key = -1;
+ sp = p_stack;
+
+ while (frm_off < frm_size) {
+ /* copy data to frame buffer */
+ while (sp > p_stack) {
+ if(frm_off >= frm_size){
+ ESP_LOGW(TAG, "LZW table token overflows the frame buffer");
+ return -1;
+ }
+ *ptr++ = *(--sp);
+ frm_off += 1;
+ /* read one line */
+ if ((ptr - ptr_row_start) == gif->fw) {
+ if (interlace) {
+ switch(pass) {
+ case 0:
+ case 1:
+ y += 8;
+ ptr_row_start += linesize * 8;
+ break;
+ case 2:
+ y += 4;
+ ptr_row_start += linesize * 4;
+ break;
+ case 3:
+ y += 2;
+ ptr_row_start += linesize * 2;
+ break;
+ default:
+ break;
+ }
+ while (y >= gif->fh) {
+ y = 4 >> pass;
+ ptr_row_start = ptr_base + linesize * y;
+ pass++;
+ }
+ } else {
+ ptr_row_start += linesize;
+ }
+ ptr = ptr_row_start;
+ }
+ }
+
+ key = get_key(gif, curr_size, &sub_len, &shift, &byte);
+
+ if (key == stop_code || key >= LZW_TABLE_SIZE)
+ break;
+
+ if (key == clear_code) {
+ curr_size = key_size + 1;
+ slot = new_codes;
+ top_slot = 1 << curr_size;
+ first_value = last_key = -1;
+ sp = p_stack;
+ continue;
+ }
+
+ curr_code = key;
+ /*
+ * If the current code is a code that will be added to the decoding
+ * dictionary, it is composed of the data list corresponding to the
+ * previous key and its first data.
+ * */
+ if (curr_code == slot && first_value >= 0) {
+ *sp++ = first_value;
+ curr_code = last_key;
+ }else if(curr_code >= slot)
+ break;
+
+ while (curr_code >= new_codes) {
+ *sp++ = p_suffix[curr_code];
+ curr_code = p_prefix[curr_code];
+ }
+ *sp++ = curr_code;
+
+ /* Add code to decoding dictionary */
+ if (slot < top_slot && last_key >= 0) {
+ p_suffix[slot] = curr_code;
+ p_prefix[slot++] = last_key;
+ }
+ first_value = curr_code;
+ last_key = key;
+ if (slot >= top_slot) {
+ if (curr_size < LZW_MAXBITS) {
+ top_slot <<= 1;
+ curr_size += 1;
+ }
+ }
+ }
+
+ if (key == stop_code) f_gif_read(gif, &sub_len, 1); /* Must be zero! */
+ f_gif_seek(gif, end, LV_FS_SEEK_SET);
+ return ret;
+}
+#else
+static Table *
+new_table(int key_size)
+{
+ int key;
+ int init_bulk = MAX(1 << (key_size + 1), 0x100);
+ Table * table = lv_malloc(sizeof(*table) + sizeof(Entry) * init_bulk);
+ if(table) {
+ table->bulk = init_bulk;
+ table->nentries = (1 << key_size) + 2;
+ table->entries = (Entry *) &table[1];
+ for(key = 0; key < (1 << key_size); key++)
+ table->entries[key] = (Entry) {
+ 1, 0xFFF, key
+ };
+ }
+ return table;
+}
+
+/* Add table entry. Return value:
+ * 0 on success
+ * +1 if key size must be incremented after this addition
+ * -1 if could not realloc table */
+static int
+add_entry(Table ** tablep, uint16_t length, uint16_t prefix, uint8_t suffix)
+{
+ Table * table = *tablep;
+ if(table->nentries == table->bulk) {
+ table->bulk *= 2;
+ table = lv_realloc(table, sizeof(*table) + sizeof(Entry) * table->bulk);
+ if(!table) return -1;
+ table->entries = (Entry *) &table[1];
+ *tablep = table;
+ }
+ table->entries[table->nentries] = (Entry) {
+ length, prefix, suffix
+ };
+ table->nentries++;
+ if((table->nentries & (table->nentries - 1)) == 0)
+ return 1;
+ return 0;
+}
+
+/* Compute output index of y-th input line, in frame of height h. */
+static int
+interlaced_line_index(int h, int y)
+{
+ int p; /* number of lines in current pass */
+
+ p = (h - 1) / 8 + 1;
+ if(y < p) /* pass 1 */
+ return y * 8;
+ y -= p;
+ p = (h - 5) / 8 + 1;
+ if(y < p) /* pass 2 */
+ return y * 8 + 4;
+ y -= p;
+ p = (h - 3) / 4 + 1;
+ if(y < p) /* pass 3 */
+ return y * 4 + 2;
+ y -= p;
+ /* pass 4 */
+ return y * 2 + 1;
+}
+
+/* Decompress image pixels.
+ * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */
+static int
+read_image_data(gd_GIF * gif, int interlace)
+{
+ uint8_t sub_len, shift, byte;
+ int init_key_size, key_size, table_is_full = 0;
+ int frm_off, frm_size, str_len = 0, i, p, x, y;
+ uint16_t key, clear, stop;
+ int ret;
+ Table * table;
+ Entry entry = {0};
+ size_t start, end;
+
+ f_gif_read(gif, &byte, 1);
+ key_size = (int) byte;
+ start = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ discard_sub_blocks(gif);
+ end = f_gif_seek(gif, 0, LV_FS_SEEK_CUR);
+ f_gif_seek(gif, start, LV_FS_SEEK_SET);
+ clear = 1 << key_size;
+ stop = clear + 1;
+ table = new_table(key_size);
+ key_size++;
+ init_key_size = key_size;
+ sub_len = shift = 0;
+ key = get_key(gif, key_size, &sub_len, &shift, &byte); /* clear code */
+ frm_off = 0;
+ ret = 0;
+ frm_size = gif->fw * gif->fh;
+ while(frm_off < frm_size) {
+ if(key == clear) {
+ key_size = init_key_size;
+ table->nentries = (1 << (key_size - 1)) + 2;
+ table_is_full = 0;
+ }
+ else if(!table_is_full) {
+ ret = add_entry(&table, str_len + 1, key, entry.suffix);
+ if(ret == -1) {
+ lv_free(table);
+ return -1;
+ }
+ if(table->nentries == 0x1000) {
+ ret = 0;
+ table_is_full = 1;
+ }
+ }
+ key = get_key(gif, key_size, &sub_len, &shift, &byte);
+ if(key == clear) continue;
+ if(key == stop || key == 0x1000) break;
+ if(ret == 1) key_size++;
+ entry = table->entries[key];
+ str_len = entry.length;
+ if(frm_off + str_len > frm_size){
+ ESP_LOGW(TAG, "LZW table token overflows the frame buffer");
+ lv_free(table);
+ return -1;
+ }
+ for(i = 0; i < str_len; i++) {
+ p = frm_off + entry.length - 1;
+ x = p % gif->fw;
+ y = p / gif->fw;
+ if(interlace)
+ y = interlaced_line_index((int) gif->fh, y);
+ gif->frame[(gif->fy + y) * gif->width + gif->fx + x] = entry.suffix;
+ if(entry.prefix == 0xFFF)
+ break;
+ else
+ entry = table->entries[entry.prefix];
+ }
+ frm_off += str_len;
+ if(key < table->nentries - 1 && !table_is_full)
+ table->entries[table->nentries - 1].suffix = entry.suffix;
+ }
+ lv_free(table);
+ if(key == stop) f_gif_read(gif, &sub_len, 1); /* Must be zero! */
+ f_gif_seek(gif, end, LV_FS_SEEK_SET);
+ return 0;
+}
+
+#endif
+
+/* Read image.
+ * Return 0 on success or -1 on out-of-memory (w.r.t. LZW code table) or parse error. */
+static int
+read_image(gd_GIF * gif)
+{
+ uint8_t fisrz;
+ int interlace;
+
+ /* Image Descriptor. */
+ gif->fx = read_num(gif);
+ gif->fy = read_num(gif);
+ gif->fw = read_num(gif);
+ gif->fh = read_num(gif);
+ if(gif->fx + (uint32_t)gif->fw > gif->width || gif->fy + (uint32_t)gif->fh > gif->height){
+ ESP_LOGW(TAG, "Frame coordinates out of image bounds");
+ return -1;
+ }
+ f_gif_read(gif, &fisrz, 1);
+ interlace = fisrz & 0x40;
+ /* Ignore Sort Flag. */
+ /* Local Color Table? */
+ if(fisrz & 0x80) {
+ /* Read LCT */
+ gif->lct.size = 1 << ((fisrz & 0x07) + 1);
+ f_gif_read(gif, gif->lct.colors, 3 * gif->lct.size);
+ gif->palette = &gif->lct;
+ }
+ else
+ gif->palette = &gif->gct;
+ /* Image Data. */
+ return read_image_data(gif, interlace);
+}
+
+static void
+render_frame_rect(gd_GIF * gif, uint8_t * buffer)
+{
+ int i = gif->fy * gif->width + gif->fx;
+#ifdef GIFDEC_RENDER_FRAME
+ GIFDEC_RENDER_FRAME(&buffer[i * 4], gif->fw, gif->fh, gif->width,
+ &gif->frame[i], gif->palette->colors,
+ gif->gce.transparency ? gif->gce.tindex : 0x100);
+#else
+ int j, k;
+ uint8_t index, * color;
+
+ for(j = 0; j < gif->fh; j++) {
+ for(k = 0; k < gif->fw; k++) {
+ index = gif->frame[(gif->fy + j) * gif->width + gif->fx + k];
+ color = &gif->palette->colors[index * 3];
+ if(!gif->gce.transparency || index != gif->gce.tindex) {
+ buffer[(i + k) * 4 + 0] = *(color + 2);
+ buffer[(i + k) * 4 + 1] = *(color + 1);
+ buffer[(i + k) * 4 + 2] = *(color + 0);
+ buffer[(i + k) * 4 + 3] = 0xFF;
+ }
+ }
+ i += gif->width;
+ }
+#endif
+}
+
+static void
+dispose(gd_GIF * gif)
+{
+ int i;
+ uint8_t * bgcolor;
+ switch(gif->gce.disposal) {
+ case 2: /* Restore to background color. */
+ bgcolor = &gif->palette->colors[gif->bgindex * 3];
+
+ uint8_t opa = 0xff;
+ if(gif->gce.transparency) opa = 0x00;
+
+ i = gif->fy * gif->width + gif->fx;
+#ifdef GIFDEC_FILL_BG
+ GIFDEC_FILL_BG(&(gif->canvas[i * 4]), gif->fw, gif->fh, gif->width, bgcolor, opa);
+#else
+ int j, k;
+ for(j = 0; j < gif->fh; j++) {
+ for(k = 0; k < gif->fw; k++) {
+ gif->canvas[(i + k) * 4 + 0] = *(bgcolor + 2);
+ gif->canvas[(i + k) * 4 + 1] = *(bgcolor + 1);
+ gif->canvas[(i + k) * 4 + 2] = *(bgcolor + 0);
+ gif->canvas[(i + k) * 4 + 3] = opa;
+ }
+ i += gif->width;
+ }
+#endif
+ break;
+ case 3: /* Restore to previous, i.e., don't update canvas.*/
+ break;
+ default:
+ /* Add frame non-transparent pixels to canvas. */
+ render_frame_rect(gif, gif->canvas);
+ }
+}
+
+/* Return 1 if got a frame; 0 if got GIF trailer; -1 if error. */
+int
+gd_get_frame(gd_GIF * gif)
+{
+ char sep;
+
+ dispose(gif);
+ f_gif_read(gif, &sep, 1);
+ while(sep != ',') {
+ if(sep == ';') {
+ f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET);
+ if(gif->loop_count == 1 || gif->loop_count < 0) {
+ return 0;
+ }
+ else if(gif->loop_count > 1) {
+ gif->loop_count--;
+ }
+ }
+ else if(sep == '!')
+ read_ext(gif);
+ else return -1;
+ f_gif_read(gif, &sep, 1);
+ }
+ if(read_image(gif) == -1)
+ return -1;
+ return 1;
+}
+
+void
+gd_render_frame(gd_GIF * gif, uint8_t * buffer)
+{
+ render_frame_rect(gif, buffer);
+}
+
+void
+gd_rewind(gd_GIF * gif)
+{
+ gif->loop_count = -1;
+ f_gif_seek(gif, gif->anim_start, LV_FS_SEEK_SET);
+}
+
+void
+gd_close_gif(gd_GIF * gif)
+{
+ f_gif_close(gif);
+ lv_free(gif);
+}
+
+static bool f_gif_open(gd_GIF * gif, const void * path, bool is_file)
+{
+ gif->f_rw_p = 0;
+ gif->data = NULL;
+ gif->is_file = is_file;
+
+ if(is_file) {
+ lv_fs_res_t res = lv_fs_open(&gif->fd, path, LV_FS_MODE_RD);
+ if(res != LV_FS_RES_OK) return false;
+ else return true;
+ }
+ else {
+ gif->data = path;
+ return true;
+ }
+}
+
+static void f_gif_read(gd_GIF * gif, void * buf, size_t len)
+{
+ if(gif->is_file) {
+ lv_fs_read(&gif->fd, buf, len, NULL);
+ }
+ else {
+ memcpy(buf, &gif->data[gif->f_rw_p], len);
+ gif->f_rw_p += len;
+ }
+}
+
+static int f_gif_seek(gd_GIF * gif, size_t pos, int k)
+{
+ if(gif->is_file) {
+ lv_fs_seek(&gif->fd, pos, k);
+ uint32_t x;
+ lv_fs_tell(&gif->fd, &x);
+ return x;
+ }
+ else {
+ if(k == LV_FS_SEEK_CUR) gif->f_rw_p += pos;
+ else if(k == LV_FS_SEEK_SET) gif->f_rw_p = pos;
+ return gif->f_rw_p;
+ }
+}
+
+static void f_gif_close(gd_GIF * gif)
+{
+ if(gif->is_file) {
+ lv_fs_close(&gif->fd);
+ }
+}
+
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.h
new file mode 100644
index 0000000..12c171e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec.h
@@ -0,0 +1,68 @@
+#ifndef GIFDEC_H
+#define GIFDEC_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#include <lvgl.h>
+
+#include <stdint.h>
+
+typedef struct _gd_Palette {
+ int size;
+ uint8_t colors[0x100 * 3];
+} gd_Palette;
+
+typedef struct _gd_GCE {
+ uint16_t delay;
+ uint8_t tindex;
+ uint8_t disposal;
+ int input;
+ int transparency;
+} gd_GCE;
+
+
+
+typedef struct _gd_GIF {
+ lv_fs_file_t fd;
+ const char * data;
+ uint8_t is_file;
+ uint32_t f_rw_p;
+ int32_t anim_start;
+ uint16_t width, height;
+ uint16_t depth;
+ int32_t loop_count;
+ gd_GCE gce;
+ gd_Palette * palette;
+ gd_Palette lct, gct;
+ void (*plain_text)(
+ struct _gd_GIF * gif, uint16_t tx, uint16_t ty,
+ uint16_t tw, uint16_t th, uint8_t cw, uint8_t ch,
+ uint8_t fg, uint8_t bg
+ );
+ void (*comment)(struct _gd_GIF * gif);
+ void (*application)(struct _gd_GIF * gif, char id[8], char auth[3]);
+ uint16_t fx, fy, fw, fh;
+ uint8_t bgindex;
+ uint8_t * canvas, * frame;
+#if LV_GIF_CACHE_DECODE_DATA
+ uint8_t *lzw_cache;
+#endif
+} gd_GIF;
+
+gd_GIF * gd_open_gif_file(const char * fname);
+
+gd_GIF * gd_open_gif_data(const void * data);
+
+void gd_render_frame(gd_GIF * gif, uint8_t * buffer);
+
+int gd_get_frame(gd_GIF * gif);
+void gd_rewind(gd_GIF * gif);
+void gd_close_gif(gd_GIF * gif);
+
+#ifdef __cplusplus
+} /* extern "C" */
+#endif
+
+#endif /* GIFDEC_H */
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec_mve.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec_mve.h
new file mode 100644
index 0000000..6d83393
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/gifdec_mve.h
@@ -0,0 +1,140 @@
+/**
+ * @file gifdec_mve.h
+ *
+ */
+
+#ifndef GIFDEC_MVE_H
+#define GIFDEC_MVE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*********************
+ * INCLUDES
+ *********************/
+#include <stdint.h>
+#include "../../misc/lv_color.h"
+
+/*********************
+ * DEFINES
+ *********************/
+
+#define GIFDEC_FILL_BG(dst, w, h, stride, color, opa) \
+ _gifdec_fill_bg_mve(dst, w, h, stride, color, opa)
+
+#define GIFDEC_RENDER_FRAME(dst, w, h, stride, frame, pattern, tindex) \
+ _gifdec_render_frame_mve(dst, w, h, stride, frame, pattern, tindex)
+
+/**********************
+ * MACROS
+ **********************/
+
+/**********************
+ * TYPEDEFS
+ **********************/
+
+/**********************
+ * GLOBAL PROTOTYPES
+ **********************/
+
+static inline void _gifdec_fill_bg_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * color,
+ uint8_t opa)
+{
+ lv_color32_t c = lv_color32_make(*(color + 0), *(color + 1), *(color + 2), opa);
+ uint32_t color_32 = *(uint32_t *)&c;
+
+ __asm volatile(
+ ".p2align 2 \n"
+ "vdup.32 q0, %[src] \n"
+ "3: \n"
+ "mov r0, %[dst] \n"
+
+ "wlstp.32 lr, %[w], 1f \n"
+ "2: \n"
+
+ "vstrw.32 q0, [r0], #16 \n"
+ "letp lr, 2b \n"
+ "1: \n"
+ "add %[dst], %[iTargetStride] \n"
+ "subs %[h], #1 \n"
+ "bne 3b \n"
+ : [dst] "+r"(dst),
+ [h] "+r"(h)
+ : [src] "r"(color_32),
+ [w] "r"(w),
+ [iTargetStride] "r"(stride * sizeof(uint32_t))
+ : "r0", "q0", "memory", "r14", "cc");
+}
+
+static inline void _gifdec_render_frame_mve(uint8_t * dst, uint16_t w, uint16_t h, uint16_t stride, uint8_t * frame,
+ uint8_t * pattern, uint16_t tindex)
+{
+ if(w == 0 || h == 0) {
+ return;
+ }
+
+ __asm volatile(
+ "vmov.u16 q3, #255 \n"
+ "vshl.u16 q3, q3, #8 \n" /* left shift 8 for a*/
+
+ "mov r0, #2 \n"
+ "vidup.u16 q6, r0, #4 \n" /* [2, 6, 10, 14, 18, 22, 26, 30] */
+ "mov r0, #0 \n"
+ "vidup.u16 q7, r0, #4 \n" /* [0, 4, 8, 12, 16, 20, 24, 28] */
+
+ "3: \n"
+ "mov r1, %[dst] \n"
+ "mov r2, %[frame] \n"
+
+ "wlstp.16 lr, %[w], 1f \n"
+ "2: \n"
+
+ "mov r0, #3 \n"
+ "vldrb.u16 q4, [r2], #8 \n"
+ "vmul.u16 q5, q4, r0 \n"
+
+ "mov r0, #1 \n"
+ "vldrb.u16 q2, [%[pattern], q5] \n" /* load 8 pixel r*/
+
+ "vadd.u16 q5, q5, r0 \n"
+ "vldrb.u16 q1, [%[pattern], q5] \n" /* load 8 pixel g*/
+
+ "vadd.u16 q5, q5, r0 \n"
+ "vldrb.u16 q0, [%[pattern], q5] \n" /* load 8 pixel b*/
+
+ "vshl.u16 q1, q1, #8 \n" /* left shift 8 for g*/
+
+ "vorr.u16 q0, q0, q1 \n" /* make 8 pixel gb*/
+ "vorr.u16 q1, q2, q3 \n" /* make 8 pixel ar*/
+
+ "vcmp.i16 ne, q4, %[tindex] \n"
+ "vpstt \n"
+ "vstrht.16 q0, [r1, q7] \n"
+ "vstrht.16 q1, [r1, q6] \n"
+ "add r1, r1, #32 \n"
+
+ "letp lr, 2b \n"
+
+ "1: \n"
+ "mov r0, %[stride], LSL #2 \n"
+ "add %[dst], r0 \n"
+ "add %[frame], %[stride] \n"
+ "subs %[h], #1 \n"
+ "bne 3b \n"
+
+ : [dst] "+r"(dst),
+ [frame] "+r"(frame),
+ [h] "+r"(h)
+ : [pattern] "r"(pattern),
+ [w] "r"(w),
+ [stride] "r"(stride),
+ [tindex] "r"(tindex)
+ : "r0", "r1", "r2", "q0", "q1", "q2", "q3", "q4", "q5", "q6", "q7", "memory", "r14", "cc");
+}
+
+#ifdef __cplusplus
+} /*extern "C"*/
+#endif
+
+#endif /*GIFDEC_MVE_H*/
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.cc b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.cc
new file mode 100644
index 0000000..de00257
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.cc
@@ -0,0 +1,252 @@
+#include "lvgl_gif.h"
+#include <esp_log.h>
+#include <cstring>
+
+#define TAG "LvglGif"
+
+LvglGif::LvglGif(const lv_img_dsc_t* img_dsc)
+ : gif_(nullptr), timer_(nullptr), last_call_(0), playing_(false), loaded_(false),
+ loop_delay_ms_(0), loop_waiting_(false), loop_wait_start_(0) {
+ if (!img_dsc || !img_dsc->data) {
+ ESP_LOGE(TAG, "Invalid image descriptor");
+ return;
+ }
+
+ gif_ = gd_open_gif_data(img_dsc->data);
+ if (!gif_) {
+ ESP_LOGE(TAG, "Failed to open GIF from image descriptor");
+ return;
+ }
+
+ // Setup LVGL image descriptor
+ memset(&img_dsc_, 0, sizeof(img_dsc_));
+ img_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
+ img_dsc_.header.flags = LV_IMAGE_FLAGS_MODIFIABLE;
+ img_dsc_.header.cf = LV_COLOR_FORMAT_ARGB8888;
+ img_dsc_.header.w = gif_->width;
+ img_dsc_.header.h = gif_->height;
+ img_dsc_.header.stride = gif_->width * 4;
+ img_dsc_.data = gif_->canvas;
+ img_dsc_.data_size = gif_->width * gif_->height * 4;
+
+ // Render first frame
+ if (gif_->canvas) {
+ gd_render_frame(gif_, gif_->canvas);
+ }
+
+ loaded_ = true;
+ ESP_LOGD(TAG, "GIF loaded from image descriptor: %dx%d", gif_->width, gif_->height);
+}
+
+// Destructor
+LvglGif::~LvglGif() {
+ Cleanup();
+}
+
+// LvglImage interface implementation
+const lv_img_dsc_t* LvglGif::image_dsc() const {
+ if (!loaded_) {
+ return nullptr;
+ }
+ return &img_dsc_;
+}
+
+// Animation control methods
+void LvglGif::Start() {
+ if (!loaded_ || !gif_) {
+ ESP_LOGW(TAG, "GIF not loaded, cannot start");
+ return;
+ }
+
+ if (!timer_) {
+ timer_ = lv_timer_create([](lv_timer_t* timer) {
+ LvglGif* gif_obj = static_cast<LvglGif*>(lv_timer_get_user_data(timer));
+ gif_obj->NextFrame();
+ }, 10, this);
+ }
+
+ if (timer_) {
+ playing_ = true;
+ loop_waiting_ = false; // Reset loop waiting state
+ last_call_ = lv_tick_get();
+ lv_timer_resume(timer_);
+ lv_timer_reset(timer_);
+
+ // Render first frame
+ NextFrame();
+
+ ESP_LOGD(TAG, "GIF animation started");
+ }
+}
+
+void LvglGif::Pause() {
+ if (timer_) {
+ playing_ = false;
+ lv_timer_pause(timer_);
+ ESP_LOGD(TAG, "GIF animation paused");
+ }
+}
+
+void LvglGif::Resume() {
+ if (!loaded_ || !gif_) {
+ ESP_LOGW(TAG, "GIF not loaded, cannot resume");
+ return;
+ }
+
+ if (timer_) {
+ playing_ = true;
+ lv_timer_resume(timer_);
+ ESP_LOGD(TAG, "GIF animation resumed");
+ }
+}
+
+void LvglGif::Stop() {
+ if (timer_) {
+ playing_ = false;
+ lv_timer_pause(timer_);
+ }
+
+ // Reset loop waiting state
+ loop_waiting_ = false;
+
+ if (gif_) {
+ gd_rewind(gif_);
+ // Render first frame without advancing
+ if (gif_->canvas) {
+ gd_render_frame(gif_, gif_->canvas);
+ }
+ ESP_LOGD(TAG, "GIF animation stopped and rewound");
+ }
+}
+
+bool LvglGif::IsPlaying() const {
+ return playing_;
+}
+
+bool LvglGif::IsLoaded() const {
+ return loaded_;
+}
+
+int32_t LvglGif::GetLoopCount() const {
+ if (!loaded_ || !gif_) {
+ return -1;
+ }
+ return gif_->loop_count;
+}
+
+void LvglGif::SetLoopCount(int32_t count) {
+ if (!loaded_ || !gif_) {
+ ESP_LOGW(TAG, "GIF not loaded, cannot set loop count");
+ return;
+ }
+ gif_->loop_count = count;
+}
+
+uint32_t LvglGif::GetLoopDelay() const {
+ return loop_delay_ms_;
+}
+
+void LvglGif::SetLoopDelay(uint32_t delay_ms) {
+ loop_delay_ms_ = delay_ms;
+ ESP_LOGD(TAG, "Loop delay set to %lu ms", delay_ms);
+}
+
+uint16_t LvglGif::width() const {
+ if (!loaded_ || !gif_) {
+ return 0;
+ }
+ return gif_->width;
+}
+
+uint16_t LvglGif::height() const {
+ if (!loaded_ || !gif_) {
+ return 0;
+ }
+ return gif_->height;
+}
+
+void LvglGif::SetFrameCallback(std::function<void()> callback) {
+ frame_callback_ = callback;
+}
+
+void LvglGif::NextFrame() {
+ if (!loaded_ || !gif_ || !playing_) {
+ return;
+ }
+
+ // Check if we're in loop wait state (only for infinite loop GIFs with delay)
+ if (loop_waiting_) {
+ uint32_t wait_elapsed = lv_tick_elaps(loop_wait_start_);
+ if (wait_elapsed < loop_delay_ms_) {
+ // Still waiting for loop delay
+ return;
+ }
+ // Loop delay completed, continue playing
+ loop_waiting_ = false;
+ ESP_LOGD(TAG, "Loop delay completed, continuing GIF");
+ }
+
+ // Check if enough time has passed for the next frame
+ uint32_t elapsed = lv_tick_elaps(last_call_);
+ if (elapsed < gif_->gce.delay * 10) {
+ return;
+ }
+
+ last_call_ = lv_tick_get();
+
+ // Save file position before getting next frame to detect loop
+ uint32_t pos_before = gif_->f_rw_p;
+
+ // Get next frame
+ int has_next = gd_get_frame(gif_);
+ if (has_next == 0) {
+ // Animation truly finished (non-infinite loop)
+ playing_ = false;
+ if (timer_) {
+ lv_timer_pause(timer_);
+ }
+ ESP_LOGD(TAG, "GIF animation completed");
+ return;
+ }
+
+ // Detect loop by checking if file position jumped back (rewound to start)
+ // This works for looping GIFs regardless of when loop_count is set
+ if (loop_delay_ms_ > 0 && gif_->f_rw_p < pos_before) {
+ // File position decreased, meaning GIF looped back to beginning
+ // Start waiting before rendering this frame
+ loop_waiting_ = true;
+ loop_wait_start_ = lv_tick_get();
+ ESP_LOGD(TAG, "GIF completed one cycle, waiting %lu ms before next loop", loop_delay_ms_);
+ return;
+ }
+
+ // Render current frame
+ if (gif_->canvas) {
+ gd_render_frame(gif_, gif_->canvas);
+
+ // Call frame callback if set
+ if (frame_callback_) {
+ frame_callback_();
+ }
+ }
+}
+
+void LvglGif::Cleanup() {
+ // Stop and delete timer
+ if (timer_) {
+ lv_timer_delete(timer_);
+ timer_ = nullptr;
+ }
+
+ // Close GIF decoder
+ if (gif_) {
+ gd_close_gif(gif_);
+ gif_ = nullptr;
+ }
+
+ playing_ = false;
+ loaded_ = false;
+
+ // Clear image descriptor
+ memset(&img_dsc_, 0, sizeof(img_dsc_));
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.h
new file mode 100644
index 0000000..17b9afd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/gif/lvgl_gif.h
@@ -0,0 +1,117 @@
+#pragma once
+
+#include "../lvgl_image.h"
+#include "gifdec.h"
+#include <lvgl.h>
+#include <memory>
+#include <functional>
+
+/**
+ * C++ implementation of LVGL GIF widget
+ * Provides GIF animation functionality using gifdec library
+ */
+class LvglGif {
+public:
+ explicit LvglGif(const lv_img_dsc_t* img_dsc);
+ virtual ~LvglGif();
+
+ // LvglImage interface implementation
+ virtual const lv_img_dsc_t* image_dsc() const;
+
+ /**
+ * Start/restart GIF animation
+ */
+ void Start();
+
+ /**
+ * Pause GIF animation
+ */
+ void Pause();
+
+ /**
+ * Resume GIF animation
+ */
+ void Resume();
+
+ /**
+ * Stop GIF animation and rewind to first frame
+ */
+ void Stop();
+
+ /**
+ * Check if GIF is currently playing
+ */
+ bool IsPlaying() const;
+
+ /**
+ * Check if GIF was loaded successfully
+ */
+ bool IsLoaded() const;
+
+ /**
+ * Get loop count
+ */
+ int32_t GetLoopCount() const;
+
+ /**
+ * Set loop count
+ */
+ void SetLoopCount(int32_t count);
+
+ /**
+ * Get loop delay in milliseconds (delay between loops)
+ */
+ uint32_t GetLoopDelay() const;
+
+ /**
+ * Set loop delay in milliseconds (delay between loops)
+ * @param delay_ms Delay in milliseconds before starting next loop. 0 means no delay.
+ */
+ void SetLoopDelay(uint32_t delay_ms);
+
+ /**
+ * Get GIF dimensions
+ */
+ uint16_t width() const;
+ uint16_t height() const;
+
+ /**
+ * Set frame update callback
+ */
+ void SetFrameCallback(std::function<void()> callback);
+
+private:
+ // GIF decoder instance
+ gd_GIF* gif_;
+
+ // LVGL image descriptor
+ lv_img_dsc_t img_dsc_;
+
+ // Animation timer
+ lv_timer_t* timer_;
+
+ // Last frame update time
+ uint32_t last_call_;
+
+ // Animation state
+ bool playing_;
+ bool loaded_;
+
+ // Loop delay configuration
+ uint32_t loop_delay_ms_; // Delay between loops in milliseconds
+ bool loop_waiting_; // Whether we're waiting for the next loop
+ uint32_t loop_wait_start_; // Timestamp when loop wait started
+
+ // Frame update callback
+ std::function<void()> frame_callback_;
+
+ /**
+ * Update to next frame
+ */
+ void NextFrame();
+
+ /**
+ * Cleanup resources
+ */
+ void Cleanup();
+};
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.cpp b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.cpp
new file mode 100644
index 0000000..485ca8b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.cpp
@@ -0,0 +1,467 @@
+#include <esp_attr.h>
+#include <esp_heap_caps.h>
+#include <esp_log.h>
+#include <stddef.h>
+#include <string.h>
+#include <utility>
+
+#include "esp_jpeg_common.h"
+#include "esp_jpeg_enc.h"
+#include "esp_imgfx_color_convert.h"
+
+#if CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+#include "driver/jpeg_encode.h"
+#endif
+#include "image_to_jpeg.h"
+
+#define TAG "image_to_jpeg"
+
+static void* malloc_psram(size_t size) {
+ void* p = malloc(size);
+ if (p)
+ return p;
+#if (CONFIG_SPIRAM_SUPPORT && (CONFIG_SPIRAM_USE_CAPS_ALLOC || CONFIG_SPIRAM_USE_MALLOC))
+ return heap_caps_malloc(size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+#else
+ return NULL;
+#endif
+}
+
+static __always_inline uint8_t expand_5_to_8(uint8_t v) {
+ return (uint8_t)((v << 3) | (v >> 2));
+}
+
+static __always_inline uint8_t expand_6_to_8(uint8_t v) {
+ return (uint8_t)((v << 2) | (v >> 4));
+}
+
+static uint8_t* convert_input_to_encoder_buf(const uint8_t* src, uint16_t width, uint16_t height, v4l2_pix_fmt_t format,
+ jpeg_pixel_format_t* out_fmt, int* out_size) {
+ // GRAY 鐩存帴浣滀负 JPEG_PIXEL_FORMAT_GRAY 杈撳叆
+ if (format == V4L2_PIX_FMT_GREY) {
+ int sz = (int)width * (int)height;
+ uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
+ if (!buf)
+ return NULL;
+ memcpy(buf, src, sz);
+ if (out_fmt)
+ *out_fmt = JPEG_PIXEL_FORMAT_GRAY;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ // V4L2 YUYV (Y Cb Y Cr) 鍙洿鎺ヤ綔涓� JPEG_PIXEL_FORMAT_YCbYCr 杈撳叆
+ if (format == V4L2_PIX_FMT_YUYV) {
+ int sz = (int)width * (int)height * 2;
+ uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
+ if (!buf)
+ return NULL;
+ memcpy(buf, src, sz);
+ if (out_fmt)
+ *out_fmt = JPEG_PIXEL_FORMAT_YCbYCr;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ // V4L2 UYVY (Cb Y Cr Y) -> 閲嶆帓涓� YUYV 鍐嶄綔涓� YCbYCr 杈撳叆
+ // 褰撳墠鐗堟湰鏆傛椂涓嶄細鍑虹幇 UYVY 鏍煎紡
+ if (format == V4L2_PIX_FMT_UYVY) [[unlikely]] {
+ int sz = (int)width * (int)height * 2;
+ const uint8_t* s = src;
+ uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
+ if (!buf)
+ return NULL;
+ uint8_t* d = buf;
+ for (int i = 0; i < sz; i += 4) {
+ // src: Cb, Y0, Cr, Y1 -> dst: Y0, Cb, Y1, Cr
+ d[0] = s[1];
+ d[1] = s[0];
+ d[2] = s[3];
+ d[3] = s[2];
+ s += 4;
+ d += 4;
+ }
+ if (out_fmt)
+ *out_fmt = JPEG_PIXEL_FORMAT_YCbYCr;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ // V4L2 YUV422P (YUV422 Planar) -> 閲嶆帓涓� YUYV (YCbYCr)
+ // 褰撳墠鐗堟湰鏆傛椂涓嶄細鍑虹幇 YUV422P 鏍煎紡
+ if (format == V4L2_PIX_FMT_YUV422P) [[unlikely]] {
+ int sz = (int)width * (int)height * 2;
+ const uint8_t* y_plane = src;
+ const uint8_t* u_plane = y_plane + (int)width * (int)height;
+ const uint8_t* v_plane = u_plane + ((int)width / 2) * (int)height;
+ uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
+ if (!buf)
+ return NULL;
+ uint8_t* dst = buf;
+ for (int y = 0; y < height; y++) {
+ const uint8_t* y_row = y_plane + y * (int)width;
+ const uint8_t* u_row = u_plane + y * ((int)width / 2);
+ const uint8_t* v_row = v_plane + y * ((int)width / 2);
+ for (int x = 0; x < width; x += 2) {
+ uint8_t y0 = y_row[x + 0];
+ uint8_t y1 = y_row[x + 1];
+ uint8_t cb = u_row[x / 2];
+ uint8_t cr = v_row[x / 2];
+ dst[0] = y0;
+ dst[1] = cb;
+ dst[2] = y1;
+ dst[3] = cr;
+ dst += 4;
+ }
+ }
+ if (out_fmt)
+ *out_fmt = JPEG_PIXEL_FORMAT_YCbYCr;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ // RGB 杞崲涓� YUV422 (YCbYCr) 鍐嶈緭鍏�
+ // 瑙� https://github.com/78/xiaozhi-esp32/issues/1380#issuecomment-3497156378
+ else if (format == V4L2_PIX_FMT_RGB24 || format == V4L2_PIX_FMT_RGB565 || format == V4L2_PIX_FMT_RGB565X) {
+ esp_imgfx_pixel_fmt_t in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888;
+ uint32_t src_len = 0;
+ switch (format) {
+ case V4L2_PIX_FMT_RGB24:
+ in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB888;
+ src_len = static_cast<uint32_t>(width * height * 3);
+ break;
+ case V4L2_PIX_FMT_RGB565:
+ in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_LE;
+ src_len = static_cast<uint32_t>(width * height * 2);
+ break;
+ [[unlikely]] case V4L2_PIX_FMT_RGB565X: // 褰撳墠鐗堟湰鏆傛椂涓嶄細鍑虹幇 RGB565X
+ in_pixel_fmt = ESP_IMGFX_PIXEL_FMT_RGB565_BE;
+ src_len = static_cast<uint32_t>(width * height * 2);
+ break;
+ [[unlikely]] default:
+ ESP_LOGE(TAG, "[Unreachable Case] unsupported format: 0x%08lx", format);
+ std::unreachable();
+ }
+ int sz = (int)width * (int)height * 2;
+ uint8_t* buf = (uint8_t*)jpeg_calloc_align(sz, 16);
+ if (!buf)
+ return nullptr;
+ esp_imgfx_color_convert_cfg_t convert_cfg = {
+ .in_res = {.width = static_cast<int16_t>(width),
+ .height = static_cast<int16_t>(height)},
+ .in_pixel_fmt = in_pixel_fmt,
+ .out_pixel_fmt = ESP_IMGFX_PIXEL_FMT_YUYV,
+ .color_space_std = ESP_IMGFX_COLOR_SPACE_STD_BT601,
+ };
+ esp_imgfx_color_convert_handle_t convert_handle = nullptr;
+ esp_imgfx_err_t err = esp_imgfx_color_convert_open(&convert_cfg, &convert_handle);
+ if (err != ESP_IMGFX_ERR_OK || convert_handle == nullptr) {
+ ESP_LOGE(TAG, "esp_imgfx_color_convert_open failed");
+ jpeg_free_align(buf);
+ return nullptr;
+ }
+ esp_imgfx_data_t convert_input_data = {
+ .data = const_cast<uint8_t*>(src),
+ .data_len = static_cast<uint32_t>(src_len),
+ };
+ esp_imgfx_data_t convert_output_data = {
+ .data = buf,
+ .data_len = static_cast<uint32_t>(sz),
+ };
+ err = esp_imgfx_color_convert_process(convert_handle, &convert_input_data, &convert_output_data);
+ if (err != ESP_IMGFX_ERR_OK) {
+ ESP_LOGE(TAG, "esp_imgfx_color_convert_process failed");
+ jpeg_free_align(buf);
+ return nullptr;
+ }
+ esp_imgfx_color_convert_close(convert_handle);
+ convert_handle = nullptr;
+ if (out_fmt)
+ *out_fmt = JPEG_PIXEL_FORMAT_YCbYCr;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+ ESP_LOGE(TAG, "unsupported format: 0x%08lx", format);
+ if (out_size)
+ *out_size = 0;
+ return nullptr;
+}
+
+#if CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+static jpeg_encoder_handle_t s_hw_jpeg_handle = NULL;
+
+static bool hw_jpeg_ensure_inited(void) {
+ if (s_hw_jpeg_handle) {
+ return true;
+ }
+ jpeg_encode_engine_cfg_t eng_cfg = {
+ .intr_priority = 0,
+ .timeout_ms = 100,
+ };
+ esp_err_t er = jpeg_new_encoder_engine(&eng_cfg, &s_hw_jpeg_handle);
+ if (er != ESP_OK) {
+ ESP_LOGE(TAG, "jpeg_new_encoder_engine failed: %d", (int)er);
+ s_hw_jpeg_handle = NULL;
+ return false;
+ }
+ return true;
+}
+
+static uint8_t* convert_input_to_hw_encoder_buf(const uint8_t* src, uint16_t width, uint16_t height, v4l2_pix_fmt_t format,
+ jpeg_enc_input_format_t* out_fmt, int* out_size) {
+ if (format == V4L2_PIX_FMT_GREY) {
+ int sz = (int)width * (int)height;
+ uint8_t* buf = (uint8_t*)malloc_psram(sz);
+ if (!buf)
+ return NULL;
+ memcpy(buf, src, sz);
+ if (out_fmt)
+ *out_fmt = JPEG_ENCODE_IN_FORMAT_GRAY;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ if (format == V4L2_PIX_FMT_RGB24) {
+ int sz = (int)width * (int)height * 3;
+ uint8_t* buf = (uint8_t*)malloc_psram(sz);
+ if (!buf) {
+ ESP_LOGE(TAG, "malloc_psram failed");
+ return NULL;
+ }
+ memcpy(buf, src, sz);
+ if (out_fmt)
+ *out_fmt = JPEG_ENCODE_IN_FORMAT_RGB888;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ if (format == V4L2_PIX_FMT_RGB565) {
+ int sz = (int)width * (int)height * 2;
+ uint8_t* buf = (uint8_t*)malloc_psram(sz);
+ if (!buf)
+ return NULL;
+ memcpy(buf, src, sz);
+ if (out_fmt)
+ *out_fmt = JPEG_ENCODE_IN_FORMAT_RGB565;
+ if (out_size)
+ *out_size = sz;
+ return buf;
+ }
+
+ if (format == V4L2_PIX_FMT_YUYV) {
+ // 纭欢闇�瑕� | Y1 V Y0 U | 鐨勨�滃ぇ绔�濇牸寮忥紝鍥犳闇�瑕� bswap16
+ int sz = (int)width * (int)height * 2;
+ uint16_t* buf = (uint16_t*)malloc_psram(sz);
+ if (!buf)
+ return NULL;
+ const uint16_t* bsrc = (const uint16_t*)src;
+ for (int i = 0; i < sz / 2; i++) {
+ buf[i] = __builtin_bswap16(bsrc[i]);
+ }
+ if (out_fmt)
+ *out_fmt = JPEG_ENCODE_IN_FORMAT_YUV422;
+ if (out_size)
+ *out_size = sz;
+ return (uint8_t*)buf;
+ }
+
+ return NULL;
+}
+
+static bool encode_with_hw_jpeg(const uint8_t* src, size_t src_len, uint16_t width, uint16_t height,
+ v4l2_pix_fmt_t format, uint8_t quality, uint8_t** jpg_out, size_t* jpg_out_len,
+ jpg_out_cb cb, void* cb_arg) {
+ if (quality < 1)
+ quality = 1;
+ if (quality > 100)
+ quality = 100;
+
+ jpeg_enc_input_format_t enc_src_type = JPEG_ENCODE_IN_FORMAT_RGB888;
+ int enc_in_size = 0;
+ uint8_t* enc_in = convert_input_to_hw_encoder_buf(src, width, height, format, &enc_src_type, &enc_in_size);
+ if (!enc_in) {
+ ESP_LOGW(TAG, "hw jpeg: unsupported format, fallback to sw");
+ return false;
+ }
+
+ if (!hw_jpeg_ensure_inited()) {
+ free(enc_in);
+ return false;
+ }
+
+ jpeg_encode_cfg_t enc_cfg = {0};
+ enc_cfg.width = width;
+ enc_cfg.height = height;
+ enc_cfg.src_type = enc_src_type;
+ enc_cfg.image_quality = quality;
+ enc_cfg.sub_sample = (enc_src_type == JPEG_ENCODE_IN_FORMAT_GRAY) ? JPEG_DOWN_SAMPLING_GRAY : JPEG_DOWN_SAMPLING_YUV422;
+
+ size_t out_cap = (size_t)width * (size_t)height * 3 / 2 + 64 * 1024;
+ if (out_cap < 128 * 1024)
+ out_cap = 128 * 1024;
+ jpeg_encode_memory_alloc_cfg_t jpeg_enc_output_mem_cfg = { .buffer_direction = JPEG_ENC_ALLOC_OUTPUT_BUFFER };
+ size_t out_cap_aligned = 0;
+ uint8_t* outbuf = (uint8_t*)jpeg_alloc_encoder_mem(out_cap, &jpeg_enc_output_mem_cfg, &out_cap_aligned);
+ if (!outbuf) {
+ free(enc_in);
+ ESP_LOGE(TAG, "alloc out buffer failed");
+ return false;
+ }
+
+ uint32_t out_len = 0;
+ esp_err_t er = jpeg_encoder_process(s_hw_jpeg_handle, &enc_cfg, enc_in, (uint32_t)enc_in_size, outbuf, (uint32_t)out_cap_aligned, &out_len);
+ free(enc_in);
+
+ if (er != ESP_OK) {
+ free(outbuf);
+ ESP_LOGE(TAG, "jpeg_encoder_process failed: %d", (int)er);
+ return false;
+ }
+
+ if (cb) {
+ cb(cb_arg, 0, outbuf, (size_t)out_len);
+ cb(cb_arg, 1, NULL, 0);
+ free(outbuf);
+ if (jpg_out)
+ *jpg_out = NULL;
+ if (jpg_out_len)
+ *jpg_out_len = 0;
+ return true;
+ }
+
+ if (jpg_out && jpg_out_len) {
+ *jpg_out = outbuf;
+ *jpg_out_len = (size_t)out_len;
+ return true;
+ }
+
+ free(outbuf);
+ return true;
+}
+#endif // CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+
+static bool encode_with_esp_new_jpeg(const uint8_t* src, size_t src_len, uint16_t width, uint16_t height,
+ v4l2_pix_fmt_t format, uint8_t quality, uint8_t** jpg_out, size_t* jpg_out_len,
+ jpg_out_cb cb, void* cb_arg) {
+ if (quality < 1)
+ quality = 1;
+ if (quality > 100)
+ quality = 100;
+
+ jpeg_pixel_format_t enc_src_type = JPEG_PIXEL_FORMAT_RGB888;
+ int enc_in_size = 0;
+ uint8_t* enc_in = convert_input_to_encoder_buf(src, width, height, format, &enc_src_type, &enc_in_size);
+ if (!enc_in) {
+ ESP_LOGE(TAG, "alloc/convert input failed");
+ return false;
+ }
+
+ jpeg_enc_config_t cfg = DEFAULT_JPEG_ENC_CONFIG();
+ cfg.width = width;
+ cfg.height = height;
+ cfg.src_type = enc_src_type;
+ cfg.subsampling = (enc_src_type == JPEG_PIXEL_FORMAT_GRAY) ? JPEG_SUBSAMPLE_GRAY : JPEG_SUBSAMPLE_420;
+ cfg.quality = quality;
+ cfg.rotate = JPEG_ROTATE_0D;
+ cfg.task_enable = false;
+
+ jpeg_enc_handle_t h = NULL;
+ jpeg_error_t ret = jpeg_enc_open(&cfg, &h);
+ if (ret != JPEG_ERR_OK) {
+ jpeg_free_align(enc_in);
+ ESP_LOGE(TAG, "jpeg_enc_open failed: %d", (int)ret);
+ return false;
+ }
+
+ // 浼扮畻杈撳嚭缂撳啿鍖猴細瀹介珮鐨� 1.5 鍊� + 64KB
+ size_t out_cap = (size_t)width * (size_t)height * 3 / 2 + 64 * 1024;
+ if (out_cap < 128 * 1024)
+ out_cap = 128 * 1024;
+ uint8_t* outbuf = (uint8_t*)malloc_psram(out_cap);
+ if (!outbuf) {
+ jpeg_enc_close(h);
+ jpeg_free_align(enc_in);
+ ESP_LOGE(TAG, "alloc out buffer failed");
+ return false;
+ }
+
+ int out_len = 0;
+ ret = jpeg_enc_process(h, enc_in, enc_in_size, outbuf, (int)out_cap, &out_len);
+ jpeg_enc_close(h);
+ jpeg_free_align(enc_in);
+
+ if (ret != JPEG_ERR_OK) {
+ free(outbuf);
+ ESP_LOGE(TAG, "jpeg_enc_process failed: %d", (int)ret);
+ return false;
+ }
+
+ if (cb) {
+ cb(cb_arg, 0, outbuf, (size_t)out_len);
+ cb(cb_arg, 1, NULL, 0); // 缁撴潫淇″彿
+ free(outbuf);
+ if (jpg_out)
+ *jpg_out = NULL;
+ if (jpg_out_len)
+ *jpg_out_len = 0;
+ return true;
+ }
+
+ if (jpg_out && jpg_out_len) {
+ *jpg_out = outbuf;
+ *jpg_out_len = (size_t)out_len;
+ return true;
+ }
+
+ free(outbuf);
+ return true;
+}
+
+bool image_to_jpeg(uint8_t* src, size_t src_len, uint16_t width, uint16_t height, v4l2_pix_fmt_t format,
+ uint8_t quality, uint8_t** out, size_t* out_len) {
+#ifdef CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+ if (format == V4L2_PIX_FMT_JPEG) {
+ uint8_t * out_data = (uint8_t*)heap_caps_malloc(src_len, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
+ if (!out_data) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG output");
+ return false;
+ }
+ memcpy(out_data, src, src_len);
+ *out = out_data;
+ *out_len = src_len;
+ return true;
+ }
+#endif // CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+#if CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+ if (encode_with_hw_jpeg(src, src_len, width, height, format, quality, out, out_len, NULL, NULL)) {
+ return true;
+ }
+ // Fallback to esp_new_jpeg
+#endif
+ return encode_with_esp_new_jpeg(src, src_len, width, height, format, quality, out, out_len, NULL, NULL);
+}
+
+bool image_to_jpeg_cb(uint8_t* src, size_t src_len, uint16_t width, uint16_t height, v4l2_pix_fmt_t format,
+ uint8_t quality, jpg_out_cb cb, void* arg) {
+#ifdef CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+ if (format == V4L2_PIX_FMT_JPEG) {
+ cb(arg, 0, src, src_len);
+ cb(arg, 1, nullptr, 0); // end signal
+ return true;
+ }
+#endif // CONFIG_XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
+#if CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_ENCODER
+ if (encode_with_hw_jpeg(src, src_len, width, height, format, quality, NULL, NULL, cb, arg)) {
+ return true;
+ }
+ // Fallback to esp_new_jpeg
+#endif
+ return encode_with_esp_new_jpeg(src, src_len, width, height, format, quality, NULL, NULL, cb, arg);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.h
new file mode 100644
index 0000000..27c5d31
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/image_to_jpeg.h
@@ -0,0 +1,86 @@
+// image_to_jpeg.h - 鍥惧儚鍒癑PEG杞崲鐨勯珮鏁堢紪鐮佹帴鍙�
+// 鑺傜渷绾�8KB SRAM鐨凧PEG缂栫爜瀹炵幇
+#pragma once
+#include "sdkconfig.h"
+#ifndef CONFIG_IDF_TARGET_ESP32
+
+#include <stdint.h>
+#include <stddef.h>
+
+#if defined(CONFIG_IDF_TARGET_ESP32P4) || defined(CONFIG_IDF_TARGET_ESP32S3)
+// ESP32-P4 浣跨敤 esp_video 缁勪欢鎻愪緵鐨� V4L2 澶存枃浠�
+#include <linux/videodev2.h>
+#else
+// ESP32-S3 绛夊叾浠栬姱鐗囷細瀹氫箟甯哥敤鐨� V4L2 鍍忕礌鏍煎紡
+#define V4L2_PIX_FMT_RGB565 0x50424752 // 'RGBP'
+#define V4L2_PIX_FMT_RGB565X 0x52474250 // 'PRGB'
+#define V4L2_PIX_FMT_RGB24 0x33424752 // 'RGB3'
+#define V4L2_PIX_FMT_YUYV 0x56595559 // 'YUYV'
+#define V4L2_PIX_FMT_YUV422P 0x36315559 // 'YU16'
+#define V4L2_PIX_FMT_YUV420 0x32315559 // 'YU12'
+#define V4L2_PIX_FMT_GREY 0x59455247 // 'GREY'
+#define V4L2_PIX_FMT_UYVY 0x59565955 // 'UYVY'
+#define V4L2_PIX_FMT_JPEG 0x4745504A // 'JPEG'
+#endif
+
+typedef uint32_t v4l2_pix_fmt_t;
+
+#ifdef __cplusplus
+extern "C"
+{
+#endif
+
+ // JPEG杈撳嚭鍥炶皟鍑芥暟绫诲瀷
+ // arg: 鐢ㄦ埛鑷畾涔夊弬鏁�, index: 褰撳墠鏁版嵁绱㈠紩, data: JPEG鏁版嵁鍧�, len: 鏁版嵁鍧楅暱搴�
+ // 杩斿洖: 瀹為檯澶勭悊鐨勫瓧鑺傛暟
+ typedef size_t (*jpg_out_cb)(void *arg, size_t index, const void *data, size_t len);
+
+ /**
+ * @brief 灏嗗浘鍍忔牸寮忛珮鏁堣浆鎹负JPEG
+ *
+ * 杩欎釜鍑芥暟浣跨敤浼樺寲鐨凧PEG缂栫爜鍣ㄨ繘琛岀紪鐮侊紝涓昏鐗圭偣锛�
+ * - 鑺傜渷绾�8KB鐨凷RAM浣跨敤锛堥潤鎬佸彉閲忔敼涓哄爢鍒嗛厤锛�
+ * - 鏀寔澶氱鍥惧儚鏍煎紡杈撳叆
+ * - 楂樿川閲廕PEG杈撳嚭
+ *
+ * @param src 婧愬浘鍍忔暟鎹�
+ * @param src_len 婧愬浘鍍忔暟鎹暱搴�
+ * @param width 鍥惧儚瀹藉害
+ * @param height 鍥惧儚楂樺害
+ * @param format 鍥惧儚鏍煎紡 (PIXFORMAT_RGB565, PIXFORMAT_RGB888, 绛�)
+ * @param quality JPEG璐ㄩ噺 (1-100)
+ * @param out 杈撳嚭JPEG鏁版嵁鎸囬拡 (闇�瑕佽皟鐢ㄨ�呴噴鏀�)
+ * @param out_len 杈撳嚭JPEG鏁版嵁闀垮害
+ *
+ * @return true 鎴愬姛, false 澶辫触
+ */
+ bool image_to_jpeg(uint8_t *src, size_t src_len, uint16_t width, uint16_t height,
+ v4l2_pix_fmt_t format, uint8_t quality, uint8_t **out, size_t *out_len);
+
+ /**
+ * @brief 灏嗗浘鍍忔牸寮忚浆鎹负JPEG锛堝洖璋冪増鏈級
+ *
+ * 浣跨敤鍥炶皟鍑芥暟澶勭悊JPEG杈撳嚭鏁版嵁锛岄�傚悎娴佸紡浼犺緭鎴栧垎鍧楀鐞嗭細
+ * - 鑺傜渷绾�8KB鐨凷RAM浣跨敤锛堥潤鎬佸彉閲忔敼涓哄爢鍒嗛厤锛�
+ * - 鏀寔娴佸紡杈撳嚭锛屾棤闇�棰勫垎閰嶅ぇ缂撳啿鍖�
+ * - 閫氳繃鍥炶皟鍑芥暟閫愬潡澶勭悊JPEG鏁版嵁
+ *
+ * @param src 婧愬浘鍍忔暟鎹�
+ * @param src_len 婧愬浘鍍忔暟鎹暱搴�
+ * @param width 鍥惧儚瀹藉害
+ * @param height 鍥惧儚楂樺害
+ * @param format 鍥惧儚鏍煎紡
+ * @param quality JPEG璐ㄩ噺 (1-100)
+ * @param cb 杈撳嚭鍥炶皟鍑芥暟
+ * @param arg 浼犻�掔粰鍥炶皟鍑芥暟鐨勭敤鎴峰弬鏁�
+ *
+ * @return true 鎴愬姛, false 澶辫触
+ */
+ bool image_to_jpeg_cb(uint8_t *src, size_t src_len, uint16_t width, uint16_t height,
+ v4l2_pix_fmt_t format, uint8_t quality, jpg_out_cb cb, void *arg);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // ndef CONFIG_IDF_TARGET_ESP32
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.c b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.c
new file mode 100644
index 0000000..da92455
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.c
@@ -0,0 +1,264 @@
+#include <esp_check.h>
+#include <esp_err.h>
+#include <esp_heap_caps.h>
+#include <sys/param.h>
+
+#include "esp_jpeg_common.h"
+#include "esp_jpeg_dec.h"
+
+#include "jpeg_to_image.h"
+
+#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+#undef LOG_LOCAL_LEVEL
+#define LOG_LOCAL_LEVEL MAX(CONFIG_LOG_DEFAULT_LEVEL, ESP_LOG_DEBUG)
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+#include <esp_log.h>
+
+#ifdef CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER
+#include "driver/jpeg_decode.h"
+#endif
+
+#define TAG "jpeg_to_image"
+
+static esp_err_t decode_with_new_jpeg(const uint8_t* src, size_t src_len, uint8_t** out, size_t* out_len, size_t* width,
+ size_t* height, size_t* stride) {
+ ESP_LOGD(TAG, "Decoding JPEG with software decoder");
+ esp_err_t ret = ESP_OK;
+ jpeg_error_t jpeg_ret = JPEG_ERR_OK;
+ uint8_t* out_buf = NULL;
+ jpeg_dec_io_t jpeg_io = {0};
+ jpeg_dec_header_info_t out_info = {0};
+
+ jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
+ config.output_type = JPEG_PIXEL_FORMAT_RGB565_LE;
+ config.rotate = JPEG_ROTATE_0D;
+
+ jpeg_dec_handle_t jpeg_dec = NULL;
+ jpeg_ret = jpeg_dec_open(&config, &jpeg_dec);
+ if (jpeg_ret != JPEG_ERR_OK) {
+ ESP_LOGE(TAG, "Failed to open JPEG decoder");
+ ret = ESP_FAIL;
+ goto jpeg_dec_failed;
+ }
+
+ jpeg_io.inbuf = (uint8_t*)src;
+ jpeg_io.inbuf_len = (int)src_len;
+
+ jpeg_ret = jpeg_dec_parse_header(jpeg_dec, &jpeg_io, &out_info);
+ if (jpeg_ret != JPEG_ERR_OK) {
+ ESP_LOGE(TAG, "Failed to parse JPEG header");
+ ret = ESP_ERR_INVALID_ARG;
+ goto jpeg_dec_failed;
+ }
+
+ ESP_LOGD(TAG, "JPEG header info: width=%d, height=%d", out_info.width, out_info.height);
+
+ out_buf = jpeg_calloc_align(out_info.width * out_info.height * 2, 16);
+ if (out_buf == NULL) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG output buffer");
+ ret = ESP_ERR_NO_MEM;
+ goto jpeg_dec_failed;
+ }
+
+ jpeg_io.outbuf = out_buf;
+ jpeg_ret = jpeg_dec_process(jpeg_dec, &jpeg_io);
+ if (jpeg_ret != JPEG_ERR_OK) {
+ ESP_LOGE(TAG, "Failed to decode JPEG");
+ ret = ESP_FAIL;
+ goto jpeg_dec_failed;
+ }
+
+ ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf, MIN(out_info.width * out_info.height * 2, 256), ESP_LOG_DEBUG);
+
+ *out = out_buf;
+ out_buf = NULL;
+ *out_len = (size_t)(out_info.width * out_info.height * 2);
+ *width = (size_t)out_info.width;
+ *height = (size_t)out_info.height;
+ *stride = (size_t)out_info.width * 2;
+ jpeg_dec_close(jpeg_dec);
+ jpeg_dec = NULL;
+
+ return ret;
+
+jpeg_dec_failed:
+ if (jpeg_dec) {
+ jpeg_dec_close(jpeg_dec);
+ jpeg_dec = NULL;
+ }
+ if (out_buf) {
+ jpeg_free_align(out_buf);
+ out_buf = NULL;
+ }
+
+ *out = NULL;
+ *out_len = 0;
+ *width = 0;
+ *height = 0;
+ *stride = 0;
+ return ret;
+}
+
+#ifdef CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER
+static esp_err_t decode_with_hardware_jpeg(const uint8_t* src, size_t src_len, uint8_t** out, size_t* out_len,
+ size_t* width, size_t* height, size_t* stride) {
+ ESP_LOGD(TAG, "Decoding JPEG with hardware decoder");
+ esp_err_t ret = ESP_OK;
+
+ jpeg_decoder_handle_t jpeg_dec = NULL;
+ uint8_t* bit_stream = NULL;
+ uint8_t* out_buf = NULL;
+ size_t out_buf_len = 0;
+ size_t tx_buffer_size = 0;
+ size_t rx_buffer_size = 0;
+
+ jpeg_decode_engine_cfg_t eng_cfg = {
+ .intr_priority = 1,
+ .timeout_ms = 1000,
+ };
+
+ jpeg_decode_cfg_t decode_cfg_rgb = {
+ .output_format = JPEG_DECODE_OUT_FORMAT_RGB565,
+ .rgb_order = JPEG_DEC_RGB_ELEMENT_ORDER_BGR,
+ };
+
+ ret = jpeg_new_decoder_engine(&eng_cfg, &jpeg_dec);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to create JPEG decoder engine");
+ goto jpeg_hw_dec_failed;
+ }
+
+ jpeg_decode_memory_alloc_cfg_t tx_mem_cfg = {
+ .buffer_direction = JPEG_DEC_ALLOC_INPUT_BUFFER,
+ };
+
+ jpeg_decode_memory_alloc_cfg_t rx_mem_cfg = {
+ .buffer_direction = JPEG_DEC_ALLOC_OUTPUT_BUFFER,
+ };
+
+ bit_stream = (uint8_t*)jpeg_alloc_decoder_mem(src_len, &tx_mem_cfg, &tx_buffer_size);
+ if (bit_stream == NULL || tx_buffer_size < src_len) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG bit stream");
+ ret = ESP_ERR_NO_MEM;
+ goto jpeg_hw_dec_failed;
+ }
+
+ memcpy(bit_stream, src, src_len);
+
+ jpeg_decode_picture_info_t header_info;
+ ESP_GOTO_ON_ERROR(jpeg_decoder_get_info(bit_stream, src_len, &header_info), jpeg_hw_dec_failed, TAG,
+ "Failed to get JPEG header info");
+
+ ESP_LOGD(TAG, "JPEG header info: width=%d, height=%d, sample_method=%d", header_info.width, header_info.height,
+ (int)header_info.sample_method);
+
+ switch (header_info.sample_method) {
+ case JPEG_DOWN_SAMPLING_GRAY:
+ case JPEG_DOWN_SAMPLING_YUV444:
+ out_buf_len = header_info.width * header_info.height * 2;
+ *stride = header_info.width * 2;
+ break;
+ case JPEG_DOWN_SAMPLING_YUV422:
+ case JPEG_DOWN_SAMPLING_YUV420:
+ out_buf_len = ((header_info.width + 15) & ~15) * ((header_info.height + 15) & ~15) * 2;
+ *stride = ((header_info.width + 15) & ~15) * 2;
+ break;
+ default:
+ ESP_LOGE(TAG, "Unsupported JPEG sample method");
+ ret = ESP_ERR_NOT_SUPPORTED;
+ goto jpeg_hw_dec_failed;
+ }
+
+ out_buf = (uint8_t*)jpeg_alloc_decoder_mem(out_buf_len, &rx_mem_cfg, &rx_buffer_size);
+ if (out_buf == NULL || rx_buffer_size < out_buf_len) {
+ ESP_LOGE(TAG, "Failed to allocate memory for JPEG output buffer");
+ ret = ESP_ERR_NO_MEM;
+ goto jpeg_hw_dec_failed;
+ }
+
+ uint32_t out_size = 0;
+
+ ESP_GOTO_ON_ERROR(
+ jpeg_decoder_process(jpeg_dec, &decode_cfg_rgb, bit_stream, src_len, out_buf, out_buf_len, &out_size),
+ jpeg_hw_dec_failed, TAG, "Failed to decode JPEG");
+
+ ESP_LOGD(TAG, "Expected %d bytes, got %" PRIu32 " bytes", out_buf_len, out_size);
+
+ if (out_size != out_buf_len) {
+ ESP_LOGE(TAG, "Decoded image size mismatch: Expected %zu bytes, got %" PRIu32 " bytes", out_buf_len, out_size);
+ ret = ESP_ERR_INVALID_SIZE;
+ goto jpeg_hw_dec_failed;
+ }
+
+ if (header_info.sample_method == JPEG_DOWN_SAMPLING_GRAY) {
+ // convert GRAY8 to RGB565
+ uint32_t i = header_info.width * header_info.height;
+ do {
+ --i;
+ uint8_t r = (out_buf[i] >> 3) & 0x1F;
+ uint8_t g = (out_buf[i] >> 2) & 0x3F;
+ // b is same as r
+ uint16_t rgb565 = (r << 11) | (g << 5) | r;
+ out_buf[2 * i + 1] = (rgb565 >> 8) & 0xFF;
+ out_buf[2 * i] = rgb565 & 0xFF;
+ } while (i != 0);
+ out_size = header_info.width * header_info.height * 2;
+ ESP_LOGD(TAG, "Converted GRAY8 to RGB565, new size: %zu", out_size);
+ }
+
+ ESP_LOG_BUFFER_HEXDUMP(TAG, out_buf, MIN(out_size, 256), ESP_LOG_DEBUG);
+
+ *out = out_buf;
+ out_buf = NULL;
+ *out_len = (size_t)out_size;
+ jpeg_del_decoder_engine(jpeg_dec);
+ jpeg_dec = NULL;
+ heap_caps_free(bit_stream);
+ bit_stream = NULL;
+ *width = header_info.width;
+ *height = header_info.height;
+
+ return ret;
+
+jpeg_hw_dec_failed:
+ if (out_buf) {
+ heap_caps_free(out_buf);
+ out_buf = NULL;
+ }
+ if (bit_stream) {
+ heap_caps_free(bit_stream);
+ bit_stream = NULL;
+ }
+ if (jpeg_dec) {
+ jpeg_del_decoder_engine(jpeg_dec);
+ jpeg_dec = NULL;
+ }
+ *out = NULL;
+ *out_len = 0;
+ *width = 0;
+ *height = 0;
+ *stride = 0;
+ return ret;
+}
+#endif // CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER
+
+esp_err_t jpeg_to_image(const uint8_t* src, size_t src_len, uint8_t** out, size_t* out_len, size_t* width,
+ size_t* height, size_t* stride) {
+#ifdef CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+ esp_log_level_set(TAG, ESP_LOG_DEBUG);
+#endif // CONFIG_XIAOZHI_ENABLE_CAMERA_DEBUG_MODE
+ if (src == NULL || src_len == 0 || out == NULL || out_len == NULL || width == NULL || height == NULL ||
+ stride == NULL) {
+ ESP_LOGE(TAG, "Invalid parameters");
+ return ESP_ERR_INVALID_ARG;
+ }
+#ifdef CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER
+ esp_err_t ret = decode_with_hardware_jpeg(src, src_len, out, out_len, width, height, stride);
+ if (ret == ESP_OK) {
+ return ret;
+ }
+ ESP_LOGW(TAG, "Failed to decode with hardware JPEG, fallback to software decoder");
+ // Fallback to esp_new_jpeg
+#endif
+ return decode_with_new_jpeg(src, src_len, out, out_len, width, height, stride);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.h
new file mode 100644
index 0000000..b33dcef
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/jpg/jpeg_to_image.h
@@ -0,0 +1,62 @@
+#include "sdkconfig.h"
+#ifndef CONFIG_IDF_TARGET_ESP32
+
+#include <esp_err.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @brief Decodes a JPEG image from memory to raw RGB565 pixel data
+ *
+ * This function attempts to decode a JPEG image using hardware acceleration first (if enabled),
+ * falling back to a software decoder if hardware decoding fails or is unavailable.
+ *
+ * @param[in] src Pointer to the JPEG bitstream in memory
+ * @param[in] src_len Length of the JPEG bitstream in bytes
+ * @param[out] out Pointer to a buffer pointer that will be set to the decoded image data.
+ * This buffer is allocated internally and MUST be freed by the caller using heap_caps_free().
+ * @param[out] out_len Pointer to a variable that will receive the size of the decoded image data in bytes
+ * @param[out] width Pointer to a variable that will receive the image width in pixels
+ * @param[out] height Pointer to a variable that will receive the image height in pixels
+ * @param[out] stride Pointer to a variable that will receive the image stride in bytes
+ *
+ * @return ESP_OK on successful decoding
+ * @return ESP_ERR_INVALID_ARG on invalid parameters
+ * @return ESP_ERR_NO_MEM on memory allocation failure
+ * @return ESP_FAIL on failure
+ *
+ * @attention Memory Management for `*out`:
+ * - The function allocates memory for the decoded image internally
+ * - On success, the caller takes ownership of this memory and SHOULD free it using heap_caps_free()
+ * - On failure, `*out` is guaranteed to be NULL and no freeing is required
+ * - Example usage:
+ * @code{.c}
+ * uint8_t *image = NULL;
+ * size_t len, width, height;
+ * if (jpeg_to_image(jpeg_data, jpeg_len, &image, &len, &width, &height)) {
+ * // Use image data...
+ * heap_caps_free(image); // Critical: use heap_caps_free
+ * }
+ * @endcode
+ *
+ * @note Configuration dependency:
+ * - When CONFIG_XIAOZHI_ENABLE_HARDWARE_JPEG_DECODER is enabled, hardware acceleration is attempted first
+ * - Both hardware and software paths allocate memory that requires heap_caps_free() for deallocation
+ * - The decoded image format is always RGB565 (2 bytes per pixel)
+ *
+ * @note When using hardware decoder, the decoded image dimensions might be aligned up to 16-byte boundaries.
+ * For YUV420 or YUV422 compressed images, both width and height will be rounded up to the nearest multiple of 16.
+ * See details at
+ * <https://docs.espressif.com/projects/esp-idf/en/stable/esp32p4/api-reference/peripherals/jpeg.html#jpeg-decoder-engine>
+ *
+ */
+esp_err_t jpeg_to_image(const uint8_t* src, size_t src_len, uint8_t** out, size_t* out_len, size_t* width,
+ size_t* height, size_t* stride);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif // CONFIG_IDF_TARGET_ESP32
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.cc b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.cc
new file mode 100644
index 0000000..46668dd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.cc
@@ -0,0 +1,274 @@
+#include <esp_log.h>
+#include <esp_err.h>
+#include <string>
+#include <cstdlib>
+#include <cstring>
+#include <font_awesome.h>
+
+#include "lvgl_display.h"
+#include "board.h"
+#include "application.h"
+#include "audio_codec.h"
+#include "settings.h"
+#include "assets/lang_config.h"
+#include "jpg/image_to_jpeg.h"
+
+#define TAG "Display"
+
+LvglDisplay::LvglDisplay() {
+ // Notification timer
+ esp_timer_create_args_t notification_timer_args = {
+ .callback = [](void *arg) {
+ LvglDisplay *display = static_cast<LvglDisplay*>(arg);
+ DisplayLockGuard lock(display);
+ lv_obj_add_flag(display->notification_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_remove_flag(display->status_label_, LV_OBJ_FLAG_HIDDEN);
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "notification_timer",
+ .skip_unhandled_events = false,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(¬ification_timer_args, ¬ification_timer_));
+
+ // Create a power management lock
+ auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_);
+ if (ret == ESP_ERR_NOT_SUPPORTED) {
+ ESP_LOGI(TAG, "Power management not supported");
+ } else {
+ ESP_ERROR_CHECK(ret);
+ }
+}
+
+LvglDisplay::~LvglDisplay() {
+ if (notification_timer_ != nullptr) {
+ esp_timer_stop(notification_timer_);
+ esp_timer_delete(notification_timer_);
+ }
+
+ if (network_label_ != nullptr) {
+ lv_obj_del(network_label_);
+ }
+ if (notification_label_ != nullptr) {
+ lv_obj_del(notification_label_);
+ }
+ if (status_label_ != nullptr) {
+ lv_obj_del(status_label_);
+ }
+ if (mute_label_ != nullptr) {
+ lv_obj_del(mute_label_);
+ }
+ if (battery_label_ != nullptr) {
+ lv_obj_del(battery_label_);
+ }
+ if( low_battery_popup_ != nullptr ) {
+ lv_obj_del(low_battery_popup_);
+ }
+ if (pm_lock_ != nullptr) {
+ esp_pm_lock_delete(pm_lock_);
+ }
+}
+
+void LvglDisplay::SetStatus(const char* status) {
+ if (!setup_ui_called_) {
+ ESP_LOGW(TAG, "SetStatus('%s') called before SetupUI() - message will be lost!", status);
+ }
+ DisplayLockGuard lock(this);
+ if (status_label_ == nullptr) {
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetStatus('%s') failed: status_label_ is nullptr (SetupUI() was called but label not created)", status);
+ }
+ return;
+ }
+ lv_label_set_text(status_label_, status);
+ lv_obj_remove_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
+
+ last_status_update_time_ = std::chrono::system_clock::now();
+}
+
+void LvglDisplay::ShowNotification(const std::string ¬ification, int duration_ms) {
+ ShowNotification(notification.c_str(), duration_ms);
+}
+
+void LvglDisplay::ShowNotification(const char* notification, int duration_ms) {
+ if (!setup_ui_called_) {
+ ESP_LOGW(TAG, "ShowNotification('%s') called before SetupUI() - message will be lost!", notification);
+ }
+ DisplayLockGuard lock(this);
+ if (notification_label_ == nullptr) {
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "ShowNotification('%s') failed: notification_label_ is nullptr (SetupUI() was called but label not created)", notification);
+ }
+ return;
+ }
+ lv_label_set_text(notification_label_, notification);
+ lv_obj_remove_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
+ lv_obj_add_flag(status_label_, LV_OBJ_FLAG_HIDDEN);
+
+ esp_timer_stop(notification_timer_);
+ ESP_ERROR_CHECK(esp_timer_start_once(notification_timer_, duration_ms * 1000));
+}
+
+void LvglDisplay::UpdateStatusBar(bool update_all) {
+ auto& app = Application::GetInstance();
+ auto& board = Board::GetInstance();
+ auto codec = board.GetAudioCodec();
+
+ // Update mute icon
+ {
+ DisplayLockGuard lock(this);
+ if (mute_label_ == nullptr) {
+ return;
+ }
+
+ // Update icon if mute state changes
+ if (codec->output_volume() == 0 && !muted_) {
+ muted_ = true;
+ lv_label_set_text(mute_label_, FONT_AWESOME_VOLUME_XMARK);
+ } else if (codec->output_volume() > 0 && muted_) {
+ muted_ = false;
+ lv_label_set_text(mute_label_, "");
+ }
+ }
+
+ // Update time
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ if (last_status_update_time_ + std::chrono::seconds(10) < std::chrono::system_clock::now()) {
+ // Set status to clock "HH:MM"
+ time_t now = time(NULL);
+ struct tm* tm = localtime(&now);
+ // Check if the we have already set the time
+ if (tm->tm_year >= 2025 - 1900) {
+ char time_str[16];
+ strftime(time_str, sizeof(time_str), "%H:%M", tm);
+ SetStatus(time_str);
+ } else {
+ ESP_LOGW(TAG, "System time is not set, tm_year: %d", tm->tm_year);
+ }
+ }
+ }
+
+ esp_pm_lock_acquire(pm_lock_);
+ // Update battery icon
+ int battery_level;
+ bool charging, discharging;
+ const char* icon = nullptr;
+ if (board.GetBatteryLevel(battery_level, charging, discharging)) {
+ if (charging) {
+ icon = FONT_AWESOME_BATTERY_BOLT;
+ } else {
+ const char* levels[] = {
+ FONT_AWESOME_BATTERY_EMPTY, // 0-19%
+ FONT_AWESOME_BATTERY_QUARTER, // 20-39%
+ FONT_AWESOME_BATTERY_HALF, // 40-59%
+ FONT_AWESOME_BATTERY_THREE_QUARTERS, // 60-79%
+ FONT_AWESOME_BATTERY_FULL, // 80-99%
+ FONT_AWESOME_BATTERY_FULL, // 100%
+ };
+ icon = levels[battery_level / 20];
+ }
+ DisplayLockGuard lock(this);
+ if (battery_label_ != nullptr && battery_icon_ != icon) {
+ battery_icon_ = icon;
+ lv_label_set_text(battery_label_, battery_icon_);
+ }
+
+ // Check low battery popup only when clock tick event is triggered
+ // Because when initializing, the battery level is not ready yet.
+ if (low_battery_popup_ != nullptr && !update_all) {
+ if (strcmp(icon, FONT_AWESOME_BATTERY_EMPTY) == 0 && discharging) {
+ if (lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // Show if low battery popup is hidden
+ lv_obj_remove_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
+ app.Schedule([&app]() {
+ app.PlaySound(Lang::Sounds::OGG_LOW_BATTERY);
+ });
+ }
+ } else {
+ // Hide the low battery popup when the battery is not empty
+ if (!lv_obj_has_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN)) { // Hide if low battery popup is shown
+ lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
+ }
+ }
+ }
+ }
+
+ // Update network icon every 10 seconds
+ static int seconds_counter = 0;
+ if (update_all || seconds_counter++ % 10 == 0) {
+ // Don't read 4G network status during firmware upgrade to avoid occupying UART resources
+ auto device_state = Application::GetInstance().GetDeviceState();
+ static const std::vector<DeviceState> allowed_states = {
+ kDeviceStateIdle,
+ kDeviceStateStarting,
+ kDeviceStateWifiConfiguring,
+ kDeviceStateListening,
+ kDeviceStateActivating,
+ };
+ if (std::find(allowed_states.begin(), allowed_states.end(), device_state) != allowed_states.end()) {
+ icon = board.GetNetworkStateIcon();
+ if (network_label_ != nullptr && icon != nullptr && network_icon_ != icon) {
+ DisplayLockGuard lock(this);
+ network_icon_ = icon;
+ lv_label_set_text(network_label_, network_icon_);
+ }
+ }
+ }
+
+ esp_pm_lock_release(pm_lock_);
+}
+
+void LvglDisplay::SetPreviewImage(std::unique_ptr<LvglImage> image) {
+}
+
+void LvglDisplay::SetPowerSaveMode(bool on) {
+ if (on) {
+ SetChatMessage("system", "");
+ SetEmotion("sleepy");
+ } else {
+ SetChatMessage("system", "");
+ SetEmotion("neutral");
+ }
+}
+
+bool LvglDisplay::SnapshotToJpeg(std::string& jpeg_data, int quality) {
+#if CONFIG_LV_USE_SNAPSHOT
+ DisplayLockGuard lock(this);
+
+ lv_obj_t* screen = lv_screen_active();
+ lv_draw_buf_t* draw_buffer = lv_snapshot_take(screen, LV_COLOR_FORMAT_RGB565);
+ if (draw_buffer == nullptr) {
+ ESP_LOGE(TAG, "Failed to take snapshot, draw_buffer is nullptr");
+ return false;
+ }
+
+ // swap bytes
+ uint16_t* data = (uint16_t*)draw_buffer->data;
+ size_t pixel_count = draw_buffer->data_size / 2;
+ for (size_t i = 0; i < pixel_count; i++) {
+ data[i] = __builtin_bswap16(data[i]);
+ }
+
+ // Clear output string and use callback version to avoid pre-allocating large memory blocks
+ jpeg_data.clear();
+
+ // Use callback-based JPEG encoder to further save memory
+ bool ret = image_to_jpeg_cb((uint8_t*)draw_buffer->data, draw_buffer->data_size, draw_buffer->header.w, draw_buffer->header.h, V4L2_PIX_FMT_RGB565, quality,
+ [](void *arg, size_t index, const void *data, size_t len) -> size_t {
+ std::string* output = static_cast<std::string*>(arg);
+ if (data && len > 0) {
+ output->append(static_cast<const char*>(data), len);
+ }
+ return len;
+ }, &jpeg_data);
+ if (!ret) {
+ ESP_LOGE(TAG, "Failed to convert image to JPEG");
+ }
+
+ lv_draw_buf_destroy(draw_buffer);
+ return ret;
+#else
+ ESP_LOGE(TAG, "LV_USE_SNAPSHOT is not enabled");
+ return false;
+#endif
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.h
new file mode 100644
index 0000000..226a6ff
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_display.h
@@ -0,0 +1,53 @@
+#ifndef LVGL_DISPLAY_H
+#define LVGL_DISPLAY_H
+
+#include "display.h"
+#include "lvgl_image.h"
+
+#include <lvgl.h>
+#include <esp_timer.h>
+#include <esp_log.h>
+#include <esp_pm.h>
+
+#include <string>
+#include <chrono>
+
+class LvglDisplay : public Display {
+public:
+ LvglDisplay();
+ virtual ~LvglDisplay();
+
+ virtual void SetStatus(const char* status);
+ virtual void ShowNotification(const char* notification, int duration_ms = 3000);
+ virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000);
+ virtual void SetPreviewImage(std::unique_ptr<LvglImage> image);
+ virtual void UpdateStatusBar(bool update_all = false);
+ virtual void SetPowerSaveMode(bool on);
+ virtual bool SnapshotToJpeg(std::string& jpeg_data, int quality = 80);
+
+protected:
+ esp_pm_lock_handle_t pm_lock_ = nullptr;
+ lv_display_t *display_ = nullptr;
+
+ lv_obj_t *network_label_ = nullptr;
+ lv_obj_t *status_label_ = nullptr;
+ lv_obj_t *notification_label_ = nullptr;
+ lv_obj_t *mute_label_ = nullptr;
+ lv_obj_t *battery_label_ = nullptr;
+ lv_obj_t* low_battery_popup_ = nullptr;
+ lv_obj_t* low_battery_label_ = nullptr;
+
+ const char* battery_icon_ = nullptr;
+ const char* network_icon_ = nullptr;
+ bool muted_ = false;
+
+ std::chrono::system_clock::time_point last_status_update_time_;
+ esp_timer_handle_t notification_timer_ = nullptr;
+
+ friend class DisplayLockGuard;
+ virtual bool Lock(int timeout_ms = 0) = 0;
+ virtual void Unlock() = 0;
+};
+
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.cc b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.cc
new file mode 100644
index 0000000..b0a45c7
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.cc
@@ -0,0 +1,13 @@
+#include "lvgl_font.h"
+#include <cbin_font.h>
+
+
+LvglCBinFont::LvglCBinFont(void* data) {
+ font_ = cbin_font_create(static_cast<uint8_t*>(data));
+}
+
+LvglCBinFont::~LvglCBinFont() {
+ if (font_ != nullptr) {
+ cbin_font_delete(font_);
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.h
new file mode 100644
index 0000000..d539dc0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_font.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include <lvgl.h>
+
+
+class LvglFont {
+public:
+ virtual const lv_font_t* font() const = 0;
+ virtual ~LvglFont() = default;
+};
+
+// Built-in font
+class LvglBuiltInFont : public LvglFont {
+public:
+ LvglBuiltInFont(const lv_font_t* font) : font_(font) {}
+ virtual const lv_font_t* font() const override { return font_; }
+
+private:
+ const lv_font_t* font_;
+};
+
+
+class LvglCBinFont : public LvglFont {
+public:
+ LvglCBinFont(void* data);
+ virtual ~LvglCBinFont();
+ virtual const lv_font_t* font() const override { return font_; }
+
+private:
+ lv_font_t* font_;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.cc b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.cc
new file mode 100644
index 0000000..eefc031
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.cc
@@ -0,0 +1,64 @@
+#include "lvgl_image.h"
+#include <cbin_font.h>
+
+#include <esp_log.h>
+#include <stdexcept>
+#include <cstring>
+#include <esp_heap_caps.h>
+
+#define TAG "LvglImage"
+
+
+LvglRawImage::LvglRawImage(void* data, size_t size) {
+ bzero(&image_dsc_, sizeof(image_dsc_));
+ image_dsc_.data_size = size;
+ image_dsc_.data = static_cast<uint8_t*>(data);
+ image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
+ image_dsc_.header.cf = LV_COLOR_FORMAT_RAW_ALPHA;
+ image_dsc_.header.w = 0;
+ image_dsc_.header.h = 0;
+}
+
+bool LvglRawImage::IsGif() const {
+ auto ptr = (const uint8_t*)image_dsc_.data;
+ return ptr[0] == 'G' && ptr[1] == 'I' && ptr[2] == 'F';
+}
+
+LvglCBinImage::LvglCBinImage(void* data) {
+ image_dsc_ = cbin_img_dsc_create(static_cast<uint8_t*>(data));
+}
+
+LvglCBinImage::~LvglCBinImage() {
+ if (image_dsc_ != nullptr) {
+ cbin_img_dsc_delete(image_dsc_);
+ }
+}
+
+LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size) {
+ bzero(&image_dsc_, sizeof(image_dsc_));
+ image_dsc_.data_size = size;
+ image_dsc_.data = static_cast<uint8_t*>(data);
+
+ if (lv_image_decoder_get_info(&image_dsc_, &image_dsc_.header) != LV_RESULT_OK) {
+ ESP_LOGE(TAG, "Failed to get image info, data: %p size: %u", data, size);
+ throw std::runtime_error("Failed to get image info");
+ }
+}
+
+LvglAllocatedImage::LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format) {
+ bzero(&image_dsc_, sizeof(image_dsc_));
+ image_dsc_.data_size = size;
+ image_dsc_.data = static_cast<uint8_t*>(data);
+ image_dsc_.header.magic = LV_IMAGE_HEADER_MAGIC;
+ image_dsc_.header.cf = color_format;
+ image_dsc_.header.w = width;
+ image_dsc_.header.h = height;
+ image_dsc_.header.stride = stride;
+}
+
+LvglAllocatedImage::~LvglAllocatedImage() {
+ if (image_dsc_.data) {
+ heap_caps_free((void*)image_dsc_.data);
+ image_dsc_.data = nullptr;
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.h
new file mode 100644
index 0000000..0bbf39b
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_image.h
@@ -0,0 +1,53 @@
+#pragma once
+
+#include <lvgl.h>
+
+
+// Wrap around lv_img_dsc_t
+class LvglImage {
+public:
+ virtual const lv_img_dsc_t* image_dsc() const = 0;
+ virtual bool IsGif() const { return false; }
+ virtual ~LvglImage() = default;
+};
+
+
+class LvglRawImage : public LvglImage {
+public:
+ LvglRawImage(void* data, size_t size);
+ virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; }
+ virtual bool IsGif() const;
+
+private:
+ lv_img_dsc_t image_dsc_;
+};
+
+class LvglCBinImage : public LvglImage {
+public:
+ LvglCBinImage(void* data);
+ virtual ~LvglCBinImage();
+ virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; }
+
+private:
+ lv_img_dsc_t* image_dsc_ = nullptr;
+};
+
+class LvglSourceImage : public LvglImage {
+public:
+ LvglSourceImage(const lv_img_dsc_t* image_dsc) : image_dsc_(image_dsc) {}
+ virtual const lv_img_dsc_t* image_dsc() const override { return image_dsc_; }
+
+private:
+ const lv_img_dsc_t* image_dsc_;
+};
+
+class LvglAllocatedImage : public LvglImage {
+public:
+ LvglAllocatedImage(void* data, size_t size);
+ LvglAllocatedImage(void* data, size_t size, int width, int height, int stride, int color_format);
+ virtual ~LvglAllocatedImage();
+ virtual const lv_img_dsc_t* image_dsc() const override { return &image_dsc_; }
+
+private:
+ lv_img_dsc_t image_dsc_;
+};
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.cc b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.cc
new file mode 100644
index 0000000..f1bdffe
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.cc
@@ -0,0 +1,30 @@
+#include "lvgl_theme.h"
+
+LvglTheme::LvglTheme(const std::string& name) : Theme(name) {
+}
+
+lv_color_t LvglTheme::ParseColor(const std::string& color) {
+ if (color.find("#") == 0) {
+ // Convert #112233 to lv_color_t
+ uint8_t r = strtol(color.substr(1, 2).c_str(), nullptr, 16);
+ uint8_t g = strtol(color.substr(3, 2).c_str(), nullptr, 16);
+ uint8_t b = strtol(color.substr(5, 2).c_str(), nullptr, 16);
+ return lv_color_make(r, g, b);
+ }
+ return lv_color_black();
+}
+
+LvglThemeManager::LvglThemeManager() {
+}
+
+LvglTheme* LvglThemeManager::GetTheme(const std::string& theme_name) {
+ auto it = themes_.find(theme_name);
+ if (it != themes_.end()) {
+ return it->second;
+ }
+ return nullptr;
+}
+
+void LvglThemeManager::RegisterTheme(const std::string& theme_name, LvglTheme* theme) {
+ themes_[theme_name] = theme;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.h b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.h
new file mode 100644
index 0000000..85527a9
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/lvgl_display/lvgl_theme.h
@@ -0,0 +1,94 @@
+#pragma once
+
+#include "display.h"
+#include "lvgl_image.h"
+#include "lvgl_font.h"
+#include "emoji_collection.h"
+
+#include <lvgl.h>
+#include <memory>
+#include <map>
+#include <string>
+
+
+class LvglTheme : public Theme {
+public:
+ static lv_color_t ParseColor(const std::string& color);
+
+ LvglTheme(const std::string& name);
+
+ // Properties
+ inline lv_color_t background_color() const { return background_color_; }
+ inline lv_color_t text_color() const { return text_color_; }
+ inline lv_color_t chat_background_color() const { return chat_background_color_; }
+ inline lv_color_t user_bubble_color() const { return user_bubble_color_; }
+ inline lv_color_t assistant_bubble_color() const { return assistant_bubble_color_; }
+ inline lv_color_t system_bubble_color() const { return system_bubble_color_; }
+ inline lv_color_t system_text_color() const { return system_text_color_; }
+ inline lv_color_t border_color() const { return border_color_; }
+ inline lv_color_t low_battery_color() const { return low_battery_color_; }
+ inline std::shared_ptr<LvglImage> background_image() const { return background_image_; }
+ inline std::shared_ptr<EmojiCollection> emoji_collection() const { return emoji_collection_; }
+ inline std::shared_ptr<LvglFont> text_font() const { return text_font_; }
+ inline std::shared_ptr<LvglFont> icon_font() const { return icon_font_; }
+ inline std::shared_ptr<LvglFont> large_icon_font() const { return large_icon_font_; }
+ inline int spacing(int scale) const { return spacing_ * scale; }
+
+ inline void set_background_color(lv_color_t background) { background_color_ = background; }
+ inline void set_text_color(lv_color_t text) { text_color_ = text; }
+ inline void set_chat_background_color(lv_color_t chat_background) { chat_background_color_ = chat_background; }
+ inline void set_user_bubble_color(lv_color_t user_bubble) { user_bubble_color_ = user_bubble; }
+ inline void set_assistant_bubble_color(lv_color_t assistant_bubble) { assistant_bubble_color_ = assistant_bubble; }
+ inline void set_system_bubble_color(lv_color_t system_bubble) { system_bubble_color_ = system_bubble; }
+ inline void set_system_text_color(lv_color_t system_text) { system_text_color_ = system_text; }
+ inline void set_border_color(lv_color_t border) { border_color_ = border; }
+ inline void set_low_battery_color(lv_color_t low_battery) { low_battery_color_ = low_battery; }
+ inline void set_background_image(std::shared_ptr<LvglImage> background_image) { background_image_ = background_image; }
+ inline void set_emoji_collection(std::shared_ptr<EmojiCollection> emoji_collection) { emoji_collection_ = emoji_collection; }
+ inline void set_text_font(std::shared_ptr<LvglFont> text_font) { text_font_ = text_font; }
+ inline void set_icon_font(std::shared_ptr<LvglFont> icon_font) { icon_font_ = icon_font; }
+ inline void set_large_icon_font(std::shared_ptr<LvglFont> large_icon_font) { large_icon_font_ = large_icon_font; }
+
+private:
+ int spacing_ = 2;
+
+ // Colors
+ lv_color_t background_color_;
+ lv_color_t text_color_;
+ lv_color_t chat_background_color_;
+ lv_color_t user_bubble_color_;
+ lv_color_t assistant_bubble_color_;
+ lv_color_t system_bubble_color_;
+ lv_color_t system_text_color_;
+ lv_color_t border_color_;
+ lv_color_t low_battery_color_;
+
+ // Background image
+ std::shared_ptr<LvglImage> background_image_ = nullptr;
+
+ // fonts
+ std::shared_ptr<LvglFont> text_font_ = nullptr;
+ std::shared_ptr<LvglFont> icon_font_ = nullptr;
+ std::shared_ptr<LvglFont> large_icon_font_ = nullptr;
+
+ // Emoji collection
+ std::shared_ptr<EmojiCollection> emoji_collection_ = nullptr;
+};
+
+
+class LvglThemeManager {
+public:
+ static LvglThemeManager& GetInstance() {
+ static LvglThemeManager instance;
+ return instance;
+ }
+
+ void RegisterTheme(const std::string& theme_name, LvglTheme* theme);
+ LvglTheme* GetTheme(const std::string& theme_name);
+
+private:
+ LvglThemeManager();
+ void InitializeDefaultThemes();
+
+ std::map<std::string, LvglTheme*> themes_;
+};
diff --git a/xiaozhi-esp32-2.2.6/main/display/oled_display.cc b/xiaozhi-esp32-2.2.6/main/display/oled_display.cc
new file mode 100644
index 0000000..2083a50
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/oled_display.cc
@@ -0,0 +1,408 @@
+#include "oled_display.h"
+#include "assets/lang_config.h"
+#include "lvgl_theme.h"
+#include "lvgl_font.h"
+
+#include <string>
+#include <algorithm>
+
+#include <esp_log.h>
+#include <esp_err.h>
+#include <esp_lvgl_port.h>
+#include <font_awesome.h>
+
+#define TAG "OledDisplay"
+
+LV_FONT_DECLARE(BUILTIN_TEXT_FONT);
+LV_FONT_DECLARE(BUILTIN_ICON_FONT);
+LV_FONT_DECLARE(font_awesome_30_1);
+
+OledDisplay::OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel,
+ int width, int height, bool mirror_x, bool mirror_y)
+ : panel_io_(panel_io), panel_(panel) {
+ width_ = width;
+ height_ = height;
+
+ auto text_font = std::make_shared<LvglBuiltInFont>(&BUILTIN_TEXT_FONT);
+ auto icon_font = std::make_shared<LvglBuiltInFont>(&BUILTIN_ICON_FONT);
+ auto large_icon_font = std::make_shared<LvglBuiltInFont>(&font_awesome_30_1);
+
+ auto dark_theme = new LvglTheme("dark");
+ dark_theme->set_text_font(text_font);
+ dark_theme->set_icon_font(icon_font);
+ dark_theme->set_large_icon_font(large_icon_font);
+
+ auto& theme_manager = LvglThemeManager::GetInstance();
+ theme_manager.RegisterTheme("dark", dark_theme);
+ current_theme_ = dark_theme;
+
+ ESP_LOGI(TAG, "Initialize LVGL");
+ lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG();
+ port_cfg.task_priority = 1;
+ port_cfg.task_stack = 6144;
+#if CONFIG_SOC_CPU_CORES_NUM > 1
+ port_cfg.task_affinity = 1;
+#endif
+ lvgl_port_init(&port_cfg);
+
+ ESP_LOGI(TAG, "Adding OLED display");
+ const lvgl_port_display_cfg_t display_cfg = {
+ .io_handle = panel_io_,
+ .panel_handle = panel_,
+ .control_handle = nullptr,
+ .buffer_size = static_cast<uint32_t>(width_ * height_),
+ .double_buffer = false,
+ .trans_size = 0,
+ .hres = static_cast<uint32_t>(width_),
+ .vres = static_cast<uint32_t>(height_),
+ .monochrome = true,
+ .rotation = {
+ .swap_xy = false,
+ .mirror_x = mirror_x,
+ .mirror_y = mirror_y,
+ },
+ .flags = {
+ .buff_dma = 1,
+ .buff_spiram = 0,
+ .sw_rotate = 0,
+ .full_refresh = 0,
+ .direct_mode = 0,
+ },
+ };
+
+ display_ = lvgl_port_add_disp(&display_cfg);
+ if (display_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to add display");
+ return;
+ }
+
+ // Note: SetupUI() should be called by Application::Initialize(), not in constructor
+ // to ensure lvgl objects are created after the display is fully initialized.
+}
+
+void OledDisplay::SetupUI() {
+ // Prevent duplicate calls - if already called, return early
+ if (setup_ui_called_) {
+ ESP_LOGW(TAG, "SetupUI() called multiple times, skipping duplicate call");
+ return;
+ }
+
+ Display::SetupUI(); // Mark SetupUI as called
+ if (height_ == 64) {
+ SetupUI_128x64();
+ } else {
+ SetupUI_128x32();
+ }
+}
+
+OledDisplay::~OledDisplay() {
+ if (content_ != nullptr) {
+ lv_obj_del(content_);
+ }
+
+ bool is_128x64_layout = (top_bar_ != nullptr);
+ if (status_bar_ != nullptr && is_128x64_layout) {
+ status_label_ = nullptr;
+ notification_label_ = nullptr;
+ lv_obj_del(status_bar_);
+ }
+ if (top_bar_ != nullptr) {
+ network_label_ = nullptr;
+ mute_label_ = nullptr;
+ battery_label_ = nullptr;
+ lv_obj_del(top_bar_);
+ }
+ if (side_bar_ != nullptr) {
+ if (!is_128x64_layout) {
+ status_label_ = nullptr;
+ notification_label_ = nullptr;
+ network_label_ = nullptr;
+ mute_label_ = nullptr;
+ battery_label_ = nullptr;
+ }
+ lv_obj_del(side_bar_);
+ }
+ if (container_ != nullptr) {
+ lv_obj_del(container_);
+ }
+
+ if (panel_ != nullptr) {
+ esp_lcd_panel_del(panel_);
+ }
+ if (panel_io_ != nullptr) {
+ esp_lcd_panel_io_del(panel_io_);
+ }
+ lvgl_port_deinit();
+}
+
+bool OledDisplay::Lock(int timeout_ms) {
+ return lvgl_port_lock(timeout_ms);
+}
+
+void OledDisplay::Unlock() {
+ lvgl_port_unlock();
+}
+
+void OledDisplay::SetChatMessage(const char* role, const char* content) {
+ DisplayLockGuard lock(this);
+ if (chat_message_label_ == nullptr) {
+ return;
+ }
+
+ // Replace all newlines with spaces
+ std::string content_str = content;
+ std::replace(content_str.begin(), content_str.end(), '\n', ' ');
+
+ if (content_right_ == nullptr) {
+ lv_label_set_text(chat_message_label_, content_str.c_str());
+ } else {
+ if (content == nullptr || content[0] == '\0') {
+ lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
+ } else {
+ lv_label_set_text(chat_message_label_, content_str.c_str());
+ lv_obj_remove_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
+ }
+ }
+}
+
+void OledDisplay::SetupUI_128x64() {
+ DisplayLockGuard lock(this);
+
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+ auto large_icon_font = lvgl_theme->large_icon_font()->font();
+
+ auto screen = lv_screen_active();
+ lv_obj_set_style_text_font(screen, text_font, 0);
+ lv_obj_set_style_text_color(screen, lv_color_black(), 0);
+
+ /* Container */
+ container_ = lv_obj_create(screen);
+ lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
+ lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_style_pad_all(container_, 0, 0);
+ lv_obj_set_style_border_width(container_, 0, 0);
+ lv_obj_set_style_pad_row(container_, 0, 0);
+
+ /* Layer 1: Top bar - for status icons */
+ top_bar_ = lv_obj_create(container_);
+ lv_obj_set_size(top_bar_, LV_HOR_RES, 16);
+ lv_obj_set_style_radius(top_bar_, 0, 0);
+ lv_obj_set_style_bg_opa(top_bar_, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_border_width(top_bar_, 0, 0);
+ lv_obj_set_style_pad_all(top_bar_, 0, 0);
+ lv_obj_set_flex_flow(top_bar_, LV_FLEX_FLOW_ROW);
+ lv_obj_set_flex_align(top_bar_, LV_FLEX_ALIGN_SPACE_BETWEEN, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+ lv_obj_set_scrollbar_mode(top_bar_, LV_SCROLLBAR_MODE_OFF);
+
+ network_label_ = lv_label_create(top_bar_);
+ lv_label_set_text(network_label_, "");
+ lv_obj_set_style_text_font(network_label_, icon_font, 0);
+
+ lv_obj_t* right_icons = lv_obj_create(top_bar_);
+ lv_obj_set_size(right_icons, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
+ lv_obj_set_style_bg_opa(right_icons, LV_OPA_TRANSP, 0);
+ lv_obj_set_style_border_width(right_icons, 0, 0);
+ lv_obj_set_style_pad_all(right_icons, 0, 0);
+ lv_obj_set_flex_flow(right_icons, LV_FLEX_FLOW_ROW);
+ lv_obj_set_flex_align(right_icons, LV_FLEX_ALIGN_END, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER);
+
+ mute_label_ = lv_label_create(right_icons);
+ lv_label_set_text(mute_label_, "");
+ lv_obj_set_style_text_font(mute_label_, icon_font, 0);
+
+ battery_label_ = lv_label_create(right_icons);
+ lv_label_set_text(battery_label_, "");
+ lv_obj_set_style_text_font(battery_label_, icon_font, 0);
+
+ /* Layer 2: Status bar - for center text labels */
+ status_bar_ = lv_obj_create(screen);
+ lv_obj_set_size(status_bar_, LV_HOR_RES, 16);
+ lv_obj_set_style_radius(status_bar_, 0, 0);
+ lv_obj_set_style_bg_opa(status_bar_, LV_OPA_TRANSP, 0); // Transparent background
+ lv_obj_set_style_border_width(status_bar_, 0, 0);
+ lv_obj_set_style_pad_all(status_bar_, 0, 0);
+ lv_obj_set_scrollbar_mode(status_bar_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_style_layout(status_bar_, LV_LAYOUT_NONE, 0); // Use absolute positioning
+ lv_obj_align(status_bar_, LV_ALIGN_TOP_MID, 0, 0); // Overlap with top_bar_
+
+ notification_label_ = lv_label_create(status_bar_);
+ lv_obj_set_width(notification_label_, LV_HOR_RES);
+ lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_label_set_text(notification_label_, "");
+ lv_obj_align(notification_label_, LV_ALIGN_CENTER, 0, 0);
+ lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
+
+ status_label_ = lv_label_create(status_bar_);
+ lv_obj_set_width(status_label_, LV_HOR_RES);
+ lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0);
+ lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
+ lv_obj_align(status_label_, LV_ALIGN_CENTER, 0, 0);
+
+ /* Content */
+ content_ = lv_obj_create(container_);
+ lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_style_radius(content_, 0, 0);
+ lv_obj_set_style_pad_all(content_, 0, 0);
+ lv_obj_set_width(content_, LV_HOR_RES);
+ lv_obj_set_flex_grow(content_, 1);
+ lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW);
+ lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0);
+
+ content_left_ = lv_obj_create(content_);
+ lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT);
+ lv_obj_set_style_pad_all(content_left_, 0, 0);
+ lv_obj_set_style_border_width(content_left_, 0, 0);
+
+ emotion_label_ = lv_label_create(content_left_);
+ lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
+ lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
+ lv_obj_center(emotion_label_);
+ lv_obj_set_style_pad_top(emotion_label_, 8, 0);
+
+ content_right_ = lv_obj_create(content_);
+ lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT);
+ lv_obj_set_style_pad_all(content_right_, 0, 0);
+ lv_obj_set_style_border_width(content_right_, 0, 0);
+ lv_obj_set_flex_grow(content_right_, 1);
+ lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN);
+
+ chat_message_label_ = lv_label_create(content_right_);
+ lv_label_set_text(chat_message_label_, "");
+ lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0);
+ lv_obj_set_width(chat_message_label_, width_ - 32);
+ lv_obj_set_style_pad_top(chat_message_label_, 14, 0);
+
+ // Start scrolling subtitle after a delay
+ static lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_delay(&a, 1000);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+ lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
+ lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
+
+ low_battery_popup_ = lv_obj_create(screen);
+ lv_obj_set_scrollbar_mode(low_battery_popup_, LV_SCROLLBAR_MODE_OFF);
+ lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, text_font->line_height * 2);
+ lv_obj_align(low_battery_popup_, LV_ALIGN_BOTTOM_MID, 0, 0);
+ lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0);
+ lv_obj_set_style_radius(low_battery_popup_, 10, 0);
+ low_battery_label_ = lv_label_create(low_battery_popup_);
+ lv_label_set_text(low_battery_label_, Lang::Strings::BATTERY_NEED_CHARGE);
+ lv_obj_set_style_text_color(low_battery_label_, lv_color_white(), 0);
+ lv_obj_center(low_battery_label_);
+ lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN);
+}
+
+void OledDisplay::SetupUI_128x32() {
+ DisplayLockGuard lock(this);
+
+ auto lvgl_theme = static_cast<LvglTheme*>(current_theme_);
+ auto text_font = lvgl_theme->text_font()->font();
+ auto icon_font = lvgl_theme->icon_font()->font();
+ auto large_icon_font = lvgl_theme->large_icon_font()->font();
+
+ auto screen = lv_screen_active();
+ lv_obj_set_style_text_font(screen, text_font, 0);
+
+ /* Container */
+ container_ = lv_obj_create(screen);
+ lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES);
+ lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW);
+ lv_obj_set_style_pad_all(container_, 0, 0);
+ lv_obj_set_style_border_width(container_, 0, 0);
+ lv_obj_set_style_pad_column(container_, 0, 0);
+
+ /* Emotion label on the left side */
+ content_ = lv_obj_create(container_);
+ lv_obj_set_size(content_, 32, 32);
+ lv_obj_set_style_pad_all(content_, 0, 0);
+ lv_obj_set_style_border_width(content_, 0, 0);
+ lv_obj_set_style_radius(content_, 0, 0);
+
+ emotion_label_ = lv_label_create(content_);
+ lv_obj_set_style_text_font(emotion_label_, large_icon_font, 0);
+ lv_label_set_text(emotion_label_, FONT_AWESOME_MICROCHIP_AI);
+ lv_obj_center(emotion_label_);
+
+ /* Right side */
+ side_bar_ = lv_obj_create(container_);
+ lv_obj_set_size(side_bar_, width_ - 32, 32);
+ lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN);
+ lv_obj_set_style_pad_all(side_bar_, 0, 0);
+ lv_obj_set_style_border_width(side_bar_, 0, 0);
+ lv_obj_set_style_radius(side_bar_, 0, 0);
+ lv_obj_set_style_pad_row(side_bar_, 0, 0);
+
+ /* Status bar */
+ status_bar_ = lv_obj_create(side_bar_);
+ lv_obj_set_size(status_bar_, width_ - 32, 16);
+ lv_obj_set_style_radius(status_bar_, 0, 0);
+ lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW);
+ lv_obj_set_style_pad_all(status_bar_, 0, 0);
+ lv_obj_set_style_border_width(status_bar_, 0, 0);
+ lv_obj_set_style_pad_column(status_bar_, 0, 0);
+
+ status_label_ = lv_label_create(status_bar_);
+ lv_obj_set_flex_grow(status_label_, 1);
+ lv_obj_set_style_pad_left(status_label_, 2, 0);
+ lv_label_set_text(status_label_, Lang::Strings::INITIALIZING);
+
+ notification_label_ = lv_label_create(status_bar_);
+ lv_obj_set_flex_grow(notification_label_, 1);
+ lv_obj_set_style_pad_left(notification_label_, 2, 0);
+ lv_label_set_text(notification_label_, "");
+ lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN);
+
+ mute_label_ = lv_label_create(status_bar_);
+ lv_label_set_text(mute_label_, "");
+ lv_obj_set_style_text_font(mute_label_, icon_font, 0);
+
+ network_label_ = lv_label_create(status_bar_);
+ lv_label_set_text(network_label_, "");
+ lv_obj_set_style_text_font(network_label_, icon_font, 0);
+
+ battery_label_ = lv_label_create(status_bar_);
+ lv_label_set_text(battery_label_, "");
+ lv_obj_set_style_text_font(battery_label_, icon_font, 0);
+
+ chat_message_label_ = lv_label_create(side_bar_);
+ lv_obj_set_size(chat_message_label_, width_ - 32, LV_SIZE_CONTENT);
+ lv_obj_set_style_pad_left(chat_message_label_, 2, 0);
+ lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR);
+ lv_label_set_text(chat_message_label_, "");
+
+ // Start scrolling subtitle after a delay
+ static lv_anim_t a;
+ lv_anim_init(&a);
+ lv_anim_set_delay(&a, 1000);
+ lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE);
+ lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN);
+ lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN);
+}
+
+void OledDisplay::SetEmotion(const char* emotion) {
+ const char* utf8 = font_awesome_get_utf8(emotion);
+ DisplayLockGuard lock(this);
+ if (emotion_label_ == nullptr) {
+ return;
+ }
+ if (utf8 != nullptr) {
+ lv_label_set_text(emotion_label_, utf8);
+ } else {
+ lv_label_set_text(emotion_label_, FONT_AWESOME_NEUTRAL);
+ }
+}
+
+void OledDisplay::SetTheme(Theme* theme) {
+ DisplayLockGuard lock(this);
+
+ auto lvgl_theme = static_cast<LvglTheme*>(theme);
+ auto text_font = lvgl_theme->text_font()->font();
+
+ auto screen = lv_screen_active();
+ lv_obj_set_style_text_font(screen, text_font, 0);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/display/oled_display.h b/xiaozhi-esp32-2.2.6/main/display/oled_display.h
new file mode 100644
index 0000000..40dd184
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/display/oled_display.h
@@ -0,0 +1,41 @@
+#ifndef OLED_DISPLAY_H
+#define OLED_DISPLAY_H
+
+#include "lvgl_display.h"
+
+#include <esp_lcd_panel_io.h>
+#include <esp_lcd_panel_ops.h>
+
+
+class OledDisplay : public LvglDisplay {
+private:
+ esp_lcd_panel_io_handle_t panel_io_ = nullptr;
+ esp_lcd_panel_handle_t panel_ = nullptr;
+
+ lv_obj_t* top_bar_ = nullptr;
+ lv_obj_t* status_bar_ = nullptr;
+ lv_obj_t* content_ = nullptr;
+ lv_obj_t* content_left_ = nullptr;
+ lv_obj_t* content_right_ = nullptr;
+ lv_obj_t* container_ = nullptr;
+ lv_obj_t* side_bar_ = nullptr;
+ lv_obj_t *emotion_label_ = nullptr;
+ lv_obj_t* chat_message_label_ = nullptr;
+
+ virtual bool Lock(int timeout_ms = 0) override;
+ virtual void Unlock() override;
+
+ void SetupUI_128x64();
+ void SetupUI_128x32();
+
+public:
+ OledDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, int width, int height, bool mirror_x, bool mirror_y);
+ ~OledDisplay();
+
+ virtual void SetupUI() override;
+ virtual void SetChatMessage(const char* role, const char* content) override;
+ virtual void SetEmotion(const char* emotion) override;
+ virtual void SetTheme(Theme* theme) override;
+};
+
+#endif // OLED_DISPLAY_H
diff --git a/xiaozhi-esp32-2.2.6/main/idf_component.yml b/xiaozhi-esp32-2.2.6/main/idf_component.yml
new file mode 100644
index 0000000..9fbbb35
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/idf_component.yml
@@ -0,0 +1,136 @@
+## IDF Component Manager Manifest File
+dependencies:
+ waveshare/esp_lcd_sh8601: 1.0.2
+ espressif/esp_lcd_co5300: ^2.0.3
+ espressif/esp_lcd_ili9341: ==1.2.0
+ espressif/esp_lcd_gc9a01: ==2.0.1
+ espressif/esp_lcd_st77916: ^1.0.1
+ espressif/esp_lcd_axs15231b: ^1.0.0
+
+ espressif/esp_lcd_st7701:
+ version: ^1.1.4
+ rules:
+ - if: target in [esp32s3, esp32p4]
+ espressif/esp_lcd_st7796:
+ version: 1.3.5
+ rules:
+ - if: target in [esp32, esp32s2, esp32s3, esp32p4]
+ espressif/esp_lcd_spd2010: ==1.0.2
+ espressif/esp_io_expander_tca9554: ==2.0.0
+ waveshare/custom_io_expander_ch32v003: ^1.0.0
+ espressif/esp_lcd_panel_io_additions: ^1.0.1
+ 78/esp_lcd_nv3023: ~1.0.0
+ 78/esp-wifi-connect: ~3.1.2
+ espressif/esp_audio_effects: ~1.2.1
+ espressif/esp_audio_codec: ~2.4.1
+ 78/esp-ml307: ~3.6.5
+ 78/uart-eth-modem:
+ version: ~0.3.4
+ rules:
+ - if: target not in [esp32]
+ 78/xiaozhi-fonts: ~1.6.0
+ espressif/led_strip: ~3.0.2
+ espressif/esp_codec_dev: ~1.5.6
+ espressif/esp-sr: ~2.3.0
+ espressif/button: ~4.1.5
+ espressif/knob: ^1.0.0
+ espressif/esp32-camera:
+ version: ^2.1.6
+ rules:
+ - if: target in [esp32s3]
+ espressif/esp_video:
+ version: ==1.3.1 # for compatibility. update version may need to modify this project code.
+ rules:
+ - if: target in [esp32p4, esp32s3]
+
+ espressif/esp_image_effects:
+ version: ^1.0.1
+ rules:
+ - if: target not in [esp32]
+ espressif/esp_lcd_touch_ft5x06: ~1
+ espressif/esp_lcd_touch_gt911: ^1
+ espressif/esp_lcd_touch_gt1151: ^1
+ waveshare/esp_lcd_touch_cst9217: ^1.0.3
+ espressif/esp_lcd_touch_cst816s: ^1
+ lvgl/lvgl: ~9.5.0
+ esp_lvgl_port: ~2.7.2
+ espressif/esp_io_expander_tca95xx_16bit: ^2.0.0
+ espressif2022/image_player: ^1.1.1
+ espressif2022/esp_emote_expression: ^0.1.0
+ espressif/esp_mmap_assets: ^1.3.2
+ txp666/otto-emoji-gif-component:
+ version: ^1.1.1
+ rules:
+ - if: target in [esp32s3]
+ espressif/adc_battery_estimation: ^0.2.1
+ espressif/esp_new_jpeg: ^0.6.1
+
+ # SenseCAP Watcher Board
+ wvirgil123/sscma_client:
+ version: 1.0.2
+ rules:
+ - if: target in [esp32s3]
+
+ tny-robotics/sh1106-esp-idf: ^1.0.0
+ espressif/esp_lcd_jd9365:
+ version: '*'
+ rules:
+ - if: target in [esp32p4]
+ waveshare/esp_lcd_st7703:
+ version: '*'
+ rules:
+ - if: target in [esp32p4]
+ espressif/esp32_p4_function_ev_board:
+ version: ^5.0.3
+ rules:
+ - if: target in [esp32p4]
+ espressif/esp_lcd_ili9881c:
+ version: ^1.0.1
+ rules:
+ - if: target in [esp32p4]
+ espressif/esp_lcd_ek79007:
+ version: ^1.0.3
+ rules:
+ - if: target in [esp32p4]
+ espressif/esp_hosted:
+ version: 2.0.17
+ rules:
+ - if: target in [esp32h2, esp32p4]
+ espressif/esp_wifi_remote:
+ version: '*'
+ rules:
+ - if: target in [esp32p4]
+ espfriends/servo_dog_ctrl:
+ version: ^0.1.8
+ rules:
+ - if: target in [esp32c3]
+
+ llgok/cpp_bus_driver:
+ version: 1.1.0
+ rules:
+ - if: target in [esp32p4]
+
+ espressif/bmi270_sensor:
+ version: ^0.1.0
+ rules:
+ - if: target in [esp32s3, esp32c5]
+
+ espressif/touch_slider_sensor:
+ version: ^0.2.0~1
+ rules:
+ - if: target in [esp32s3]
+
+ espressif/touch_button_sensor:
+ version: ^0.2.2~1
+ rules:
+ - if: target in [esp32s3]
+
+ espressif/esp_lcd_touch_st7123: ^1.0.0
+ espressif/iot_usbh_rndis:
+ version: ^0.3.1
+ rules:
+ - if: target in [esp32s3, esp32p4]
+
+ ## Required IDF version
+ idf:
+ version: '>=5.5.2'
diff --git a/xiaozhi-esp32-2.2.6/main/led/circular_strip.cc b/xiaozhi-esp32-2.2.6/main/led/circular_strip.cc
new file mode 100644
index 0000000..b4111ba
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/circular_strip.cc
@@ -0,0 +1,245 @@
+#include "circular_strip.h"
+#include "application.h"
+#include <esp_log.h>
+#include <algorithm>
+
+#define TAG "CircularStrip"
+
+#define BLINK_INFINITE -1
+
+CircularStrip::CircularStrip(gpio_num_t gpio, uint16_t max_leds) : max_leds_(max_leds) {
+ // If the gpio is not connected, you should use NoLed class
+ assert(gpio != GPIO_NUM_NC);
+
+ colors_.resize(max_leds_);
+
+ led_strip_config_t strip_config = {};
+ strip_config.strip_gpio_num = gpio;
+ strip_config.max_leds = max_leds_;
+ strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
+ strip_config.led_model = LED_MODEL_WS2812;
+
+ led_strip_rmt_config_t rmt_config = {};
+ rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz
+
+ ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_));
+ led_strip_clear(led_strip_);
+
+ esp_timer_create_args_t strip_timer_args = {
+ .callback = [](void *arg) {
+ auto strip = static_cast<CircularStrip*>(arg);
+ std::lock_guard<std::mutex> lock(strip->mutex_);
+ if (strip->strip_callback_ != nullptr) {
+ strip->strip_callback_();
+ }
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "strip_timer",
+ .skip_unhandled_events = false,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&strip_timer_args, &strip_timer_));
+}
+
+CircularStrip::~CircularStrip() {
+ esp_timer_stop(strip_timer_);
+ if (led_strip_ != nullptr) {
+ led_strip_del(led_strip_);
+ }
+}
+
+
+void CircularStrip::SetAllColor(StripColor color) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(strip_timer_);
+ for (int i = 0; i < max_leds_; i++) {
+ colors_[i] = color;
+ led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue);
+ }
+ led_strip_refresh(led_strip_);
+}
+
+void CircularStrip::SetSingleColor(uint8_t index, StripColor color) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(strip_timer_);
+ colors_[index] = color;
+ led_strip_set_pixel(led_strip_, index, color.red, color.green, color.blue);
+ led_strip_refresh(led_strip_);
+}
+
+void CircularStrip::SetMultiColors(const std::vector<StripColor>& colors) {
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(strip_timer_);
+ int count = std::min(max_leds_, static_cast<int>(colors.size()));
+ for (int i = 0; i < count; i++) {
+ colors_[i] = colors[i];
+ led_strip_set_pixel(led_strip_, i, colors[i].red, colors[i].green, colors[i].blue);
+ }
+ led_strip_refresh(led_strip_);
+}
+
+void CircularStrip::Blink(StripColor color, int interval_ms) {
+ for (int i = 0; i < max_leds_; i++) {
+ colors_[i] = color;
+ }
+ StartStripTask(interval_ms, [this]() {
+ static bool on = true;
+ if (on) {
+ for (int i = 0; i < max_leds_; i++) {
+ led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue);
+ }
+ led_strip_refresh(led_strip_);
+ } else {
+ led_strip_clear(led_strip_);
+ }
+ on = !on;
+ });
+}
+
+void CircularStrip::FadeOut(int interval_ms) {
+ StartStripTask(interval_ms, [this]() {
+ bool all_off = true;
+ for (int i = 0; i < max_leds_; i++) {
+ colors_[i].red /= 2;
+ colors_[i].green /= 2;
+ colors_[i].blue /= 2;
+ if (colors_[i].red != 0 || colors_[i].green != 0 || colors_[i].blue != 0) {
+ all_off = false;
+ }
+ led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue);
+ }
+ if (all_off) {
+ led_strip_clear(led_strip_);
+ esp_timer_stop(strip_timer_);
+ } else {
+ led_strip_refresh(led_strip_);
+ }
+ });
+}
+
+void CircularStrip::Breathe(StripColor low, StripColor high, int interval_ms) {
+ StartStripTask(interval_ms, [this, low, high]() {
+ static bool increase = true;
+ static StripColor color = low;
+ if (increase) {
+ if (color.red < high.red) {
+ color.red++;
+ }
+ if (color.green < high.green) {
+ color.green++;
+ }
+ if (color.blue < high.blue) {
+ color.blue++;
+ }
+ if (color.red == high.red && color.green == high.green && color.blue == high.blue) {
+ increase = false;
+ }
+ } else {
+ if (color.red > low.red) {
+ color.red--;
+ }
+ if (color.green > low.green) {
+ color.green--;
+ }
+ if (color.blue > low.blue) {
+ color.blue--;
+ }
+ if (color.red == low.red && color.green == low.green && color.blue == low.blue) {
+ increase = true;
+ }
+ }
+ for (int i = 0; i < max_leds_; i++) {
+ led_strip_set_pixel(led_strip_, i, color.red, color.green, color.blue);
+ }
+ led_strip_refresh(led_strip_);
+ });
+}
+
+void CircularStrip::Scroll(StripColor low, StripColor high, int length, int interval_ms) {
+ for (int i = 0; i < max_leds_; i++) {
+ colors_[i] = low;
+ }
+ StartStripTask(interval_ms, [this, low, high, length]() {
+ static int offset = 0;
+ for (int i = 0; i < max_leds_; i++) {
+ colors_[i] = low;
+ }
+ for (int j = 0; j < length; j++) {
+ int i = (offset + j) % max_leds_;
+ colors_[i] = high;
+ }
+ for (int i = 0; i < max_leds_; i++) {
+ led_strip_set_pixel(led_strip_, i, colors_[i].red, colors_[i].green, colors_[i].blue);
+ }
+ led_strip_refresh(led_strip_);
+ offset = (offset + 1) % max_leds_;
+ });
+}
+
+void CircularStrip::StartStripTask(int interval_ms, std::function<void()> cb) {
+ if (led_strip_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(strip_timer_);
+
+ strip_callback_ = cb;
+ esp_timer_start_periodic(strip_timer_, interval_ms * 1000);
+}
+
+void CircularStrip::SetBrightness(uint8_t default_brightness, uint8_t low_brightness) {
+ default_brightness_ = default_brightness;
+ low_brightness_ = low_brightness;
+ OnStateChanged();
+}
+
+void CircularStrip::OnStateChanged() {
+ auto& app = Application::GetInstance();
+ auto device_state = app.GetDeviceState();
+ switch (device_state) {
+ case kDeviceStateStarting: {
+ StripColor low = { 0, 0, 0 };
+ StripColor high = { low_brightness_, low_brightness_, default_brightness_ };
+ Scroll(low, high, 3, 100);
+ break;
+ }
+ case kDeviceStateWifiConfiguring: {
+ StripColor color = { low_brightness_, low_brightness_, default_brightness_ };
+ Blink(color, 500);
+ break;
+ }
+ case kDeviceStateIdle:
+ FadeOut(50);
+ break;
+ case kDeviceStateConnecting: {
+ StripColor color = { low_brightness_, low_brightness_, default_brightness_ };
+ SetAllColor(color);
+ break;
+ }
+ case kDeviceStateListening:
+ case kDeviceStateAudioTesting: {
+ StripColor color = { default_brightness_, low_brightness_, low_brightness_ };
+ SetAllColor(color);
+ break;
+ }
+ case kDeviceStateSpeaking: {
+ StripColor color = { low_brightness_, default_brightness_, low_brightness_ };
+ SetAllColor(color);
+ break;
+ }
+ case kDeviceStateUpgrading: {
+ StripColor color = { low_brightness_, default_brightness_, low_brightness_ };
+ Blink(color, 100);
+ break;
+ }
+ case kDeviceStateActivating: {
+ StripColor color = { low_brightness_, default_brightness_, low_brightness_ };
+ Blink(color, 500);
+ break;
+ }
+ default:
+ ESP_LOGW(TAG, "Unknown led strip event: %d", device_state);
+ return;
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/led/circular_strip.h b/xiaozhi-esp32-2.2.6/main/led/circular_strip.h
new file mode 100644
index 0000000..8d931aa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/circular_strip.h
@@ -0,0 +1,52 @@
+#ifndef _CIRCULAR_STRIP_H_
+#define _CIRCULAR_STRIP_H_
+
+#include "led.h"
+#include <driver/gpio.h>
+#include <led_strip.h>
+#include <esp_timer.h>
+#include <atomic>
+#include <mutex>
+#include <vector>
+
+#define DEFAULT_BRIGHTNESS 32
+#define LOW_BRIGHTNESS 4
+
+struct StripColor {
+ uint8_t red = 0, green = 0, blue = 0;
+};
+
+class CircularStrip : public Led {
+public:
+ CircularStrip(gpio_num_t gpio, uint16_t max_leds);
+ virtual ~CircularStrip();
+
+ void OnStateChanged() override;
+ void SetBrightness(uint8_t default_brightness, uint8_t low_brightness);
+ void SetAllColor(StripColor color);
+ void SetSingleColor(uint8_t index, StripColor color);
+ void SetMultiColors(const std::vector<StripColor>& colors);
+ void Blink(StripColor color, int interval_ms);
+ void Breathe(StripColor low, StripColor high, int interval_ms);
+ void Scroll(StripColor low, StripColor high, int length, int interval_ms);
+
+private:
+ std::mutex mutex_;
+ TaskHandle_t blink_task_ = nullptr;
+ led_strip_handle_t led_strip_ = nullptr;
+ int max_leds_ = 0;
+ std::vector<StripColor> colors_;
+ int blink_counter_ = 0;
+ int blink_interval_ms_ = 0;
+ esp_timer_handle_t strip_timer_ = nullptr;
+ std::function<void()> strip_callback_ = nullptr;
+
+ uint8_t default_brightness_ = DEFAULT_BRIGHTNESS;
+ uint8_t low_brightness_ = LOW_BRIGHTNESS;
+
+ void StartStripTask(int interval_ms, std::function<void()> cb);
+ void Rainbow(StripColor low, StripColor high, int interval_ms);
+ void FadeOut(int interval_ms);
+};
+
+#endif // _CIRCULAR_STRIP_H_
diff --git a/xiaozhi-esp32-2.2.6/main/led/gpio_led.cc b/xiaozhi-esp32-2.2.6/main/led/gpio_led.cc
new file mode 100644
index 0000000..30eeeb2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/gpio_led.cc
@@ -0,0 +1,263 @@
+#include "gpio_led.h"
+#include "application.h"
+#include "device_state.h"
+#include <esp_log.h>
+
+#define TAG "GpioLed"
+
+#define DEFAULT_BRIGHTNESS 50
+#define HIGH_BRIGHTNESS 100
+#define LOW_BRIGHTNESS 10
+
+#define IDLE_BRIGHTNESS 5
+#define SPEAKING_BRIGHTNESS 75
+#define UPGRADING_BRIGHTNESS 25
+#define ACTIVATING_BRIGHTNESS 35
+
+#define BLINK_INFINITE -1
+
+// GPIO_LED
+#define LEDC_LS_TIMER LEDC_TIMER_1
+#define LEDC_LS_MODE LEDC_LOW_SPEED_MODE
+#define LEDC_LS_CH0_CHANNEL LEDC_CHANNEL_0
+
+#define LEDC_DUTY (8191)
+#define LEDC_FADE_TIME (1000)
+// GPIO_LED
+
+GpioLed::GpioLed(gpio_num_t gpio)
+ : GpioLed(gpio, 0, LEDC_LS_TIMER, LEDC_LS_CH0_CHANNEL) {
+}
+
+GpioLed::GpioLed(gpio_num_t gpio, int output_invert)
+ : GpioLed(gpio, output_invert, LEDC_LS_TIMER, LEDC_LS_CH0_CHANNEL) {
+}
+
+GpioLed::GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, ledc_channel_t channel) {
+ // If the gpio is not connected, you should use NoLed class
+ assert(gpio != GPIO_NUM_NC);
+
+ /*
+ * Prepare and set configuration of timers
+ * that will be used by LED Controller
+ */
+ ledc_timer_config_t ledc_timer = {};
+ ledc_timer.duty_resolution = LEDC_TIMER_13_BIT; // resolution of PWM duty
+ ledc_timer.freq_hz = 4000; // frequency of PWM signal
+ ledc_timer.speed_mode = LEDC_LS_MODE; // timer mode
+ ledc_timer.timer_num = timer_num; // timer index
+ ledc_timer.clk_cfg = LEDC_AUTO_CLK; // Auto select the source clock
+
+ ESP_ERROR_CHECK(ledc_timer_config(&ledc_timer));
+
+ ledc_channel_.channel = channel,
+ ledc_channel_.duty = 0,
+ ledc_channel_.gpio_num = gpio,
+ ledc_channel_.speed_mode = LEDC_LS_MODE,
+ ledc_channel_.hpoint = 0,
+ ledc_channel_.timer_sel = timer_num,
+ ledc_channel_.flags.output_invert = output_invert & 0x01,
+
+ // Set LED Controller with previously prepared configuration
+ ledc_channel_config(&ledc_channel_);
+
+ // Initialize fade service.
+ ledc_fade_func_install(0);
+
+ // When the callback registered by ledc_cb_degister is called, run led ->OnFadeEnd()
+ ledc_cbs_t ledc_callbacks = {
+ .fade_cb = FadeCallback
+ };
+ ledc_cb_register(ledc_channel_.speed_mode, ledc_channel_.channel, &ledc_callbacks, this);
+
+ esp_timer_create_args_t blink_timer_args = {
+ .callback = [](void *arg) {
+ auto led = static_cast<GpioLed*>(arg);
+ led->OnBlinkTimer();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "Blink Timer",
+ .skip_unhandled_events = false,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_));
+
+ xTaskCreate(EventTask, "LedEvent", 2048, this,
+ tskIDLE_PRIORITY + 2, &event_task_handle_);
+
+ ledc_initialized_ = true;
+}
+
+GpioLed::~GpioLed() {
+ esp_timer_stop(blink_timer_);
+ if (ledc_initialized_) {
+ ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel);
+ ledc_fade_func_uninstall();
+ }
+}
+
+
+void GpioLed::SetBrightness(uint8_t brightness) {
+ if (brightness == 100) {
+ duty_ = LEDC_DUTY;
+ } else {
+ duty_ = brightness * LEDC_DUTY / 100;
+ }
+}
+
+void GpioLed::TurnOn() {
+ if (!ledc_initialized_) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+ ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel);
+ ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, duty_);
+ ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel);
+}
+
+void GpioLed::TurnOff() {
+ if (!ledc_initialized_) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+ ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel);
+ ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, 0);
+ ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel);
+}
+
+void GpioLed::BlinkOnce() {
+ Blink(1, 100);
+}
+
+void GpioLed::Blink(int times, int interval_ms) {
+ StartBlinkTask(times, interval_ms);
+}
+
+void GpioLed::StartContinuousBlink(int interval_ms) {
+ StartBlinkTask(BLINK_INFINITE, interval_ms);
+}
+
+void GpioLed::StartBlinkTask(int times, int interval_ms) {
+ if (!ledc_initialized_) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+ ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel);
+
+ blink_counter_ = times * 2;
+ blink_interval_ms_ = interval_ms;
+ esp_timer_start_periodic(blink_timer_, interval_ms * 1000);
+}
+
+void GpioLed::OnBlinkTimer() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ blink_counter_--;
+ if (blink_counter_ & 1) {
+ ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, duty_);
+ } else {
+ ledc_set_duty(ledc_channel_.speed_mode, ledc_channel_.channel, 0);
+
+ if (blink_counter_ == 0) {
+ esp_timer_stop(blink_timer_);
+ }
+ }
+ ledc_update_duty(ledc_channel_.speed_mode, ledc_channel_.channel);
+}
+
+void GpioLed::StartFadeTask() {
+ if (!ledc_initialized_) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+ ledc_fade_stop(ledc_channel_.speed_mode, ledc_channel_.channel);
+ fade_up_ = true;
+ ledc_set_fade_with_time(ledc_channel_.speed_mode,
+ ledc_channel_.channel, LEDC_DUTY, LEDC_FADE_TIME);
+ ledc_fade_start(ledc_channel_.speed_mode,
+ ledc_channel_.channel, LEDC_FADE_NO_WAIT);
+}
+
+void GpioLed::OnFadeEnd() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ fade_up_ = !fade_up_;
+ ledc_set_fade_with_time(ledc_channel_.speed_mode,
+ ledc_channel_.channel, fade_up_ ? LEDC_DUTY : 0, LEDC_FADE_TIME);
+ ledc_fade_start(ledc_channel_.speed_mode,
+ ledc_channel_.channel, LEDC_FADE_NO_WAIT);
+}
+
+bool IRAM_ATTR GpioLed::FadeCallback(const ledc_cb_param_t *param, void *user_arg) {
+ if (param->event == LEDC_FADE_END_EVT) {
+ auto led = static_cast<GpioLed*>(user_arg);
+ BaseType_t xHigherPriorityTaskWoken = pdFALSE;
+ xTaskNotifyFromISR(led->event_task_handle_, 0x01, eSetValueWithOverwrite,
+ &xHigherPriorityTaskWoken);
+ }
+ return true;
+}
+
+void GpioLed::OnStateChanged() {
+ auto& app = Application::GetInstance();
+ auto device_state = app.GetDeviceState();
+ switch (device_state) {
+ case kDeviceStateStarting:
+ SetBrightness(DEFAULT_BRIGHTNESS);
+ StartContinuousBlink(100);
+ break;
+ case kDeviceStateWifiConfiguring:
+ SetBrightness(DEFAULT_BRIGHTNESS);
+ StartContinuousBlink(500);
+ break;
+ case kDeviceStateIdle:
+ SetBrightness(IDLE_BRIGHTNESS);
+ TurnOn();
+ // TurnOff();
+ break;
+ case kDeviceStateConnecting:
+ SetBrightness(DEFAULT_BRIGHTNESS);
+ TurnOn();
+ break;
+ case kDeviceStateListening:
+ case kDeviceStateAudioTesting:
+ if (app.IsVoiceDetected()) {
+ SetBrightness(HIGH_BRIGHTNESS);
+ } else {
+ SetBrightness(LOW_BRIGHTNESS);
+ }
+ // TurnOn();
+ StartFadeTask();
+ break;
+ case kDeviceStateSpeaking:
+ SetBrightness(SPEAKING_BRIGHTNESS);
+ TurnOn();
+ break;
+ case kDeviceStateUpgrading:
+ SetBrightness(UPGRADING_BRIGHTNESS);
+ StartContinuousBlink(100);
+ break;
+ case kDeviceStateActivating:
+ SetBrightness(ACTIVATING_BRIGHTNESS);
+ StartContinuousBlink(500);
+ break;
+ default:
+ ESP_LOGE(TAG, "Unknown gpio led event: %d", device_state);
+ return;
+ }
+}
+
+void GpioLed::EventTask(void* arg) {
+ GpioLed* led = static_cast<GpioLed*>(arg);
+
+ while (1) {
+ ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
+ led->OnFadeEnd();
+ }
+}
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/main/led/gpio_led.h b/xiaozhi-esp32-2.2.6/main/led/gpio_led.h
new file mode 100644
index 0000000..390d23c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/gpio_led.h
@@ -0,0 +1,49 @@
+#ifndef _GPIO_LED_H_
+#define _GPIO_LED_H_
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include "led.h"
+#include <driver/gpio.h>
+#include <driver/ledc.h>
+#include <esp_timer.h>
+#include <atomic>
+#include <mutex>
+
+class GpioLed : public Led {
+ public:
+ GpioLed(gpio_num_t gpio);
+ GpioLed(gpio_num_t gpio, int output_invert);
+ GpioLed(gpio_num_t gpio, int output_invert, ledc_timer_t timer_num, ledc_channel_t channel);
+ virtual ~GpioLed();
+
+ void OnStateChanged() override;
+ void TurnOn();
+ void TurnOff();
+ void SetBrightness(uint8_t brightness);
+
+ private:
+ std::mutex mutex_;
+ TaskHandle_t blink_task_ = nullptr;
+ ledc_channel_config_t ledc_channel_ = {0};
+ bool ledc_initialized_ = false;
+ uint32_t duty_ = 0;
+ int blink_counter_ = 0;
+ int blink_interval_ms_ = 0;
+ esp_timer_handle_t blink_timer_ = nullptr;
+ bool fade_up_ = true;
+ TaskHandle_t event_task_handle_;
+
+ static void EventTask(void* arg);
+ void StartBlinkTask(int times, int interval_ms);
+ void OnBlinkTimer();
+
+ void BlinkOnce();
+ void Blink(int times, int interval_ms);
+ void StartContinuousBlink(int interval_ms);
+ void StartFadeTask();
+ void OnFadeEnd();
+ static bool IRAM_ATTR FadeCallback(const ledc_cb_param_t *param, void *user_arg);
+};
+
+#endif // _GPIO_LED_H_
diff --git a/xiaozhi-esp32-2.2.6/main/led/led.h b/xiaozhi-esp32-2.2.6/main/led/led.h
new file mode 100644
index 0000000..251fd6a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/led.h
@@ -0,0 +1,17 @@
+#ifndef _LED_H_
+#define _LED_H_
+
+class Led {
+public:
+ virtual ~Led() = default;
+ // Set the led state based on the device state
+ virtual void OnStateChanged() = 0;
+};
+
+
+class NoLed : public Led {
+public:
+ virtual void OnStateChanged() override {}
+};
+
+#endif // _LED_H_
diff --git a/xiaozhi-esp32-2.2.6/main/led/single_led.cc b/xiaozhi-esp32-2.2.6/main/led/single_led.cc
new file mode 100644
index 0000000..d107619
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/single_led.cc
@@ -0,0 +1,167 @@
+#include "single_led.h"
+#include "application.h"
+#include <esp_log.h>
+
+#define TAG "SingleLed"
+
+#define DEFAULT_BRIGHTNESS 4
+#define HIGH_BRIGHTNESS 16
+#define LOW_BRIGHTNESS 2
+
+#define BLINK_INFINITE -1
+
+
+SingleLed::SingleLed(gpio_num_t gpio) {
+ if (gpio == GPIO_NUM_NC) {
+ ESP_LOGW(TAG, "SingleLed initialized with GPIO_NUM_NC, LED will not function");
+ return;
+ }
+
+ led_strip_config_t strip_config = {};
+ strip_config.strip_gpio_num = gpio;
+ strip_config.max_leds = 1;
+ strip_config.color_component_format = LED_STRIP_COLOR_COMPONENT_FMT_GRB;
+ strip_config.led_model = LED_MODEL_WS2812;
+
+ led_strip_rmt_config_t rmt_config = {};
+ rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz
+
+ ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_));
+ led_strip_clear(led_strip_);
+
+ esp_timer_create_args_t blink_timer_args = {
+ .callback = [](void *arg) {
+ auto led = static_cast<SingleLed*>(arg);
+ led->OnBlinkTimer();
+ },
+ .arg = this,
+ .dispatch_method = ESP_TIMER_TASK,
+ .name = "blink_timer",
+ .skip_unhandled_events = false,
+ };
+ ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_));
+}
+
+SingleLed::~SingleLed() {
+ if (blink_timer_ != nullptr) {
+ esp_timer_stop(blink_timer_);
+ }
+ if (led_strip_ != nullptr) {
+ led_strip_del(led_strip_);
+ }
+}
+
+
+void SingleLed::SetColor(uint8_t r, uint8_t g, uint8_t b) {
+ r_ = r;
+ g_ = g;
+ b_ = b;
+}
+
+void SingleLed::TurnOn() {
+ if (led_strip_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+ led_strip_set_pixel(led_strip_, 0, r_, g_, b_);
+ led_strip_refresh(led_strip_);
+}
+
+void SingleLed::TurnOff() {
+ if (led_strip_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+ led_strip_clear(led_strip_);
+}
+
+void SingleLed::BlinkOnce() {
+ Blink(1, 100);
+}
+
+void SingleLed::Blink(int times, int interval_ms) {
+ StartBlinkTask(times, interval_ms);
+}
+
+void SingleLed::StartContinuousBlink(int interval_ms) {
+ StartBlinkTask(BLINK_INFINITE, interval_ms);
+}
+
+void SingleLed::StartBlinkTask(int times, int interval_ms) {
+ if (led_strip_ == nullptr) {
+ return;
+ }
+
+ std::lock_guard<std::mutex> lock(mutex_);
+ esp_timer_stop(blink_timer_);
+
+ blink_counter_ = times * 2;
+ blink_interval_ms_ = interval_ms;
+ esp_timer_start_periodic(blink_timer_, interval_ms * 1000);
+}
+
+void SingleLed::OnBlinkTimer() {
+ std::lock_guard<std::mutex> lock(mutex_);
+ blink_counter_--;
+ if (blink_counter_ & 1) {
+ led_strip_set_pixel(led_strip_, 0, r_, g_, b_);
+ led_strip_refresh(led_strip_);
+ } else {
+ led_strip_clear(led_strip_);
+
+ if (blink_counter_ == 0) {
+ esp_timer_stop(blink_timer_);
+ }
+ }
+}
+
+
+void SingleLed::OnStateChanged() {
+ auto& app = Application::GetInstance();
+ auto device_state = app.GetDeviceState();
+ switch (device_state) {
+ case kDeviceStateStarting:
+ SetColor(0, 0, DEFAULT_BRIGHTNESS);
+ StartContinuousBlink(100);
+ break;
+ case kDeviceStateWifiConfiguring:
+ SetColor(0, 0, DEFAULT_BRIGHTNESS);
+ StartContinuousBlink(500);
+ break;
+ case kDeviceStateIdle:
+ TurnOff();
+ break;
+ case kDeviceStateConnecting:
+ SetColor(0, 0, DEFAULT_BRIGHTNESS);
+ TurnOn();
+ break;
+ case kDeviceStateListening:
+ case kDeviceStateAudioTesting:
+ if (app.IsVoiceDetected()) {
+ SetColor(HIGH_BRIGHTNESS, 0, 0);
+ } else {
+ SetColor(LOW_BRIGHTNESS, 0, 0);
+ }
+ TurnOn();
+ break;
+ case kDeviceStateSpeaking:
+ SetColor(0, DEFAULT_BRIGHTNESS, 0);
+ TurnOn();
+ break;
+ case kDeviceStateUpgrading:
+ SetColor(0, DEFAULT_BRIGHTNESS, 0);
+ StartContinuousBlink(100);
+ break;
+ case kDeviceStateActivating:
+ SetColor(0, DEFAULT_BRIGHTNESS, 0);
+ StartContinuousBlink(500);
+ break;
+ default:
+ ESP_LOGW(TAG, "Unknown led strip event: %d", device_state);
+ return;
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/led/single_led.h b/xiaozhi-esp32-2.2.6/main/led/single_led.h
new file mode 100644
index 0000000..b949f74
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/led/single_led.h
@@ -0,0 +1,38 @@
+#ifndef _SINGLE_LED_H_
+#define _SINGLE_LED_H_
+
+#include "led.h"
+#include <driver/gpio.h>
+#include <led_strip.h>
+#include <esp_timer.h>
+#include <atomic>
+#include <mutex>
+
+class SingleLed : public Led {
+public:
+ SingleLed(gpio_num_t gpio);
+ virtual ~SingleLed();
+
+ void OnStateChanged() override;
+
+private:
+ std::mutex mutex_;
+ TaskHandle_t blink_task_ = nullptr;
+ led_strip_handle_t led_strip_ = nullptr;
+ uint8_t r_ = 0, g_ = 0, b_ = 0;
+ int blink_counter_ = 0;
+ int blink_interval_ms_ = 0;
+ esp_timer_handle_t blink_timer_ = nullptr;
+
+ void StartBlinkTask(int times, int interval_ms);
+ void OnBlinkTimer();
+
+ void BlinkOnce();
+ void Blink(int times, int interval_ms);
+ void StartContinuousBlink(int interval_ms);
+ void TurnOn();
+ void TurnOff();
+ void SetColor(uint8_t r, uint8_t g, uint8_t b);
+};
+
+#endif // _SINGLE_LED_H_
diff --git a/xiaozhi-esp32-2.2.6/main/main.cc b/xiaozhi-esp32-2.2.6/main/main.cc
new file mode 100755
index 0000000..f7e8bc3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/main.cc
@@ -0,0 +1,29 @@
+#include <esp_log.h>
+#include <esp_err.h>
+#include <nvs.h>
+#include <nvs_flash.h>
+#include <driver/gpio.h>
+#include <esp_event.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+
+#include "application.h"
+
+#define TAG "main"
+
+extern "C" void app_main(void)
+{
+ // Initialize NVS flash for WiFi configuration
+ esp_err_t ret = nvs_flash_init();
+ if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) {
+ ESP_LOGW(TAG, "Erasing NVS flash to fix corruption");
+ ESP_ERROR_CHECK(nvs_flash_erase());
+ ret = nvs_flash_init();
+ }
+ ESP_ERROR_CHECK(ret);
+
+ // Initialize and run the application
+ auto& app = Application::GetInstance();
+ app.Initialize();
+ app.Run(); // This function runs the main event loop and never returns
+}
diff --git a/xiaozhi-esp32-2.2.6/main/mcp_server.cc b/xiaozhi-esp32-2.2.6/main/mcp_server.cc
new file mode 100644
index 0000000..7cd7ba0
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/mcp_server.cc
@@ -0,0 +1,563 @@
+/*
+ * MCP Server Implementation
+ * Reference: https://modelcontextprotocol.io/specification/2024-11-05
+ */
+
+#include "mcp_server.h"
+#include <esp_log.h>
+#include <esp_app_desc.h>
+#include <algorithm>
+#include <cstring>
+#include <esp_pthread.h>
+
+#include "application.h"
+#include "display.h"
+#include "oled_display.h"
+#include "board.h"
+#include "settings.h"
+#include "lvgl_theme.h"
+#include "lvgl_display.h"
+
+#define TAG "MCP"
+
+McpServer::McpServer() {
+}
+
+McpServer::~McpServer() {
+ for (auto tool : tools_) {
+ delete tool;
+ }
+ tools_.clear();
+}
+
+void McpServer::AddCommonTools() {
+ // *Important* To speed up the response time, we add the common tools to the beginning of
+ // the tools list to utilize the prompt cache.
+ // **閲嶈** 涓轰簡鎻愬崌鍝嶅簲閫熷害锛屾垜浠妸甯哥敤鐨勫伐鍏锋斁鍦ㄥ墠闈紝鍒╃敤 prompt cache 鐨勭壒鎬с��
+
+ // Backup the original tools list and restore it after adding the common tools.
+ auto original_tools = std::move(tools_);
+ auto& board = Board::GetInstance();
+
+ // Do not add custom tools here.
+ // Custom tools must be added in the board's InitializeTools function.
+
+ AddTool("self.get_device_status",
+ "Provides the real-time information of the device, including the current status of the audio speaker, screen, battery, network, etc.\n"
+ "Use this tool for: \n"
+ "1. Answering questions about current condition (e.g. what is the current volume of the audio speaker?)\n"
+ "2. As the first step to control the device (e.g. turn up / down the volume of the audio speaker, etc.)",
+ PropertyList(),
+ [&board](const PropertyList& properties) -> ReturnValue {
+ return board.GetDeviceStatusJson();
+ });
+
+ AddTool("self.audio_speaker.set_volume",
+ "Set the volume of the audio speaker. If the current volume is unknown, you must call `self.get_device_status` tool first and then call this tool.",
+ PropertyList({
+ Property("volume", kPropertyTypeInteger, 0, 100)
+ }),
+ [&board](const PropertyList& properties) -> ReturnValue {
+ auto codec = board.GetAudioCodec();
+ codec->SetOutputVolume(properties["volume"].value<int>());
+ return true;
+ });
+
+ auto backlight = board.GetBacklight();
+ if (backlight) {
+ AddTool("self.screen.set_brightness",
+ "Set the brightness of the screen.",
+ PropertyList({
+ Property("brightness", kPropertyTypeInteger, 0, 100)
+ }),
+ [backlight](const PropertyList& properties) -> ReturnValue {
+ uint8_t brightness = static_cast<uint8_t>(properties["brightness"].value<int>());
+ backlight->SetBrightness(brightness, true);
+ return true;
+ });
+ }
+
+#ifdef HAVE_LVGL
+ auto display = board.GetDisplay();
+ if (display && display->GetTheme() != nullptr) {
+ AddTool("self.screen.set_theme",
+ "Set the theme of the screen. The theme can be `light` or `dark`.",
+ PropertyList({
+ Property("theme", kPropertyTypeString)
+ }),
+ [display](const PropertyList& properties) -> ReturnValue {
+ auto theme_name = properties["theme"].value<std::string>();
+ auto& theme_manager = LvglThemeManager::GetInstance();
+ auto theme = theme_manager.GetTheme(theme_name);
+ if (theme != nullptr) {
+ display->SetTheme(theme);
+ return true;
+ }
+ return false;
+ });
+ }
+
+ auto camera = board.GetCamera();
+ if (camera) {
+ AddTool("self.camera.take_photo",
+ "Always remember you have a camera. If the user asks you to see something, use this tool to take a photo and then explain it.\n"
+ "Args:\n"
+ " `question`: The question that you want to ask about the photo.\n"
+ "Return:\n"
+ " A JSON object that provides the photo information.",
+ PropertyList({
+ Property("question", kPropertyTypeString)
+ }),
+ [camera](const PropertyList& properties) -> ReturnValue {
+ // Lower the priority to do the camera capture
+ TaskPriorityReset priority_reset(1);
+
+ if (!camera->Capture()) {
+ throw std::runtime_error("Failed to capture photo");
+ }
+ auto question = properties["question"].value<std::string>();
+ return camera->Explain(question);
+ });
+ }
+#endif
+
+ // Restore the original tools list to the end of the tools list
+ tools_.insert(tools_.end(), original_tools.begin(), original_tools.end());
+}
+
+void McpServer::AddUserOnlyTools() {
+ // System tools
+ AddUserOnlyTool("self.get_system_info",
+ "Get the system information",
+ PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ auto& board = Board::GetInstance();
+ return board.GetSystemInfoJson();
+ });
+
+ AddUserOnlyTool("self.reboot", "Reboot the system",
+ PropertyList(),
+ [this](const PropertyList& properties) -> ReturnValue {
+ auto& app = Application::GetInstance();
+ app.Schedule([&app]() {
+ ESP_LOGW(TAG, "User requested reboot");
+ vTaskDelay(pdMS_TO_TICKS(1000));
+
+ app.Reboot();
+ });
+ return true;
+ });
+
+ // Firmware upgrade
+ AddUserOnlyTool("self.upgrade_firmware", "Upgrade firmware from a specific URL. This will download and install the firmware, then reboot the device.",
+ PropertyList({
+ Property("url", kPropertyTypeString, "The URL of the firmware binary file to download and install")
+ }),
+ [this](const PropertyList& properties) -> ReturnValue {
+ auto url = properties["url"].value<std::string>();
+ ESP_LOGI(TAG, "User requested firmware upgrade from URL: %s", url.c_str());
+
+ auto& app = Application::GetInstance();
+ app.Schedule([url, &app]() {
+ bool success = app.UpgradeFirmware(url);
+ if (!success) {
+ ESP_LOGE(TAG, "Firmware upgrade failed");
+ }
+ });
+
+ return true;
+ });
+
+ // Display control
+#ifdef HAVE_LVGL
+ auto display = dynamic_cast<LvglDisplay*>(Board::GetInstance().GetDisplay());
+ if (display) {
+ AddUserOnlyTool("self.screen.get_info", "Information about the screen, including width, height, etc.",
+ PropertyList(),
+ [display](const PropertyList& properties) -> ReturnValue {
+ cJSON *json = cJSON_CreateObject();
+ cJSON_AddNumberToObject(json, "width", display->width());
+ cJSON_AddNumberToObject(json, "height", display->height());
+ if (dynamic_cast<OledDisplay*>(display)) {
+ cJSON_AddBoolToObject(json, "monochrome", true);
+ } else {
+ cJSON_AddBoolToObject(json, "monochrome", false);
+ }
+ return json;
+ });
+
+#if CONFIG_LV_USE_SNAPSHOT
+ AddUserOnlyTool("self.screen.snapshot", "Snapshot the screen and upload it to a specific URL",
+ PropertyList({
+ Property("url", kPropertyTypeString),
+ Property("quality", kPropertyTypeInteger, 80, 1, 100)
+ }),
+ [display](const PropertyList& properties) -> ReturnValue {
+ auto url = properties["url"].value<std::string>();
+ auto quality = properties["quality"].value<int>();
+
+ std::string jpeg_data;
+ if (!display->SnapshotToJpeg(jpeg_data, quality)) {
+ throw std::runtime_error("Failed to snapshot screen");
+ }
+
+ ESP_LOGI(TAG, "Upload snapshot %u bytes to %s", jpeg_data.size(), url.c_str());
+
+ // 鏋勯�爉ultipart/form-data璇锋眰浣�
+ std::string boundary = "----ESP32_SCREEN_SNAPSHOT_BOUNDARY";
+
+ auto http = Board::GetInstance().GetNetwork()->CreateHttp(3);
+ http->SetHeader("Content-Type", "multipart/form-data; boundary=" + boundary);
+ if (!http->Open("POST", url)) {
+ throw std::runtime_error("Failed to open URL: " + url);
+ }
+ {
+ // 鏂囦欢瀛楁澶撮儴
+ std::string file_header;
+ file_header += "--" + boundary + "\r\n";
+ file_header += "Content-Disposition: form-data; name=\"file\"; filename=\"screenshot.jpg\"\r\n";
+ file_header += "Content-Type: image/jpeg\r\n";
+ file_header += "\r\n";
+ http->Write(file_header.c_str(), file_header.size());
+ }
+
+ // JPEG鏁版嵁
+ http->Write((const char*)jpeg_data.data(), jpeg_data.size());
+
+ {
+ // multipart灏鹃儴
+ std::string multipart_footer;
+ multipart_footer += "\r\n--" + boundary + "--\r\n";
+ http->Write(multipart_footer.c_str(), multipart_footer.size());
+ }
+ http->Write("", 0);
+
+ if (http->GetStatusCode() != 200) {
+ throw std::runtime_error("Unexpected status code: " + std::to_string(http->GetStatusCode()));
+ }
+ std::string result = http->ReadAll();
+ http->Close();
+ ESP_LOGI(TAG, "Snapshot screen result: %s", result.c_str());
+ return true;
+ });
+
+ AddUserOnlyTool("self.screen.preview_image", "Preview an image on the screen",
+ PropertyList({
+ Property("url", kPropertyTypeString)
+ }),
+ [display](const PropertyList& properties) -> ReturnValue {
+ auto url = properties["url"].value<std::string>();
+ auto http = Board::GetInstance().GetNetwork()->CreateHttp(3);
+
+ if (!http->Open("GET", url)) {
+ throw std::runtime_error("Failed to open URL: " + url);
+ }
+ int status_code = http->GetStatusCode();
+ if (status_code != 200) {
+ throw std::runtime_error("Unexpected status code: " + std::to_string(status_code));
+ }
+
+ size_t content_length = http->GetBodyLength();
+ char* data = (char*)heap_caps_malloc(content_length, MALLOC_CAP_8BIT);
+ if (data == nullptr) {
+ throw std::runtime_error("Failed to allocate memory for image: " + url);
+ }
+ size_t total_read = 0;
+ while (total_read < content_length) {
+ int ret = http->Read(data + total_read, content_length - total_read);
+ if (ret < 0) {
+ heap_caps_free(data);
+ throw std::runtime_error("Failed to download image: " + url);
+ }
+ if (ret == 0) {
+ break;
+ }
+ total_read += ret;
+ }
+ http->Close();
+
+ auto image = std::make_unique<LvglAllocatedImage>(data, content_length);
+ display->SetPreviewImage(std::move(image));
+ return true;
+ });
+#endif // CONFIG_LV_USE_SNAPSHOT
+ }
+#endif // HAVE_LVGL
+
+ // Assets download url
+ auto& assets = Assets::GetInstance();
+ if (assets.partition_valid()) {
+ AddUserOnlyTool("self.assets.set_download_url", "Set the download url for the assets",
+ PropertyList({
+ Property("url", kPropertyTypeString)
+ }),
+ [](const PropertyList& properties) -> ReturnValue {
+ auto url = properties["url"].value<std::string>();
+ Settings settings("assets", true);
+ settings.SetString("download_url", url);
+ return true;
+ });
+ }
+}
+
+void McpServer::AddTool(McpTool* tool) {
+ // Prevent adding duplicate tools
+ if (std::find_if(tools_.begin(), tools_.end(), [tool](const McpTool* t) { return t->name() == tool->name(); }) != tools_.end()) {
+ ESP_LOGW(TAG, "Tool %s already added", tool->name().c_str());
+ return;
+ }
+
+ ESP_LOGI(TAG, "Add tool: %s%s", tool->name().c_str(), tool->user_only() ? " [user]" : "");
+ tools_.push_back(tool);
+}
+
+void McpServer::AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback) {
+ AddTool(new McpTool(name, description, properties, callback));
+}
+
+void McpServer::AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback) {
+ auto tool = new McpTool(name, description, properties, callback);
+ tool->set_user_only(true);
+ AddTool(tool);
+}
+
+void McpServer::ParseMessage(const std::string& message) {
+ cJSON* json = cJSON_Parse(message.c_str());
+ if (json == nullptr) {
+ ESP_LOGE(TAG, "Failed to parse MCP message: %s", message.c_str());
+ return;
+ }
+ ParseMessage(json);
+ cJSON_Delete(json);
+}
+
+void McpServer::ParseCapabilities(const cJSON* capabilities) {
+ auto vision = cJSON_GetObjectItem(capabilities, "vision");
+ if (cJSON_IsObject(vision)) {
+ auto url = cJSON_GetObjectItem(vision, "url");
+ auto token = cJSON_GetObjectItem(vision, "token");
+ if (cJSON_IsString(url)) {
+ auto camera = Board::GetInstance().GetCamera();
+ if (camera) {
+ std::string url_str = std::string(url->valuestring);
+ std::string token_str;
+ if (cJSON_IsString(token)) {
+ token_str = std::string(token->valuestring);
+ }
+ camera->SetExplainUrl(url_str, token_str);
+ }
+ }
+ }
+}
+
+void McpServer::ParseMessage(const cJSON* json) {
+ // Check JSONRPC version
+ auto version = cJSON_GetObjectItem(json, "jsonrpc");
+ if (version == nullptr || !cJSON_IsString(version) || strcmp(version->valuestring, "2.0") != 0) {
+ ESP_LOGE(TAG, "Invalid JSONRPC version: %s", version ? version->valuestring : "null");
+ return;
+ }
+
+ // Check method
+ auto method = cJSON_GetObjectItem(json, "method");
+ if (method == nullptr || !cJSON_IsString(method)) {
+ ESP_LOGE(TAG, "Missing method");
+ return;
+ }
+
+ auto method_str = std::string(method->valuestring);
+ if (method_str.find("notifications") == 0) {
+ return;
+ }
+
+ // Check params
+ auto params = cJSON_GetObjectItem(json, "params");
+ if (params != nullptr && !cJSON_IsObject(params)) {
+ ESP_LOGE(TAG, "Invalid params for method: %s", method_str.c_str());
+ return;
+ }
+
+ auto id = cJSON_GetObjectItem(json, "id");
+ if (id == nullptr || !cJSON_IsNumber(id)) {
+ ESP_LOGE(TAG, "Invalid id for method: %s", method_str.c_str());
+ return;
+ }
+ auto id_int = id->valueint;
+
+ if (method_str == "initialize") {
+ if (cJSON_IsObject(params)) {
+ auto capabilities = cJSON_GetObjectItem(params, "capabilities");
+ if (cJSON_IsObject(capabilities)) {
+ ParseCapabilities(capabilities);
+ }
+ }
+ auto app_desc = esp_app_get_description();
+ std::string message = "{\"protocolVersion\":\"2024-11-05\",\"capabilities\":{\"tools\":{}},\"serverInfo\":{\"name\":\"" BOARD_NAME "\",\"version\":\"";
+ message += app_desc->version;
+ message += "\"}}";
+ ReplyResult(id_int, message);
+ } else if (method_str == "tools/list") {
+ std::string cursor_str = "";
+ bool list_user_only_tools = false;
+ if (params != nullptr) {
+ auto cursor = cJSON_GetObjectItem(params, "cursor");
+ if (cJSON_IsString(cursor)) {
+ cursor_str = std::string(cursor->valuestring);
+ }
+ auto with_user_tools = cJSON_GetObjectItem(params, "withUserTools");
+ if (cJSON_IsBool(with_user_tools)) {
+ list_user_only_tools = with_user_tools->valueint == 1;
+ }
+ }
+ GetToolsList(id_int, cursor_str, list_user_only_tools);
+ } else if (method_str == "tools/call") {
+ if (!cJSON_IsObject(params)) {
+ ESP_LOGE(TAG, "tools/call: Missing params");
+ ReplyError(id_int, "Missing params");
+ return;
+ }
+ auto tool_name = cJSON_GetObjectItem(params, "name");
+ if (!cJSON_IsString(tool_name)) {
+ ESP_LOGE(TAG, "tools/call: Missing name");
+ ReplyError(id_int, "Missing name");
+ return;
+ }
+ auto tool_arguments = cJSON_GetObjectItem(params, "arguments");
+ if (tool_arguments != nullptr && !cJSON_IsObject(tool_arguments)) {
+ ESP_LOGE(TAG, "tools/call: Invalid arguments");
+ ReplyError(id_int, "Invalid arguments");
+ return;
+ }
+ DoToolCall(id_int, std::string(tool_name->valuestring), tool_arguments);
+ } else {
+ ESP_LOGE(TAG, "Method not implemented: %s", method_str.c_str());
+ ReplyError(id_int, "Method not implemented: " + method_str);
+ }
+}
+
+void McpServer::ReplyResult(int id, const std::string& result) {
+ std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":";
+ payload += std::to_string(id) + ",\"result\":";
+ payload += result;
+ payload += "}";
+ Application::GetInstance().SendMcpMessage(payload);
+}
+
+void McpServer::ReplyError(int id, const std::string& message) {
+ std::string payload = "{\"jsonrpc\":\"2.0\",\"id\":";
+ payload += std::to_string(id);
+ payload += ",\"error\":{\"message\":\"";
+ payload += message;
+ payload += "\"}}";
+ Application::GetInstance().SendMcpMessage(payload);
+}
+
+void McpServer::GetToolsList(int id, const std::string& cursor, bool list_user_only_tools) {
+ const int max_payload_size = 8000;
+ std::string json = "{\"tools\":[";
+
+ bool found_cursor = cursor.empty();
+ auto it = tools_.begin();
+ std::string next_cursor = "";
+
+ while (it != tools_.end()) {
+ // 濡傛灉鎴戜滑杩樻病鏈夋壘鍒拌捣濮嬩綅缃紝缁х画鎼滅储
+ if (!found_cursor) {
+ if ((*it)->name() == cursor) {
+ found_cursor = true;
+ } else {
+ ++it;
+ continue;
+ }
+ }
+
+ if (!list_user_only_tools && (*it)->user_only()) {
+ ++it;
+ continue;
+ }
+
+ // 娣诲姞tool鍓嶆鏌ュぇ灏�
+ std::string tool_json = (*it)->to_json() + ",";
+ if (json.length() + tool_json.length() + 30 > max_payload_size) {
+ // 濡傛灉娣诲姞杩欎釜tool浼氳秴鍑哄ぇ灏忛檺鍒讹紝璁剧疆next_cursor骞堕��鍑哄惊鐜�
+ next_cursor = (*it)->name();
+ break;
+ }
+
+ json += tool_json;
+ ++it;
+ }
+
+ if (json.back() == ',') {
+ json.pop_back();
+ }
+
+ if (json.back() == '[' && !tools_.empty()) {
+ // 濡傛灉娌℃湁娣诲姞浠讳綍tool锛岃繑鍥為敊璇�
+ ESP_LOGE(TAG, "tools/list: Failed to add tool %s because of payload size limit", next_cursor.c_str());
+ ReplyError(id, "Failed to add tool " + next_cursor + " because of payload size limit");
+ return;
+ }
+
+ if (next_cursor.empty()) {
+ json += "]}";
+ } else {
+ json += "],\"nextCursor\":\"" + next_cursor + "\"}";
+ }
+
+ ReplyResult(id, json);
+}
+
+void McpServer::DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments) {
+ auto tool_iter = std::find_if(tools_.begin(), tools_.end(),
+ [&tool_name](const McpTool* tool) {
+ return tool->name() == tool_name;
+ });
+
+ if (tool_iter == tools_.end()) {
+ ESP_LOGE(TAG, "tools/call: Unknown tool: %s", tool_name.c_str());
+ ReplyError(id, "Unknown tool: " + tool_name);
+ return;
+ }
+
+ PropertyList arguments = (*tool_iter)->properties();
+ try {
+ for (auto& argument : arguments) {
+ bool found = false;
+ if (cJSON_IsObject(tool_arguments)) {
+ auto value = cJSON_GetObjectItem(tool_arguments, argument.name().c_str());
+ if (argument.type() == kPropertyTypeBoolean && cJSON_IsBool(value)) {
+ argument.set_value<bool>(value->valueint == 1);
+ found = true;
+ } else if (argument.type() == kPropertyTypeInteger && cJSON_IsNumber(value)) {
+ argument.set_value<int>(value->valueint);
+ found = true;
+ } else if (argument.type() == kPropertyTypeString && cJSON_IsString(value)) {
+ argument.set_value<std::string>(value->valuestring);
+ found = true;
+ }
+ }
+
+ if (!argument.has_default_value() && !found) {
+ ESP_LOGE(TAG, "tools/call: Missing valid argument: %s", argument.name().c_str());
+ ReplyError(id, "Missing valid argument: " + argument.name());
+ return;
+ }
+ }
+ } catch (const std::exception& e) {
+ ESP_LOGE(TAG, "tools/call: %s", e.what());
+ ReplyError(id, e.what());
+ return;
+ }
+
+ // Use main thread to call the tool
+ auto& app = Application::GetInstance();
+ app.Schedule([this, id, tool_iter, arguments = std::move(arguments)]() {
+ try {
+ ReplyResult(id, (*tool_iter)->Call(arguments));
+ } catch (const std::exception& e) {
+ ESP_LOGE(TAG, "tools/call: %s", e.what());
+ ReplyError(id, e.what());
+ }
+ });
+}
diff --git a/xiaozhi-esp32-2.2.6/main/mcp_server.h b/xiaozhi-esp32-2.2.6/main/mcp_server.h
new file mode 100644
index 0000000..dacdd55
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/mcp_server.h
@@ -0,0 +1,344 @@
+#ifndef MCP_SERVER_H
+#define MCP_SERVER_H
+
+#include <string>
+#include <vector>
+#include <map>
+#include <functional>
+#include <variant>
+#include <optional>
+#include <stdexcept>
+#include <thread>
+#include <mbedtls/base64.h>
+
+#include <cJSON.h>
+
+class ImageContent {
+private:
+ std::string encoded_data_;
+ std::string mime_type_;
+
+ static std::string Base64Encode(const std::string& data) {
+ size_t dlen = 0, olen = 0;
+ mbedtls_base64_encode((unsigned char*)nullptr, 0, &dlen, (const unsigned char*)data.data(), data.size());
+ std::string result(dlen, 0);
+ mbedtls_base64_encode((unsigned char*)result.data(), result.size(), &olen, (const unsigned char*)data.data(), data.size());
+ return result;
+ }
+
+public:
+ ImageContent(const std::string& mime_type, const std::string& data) {
+ mime_type_ = mime_type;
+ // base64 encode data
+ encoded_data_ = Base64Encode(data);
+ }
+
+ std::string to_json() const {
+ cJSON *json = cJSON_CreateObject();
+ cJSON_AddStringToObject(json, "type", "image");
+ cJSON_AddStringToObject(json, "mimeType", mime_type_.c_str());
+ cJSON_AddStringToObject(json, "data", encoded_data_.c_str());
+ char* json_str = cJSON_PrintUnformatted(json);
+ std::string result(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(json);
+ return result;
+ }
+};
+
+// 娣诲姞绫诲瀷鍒悕
+using ReturnValue = std::variant<bool, int, std::string, cJSON*, ImageContent*>;
+
+enum PropertyType {
+ kPropertyTypeBoolean,
+ kPropertyTypeInteger,
+ kPropertyTypeString
+};
+
+class Property {
+private:
+ std::string name_;
+ PropertyType type_;
+ std::variant<bool, int, std::string> value_;
+ bool has_default_value_;
+ std::optional<int> min_value_; // 鏂板锛氭暣鏁版渶灏忓��
+ std::optional<int> max_value_; // 鏂板锛氭暣鏁版渶澶у��
+
+public:
+ // Required field constructor
+ Property(const std::string& name, PropertyType type)
+ : name_(name), type_(type), has_default_value_(false) {}
+
+ // Optional field constructor with default value
+ template<typename T>
+ Property(const std::string& name, PropertyType type, const T& default_value)
+ : name_(name), type_(type), has_default_value_(true) {
+ value_ = default_value;
+ }
+
+ Property(const std::string& name, PropertyType type, int min_value, int max_value)
+ : name_(name), type_(type), has_default_value_(false), min_value_(min_value), max_value_(max_value) {
+ if (type != kPropertyTypeInteger) {
+ throw std::invalid_argument("Range limits only apply to integer properties");
+ }
+ }
+
+ Property(const std::string& name, PropertyType type, int default_value, int min_value, int max_value)
+ : name_(name), type_(type), has_default_value_(true), min_value_(min_value), max_value_(max_value) {
+ if (type != kPropertyTypeInteger) {
+ throw std::invalid_argument("Range limits only apply to integer properties");
+ }
+ if (default_value < min_value || default_value > max_value) {
+ throw std::invalid_argument("Default value must be within the specified range");
+ }
+ value_ = default_value;
+ }
+
+ inline const std::string& name() const { return name_; }
+ inline PropertyType type() const { return type_; }
+ inline bool has_default_value() const { return has_default_value_; }
+ inline bool has_range() const { return min_value_.has_value() && max_value_.has_value(); }
+ inline int min_value() const { return min_value_.value_or(0); }
+ inline int max_value() const { return max_value_.value_or(0); }
+
+ template<typename T>
+ inline T value() const {
+ return std::get<T>(value_);
+ }
+
+ template<typename T>
+ inline void set_value(const T& value) {
+ // 娣诲姞瀵硅缃殑鏁存暟鍊艰繘琛岃寖鍥存鏌�
+ if constexpr (std::is_same_v<T, int>) {
+ if (min_value_.has_value() && value < min_value_.value()) {
+ throw std::invalid_argument("Value is below minimum allowed: " + std::to_string(min_value_.value()));
+ }
+ if (max_value_.has_value() && value > max_value_.value()) {
+ throw std::invalid_argument("Value exceeds maximum allowed: " + std::to_string(max_value_.value()));
+ }
+ }
+ value_ = value;
+ }
+
+ std::string to_json() const {
+ cJSON *json = cJSON_CreateObject();
+
+ if (type_ == kPropertyTypeBoolean) {
+ cJSON_AddStringToObject(json, "type", "boolean");
+ if (has_default_value_) {
+ cJSON_AddBoolToObject(json, "default", value<bool>());
+ }
+ } else if (type_ == kPropertyTypeInteger) {
+ cJSON_AddStringToObject(json, "type", "integer");
+ if (has_default_value_) {
+ cJSON_AddNumberToObject(json, "default", value<int>());
+ }
+ if (min_value_.has_value()) {
+ cJSON_AddNumberToObject(json, "minimum", min_value_.value());
+ }
+ if (max_value_.has_value()) {
+ cJSON_AddNumberToObject(json, "maximum", max_value_.value());
+ }
+ } else if (type_ == kPropertyTypeString) {
+ cJSON_AddStringToObject(json, "type", "string");
+ if (has_default_value_) {
+ cJSON_AddStringToObject(json, "default", value<std::string>().c_str());
+ }
+ }
+
+ char *json_str = cJSON_PrintUnformatted(json);
+ std::string result(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(json);
+
+ return result;
+ }
+};
+
+class PropertyList {
+private:
+ std::vector<Property> properties_;
+
+public:
+ PropertyList() = default;
+ PropertyList(const std::vector<Property>& properties) : properties_(properties) {}
+ void AddProperty(const Property& property) {
+ properties_.push_back(property);
+ }
+
+ const Property& operator[](const std::string& name) const {
+ for (const auto& property : properties_) {
+ if (property.name() == name) {
+ return property;
+ }
+ }
+ throw std::runtime_error("Property not found: " + name);
+ }
+
+ auto begin() { return properties_.begin(); }
+ auto end() { return properties_.end(); }
+
+ std::vector<std::string> GetRequired() const {
+ std::vector<std::string> required;
+ for (auto& property : properties_) {
+ if (!property.has_default_value()) {
+ required.push_back(property.name());
+ }
+ }
+ return required;
+ }
+
+ std::string to_json() const {
+ cJSON *json = cJSON_CreateObject();
+
+ for (const auto& property : properties_) {
+ cJSON *prop_json = cJSON_Parse(property.to_json().c_str());
+ cJSON_AddItemToObject(json, property.name().c_str(), prop_json);
+ }
+
+ char *json_str = cJSON_PrintUnformatted(json);
+ std::string result(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(json);
+
+ return result;
+ }
+};
+
+class McpTool {
+private:
+ std::string name_;
+ std::string description_;
+ PropertyList properties_;
+ std::function<ReturnValue(const PropertyList&)> callback_;
+ bool user_only_ = false;
+
+public:
+ McpTool(const std::string& name,
+ const std::string& description,
+ const PropertyList& properties,
+ std::function<ReturnValue(const PropertyList&)> callback)
+ : name_(name),
+ description_(description),
+ properties_(properties),
+ callback_(callback) {}
+
+ void set_user_only(bool user_only) { user_only_ = user_only; }
+ inline const std::string& name() const { return name_; }
+ inline const std::string& description() const { return description_; }
+ inline const PropertyList& properties() const { return properties_; }
+ inline bool user_only() const { return user_only_; }
+
+ std::string to_json() const {
+ std::vector<std::string> required = properties_.GetRequired();
+
+ cJSON *json = cJSON_CreateObject();
+ cJSON_AddStringToObject(json, "name", name_.c_str());
+ cJSON_AddStringToObject(json, "description", description_.c_str());
+
+ cJSON *input_schema = cJSON_CreateObject();
+ cJSON_AddStringToObject(input_schema, "type", "object");
+
+ cJSON *properties = cJSON_Parse(properties_.to_json().c_str());
+ cJSON_AddItemToObject(input_schema, "properties", properties);
+
+ if (!required.empty()) {
+ cJSON *required_array = cJSON_CreateArray();
+ for (const auto& property : required) {
+ cJSON_AddItemToArray(required_array, cJSON_CreateString(property.c_str()));
+ }
+ cJSON_AddItemToObject(input_schema, "required", required_array);
+ }
+
+ cJSON_AddItemToObject(json, "inputSchema", input_schema);
+
+ // Add audience annotation if the tool is user only (invisible to AI)
+ if (user_only_) {
+ cJSON *annotations = cJSON_CreateObject();
+ cJSON *audience = cJSON_CreateArray();
+ cJSON_AddItemToArray(audience, cJSON_CreateString("user"));
+ cJSON_AddItemToObject(annotations, "audience", audience);
+ cJSON_AddItemToObject(json, "annotations", annotations);
+ }
+
+ char *json_str = cJSON_PrintUnformatted(json);
+ std::string result(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(json);
+
+ return result;
+ }
+
+ std::string Call(const PropertyList& properties) {
+ ReturnValue return_value = callback_(properties);
+ // 杩斿洖缁撴灉
+ cJSON* result = cJSON_CreateObject();
+ cJSON* content = cJSON_CreateArray();
+
+ if (std::holds_alternative<ImageContent*>(return_value)) {
+ auto image_content = std::get<ImageContent*>(return_value);
+ cJSON* image = cJSON_CreateObject();
+ cJSON_AddStringToObject(image, "type", "image");
+ cJSON_AddStringToObject(image, "image", image_content->to_json().c_str());
+ cJSON_AddItemToArray(content, image);
+ delete image_content;
+ } else {
+ cJSON* text = cJSON_CreateObject();
+ cJSON_AddStringToObject(text, "type", "text");
+ if (std::holds_alternative<std::string>(return_value)) {
+ cJSON_AddStringToObject(text, "text", std::get<std::string>(return_value).c_str());
+ } else if (std::holds_alternative<bool>(return_value)) {
+ cJSON_AddStringToObject(text, "text", std::get<bool>(return_value) ? "true" : "false");
+ } else if (std::holds_alternative<int>(return_value)) {
+ cJSON_AddStringToObject(text, "text", std::to_string(std::get<int>(return_value)).c_str());
+ } else if (std::holds_alternative<cJSON*>(return_value)) {
+ cJSON* json = std::get<cJSON*>(return_value);
+ char* json_str = cJSON_PrintUnformatted(json);
+ cJSON_AddStringToObject(text, "text", json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(json);
+ }
+ cJSON_AddItemToArray(content, text);
+ }
+ cJSON_AddItemToObject(result, "content", content);
+ cJSON_AddBoolToObject(result, "isError", false);
+
+ auto json_str = cJSON_PrintUnformatted(result);
+ std::string result_str(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(result);
+ return result_str;
+ }
+};
+
+class McpServer {
+public:
+ static McpServer& GetInstance() {
+ static McpServer instance;
+ return instance;
+ }
+
+ void AddCommonTools();
+ void AddUserOnlyTools();
+ void AddTool(McpTool* tool);
+ void AddTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback);
+ void AddUserOnlyTool(const std::string& name, const std::string& description, const PropertyList& properties, std::function<ReturnValue(const PropertyList&)> callback);
+ void ParseMessage(const cJSON* json);
+ void ParseMessage(const std::string& message);
+
+private:
+ McpServer();
+ ~McpServer();
+
+ void ParseCapabilities(const cJSON* capabilities);
+
+ void ReplyResult(int id, const std::string& result);
+ void ReplyError(int id, const std::string& message);
+
+ void GetToolsList(int id, const std::string& cursor, bool list_user_only_tools);
+ void DoToolCall(int id, const std::string& tool_name, const cJSON* tool_arguments);
+
+ std::vector<McpTool*> tools_;
+};
+
+#endif // MCP_SERVER_H
diff --git a/xiaozhi-esp32-2.2.6/main/ota.cc b/xiaozhi-esp32-2.2.6/main/ota.cc
new file mode 100644
index 0000000..bcc0dc5
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/ota.cc
@@ -0,0 +1,492 @@
+#include "ota.h"
+#include "system_info.h"
+#include "settings.h"
+#include "assets/lang_config.h"
+
+#include <freertos/FreeRTOS.h>
+#include <freertos/task.h>
+#include <cJSON.h>
+#include <esp_log.h>
+#include <esp_partition.h>
+#include <esp_ota_ops.h>
+#include <esp_app_format.h>
+#include <esp_efuse.h>
+#include <esp_efuse_table.h>
+#include <esp_heap_caps.h>
+#ifdef SOC_HMAC_SUPPORTED
+#include <esp_hmac.h>
+#endif
+
+#include <cstring>
+#include <vector>
+#include <sstream>
+#include <algorithm>
+
+#define TAG "Ota"
+
+
+Ota::Ota() {
+#ifdef ESP_EFUSE_BLOCK_USR_DATA
+ // Read Serial Number from efuse user_data
+ uint8_t serial_number[33] = {0};
+ if (esp_efuse_read_field_blob(ESP_EFUSE_USER_DATA, serial_number, 32 * 8) == ESP_OK) {
+ if (serial_number[0] == 0) {
+ has_serial_number_ = false;
+ } else {
+ serial_number_ = std::string(reinterpret_cast<char*>(serial_number), 32);
+ has_serial_number_ = true;
+ }
+ }
+#endif
+}
+
+Ota::~Ota() {
+}
+
+std::string Ota::GetCheckVersionUrl() {
+ Settings settings("wifi", false);
+ std::string url = settings.GetString("ota_url");
+ if (url.empty()) {
+ url = CONFIG_OTA_URL;
+ }
+ return url;
+}
+
+std::unique_ptr<Http> Ota::SetupHttp() {
+ auto& board = Board::GetInstance();
+ auto network = board.GetNetwork();
+ auto http = network->CreateHttp(0);
+ auto user_agent = SystemInfo::GetUserAgent();
+ http->SetHeader("Activation-Version", has_serial_number_ ? "2" : "1");
+ http->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
+ http->SetHeader("Client-Id", board.GetUuid());
+ if (has_serial_number_) {
+ http->SetHeader("Serial-Number", serial_number_.c_str());
+ ESP_LOGI(TAG, "Setup HTTP, User-Agent: %s, Serial-Number: %s", user_agent.c_str(), serial_number_.c_str());
+ }
+ http->SetHeader("User-Agent", user_agent);
+ http->SetHeader("Accept-Language", Lang::CODE);
+ http->SetHeader("Content-Type", "application/json");
+
+ return http;
+}
+
+/*
+ * Specification: https://ccnphfhqs21z.feishu.cn/wiki/FjW6wZmisimNBBkov6OcmfvknVd
+ */
+esp_err_t Ota::CheckVersion() {
+ auto& board = Board::GetInstance();
+ auto app_desc = esp_app_get_description();
+
+ // Check if there is a new firmware version available
+ current_version_ = app_desc->version;
+ ESP_LOGI(TAG, "Current version: %s", current_version_.c_str());
+
+ std::string url = GetCheckVersionUrl();
+ if (url.length() < 10) {
+ ESP_LOGE(TAG, "Check version URL is not properly set");
+ return ESP_ERR_INVALID_ARG;
+ }
+
+ auto http = SetupHttp();
+
+ std::string data = board.GetSystemInfoJson();
+ std::string method = data.length() > 0 ? "POST" : "GET";
+ http->SetContent(std::move(data));
+
+ if (!http->Open(method, url)) {
+ int last_error = http->GetLastError();
+ ESP_LOGE(TAG, "Failed to open HTTP connection, code=0x%x", last_error);
+ return last_error;
+ }
+
+ auto status_code = http->GetStatusCode();
+ if (status_code != 200) {
+ ESP_LOGE(TAG, "Failed to check version, status code: %d", status_code);
+ return status_code;
+ }
+
+ data = http->ReadAll();
+ http->Close();
+
+ // Response: { "firmware": { "version": "1.0.0", "url": "http://" } }
+ // Parse the JSON response and check if the version is newer
+ // If it is, set has_new_version_ to true and store the new version and URL
+
+ cJSON *root = cJSON_Parse(data.c_str());
+ if (root == NULL) {
+ ESP_LOGE(TAG, "Failed to parse JSON response");
+ return ESP_ERR_INVALID_RESPONSE;
+ }
+
+ has_activation_code_ = false;
+ has_activation_challenge_ = false;
+ cJSON *activation = cJSON_GetObjectItem(root, "activation");
+ if (cJSON_IsObject(activation)) {
+ cJSON* message = cJSON_GetObjectItem(activation, "message");
+ if (cJSON_IsString(message)) {
+ activation_message_ = message->valuestring;
+ }
+ cJSON* code = cJSON_GetObjectItem(activation, "code");
+ if (cJSON_IsString(code)) {
+ activation_code_ = code->valuestring;
+ has_activation_code_ = true;
+ }
+ cJSON* challenge = cJSON_GetObjectItem(activation, "challenge");
+ if (cJSON_IsString(challenge)) {
+ activation_challenge_ = challenge->valuestring;
+ has_activation_challenge_ = true;
+ }
+ cJSON* timeout_ms = cJSON_GetObjectItem(activation, "timeout_ms");
+ if (cJSON_IsNumber(timeout_ms)) {
+ activation_timeout_ms_ = timeout_ms->valueint;
+ }
+ }
+
+ has_mqtt_config_ = false;
+ cJSON *mqtt = cJSON_GetObjectItem(root, "mqtt");
+ if (cJSON_IsObject(mqtt)) {
+ Settings settings("mqtt", true);
+ cJSON *item = NULL;
+ cJSON_ArrayForEach(item, mqtt) {
+ if (cJSON_IsString(item)) {
+ if (settings.GetString(item->string) != item->valuestring) {
+ settings.SetString(item->string, item->valuestring);
+ }
+ } else if (cJSON_IsNumber(item)) {
+ if (settings.GetInt(item->string) != item->valueint) {
+ settings.SetInt(item->string, item->valueint);
+ }
+ }
+ }
+ has_mqtt_config_ = true;
+ } else {
+ ESP_LOGI(TAG, "No mqtt section found !");
+ }
+
+ has_websocket_config_ = false;
+ cJSON *websocket = cJSON_GetObjectItem(root, "websocket");
+ if (cJSON_IsObject(websocket)) {
+ Settings settings("websocket", true);
+ cJSON *item = NULL;
+ cJSON_ArrayForEach(item, websocket) {
+ if (cJSON_IsString(item)) {
+ if (settings.GetString(item->string) != item->valuestring) {
+ settings.SetString(item->string, item->valuestring);
+ }
+ } else if (cJSON_IsNumber(item)) {
+ if (settings.GetInt(item->string) != item->valueint) {
+ settings.SetInt(item->string, item->valueint);
+ }
+ }
+ }
+ has_websocket_config_ = true;
+ } else {
+ ESP_LOGI(TAG, "No websocket section found!");
+ }
+
+ has_server_time_ = false;
+ cJSON *server_time = cJSON_GetObjectItem(root, "server_time");
+ if (cJSON_IsObject(server_time)) {
+ cJSON *timestamp = cJSON_GetObjectItem(server_time, "timestamp");
+ cJSON *timezone_offset = cJSON_GetObjectItem(server_time, "timezone_offset");
+
+ if (cJSON_IsNumber(timestamp)) {
+ // 璁剧疆绯荤粺鏃堕棿
+ struct timeval tv;
+ double ts = timestamp->valuedouble;
+
+ // 濡傛灉鏈夋椂鍖哄亸绉伙紝璁$畻鏈湴鏃堕棿
+ if (cJSON_IsNumber(timezone_offset)) {
+ ts += (timezone_offset->valueint * 60 * 1000); // 杞崲鍒嗛挓涓烘绉�
+ }
+
+ tv.tv_sec = (time_t)(ts / 1000); // 杞崲姣涓虹
+ tv.tv_usec = (suseconds_t)((long long)ts % 1000) * 1000; // 鍓╀綑鐨勬绉掕浆鎹负寰
+ settimeofday(&tv, NULL);
+ has_server_time_ = true;
+ }
+ } else {
+ ESP_LOGW(TAG, "No server_time section found!");
+ }
+
+ has_new_version_ = false;
+ cJSON *firmware = cJSON_GetObjectItem(root, "firmware");
+ if (cJSON_IsObject(firmware)) {
+ cJSON *version = cJSON_GetObjectItem(firmware, "version");
+ if (cJSON_IsString(version)) {
+ firmware_version_ = version->valuestring;
+ }
+ cJSON *url = cJSON_GetObjectItem(firmware, "url");
+ if (cJSON_IsString(url)) {
+ firmware_url_ = url->valuestring;
+ }
+
+ if (cJSON_IsString(version) && cJSON_IsString(url)) {
+ // Check if the version is newer, for example, 0.1.0 is newer than 0.0.1
+ has_new_version_ = IsNewVersionAvailable(current_version_, firmware_version_);
+ if (has_new_version_) {
+ ESP_LOGI(TAG, "New version available: %s", firmware_version_.c_str());
+ } else {
+ ESP_LOGI(TAG, "Current is the latest version");
+ }
+ // If the force flag is set to 1, the given version is forced to be installed
+ cJSON *force = cJSON_GetObjectItem(firmware, "force");
+ if (cJSON_IsNumber(force) && force->valueint == 1) {
+ has_new_version_ = true;
+ }
+ }
+ } else {
+ ESP_LOGW(TAG, "No firmware section found!");
+ }
+
+ cJSON_Delete(root);
+ return ESP_OK;
+}
+
+void Ota::MarkCurrentVersionValid() {
+ auto partition = esp_ota_get_running_partition();
+ if (strcmp(partition->label, "factory") == 0) {
+ ESP_LOGI(TAG, "Running from factory partition, skipping");
+ return;
+ }
+
+ ESP_LOGI(TAG, "Running partition: %s", partition->label);
+ esp_ota_img_states_t state;
+ if (esp_ota_get_state_partition(partition, &state) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to get state of partition");
+ return;
+ }
+
+ if (state == ESP_OTA_IMG_PENDING_VERIFY) {
+ ESP_LOGI(TAG, "Marking firmware as valid");
+ esp_ota_mark_app_valid_cancel_rollback();
+ }
+}
+
+bool Ota::Upgrade(const std::string& firmware_url, std::function<void(int progress, size_t speed)> callback) {
+ ESP_LOGI(TAG, "Upgrading firmware from %s", firmware_url.c_str());
+ esp_ota_handle_t update_handle = 0;
+ auto update_partition = esp_ota_get_next_update_partition(NULL);
+ if (update_partition == NULL) {
+ ESP_LOGE(TAG, "Failed to get update partition");
+ return false;
+ }
+
+ ESP_LOGI(TAG, "Writing to partition %s at offset 0x%lx", update_partition->label, update_partition->address);
+ bool image_header_checked = false;
+ std::string image_header;
+
+ auto network = Board::GetInstance().GetNetwork();
+ auto http = network->CreateHttp(0);
+ if (!http->Open("GET", firmware_url)) {
+ ESP_LOGE(TAG, "Failed to open HTTP connection");
+ return false;
+ }
+
+ if (http->GetStatusCode() != 200) {
+ ESP_LOGE(TAG, "Failed to get firmware, 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;
+ }
+
+ constexpr size_t PAGE_SIZE = 4096;
+ char* buffer = (char*)heap_caps_malloc(PAGE_SIZE, MALLOC_CAP_INTERNAL);
+ if (buffer == nullptr) {
+ ESP_LOGE(TAG, "Failed to allocate buffer");
+ return false;
+ }
+
+ size_t buffer_offset = 0; // Current data size in buffer
+ size_t total_read = 0, recent_read = 0;
+ auto last_calc_time = esp_timer_get_time();
+ while (true) {
+ int ret = http->Read(buffer + buffer_offset, PAGE_SIZE - buffer_offset);
+ if (ret < 0) {
+ ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
+ heap_caps_free(buffer);
+ return false;
+ }
+
+ // Calculate speed and progress every second
+ recent_read += ret;
+ total_read += ret;
+ buffer_offset += ret;
+ if (esp_timer_get_time() - last_calc_time >= 1000000 || ret == 0) {
+ size_t progress = total_read * 100 / content_length;
+ ESP_LOGI(TAG, "Progress: %u%% (%u/%u), Speed: %uB/s", progress, total_read, content_length, recent_read);
+ if (callback) {
+ callback(progress, recent_read);
+ }
+ last_calc_time = esp_timer_get_time();
+ recent_read = 0;
+ }
+
+ if (!image_header_checked) {
+ image_header.append(buffer, buffer_offset);
+ if (image_header.size() >= sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t) + sizeof(esp_app_desc_t)) {
+ esp_app_desc_t new_app_info;
+ memcpy(&new_app_info, image_header.data() + sizeof(esp_image_header_t) + sizeof(esp_image_segment_header_t), sizeof(esp_app_desc_t));
+
+ if (esp_ota_begin(update_partition, OTA_WITH_SEQUENTIAL_WRITES, &update_handle)) {
+ esp_ota_abort(update_handle);
+ ESP_LOGE(TAG, "Failed to begin OTA");
+ heap_caps_free(buffer);
+ return false;
+ }
+
+ image_header_checked = true;
+ std::string().swap(image_header);
+ }
+ }
+
+ // Write to flash when buffer is full (4KB) or it's the last chunk
+ bool is_last_chunk = (ret == 0);
+ if (buffer_offset == PAGE_SIZE || (is_last_chunk && buffer_offset > 0)) {
+ auto err = esp_ota_write(update_handle, buffer, buffer_offset);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to write OTA data: %s", esp_err_to_name(err));
+ esp_ota_abort(update_handle);
+ heap_caps_free(buffer);
+ return false;
+ }
+
+ buffer_offset = 0;
+ }
+
+ if (is_last_chunk) {
+ break;
+ }
+ }
+ http->Close();
+ heap_caps_free(buffer);
+
+ esp_err_t err = esp_ota_end(update_handle);
+ if (err != ESP_OK) {
+ if (err == ESP_ERR_OTA_VALIDATE_FAILED) {
+ ESP_LOGE(TAG, "Image validation failed, image is corrupted");
+ } else {
+ ESP_LOGE(TAG, "Failed to end OTA: %s", esp_err_to_name(err));
+ }
+ return false;
+ }
+
+ err = esp_ota_set_boot_partition(update_partition);
+ if (err != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to set boot partition: %s", esp_err_to_name(err));
+ return false;
+ }
+
+ ESP_LOGI(TAG, "Firmware upgrade successful");
+ return true;
+}
+
+bool Ota::StartUpgrade(std::function<void(int progress, size_t speed)> callback) {
+ return Upgrade(firmware_url_, callback);
+}
+
+
+std::vector<int> Ota::ParseVersion(const std::string& version) {
+ std::vector<int> versionNumbers;
+ std::stringstream ss(version);
+ std::string segment;
+
+ while (std::getline(ss, segment, '.')) {
+ versionNumbers.push_back(std::stoi(segment));
+ }
+
+ return versionNumbers;
+}
+
+bool Ota::IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion) {
+ std::vector<int> current = ParseVersion(currentVersion);
+ std::vector<int> newer = ParseVersion(newVersion);
+
+ for (size_t i = 0; i < std::min(current.size(), newer.size()); ++i) {
+ if (newer[i] > current[i]) {
+ return true;
+ } else if (newer[i] < current[i]) {
+ return false;
+ }
+ }
+
+ return newer.size() > current.size();
+}
+
+std::string Ota::GetActivationPayload() {
+ if (!has_serial_number_) {
+ return "{}";
+ }
+
+ std::string hmac_hex;
+#ifdef SOC_HMAC_SUPPORTED
+ uint8_t hmac_result[32]; // SHA-256 杈撳嚭涓�32瀛楄妭
+
+ // 浣跨敤Key0璁$畻HMAC
+ esp_err_t ret = esp_hmac_calculate(HMAC_KEY0, (uint8_t*)activation_challenge_.data(), activation_challenge_.size(), hmac_result);
+ if (ret != ESP_OK) {
+ ESP_LOGE(TAG, "HMAC calculation failed: %s", esp_err_to_name(ret));
+ return "{}";
+ }
+
+ for (size_t i = 0; i < sizeof(hmac_result); i++) {
+ char buffer[3];
+ sprintf(buffer, "%02x", hmac_result[i]);
+ hmac_hex += buffer;
+ }
+#endif
+
+ cJSON *payload = cJSON_CreateObject();
+ cJSON_AddStringToObject(payload, "algorithm", "hmac-sha256");
+ cJSON_AddStringToObject(payload, "serial_number", serial_number_.c_str());
+ cJSON_AddStringToObject(payload, "challenge", activation_challenge_.c_str());
+ cJSON_AddStringToObject(payload, "hmac", hmac_hex.c_str());
+ auto json_str = cJSON_PrintUnformatted(payload);
+ std::string json(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(payload);
+
+ ESP_LOGI(TAG, "Activation payload: %s", json.c_str());
+ return json;
+}
+
+esp_err_t Ota::Activate() {
+ if (!has_activation_challenge_) {
+ ESP_LOGW(TAG, "No activation challenge found");
+ return ESP_FAIL;
+ }
+
+ std::string url = GetCheckVersionUrl();
+ if (url.back() != '/') {
+ url += "/activate";
+ } else {
+ url += "activate";
+ }
+
+ auto http = SetupHttp();
+
+ std::string data = GetActivationPayload();
+ http->SetContent(std::move(data));
+
+ if (!http->Open("POST", url)) {
+ ESP_LOGE(TAG, "Failed to open HTTP connection");
+ return ESP_FAIL;
+ }
+
+ auto status_code = http->GetStatusCode();
+ if (status_code == 202) {
+ return ESP_ERR_TIMEOUT;
+ }
+ if (status_code != 200) {
+ ESP_LOGE(TAG, "Failed to activate, code: %d, body: %s", status_code, http->ReadAll().c_str());
+ return ESP_FAIL;
+ }
+
+ ESP_LOGI(TAG, "Activation successful");
+ return ESP_OK;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/ota.h b/xiaozhi-esp32-2.2.6/main/ota.h
new file mode 100644
index 0000000..1e5d687
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/ota.h
@@ -0,0 +1,58 @@
+#ifndef _OTA_H
+#define _OTA_H
+
+#include <functional>
+#include <string>
+
+#include <esp_err.h>
+#include "board.h"
+
+class Ota {
+public:
+ Ota();
+ ~Ota();
+
+ esp_err_t CheckVersion();
+ esp_err_t Activate();
+ bool HasActivationChallenge() { return has_activation_challenge_; }
+ bool HasNewVersion() { return has_new_version_; }
+ bool HasMqttConfig() { return has_mqtt_config_; }
+ bool HasWebsocketConfig() { return has_websocket_config_; }
+ bool HasActivationCode() { return has_activation_code_; }
+ bool HasServerTime() { return has_server_time_; }
+ bool StartUpgrade(std::function<void(int progress, size_t speed)> callback);
+ static bool Upgrade(const std::string& firmware_url, std::function<void(int progress, size_t speed)> callback);
+ void MarkCurrentVersionValid();
+
+ const std::string& GetFirmwareVersion() const { return firmware_version_; }
+ const std::string& GetCurrentVersion() const { return current_version_; }
+ const std::string& GetFirmwareUrl() const { return firmware_url_; }
+ const std::string& GetActivationMessage() const { return activation_message_; }
+ const std::string& GetActivationCode() const { return activation_code_; }
+ std::string GetCheckVersionUrl();
+
+private:
+ std::string activation_message_;
+ std::string activation_code_;
+ bool has_new_version_ = false;
+ bool has_mqtt_config_ = false;
+ bool has_websocket_config_ = false;
+ bool has_server_time_ = false;
+ bool has_activation_code_ = false;
+ bool has_serial_number_ = false;
+ bool has_activation_challenge_ = false;
+ std::string current_version_;
+ std::string firmware_version_;
+ std::string firmware_url_;
+ std::string activation_challenge_;
+ std::string serial_number_;
+ int activation_timeout_ms_ = 30000;
+
+ std::function<void(int progress, size_t speed)> upgrade_callback_;
+ std::vector<int> ParseVersion(const std::string& version);
+ bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion);
+ std::string GetActivationPayload();
+ std::unique_ptr<Http> SetupHttp();
+};
+
+#endif // _OTA_H
diff --git a/xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.cc b/xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.cc
new file mode 100644
index 0000000..94d9d12
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.cc
@@ -0,0 +1,389 @@
+#include "mqtt_protocol.h"
+#include "board.h"
+#include "application.h"
+#include "settings.h"
+
+#include <esp_log.h>
+#include <cstring>
+#include <arpa/inet.h>
+#include "assets/lang_config.h"
+
+#define TAG "MQTT"
+
+MqttProtocol::MqttProtocol() {
+ event_group_handle_ = xEventGroupCreate();
+
+ // Initialize reconnect timer
+ esp_timer_create_args_t reconnect_timer_args = {
+ .callback = [](void* arg) {
+ MqttProtocol* protocol = (MqttProtocol*)arg;
+ auto& app = Application::GetInstance();
+ if (app.GetDeviceState() == kDeviceStateIdle) {
+ ESP_LOGI(TAG, "Reconnecting to MQTT server");
+ auto alive = protocol->alive_; // Capture alive flag
+ app.Schedule([protocol, alive]() {
+ if (*alive) {
+ protocol->StartMqttClient(false);
+ }
+ });
+ }
+ },
+ .arg = this,
+ };
+ esp_timer_create(&reconnect_timer_args, &reconnect_timer_);
+}
+
+MqttProtocol::~MqttProtocol() {
+ ESP_LOGI(TAG, "MqttProtocol deinit");
+
+ // Mark as dead first to prevent any pending scheduled tasks from executing
+ *alive_ = false;
+
+ if (reconnect_timer_ != nullptr) {
+ esp_timer_stop(reconnect_timer_);
+ esp_timer_delete(reconnect_timer_);
+ }
+
+ udp_.reset();
+ mqtt_.reset();
+
+ if (event_group_handle_ != nullptr) {
+ vEventGroupDelete(event_group_handle_);
+ }
+}
+
+bool MqttProtocol::Start() {
+ return StartMqttClient(false);
+}
+
+bool MqttProtocol::StartMqttClient(bool report_error) {
+ if (mqtt_ != nullptr) {
+ ESP_LOGW(TAG, "Mqtt client already started");
+ mqtt_.reset();
+ }
+
+ Settings settings("mqtt", false);
+ auto endpoint = settings.GetString("endpoint");
+ auto client_id = settings.GetString("client_id");
+ auto username = settings.GetString("username");
+ auto password = settings.GetString("password");
+ int keepalive_interval = settings.GetInt("keepalive", 240);
+ publish_topic_ = settings.GetString("publish_topic");
+
+ if (endpoint.empty()) {
+ ESP_LOGW(TAG, "MQTT endpoint is not specified");
+ if (report_error) {
+ SetError(Lang::Strings::SERVER_NOT_FOUND);
+ }
+ return false;
+ }
+
+ auto network = Board::GetInstance().GetNetwork();
+ mqtt_ = network->CreateMqtt(0);
+ mqtt_->SetKeepAlive(keepalive_interval);
+
+ mqtt_->OnDisconnected([this]() {
+ if (on_disconnected_ != nullptr) {
+ on_disconnected_();
+ }
+ ESP_LOGI(TAG, "MQTT disconnected, schedule reconnect in %d seconds", MQTT_RECONNECT_INTERVAL_MS / 1000);
+ esp_timer_start_once(reconnect_timer_, MQTT_RECONNECT_INTERVAL_MS * 1000);
+ });
+
+ mqtt_->OnConnected([this]() {
+ if (on_connected_ != nullptr) {
+ on_connected_();
+ }
+ esp_timer_stop(reconnect_timer_);
+ });
+
+ mqtt_->OnMessage([this](const std::string& topic, const std::string& payload) {
+ cJSON* root = cJSON_Parse(payload.c_str());
+ if (root == nullptr) {
+ ESP_LOGE(TAG, "Failed to parse json message %s", payload.c_str());
+ return;
+ }
+ cJSON* type = cJSON_GetObjectItem(root, "type");
+ if (!cJSON_IsString(type)) {
+ ESP_LOGE(TAG, "Message type is invalid");
+ cJSON_Delete(root);
+ return;
+ }
+
+ if (strcmp(type->valuestring, "hello") == 0) {
+ ParseServerHello(root);
+ } else if (strcmp(type->valuestring, "goodbye") == 0) {
+ auto session_id = cJSON_GetObjectItem(root, "session_id");
+ ESP_LOGI(TAG, "Received goodbye message, session_id: %s", session_id ? session_id->valuestring : "null");
+ if (session_id == nullptr || session_id_ == session_id->valuestring) {
+ auto alive = alive_; // Capture alive flag
+ Application::GetInstance().Schedule([this, alive]() {
+ if (*alive) {
+ // Server initiated goodbye, don't send goodbye back to avoid ping-pong
+ CloseAudioChannel(false);
+ }
+ });
+ }
+ } else if (on_incoming_json_ != nullptr) {
+ on_incoming_json_(root);
+ }
+ cJSON_Delete(root);
+ last_incoming_time_ = std::chrono::steady_clock::now();
+ });
+
+ ESP_LOGI(TAG, "Connecting to endpoint %s", endpoint.c_str());
+ std::string broker_address;
+ int broker_port = 8883;
+ size_t pos = endpoint.find(':');
+ if (pos != std::string::npos) {
+ broker_address = endpoint.substr(0, pos);
+ broker_port = std::stoi(endpoint.substr(pos + 1));
+ } else {
+ broker_address = endpoint;
+ }
+ if (!mqtt_->Connect(broker_address, broker_port, client_id, username, password)) {
+ ESP_LOGE(TAG, "Failed to connect to endpoint, code=%d", mqtt_->GetLastError());
+ SetError(Lang::Strings::SERVER_NOT_CONNECTED);
+ return false;
+ }
+
+ ESP_LOGI(TAG, "Connected to endpoint");
+ return true;
+}
+
+bool MqttProtocol::SendText(const std::string& text) {
+ if (publish_topic_.empty()) {
+ return false;
+ }
+ if (!mqtt_->Publish(publish_topic_, text)) {
+ ESP_LOGE(TAG, "Failed to publish message: %s", text.c_str());
+ SetError(Lang::Strings::SERVER_ERROR);
+ return false;
+ }
+ return true;
+}
+
+bool MqttProtocol::SendAudio(std::unique_ptr<AudioStreamPacket> packet) {
+ std::lock_guard<std::mutex> lock(channel_mutex_);
+ if (udp_ == nullptr) {
+ return false;
+ }
+
+ std::string nonce(aes_nonce_);
+ *(uint16_t*)&nonce[2] = htons(packet->payload.size());
+ *(uint32_t*)&nonce[8] = htonl(packet->timestamp);
+ *(uint32_t*)&nonce[12] = htonl(++local_sequence_);
+
+ std::string encrypted;
+ encrypted.resize(aes_nonce_.size() + packet->payload.size());
+ memcpy(encrypted.data(), nonce.data(), nonce.size());
+
+ size_t nc_off = 0;
+ uint8_t stream_block[16] = {0};
+ if (mbedtls_aes_crypt_ctr(&aes_ctx_, packet->payload.size(), &nc_off, (uint8_t*)nonce.c_str(), stream_block,
+ (uint8_t*)packet->payload.data(), (uint8_t*)&encrypted[nonce.size()]) != 0) {
+ ESP_LOGE(TAG, "Failed to encrypt audio data");
+ return false;
+ }
+
+ return udp_->Send(encrypted) > 0;
+}
+
+void MqttProtocol::CloseAudioChannel(bool send_goodbye) {
+ {
+ std::lock_guard<std::mutex> lock(channel_mutex_);
+ udp_.reset();
+ }
+
+ ESP_LOGI(TAG, "Closing audio channel, send_goodbye: %d", send_goodbye);
+
+ // Only send goodbye when client initiates the close
+ // Don't send if server already sent goodbye (to avoid ping-pong)
+ if (send_goodbye) {
+ std::string message = "{";
+ message += "\"session_id\":\"" + session_id_ + "\",";
+ message += "\"type\":\"goodbye\"";
+ message += "}";
+ SendText(message);
+ }
+
+ if (on_audio_channel_closed_ != nullptr) {
+ on_audio_channel_closed_();
+ }
+}
+
+bool MqttProtocol::OpenAudioChannel() {
+ if (mqtt_ == nullptr || !mqtt_->IsConnected()) {
+ ESP_LOGI(TAG, "MQTT is not connected, try to connect now");
+ if (!StartMqttClient(true)) {
+ return false;
+ }
+ }
+
+ error_occurred_ = false;
+ session_id_ = "";
+ xEventGroupClearBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT);
+
+ auto message = GetHelloMessage();
+ if (!SendText(message)) {
+ return false;
+ }
+
+ // 绛夊緟鏈嶅姟鍣ㄥ搷搴�
+ EventBits_t bits = xEventGroupWaitBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
+ if (!(bits & MQTT_PROTOCOL_SERVER_HELLO_EVENT)) {
+ ESP_LOGE(TAG, "Failed to receive server hello");
+ SetError(Lang::Strings::SERVER_TIMEOUT);
+ return false;
+ }
+
+ std::lock_guard<std::mutex> lock(channel_mutex_);
+ auto network = Board::GetInstance().GetNetwork();
+ udp_ = network->CreateUdp(2);
+ udp_->OnMessage([this](const std::string& data) {
+ /*
+ * UDP Encrypted OPUS Packet Format:
+ * |type 1u|flags 1u|payload_len 2u|ssrc 4u|timestamp 4u|sequence 4u|
+ * |payload payload_len|
+ */
+ if (data.size() < sizeof(aes_nonce_)) {
+ ESP_LOGE(TAG, "Invalid audio packet size: %u", data.size());
+ return;
+ }
+ if (data[0] != 0x01) {
+ ESP_LOGE(TAG, "Invalid audio packet type: %x", data[0]);
+ return;
+ }
+ uint32_t timestamp = ntohl(*(uint32_t*)&data[8]);
+ uint32_t sequence = ntohl(*(uint32_t*)&data[12]);
+ if (sequence < remote_sequence_) {
+ ESP_LOGW(TAG, "Received audio packet with old sequence: %lu, expected: %lu", sequence, remote_sequence_);
+ return;
+ }
+ if (sequence != remote_sequence_ + 1) {
+ ESP_LOGW(TAG, "Received audio packet with wrong sequence: %lu, expected: %lu", sequence, remote_sequence_ + 1);
+ }
+
+ size_t decrypted_size = data.size() - aes_nonce_.size();
+ size_t nc_off = 0;
+ uint8_t stream_block[16] = {0};
+ auto nonce = (uint8_t*)data.data();
+ auto encrypted = (uint8_t*)data.data() + aes_nonce_.size();
+ auto packet = std::make_unique<AudioStreamPacket>();
+ packet->sample_rate = server_sample_rate_;
+ packet->frame_duration = server_frame_duration_;
+ packet->timestamp = timestamp;
+ packet->payload.resize(decrypted_size);
+ int ret = mbedtls_aes_crypt_ctr(&aes_ctx_, decrypted_size, &nc_off, nonce, stream_block, encrypted, (uint8_t*)packet->payload.data());
+ if (ret != 0) {
+ ESP_LOGE(TAG, "Failed to decrypt audio data, ret: %d", ret);
+ return;
+ }
+ if (on_incoming_audio_ != nullptr) {
+ on_incoming_audio_(std::move(packet));
+ }
+ remote_sequence_ = sequence;
+ last_incoming_time_ = std::chrono::steady_clock::now();
+ });
+
+ udp_->Connect(udp_server_, udp_port_);
+
+ if (on_audio_channel_opened_ != nullptr) {
+ on_audio_channel_opened_();
+ }
+ return true;
+}
+
+std::string MqttProtocol::GetHelloMessage() {
+ // 鍙戦�� hello 娑堟伅鐢宠 UDP 閫氶亾
+ cJSON* root = cJSON_CreateObject();
+ cJSON_AddStringToObject(root, "type", "hello");
+ cJSON_AddNumberToObject(root, "version", 3);
+ cJSON_AddStringToObject(root, "transport", "udp");
+ cJSON* features = cJSON_CreateObject();
+#if CONFIG_USE_SERVER_AEC
+ cJSON_AddBoolToObject(features, "aec", true);
+#endif
+ cJSON_AddBoolToObject(features, "mcp", true);
+ cJSON_AddItemToObject(root, "features", features);
+ cJSON* audio_params = cJSON_CreateObject();
+ cJSON_AddStringToObject(audio_params, "format", "opus");
+ cJSON_AddNumberToObject(audio_params, "sample_rate", 16000);
+ cJSON_AddNumberToObject(audio_params, "channels", 1);
+ cJSON_AddNumberToObject(audio_params, "frame_duration", OPUS_FRAME_DURATION_MS);
+ cJSON_AddItemToObject(root, "audio_params", audio_params);
+ auto json_str = cJSON_PrintUnformatted(root);
+ std::string message(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(root);
+ return message;
+}
+
+void MqttProtocol::ParseServerHello(const cJSON* root) {
+ auto transport = cJSON_GetObjectItem(root, "transport");
+ if (transport == nullptr || strcmp(transport->valuestring, "udp") != 0) {
+ ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring);
+ return;
+ }
+
+ auto session_id = cJSON_GetObjectItem(root, "session_id");
+ if (cJSON_IsString(session_id)) {
+ session_id_ = session_id->valuestring;
+ ESP_LOGI(TAG, "Session ID: %s", session_id_.c_str());
+ }
+
+ // Get sample rate from hello message
+ auto audio_params = cJSON_GetObjectItem(root, "audio_params");
+ if (cJSON_IsObject(audio_params)) {
+ auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate");
+ if (cJSON_IsNumber(sample_rate)) {
+ server_sample_rate_ = sample_rate->valueint;
+ }
+ auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration");
+ if (cJSON_IsNumber(frame_duration)) {
+ server_frame_duration_ = frame_duration->valueint;
+ }
+ }
+
+ auto udp = cJSON_GetObjectItem(root, "udp");
+ if (!cJSON_IsObject(udp)) {
+ ESP_LOGE(TAG, "UDP is not specified");
+ return;
+ }
+ udp_server_ = cJSON_GetObjectItem(udp, "server")->valuestring;
+ udp_port_ = cJSON_GetObjectItem(udp, "port")->valueint;
+ auto key = cJSON_GetObjectItem(udp, "key")->valuestring;
+ auto nonce = cJSON_GetObjectItem(udp, "nonce")->valuestring;
+
+ // auto encryption = cJSON_GetObjectItem(udp, "encryption")->valuestring;
+ // ESP_LOGI(TAG, "UDP server: %s, port: %d, encryption: %s", udp_server_.c_str(), udp_port_, encryption);
+ aes_nonce_ = DecodeHexString(nonce);
+ mbedtls_aes_init(&aes_ctx_);
+ mbedtls_aes_setkey_enc(&aes_ctx_, (const unsigned char*)DecodeHexString(key).c_str(), 128);
+ local_sequence_ = 0;
+ remote_sequence_ = 0;
+ xEventGroupSetBits(event_group_handle_, MQTT_PROTOCOL_SERVER_HELLO_EVENT);
+}
+
+static const char hex_chars[] = "0123456789ABCDEF";
+// 杈呭姪鍑芥暟锛屽皢鍗曚釜鍗佸叚杩涘埗瀛楃杞崲涓哄搴旂殑鏁板��
+static inline uint8_t CharToHex(char c) {
+ if (c >= '0' && c <= '9') return c - '0';
+ if (c >= 'A' && c <= 'F') return c - 'A' + 10;
+ if (c >= 'a' && c <= 'f') return c - 'a' + 10;
+ return 0; // 瀵逛簬鏃犳晥杈撳叆锛岃繑鍥�0
+}
+
+std::string MqttProtocol::DecodeHexString(const std::string& hex_string) {
+ std::string decoded;
+ decoded.reserve(hex_string.size() / 2);
+ for (size_t i = 0; i < hex_string.size(); i += 2) {
+ char byte = (CharToHex(hex_string[i]) << 4) | CharToHex(hex_string[i + 1]);
+ decoded.push_back(byte);
+ }
+ return decoded;
+}
+
+bool MqttProtocol::IsAudioChannelOpened() const {
+ return udp_ != nullptr && !error_occurred_ && !IsTimeout();
+}
diff --git a/xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.h b/xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.h
new file mode 100644
index 0000000..90963db
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/protocols/mqtt_protocol.h
@@ -0,0 +1,65 @@
+#ifndef MQTT_PROTOCOL_H
+#define MQTT_PROTOCOL_H
+
+
+#include "protocol.h"
+#include <mqtt.h>
+#include <udp.h>
+#include <cJSON.h>
+#include <mbedtls/aes.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+#include <esp_timer.h>
+
+#include <functional>
+#include <string>
+#include <map>
+#include <mutex>
+#include <memory>
+#include <atomic>
+
+#define MQTT_PING_INTERVAL_SECONDS 90
+#define MQTT_RECONNECT_INTERVAL_MS 60000
+
+#define MQTT_PROTOCOL_SERVER_HELLO_EVENT (1 << 0)
+
+class MqttProtocol : public Protocol {
+public:
+ MqttProtocol();
+ ~MqttProtocol();
+
+ bool Start() override;
+ bool SendAudio(std::unique_ptr<AudioStreamPacket> packet) override;
+ bool OpenAudioChannel() override;
+ void CloseAudioChannel(bool send_goodbye = true) override;
+ bool IsAudioChannelOpened() const override;
+
+private:
+ // Alive flag for safe scheduled callbacks - set to false in destructor
+ std::shared_ptr<std::atomic<bool>> alive_ = std::make_shared<std::atomic<bool>>(true);
+
+ EventGroupHandle_t event_group_handle_;
+
+ std::string publish_topic_;
+
+ std::mutex channel_mutex_;
+ std::unique_ptr<Mqtt> mqtt_;
+ std::unique_ptr<Udp> udp_;
+ mbedtls_aes_context aes_ctx_;
+ std::string aes_nonce_;
+ std::string udp_server_;
+ int udp_port_;
+ uint32_t local_sequence_;
+ uint32_t remote_sequence_;
+ esp_timer_handle_t reconnect_timer_;
+
+ bool StartMqttClient(bool report_error=false);
+ void ParseServerHello(const cJSON* root);
+ std::string DecodeHexString(const std::string& hex_string);
+
+ bool SendText(const std::string& text) override;
+ std::string GetHelloMessage();
+};
+
+
+#endif // MQTT_PROTOCOL_H
diff --git a/xiaozhi-esp32-2.2.6/main/protocols/protocol.cc b/xiaozhi-esp32-2.2.6/main/protocols/protocol.cc
new file mode 100644
index 0000000..470cc91
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/protocols/protocol.cc
@@ -0,0 +1,90 @@
+#include "protocol.h"
+
+#include <esp_log.h>
+
+#define TAG "Protocol"
+
+void Protocol::OnIncomingJson(std::function<void(const cJSON* root)> callback) {
+ on_incoming_json_ = callback;
+}
+
+void Protocol::OnIncomingAudio(std::function<void(std::unique_ptr<AudioStreamPacket> packet)> callback) {
+ on_incoming_audio_ = callback;
+}
+
+void Protocol::OnAudioChannelOpened(std::function<void()> callback) {
+ on_audio_channel_opened_ = callback;
+}
+
+void Protocol::OnAudioChannelClosed(std::function<void()> callback) {
+ on_audio_channel_closed_ = callback;
+}
+
+void Protocol::OnNetworkError(std::function<void(const std::string& message)> callback) {
+ on_network_error_ = callback;
+}
+
+void Protocol::OnConnected(std::function<void()> callback) {
+ on_connected_ = callback;
+}
+
+void Protocol::OnDisconnected(std::function<void()> callback) {
+ on_disconnected_ = callback;
+}
+
+void Protocol::SetError(const std::string& message) {
+ error_occurred_ = true;
+ if (on_network_error_ != nullptr) {
+ on_network_error_(message);
+ }
+}
+
+void Protocol::SendAbortSpeaking(AbortReason reason) {
+ std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"abort\"";
+ if (reason == kAbortReasonWakeWordDetected) {
+ message += ",\"reason\":\"wake_word_detected\"";
+ }
+ message += "}";
+ SendText(message);
+}
+
+void Protocol::SendWakeWordDetected(const std::string& wake_word) {
+ std::string json = "{\"session_id\":\"" + session_id_ +
+ "\",\"type\":\"listen\",\"state\":\"detect\",\"text\":\"" + wake_word + "\"}";
+ SendText(json);
+}
+
+void Protocol::SendStartListening(ListeningMode mode) {
+ std::string message = "{\"session_id\":\"" + session_id_ + "\"";
+ message += ",\"type\":\"listen\",\"state\":\"start\"";
+ if (mode == kListeningModeRealtime) {
+ message += ",\"mode\":\"realtime\"";
+ } else if (mode == kListeningModeAutoStop) {
+ message += ",\"mode\":\"auto\"";
+ } else {
+ message += ",\"mode\":\"manual\"";
+ }
+ message += "}";
+ SendText(message);
+}
+
+void Protocol::SendStopListening() {
+ std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"listen\",\"state\":\"stop\"}";
+ SendText(message);
+}
+
+void Protocol::SendMcpMessage(const std::string& payload) {
+ std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"mcp\",\"payload\":" + payload + "}";
+ SendText(message);
+}
+
+bool Protocol::IsTimeout() const {
+ const int kTimeoutSeconds = 120;
+ auto now = std::chrono::steady_clock::now();
+ auto duration = std::chrono::duration_cast<std::chrono::seconds>(now - last_incoming_time_);
+ bool timeout = duration.count() > kTimeoutSeconds;
+ if (timeout) {
+ ESP_LOGE(TAG, "Channel timeout %ld seconds", (long)duration.count());
+ }
+ return timeout;
+}
diff --git a/xiaozhi-esp32-2.2.6/main/protocols/protocol.h b/xiaozhi-esp32-2.2.6/main/protocols/protocol.h
new file mode 100644
index 0000000..28ef604
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/protocols/protocol.h
@@ -0,0 +1,98 @@
+#ifndef PROTOCOL_H
+#define PROTOCOL_H
+
+#include <cJSON.h>
+#include <string>
+#include <functional>
+#include <chrono>
+#include <vector>
+
+struct AudioStreamPacket {
+ int sample_rate = 0;
+ int frame_duration = 0;
+ uint32_t timestamp = 0;
+ std::vector<uint8_t> payload;
+};
+
+struct BinaryProtocol2 {
+ uint16_t version;
+ uint16_t type; // Message type (0: OPUS, 1: JSON)
+ uint32_t reserved; // Reserved for future use
+ uint32_t timestamp; // Timestamp in milliseconds (used for server-side AEC)
+ uint32_t payload_size; // Payload size in bytes
+ uint8_t payload[]; // Payload data
+} __attribute__((packed));
+
+struct BinaryProtocol3 {
+ uint8_t type;
+ uint8_t reserved;
+ uint16_t payload_size;
+ uint8_t payload[];
+} __attribute__((packed));
+
+enum AbortReason {
+ kAbortReasonNone,
+ kAbortReasonWakeWordDetected
+};
+
+enum ListeningMode {
+ kListeningModeAutoStop,
+ kListeningModeManualStop,
+ kListeningModeRealtime // 闇�瑕� AEC 鏀寔
+};
+
+class Protocol {
+public:
+ virtual ~Protocol() = default;
+
+ inline int server_sample_rate() const {
+ return server_sample_rate_;
+ }
+ inline int server_frame_duration() const {
+ return server_frame_duration_;
+ }
+ inline const std::string& session_id() const {
+ return session_id_;
+ }
+
+ void OnIncomingAudio(std::function<void(std::unique_ptr<AudioStreamPacket> packet)> callback);
+ void OnIncomingJson(std::function<void(const cJSON* root)> callback);
+ void OnAudioChannelOpened(std::function<void()> callback);
+ void OnAudioChannelClosed(std::function<void()> callback);
+ void OnNetworkError(std::function<void(const std::string& message)> callback);
+ void OnConnected(std::function<void()> callback);
+ void OnDisconnected(std::function<void()> callback);
+
+ virtual bool Start() = 0;
+ virtual bool OpenAudioChannel() = 0;
+ virtual void CloseAudioChannel(bool send_goodbye = true) = 0;
+ virtual bool IsAudioChannelOpened() const = 0;
+ virtual bool SendAudio(std::unique_ptr<AudioStreamPacket> packet) = 0;
+ virtual void SendWakeWordDetected(const std::string& wake_word);
+ virtual void SendStartListening(ListeningMode mode);
+ virtual void SendStopListening();
+ virtual void SendAbortSpeaking(AbortReason reason);
+ virtual void SendMcpMessage(const std::string& message);
+
+protected:
+ std::function<void(const cJSON* root)> on_incoming_json_;
+ std::function<void(std::unique_ptr<AudioStreamPacket> packet)> on_incoming_audio_;
+ std::function<void()> on_audio_channel_opened_;
+ std::function<void()> on_audio_channel_closed_;
+ std::function<void(const std::string& message)> on_network_error_;
+ std::function<void()> on_connected_;
+ std::function<void()> on_disconnected_;
+
+ int server_sample_rate_ = 24000;
+ int server_frame_duration_ = 60;
+ bool error_occurred_ = false;
+ std::string session_id_;
+ std::chrono::time_point<std::chrono::steady_clock> last_incoming_time_;
+
+ virtual bool SendText(const std::string& text) = 0;
+ virtual void SetError(const std::string& message);
+ virtual bool IsTimeout() const;
+};
+
+#endif // PROTOCOL_H
+
diff --git a/xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.cc b/xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.cc
new file mode 100644
index 0000000..4b74a5e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.cc
@@ -0,0 +1,254 @@
+#include "websocket_protocol.h"
+#include "board.h"
+#include "system_info.h"
+#include "application.h"
+#include "settings.h"
+
+#include <cstring>
+#include <cJSON.h>
+#include <esp_log.h>
+#include <arpa/inet.h>
+#include "assets/lang_config.h"
+
+#define TAG "WS"
+
+WebsocketProtocol::WebsocketProtocol() {
+ event_group_handle_ = xEventGroupCreate();
+}
+
+WebsocketProtocol::~WebsocketProtocol() {
+ vEventGroupDelete(event_group_handle_);
+}
+
+bool WebsocketProtocol::Start() {
+ // Only connect to server when audio channel is needed
+ return true;
+}
+
+bool WebsocketProtocol::SendAudio(std::unique_ptr<AudioStreamPacket> packet) {
+ if (websocket_ == nullptr || !websocket_->IsConnected()) {
+ return false;
+ }
+
+ if (version_ == 2) {
+ std::string serialized;
+ serialized.resize(sizeof(BinaryProtocol2) + packet->payload.size());
+ auto bp2 = (BinaryProtocol2*)serialized.data();
+ bp2->version = htons(version_);
+ bp2->type = 0;
+ bp2->reserved = 0;
+ bp2->timestamp = htonl(packet->timestamp);
+ bp2->payload_size = htonl(packet->payload.size());
+ memcpy(bp2->payload, packet->payload.data(), packet->payload.size());
+
+ return websocket_->Send(serialized.data(), serialized.size(), true);
+ } else if (version_ == 3) {
+ std::string serialized;
+ serialized.resize(sizeof(BinaryProtocol3) + packet->payload.size());
+ auto bp3 = (BinaryProtocol3*)serialized.data();
+ bp3->type = 0;
+ bp3->reserved = 0;
+ bp3->payload_size = htons(packet->payload.size());
+ memcpy(bp3->payload, packet->payload.data(), packet->payload.size());
+
+ return websocket_->Send(serialized.data(), serialized.size(), true);
+ } else {
+ return websocket_->Send(packet->payload.data(), packet->payload.size(), true);
+ }
+}
+
+bool WebsocketProtocol::SendText(const std::string& text) {
+ if (websocket_ == nullptr || !websocket_->IsConnected()) {
+ return false;
+ }
+
+ if (!websocket_->Send(text)) {
+ ESP_LOGE(TAG, "Failed to send text: %s", text.c_str());
+ SetError(Lang::Strings::SERVER_ERROR);
+ return false;
+ }
+
+ return true;
+}
+
+bool WebsocketProtocol::IsAudioChannelOpened() const {
+ return websocket_ != nullptr && websocket_->IsConnected() && !error_occurred_ && !IsTimeout();
+}
+
+void WebsocketProtocol::CloseAudioChannel(bool send_goodbye) {
+ (void)send_goodbye; // Websocket doesn't need to send goodbye message
+ websocket_.reset();
+}
+
+bool WebsocketProtocol::OpenAudioChannel() {
+ Settings settings("websocket", false);
+ std::string url = settings.GetString("url");
+ std::string token = settings.GetString("token");
+ int version = settings.GetInt("version");
+ if (version != 0) {
+ version_ = version;
+ }
+
+ error_occurred_ = false;
+
+ auto network = Board::GetInstance().GetNetwork();
+ websocket_ = network->CreateWebSocket(1);
+ if (websocket_ == nullptr) {
+ ESP_LOGE(TAG, "Failed to create websocket");
+ return false;
+ }
+
+ if (!token.empty()) {
+ // If token not has a space, add "Bearer " prefix
+ if (token.find(" ") == std::string::npos) {
+ token = "Bearer " + token;
+ }
+ websocket_->SetHeader("Authorization", token.c_str());
+ }
+ websocket_->SetHeader("Protocol-Version", std::to_string(version_).c_str());
+ websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str());
+ websocket_->SetHeader("Client-Id", Board::GetInstance().GetUuid().c_str());
+
+ websocket_->OnData([this](const char* data, size_t len, bool binary) {
+ if (binary) {
+ if (on_incoming_audio_ != nullptr) {
+ if (version_ == 2) {
+ BinaryProtocol2* bp2 = (BinaryProtocol2*)data;
+ bp2->version = ntohs(bp2->version);
+ bp2->type = ntohs(bp2->type);
+ bp2->timestamp = ntohl(bp2->timestamp);
+ bp2->payload_size = ntohl(bp2->payload_size);
+ auto payload = (uint8_t*)bp2->payload;
+ on_incoming_audio_(std::make_unique<AudioStreamPacket>(AudioStreamPacket{
+ .sample_rate = server_sample_rate_,
+ .frame_duration = server_frame_duration_,
+ .timestamp = bp2->timestamp,
+ .payload = std::vector<uint8_t>(payload, payload + bp2->payload_size)
+ }));
+ } else if (version_ == 3) {
+ BinaryProtocol3* bp3 = (BinaryProtocol3*)data;
+ bp3->type = bp3->type;
+ bp3->payload_size = ntohs(bp3->payload_size);
+ auto payload = (uint8_t*)bp3->payload;
+ on_incoming_audio_(std::make_unique<AudioStreamPacket>(AudioStreamPacket{
+ .sample_rate = server_sample_rate_,
+ .frame_duration = server_frame_duration_,
+ .timestamp = 0,
+ .payload = std::vector<uint8_t>(payload, payload + bp3->payload_size)
+ }));
+ } else {
+ on_incoming_audio_(std::make_unique<AudioStreamPacket>(AudioStreamPacket{
+ .sample_rate = server_sample_rate_,
+ .frame_duration = server_frame_duration_,
+ .timestamp = 0,
+ .payload = std::vector<uint8_t>((uint8_t*)data, (uint8_t*)data + len)
+ }));
+ }
+ }
+ } else {
+ // Parse JSON data
+ auto root = cJSON_ParseWithLength(data, len);
+ auto type = cJSON_GetObjectItem(root, "type");
+ if (cJSON_IsString(type)) {
+ if (strcmp(type->valuestring, "hello") == 0) {
+ ParseServerHello(root);
+ } else {
+ if (on_incoming_json_ != nullptr) {
+ on_incoming_json_(root);
+ }
+ }
+ } else {
+ ESP_LOGE(TAG, "Missing message type, data: %s", std::string(data, len).c_str());
+ }
+ cJSON_Delete(root);
+ }
+ last_incoming_time_ = std::chrono::steady_clock::now();
+ });
+
+ websocket_->OnDisconnected([this]() {
+ ESP_LOGI(TAG, "Websocket disconnected");
+ if (on_audio_channel_closed_ != nullptr) {
+ on_audio_channel_closed_();
+ }
+ });
+
+ ESP_LOGI(TAG, "Connecting to websocket server: %s with version: %d", url.c_str(), version_);
+ if (!websocket_->Connect(url.c_str())) {
+ ESP_LOGE(TAG, "Failed to connect to websocket server, code=%d", websocket_->GetLastError());
+ SetError(Lang::Strings::SERVER_NOT_CONNECTED);
+ return false;
+ }
+
+ // Send hello message to describe the client
+ auto message = GetHelloMessage();
+ if (!SendText(message)) {
+ return false;
+ }
+
+ // Wait for server hello
+ EventBits_t bits = xEventGroupWaitBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000));
+ if (!(bits & WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT)) {
+ ESP_LOGE(TAG, "Failed to receive server hello");
+ SetError(Lang::Strings::SERVER_TIMEOUT);
+ return false;
+ }
+
+ if (on_audio_channel_opened_ != nullptr) {
+ on_audio_channel_opened_();
+ }
+
+ return true;
+}
+
+std::string WebsocketProtocol::GetHelloMessage() {
+ // keys: message type, version, audio_params (format, sample_rate, channels)
+ cJSON* root = cJSON_CreateObject();
+ cJSON_AddStringToObject(root, "type", "hello");
+ cJSON_AddNumberToObject(root, "version", version_);
+ cJSON* features = cJSON_CreateObject();
+#if CONFIG_USE_SERVER_AEC
+ cJSON_AddBoolToObject(features, "aec", true);
+#endif
+ cJSON_AddBoolToObject(features, "mcp", true);
+ cJSON_AddItemToObject(root, "features", features);
+ cJSON_AddStringToObject(root, "transport", "websocket");
+ cJSON* audio_params = cJSON_CreateObject();
+ cJSON_AddStringToObject(audio_params, "format", "opus");
+ cJSON_AddNumberToObject(audio_params, "sample_rate", 16000);
+ cJSON_AddNumberToObject(audio_params, "channels", 1);
+ cJSON_AddNumberToObject(audio_params, "frame_duration", OPUS_FRAME_DURATION_MS);
+ cJSON_AddItemToObject(root, "audio_params", audio_params);
+ auto json_str = cJSON_PrintUnformatted(root);
+ std::string message(json_str);
+ cJSON_free(json_str);
+ cJSON_Delete(root);
+ return message;
+}
+
+void WebsocketProtocol::ParseServerHello(const cJSON* root) {
+ auto transport = cJSON_GetObjectItem(root, "transport");
+ if (transport == nullptr || strcmp(transport->valuestring, "websocket") != 0) {
+ ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring);
+ return;
+ }
+
+ auto session_id = cJSON_GetObjectItem(root, "session_id");
+ if (cJSON_IsString(session_id)) {
+ session_id_ = session_id->valuestring;
+ ESP_LOGI(TAG, "Session ID: %s", session_id_.c_str());
+ }
+
+ auto audio_params = cJSON_GetObjectItem(root, "audio_params");
+ if (cJSON_IsObject(audio_params)) {
+ auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate");
+ if (cJSON_IsNumber(sample_rate)) {
+ server_sample_rate_ = sample_rate->valueint;
+ }
+ auto frame_duration = cJSON_GetObjectItem(audio_params, "frame_duration");
+ if (cJSON_IsNumber(frame_duration)) {
+ server_frame_duration_ = frame_duration->valueint;
+ }
+ }
+
+ xEventGroupSetBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.h b/xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.h
new file mode 100644
index 0000000..48bbc00
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/protocols/websocket_protocol.h
@@ -0,0 +1,34 @@
+#ifndef _WEBSOCKET_PROTOCOL_H_
+#define _WEBSOCKET_PROTOCOL_H_
+
+
+#include "protocol.h"
+
+#include <web_socket.h>
+#include <freertos/FreeRTOS.h>
+#include <freertos/event_groups.h>
+
+#define WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT (1 << 0)
+
+class WebsocketProtocol : public Protocol {
+public:
+ WebsocketProtocol();
+ ~WebsocketProtocol();
+
+ bool Start() override;
+ bool SendAudio(std::unique_ptr<AudioStreamPacket> packet) override;
+ bool OpenAudioChannel() override;
+ void CloseAudioChannel(bool send_goodbye = true) override;
+ bool IsAudioChannelOpened() const override;
+
+private:
+ EventGroupHandle_t event_group_handle_;
+ std::unique_ptr<WebSocket> websocket_;
+ int version_ = 1;
+
+ void ParseServerHello(const cJSON* root);
+ bool SendText(const std::string& text) override;
+ std::string GetHelloMessage();
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/settings.cc b/xiaozhi-esp32-2.2.6/main/settings.cc
new file mode 100644
index 0000000..3b40622
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/settings.cc
@@ -0,0 +1,108 @@
+#include "settings.h"
+
+#include <esp_log.h>
+#include <nvs_flash.h>
+
+#define TAG "Settings"
+
+Settings::Settings(const std::string& ns, bool read_write) : ns_(ns), read_write_(read_write) {
+ nvs_open(ns.c_str(), read_write_ ? NVS_READWRITE : NVS_READONLY, &nvs_handle_);
+}
+
+Settings::~Settings() {
+ if (nvs_handle_ != 0) {
+ if (read_write_ && dirty_) {
+ ESP_ERROR_CHECK(nvs_commit(nvs_handle_));
+ }
+ nvs_close(nvs_handle_);
+ }
+}
+
+std::string Settings::GetString(const std::string& key, const std::string& default_value) {
+ if (nvs_handle_ == 0) {
+ return default_value;
+ }
+
+ size_t length = 0;
+ if (nvs_get_str(nvs_handle_, key.c_str(), nullptr, &length) != ESP_OK) {
+ return default_value;
+ }
+
+ std::string value;
+ value.resize(length);
+ ESP_ERROR_CHECK(nvs_get_str(nvs_handle_, key.c_str(), value.data(), &length));
+ while (!value.empty() && value.back() == '\0') {
+ value.pop_back();
+ }
+ return value;
+}
+
+void Settings::SetString(const std::string& key, const std::string& value) {
+ if (read_write_) {
+ ESP_ERROR_CHECK(nvs_set_str(nvs_handle_, key.c_str(), value.c_str()));
+ dirty_ = true;
+ } else {
+ ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
+ }
+}
+
+int32_t Settings::GetInt(const std::string& key, int32_t default_value) {
+ if (nvs_handle_ == 0) {
+ return default_value;
+ }
+
+ int32_t value;
+ if (nvs_get_i32(nvs_handle_, key.c_str(), &value) != ESP_OK) {
+ return default_value;
+ }
+ return value;
+}
+
+void Settings::SetInt(const std::string& key, int32_t value) {
+ if (read_write_) {
+ ESP_ERROR_CHECK(nvs_set_i32(nvs_handle_, key.c_str(), value));
+ dirty_ = true;
+ } else {
+ ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
+ }
+}
+
+bool Settings::GetBool(const std::string& key, bool default_value) {
+ if (nvs_handle_ == 0) {
+ return default_value;
+ }
+
+ uint8_t value;
+ if (nvs_get_u8(nvs_handle_, key.c_str(), &value) != ESP_OK) {
+ return default_value;
+ }
+ return value != 0;
+}
+
+void Settings::SetBool(const std::string& key, bool value) {
+ if (read_write_) {
+ ESP_ERROR_CHECK(nvs_set_u8(nvs_handle_, key.c_str(), value ? 1 : 0));
+ dirty_ = true;
+ } else {
+ ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
+ }
+}
+
+void Settings::EraseKey(const std::string& key) {
+ if (read_write_) {
+ auto ret = nvs_erase_key(nvs_handle_, key.c_str());
+ if (ret != ESP_ERR_NVS_NOT_FOUND) {
+ ESP_ERROR_CHECK(ret);
+ }
+ } else {
+ ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
+ }
+}
+
+void Settings::EraseAll() {
+ if (read_write_) {
+ ESP_ERROR_CHECK(nvs_erase_all(nvs_handle_));
+ } else {
+ ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str());
+ }
+}
diff --git a/xiaozhi-esp32-2.2.6/main/settings.h b/xiaozhi-esp32-2.2.6/main/settings.h
new file mode 100644
index 0000000..7eb596e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/settings.h
@@ -0,0 +1,28 @@
+#ifndef SETTINGS_H
+#define SETTINGS_H
+
+#include <string>
+#include <nvs_flash.h>
+
+class Settings {
+public:
+ Settings(const std::string& ns, bool read_write = false);
+ ~Settings();
+
+ std::string GetString(const std::string& key, const std::string& default_value = "");
+ void SetString(const std::string& key, const std::string& value);
+ int32_t GetInt(const std::string& key, int32_t default_value = 0);
+ void SetInt(const std::string& key, int32_t value);
+ bool GetBool(const std::string& key, bool default_value = false);
+ void SetBool(const std::string& key, bool value);
+ void EraseKey(const std::string& key);
+ void EraseAll();
+
+private:
+ std::string ns_;
+ nvs_handle_t nvs_handle_ = 0;
+ bool read_write_ = false;
+ bool dirty_ = false;
+};
+
+#endif
diff --git a/xiaozhi-esp32-2.2.6/main/system_info.cc b/xiaozhi-esp32-2.2.6/main/system_info.cc
new file mode 100644
index 0000000..3c011d1
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/system_info.cc
@@ -0,0 +1,156 @@
+#include "system_info.h"
+
+#include <freertos/task.h>
+#include <esp_log.h>
+#include <esp_flash.h>
+#include <esp_mac.h>
+#include <esp_system.h>
+#include <esp_partition.h>
+#include <esp_app_desc.h>
+#include <esp_ota_ops.h>
+#include <esp_pm.h>
+#if CONFIG_IDF_TARGET_ESP32P4
+#include "esp_wifi_remote.h"
+#endif
+
+#define TAG "SystemInfo"
+
+size_t SystemInfo::GetFlashSize() {
+ uint32_t flash_size;
+ if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) {
+ ESP_LOGE(TAG, "Failed to get flash size");
+ return 0;
+ }
+ return (size_t)flash_size;
+}
+
+size_t SystemInfo::GetMinimumFreeHeapSize() {
+ return esp_get_minimum_free_heap_size();
+}
+
+size_t SystemInfo::GetFreeHeapSize() {
+ return esp_get_free_heap_size();
+}
+
+std::string SystemInfo::GetMacAddress() {
+ uint8_t mac[6];
+#if CONFIG_IDF_TARGET_ESP32P4
+ esp_wifi_get_mac(WIFI_IF_STA, mac);
+#else
+ esp_read_mac(mac, ESP_MAC_WIFI_STA);
+#endif
+ char mac_str[18];
+ snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
+ return std::string(mac_str);
+}
+
+std::string SystemInfo::GetChipModelName() {
+ return std::string(CONFIG_IDF_TARGET);
+}
+
+std::string SystemInfo::GetUserAgent() {
+ auto app_desc = esp_app_get_description();
+ auto user_agent = std::string(BOARD_NAME "/") + app_desc->version;
+ return user_agent;
+}
+
+esp_err_t SystemInfo::PrintTaskCpuUsage(TickType_t xTicksToWait) {
+ #define ARRAY_SIZE_OFFSET 5
+ TaskStatus_t *start_array = NULL, *end_array = NULL;
+ UBaseType_t start_array_size, end_array_size;
+ configRUN_TIME_COUNTER_TYPE start_run_time, end_run_time;
+ esp_err_t ret;
+ uint32_t total_elapsed_time;
+
+ //Allocate array to store current task states
+ start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
+ start_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * start_array_size);
+ if (start_array == NULL) {
+ ret = ESP_ERR_NO_MEM;
+ goto exit;
+ }
+ //Get current task states
+ start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time);
+ if (start_array_size == 0) {
+ ret = ESP_ERR_INVALID_SIZE;
+ goto exit;
+ }
+
+ vTaskDelay(xTicksToWait);
+
+ //Allocate array to store tasks states post delay
+ end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET;
+ end_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * end_array_size);
+ if (end_array == NULL) {
+ ret = ESP_ERR_NO_MEM;
+ goto exit;
+ }
+ //Get post delay task states
+ end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time);
+ if (end_array_size == 0) {
+ ret = ESP_ERR_INVALID_SIZE;
+ goto exit;
+ }
+
+ //Calculate total_elapsed_time in units of run time stats clock period.
+ total_elapsed_time = (end_run_time - start_run_time);
+ if (total_elapsed_time == 0) {
+ ret = ESP_ERR_INVALID_STATE;
+ goto exit;
+ }
+
+ printf("| Task | Run Time | Percentage\n");
+ //Match each task in start_array to those in the end_array
+ for (int i = 0; i < start_array_size; i++) {
+ int k = -1;
+ for (int j = 0; j < end_array_size; j++) {
+ if (start_array[i].xHandle == end_array[j].xHandle) {
+ k = j;
+ //Mark that task have been matched by overwriting their handles
+ start_array[i].xHandle = NULL;
+ end_array[j].xHandle = NULL;
+ break;
+ }
+ }
+ //Check if matching task found
+ if (k >= 0) {
+ uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter;
+ uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CONFIG_FREERTOS_NUMBER_OF_CORES);
+ printf("| %-16s | %8lu | %4lu%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time);
+ }
+ }
+
+ //Print unmatched tasks
+ for (int i = 0; i < start_array_size; i++) {
+ if (start_array[i].xHandle != NULL) {
+ printf("| %s | Deleted\n", start_array[i].pcTaskName);
+ }
+ }
+ for (int i = 0; i < end_array_size; i++) {
+ if (end_array[i].xHandle != NULL) {
+ printf("| %s | Created\n", end_array[i].pcTaskName);
+ }
+ }
+ ret = ESP_OK;
+
+exit: //Common return path
+ free(start_array);
+ free(end_array);
+ return ret;
+}
+
+void SystemInfo::PrintTaskList() {
+ char buffer[1000];
+ vTaskList(buffer);
+ ESP_LOGI(TAG, "Task list: \n%s", buffer);
+}
+
+void SystemInfo::PrintHeapStats() {
+ int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL);
+ int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL);
+ ESP_LOGI(TAG, "free sram: %u minimal sram: %u", free_sram, min_free_sram);
+}
+
+void SystemInfo::PrintPmLocks() {
+ esp_pm_dump_locks(stdout);
+}
diff --git a/xiaozhi-esp32-2.2.6/main/system_info.h b/xiaozhi-esp32-2.2.6/main/system_info.h
new file mode 100644
index 0000000..1c0d410
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/main/system_info.h
@@ -0,0 +1,23 @@
+#ifndef _SYSTEM_INFO_H_
+#define _SYSTEM_INFO_H_
+
+#include <string>
+
+#include <esp_err.h>
+#include <freertos/FreeRTOS.h>
+
+class SystemInfo {
+public:
+ static size_t GetFlashSize();
+ static size_t GetMinimumFreeHeapSize();
+ static size_t GetFreeHeapSize();
+ static std::string GetMacAddress();
+ static std::string GetChipModelName();
+ static std::string GetUserAgent();
+ static esp_err_t PrintTaskCpuUsage(TickType_t xTicksToWait);
+ static void PrintTaskList();
+ static void PrintHeapStats();
+ static void PrintPmLocks();
+};
+
+#endif // _SYSTEM_INFO_H_
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/16m.csv b/xiaozhi-esp32-2.2.6/partitions/v1/16m.csv
new file mode 100644
index 0000000..bc3a0e8
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/16m.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+model, data, spiffs, 0x10000, 0xF0000,
+ota_0, app, ota_0, 0x100000, 6M,
+ota_1, app, ota_1, 0x700000, 6M,
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/16m_custom_wakeword.csv b/xiaozhi-esp32-2.2.6/partitions/v1/16m_custom_wakeword.csv
new file mode 100644
index 0000000..868294e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/16m_custom_wakeword.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+model, data, spiffs, 0x10000, 0x3f0000,
+ota_0, app, ota_0, 0x400000, 6M,
+ota_1, app, ota_1, 0xa00000, 6M,
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/16m_echoear.csv b/xiaozhi-esp32-2.2.6/partitions/v1/16m_echoear.csv
new file mode 100644
index 0000000..543c92c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/16m_echoear.csv
@@ -0,0 +1,9 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+model, data, spiffs, 0x10000, 0xF0000,
+ota_0, app, ota_0, 0x100000, 5M,
+ota_1, app, ota_1, 0x700000, 5M,
+assets_A, data, spiffs, , 4000K,
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/32m.csv b/xiaozhi-esp32-2.2.6/partitions/v1/32m.csv
new file mode 100644
index 0000000..e95eb22
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/32m.csv
@@ -0,0 +1,10 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvsfactory, data, nvs, , 200K,
+nvs, data, nvs, , 840K,
+otadata, data, ota, , 0x2000,
+phy_init, data, phy, , 0x1000,
+model, data, spiffs, , 0xF0000,
+# According to scripts/versions.py, app partition must be aligned to 1MB
+ota_0, app, ota_0, 0x200000, 12M,
+ota_1, app, ota_1, , 12M,
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/4m.csv b/xiaozhi-esp32-2.2.6/partitions/v1/4m.csv
new file mode 100644
index 0000000..101349f
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/4m.csv
@@ -0,0 +1,7 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+model, data, spiffs, 0x10000, 0xF0000,
+factory, app, factory, 0x100000, 3M,
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/4m_esp-hi.csv b/xiaozhi-esp32-2.2.6/partitions/v1/4m_esp-hi.csv
new file mode 100644
index 0000000..90c9c43
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/4m_esp-hi.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+model, data, spiffs, 0x10000, 0xD0000,
+factory, app, factory, 0xe0000, 2200K,
+assets_A, data, spiffs, , 700K,
diff --git a/xiaozhi-esp32-2.2.6/partitions/v1/8m.csv b/xiaozhi-esp32-2.2.6/partitions/v1/8m.csv
new file mode 100644
index 0000000..1e0e943
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v1/8m.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+model, data, spiffs, 0x10000, 0xF0000,
+ota_0, app, ota_0, 0x100000, 0x380000,
+ota_1, app, ota_1, 0x480000, 0x380000,
diff --git a/xiaozhi-esp32-2.2.6/partitions/v2/16m.csv b/xiaozhi-esp32-2.2.6/partitions/v2/16m.csv
new file mode 100644
index 0000000..8af93a3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v2/16m.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+ota_0, app, ota_0, 0x20000, 0x3f0000,
+ota_1, app, ota_1, , 0x3f0000,
+assets, data, spiffs, 0x800000, 8M
diff --git a/xiaozhi-esp32-2.2.6/partitions/v2/16m_c3.csv b/xiaozhi-esp32-2.2.6/partitions/v2/16m_c3.csv
new file mode 100644
index 0000000..e444023
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v2/16m_c3.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+ota_0, app, ota_0, 0x20000, 0x3f0000,
+ota_1, app, ota_1, , 0x3f0000,
+assets, data, spiffs, 0x800000, 4000K
diff --git a/xiaozhi-esp32-2.2.6/partitions/v2/32m.csv b/xiaozhi-esp32-2.2.6/partitions/v2/32m.csv
new file mode 100644
index 0000000..d829cbb
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v2/32m.csv
@@ -0,0 +1,9 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvsfactory, data, nvs, , 200K,
+nvs, data, nvs, , 840K,
+otadata, data, ota, , 0x2000,
+phy_init, data, phy, , 0x1000,
+ota_0, app, ota_0, 0x200000, 4M,
+ota_1, app, ota_1, 0x600000, 4M,
+assets, data, spiffs, 0xA00000, 16M
diff --git a/xiaozhi-esp32-2.2.6/partitions/v2/4m.csv b/xiaozhi-esp32-2.2.6/partitions/v2/4m.csv
new file mode 100644
index 0000000..7c4272e
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v2/4m.csv
@@ -0,0 +1,7 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+factory, app, factory, 0x10000, 0x2F0000,
+assets, data, spiffs, 0x300000, 0x100000,
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/partitions/v2/8m.csv b/xiaozhi-esp32-2.2.6/partitions/v2/8m.csv
new file mode 100644
index 0000000..bae9f15
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v2/8m.csv
@@ -0,0 +1,8 @@
+# ESP-IDF Partition Table
+# Name, Type, SubType, Offset, Size, Flags
+nvs, data, nvs, 0x9000, 0x4000,
+otadata, data, ota, 0xd000, 0x2000,
+phy_init, data, phy, 0xf000, 0x1000,
+ota_0, app, ota_0, 0x20000, 0x2f0000,
+ota_1, app, ota_1, , 0x2f0000,
+assets, data, spiffs, 0x600000, 2M
diff --git a/xiaozhi-esp32-2.2.6/partitions/v2/README.md b/xiaozhi-esp32-2.2.6/partitions/v2/README.md
new file mode 100644
index 0000000..0b56851
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/partitions/v2/README.md
@@ -0,0 +1,107 @@
+# Version 2 Partition Table
+
+This version introduces significant improvements over v1 by adding an `assets` partition to support network-loadable content and optimizing partition layouts for different flash sizes.
+
+## Key Changes from v1
+
+### Major Improvements
+1. **Added Assets Partition**: New `assets` partition for network-loadable content
+2. **Replaced Model Partition**: The old `model` partition (960KB) is replaced with a larger `assets` partition
+3. **Optimized App Partitions**: Reduced application partition sizes to accommodate assets
+4. **Enhanced Flexibility**: Support for dynamic content updates without reflashing
+
+### Assets Partition Features
+The `assets` partition stores:
+- **Wake word models**: Customizable wake word models that can be loaded from the network
+- **Theme files**: Complete theming system including:
+ - Fonts (text and icon fonts)
+ - Audio effects and sound files
+ - Background images and UI elements
+ - Custom emoji packs
+ - Language configuration files
+- **Dynamic Content**: All content can be updated over-the-air via HTTP downloads
+
+## Partition Layout Comparison
+
+### v1 Layout (16MB)
+- `nvs`: 16KB (non-volatile storage)
+- `otadata`: 8KB (OTA data)
+- `phy_init`: 4KB (PHY initialization data)
+- `model`: 960KB (model storage - fixed content)
+- `ota_0`: 6MB (application partition 0)
+- `ota_1`: 6MB (application partition 1)
+
+### v2 Layout (16MB)
+- `nvs`: 16KB (non-volatile storage)
+- `otadata`: 8KB (OTA data)
+- `phy_init`: 4KB (PHY initialization data)
+- `ota_0`: 4MB (application partition 0)
+- `ota_1`: 4MB (application partition 1)
+- `assets`: 8MB (network-loadable assets)
+
+## Available Configurations
+
+### 8MB Flash Devices (`8m.csv`)
+- `nvs`: 16KB
+- `otadata`: 8KB
+- `phy_init`: 4KB
+- `ota_0`: 3MB
+- `ota_1`: 3MB
+- `assets`: 2MB
+
+### 16MB Flash Devices (`16m.csv`) - Standard
+- `nvs`: 16KB
+- `otadata`: 8KB
+- `phy_init`: 4KB
+- `ota_0`: 4MB
+- `ota_1`: 4MB
+- `assets`: 8MB
+
+### 16MB Flash Devices (`16m_c3.csv`) - ESP32-C3 Optimized
+- `nvs`: 16KB
+- `otadata`: 8KB
+- `phy_init`: 4KB
+- `ota_0`: 4MB
+- `ota_1`: 4MB
+- `assets`: 4MB (4000K - limited by available mmap pages)
+
+### 32MB Flash Devices (`32m.csv`)
+- `nvsfactory`: 200KB
+- `nvs`: 840KB
+- `otadata`: 8KB
+- `phy_init`: 4KB
+- `ota_0`: 4MB
+- `ota_1`: 4MB
+- `assets`: 16MB
+
+## Benefits
+
+1. **Dynamic Content Management**: Users can download and update wake word models, themes, and other assets without reflashing the device
+2. **Reduced App Size**: Application partitions are optimized, allowing more space for dynamic content
+3. **Enhanced Customization**: Support for custom themes, wake words, and language packs enhances user experience
+4. **Network Flexibility**: Assets can be updated independently of the main application firmware
+5. **Better Resource Utilization**: Efficient use of flash memory with configurable asset storage
+6. **OTA Asset Updates**: Assets can be updated over-the-air via HTTP downloads
+
+## Technical Details
+
+- **Partition Type**: Assets partition uses `spiffs` subtype for SPIFFS filesystem compatibility
+- **Memory Mapping**: Assets are memory-mapped for efficient access during runtime
+- **Checksum Validation**: Built-in integrity checking ensures asset data validity
+- **Progressive Download**: Assets can be downloaded progressively with progress tracking
+- **Fallback Support**: Graceful fallback to default assets if network updates fail
+
+## Migration from v1
+
+When upgrading from v1 to v2:
+1. **Backup Important Data**: Ensure any important data in the old `model` partition is backed up
+2. **Flash New Partition Table**: Use the appropriate v2 partition table for your flash size
+3. **Download Assets**: The device will automatically download required assets on first boot
+4. **Verify Functionality**: Ensure all features work correctly with the new partition layout
+
+## Usage Notes
+
+- The `assets` partition size varies by configuration to optimize for different flash sizes
+- ESP32-C3 devices use a smaller assets partition (4MB) due to limited available mmap pages in the system
+- 32MB devices get the largest assets partition (16MB) for maximum content storage
+- All partition tables maintain proper alignment for optimal flash performance
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/Image_Converter/LVGLImage.py b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/LVGLImage.py
new file mode 100644
index 0000000..b2ffbb3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/LVGLImage.py
@@ -0,0 +1,1426 @@
+#!/usr/bin/env python3
+import os
+import logging
+import argparse
+import subprocess
+from os import path
+from enum import Enum
+from typing import List
+from pathlib import Path
+
+try:
+ import png
+except ImportError:
+ raise ImportError("Need pypng package, do `pip3 install pypng`")
+
+try:
+ import lz4.block
+except ImportError:
+ raise ImportError("Need lz4 package, do `pip3 install lz4`")
+
+
+def uint8_t(val) -> bytes:
+ return val.to_bytes(1, byteorder='little')
+
+
+def uint16_t(val) -> bytes:
+ return val.to_bytes(2, byteorder='little')
+
+
+def uint24_t(val) -> bytes:
+ return val.to_bytes(3, byteorder='little')
+
+
+def uint32_t(val) -> bytes:
+ try:
+ return val.to_bytes(4, byteorder='little')
+ except OverflowError:
+ raise ParameterError(f"overflow: {hex(val)}")
+
+
+def color_pre_multiply(r, g, b, a, background):
+ bb = background & 0xff
+ bg = (background >> 8) & 0xff
+ br = (background >> 16) & 0xff
+
+ return ((r * a + (255 - a) * br) >> 8, (g * a + (255 - a) * bg) >> 8,
+ (b * a + (255 - a) * bb) >> 8, a)
+
+
+class Error(Exception):
+
+ def __str__(self):
+ return self.__class__.__name__ + ': ' + ' '.join(self.args)
+
+
+class FormatError(Error):
+ """
+ Problem with input filename format.
+ BIN filename does not conform to standard lvgl bin image format
+ """
+
+
+class ParameterError(Error):
+ """
+ Parameter for LVGL image not correct
+ """
+
+
+class PngQuant:
+ """
+ Compress PNG file to 8bit mode using `pngquant`
+ """
+
+ def __init__(self, ncolors=256, dither=True, exec_path="") -> None:
+ executable = path.join(exec_path, "pngquant")
+ self.cmd = (f"{executable} {'--nofs' if not dither else ''} "
+ f"{ncolors} --force - < ")
+
+ def convert(self, filename) -> bytes:
+ if not os.path.isfile(filename):
+ raise BaseException(f"file not found: {filename}")
+
+ try:
+ compressed = subprocess.check_output(
+ f'{self.cmd} "{str(filename)}"',
+ stderr=subprocess.STDOUT,
+ shell=True)
+ except subprocess.CalledProcessError:
+ raise BaseException(
+ "cannot find pngquant tool, install it via "
+ "`sudo apt install pngquant` for debian "
+ "or `brew install pngquant` for macintosh "
+ "For windows, you may need to download pngquant.exe from "
+ "https://pngquant.org/, and put it in your PATH.")
+
+ return compressed
+
+
+class CompressMethod(Enum):
+ NONE = 0x00
+ RLE = 0x01
+ LZ4 = 0x02
+
+
+class ColorFormat(Enum):
+ UNKNOWN = 0x00
+ RAW = 0x01,
+ RAW_ALPHA = 0x02,
+ L8 = 0x06
+ I1 = 0x07
+ I2 = 0x08
+ I4 = 0x09
+ I8 = 0x0A
+ A1 = 0x0B
+ A2 = 0x0C
+ A4 = 0x0D
+ A8 = 0x0E
+ ARGB8888 = 0x10
+ XRGB8888 = 0x11
+ RGB565 = 0x12
+ ARGB8565 = 0x13
+ RGB565A8 = 0x14
+ RGB888 = 0x0F
+
+ @property
+ def bpp(self) -> int:
+ """
+ Return bit per pixel for this cf
+ """
+ cf_map = {
+ ColorFormat.L8: 8,
+ ColorFormat.I1: 1,
+ ColorFormat.I2: 2,
+ ColorFormat.I4: 4,
+ ColorFormat.I8: 8,
+ ColorFormat.A1: 1,
+ ColorFormat.A2: 2,
+ ColorFormat.A4: 4,
+ ColorFormat.A8: 8,
+ ColorFormat.ARGB8888: 32,
+ ColorFormat.XRGB8888: 32,
+ ColorFormat.RGB565: 16,
+ ColorFormat.RGB565A8: 16, # 16bpp + a8 map
+ ColorFormat.ARGB8565: 24,
+ ColorFormat.RGB888: 24,
+ }
+
+ return cf_map[self] if self in cf_map else 0
+
+ @property
+ def ncolors(self) -> int:
+ """
+ Return number of colors in palette if cf is indexed1/2/4/8.
+ Return zero if cf is not indexed format
+ """
+
+ cf_map = {
+ ColorFormat.I1: 2,
+ ColorFormat.I2: 4,
+ ColorFormat.I4: 16,
+ ColorFormat.I8: 256,
+ }
+ return cf_map.get(self, 0)
+
+ @property
+ def is_indexed(self) -> bool:
+ """
+ Return if cf is indexed color format
+ """
+ return self.ncolors != 0
+
+ @property
+ def is_alpha_only(self) -> bool:
+ return ColorFormat.A1.value <= self.value <= ColorFormat.A8.value
+
+ @property
+ def has_alpha(self) -> bool:
+ return self.is_alpha_only or self.is_indexed or self in (
+ ColorFormat.ARGB8888,
+ ColorFormat.XRGB8888, # const alpha: 0xff
+ ColorFormat.ARGB8565,
+ ColorFormat.RGB565A8)
+
+ @property
+ def is_colormap(self) -> bool:
+ return self in (ColorFormat.ARGB8888, ColorFormat.RGB888,
+ ColorFormat.XRGB8888, ColorFormat.RGB565A8,
+ ColorFormat.ARGB8565, ColorFormat.RGB565)
+
+ @property
+ def is_luma_only(self) -> bool:
+ return self in (ColorFormat.L8, )
+
+
+def bit_extend(value, bpp):
+ """
+ Extend value from bpp to 8 bit with interpolation to reduce rounding error.
+ """
+
+ if value == 0:
+ return 0
+
+ res = value
+ bpp_now = bpp
+ while bpp_now < 8:
+ res |= value << (8 - bpp_now)
+ bpp_now += bpp
+
+ return res
+
+
+def unpack_colors(data: bytes, cf: ColorFormat, w) -> List:
+ """
+ Unpack lvgl 1/2/4/8/16/32 bpp color to png color: alpha map, grey scale,
+ or R,G,B,(A) map
+ """
+ ret = []
+ bpp = cf.bpp
+ if bpp == 8:
+ ret = data
+ elif bpp == 4:
+ if cf == ColorFormat.A4:
+ values = [x * 17 for x in range(16)]
+ else:
+ values = [x for x in range(16)]
+
+ for p in data:
+ for i in range(2):
+ ret.append(values[(p >> (4 - i * 4)) & 0x0f])
+ if len(ret) % w == 0:
+ break
+
+ elif bpp == 2:
+ if cf == ColorFormat.A2:
+ values = [x * 85 for x in range(4)]
+ else: # must be ColorFormat.I2
+ values = [x for x in range(4)]
+ for p in data:
+ for i in range(4):
+ ret.append(values[(p >> (6 - i * 2)) & 0x03])
+ if len(ret) % w == 0:
+ break
+ elif bpp == 1:
+ if cf == ColorFormat.A1:
+ values = [0, 255]
+ else:
+ values = [0, 1]
+ for p in data:
+ for i in range(8):
+ ret.append(values[(p >> (7 - i)) & 0x01])
+ if len(ret) % w == 0:
+ break
+ elif bpp == 16:
+ # This is RGB565
+ pixels = [(data[2 * i + 1] << 8) | data[2 * i]
+ for i in range(len(data) // 2)]
+
+ for p in pixels:
+ ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R
+ ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G
+ ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B
+ elif bpp == 24:
+ if cf == ColorFormat.RGB888:
+ B = data[0::3]
+ G = data[1::3]
+ R = data[2::3]
+ for r, g, b in zip(R, G, B):
+ ret += [r, g, b]
+ elif cf == ColorFormat.RGB565A8:
+ alpha_size = len(data) // 3
+ pixel_alpha = data[-alpha_size:]
+ pixel_data = data[:-alpha_size]
+ pixels = [(pixel_data[2 * i + 1] << 8) | pixel_data[2 * i]
+ for i in range(len(pixel_data) // 2)]
+
+ for a, p in zip(pixel_alpha, pixels):
+ ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R
+ ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G
+ ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B
+ ret.append(a)
+ elif cf == ColorFormat.ARGB8565:
+ L = data[0::3]
+ H = data[1::3]
+ A = data[2::3]
+
+ for h, l, a in zip(H, L, A):
+ p = (h << 8) | (l)
+ ret.append(bit_extend((p >> 11) & 0x1f, 5)) # R
+ ret.append(bit_extend((p >> 5) & 0x3f, 6)) # G
+ ret.append(bit_extend((p >> 0) & 0x1f, 5)) # B
+ ret.append(a) # A
+
+ elif bpp == 32:
+ B = data[0::4]
+ G = data[1::4]
+ R = data[2::4]
+ A = data[3::4]
+ for r, g, b, a in zip(R, G, B, A):
+ ret += [r, g, b, a]
+ else:
+ assert 0
+
+ return ret
+
+
+def write_c_array_file(
+ w: int, h: int,
+ stride: int,
+ cf: ColorFormat,
+ filename: str,
+ premultiplied: bool,
+ compress: CompressMethod,
+ data: bytes):
+ varname = path.basename(filename).split('.')[0]
+ varname = varname.replace("-", "_")
+ varname = varname.replace(".", "_")
+
+ flags = "0"
+ if compress is not CompressMethod.NONE:
+ flags += " | LV_IMAGE_FLAGS_COMPRESSED"
+ if premultiplied:
+ flags += " | LV_IMAGE_FLAGS_PREMULTIPLIED"
+
+ macro = "LV_ATTRIBUTE_" + varname.upper()
+ header = f'''
+#if defined(LV_LVGL_H_INCLUDE_SIMPLE)
+#include "lvgl.h"
+#elif defined(LV_BUILD_TEST)
+#include "../lvgl.h"
+#else
+#include "lvgl/lvgl.h"
+#endif
+
+
+#ifndef LV_ATTRIBUTE_MEM_ALIGN
+#define LV_ATTRIBUTE_MEM_ALIGN
+#endif
+
+#ifndef {macro}
+#define {macro}
+#endif
+
+static const
+LV_ATTRIBUTE_MEM_ALIGN LV_ATTRIBUTE_LARGE_CONST {macro}
+uint8_t {varname}_map[] = {{
+'''
+
+ ending = f'''
+}};
+
+const lv_image_dsc_t {varname} = {{
+ .header.magic = LV_IMAGE_HEADER_MAGIC,
+ .header.cf = LV_COLOR_FORMAT_{cf.name},
+ .header.flags = {flags},
+ .header.w = {w},
+ .header.h = {h},
+ .header.stride = {stride},
+ .data_size = sizeof({varname}_map),
+ .data = {varname}_map,
+}};
+
+'''
+
+ def write_binary(f, data, stride):
+ stride = 16 if stride == 0 else stride
+ for i, v in enumerate(data):
+ if i % stride == 0:
+ f.write("\n ")
+ f.write(f"0x{v:02x},")
+ f.write("\n")
+
+ with open(filename, "w+") as f:
+ f.write(header)
+
+ if compress != CompressMethod.NONE:
+ write_binary(f, data, 16)
+ else:
+ # write palette separately
+ ncolors = cf.ncolors
+ if ncolors:
+ write_binary(f, data[:ncolors * 4], 16)
+
+ write_binary(f, data[ncolors * 4:], stride)
+
+ f.write(ending)
+
+
+class LVGLImageHeader:
+
+ def __init__(self,
+ cf: ColorFormat = ColorFormat.UNKNOWN,
+ w: int = 0,
+ h: int = 0,
+ stride: int = 0,
+ align: int = 1,
+ flags: int = 0):
+ self.cf = cf
+ self.flags = flags
+ self.w = w & 0xffff
+ self.h = h & 0xffff
+ if w > 0xffff or h > 0xffff:
+ raise ParameterError(f"w, h overflow: {w}x{h}")
+ if align < 1:
+ # stride align in bytes must be larger than 1
+ raise ParameterError(f"Invalid stride align: {align}")
+
+ self.stride = self.stride_align(align) if stride == 0 else stride
+
+ def stride_align(self, align: int) -> int:
+ stride = self.stride_default
+ if align == 1:
+ pass
+ elif align > 1:
+ stride = (stride + align - 1) // align
+ stride *= align
+ else:
+ raise ParameterError(f"Invalid stride align: {align}")
+
+ self.stride = stride
+ return stride
+
+ @property
+ def stride_default(self) -> int:
+ return (self.w * self.cf.bpp + 7) // 8
+
+ @property
+ def binary(self) -> bytearray:
+ binary = bytearray()
+ binary += uint8_t(0x19) # magic number for lvgl version 9
+ binary += uint8_t(self.cf.value)
+ binary += uint16_t(self.flags) # 16bits flags
+
+ binary += uint16_t(self.w) # 16bits width
+ binary += uint16_t(self.h) # 16bits height
+ binary += uint16_t(self.stride) # 16bits stride
+
+ binary += uint16_t(0) # 16bits reserved
+ return binary
+
+ def from_binary(self, data: bytes):
+ if len(data) < 12:
+ raise FormatError("invalid header length")
+
+ try:
+ self.cf = ColorFormat(data[1] & 0x1f) # color format
+ except ValueError as exc:
+ raise FormatError(f"invalid color format: {hex(data[0])}") from exc
+ self.w = int.from_bytes(data[4:6], 'little')
+ self.h = int.from_bytes(data[6:8], 'little')
+ self.stride = int.from_bytes(data[8:10], 'little')
+ return self
+
+
+class LVGLCompressData:
+
+ def __init__(self,
+ cf: ColorFormat,
+ method: CompressMethod,
+ raw_data: bytes = b''):
+ self.blk_size = (cf.bpp + 7) // 8
+ self.compress = method
+ self.raw_data = raw_data
+ self.raw_data_len = len(raw_data)
+ self.compressed = self._compress(raw_data)
+
+ def _compress(self, raw_data: bytes) -> bytearray:
+ if self.compress == CompressMethod.NONE:
+ return raw_data
+
+ if self.compress == CompressMethod.RLE:
+ # RLE compression performs on pixel unit, pad data to pixel unit
+ pad = b'\x00' * 0
+ if self.raw_data_len % self.blk_size:
+ pad = b'\x00' * (self.blk_size - self.raw_data_len % self.blk_size)
+ compressed = RLEImage().rle_compress(raw_data + pad, self.blk_size)
+ elif self.compress == CompressMethod.LZ4:
+ compressed = lz4.block.compress(raw_data, store_size=False)
+ else:
+ raise ParameterError(f"Invalid compress method: {self.compress}")
+
+ self.compressed_len = len(compressed)
+
+ bin = bytearray()
+ bin += uint32_t(self.compress.value)
+ bin += uint32_t(self.compressed_len)
+ bin += uint32_t(self.raw_data_len)
+ bin += compressed
+ return bin
+
+
+class LVGLImage:
+
+ def __init__(self,
+ cf: ColorFormat = ColorFormat.UNKNOWN,
+ w: int = 0,
+ h: int = 0,
+ data: bytes = b'') -> None:
+ self.stride = 0 # default no valid stride value
+ self.premultiplied = False
+ self.rgb565_dither = False
+ self.set_data(cf, w, h, data)
+
+ def __repr__(self) -> str:
+ return (f"'LVGL image {self.w}x{self.h}, {self.cf.name}, "
+ f"{'Pre-multiplied, ' if self.premultiplied else ''}"
+ f"stride: {self.stride} "
+ f"(12+{self.data_len})Byte'")
+
+ def adjust_stride(self, stride: int = 0, align: int = 1):
+ """
+ Stride can be set directly, or by stride alignment in bytes
+ """
+ if self.stride == 0:
+ # stride can only be 0, when LVGLImage is created with empty data
+ logging.warning("Cannot adjust stride for empty image")
+ return
+
+ if align >= 1 and stride == 0:
+ # The header with specified stride alignment
+ header = LVGLImageHeader(self.cf, self.w, self.h, align=align)
+ stride = header.stride
+ elif stride > 0:
+ pass
+ else:
+ raise ParameterError(f"Invalid parameter, align:{align},"
+ f" stride:{stride}")
+
+ if self.stride == stride:
+ return # no stride adjustment
+
+ # if current image is empty, no need to do anything
+ if self.data_len == 0:
+ self.stride = 0
+ return
+
+ current = LVGLImageHeader(self.cf, self.w, self.h, stride=self.stride)
+
+ if stride < current.stride_default:
+ raise ParameterError(f"Stride is too small:{stride}, "
+ f"minimal:{current.stride_default}")
+
+ def change_stride(data: bytearray, h, current_stride, new_stride):
+ data_in = data
+ data_out = [] # stride adjusted new data
+ if new_stride < current_stride: # remove padding byte
+ for i in range(h):
+ start = i * current_stride
+ end = start + new_stride
+ data_out.append(data_in[start:end])
+ else: # adding more padding bytes
+ padding = b'\x00' * (new_stride - current_stride)
+ for i in range(h):
+ data_out.append(data_in[i * current_stride:(i + 1) *
+ current_stride])
+ data_out.append(padding)
+ return b''.join(data_out)
+
+ palette_size = self.cf.ncolors * 4
+ data_out = [self.data[:palette_size]]
+ data_out.append(
+ change_stride(self.data[palette_size:], self.h, current.stride,
+ stride))
+
+ # deal with alpha map for RGB565A8
+ if self.cf == ColorFormat.RGB565A8:
+ logging.warning("handle RGB565A8 alpha map")
+ a8_stride = self.stride // 2
+ a8_map = self.data[-a8_stride * self.h:]
+ data_out.append(
+ change_stride(a8_map, self.h, current.stride // 2,
+ stride // 2))
+
+ self.stride = stride
+ self.data = bytearray(b''.join(data_out))
+
+ def premultiply(self):
+ """
+ Pre-multiply image RGB data with alpha, set corresponding image header flags
+ """
+ if self.premultiplied:
+ raise ParameterError("Image already pre-multiplied")
+
+ if not self.cf.has_alpha:
+ raise ParameterError(f"Image has no alpha channel: {self.cf.name}")
+
+ if self.cf.is_indexed:
+
+ def multiply(r, g, b, a):
+ r, g, b = (r * a) >> 8, (g * a) >> 8, (b * a) >> 8
+ return uint8_t(b) + uint8_t(g) + uint8_t(r) + uint8_t(a)
+
+ # process the palette only.
+ palette_size = self.cf.ncolors * 4
+ palette = self.data[:palette_size]
+ palette = [
+ multiply(palette[i], palette[i + 1], palette[i + 2],
+ palette[i + 3]) for i in range(0, len(palette), 4)
+ ]
+ palette = b''.join(palette)
+ self.data = palette + self.data[palette_size:]
+ elif self.cf is ColorFormat.ARGB8888:
+
+ def multiply(b, g, r, a):
+ r, g, b = (r * a) >> 8, (g * a) >> 8, (b * a) >> 8
+ return uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0))
+
+ line_width = self.w * 4
+ for h in range(self.h):
+ offset = h * self.stride
+ map = self.data[offset:offset + self.stride]
+
+ processed = b''.join([
+ multiply(map[i], map[i + 1], map[i + 2], map[i + 3])
+ for i in range(0, line_width, 4)
+ ])
+ self.data[offset:offset + line_width] = processed
+ elif self.cf is ColorFormat.RGB565A8:
+
+ def multiply(data, a):
+ r = (data >> 11) & 0x1f
+ g = (data >> 5) & 0x3f
+ b = (data >> 0) & 0x1f
+
+ r, g, b = (r * a) // 255, (g * a) // 255, (b * a) // 255
+ return uint16_t((r << 11) | (g << 5) | (b << 0))
+
+ line_width = self.w * 2
+ for h in range(self.h):
+ # alpha map offset for this line
+ offset = self.h * self.stride + h * (self.stride // 2)
+ a = self.data[offset:offset + self.stride // 2]
+
+ # RGB map offset
+ offset = h * self.stride
+ rgb = self.data[offset:offset + self.stride]
+
+ processed = b''.join([
+ multiply((rgb[i + 1] << 8) | rgb[i], a[i // 2])
+ for i in range(0, line_width, 2)
+ ])
+ self.data[offset:offset + line_width] = processed
+ elif self.cf is ColorFormat.ARGB8565:
+
+ def multiply(data, a):
+ r = (data >> 11) & 0x1f
+ g = (data >> 5) & 0x3f
+ b = (data >> 0) & 0x1f
+
+ r, g, b = (r * a) // 255, (g * a) // 255, (b * a) // 255
+ return uint24_t((a << 16) | (r << 11) | (g << 5) | (b << 0))
+
+ line_width = self.w * 3
+ for h in range(self.h):
+ offset = h * self.stride
+ map = self.data[offset:offset + self.stride]
+
+ processed = b''.join([
+ multiply((map[i + 1] << 8) | map[i], map[i + 2])
+ for i in range(0, line_width, 3)
+ ])
+ self.data[offset:offset + line_width] = processed
+ else:
+ raise ParameterError(f"Not supported yet: {self.cf.name}")
+
+ self.premultiplied = True
+
+ @property
+ def data_len(self) -> int:
+ """
+ Return data_len in byte of this image, excluding image header
+ """
+
+ # palette is always in ARGB format, 4Byte per color
+ p = self.cf.ncolors * 4 if self.is_indexed and self.w * self.h else 0
+ p += self.stride * self.h
+ if self.cf is ColorFormat.RGB565A8:
+ a8_stride = self.stride // 2
+ p += a8_stride * self.h
+ return p
+
+ @property
+ def header(self) -> bytearray:
+ return LVGLImageHeader(self.cf, self.w, self.h)
+
+ @property
+ def is_indexed(self):
+ return self.cf.is_indexed
+
+ def set_data(self,
+ cf: ColorFormat,
+ w: int,
+ h: int,
+ data: bytes,
+ stride: int = 0):
+ """
+ Directly set LVGL image parameters
+ """
+
+ if w > 0xffff or h > 0xffff:
+ raise ParameterError(f"w, h overflow: {w}x{h}")
+
+ self.cf = cf
+ self.w = w
+ self.h = h
+
+ # if stride is 0, then it's aligned to 1byte by default,
+ # let image header handle it
+ self.stride = LVGLImageHeader(cf, w, h, stride, align=1).stride
+
+ if self.data_len != len(data):
+ raise ParameterError(f"{self} data length error got: {len(data)}, "
+ f"expect: {self.data_len}, {self}")
+
+ self.data = data
+
+ return self
+
+ def from_data(self, data: bytes):
+ header = LVGLImageHeader().from_binary(data)
+ return self.set_data(header.cf, header.w, header.h,
+ data[len(header.binary):], header.stride)
+
+ def from_bin(self, filename: str):
+ """
+ Read from existing bin file and update image parameters
+ """
+
+ if not filename.endswith(".bin"):
+ raise FormatError("filename not ended with '.bin'")
+
+ with open(filename, "rb") as f:
+ data = f.read()
+ return self.from_data(data)
+
+ def _check_ext(self, filename: str, ext):
+ if not filename.lower().endswith(ext):
+ raise FormatError(f"filename not ended with {ext}")
+
+ def _check_dir(self, filename: str):
+ dir = path.dirname(filename)
+ if dir and not path.exists(dir):
+ logging.info(f"mkdir of {dir} for {filename}")
+ os.makedirs(dir)
+
+ def to_bin(self,
+ filename: str,
+ compress: CompressMethod = CompressMethod.NONE):
+ """
+ Write this image to file, filename should be ended with '.bin'
+ """
+ self._check_ext(filename, ".bin")
+ self._check_dir(filename)
+
+ with open(filename, "wb+") as f:
+ bin = bytearray()
+ flags = 0
+ flags |= 0x08 if compress != CompressMethod.NONE else 0
+ flags |= 0x01 if self.premultiplied else 0
+
+ header = LVGLImageHeader(self.cf,
+ self.w,
+ self.h,
+ self.stride,
+ flags=flags)
+ bin += header.binary
+ compressed = LVGLCompressData(self.cf, compress, self.data)
+ bin += compressed.compressed
+
+ f.write(bin)
+
+ return self
+
+ def to_c_array(self,
+ filename: str,
+ compress: CompressMethod = CompressMethod.NONE):
+ self._check_ext(filename, ".c")
+ self._check_dir(filename)
+
+ if compress != CompressMethod.NONE:
+ data = LVGLCompressData(self.cf, compress, self.data).compressed
+ else:
+ data = self.data
+ write_c_array_file(self.w, self.h, self.stride, self.cf, filename,
+ self.premultiplied,
+ compress, data)
+
+ def to_png(self, filename: str):
+ self._check_ext(filename, ".png")
+ self._check_dir(filename)
+
+ old_stride = self.stride
+ self.adjust_stride(align=1)
+ if self.cf.is_indexed:
+ data = self.data
+ # Separate lvgl bin image data to palette and bitmap
+ # The palette is in format of [(RGBA), (RGBA)...].
+ # LVGL palette is in format of B,G,R,A,...
+ palette = [(data[i * 4 + 2], data[i * 4 + 1], data[i * 4 + 0],
+ data[i * 4 + 3]) for i in range(self.cf.ncolors)]
+
+ data = data[self.cf.ncolors * 4:]
+
+ encoder = png.Writer(self.w,
+ self.h,
+ palette=palette,
+ bitdepth=self.cf.bpp)
+ # separate packed data to plain data
+ data = unpack_colors(data, self.cf, self.w)
+ elif self.cf.is_alpha_only:
+ # separate packed data to plain data
+ transparency = unpack_colors(self.data, self.cf, self.w)
+ data = []
+ for a in transparency:
+ data += [0, 0, 0, a]
+ encoder = png.Writer(self.w, self.h, greyscale=False, alpha=True)
+ elif self.cf == ColorFormat.L8:
+ # to grayscale
+ encoder = png.Writer(self.w,
+ self.h,
+ bitdepth=self.cf.bpp,
+ greyscale=True,
+ alpha=False)
+ data = self.data
+ elif self.cf.is_colormap:
+ encoder = png.Writer(self.w,
+ self.h,
+ alpha=self.cf.has_alpha,
+ greyscale=False)
+ data = unpack_colors(self.data, self.cf, self.w)
+ else:
+ logging.warning(f"missing logic: {self.cf.name}")
+ return
+
+ with open(filename, "wb") as f:
+ encoder.write_array(f, data)
+
+ self.adjust_stride(stride=old_stride)
+
+ def from_png(self,
+ filename: str,
+ cf: ColorFormat = None,
+ background: int = 0x00_00_00,
+ rgb565_dither=False):
+ """
+ Create lvgl image from png file.
+ If cf is none, used I1/2/4/8 based on palette size
+ """
+
+ self.background = background
+ self.rgb565_dither = rgb565_dither
+
+ if cf is None: # guess cf from filename
+ # split filename string and match with ColorFormat to check
+ # which cf to use
+ names = str(path.basename(filename)).split(".")
+ for c in names[1:-1]:
+ if c in ColorFormat.__members__:
+ cf = ColorFormat[c]
+ break
+
+ if cf is None or cf.is_indexed: # palette mode
+ self._png_to_indexed(cf, filename)
+ elif cf.is_alpha_only:
+ self._png_to_alpha_only(cf, filename)
+ elif cf.is_luma_only:
+ self._png_to_luma_only(cf, filename)
+ elif cf.is_colormap:
+ self._png_to_colormap(cf, filename)
+ else:
+ logging.warning(f"missing logic: {cf.name}")
+
+ logging.info(f"from png: {filename}, cf: {self.cf.name}")
+ return self
+
+ def _png_to_indexed(self, cf: ColorFormat, filename: str):
+ # convert to palette mode
+ auto_cf = cf is None
+
+ # read the image data to get the metadata
+ reader = png.Reader(filename=filename)
+ w, h, rows, metadata = reader.read()
+
+ # to preserve original palette data only convert the image if needed. For this
+ # check if image has a palette and the requested palette size equals the existing one
+ if not 'palette' in metadata or not auto_cf and len(metadata['palette']) != 2 ** cf.bpp:
+ # reread and convert file
+ reader = png.Reader(
+ bytes=PngQuant(256 if auto_cf else cf.ncolors).convert(filename))
+ w, h, rows, _ = reader.read()
+
+ palette = reader.palette(alpha="force") # always return alpha
+
+ palette_len = len(palette)
+ if auto_cf:
+ if palette_len <= 2:
+ cf = ColorFormat.I1
+ elif palette_len <= 4:
+ cf = ColorFormat.I2
+ elif palette_len <= 16:
+ cf = ColorFormat.I4
+ else:
+ cf = ColorFormat.I8
+
+ if palette_len != cf.ncolors:
+ if not auto_cf:
+ logging.warning(
+ f"{path.basename(filename)} palette: {palette_len}, "
+ f"extended to: {cf.ncolors}")
+ palette += [(255, 255, 255, 0)] * (cf.ncolors - palette_len)
+
+ # Assemble lvgl image palette from PNG palette.
+ # PNG palette is a list of tuple(R,G,B,A)
+
+ rawdata = bytearray()
+ for (r, g, b, a) in palette:
+ rawdata += uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0))
+
+ # pack data if not in I8 format
+ if cf == ColorFormat.I8:
+ for e in rows:
+ rawdata += e
+ else:
+ for e in png.pack_rows(rows, cf.bpp):
+ rawdata += e
+
+ self.set_data(cf, w, h, rawdata)
+
+ def _png_to_alpha_only(self, cf: ColorFormat, filename: str):
+ reader = png.Reader(str(filename))
+ w, h, rows, info = reader.asRGBA8()
+ if not info['alpha']:
+ raise FormatError(f"{filename} has no alpha channel")
+
+ rawdata = bytearray()
+ if cf == ColorFormat.A8:
+ for row in rows:
+ A = row[3::4]
+ for e in A:
+ rawdata += uint8_t(e)
+ else:
+ shift = 8 - cf.bpp
+ mask = 2**cf.bpp - 1
+ rows = [[(a >> shift) & mask for a in row[3::4]] for row in rows]
+ for row in png.pack_rows(rows, cf.bpp):
+ rawdata += row
+
+ self.set_data(cf, w, h, rawdata)
+
+ def sRGB_to_linear(self, x):
+ if x < 0.04045:
+ return x / 12.92
+ return pow((x + 0.055) / 1.055, 2.4)
+
+ def linear_to_sRGB(self, y):
+ if y <= 0.0031308:
+ return 12.92 * y
+ return 1.055 * pow(y, 1 / 2.4) - 0.055
+
+ def _png_to_luma_only(self, cf: ColorFormat, filename: str):
+ reader = png.Reader(str(filename))
+ w, h, rows, info = reader.asRGBA8()
+ rawdata = bytearray()
+ for row in rows:
+ R = row[0::4]
+ G = row[1::4]
+ B = row[2::4]
+ A = row[3::4]
+ for r, g, b, a in zip(R, G, B, A):
+ r, g, b, a = color_pre_multiply(r, g, b, a, self.background)
+ r = self.sRGB_to_linear(r / 255.0)
+ g = self.sRGB_to_linear(g / 255.0)
+ b = self.sRGB_to_linear(b / 255.0)
+ luma = 0.2126 * r + 0.7152 * g + 0.0722 * b
+ rawdata += uint8_t(int(self.linear_to_sRGB(luma) * 255))
+
+ self.set_data(ColorFormat.L8, w, h, rawdata)
+
+ def _png_to_colormap(self, cf, filename: str):
+
+ if cf == ColorFormat.ARGB8888:
+
+ def pack(r, g, b, a):
+ return uint32_t((a << 24) | (r << 16) | (g << 8) | (b << 0))
+ elif cf == ColorFormat.XRGB8888:
+
+ def pack(r, g, b, a):
+ r, g, b, a = color_pre_multiply(r, g, b, a, self.background)
+ return uint32_t((0xff << 24) | (r << 16) | (g << 8) | (b << 0))
+ elif cf == ColorFormat.RGB888:
+
+ def pack(r, g, b, a):
+ r, g, b, a = color_pre_multiply(r, g, b, a, self.background)
+ return uint24_t((r << 16) | (g << 8) | (b << 0))
+ elif cf == ColorFormat.RGB565:
+
+ def pack(r, g, b, a):
+ r, g, b, a = color_pre_multiply(r, g, b, a, self.background)
+ color = (r >> 3) << 11
+ color |= (g >> 2) << 5
+ color |= (b >> 3) << 0
+ return uint16_t(color)
+
+ elif cf == ColorFormat.RGB565A8:
+
+ def pack(r, g, b, a):
+ color = (r >> 3) << 11
+ color |= (g >> 2) << 5
+ color |= (b >> 3) << 0
+ return uint16_t(color)
+ elif cf == ColorFormat.ARGB8565:
+
+ def pack(r, g, b, a):
+ color = (r >> 3) << 11
+ color |= (g >> 2) << 5
+ color |= (b >> 3) << 0
+ return uint24_t((a << 16) | color)
+ else:
+ raise FormatError(f"Invalid color format: {cf.name}")
+
+ reader = png.Reader(str(filename))
+ w, h, rows, _ = reader.asRGBA8()
+ rawdata = bytearray()
+ alpha = bytearray()
+ for y, row in enumerate(rows):
+ R = row[0::4]
+ G = row[1::4]
+ B = row[2::4]
+ A = row[3::4]
+ for x, (r, g, b, a) in enumerate(zip(R, G, B, A)):
+ if cf == ColorFormat.RGB565A8:
+ alpha += uint8_t(a)
+
+ if (
+ self.rgb565_dither and
+ cf in (ColorFormat.RGB565, ColorFormat.RGB565A8, ColorFormat.ARGB8565)
+ ):
+ treshold_id = ((y & 7) << 3) + (x & 7)
+
+ r = min(r + red_thresh[treshold_id], 0xFF) & 0xF8
+ g = min(g + green_thresh[treshold_id], 0xFF) & 0xFC
+ b = min(b + blue_thresh[treshold_id], 0xFF) & 0xF8
+
+ rawdata += pack(r, g, b, a)
+
+ if cf == ColorFormat.RGB565A8:
+ rawdata += alpha
+
+ self.set_data(cf, w, h, rawdata)
+
+
+red_thresh = [
+ 1, 7, 3, 5, 0, 8, 2, 6,
+ 7, 1, 5, 3, 8, 0, 6, 2,
+ 3, 5, 0, 8, 2, 6, 1, 7,
+ 5, 3, 8, 0, 6, 2, 7, 1,
+ 0, 8, 2, 6, 1, 7, 3, 5,
+ 8, 0, 6, 2, 7, 1, 5, 3,
+ 2, 6, 1, 7, 3, 5, 0, 8,
+ 6, 2, 7, 1, 5, 3, 8, 0
+]
+
+green_thresh = [
+ 1, 3, 2, 2, 3, 1, 2, 2,
+ 2, 2, 0, 4, 2, 2, 4, 0,
+ 3, 1, 2, 2, 1, 3, 2, 2,
+ 2, 2, 4, 0, 2, 2, 0, 4,
+ 1, 3, 2, 2, 3, 1, 2, 2,
+ 2, 2, 0, 4, 2, 2, 4, 0,
+ 3, 1, 2, 2, 1, 3, 2, 2,
+ 2, 2, 4, 0, 2, 2, 0, 4
+]
+
+blue_thresh = [
+ 5, 3, 8, 0, 6, 2, 7, 1,
+ 3, 5, 0, 8, 2, 6, 1, 7,
+ 8, 0, 6, 2, 7, 1, 5, 3,
+ 0, 8, 2, 6, 1, 7, 3, 5,
+ 6, 2, 7, 1, 5, 3, 8, 0,
+ 2, 6, 1, 7, 3, 5, 0, 8,
+ 7, 1, 5, 3, 8, 0, 6, 2,
+ 1, 7, 3, 5, 0, 8, 2, 6
+]
+
+
+class RLEHeader:
+
+ def __init__(self, blksize: int, len: int):
+ self.blksize = blksize
+ self.len = len
+
+ @property
+ def binary(self):
+ magic = 0x5aa521e0
+
+ rle_header = self.blksize
+ rle_header |= (self.len & 0xffffff) << 4
+
+ binary = bytearray()
+ binary.extend(uint32_t(magic))
+ binary.extend(uint32_t(rle_header))
+ return binary
+
+
+class RLEImage(LVGLImage):
+
+ def __init__(self,
+ cf: ColorFormat = ColorFormat.UNKNOWN,
+ w: int = 0,
+ h: int = 0,
+ data: bytes = b'') -> None:
+ super().__init__(cf, w, h, data)
+
+ def to_rle(self, filename: str):
+ """
+ Compress this image to file, filename should be ended with '.rle'
+ """
+ self._check_ext(filename, ".rle")
+ self._check_dir(filename)
+
+ # compress image data excluding lvgl image header
+ blksize = (self.cf.bpp + 7) // 8
+ compressed = self.rle_compress(self.data, blksize)
+ with open(filename, "wb+") as f:
+ header = RLEHeader(blksize, len(self.data)).binary
+ header.extend(self.header.binary)
+ f.write(header)
+ f.write(compressed)
+
+ def rle_compress(self, data: bytearray, blksize: int, threshold=16):
+ index = 0
+ data_len = len(data)
+ compressed_data = []
+ memview = memoryview(data)
+ while index < data_len:
+ repeat_cnt = self.get_repeat_count(memview[index:], blksize)
+ if repeat_cnt == 0:
+ # done
+ break
+ elif repeat_cnt < threshold:
+ nonrepeat_cnt = self.get_nonrepeat_count(
+ memview[index:], blksize, threshold)
+ ctrl_byte = uint8_t(nonrepeat_cnt | 0x80)
+ compressed_data.append(ctrl_byte)
+ compressed_data.append(memview[index:index +
+ nonrepeat_cnt * blksize])
+ index += nonrepeat_cnt * blksize
+ else:
+ ctrl_byte = uint8_t(repeat_cnt)
+ compressed_data.append(ctrl_byte)
+ compressed_data.append(memview[index:index + blksize])
+ index += repeat_cnt * blksize
+
+ return b"".join(compressed_data)
+
+ def get_repeat_count(self, data: bytearray, blksize: int):
+ if len(data) < blksize:
+ return 0
+
+ start = data[:blksize]
+ index = 0
+ repeat_cnt = 0
+ value = 0
+
+ while index < len(data):
+ value = data[index:index + blksize]
+
+ if value == start:
+ repeat_cnt += 1
+ if repeat_cnt == 127: # limit max repeat count to max value of signed char.
+ break
+ else:
+ break
+ index += blksize
+
+ return repeat_cnt
+
+ def get_nonrepeat_count(self, data: bytearray, blksize: int, threshold):
+ if len(data) < blksize:
+ return 0
+
+ pre_value = data[:blksize]
+
+ index = 0
+ nonrepeat_count = 0
+
+ repeat_cnt = 0
+ while True:
+ value = data[index:index + blksize]
+ if value == pre_value:
+ repeat_cnt += 1
+ if repeat_cnt > threshold:
+ # repeat found.
+ break
+ else:
+ pre_value = value
+ nonrepeat_count += 1 + repeat_cnt
+ repeat_cnt = 0
+ if nonrepeat_count >= 127: # limit max repeat count to max value of signed char.
+ nonrepeat_count = 127
+ break
+
+ index += blksize # move to next position
+ if index >= len(data): # data end
+ nonrepeat_count += repeat_cnt
+ break
+
+ return nonrepeat_count
+
+
+class RAWImage():
+ '''
+ RAW image is an exception to LVGL image, it has color format of RAW or RAW_ALPHA.
+ It has same image header as LVGL image, but the data is pure raw data from file.
+ It does not support stride adjustment etc. features for LVGL image.
+ It only supports convert an image to C array with RAW or RAW_ALPHA format.
+ '''
+ CF_SUPPORTED = (ColorFormat.RAW, ColorFormat.RAW_ALPHA)
+
+ class NotSupported(NotImplementedError):
+ pass
+
+ def __init__(self,
+ cf: ColorFormat = ColorFormat.UNKNOWN,
+ data: bytes = b'') -> None:
+ self.cf = cf
+ self.data = data
+
+ def to_c_array(self,
+ filename: str):
+ # Image size is set to zero, to let PNG or JPEG decoder to handle it
+ # Stride is meaningless for RAW image
+ write_c_array_file(0, 0, 0, self.cf, filename,
+ False, CompressMethod.NONE, self.data)
+
+ def from_file(self,
+ filename: str,
+ cf: ColorFormat = None):
+ if cf not in RAWImage.CF_SUPPORTED:
+ raise RAWImage.NotSupported(f"Invalid color format: {cf.name}")
+
+ with open(filename, "rb") as f:
+ self.data = f.read()
+ self.cf = cf
+ return self
+
+
+class OutputFormat(Enum):
+ C_ARRAY = "C"
+ BIN_FILE = "BIN"
+ PNG_FILE = "PNG" # convert to lvgl image and then to png
+
+
+class PNGConverter:
+
+ def __init__(self,
+ files: List,
+ cf: ColorFormat,
+ ofmt: OutputFormat,
+ odir: str,
+ background: int = 0x00,
+ align: int = 1,
+ premultiply: bool = False,
+ compress: CompressMethod = CompressMethod.NONE,
+ keep_folder=True,
+ rgb565_dither=False) -> None:
+ self.files = files
+ self.cf = cf
+ self.ofmt = ofmt
+ self.output = odir
+ self.pngquant = None
+ self.keep_folder = keep_folder
+ self.align = align
+ self.premultiply = premultiply
+ self.compress = compress
+ self.background = background
+ self.rgb565_dither = rgb565_dither
+
+ def _replace_ext(self, input, ext):
+ if self.keep_folder:
+ name, _ = path.splitext(input)
+ else:
+ name, _ = path.splitext(path.basename(input))
+ output = name + ext
+ output = path.join(self.output, output)
+ return output
+
+ def convert(self):
+ output = []
+ for f in self.files:
+ if self.cf in (ColorFormat.RAW, ColorFormat.RAW_ALPHA):
+ # Process RAW image explicitly
+ img = RAWImage().from_file(f, self.cf)
+ img.to_c_array(self._replace_ext(f, ".c"))
+ else:
+ img = LVGLImage().from_png(f, self.cf, background=self.background, rgb565_dither=self.rgb565_dither)
+ img.adjust_stride(align=self.align)
+
+ if self.premultiply:
+ img.premultiply()
+ output.append((f, img))
+ if self.ofmt == OutputFormat.BIN_FILE:
+ img.to_bin(self._replace_ext(f, ".bin"),
+ compress=self.compress)
+ elif self.ofmt == OutputFormat.C_ARRAY:
+ img.to_c_array(self._replace_ext(f, ".c"),
+ compress=self.compress)
+ elif self.ofmt == OutputFormat.PNG_FILE:
+ img.to_png(self._replace_ext(f, ".png"))
+
+ return output
+
+
+def main():
+ parser = argparse.ArgumentParser(description='LVGL PNG to bin image tool.')
+ parser.add_argument('--ofmt',
+ help="output filename format, C or BIN",
+ default="BIN",
+ choices=["C", "BIN", "PNG"])
+ parser.add_argument(
+ '--cf',
+ help=("bin image color format, use AUTO for automatically "
+ "choose from I1/2/4/8"),
+ default="I8",
+ choices=[
+ "L8", "I1", "I2", "I4", "I8", "A1", "A2", "A4", "A8", "ARGB8888",
+ "XRGB8888", "RGB565", "RGB565A8", "ARGB8565", "RGB888", "AUTO",
+ "RAW", "RAW_ALPHA"
+ ])
+
+ parser.add_argument('--rgb565dither', action='store_true',
+ help="use dithering to correct banding in gradients", default=False)
+
+ parser.add_argument('--premultiply', action='store_true',
+ help="pre-multiply color with alpha", default=False)
+
+ parser.add_argument('--compress',
+ help=("Binary data compress method, default to NONE"),
+ default="NONE",
+ choices=["NONE", "RLE", "LZ4"])
+
+ parser.add_argument('--align',
+ help="stride alignment in bytes for bin image",
+ default=1,
+ type=int,
+ metavar='byte',
+ nargs='?')
+ parser.add_argument('--background',
+ help="Background color for formats without alpha",
+ default=0x00_00_00,
+ type=lambda x: int(x, 0),
+ metavar='color',
+ nargs='?')
+ parser.add_argument('-o',
+ '--output',
+ default="./output",
+ help="Select the output folder, default to ./output")
+ parser.add_argument('-v', '--verbose', action='store_true')
+ parser.add_argument(
+ 'input', help="the filename or folder to be recursively converted")
+
+ args = parser.parse_args()
+
+ if path.isfile(args.input):
+ files = [args.input]
+ elif path.isdir(args.input):
+ files = list(Path(args.input).rglob("*.[pP][nN][gG]"))
+ else:
+ raise BaseException(f"invalid input: {args.input}")
+
+ if args.verbose:
+ logging.basicConfig(level=logging.INFO)
+
+ logging.info(f"options: {args.__dict__}, files:{[str(f) for f in files]}")
+
+ if args.cf == "AUTO":
+ cf = None
+ else:
+ cf = ColorFormat[args.cf]
+
+ ofmt = OutputFormat(args.ofmt) if cf not in (
+ ColorFormat.RAW, ColorFormat.RAW_ALPHA) else OutputFormat.C_ARRAY
+ compress = CompressMethod[args.compress]
+
+ converter = PNGConverter(files,
+ cf,
+ ofmt,
+ args.output,
+ background=args.background,
+ align=args.align,
+ premultiply=args.premultiply,
+ compress=compress,
+ keep_folder=False,
+ rgb565_dither=args.rgb565dither)
+ output = converter.convert()
+ for f, img in output:
+ logging.info(f"len: {img.data_len} for {path.basename(f)} ")
+
+ print(f"done {len(files)} files")
+
+
+def test():
+ logging.basicConfig(level=logging.INFO)
+ f = "pngs/cogwheel.RGB565A8.png"
+ img = LVGLImage().from_png(f,
+ cf=ColorFormat.ARGB8565,
+ background=0xFF_FF_00,
+ rgb565_dither=True)
+ img.adjust_stride(align=16)
+ img.premultiply()
+ img.to_bin("output/cogwheel.ARGB8565.bin")
+ img.to_c_array("output/cogwheel-abc.c") # file name is used as c var name
+ img.to_png("output/cogwheel.ARGB8565.png.png") # convert back to png
+
+
+def test_raw():
+ logging.basicConfig(level=logging.INFO)
+ f = "pngs/cogwheel.RGB565A8.png"
+ img = RAWImage().from_file(f,
+ cf=ColorFormat.RAW_ALPHA)
+ img.to_c_array("output/cogwheel-raw.c")
+
+
+if __name__ == "__main__":
+ # test()
+ # test_raw()
+ main()
diff --git a/xiaozhi-esp32-2.2.6/scripts/Image_Converter/README.md b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/README.md
new file mode 100644
index 0000000..62a02cd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/README.md
@@ -0,0 +1,45 @@
+# LVGL鍥剧墖杞崲宸ュ叿
+
+杩欎釜鐩綍鍖呭惈涓や釜鐢ㄤ簬澶勭悊鍜岃浆鎹㈠浘鐗囦负LVGL鏍煎紡鐨凱ython鑴氭湰锛�
+
+## 1. LVGLImage (LVGLImage.py)
+
+寮曠敤鑷狶VGL[瀹樻柟repo](https://github.com/lvgl/lvgl)鐨勮浆鎹㈣剼鏈琜LVGLImage.py](https://github.com/lvgl/lvgl/blob/master/scripts/LVGLImage.py)
+
+## 2. LVGL鍥剧墖杞崲宸ュ叿 (lvgl_tools_gui.py)
+
+璋冪敤`LVGLImage.py`锛屽皢鍥剧墖鎵归噺杞崲涓篖VGL鍥剧墖鏍煎紡
+鍙敤浜庝慨鏀瑰皬鏅虹殑榛樿琛ㄦ儏锛屽叿浣撲慨鏀规暀绋媅鍦ㄨ繖閲宂(https://www.bilibili.com/video/BV12FQkYeEJ3/)
+
+### 鐗规��
+
+- 鍥惧舰鍖栨搷浣滐紝鐣岄潰鏇村弸濂�
+- 鏀寔鎵归噺杞崲鍥剧墖
+- 鑷姩璇嗗埆鍥剧墖鏍煎紡骞堕�夋嫨鏈�浣崇殑棰滆壊鏍煎紡杞崲
+- 澶氬垎杈ㄧ巼鏀寔
+
+### 浣跨敤鏂规硶
+
+鍒涘缓铏氭嫙鐜
+```bash
+# 鍒涘缓 venv
+python -m venv venv
+# 婵�娲荤幆澧�
+source venv/bin/activate # Linux/Mac
+venv\Scripts\activate # Windows
+```
+
+瀹夎渚濊禆
+```bash
+pip install -r requirements.txt
+```
+
+杩愯杞崲宸ュ叿
+
+```bash
+# 婵�娲荤幆澧�
+source venv/bin/activate # Linux/Mac
+venv\Scripts\activate # Windows
+# 杩愯
+python lvgl_tools_gui.py
+```
diff --git a/xiaozhi-esp32-2.2.6/scripts/Image_Converter/lvgl_tools_gui.py b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/lvgl_tools_gui.py
new file mode 100644
index 0000000..de7c1f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/lvgl_tools_gui.py
@@ -0,0 +1,253 @@
+import tkinter as tk
+from tkinter import ttk, filedialog, messagebox
+from PIL import Image
+import os
+import tempfile
+import sys
+from LVGLImage import LVGLImage, ColorFormat, CompressMethod
+
+HELP_TEXT = """LVGL鍥剧墖杞崲宸ュ叿浣跨敤璇存槑锛�
+
+1. 娣诲姞鏂囦欢锛氱偣鍑烩�滄坊鍔犳枃浠垛�濇寜閽�夋嫨闇�瑕佽浆鎹㈢殑鍥剧墖锛屾敮鎸佹壒閲忓鍏�
+
+2. 绉婚櫎鏂囦欢锛氬湪鍒楄〃涓�変腑鏂囦欢鍓嶇殑澶嶉�夋鈥淸 ]鈥濓紙閫変腑鍚庝細鍙樻垚鈥淸鈭歖鈥濓級锛岀偣鍑烩�滅Щ闄ら�変腑鈥濆彲鍒犻櫎閫夊畾鏂囦欢
+
+3. 璁剧疆鍒嗚鲸鐜囷細閫夋嫨闇�瑕佺殑鍒嗚鲸鐜囷紝濡�128x128
+ 寤鸿鏍规嵁鑷繁鐨勮澶囩殑灞忓箷鍒嗚鲸鐜囨潵閫夋嫨銆傝繃澶у拰杩囧皬閮戒細褰卞搷鏄剧ず鏁堟灉銆�
+
+4. 棰滆壊鏍煎紡锛氶�夋嫨鈥滆嚜鍔ㄨ瘑鍒�濅細鏍规嵁鍥剧墖鏄惁閫忔槑鑷姩閫夋嫨锛屾垨鎵嬪姩鎸囧畾
+ 闄ら潪浣犱簡瑙h繖涓�夐」锛屽惁鍒欏缓璁娇鐢ㄨ嚜鍔ㄨ瘑鍒紝涓嶇劧鍙兘浼氬嚭鐜颁竴浜涙剰鎯充笉鍒扮殑闂鈥︹��
+
+5. 鍘嬬缉鏂瑰紡锛氶�夋嫨NONE鎴朢LE鍘嬬缉
+ 闄ら潪浣犱簡瑙h繖涓�夐」锛屽惁鍒欏缓璁繚鎸侀粯璁ONE涓嶅帇缂�
+
+6. 杈撳嚭鐩綍锛氳缃浆鎹㈠悗鏂囦欢鐨勪繚瀛樿矾寰�
+ 榛樿涓虹▼搴忔墍鍦ㄧ洰褰曚笅鐨刼utput鏂囦欢澶�
+
+7. 杞崲锛氱偣鍑烩�滆浆鎹㈠叏閮ㄢ�濇垨鈥滆浆鎹㈤�変腑鈥濆紑濮嬭浆鎹�
+"""
+
+class ImageConverterApp:
+ def __init__(self, root):
+ self.root = root
+ self.root.title("LVGL鍥剧墖杞崲宸ュ叿")
+ self.root.geometry("750x650")
+
+ # 鍒濆鍖栧彉閲�
+ self.output_dir = tk.StringVar(value=os.path.abspath("output"))
+ self.resolution = tk.StringVar(value="128x128")
+ self.color_format = tk.StringVar(value="鑷姩璇嗗埆")
+ self.compress_method = tk.StringVar(value="NONE")
+
+ # 鍒涘缓UI缁勪欢
+ self.create_widgets()
+ self.redirect_output()
+
+ def create_widgets(self):
+ # 鍙傛暟璁剧疆妗嗘灦
+ settings_frame = ttk.LabelFrame(self.root, text="杞崲璁剧疆")
+ settings_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
+
+ # 鍒嗚鲸鐜囪缃�
+ ttk.Label(settings_frame, text="鍒嗚鲸鐜�:").grid(row=0, column=0, padx=2)
+ ttk.Combobox(settings_frame, textvariable=self.resolution,
+ values=["512x512", "256x256", "128x128", "64x64", "32x32"], width=8).grid(row=0, column=1, padx=2)
+
+ # 棰滆壊鏍煎紡
+ ttk.Label(settings_frame, text="棰滆壊鏍煎紡:").grid(row=0, column=2, padx=2)
+ ttk.Combobox(settings_frame, textvariable=self.color_format,
+ values=["鑷姩璇嗗埆", "RGB565", "RGB565A8"], width=10).grid(row=0, column=3, padx=2)
+
+ # 鍘嬬缉鏂瑰紡
+ ttk.Label(settings_frame, text="鍘嬬缉鏂瑰紡:").grid(row=0, column=4, padx=2)
+ ttk.Combobox(settings_frame, textvariable=self.compress_method,
+ values=["NONE", "RLE"], width=8).grid(row=0, column=5, padx=2)
+
+ # 鏂囦欢鎿嶄綔妗嗘灦
+ file_frame = ttk.LabelFrame(self.root, text="閫夊彇鏂囦欢")
+ file_frame.grid(row=1, column=0, padx=10, pady=5, sticky="nsew")
+
+ # 鏂囦欢鎿嶄綔鎸夐挳
+ btn_frame = ttk.Frame(file_frame)
+ btn_frame.pack(fill=tk.X, pady=2)
+ ttk.Button(btn_frame, text="娣诲姞鏂囦欢", command=self.select_files).pack(side=tk.LEFT, padx=2)
+ ttk.Button(btn_frame, text="绉婚櫎閫変腑", command=self.remove_selected).pack(side=tk.LEFT, padx=2)
+ ttk.Button(btn_frame, text="娓呯┖鍒楄〃", command=self.clear_files).pack(side=tk.LEFT, padx=2)
+
+ # 鏂囦欢鍒楄〃锛圱reeview锛�
+ self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
+ show="headings", height=10)
+ self.tree.heading("selected", text="閫夋嫨", anchor=tk.W)
+ self.tree.heading("filename", text="鏂囦欢鍚�", anchor=tk.W)
+ self.tree.column("selected", width=60, anchor=tk.W)
+ self.tree.column("filename", width=600, anchor=tk.W)
+ self.tree.pack(fill=tk.BOTH, expand=True)
+ self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
+
+ # 杈撳嚭鐩綍
+ output_frame = ttk.LabelFrame(self.root, text="杈撳嚭鐩綍")
+ output_frame.grid(row=2, column=0, padx=10, pady=5, sticky="ew")
+ ttk.Entry(output_frame, textvariable=self.output_dir, width=60).pack(side=tk.LEFT, padx=5)
+ ttk.Button(output_frame, text="娴忚", command=self.select_output_dir).pack(side=tk.RIGHT, padx=5)
+
+ # 杞崲鎸夐挳鍜屽府鍔╂寜閽�
+ convert_frame = ttk.Frame(self.root)
+ convert_frame.grid(row=3, column=0, padx=10, pady=10)
+ ttk.Button(convert_frame, text="杞崲鍏ㄩ儴鏂囦欢", command=lambda: self.start_conversion(True)).pack(side=tk.LEFT, padx=5)
+ ttk.Button(convert_frame, text="杞崲閫変腑鏂囦欢", command=lambda: self.start_conversion(False)).pack(side=tk.LEFT, padx=5)
+ ttk.Button(convert_frame, text="甯姪", command=self.show_help).pack(side=tk.RIGHT, padx=5)
+
+ # 鏃ュ織鍖哄煙锛堟柊澧炴竻绌烘寜閽儴鍒嗭級
+ log_frame = ttk.LabelFrame(self.root, text="鏃ュ織")
+ log_frame.grid(row=4, column=0, padx=10, pady=5, sticky="nsew")
+
+ # 娣诲姞鎸夐挳妗嗘灦
+ log_btn_frame = ttk.Frame(log_frame)
+ log_btn_frame.pack(fill=tk.X, side=tk.BOTTOM)
+
+ # 娓呯┖鏃ュ織鎸夐挳
+ ttk.Button(log_btn_frame, text="娓呯┖鏃ュ織", command=self.clear_log).pack(side=tk.RIGHT, padx=5, pady=2)
+
+ self.log_text = tk.Text(log_frame, height=15)
+ self.log_text.pack(fill=tk.BOTH, expand=True)
+
+ # 甯冨眬閰嶇疆
+ self.root.columnconfigure(0, weight=1)
+ self.root.rowconfigure(1, weight=1)
+ self.root.rowconfigure(4, weight=1)
+
+ def clear_log(self):
+ """娓呯┖鏃ュ織鍐呭"""
+ self.log_text.delete(1.0, tk.END)
+
+ def show_help(self):
+ messagebox.showinfo("甯姪", HELP_TEXT)
+
+ def redirect_output(self):
+ class StdoutRedirector:
+ def __init__(self, text_widget):
+ self.text_widget = text_widget
+ self.original_stdout = sys.stdout
+
+ def write(self, message):
+ self.text_widget.insert(tk.END, message)
+ self.text_widget.see(tk.END)
+ self.original_stdout.write(message)
+
+ def flush(self):
+ self.original_stdout.flush()
+
+ sys.stdout = StdoutRedirector(self.log_text)
+
+ def on_tree_click(self, event):
+ region = self.tree.identify("region", event.x, event.y)
+ if region == "cell":
+ col = self.tree.identify_column(event.x)
+ item = self.tree.identify_row(event.y)
+ if col == "#1": # 鐐瑰嚮鐨勬槸閫変腑鍒�
+ current_val = self.tree.item(item, "values")[0]
+ new_val = "[鈭歖" if current_val == "[ ]" else "[ ]"
+ self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
+
+ def select_output_dir(self):
+ path = filedialog.askdirectory()
+ if path:
+ self.output_dir.set(path)
+
+ def select_files(self):
+ files = filedialog.askopenfilenames(filetypes=[("鍥剧墖鏂囦欢", "*.png;*.jpg;*.jpeg;*.bmp;*.gif")])
+ for f in files:
+ self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
+
+ def remove_selected(self):
+ to_remove = []
+ for item in self.tree.get_children():
+ if self.tree.item(item, "values")[0] == "[鈭歖":
+ to_remove.append(item)
+ for item in reversed(to_remove):
+ self.tree.delete(item)
+
+ def clear_files(self):
+ for item in self.tree.get_children():
+ self.tree.delete(item)
+
+ def start_conversion(self, convert_all):
+ input_files = [
+ self.tree.item(item, "tags")[0]
+ for item in self.tree.get_children()
+ if convert_all or self.tree.item(item, "values")[0] == "[鈭歖"
+ ]
+
+ if not input_files:
+ msg = "娌℃湁鎵惧埌鍙浆鎹㈢殑鏂囦欢" if convert_all else "娌℃湁閫変腑浠讳綍鏂囦欢"
+ messagebox.showwarning("璀﹀憡", msg)
+ return
+
+ os.makedirs(self.output_dir.get(), exist_ok=True)
+
+ # 瑙f瀽杞崲鍙傛暟
+ width, height = map(int, self.resolution.get().split('x'))
+ compress = CompressMethod.RLE if self.compress_method.get() == "RLE" else CompressMethod.NONE
+
+ # 鎵ц杞崲
+ self.convert_images(input_files, width, height, compress)
+
+ def convert_images(self, input_files, width, height, compress):
+ success_count = 0
+ total_files = len(input_files)
+
+ for idx, file_path in enumerate(input_files):
+ try:
+ print(f"姝e湪澶勭悊: {os.path.basename(file_path)}")
+
+ with Image.open(file_path) as img:
+ # 璋冩暣鍥剧墖澶у皬
+ img = img.resize((width, height), Image.Resampling.LANCZOS)
+
+ # 澶勭悊棰滆壊鏍煎紡
+ color_format_str = self.color_format.get()
+ if color_format_str == "鑷姩璇嗗埆":
+ # 妫�娴嬮�忔槑閫氶亾
+ has_alpha = img.mode in ('RGBA', 'LA') or (img.mode == 'P' and 'transparency' in img.info)
+ if has_alpha:
+ img = img.convert('RGBA')
+ cf = ColorFormat.RGB565A8
+ else:
+ img = img.convert('RGB')
+ cf = ColorFormat.RGB565
+ else:
+ if color_format_str == "RGB565A8":
+ img = img.convert('RGBA')
+ cf = ColorFormat.RGB565A8
+ else:
+ img = img.convert('RGB')
+ cf = ColorFormat.RGB565
+
+ # 淇濆瓨璋冩暣鍚庣殑鍥剧墖
+ base_name = os.path.splitext(os.path.basename(file_path))[0]
+ output_image_path = os.path.join(self.output_dir.get(), f"{base_name}_{width}x{height}.png")
+ img.save(output_image_path, 'PNG')
+
+ # 鍒涘缓涓存椂鏂囦欢
+ with tempfile.NamedTemporaryFile(suffix=".png", delete=False) as tmpfile:
+ temp_path = tmpfile.name
+ img.save(temp_path, 'PNG')
+
+ # 杞崲涓篖VGL C鏁扮粍
+ lvgl_img = LVGLImage().from_png(temp_path, cf=cf)
+ output_c_path = os.path.join(self.output_dir.get(), f"{base_name}.c")
+ lvgl_img.to_c_array(output_c_path, compress=compress)
+
+ success_count += 1
+ os.unlink(temp_path)
+ print(f"鎴愬姛杞崲: {base_name}.c\n")
+
+ except Exception as e:
+ print(f"杞崲澶辫触: {str(e)}\n")
+
+ print(f"杞崲瀹屾垚! 鎴愬姛 {success_count}/{total_files} 涓枃浠禱n")
+
+if __name__ == "__main__":
+ root = tk.Tk()
+ app = ImageConverterApp(root)
+ root.mainloop()
diff --git a/xiaozhi-esp32-2.2.6/scripts/Image_Converter/requirements.txt b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/requirements.txt
new file mode 100644
index 0000000..5dbc310
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/Image_Converter/requirements.txt
@@ -0,0 +1,3 @@
+lz4==4.4.4
+Pillow==11.3.0
+pypng==0.20220715.0
diff --git a/xiaozhi-esp32-2.2.6/scripts/acoustic_check/demod.py b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/demod.py
new file mode 100644
index 0000000..89c8196
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/demod.py
@@ -0,0 +1,280 @@
+"""
+瀹炴椂AFSK瑙h皟鍣� - 鍩轰簬Goertzel绠楁硶
+"""
+
+import numpy as np
+from collections import deque
+
+
+class TraceGoertzel:
+ """瀹炴椂Goertzel绠楁硶瀹炵幇"""
+
+ def __init__(self, freq: float, n: int):
+ """
+ 鍒濆鍖朑oertzel绠楁硶
+
+ Args:
+ freq: 褰掍竴鍖栭鐜� (鐩爣棰戠巼/閲囨牱棰戠巼)
+ n: 绐楀彛澶у皬
+ """
+ self.freq = freq
+ self.n = n
+
+ # 棰勮绠楃郴鏁� - 涓庡弬鑰冧唬鐮佷竴鑷�
+ self.k = int(freq * n)
+ self.w = 2.0 * np.pi * freq
+ self.cw = np.cos(self.w)
+ self.sw = np.sin(self.w)
+ self.c = 2.0 * self.cw
+
+ # 鍒濆鍖栫姸鎬佸彉閲� - 浣跨敤deque瀛樺偍鏈�杩戜袱涓��
+ self.zs = deque([0.0, 0.0], maxlen=2)
+
+ def reset(self):
+ """閲嶇疆绠楁硶鐘舵��"""
+ self.zs.clear()
+ self.zs.extend([0.0, 0.0])
+
+ def __call__(self, xs):
+ """
+ 澶勭悊涓�缁勯噰鏍风偣 - 涓庡弬鑰冧唬鐮佷竴鑷寸殑鎺ュ彛
+
+ Args:
+ xs: 閲囨牱鐐瑰簭鍒�
+
+ Returns:
+ 璁$畻鍑虹殑鎸箙
+ """
+ self.reset()
+ for x in xs:
+ z1, z2 = self.zs[-1], self.zs[-2] # Z[-1], Z[-2]
+ z0 = x + self.c * z1 - z2 # S[n] = x[n] + C * S[n-1] - S[n-2]
+ self.zs.append(float(z0)) # 鏇存柊搴忓垪
+ return self.amp
+
+ @property
+ def amp(self) -> float:
+ """璁$畻褰撳墠鎸箙 - 涓庡弬鑰冧唬鐮佷竴鑷�"""
+ z1, z2 = self.zs[-1], self.zs[-2]
+ ip = self.cw * z1 - z2
+ qp = self.sw * z1
+ return np.sqrt(ip**2 + qp**2) / (self.n / 2.0)
+
+
+class PairGoertzel:
+ """鍙岄Goertzel瑙h皟鍣�"""
+
+ def __init__(self, f_sample: int, f_space: int, f_mark: int,
+ bit_rate: int, win_size: int):
+ """
+ 鍒濆鍖栧弻棰戣В璋冨櫒
+
+ Args:
+ f_sample: 閲囨牱棰戠巼
+ f_space: Space棰戠巼 (閫氬父瀵瑰簲0)
+ f_mark: Mark棰戠巼 (閫氬父瀵瑰簲1)
+ bit_rate: 姣旂壒鐜�
+ win_size: Goertzel绐楀彛澶у皬
+ """
+ assert f_sample % bit_rate == 0, "閲囨牱棰戠巼蹇呴』鏄瘮鐗圭巼鐨勬暣鏁板��"
+
+ self.Fs = f_sample
+ self.F0 = f_space
+ self.F1 = f_mark
+ self.bit_rate = bit_rate
+ self.n_per_bit = int(f_sample // bit_rate) # 姣忎釜姣旂壒鐨勯噰鏍风偣鏁�
+
+ # 璁$畻褰掍竴鍖栭鐜�
+ f1 = f_mark / f_sample
+ f0 = f_space / f_sample
+
+ # 鍒濆鍖朑oertzel绠楁硶
+ self.g0 = TraceGoertzel(freq=f0, n=win_size)
+ self.g1 = TraceGoertzel(freq=f1, n=win_size)
+
+ # 杈撳叆缂撳啿鍖�
+ self.in_buffer = deque(maxlen=win_size)
+ self.out_count = 0
+
+ print(f"PairGoertzel initialized: f0={f0:.6f}, f1={f1:.6f}, win_size={win_size}, n_per_bit={self.n_per_bit}")
+
+ def __call__(self, s: float):
+ """
+ 澶勭悊鍗曚釜閲囨牱鐐� - 涓庡弬鑰冧唬鐮佷竴鑷寸殑鎺ュ彛
+
+ Args:
+ s: 閲囨牱鐐瑰��
+
+ Returns:
+ (amp0, amp1, p1_prob) - 绌洪棿棰戠巼鎸箙锛屾爣璁伴鐜囨尟骞咃紝鏍囪姒傜巼
+ """
+ self.in_buffer.append(s)
+ self.out_count += 1
+
+ amp0, amp1, p1_prob = 0, 0, None
+
+ # 姣忎釜姣旂壒鍛ㄦ湡杈撳嚭涓�娆$粨鏋�
+ if self.out_count >= self.n_per_bit:
+ amp0 = self.g0(self.in_buffer) # 璁$畻space棰戠巼鎸箙
+ amp1 = self.g1(self.in_buffer) # 璁$畻mark棰戠巼鎸箙
+ p1_prob = amp1 / (amp0 + amp1 + 1e-8) # 璁$畻mark姒傜巼
+ self.out_count = 0
+
+ return amp0, amp1, p1_prob
+
+
+class RealTimeAFSKDecoder:
+ """瀹炴椂AFSK瑙g爜鍣� - 鍩轰簬璧峰甯цЕ鍙�"""
+
+ def __init__(self, f_sample: int = 16000, mark_freq: int = 1800,
+ space_freq: int = 1500, bitrate: int = 100,
+ s_goertzel: int = 9, threshold: float = 0.5):
+ """
+ 鍒濆鍖栧疄鏃禔FSK瑙g爜鍣�
+
+ Args:
+ f_sample: 閲囨牱棰戠巼
+ mark_freq: Mark棰戠巼
+ space_freq: Space棰戠巼
+ bitrate: 姣旂壒鐜�
+ s_goertzel: Goertzel绐楀彛澶у皬绯绘暟 (win_size = f_sample // mark_freq * s_goertzel)
+ threshold: 鍒ゅ喅闂ㄩ檺
+ """
+ self.f_sample = f_sample
+ self.mark_freq = mark_freq
+ self.space_freq = space_freq
+ self.bitrate = bitrate
+ self.threshold = threshold
+
+ # 璁$畻绐楀彛澶у皬 - 涓庡弬鑰冧唬鐮佷竴鑷�
+ win_size = int(f_sample / mark_freq * s_goertzel)
+
+ # 鍒濆鍖栬В璋冨櫒
+ self.demodulator = PairGoertzel(f_sample, space_freq, mark_freq,
+ bitrate, win_size)
+
+ # 甯у畾涔� - 涓庡弬鑰冧唬鐮佷竴鑷�
+ self.start_bytes = b'\x01\x02'
+ self.end_bytes = b'\x03\x04'
+ self.start_bits = "".join(format(int(x), '08b') for x in self.start_bytes)
+ self.end_bits = "".join(format(int(x), '08b') for x in self.end_bytes)
+
+ # 鐘舵�佹満
+ self.state = "idle" # idle / entering
+
+ # 瀛樺偍瑙h皟缁撴灉
+ self.buffer_prelude:deque = deque(maxlen=len(self.start_bits)) # 鍒ゆ柇鏄惁鍚姩
+ self.indicators = [] # 瀛樺偍姒傜巼搴忓垪
+ self.signal_bits = "" # 瀛樺偍姣旂壒搴忓垪
+ self.text_cache = ""
+
+ # 瑙g爜缁撴灉
+ self.decoded_messages = []
+ self.total_bits_received = 0
+
+ print(f"Decoder initialized: win_size={win_size}")
+ print(f"Start frame: {self.start_bits} (from {self.start_bytes.hex()})")
+ print(f"End frame: {self.end_bits} (from {self.end_bytes.hex()})")
+
+ def process_audio(self, samples: np.array) -> str:
+ """
+ 澶勭悊闊抽鏁版嵁骞惰繑鍥炶В鐮佹枃鏈�
+
+ Args:
+ audio_data: 闊抽瀛楄妭鏁版嵁 (16-bit PCM)
+
+ Returns:
+ 鏂拌В鐮佺殑鏂囨湰
+ """
+ new_text = ""
+ # 閫愪釜澶勭悊閲囨牱鐐�
+ for sample in samples:
+ amp0, amp1, p1_prob = self.demodulator(sample)
+ # 濡傛灉鏈夋鐜囪緭鍑猴紝璁板綍骞跺垽鍐�
+ if p1_prob is not None:
+ bit = '1' if p1_prob > self.threshold else '0'
+ match self.state:
+ case "idle":
+ self.buffer_prelude.append(bit)
+ pass
+ case "entering":
+ self.buffer_prelude.append(bit)
+ self.signal_bits += bit
+ self.total_bits_received += 1
+ case _:
+ pass
+ self.indicators.append(p1_prob)
+
+ # 妫�鏌ョ姸鎬佹満
+ if self.state == "idle" and "".join(self.buffer_prelude) == self.start_bits:
+ self.state = "entering"
+ self.text_cache = ""
+ self.signal_bits = "" # 娓呯┖姣旂壒搴忓垪
+ self.buffer_prelude.clear()
+ elif self.state == "entering" and ("".join(self.buffer_prelude) == self.end_bits or len(self.signal_bits) >= 256):
+ self.state = "idle"
+ self.buffer_prelude.clear()
+
+ # 姣忔敹闆嗕竴瀹氭暟閲忕殑姣旂壒鍚庡皾璇曡В鐮�
+ if len(self.signal_bits) >= 8:
+ text = self._decode_bits_to_text(self.signal_bits)
+ if len(text) > len(self.text_cache):
+ new_text = text[len(self.text_cache) - len(text):]
+ self.text_cache = text
+ return new_text
+
+ def _decode_bits_to_text(self, bits: str) -> str:
+ """
+ 灏嗘瘮鐗逛覆瑙g爜涓烘枃鏈�
+
+ Args:
+ bits: 姣旂壒涓�
+
+ Returns:
+ 瑙g爜鍑虹殑鏂囨湰
+ """
+ if len(bits) < 8:
+ return ""
+
+ decoded_text = ""
+ byte_count = len(bits) // 8
+
+ for i in range(byte_count):
+ # 鎻愬彇8浣�
+ byte_bits = bits[i*8:(i+1)*8]
+
+ # 浣嶈浆瀛楄妭
+ byte_val = int(byte_bits, 2)
+
+ # 灏濊瘯瑙g爜涓篈SCII瀛楃
+ if 32 <= byte_val <= 126: # 鍙墦鍗癆SCII瀛楃
+ decoded_text += chr(byte_val)
+ elif byte_val == 0: # NULL瀛楃锛屽拷鐣�
+ continue
+ else:
+ # 闈炲彲鎵撳嵃瀛楃pass锛屼互鍗佸叚杩涘埗鏄剧ず
+ pass
+ # decoded_text += f"\\x{byte_val:02X}"
+
+ return decoded_text
+
+ def clear(self):
+ """娓呯┖瑙g爜鐘舵��"""
+ self.indicators = []
+ self.signal_bits = ""
+ self.decoded_messages = []
+ self.total_bits_received = 0
+ print("瑙g爜鍣ㄧ姸鎬佸凡娓呯┖")
+
+ def get_stats(self) -> dict:
+ """鑾峰彇瑙g爜缁熻淇℃伅"""
+ return {
+ 'prelude_bits': "".join(self.buffer_prelude),
+ "state": self.state,
+ 'total_chars': sum(len(msg) for msg in self.text_cache),
+ 'buffer_bits': len(self.signal_bits),
+ 'mark_freq': self.mark_freq,
+ 'space_freq': self.space_freq,
+ 'bitrate': self.bitrate,
+ 'threshold': self.threshold,
+ }
diff --git a/xiaozhi-esp32-2.2.6/scripts/acoustic_check/graphic.py b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/graphic.py
new file mode 100644
index 0000000..e60cf06
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/graphic.py
@@ -0,0 +1,444 @@
+import sys
+import numpy as np
+import asyncio
+import wave
+from collections import deque
+import qasync
+
+import matplotlib
+matplotlib.use('qtagg')
+
+from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
+from matplotlib.backends.backend_qtagg import NavigationToolbar2QT as NavigationToolbar # noqa: F401
+from matplotlib.figure import Figure
+
+from PyQt6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QWidget,
+ QHBoxLayout, QLineEdit, QPushButton, QLabel, QTextEdit)
+from PyQt6.QtCore import QTimer
+
+# 瀵煎叆瑙g爜鍣�
+from demod import RealTimeAFSKDecoder
+
+
+class UDPServerProtocol(asyncio.DatagramProtocol):
+ """UDP鏈嶅姟鍣ㄥ崗璁被"""
+ def __init__(self, data_queue):
+ self.client_address = None
+ self.data_queue: deque = data_queue
+
+ def connection_made(self, transport):
+ self.transport = transport
+
+ def datagram_received(self, data, addr):
+ # 濡傛灉杩樻病鏈夊鎴风鍦板潃锛岃褰曠涓�涓繛鎺ョ殑瀹㈡埛绔�
+ if self.client_address is None:
+ self.client_address = addr
+ print(f"鎺ュ彈鏉ヨ嚜 {addr} 鐨勮繛鎺�")
+
+ # 鍙鐞嗘潵鑷凡璁板綍瀹㈡埛绔殑鏁版嵁
+ if addr == self.client_address:
+ # 灏嗘帴鏀跺埌鐨勯煶棰戞暟鎹坊鍔犲埌闃熷垪
+ self.data_queue.extend(data)
+ else:
+ print(f"蹇界暐鏉ヨ嚜鏈煡鍦板潃 {addr} 鐨勬暟鎹�")
+
+
+class MatplotlibWidget(QWidget):
+ def __init__(self, parent=None):
+ super().__init__(parent)
+
+ # 鍒涘缓 Matplotlib 鐨� Figure 瀵硅薄
+ self.figure = Figure()
+
+ # 鍒涘缓 FigureCanvas 瀵硅薄锛屽畠鏄� Figure 鐨� QWidget 瀹瑰櫒
+ self.canvas = FigureCanvas(self.figure)
+
+ # 鍒涘缓 Matplotlib 鐨勫鑸伐鍏锋爮
+ # self.toolbar = NavigationToolbar(self.canvas, self)
+ self.toolbar = None
+
+ # 鍒涘缓甯冨眬
+ layout = QVBoxLayout()
+ layout.addWidget(self.toolbar)
+ layout.addWidget(self.canvas)
+ self.setLayout(layout)
+
+ # 鍒濆鍖栭煶棰戞暟鎹弬鏁�
+ self.freq = 16000 # 閲囨牱棰戠巼
+ self.time_window = 20 # 鏄剧ず鏃堕棿绐楀彛
+ self.wave_data = deque(maxlen=self.freq * self.time_window * 2) # 缂撳啿闃熷垪, 鐢ㄤ簬鍒嗗彂璁$畻/缁樺浘
+ self.signals = deque(maxlen=self.freq * self.time_window) # 鍙岀闃熷垪瀛樺偍淇″彿鏁版嵁
+
+ # 鍒涘缓鍖呭惈涓や釜瀛愬浘鐨勭敾甯�
+ self.ax1 = self.figure.add_subplot(2, 1, 1)
+ self.ax2 = self.figure.add_subplot(2, 1, 2)
+
+ # 鏃跺煙瀛愬浘
+ self.ax1.set_title('Real-time Audio Waveform')
+ self.ax1.set_xlabel('Sample Index')
+ self.ax1.set_ylabel('Amplitude')
+ self.line_time, = self.ax1.plot([], [])
+ self.ax1.grid(True, alpha=0.3)
+
+ # 棰戝煙瀛愬浘
+ self.ax2.set_title('Real-time Frequency Spectrum')
+ self.ax2.set_xlabel('Frequency (Hz)')
+ self.ax2.set_ylabel('Magnitude')
+ self.line_freq, = self.ax2.plot([], [])
+ self.ax2.grid(True, alpha=0.3)
+
+ self.figure.tight_layout()
+
+ # 瀹氭椂鍣ㄧ敤浜庢洿鏂板浘琛�
+ self.timer = QTimer(self)
+ self.timer.setInterval(100) # 100姣鏇存柊涓�娆�
+ self.timer.timeout.connect(self.update_plot)
+
+ # 鍒濆鍖朅FSK瑙g爜鍣�
+ self.decoder = RealTimeAFSKDecoder(
+ f_sample=self.freq,
+ mark_freq=1800,
+ space_freq=1500,
+ bitrate=100,
+ s_goertzel=9,
+ threshold=0.5
+ )
+
+ # 瑙g爜缁撴灉鍥炶皟
+ self.decode_callback = None
+
+ def start_plotting(self):
+ """寮�濮嬬粯鍥�"""
+ self.timer.start()
+
+ def stop_plotting(self):
+ """鍋滄缁樺浘"""
+ self.timer.stop()
+
+ def update_plot(self):
+ """鏇存柊缁樺浘鏁版嵁"""
+ if len(self.wave_data) >= 2:
+ # 杩涜瀹炴椂瑙g爜
+ # 鑾峰彇鏈�鏂扮殑闊抽鏁版嵁杩涜瑙g爜
+ even = len(self.wave_data) // 2 * 2
+ print(f"length of wave_data: {len(self.wave_data)}")
+ drained = [self.wave_data.popleft() for _ in range(even)]
+ signal = np.frombuffer(bytearray(drained), dtype='<i2') / 32768
+ decoded_text_new = self.decoder.process_audio(signal) # 澶勭悊鏂板淇″彿, 杩斿洖鍏ㄩ噺瑙g爜鏂囨湰
+ if decoded_text_new and self.decode_callback:
+ self.decode_callback(decoded_text_new)
+ self.signals.extend(signal.tolist()) # 灏嗘尝褰㈡暟鎹坊鍔犲埌缁樺浘鏁版嵁
+
+ if len(self.signals) > 0:
+ # 鍙樉绀烘渶杩戠殑涓�娈垫暟鎹紝閬垮厤鍥捐〃杩囦簬瀵嗛泦
+ signal = np.array(self.signals)
+ max_samples = min(len(signal), self.freq * self.time_window)
+ if len(signal) > max_samples:
+ signal = signal[-max_samples:]
+
+ # 鏇存柊鏃跺煙鍥�
+ x = np.arange(len(signal))
+ self.line_time.set_data(x, signal)
+
+ # 鑷姩璋冩暣鏃跺煙鍧愭爣杞磋寖鍥�
+ if len(signal) > 0:
+ self.ax1.set_xlim(0, len(signal))
+ y_min, y_max = np.min(signal), np.max(signal)
+ if y_min != y_max:
+ margin = (y_max - y_min) * 0.1
+ self.ax1.set_ylim(y_min - margin, y_max + margin)
+ else:
+ self.ax1.set_ylim(-1, 1)
+
+ # 璁$畻棰戣氨锛堢煭鏃剁鏁e倕绔嬪彾鍙樻崲锛�
+ if len(signal) > 1:
+ # 璁$畻FFT
+ fft_signal = np.abs(np.fft.fft(signal))
+ frequencies = np.fft.fftfreq(len(signal), 1/self.freq)
+
+ # 鍙彇姝i鐜囬儴鍒�
+ positive_freq_idx = frequencies >= 0
+ freq_positive = frequencies[positive_freq_idx]
+ fft_positive = fft_signal[positive_freq_idx]
+
+ # 鏇存柊棰戝煙鍥�
+ self.line_freq.set_data(freq_positive, fft_positive)
+
+ # 鑷姩璋冩暣棰戝煙鍧愭爣杞磋寖鍥�
+ if len(fft_positive) > 0:
+ # 闄愬埗棰戠巼鏄剧ず鑼冨洿鍒�0-4000Hz锛岄伩鍏嶈繃浜庡瘑闆�
+ max_freq_show = min(4000, self.freq // 2)
+ freq_mask = freq_positive <= max_freq_show
+ if np.any(freq_mask):
+ self.ax2.set_xlim(0, max_freq_show)
+ fft_masked = fft_positive[freq_mask]
+ if len(fft_masked) > 0:
+ fft_max = np.max(fft_masked)
+ if fft_max > 0:
+ self.ax2.set_ylim(0, fft_max * 1.1)
+ else:
+ self.ax2.set_ylim(0, 1)
+
+ self.canvas.draw()
+
+
+class MainWindow(QMainWindow):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Acoustic Check")
+ self.setGeometry(100, 100, 1000, 800)
+
+ # 涓荤獥鍙i儴浠�
+ main_widget = QWidget()
+ self.setCentralWidget(main_widget)
+
+ # 涓诲竷灞�
+ main_layout = QVBoxLayout(main_widget)
+
+ # 缁樺浘鍖哄煙
+ self.matplotlib_widget = MatplotlibWidget()
+ main_layout.addWidget(self.matplotlib_widget)
+
+ # 鎺у埗闈㈡澘
+ control_panel = QWidget()
+ control_layout = QHBoxLayout(control_panel)
+
+ # 鐩戝惉鍦板潃鍜岀鍙h緭鍏�
+ control_layout.addWidget(QLabel("鐩戝惉鍦板潃:"))
+ self.address_input = QLineEdit("0.0.0.0")
+ self.address_input.setFixedWidth(120)
+ control_layout.addWidget(self.address_input)
+
+ control_layout.addWidget(QLabel("绔彛:"))
+ self.port_input = QLineEdit("8000")
+ self.port_input.setFixedWidth(80)
+ control_layout.addWidget(self.port_input)
+
+ # 鐩戝惉鎸夐挳
+ self.listen_button = QPushButton("寮�濮嬬洃鍚�")
+ self.listen_button.clicked.connect(self.toggle_listening)
+ control_layout.addWidget(self.listen_button)
+
+ # 鐘舵�佹爣绛�
+ self.status_label = QLabel("鐘舵��: 鏈繛鎺�")
+ control_layout.addWidget(self.status_label)
+
+ # 鏁版嵁缁熻鏍囩
+ self.data_label = QLabel("鎺ユ敹鏁版嵁: 0 bytes")
+ control_layout.addWidget(self.data_label)
+
+ # 淇濆瓨鎸夐挳
+ self.save_button = QPushButton("淇濆瓨闊抽")
+ self.save_button.clicked.connect(self.save_audio)
+ self.save_button.setEnabled(False)
+ control_layout.addWidget(self.save_button)
+
+ control_layout.addStretch() # 娣诲姞寮规�х┖闂�
+
+ main_layout.addWidget(control_panel)
+
+ # 瑙g爜鏄剧ず鍖哄煙
+ decode_panel = QWidget()
+ decode_layout = QVBoxLayout(decode_panel)
+
+ # 瑙g爜鏍囬
+ decode_title = QLabel("瀹炴椂AFSK瑙g爜缁撴灉:")
+ decode_title.setStyleSheet("font-weight: bold; font-size: 14px;")
+ decode_layout.addWidget(decode_title)
+
+ # 瑙g爜鏂囨湰鏄剧ず
+ self.decode_text = QTextEdit()
+ self.decode_text.setMaximumHeight(150)
+ self.decode_text.setReadOnly(True)
+ self.decode_text.setStyleSheet("font-family: 'Courier New', monospace; font-size: 12px;")
+ decode_layout.addWidget(self.decode_text)
+
+ # 瑙g爜鎺у埗鎸夐挳
+ decode_control_layout = QHBoxLayout()
+
+ # 娓呯┖鎸夐挳
+ self.clear_decode_button = QPushButton("娓呯┖瑙g爜")
+ self.clear_decode_button.clicked.connect(self.clear_decode_text)
+ decode_control_layout.addWidget(self.clear_decode_button)
+
+ # 瑙g爜缁熻鏍囩
+ self.decode_stats_label = QLabel("瑙g爜缁熻: 0 bits, 0 chars")
+ decode_control_layout.addWidget(self.decode_stats_label)
+
+ decode_control_layout.addStretch()
+ decode_layout.addLayout(decode_control_layout)
+
+ main_layout.addWidget(decode_panel)
+
+ # 璁剧疆瑙g爜鍥炶皟
+ self.matplotlib_widget.decode_callback = self.on_decode_text
+
+ # UDP鐩稿叧灞炴��
+ self.udp_transport = None
+ self.is_listening = False
+
+ # 鏁版嵁缁熻瀹氭椂鍣�
+ self.stats_timer = QTimer(self)
+ self.stats_timer.setInterval(1000) # 姣忕鏇存柊涓�娆$粺璁�
+ self.stats_timer.timeout.connect(self.update_stats)
+
+ def on_decode_text(self, new_text: str):
+ """瑙g爜鏂囨湰鍥炶皟"""
+ if new_text:
+ # 娣诲姞鏂拌В鐮佺殑鏂囨湰
+ current_text = self.decode_text.toPlainText()
+ updated_text = current_text + new_text
+
+ # 闄愬埗鏂囨湰闀垮害锛屼繚鐣欐渶鏂扮殑1000涓瓧绗�
+ if len(updated_text) > 1000:
+ updated_text = updated_text[-1000:]
+
+ self.decode_text.setPlainText(updated_text)
+
+ # 婊氬姩鍒板簳閮�
+ cursor = self.decode_text.textCursor()
+ cursor.movePosition(cursor.MoveOperation.End)
+ self.decode_text.setTextCursor(cursor)
+
+ def clear_decode_text(self):
+ """娓呯┖瑙g爜鏂囨湰"""
+ self.decode_text.clear()
+ if hasattr(self.matplotlib_widget, 'decoder'):
+ self.matplotlib_widget.decoder.clear()
+ self.decode_stats_label.setText("瑙g爜缁熻: 0 bits, 0 chars")
+
+ def update_decode_stats(self):
+ """鏇存柊瑙g爜缁熻"""
+ if hasattr(self.matplotlib_widget, 'decoder'):
+ stats = self.matplotlib_widget.decoder.get_stats()
+ stats_text = (
+ f"鍓嶇疆: {stats['prelude_bits']} , 宸叉帴鏀秢stats['total_chars']} chars, "
+ f"缂撳啿: {stats['buffer_bits']} bits, 鐘舵��: {stats['state']}"
+ )
+ self.decode_stats_label.setText(stats_text)
+
+ def toggle_listening(self):
+ """鍒囨崲鐩戝惉鐘舵��"""
+ if not self.is_listening:
+ self.start_listening()
+ else:
+ self.stop_listening()
+
+ async def start_listening_async(self):
+ """寮傛鍚姩UDP鐩戝惉"""
+ try:
+ address = self.address_input.text().strip()
+ port = int(self.port_input.text().strip())
+
+ loop = asyncio.get_running_loop()
+ self.udp_transport, protocol = await loop.create_datagram_endpoint(
+ lambda: UDPServerProtocol(self.matplotlib_widget.wave_data),
+ local_addr=(address, port)
+ )
+
+ self.status_label.setText(f"鐘舵��: 鐩戝惉涓� ({address}:{port})")
+ print(f"UDP鏈嶅姟鍣ㄥ惎鍔�, 鐩戝惉 {address}:{port}")
+
+ except Exception as e:
+ self.status_label.setText(f"鐘舵��: 鍚姩澶辫触 - {str(e)}")
+ print(f"UDP鏈嶅姟鍣ㄥ惎鍔ㄥけ璐�: {e}")
+ self.is_listening = False
+ self.listen_button.setText("寮�濮嬬洃鍚�")
+ self.address_input.setEnabled(True)
+ self.port_input.setEnabled(True)
+
+ def start_listening(self):
+ """寮�濮嬬洃鍚�"""
+ try:
+ int(self.port_input.text().strip()) # 楠岃瘉绔彛鍙锋牸寮�
+ except ValueError:
+ self.status_label.setText("鐘舵��: 绔彛鍙峰繀椤绘槸鏁板瓧")
+ return
+
+ self.is_listening = True
+ self.listen_button.setText("鍋滄鐩戝惉")
+ self.address_input.setEnabled(False)
+ self.port_input.setEnabled(False)
+ self.save_button.setEnabled(True)
+
+ # 娓呯┖鏁版嵁闃熷垪
+ self.matplotlib_widget.wave_data.clear()
+
+ # 鍚姩缁樺浘鍜岀粺璁℃洿鏂�
+ self.matplotlib_widget.start_plotting()
+ self.stats_timer.start()
+
+ # 寮傛鍚姩UDP鏈嶅姟鍣�
+ loop = asyncio.get_event_loop()
+ loop.create_task(self.start_listening_async())
+
+ def stop_listening(self):
+ """鍋滄鐩戝惉"""
+ self.is_listening = False
+ self.listen_button.setText("寮�濮嬬洃鍚�")
+ self.address_input.setEnabled(True)
+ self.port_input.setEnabled(True)
+
+ # 鍋滄UDP鏈嶅姟鍣�
+ if self.udp_transport:
+ self.udp_transport.close()
+ self.udp_transport = None
+
+ # 鍋滄缁樺浘鍜岀粺璁℃洿鏂�
+ self.matplotlib_widget.stop_plotting()
+ self.matplotlib_widget.wave_data.clear()
+ self.stats_timer.stop()
+
+ self.status_label.setText("鐘舵��: 宸插仠姝�")
+
+ def update_stats(self):
+ """鏇存柊鏁版嵁缁熻"""
+ data_size = len(self.matplotlib_widget.signals)
+ self.data_label.setText(f"鎺ユ敹鏁版嵁: {data_size} 閲囨牱")
+
+ # 鏇存柊瑙g爜缁熻
+ self.update_decode_stats()
+
+ def save_audio(self):
+ """淇濆瓨闊抽鏁版嵁"""
+ if len(self.matplotlib_widget.signals) > 0:
+ try:
+ signal_data = np.array(self.matplotlib_widget.signals)
+
+ # 淇濆瓨涓篧AV鏂囦欢
+ with wave.open("received_audio.wav", "wb") as wf:
+ wf.setnchannels(1) # 鍗曞0閬�
+ wf.setsampwidth(2) # 閲囨牱瀹藉害涓�2瀛楄妭
+ wf.setframerate(self.matplotlib_widget.freq) # 璁剧疆閲囨牱鐜�
+ wf.writeframes(signal_data.tobytes()) # 鍐欏叆鏁版嵁
+
+ self.status_label.setText("鐘舵��: 闊抽宸蹭繚瀛樹负 received_audio.wav")
+ print("闊抽宸蹭繚瀛樹负 received_audio.wav")
+
+ except Exception as e:
+ self.status_label.setText(f"鐘舵��: 淇濆瓨澶辫触 - {str(e)}")
+ print(f"淇濆瓨闊抽澶辫触: {e}")
+ else:
+ self.status_label.setText("鐘舵��: 娌℃湁瓒冲鐨勬暟鎹彲淇濆瓨")
+
+
+async def main():
+ """寮傛涓诲嚱鏁�"""
+ app = QApplication(sys.argv)
+
+ # 璁剧疆寮傛浜嬩欢寰幆
+ loop = qasync.QEventLoop(app)
+ asyncio.set_event_loop(loop)
+
+ window = MainWindow()
+ window.show()
+
+ try:
+ with loop:
+ await loop.run_forever()
+ except KeyboardInterrupt:
+ print("绋嬪簭琚敤鎴蜂腑鏂�")
+ finally:
+ # 纭繚娓呯悊璧勬簮
+ if window.udp_transport:
+ window.udp_transport.close()
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/acoustic_check/main.py b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/main.py
new file mode 100644
index 0000000..63ec849
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/main.py
@@ -0,0 +1,18 @@
+#!/usr/bin/env python3
+"""
+闊抽瀹炴椂鐩戝惉涓庣粯鍥剧郴缁熶富绋嬪簭
+鍩轰簬Qt GUI + Matplotlib + UDP鎺ユ敹 + AFSK瑙g爜瀛楃涓�
+"""
+
+import sys
+import asyncio
+from graphic import main
+
+if __name__ == '__main__':
+ try:
+ asyncio.run(main())
+ except KeyboardInterrupt:
+ print("绋嬪簭琚敤鎴蜂腑鏂�")
+ except Exception as e:
+ print(f"绋嬪簭鎵ц鍑洪敊: {e}")
+ sys.exit(1)
diff --git a/xiaozhi-esp32-2.2.6/scripts/acoustic_check/readme.md b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/readme.md
new file mode 100644
index 0000000..9eb9c7c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/readme.md
@@ -0,0 +1,23 @@
+# 澹版尝娴嬭瘯
+璇ui鐢ㄤ簬娴嬭瘯鎺ュ彈灏忔櫤璁惧閫氳繃`udp`鍥炰紶鐨刞pcm`杞椂鍩�/棰戝煙, 鍙互淇濆瓨绐楀彛闀垮害鐨勫0闊�, 鐢ㄤ簬鍒ゆ柇鍣煶棰戠巼鍒嗗竷鍜屾祴璇曞0娉紶杈揳scii鐨勫噯纭害,
+
+鍥轰欢娴嬭瘯闇�瑕佹墦寮�`USE_AUDIO_DEBUGGER`, 骞惰缃ソ`AUDIO_DEBUG_UDP_SERVER`鏄湰鏈哄湴鍧�.
+澹版尝`demod`鍙互閫氳繃`sonic_wifi_config.html`鎴栬�呬笂浼犺嚦`PinMe`鐨刐灏忔櫤澹版尝閰嶇綉](https://iqf7jnhi.pinit.eth.limo)鏉ヨ緭鍑哄0娉㈡祴璇�
+
+# 澹版尝瑙g爜娴嬭瘯璁板綍
+
+> `鉁揱浠h〃鍦↖2S DIN鎺ユ敹鍘熷PCM淇″彿鏃跺氨鑳芥垚鍔熻В鐮�, `鈻砢浠h〃闇�瑕侀檷鍣垨棰濆鎿嶄綔鍙ǔ瀹氳В鐮�, `X`浠h〃闄嶅櫔鍚庢晥鏋滀篃涓嶅ソ(鍙兘鑳借В閮ㄥ垎浣嗛潪甯镐笉绋冲畾)銆�
+> 涓埆ADC闇�瑕両2C閰嶇疆闃舵鍋氭洿绮剧粏鐨勯檷鍣皟鏁�, 鐢变簬璁惧涓嶉�氱敤鏆傚彧鎸夌収boards鍐呮彁渚涚殑config娴嬭瘯
+
+| 璁惧 | ADC | MIC | 鏁堟灉 | 澶囨敞 |
+| ---- | ---- | --- | --- | ---- |
+| bread-compact | INMP441 | 闆嗘垚MEMEMIC | 鉁� |
+| atk-dnesp32s3-box | ES8311 | | 鉁� |
+| magiclick-2p5 | ES8311 | | 鉁� |
+| lichuang-dev | ES7210 | | 鈻� | 娴嬭瘯鏃堕渶瑕佸叧鎺塈NPUT_REFERENCE
+| kevin-box-2 | ES7210 | | 鈻� | 娴嬭瘯鏃堕渶瑕佸叧鎺塈NPUT_REFERENCE
+| m5stack-core-s3 | ES7210 | | 鈻� | 娴嬭瘯鏃堕渶瑕佸叧鎺塈NPUT_REFERENCE
+| xmini-c3 | ES8311 | | 鈻� | 闇�闄嶅櫔
+| atoms3r-echo-base | ES8311 | | 鈻� | 闇�闄嶅櫔
+| atk-dnesp32s3-box0 | ES8311 | | X | 鑳芥帴鏀朵笖瑙g爜, 浣嗘槸涓㈠寘鐜囧緢楂�
+| movecall-moji-esp32s3 | ES8311 | | X | 鑳芥帴鏀朵笖瑙g爜, 浣嗘槸涓㈠寘鐜囧緢楂�
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/acoustic_check/requirements.txt b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/requirements.txt
new file mode 100644
index 0000000..91bc5ec
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/acoustic_check/requirements.txt
@@ -0,0 +1,4 @@
+matplotlib==3.10.5
+numpy==2.3.2
+PyQt6==6.9.1
+qasync==0.27.1
diff --git a/xiaozhi-esp32-2.2.6/scripts/audio_debug_server.py b/xiaozhi-esp32-2.2.6/scripts/audio_debug_server.py
new file mode 100644
index 0000000..872c490
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/audio_debug_server.py
@@ -0,0 +1,54 @@
+import socket
+import wave
+import argparse
+
+
+'''
+ Create a UDP socket and bind it to the server's IP:8000.
+ Listen for incoming messages and print them to the console.
+ Save the audio to a WAV file.
+'''
+def main(samplerate, channels):
+ # Create a UDP socket
+ server_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ server_socket.bind(('0.0.0.0', 8000))
+
+ # Create WAV file with parameters
+ filename = f"{samplerate}_{channels}.wav"
+ wav_file = wave.open(filename, "wb")
+ wav_file.setnchannels(channels) # channels parameter
+ wav_file.setsampwidth(2) # 2 bytes per sample (16-bit)
+ wav_file.setframerate(samplerate) # samplerate parameter
+
+ print(f"Start saving audio from 0.0.0.0:8000 to {filename}...")
+
+ try:
+ while True:
+ # Receive a message from the client
+ message, address = server_socket.recvfrom(8000)
+
+ # Write PCM data to WAV file
+ wav_file.writeframes(message)
+
+ # Print length of the message
+ print(f"Received {len(message)} bytes from {address}")
+
+ except KeyboardInterrupt:
+ print("\nStopping recording...")
+
+ finally:
+ # Close files and socket
+ wav_file.close()
+ server_socket.close()
+ print(f"WAV file '{filename}' saved successfully")
+
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='UDP闊抽鏁版嵁鎺ユ敹鍣紝淇濆瓨涓篧AV鏂囦欢')
+ parser.add_argument('--samplerate', '-s', type=int, default=16000,
+ help='閲囨牱鐜� (榛樿: 16000)')
+ parser.add_argument('--channels', '-c', type=int, default=2,
+ help='澹伴亾鏁� (榛樿: 2)')
+
+ args = parser.parse_args()
+ main(args.samplerate, args.channels)
diff --git a/xiaozhi-esp32-2.2.6/scripts/build_default_assets.py b/xiaozhi-esp32-2.2.6/scripts/build_default_assets.py
new file mode 100755
index 0000000..90cee7d
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/build_default_assets.py
@@ -0,0 +1,934 @@
+#!/usr/bin/env python3
+"""
+Build default assets based on configuration
+
+This script reads configuration from sdkconfig and builds the appropriate assets.bin
+for the current board configuration.
+
+Usage:
+ ./build_default_assets.py --sdkconfig <path> --builtin_text_font <font_name> \
+ --default_emoji_collection <collection_name> --output <output_path>
+"""
+
+import argparse
+import io
+import os
+import shutil
+import sys
+import json
+import struct
+from datetime import datetime
+
+
+# =============================================================================
+# Pack model functions (from pack_model.py)
+# =============================================================================
+
+def struct_pack_string(string, max_len=None):
+ """
+ pack string to binary data.
+ if max_len is None, max_len = len(string) + 1
+ else len(string) < max_len, the left will be padded by struct.pack('x')
+ """
+ if max_len == None :
+ max_len = len(string)
+ else:
+ assert len(string) <= max_len
+
+ left_num = max_len - len(string)
+ out_bytes = None
+ for char in string:
+ if out_bytes == None:
+ out_bytes = struct.pack('b', ord(char))
+ else:
+ out_bytes += struct.pack('b', ord(char))
+ for i in range(left_num):
+ out_bytes += struct.pack('x')
+ return out_bytes
+
+
+def read_data(filename):
+ """Read binary data, like index and mndata"""
+ data = None
+ with open(filename, "rb") as f:
+ data = f.read()
+ return data
+
+
+def pack_models(model_path, out_file="srmodels.bin"):
+ """
+ Pack all models into one binary file by the following format:
+ {
+ model_num: int
+ model1_info: model_info_t
+ model2_info: model_info_t
+ ...
+ model1_index,model1_data,model1_MODEL_INFO
+ model1_index,model1_data,model1_MODEL_INFO
+ ...
+ }model_pack_t
+
+ {
+ model_name: char[32]
+ file_number: int
+ file1_name: char[32]
+ file1_start: int
+ file1_len: int
+ file2_name: char[32]
+ file2_start: int // data_len = info_start - data_start
+ file2_len: int
+ ...
+ }model_info_t
+ """
+ models = {}
+ file_num = 0
+ model_num = 0
+ for root, dirs, _ in os.walk(model_path):
+ for model_name in dirs:
+ models[model_name] = {}
+ model_dir = os.path.join(root, model_name)
+ model_num += 1
+ for _, _, files in os.walk(model_dir):
+ for file_name in files:
+ file_num += 1
+ file_path = os.path.join(model_dir, file_name)
+ models[model_name][file_name] = read_data(file_path)
+
+ model_num = len(models)
+ header_len = 4 + model_num*(32+4) + file_num*(32+4+4)
+ out_bin = struct.pack('I', model_num) # model number
+ data_bin = None
+ for key in models:
+ model_bin = struct_pack_string(key, 32) # + model name
+ model_bin += struct.pack('I', len(models[key])) # + file number in this model
+
+ for file_name in models[key]:
+ model_bin += struct_pack_string(file_name, 32) # + file name
+ if data_bin == None:
+ model_bin += struct.pack('I', header_len)
+ data_bin = models[key][file_name]
+ model_bin += struct.pack('I', len(models[key][file_name]))
+ else:
+ model_bin += struct.pack('I', header_len+len(data_bin))
+ data_bin += models[key][file_name]
+ model_bin += struct.pack('I', len(models[key][file_name]))
+
+ out_bin += model_bin
+ assert len(out_bin) == header_len
+ if data_bin != None:
+ out_bin += data_bin
+
+ out_file = os.path.join(model_path, out_file)
+ with open(out_file, "wb") as f:
+ f.write(out_bin)
+
+
+# =============================================================================
+# Build assets functions (from build.py)
+# =============================================================================
+
+def ensure_dir(directory):
+ """Ensure directory exists, create if not"""
+ os.makedirs(directory, exist_ok=True)
+
+
+def copy_file(src, dst):
+ """Copy file"""
+ if os.path.exists(src):
+ shutil.copy2(src, dst)
+ print(f"Copied: {src} -> {dst}")
+ return True
+ else:
+ print(f"Warning: Source file does not exist: {src}")
+ return False
+
+
+def copy_directory(src, dst):
+ """Copy directory"""
+ if os.path.exists(src):
+ shutil.copytree(src, dst, dirs_exist_ok=True)
+ print(f"Copied directory: {src} -> {dst}")
+ return True
+ else:
+ print(f"Warning: Source directory does not exist: {src}")
+ return False
+
+
+def process_sr_models(wakenet_model_dirs, multinet_model_dirs, build_dir, assets_dir):
+ """Process SR models (wakenet and multinet) and generate srmodels.bin"""
+ if not wakenet_model_dirs and not multinet_model_dirs:
+ return None
+
+ # Create SR models build directory
+ sr_models_build_dir = os.path.join(build_dir, "srmodels")
+ if os.path.exists(sr_models_build_dir):
+ shutil.rmtree(sr_models_build_dir)
+ os.makedirs(sr_models_build_dir)
+
+ models_processed = 0
+
+ # Copy wakenet models if available
+ if wakenet_model_dirs:
+ for wakenet_model_dir in wakenet_model_dirs:
+ wakenet_name = os.path.basename(wakenet_model_dir)
+ wakenet_dst = os.path.join(sr_models_build_dir, wakenet_name)
+ if copy_directory(wakenet_model_dir, wakenet_dst):
+ models_processed += 1
+ print(f"Added wakenet model: {wakenet_name}")
+
+ # Copy multinet models if available
+ if multinet_model_dirs:
+ for multinet_model_dir in multinet_model_dirs:
+ multinet_name = os.path.basename(multinet_model_dir)
+ multinet_dst = os.path.join(sr_models_build_dir, multinet_name)
+ if copy_directory(multinet_model_dir, multinet_dst):
+ models_processed += 1
+ print(f"Added multinet model: {multinet_name}")
+
+ if models_processed == 0:
+ print("Warning: No SR models were successfully processed")
+ return None
+
+ # Use pack_models function to generate srmodels.bin
+ srmodels_output = os.path.join(sr_models_build_dir, "srmodels.bin")
+ try:
+ pack_models(sr_models_build_dir, "srmodels.bin")
+ print(f"Generated: {srmodels_output}")
+ # Copy srmodels.bin to assets directory
+ copy_file(srmodels_output, os.path.join(assets_dir, "srmodels.bin"))
+ return "srmodels.bin"
+ except Exception as e:
+ print(f"Error: Failed to generate srmodels.bin: {e}")
+ return None
+
+
+def process_text_font(text_font_file, assets_dir):
+ """Process text_font parameter"""
+ if not text_font_file:
+ return None
+
+ # Copy input file to build/assets directory
+ font_filename = os.path.basename(text_font_file)
+ font_dst = os.path.join(assets_dir, font_filename)
+ if copy_file(text_font_file, font_dst):
+ return font_filename
+ return None
+
+
+def process_emoji_collection(emoji_collection_dir, assets_dir):
+ """Process emoji_collection parameter"""
+ if not emoji_collection_dir:
+ return []
+
+ emoji_list = []
+
+ # Check if this is otto-gif collection
+ is_otto_gif = 'otto-emoji-gif-component' in emoji_collection_dir or emoji_collection_dir.endswith('otto-gif')
+
+ # Otto GIF emoji aliases mapping
+ otto_gif_aliases = {
+ "staticstate": ["neutral", "relaxed", "sleepy", "idle"],
+ "happy": ["laughing", "funny", "loving", "confident", "winking", "cool", "delicious", "kissy", "silly"],
+ "sad": ["crying"],
+ "anger": ["angry"],
+ "scare": ["surprised", "shocked"],
+ "buxue": ["thinking", "confused", "embarrassed"]
+ }
+
+ # Copy each image from input directory to build/assets directory
+ for root, dirs, files in os.walk(emoji_collection_dir):
+ for file in files:
+ if file.lower().endswith(('.png', '.gif')):
+ # Copy file
+ src_file = os.path.join(root, file)
+ dst_file = os.path.join(assets_dir, file)
+ if copy_file(src_file, dst_file):
+ # Get filename without extension
+ filename_without_ext = os.path.splitext(file)[0]
+
+ # Add main emoji entry
+ emoji_list.append({
+ "name": filename_without_ext,
+ "file": file
+ })
+
+ # Add aliases for otto-gif emojis
+ if is_otto_gif and filename_without_ext in otto_gif_aliases:
+ for alias in otto_gif_aliases[filename_without_ext]:
+ emoji_list.append({
+ "name": alias,
+ "file": file
+ })
+
+ return emoji_list
+
+
+def process_extra_files(extra_files_dir, assets_dir):
+ """Process default_assets_extra_files parameter"""
+ if not extra_files_dir:
+ return []
+
+ if not os.path.exists(extra_files_dir):
+ print(f"Warning: Extra files directory not found: {extra_files_dir}")
+ return []
+
+ extra_files_list = []
+
+ # Copy each file from input directory to build/assets directory
+ for root, dirs, files in os.walk(extra_files_dir):
+ for file in files:
+ # Skip hidden files and directories
+ if file.startswith('.'):
+ continue
+
+ # Copy file
+ src_file = os.path.join(root, file)
+ dst_file = os.path.join(assets_dir, file)
+ if copy_file(src_file, dst_file):
+ extra_files_list.append(file)
+
+ if extra_files_list:
+ print(f"Processed {len(extra_files_list)} extra files from: {extra_files_dir}")
+
+ return extra_files_list
+
+
+def generate_index_json(assets_dir, srmodels, text_font, emoji_collection, extra_files=None, multinet_model_info=None):
+ """Generate index.json file"""
+ index_data = {
+ "version": 1
+ }
+
+ if srmodels:
+ index_data["srmodels"] = srmodels
+
+ if text_font:
+ index_data["text_font"] = text_font
+
+ if emoji_collection:
+ index_data["emoji_collection"] = emoji_collection
+
+ if extra_files:
+ index_data["extra_files"] = extra_files
+
+ if multinet_model_info:
+ index_data["multinet_model"] = multinet_model_info
+
+ # Write index.json
+ index_path = os.path.join(assets_dir, "index.json")
+ with open(index_path, 'w', encoding='utf-8') as f:
+ json.dump(index_data, f, indent=4, ensure_ascii=False)
+
+ print(f"Generated: {index_path}")
+
+
+def generate_config_json(build_dir, assets_dir):
+ """Generate config.json file"""
+ config_data = {
+ "include_path": os.path.join(build_dir, "include"),
+ "assets_path": assets_dir,
+ "image_file": os.path.join(build_dir, "output", "assets.bin"),
+ "lvgl_ver": "9.3.0",
+ "assets_size": "0x400000",
+ "support_format": ".png, .gif, .jpg, .bin, .json",
+ "name_length": "32",
+ "split_height": "0",
+ "support_qoi": False,
+ "support_spng": False,
+ "support_sjpg": False,
+ "support_sqoi": False,
+ "support_raw": False,
+ "support_raw_dither": False,
+ "support_raw_bgr": False
+ }
+
+ # Write config.json
+ config_path = os.path.join(build_dir, "config.json")
+ with open(config_path, 'w', encoding='utf-8') as f:
+ json.dump(config_data, f, indent=4, ensure_ascii=False)
+
+ print(f"Generated: {config_path}")
+ return config_path
+
+
+# =============================================================================
+# Simplified SPIFFS assets generation (from spiffs_assets_gen.py)
+# =============================================================================
+
+def compute_checksum(data):
+ checksum = sum(data) & 0xFFFF
+ return checksum
+
+
+def sort_key(filename):
+ basename, extension = os.path.splitext(filename)
+ return extension, basename
+
+
+def pack_assets_simple(target_path, include_path, out_file, assets_path, max_name_len=32):
+ """
+ Simplified version of pack_assets that handles basic file packing
+ """
+ merged_data = bytearray()
+ file_info_list = []
+ skip_files = ['config.json']
+
+ # Ensure output directory exists
+ os.makedirs(os.path.dirname(out_file), exist_ok=True)
+ os.makedirs(include_path, exist_ok=True)
+
+ file_list = sorted(os.listdir(target_path), key=sort_key)
+ for filename in file_list:
+ if filename in skip_files:
+ continue
+
+ file_path = os.path.join(target_path, filename)
+ if not os.path.isfile(file_path):
+ continue
+
+ file_name = os.path.basename(file_path)
+ file_size = os.path.getsize(file_path)
+
+ file_info_list.append((file_name, len(merged_data), file_size, 0, 0))
+ # Add 0x5A5A prefix to merged_data
+ merged_data.extend(b'\x5A' * 2)
+
+ with open(file_path, 'rb') as bin_file:
+ bin_data = bin_file.read()
+
+ merged_data.extend(bin_data)
+
+ total_files = len(file_info_list)
+
+ mmap_table = bytearray()
+ for file_name, offset, file_size, width, height in file_info_list:
+ if len(file_name) > max_name_len:
+ print(f'Warning: "{file_name}" exceeds {max_name_len} bytes and will be truncated.')
+ fixed_name = file_name.ljust(max_name_len, '\0')[:max_name_len]
+ mmap_table.extend(fixed_name.encode('utf-8'))
+ mmap_table.extend(file_size.to_bytes(4, byteorder='little'))
+ mmap_table.extend(offset.to_bytes(4, byteorder='little'))
+ mmap_table.extend(width.to_bytes(2, byteorder='little'))
+ mmap_table.extend(height.to_bytes(2, byteorder='little'))
+
+ combined_data = mmap_table + merged_data
+ combined_checksum = compute_checksum(combined_data)
+ combined_data_length = len(combined_data).to_bytes(4, byteorder='little')
+ header_data = total_files.to_bytes(4, byteorder='little') + combined_checksum.to_bytes(4, byteorder='little')
+ final_data = header_data + combined_data_length + combined_data
+
+ with open(out_file, 'wb') as output_bin:
+ output_bin.write(final_data)
+
+ # Generate header file
+ current_year = datetime.now().year
+ asset_name = os.path.basename(assets_path)
+ header_file_path = os.path.join(include_path, f'mmap_generate_{asset_name}.h')
+ with open(header_file_path, 'w', encoding='utf-8') as output_header:
+ output_header.write('/*\n')
+ output_header.write(' * SPDX-FileCopyrightText: 2022-{} Espressif Systems (Shanghai) CO LTD\n'.format(current_year))
+ output_header.write(' *\n')
+ output_header.write(' * SPDX-License-Identifier: Apache-2.0\n')
+ output_header.write(' */\n\n')
+ output_header.write('/**\n')
+ output_header.write(' * @file\n')
+ output_header.write(" * @brief This file was generated by esp_mmap_assets, don't modify it\n")
+ output_header.write(' */\n\n')
+ output_header.write('#pragma once\n\n')
+ output_header.write("#include \"esp_mmap_assets.h\"\n\n")
+ output_header.write(f'#define MMAP_{asset_name.upper()}_FILES {total_files}\n')
+ output_header.write(f'#define MMAP_{asset_name.upper()}_CHECKSUM 0x{combined_checksum:04X}\n\n')
+ output_header.write(f'enum MMAP_{asset_name.upper()}_LISTS {{\n')
+
+ for i, (file_name, _, _, _, _) in enumerate(file_info_list):
+ enum_name = file_name.replace('.', '_')
+ output_header.write(f' MMAP_{asset_name.upper()}_{enum_name.upper()} = {i}, /*!< {file_name} */\n')
+
+ output_header.write('};\n')
+
+ print(f'All files have been merged into {os.path.basename(out_file)}')
+
+
+# =============================================================================
+# Configuration and main functions
+# =============================================================================
+
+def read_wakenet_from_sdkconfig(sdkconfig_path):
+ """
+ Read wakenet models from sdkconfig (based on movemodel.py logic)
+ Returns a list of wakenet model names
+ """
+ if not os.path.exists(sdkconfig_path):
+ print(f"Warning: sdkconfig file not found: {sdkconfig_path}")
+ return []
+
+ models = []
+ with io.open(sdkconfig_path, "r", encoding="utf-8") as f:
+ for label in f:
+ label = label.strip("\n")
+ if 'CONFIG_SR_WN' in label and '#' not in label[0]:
+ if '_NONE' in label:
+ continue
+ if '=' in label:
+ label = label.split("=")[0]
+ if '_MULTI' in label:
+ label = label[:-6]
+ model_name = label.split("_SR_WN_")[-1].lower()
+ models.append(model_name)
+
+ return models
+
+
+def read_multinet_from_sdkconfig(sdkconfig_path):
+ """
+ Read multinet models from sdkconfig (based on movemodel.py logic)
+ Returns a list of multinet model names
+ """
+ if not os.path.exists(sdkconfig_path):
+ print(f"Warning: sdkconfig file not found: {sdkconfig_path}")
+ return []
+
+ with io.open(sdkconfig_path, "r", encoding="utf-8") as f:
+ models_string = ''
+ for label in f:
+ label = label.strip("\n")
+ if 'CONFIG_SR_MN' in label and label[0] != '#':
+ models_string += label
+
+ models = []
+ if "CONFIG_SR_MN_CN_MULTINET3_SINGLE_RECOGNITION" in models_string:
+ models.append('mn3_cn')
+ elif "CONFIG_SR_MN_CN_MULTINET4_5_SINGLE_RECOGNITION_QUANT8" in models_string:
+ models.append('mn4q8_cn')
+ elif "CONFIG_SR_MN_CN_MULTINET4_5_SINGLE_RECOGNITION" in models_string:
+ models.append('mn4_cn')
+ elif "CONFIG_SR_MN_CN_MULTINET5_RECOGNITION_QUANT8" in models_string:
+ models.append('mn5q8_cn')
+ elif "CONFIG_SR_MN_CN_MULTINET6_QUANT" in models_string:
+ models.append('mn6_cn')
+ elif "CONFIG_SR_MN_CN_MULTINET6_AC_QUANT" in models_string:
+ models.append('mn6_cn_ac')
+ elif "CONFIG_SR_MN_CN_MULTINET7_QUANT" in models_string:
+ models.append('mn7_cn')
+ elif "CONFIG_SR_MN_CN_MULTINET7_AC_QUANT" in models_string:
+ models.append('mn7_cn_ac')
+
+ if "CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION_QUANT8" in models_string:
+ models.append('mn5q8_en')
+ elif "CONFIG_SR_MN_EN_MULTINET5_SINGLE_RECOGNITION" in models_string:
+ models.append('mn5_en')
+ elif "CONFIG_SR_MN_EN_MULTINET6_QUANT" in models_string:
+ models.append('mn6_en')
+ elif "CONFIG_SR_MN_EN_MULTINET7_QUANT" in models_string:
+ models.append('mn7_en')
+
+ if "MULTINET6" in models_string or "MULTINET7" in models_string:
+ models.append('fst')
+
+ return models
+
+
+def read_wake_word_type_from_sdkconfig(sdkconfig_path):
+ """
+ Read wake word type configuration from sdkconfig
+ Returns a dict with wake word type info
+ """
+ if not os.path.exists(sdkconfig_path):
+ print(f"Warning: sdkconfig file not found: {sdkconfig_path}")
+ return {
+ 'use_esp_wake_word': False,
+ 'use_afe_wake_word': False,
+ 'use_custom_wake_word': False,
+ 'wake_word_disabled': True
+ }
+
+ config_values = {
+ 'use_esp_wake_word': False,
+ 'use_afe_wake_word': False,
+ 'use_custom_wake_word': False,
+ 'wake_word_disabled': False
+ }
+
+ with io.open(sdkconfig_path, "r", encoding="utf-8") as f:
+ for line in f:
+ line = line.strip("\n")
+ if line.startswith('#'):
+ continue
+
+ # Check for wake word type configuration
+ if 'CONFIG_USE_ESP_WAKE_WORD=y' in line:
+ config_values['use_esp_wake_word'] = True
+ elif 'CONFIG_USE_AFE_WAKE_WORD=y' in line:
+ config_values['use_afe_wake_word'] = True
+ elif 'CONFIG_USE_CUSTOM_WAKE_WORD=y' in line:
+ config_values['use_custom_wake_word'] = True
+ elif 'CONFIG_WAKE_WORD_DISABLED=y' in line:
+ config_values['wake_word_disabled'] = True
+
+ return config_values
+
+
+def read_custom_wake_word_from_sdkconfig(sdkconfig_path):
+ """
+ Read custom wake word configuration from sdkconfig
+ Returns a dict with custom wake word info or None if not configured
+ """
+ if not os.path.exists(sdkconfig_path):
+ print(f"Warning: sdkconfig file not found: {sdkconfig_path}")
+ return None
+
+ config_values = {}
+ with io.open(sdkconfig_path, "r", encoding="utf-8") as f:
+ for line in f:
+ line = line.strip("\n")
+ if line.startswith('#') or '=' not in line:
+ continue
+
+ # Check for custom wake word configuration
+ if 'CONFIG_USE_CUSTOM_WAKE_WORD=y' in line:
+ config_values['use_custom_wake_word'] = True
+ elif 'CONFIG_CUSTOM_WAKE_WORD=' in line and not line.startswith('#'):
+ # Extract string value (remove quotes)
+ value = line.split('=', 1)[1].strip('"')
+ config_values['wake_word'] = value
+ elif 'CONFIG_CUSTOM_WAKE_WORD_DISPLAY=' in line and not line.startswith('#'):
+ # Extract string value (remove quotes)
+ value = line.split('=', 1)[1].strip('"')
+ config_values['display'] = value
+ elif 'CONFIG_CUSTOM_WAKE_WORD_THRESHOLD=' in line and not line.startswith('#'):
+ # Extract numeric value
+ value = line.split('=', 1)[1]
+ try:
+ config_values['threshold'] = int(value)
+ except ValueError:
+ try:
+ config_values['threshold'] = float(value)
+ except ValueError:
+ print(f"Warning: Invalid threshold value: {value}")
+ config_values['threshold'] = 20 # default (will be converted to 0.2)
+
+ # Return config only if custom wake word is enabled and required fields are present
+ if (config_values.get('use_custom_wake_word', False) and
+ 'wake_word' in config_values and
+ 'display' in config_values and
+ 'threshold' in config_values):
+ return {
+ 'wake_word': config_values['wake_word'],
+ 'display': config_values['display'],
+ 'threshold': config_values['threshold'] / 100.0 # Convert to decimal (20 -> 0.2)
+ }
+
+ return None
+
+
+def get_language_from_multinet_models(multinet_models):
+ """
+ Determine language from multinet model names
+ Returns 'cn', 'en', or None
+ """
+ if not multinet_models:
+ return None
+
+ # Check for Chinese models
+ cn_indicators = ['_cn', 'cn_']
+ en_indicators = ['_en', 'en_']
+
+ has_cn = any(any(indicator in model for indicator in cn_indicators) for model in multinet_models)
+ has_en = any(any(indicator in model for indicator in en_indicators) for model in multinet_models)
+
+ # If both or neither, default to cn
+ if has_cn and not has_en:
+ return 'cn'
+ elif has_en and not has_cn:
+ return 'en'
+ else:
+ return 'cn' # Default to Chinese
+
+
+def get_wakenet_model_paths(model_names, esp_sr_model_path):
+ """
+ Get the full paths to the wakenet model directories
+ Returns a list of valid model paths
+ """
+ if not model_names:
+ return []
+
+ valid_paths = []
+ for model_name in model_names:
+ wakenet_model_path = os.path.join(esp_sr_model_path, 'wakenet_model', model_name)
+ if os.path.exists(wakenet_model_path):
+ valid_paths.append(wakenet_model_path)
+ else:
+ print(f"Warning: Wakenet model directory not found: {wakenet_model_path}")
+
+ return valid_paths
+
+
+def get_multinet_model_paths(model_names, esp_sr_model_path):
+ """
+ Get the full paths to the multinet model directories
+ Returns a list of valid model paths
+ """
+ if not model_names:
+ return []
+
+ valid_paths = []
+ for model_name in model_names:
+ multinet_model_path = os.path.join(esp_sr_model_path, 'multinet_model', model_name)
+ if os.path.exists(multinet_model_path):
+ valid_paths.append(multinet_model_path)
+ else:
+ print(f"Warning: Multinet model directory not found: {multinet_model_path}")
+
+ return valid_paths
+
+
+def get_text_font_path(builtin_text_font, xiaozhi_fonts_path):
+ """
+ Get the text font path if needed
+ Returns the font file path or None if no font is needed
+ """
+ if not builtin_text_font or 'basic' not in builtin_text_font:
+ return None
+
+ # Convert from basic to common font name
+ # e.g., font_puhui_basic_16_4 -> font_puhui_common_16_4.bin
+ if builtin_text_font.startswith('font_noto_'):
+ font_name = builtin_text_font.replace('basic', 'qwen') + '.bin'
+ else:
+ font_name = builtin_text_font.replace('basic', 'common') + '.bin'
+ font_path = os.path.join(xiaozhi_fonts_path, 'cbin', font_name)
+
+ if os.path.exists(font_path):
+ return font_path
+ else:
+ print(f"Warning: Font file not found: {font_path}")
+ return None
+
+
+def get_emoji_collection_path(default_emoji_collection, xiaozhi_fonts_path, project_root=None):
+ """
+ Get the emoji collection path if needed
+ Returns the emoji directory path or None if no emoji collection is needed
+
+ Supports:
+ - PNG emoji collections from xiaozhi-fonts (e.g., emojis_32, twemoji_64)
+ - GIF emoji collections from xiaozhi-fonts (e.g., noto-emoji_128, noto-emoji_64)
+ - Otto GIF emoji collection (otto-gif)
+ """
+ if not default_emoji_collection:
+ return None
+
+ # Special handling for otto-gif collection
+ if default_emoji_collection == 'otto-gif':
+ if project_root:
+ otto_gif_path = os.path.join(project_root, 'managed_components',
+ 'txp666__otto-emoji-gif-component', 'gifs')
+ if os.path.exists(otto_gif_path):
+ return otto_gif_path
+ else:
+ print(f"Warning: Otto GIF emoji collection directory not found: {otto_gif_path}")
+ return None
+ else:
+ print("Warning: project_root not provided, cannot locate otto-gif collection")
+ return None
+
+ # Try PNG emoji collections first (e.g., emojis_32, twemoji_64)
+ emoji_path = os.path.join(xiaozhi_fonts_path, 'png', default_emoji_collection)
+ if os.path.exists(emoji_path):
+ return emoji_path
+
+ # Try GIF emoji collections (e.g., noto-emoji_128, noto-emoji_64, noto-emoji_32)
+ emoji_path = os.path.join(xiaozhi_fonts_path, 'gif', default_emoji_collection)
+ if os.path.exists(emoji_path):
+ return emoji_path
+
+ print(f"Warning: Emoji collection directory not found in png/ or gif/: {default_emoji_collection}")
+ return None
+
+
+def build_assets_integrated(wakenet_model_paths, multinet_model_paths, text_font_path, emoji_collection_path, extra_files_path, output_path, multinet_model_info=None):
+ """
+ Build assets using integrated functions (no external dependencies)
+ """
+ # Create temporary build directory
+ temp_build_dir = os.path.join(os.path.dirname(output_path), "temp_build")
+ assets_dir = os.path.join(temp_build_dir, "assets")
+
+ try:
+ # Clean and create directories
+ if os.path.exists(temp_build_dir):
+ shutil.rmtree(temp_build_dir)
+ ensure_dir(temp_build_dir)
+ ensure_dir(assets_dir)
+
+ print("Starting to build assets...")
+
+ # Process each component
+ srmodels = process_sr_models(wakenet_model_paths, multinet_model_paths, temp_build_dir, assets_dir) if (wakenet_model_paths or multinet_model_paths) else None
+ text_font = process_text_font(text_font_path, assets_dir) if text_font_path else None
+ emoji_collection = process_emoji_collection(emoji_collection_path, assets_dir) if emoji_collection_path else None
+ extra_files = process_extra_files(extra_files_path, assets_dir) if extra_files_path else None
+
+ # Generate index.json
+ generate_index_json(assets_dir, srmodels, text_font, emoji_collection, extra_files, multinet_model_info)
+
+ # Generate config.json for packing
+ config_path = generate_config_json(temp_build_dir, assets_dir)
+
+ # Load config and pack assets
+ with open(config_path, 'r', encoding='utf-8') as f:
+ config_data = json.load(f)
+
+ # Use simplified packing function
+ include_path = config_data['include_path']
+ image_file = config_data['image_file']
+ pack_assets_simple(assets_dir, include_path, image_file, "assets", int(config_data['name_length']))
+
+ # Copy final assets.bin to output location
+ if os.path.exists(image_file):
+ shutil.copy2(image_file, output_path)
+ print(f"Successfully generated assets.bin: {output_path}")
+
+ # Show size information
+ total_size = os.path.getsize(output_path)
+ print(f"Assets file size: {total_size / 1024:.2f}K ({total_size} bytes)")
+
+ return True
+ else:
+ print(f"Error: Generated assets.bin not found: {image_file}")
+ return False
+
+ except Exception as e:
+ print(f"Error: Failed to build assets: {e}")
+ return False
+ finally:
+ # Clean up temporary directory
+ if os.path.exists(temp_build_dir):
+ shutil.rmtree(temp_build_dir)
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Build default assets based on configuration')
+ parser.add_argument('--sdkconfig', required=True, help='Path to sdkconfig file')
+ parser.add_argument('--builtin_text_font', help='Builtin text font name (e.g., font_puhui_basic_16_4)')
+ parser.add_argument('--emoji_collection', help='Default emoji collection name (e.g., emojis_32)')
+ parser.add_argument('--output', required=True, help='Output path for assets.bin')
+ parser.add_argument('--esp_sr_model_path', help='Path to ESP-SR model directory')
+ parser.add_argument('--xiaozhi_fonts_path', help='Path to xiaozhi-fonts component directory')
+ parser.add_argument('--extra_files', help='Path to extra files directory to be included in assets')
+
+ args = parser.parse_args()
+
+ # Set default paths if not provided
+ if not args.esp_sr_model_path or not args.xiaozhi_fonts_path:
+ # Calculate project root from script location
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ project_root = os.path.dirname(script_dir)
+
+ if not args.esp_sr_model_path:
+ args.esp_sr_model_path = os.path.join(project_root, "managed_components", "espressif__esp-sr", "model")
+
+ if not args.xiaozhi_fonts_path:
+ args.xiaozhi_fonts_path = os.path.join(project_root, "components", "xiaozhi-fonts")
+
+ print("Building default assets...")
+ print(f" sdkconfig: {args.sdkconfig}")
+ print(f" builtin_text_font: {args.builtin_text_font}")
+ print(f" emoji_collection: {args.emoji_collection}")
+ print(f" output: {args.output}")
+
+ # Read wake word type configuration from sdkconfig
+ wake_word_config = read_wake_word_type_from_sdkconfig(args.sdkconfig)
+
+ # Read SR models from sdkconfig
+ wakenet_model_names = read_wakenet_from_sdkconfig(args.sdkconfig)
+ multinet_model_names = read_multinet_from_sdkconfig(args.sdkconfig)
+
+ # Apply wake word logic to decide which models to package
+ wakenet_model_paths = []
+ multinet_model_paths = []
+
+ # 1. Only package wakenet models if USE_ESP_WAKE_WORD=y or USE_AFE_WAKE_WORD=y
+ if wake_word_config['use_esp_wake_word'] or wake_word_config['use_afe_wake_word']:
+ wakenet_model_paths = get_wakenet_model_paths(wakenet_model_names, args.esp_sr_model_path)
+ elif wakenet_model_names:
+ print(f" Note: Found wakenet models {wakenet_model_names} but wake word type is not ESP/AFE, skipping")
+
+ # 2. Error check: if USE_CUSTOM_WAKE_WORD=y but no multinet models selected, report error
+ if wake_word_config['use_custom_wake_word'] and not multinet_model_names:
+ print("Error: USE_CUSTOM_WAKE_WORD is enabled but no multinet models are selected in sdkconfig")
+ print("Please select appropriate CONFIG_SR_MN_* options in menuconfig, or disable USE_CUSTOM_WAKE_WORD")
+ sys.exit(1)
+
+ # 3. Only package multinet models if USE_CUSTOM_WAKE_WORD=y
+ if wake_word_config['use_custom_wake_word']:
+ multinet_model_paths = get_multinet_model_paths(multinet_model_names, args.esp_sr_model_path)
+ elif multinet_model_names:
+ print(f" Note: Found multinet models {multinet_model_names} but USE_CUSTOM_WAKE_WORD is disabled, skipping")
+
+ # Print model information (only for models that will actually be packaged)
+ if wakenet_model_paths:
+ print(f" wakenet models: {', '.join(wakenet_model_names)} (will be packaged)")
+ if multinet_model_paths:
+ print(f" multinet models: {', '.join(multinet_model_names)} (will be packaged)")
+
+ # Get text font path if needed
+ text_font_path = get_text_font_path(args.builtin_text_font, args.xiaozhi_fonts_path)
+
+ # Get emoji collection path if needed
+ # Calculate project root from script location for otto-gif support
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+ project_root = os.path.dirname(script_dir)
+ emoji_collection_path = get_emoji_collection_path(args.emoji_collection, args.xiaozhi_fonts_path, project_root)
+
+ # Get extra files path if provided
+ extra_files_path = args.extra_files
+
+ # Read custom wake word configuration
+ custom_wake_word_config = read_custom_wake_word_from_sdkconfig(args.sdkconfig)
+ multinet_model_info = None
+
+ if custom_wake_word_config and multinet_model_paths:
+ # Determine language from multinet models
+ language = get_language_from_multinet_models(multinet_model_names)
+
+ # Build multinet_model info structure
+ multinet_model_info = {
+ "language": language,
+ "duration": 3000, # Default duration in ms
+ "threshold": custom_wake_word_config['threshold'],
+ "commands": [
+ {
+ "command": custom_wake_word_config['wake_word'],
+ "text": custom_wake_word_config['display'],
+ "action": "wake"
+ }
+ ]
+ }
+ print(f" custom wake word: {custom_wake_word_config['wake_word']} ({custom_wake_word_config['display']})")
+ print(f" wake word language: {language}")
+ print(f" wake word threshold: {custom_wake_word_config['threshold']}")
+
+ # Check if we have anything to build
+ if not wakenet_model_paths and not multinet_model_paths and not text_font_path and not emoji_collection_path and not extra_files_path and not multinet_model_info:
+ print("Warning: No assets to build (no SR models, text font, emoji collection, extra files, or custom wake word)")
+ # Create an empty assets.bin file
+ os.makedirs(os.path.dirname(args.output), exist_ok=True)
+ with open(args.output, 'wb') as f:
+ pass # Create empty file
+ print(f"Created empty assets.bin: {args.output}")
+ return
+
+ # Build the assets
+ success = build_assets_integrated(wakenet_model_paths, multinet_model_paths, text_font_path, emoji_collection_path,
+ extra_files_path, args.output, multinet_model_info)
+
+ if not success:
+ sys.exit(1)
+
+ print("Build completed successfully!")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/xiaozhi-esp32-2.2.6/scripts/download_github_runs.py b/xiaozhi-esp32-2.2.6/scripts/download_github_runs.py
new file mode 100644
index 0000000..d8bf0ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/download_github_runs.py
@@ -0,0 +1,294 @@
+#!/usr/bin/env python3
+"""
+Download GitHub Actions artifacts and rename them with version numbers.
+
+Usage:
+ python download_github_runs.py 2.0.4 https://github.com/78/xiaozhi-esp32/actions/runs/18866246016
+
+Output:
+ Files are downloaded to releases/<version>/ directory relative to the project root.
+ Example: releases/2.0.4/v2.0.4_atk-dnesp32s3-box0.zip
+"""
+
+import argparse
+import os
+import re
+import sys
+import zipfile
+from pathlib import Path
+from urllib.parse import urlparse
+
+import requests
+from dotenv import load_dotenv
+
+
+def parse_github_run_url(url: str) -> tuple[str, str, str]:
+ """
+ Parse GitHub Actions run URL to extract owner, repo, and run_id.
+
+ Args:
+ url: GitHub Actions run URL
+
+ Returns:
+ Tuple of (owner, repo, run_id)
+ """
+ # Example: https://github.com/78/xiaozhi-esp32/actions/runs/18866246016
+ pattern = r'github\.com/([^/]+)/([^/]+)/actions/runs/(\d+)'
+ match = re.search(pattern, url)
+
+ if not match:
+ raise ValueError(f"Invalid GitHub Actions URL: {url}")
+
+ owner, repo, run_id = match.groups()
+ return owner, repo, run_id
+
+
+def get_artifacts(owner: str, repo: str, run_id: str, token: str) -> list[dict]:
+ """
+ Get all artifacts for a specific workflow run (with pagination support).
+
+ Args:
+ owner: Repository owner
+ repo: Repository name
+ run_id: Workflow run ID
+ token: GitHub personal access token
+
+ Returns:
+ List of artifact dictionaries
+ """
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28"
+ }
+
+ all_artifacts = []
+ page = 1
+ per_page = 100 # Maximum allowed by GitHub API
+
+ while True:
+ url = f"https://api.github.com/repos/{owner}/{repo}/actions/runs/{run_id}/artifacts"
+ params = {
+ "page": page,
+ "per_page": per_page
+ }
+
+ response = requests.get(url, headers=headers, params=params)
+ response.raise_for_status()
+
+ data = response.json()
+ artifacts = data.get("artifacts", [])
+
+ if not artifacts:
+ break
+
+ all_artifacts.extend(artifacts)
+
+ # Check if there are more pages
+ total_count = data.get("total_count", 0)
+ if len(all_artifacts) >= total_count:
+ break
+
+ page += 1
+
+ return all_artifacts
+
+
+def download_artifact(artifact_url: str, token: str, output_path: Path) -> None:
+ """
+ Download an artifact from GitHub.
+
+ Args:
+ artifact_url: Artifact download URL
+ token: GitHub personal access token
+ output_path: Path to save the downloaded artifact
+ """
+ headers = {
+ "Authorization": f"Bearer {token}",
+ "Accept": "application/vnd.github+json",
+ "X-GitHub-Api-Version": "2022-11-28"
+ }
+
+ response = requests.get(artifact_url, headers=headers, stream=True)
+ response.raise_for_status()
+
+ # Create parent directory if it doesn't exist
+ output_path.parent.mkdir(parents=True, exist_ok=True)
+
+ # Download the file
+ with open(output_path, 'wb') as f:
+ for chunk in response.iter_content(chunk_size=8192):
+ if chunk:
+ f.write(chunk)
+
+
+def rename_artifact(original_name: str, version: str) -> str:
+ """
+ Rename artifact according to the specified rules.
+
+ Rules:
+ - Remove "xiaozhi_" prefix
+ - Remove hash suffix (underscore followed by hex string)
+ - Add version prefix (e.g., "v2.0.4_")
+ - Add .zip extension
+
+ Examples:
+ xiaozhi_atk-dnesp32s3-box0_43ef2f4e7f0957dc62ec7d628ac2819d226127b8
+ -> v2.0.4_atk-dnesp32s3-box0.zip
+
+ xiaozhi_waveshare-esp32-p4-nano-10.1-a_43ef2f4e7f0957dc62ec7d628ac2819d226127b8
+ -> v2.0.4_waveshare-esp32-p4-nano-10.1-a.zip
+
+ Args:
+ original_name: Original artifact name
+ version: Version string (e.g., "2.0.4")
+
+ Returns:
+ New filename
+ """
+ # Remove "xiaozhi_" prefix
+ name = original_name
+ if name.startswith("xiaozhi_"):
+ name = name[len("xiaozhi_"):]
+
+ # Remove known extensions only (not using splitext to avoid issues with
+ # names containing dots like "esp32-s3-touch-amoled-2.06")
+ known_extensions = ('.bin', '.zip')
+ for ext in known_extensions:
+ if name.endswith(ext):
+ name = name[:-len(ext)]
+ break
+
+ # Remove hash suffix (pattern: underscore followed by 40+ hex characters)
+ # This matches Git commit hashes and similar identifiers
+ name_without_hash = re.sub(r'_[a-f0-9]{40,}$', '', name)
+
+ # Add version prefix and .zip extension
+ new_name = f"v{version}_{name_without_hash}.zip"
+
+ return new_name
+
+
+def get_default_releases_dir() -> Path:
+ """
+ Get the default releases directory path relative to this script's location.
+
+ Returns:
+ Path to the releases directory (script_dir/../releases)
+ """
+ script_dir = Path(__file__).resolve().parent
+ return script_dir.parent / "releases"
+
+
+def main():
+ """Main function to download and rename GitHub Actions artifacts."""
+ parser = argparse.ArgumentParser(
+ description="Download GitHub Actions artifacts and rename them with version numbers."
+ )
+ parser.add_argument(
+ "version",
+ help="Version number (e.g., 2.0.4)"
+ )
+ parser.add_argument(
+ "url",
+ help="GitHub Actions run URL (e.g., https://github.com/owner/repo/actions/runs/12345)"
+ )
+ parser.add_argument(
+ "--output-dir",
+ default=None,
+ help="Output directory for downloaded artifacts (default: releases/<version> relative to project root)"
+ )
+
+ args = parser.parse_args()
+
+ # Load GitHub token from .env file
+ load_dotenv()
+ github_token = os.getenv("GITHUB_TOKEN")
+
+ if not github_token:
+ print("Error: GITHUB_TOKEN not found in environment variables.", file=sys.stderr)
+ print("Please create a .env file with GITHUB_TOKEN=your_token_here", file=sys.stderr)
+ sys.exit(1)
+
+ try:
+ # Parse the GitHub URL
+ owner, repo, run_id = parse_github_run_url(args.url)
+ print(f"Repository: {owner}/{repo}")
+ print(f"Run ID: {run_id}")
+ print(f"Version: {args.version}")
+ print()
+
+ # Get artifacts
+ print("Fetching artifacts...")
+ artifacts = get_artifacts(owner, repo, run_id, github_token)
+
+ if not artifacts:
+ print("No artifacts found for this run.")
+ return
+
+ print(f"Found {len(artifacts)} artifact(s):")
+ for artifact in artifacts:
+ print(f" - {artifact['name']}")
+ print()
+
+ # Determine output directory
+ if args.output_dir:
+ # User specified custom output directory
+ output_dir = Path(args.output_dir) / args.version
+ else:
+ # Default: releases/<version> relative to script location
+ output_dir = get_default_releases_dir() / args.version
+
+ # Create output directory
+ output_dir.mkdir(parents=True, exist_ok=True)
+
+ # Download and rename each artifact
+ downloaded_count = 0
+ skipped_count = 0
+
+ for artifact in artifacts:
+ original_name = artifact['name']
+ new_name = rename_artifact(original_name, args.version)
+ final_path = output_dir / new_name
+
+ # Check if file already exists
+ if final_path.exists():
+ print(f"Skipping (already exists): {original_name}")
+ print(f" -> {new_name}")
+ print(f" File: {final_path}")
+ print()
+ skipped_count += 1
+ continue
+
+ print(f"Downloading: {original_name}")
+ print(f" -> {new_name}")
+
+ # Download to temporary path first
+ temp_path = output_dir / f"{original_name}.zip"
+ download_artifact(
+ artifact['archive_download_url'],
+ github_token,
+ temp_path
+ )
+
+ # Rename to final name
+ temp_path.rename(final_path)
+
+ print(f" Saved to: {final_path}")
+ print()
+ downloaded_count += 1
+
+ print(f"Summary:")
+ print(f" Downloaded: {downloaded_count} artifact(s)")
+ print(f" Skipped: {skipped_count} artifact(s)")
+ print(f" Total: {len(artifacts)} artifact(s)")
+ print(f" Output directory: {output_dir.absolute()}")
+
+ except Exception as e:
+ print(f"Error: {e}", file=sys.stderr)
+ sys.exit(1)
+
+
+if __name__ == "__main__":
+ main()
+
diff --git a/xiaozhi-esp32-2.2.6/scripts/gen_lang.py b/xiaozhi-esp32-2.2.6/scripts/gen_lang.py
new file mode 100644
index 0000000..81c0326
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/gen_lang.py
@@ -0,0 +1,187 @@
+#!/usr/bin/env python3
+import argparse
+import json
+import os
+
+HEADER_TEMPLATE = """// Auto-generated language config
+// Language: {lang_code} with en-US fallback
+#pragma once
+
+#include <string_view>
+
+#ifndef {lang_code_for_font}
+ #define {lang_code_for_font} // 闋愯ō瑾炶█
+#endif
+
+namespace Lang {{
+ // 璇█鍏冩暟鎹�
+ constexpr const char* CODE = "{lang_code}";
+
+ // 瀛楃涓茶祫婧� (en-US as fallback for missing keys)
+ namespace Strings {{
+{strings}
+ }}
+
+ // 闊虫晥璧勬簮 (en-US as fallback for missing audio files)
+ namespace Sounds {{
+{sounds}
+ }}
+}}
+"""
+
+def load_base_language(assets_dir):
+ """鍔犺浇 en-US 鍩哄噯璇█鏁版嵁"""
+ base_lang_path = os.path.join(assets_dir, 'locales', 'en-US', 'language.json')
+ if os.path.exists(base_lang_path):
+ try:
+ with open(base_lang_path, 'r', encoding='utf-8') as f:
+ base_data = json.load(f)
+ print(f"Loaded base language en-US with {len(base_data.get('strings', {}))} strings")
+ return base_data
+ except json.JSONDecodeError as e:
+ print(f"Warning: Failed to parse en-US language file: {e}")
+ else:
+ print("Warning: en-US base language file not found, fallback mechanism disabled")
+ return {'strings': {}}
+
+def get_sound_files(directory):
+ """鑾峰彇鐩綍涓殑闊虫晥鏂囦欢鍒楄〃"""
+ if not os.path.exists(directory):
+ return []
+ return [f for f in os.listdir(directory) if f.endswith('.ogg')]
+
+def generate_header(lang_code, output_path):
+ # 浠庤緭鍑鸿矾寰勬帹瀵奸」鐩粨鏋�
+ # output_path 閫氬父鏄� main/assets/lang_config.h
+ main_dir = os.path.dirname(output_path) # main/assets
+ if os.path.basename(main_dir) == 'assets':
+ main_dir = os.path.dirname(main_dir) # main
+ project_dir = os.path.dirname(main_dir) # 椤圭洰鏍圭洰褰�
+ assets_dir = os.path.join(main_dir, 'assets')
+
+ # 鏋勫缓璇█JSON鏂囦欢璺緞
+ input_path = os.path.join(assets_dir, 'locales', lang_code, 'language.json')
+
+ print(f"Processing language: {lang_code}")
+ print(f"Input file path: {input_path}")
+ print(f"Output file path: {output_path}")
+
+ if not os.path.exists(input_path):
+ raise FileNotFoundError(f"Language file not found: {input_path}")
+
+ with open(input_path, 'r', encoding='utf-8') as f:
+ data = json.load(f)
+
+ # 楠岃瘉鏁版嵁缁撴瀯
+ if 'language' not in data or 'strings' not in data:
+ raise ValueError("Invalid JSON structure")
+
+ # 鍔犺浇 en-US 鍩哄噯璇█鏁版嵁
+ base_data = load_base_language(assets_dir)
+
+ # 鍚堝苟瀛楃涓诧細浠� en-US 涓哄熀鍑嗭紝鐢ㄦ埛璇█瑕嗙洊
+ base_strings = base_data.get('strings', {})
+ user_strings = data['strings']
+ merged_strings = base_strings.copy()
+ merged_strings.update(user_strings)
+
+ # 缁熻淇℃伅
+ base_count = len(base_strings)
+ user_count = len(user_strings)
+ total_count = len(merged_strings)
+ fallback_count = total_count - user_count
+
+ print(f"Language {lang_code} string statistics:")
+ print(f" - Base language (en-US): {base_count} strings")
+ print(f" - User language: {user_count} strings")
+ print(f" - Total: {total_count} strings")
+ if fallback_count > 0:
+ print(f" - Fallback to en-US: {fallback_count} strings")
+
+ # 鐢熸垚瀛楃涓插父閲�
+ strings = []
+ sounds = []
+ for key, value in merged_strings.items():
+ value = value.replace('"', '\\"')
+ strings.append(f' constexpr const char* {key.upper()} = "{value}";')
+
+ # 鏀堕泦闊虫晥鏂囦欢锛氫互 en-US 涓哄熀鍑嗭紝鐢ㄦ埛璇█瑕嗙洊
+ current_lang_dir = os.path.join(assets_dir, 'locales', lang_code)
+ base_lang_dir = os.path.join(assets_dir, 'locales', 'en-US')
+ common_dir = os.path.join(assets_dir, 'common')
+
+ # 鑾峰彇鎵�鏈夊彲鑳界殑闊虫晥鏂囦欢
+ base_sounds = get_sound_files(base_lang_dir)
+ current_sounds = get_sound_files(current_lang_dir)
+ common_sounds = get_sound_files(common_dir)
+
+ # 鍚堝苟闊虫晥鏂囦欢鍒楄〃锛氱敤鎴疯瑷�瑕嗙洊鍩哄噯璇█
+ all_sound_files = set(base_sounds)
+ all_sound_files.update(current_sounds)
+
+ # 闊虫晥缁熻淇℃伅
+ base_sound_count = len(base_sounds)
+ user_sound_count = len(current_sounds)
+ common_sound_count = len(common_sounds)
+ sound_fallback_count = len(set(base_sounds) - set(current_sounds))
+
+ print(f"Language {lang_code} sound statistics:")
+ print(f" - Base language (en-US): {base_sound_count} sounds")
+ print(f" - User language: {user_sound_count} sounds")
+ print(f" - Common sounds: {common_sound_count} sounds")
+ if sound_fallback_count > 0:
+ print(f" - Sound fallback to en-US: {sound_fallback_count} sounds")
+
+ # 鐢熸垚璇█鐗瑰畾闊虫晥甯搁噺
+ for file in sorted(all_sound_files):
+ base_name = os.path.splitext(file)[0]
+ # 浼樺厛浣跨敤褰撳墠璇█鐨勯煶鏁堬紝濡傛灉涓嶅瓨鍦ㄥ垯鍥為��鍒� en-US
+ if file in current_sounds:
+ sound_lang = lang_code.replace('-', '_').lower()
+ else:
+ sound_lang = 'en_us'
+
+ sounds.append(f'''
+ extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start");
+ extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end");
+ static const std::string_view OGG_{base_name.upper()} {{
+ static_cast<const char*>(ogg_{base_name}_start),
+ static_cast<size_t>(ogg_{base_name}_end - ogg_{base_name}_start)
+ }};''')
+
+ # 鐢熸垚鍏叡闊虫晥甯搁噺
+ for file in sorted(common_sounds):
+ base_name = os.path.splitext(file)[0]
+ sounds.append(f'''
+ extern const char ogg_{base_name}_start[] asm("_binary_{base_name}_ogg_start");
+ extern const char ogg_{base_name}_end[] asm("_binary_{base_name}_ogg_end");
+ static const std::string_view OGG_{base_name.upper()} {{
+ static_cast<const char*>(ogg_{base_name}_start),
+ static_cast<size_t>(ogg_{base_name}_end - ogg_{base_name}_start)
+ }};''')
+
+ # 濉厖妯℃澘
+ content = HEADER_TEMPLATE.format(
+ lang_code=lang_code,
+ lang_code_for_font=lang_code.replace('-', '_').lower(),
+ strings="\n".join(sorted(strings)),
+ sounds="\n".join(sorted(sounds))
+ )
+
+ # 鍐欏叆鏂囦欢
+ os.makedirs(os.path.dirname(output_path), exist_ok=True)
+ with open(output_path, 'w', encoding='utf-8') as f:
+ f.write(content)
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description="Generate language configuration header file with en-US fallback")
+ parser.add_argument("--language", required=True, help="Language code (e.g: zh-CN, en-US, ja-JP)")
+ parser.add_argument("--output", required=True, help="Output header file path")
+ args = parser.parse_args()
+
+ try:
+ generate_header(args.language, args.output)
+ print(f"Successfully generated language config file: {args.output}")
+ except Exception as e:
+ print(f"Error: {e}")
+ exit(1)
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/mp3_to_ogg.sh b/xiaozhi-esp32-2.2.6/scripts/mp3_to_ogg.sh
new file mode 100644
index 0000000..2cb870a
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/mp3_to_ogg.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+# mp3_to_ogg.sh <input_mp3_file> <output_ogg_file>
+ffmpeg -i $1 -c:a libopus -b:a 16k -ac 1 -ar 16000 -frame_duration 60 $2
diff --git a/xiaozhi-esp32-2.2.6/scripts/ogg_converter/README.md b/xiaozhi-esp32-2.2.6/scripts/ogg_converter/README.md
new file mode 100644
index 0000000..eecbe42
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/ogg_converter/README.md
@@ -0,0 +1,36 @@
+# ogg_covertor 灏忔櫤AI OGG 闊虫晥鎵归噺杞崲鍣�
+
+鏈剼鏈负OGG鎵归噺杞崲宸ュ叿锛屾敮鎸佸皢杈撳叆鐨勯煶棰戞枃浠惰浆鎹负灏忔櫤鍙娇鐢ㄧ殑OGG鏍煎紡
+
+鍩轰簬Python绗笁鏂瑰簱 `ffmpeg-python` 瀹炵幇锛�**闇�瑕�** `ffmpeg` 鐜
+
+鍙墠寰�[姝ゅ](https://ffmpeg.org/download.html)涓嬭浇瀵瑰簲浣犺嚜宸辩郴缁熺殑ffmpeg鍙戣鐗堬紝骞舵坊鍔犲埌鐜鍙橀噺鎴栬�呮斁鍦ㄨ剼鏈墍鍦ㄧ洰褰�
+
+鏀寔OGG鍜岄煶棰戜箣闂寸殑浜掕浆锛屽搷搴﹁皟鑺傜瓑鍔熻兘
+
+# 鍒涘缓骞舵縺娲昏櫄鎷熺幆澧�
+
+```bash
+# 鍒涘缓铏氭嫙鐜
+python -m venv venv
+# 婵�娲昏櫄鎷熺幆澧�
+source venv/bin/activate # Mac/Linux
+venv\Scripts\activate # Windows
+```
+# 涓嬭浇FFmpeg
+鍓嶅線[姝ゅ](https://ffmpeg.org/download.html)涓嬭浇ffmpeg
+
+鏍规嵁浣犲綋鍓嶇殑绯荤粺涓嬭浇瀵瑰簲鐨勭増鏈紝骞跺皢`ffmpeg`鐨勫彲鎵ц鏂囦欢鏀剧疆鍦ㄨ剼鏈墍鍦ㄧ洰褰曟垨鑰呮坊鍔犲彲鎵ц鏂囦欢鎵�鍦ㄧ洰褰曞埌鐜鍙橀噺
+
+# 瀹夎渚濊禆
+璇峰湪铏氭嫙鐜涓墽琛�
+
+```bash
+pip install ffmpeg-python
+```
+
+# 杩愯鑴氭湰
+```bash
+python ogg_covertor.py
+```
+
diff --git a/xiaozhi-esp32-2.2.6/scripts/ogg_converter/xiaozhi_ogg_converter.py b/xiaozhi-esp32-2.2.6/scripts/ogg_converter/xiaozhi_ogg_converter.py
new file mode 100644
index 0000000..5c3ddb2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/ogg_converter/xiaozhi_ogg_converter.py
@@ -0,0 +1,230 @@
+import tkinter as tk
+from tkinter import ttk, filedialog, messagebox
+import os
+import threading
+import sys
+import ffmpeg
+
+class AudioConverterApp:
+ def __init__(self, master):
+ self.master = master
+ master.title("灏忔櫤AI OGG闊抽鎵归噺杞崲宸ュ叿")
+ master.geometry("680x600") # 璋冩暣绐楀彛楂樺害
+
+ # 鍒濆鍖栧彉閲�
+ self.mode = tk.StringVar(value="audio_to_ogg")
+ self.output_dir = tk.StringVar()
+ self.output_dir.set(os.path.abspath("output"))
+ self.enable_loudnorm = tk.BooleanVar(value=True)
+ self.target_lufs = tk.DoubleVar(value=-16.0)
+
+ # 鍒涘缓UI缁勪欢
+ self.create_widgets()
+ self.redirect_output()
+
+ def create_widgets(self):
+ # 妯″紡閫夋嫨
+ mode_frame = ttk.LabelFrame(self.master, text="杞崲妯″紡")
+ mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
+
+ ttk.Radiobutton(mode_frame, text="闊抽杞埌OGG", variable=self.mode,
+ value="audio_to_ogg", command=self.toggle_settings,
+ width=12).grid(row=0, column=0, padx=5)
+ ttk.Radiobutton(mode_frame, text="OGG杞洖闊抽", variable=self.mode,
+ value="ogg_to_audio", command=self.toggle_settings,
+ width=12).grid(row=0, column=1, padx=5)
+
+ # 鍝嶅害璁剧疆
+ self.loudnorm_frame = ttk.Frame(self.master)
+ self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
+
+ ttk.Checkbutton(self.loudnorm_frame, text="鍚敤鍝嶅害璋冩暣",
+ variable=self.enable_loudnorm, width=15
+ ).grid(row=0, column=0, padx=2)
+ ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs,
+ width=6).grid(row=0, column=1, padx=2)
+ ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2)
+
+ # 鏂囦欢閫夋嫨
+ file_frame = ttk.LabelFrame(self.master, text="杈撳叆鏂囦欢")
+ file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
+
+ # 鏂囦欢鎿嶄綔鎸夐挳
+ ttk.Button(file_frame, text="閫夋嫨鏂囦欢", command=self.select_files,
+ width=12).grid(row=0, column=0, padx=5, pady=2)
+ ttk.Button(file_frame, text="绉婚櫎閫変腑", command=self.remove_selected,
+ width=12).grid(row=0, column=1, padx=5, pady=2)
+ ttk.Button(file_frame, text="娓呯┖鍒楄〃", command=self.clear_files,
+ width=12).grid(row=0, column=2, padx=5, pady=2)
+
+ # 鏂囦欢鍒楄〃锛堜娇鐢═reeview锛�
+ self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
+ show="headings", height=8)
+ self.tree.heading("selected", text="閫変腑", anchor=tk.W)
+ self.tree.heading("filename", text="鏂囦欢鍚�", anchor=tk.W)
+ self.tree.column("selected", width=60, anchor=tk.W)
+ self.tree.column("filename", width=600, anchor=tk.W)
+ self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2)
+ self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
+
+ # 杈撳嚭鐩綍
+ output_frame = ttk.LabelFrame(self.master, text="杈撳嚭鐩綍")
+ output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
+
+ ttk.Entry(output_frame, textvariable=self.output_dir, width=60
+ ).grid(row=0, column=0, padx=5, sticky="ew")
+ ttk.Button(output_frame, text="娴忚", command=self.select_output_dir,
+ width=8).grid(row=0, column=1, padx=5)
+
+ # 杞崲鎸夐挳鍖哄煙
+ button_frame = ttk.Frame(self.master)
+ button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew")
+
+ ttk.Button(button_frame, text="杞崲鍏ㄩ儴鏂囦欢", command=lambda: self.start_conversion(True),
+ width=15).pack(side=tk.LEFT, padx=5)
+ ttk.Button(button_frame, text="杞崲閫変腑鏂囦欢", command=lambda: self.start_conversion(False),
+ width=15).pack(side=tk.LEFT, padx=5)
+
+ # 鏃ュ織鍖哄煙
+ log_frame = ttk.LabelFrame(self.master, text="鏃ュ織")
+ log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
+
+ self.log_text = tk.Text(log_frame, height=14, width=80)
+ self.log_text.pack(fill=tk.BOTH, expand=True)
+
+ # 閰嶇疆甯冨眬鏉冮噸
+ self.master.columnconfigure(0, weight=1)
+ self.master.rowconfigure(2, weight=1)
+ self.master.rowconfigure(5, weight=3)
+ file_frame.columnconfigure(0, weight=1)
+ file_frame.rowconfigure(1, weight=1)
+
+ def toggle_settings(self):
+ if self.mode.get() == "audio_to_ogg":
+ self.loudnorm_frame.grid()
+ else:
+ self.loudnorm_frame.grid_remove()
+
+ def select_files(self):
+ file_types = [
+ ("闊抽鏂囦欢", "*.wav *.mogg *.ogg *.flac") if self.mode.get() == "audio_to_ogg"
+ else ("ogg鏂囦欢", "*.ogg")
+ ]
+
+ files = filedialog.askopenfilenames(filetypes=file_types)
+ for f in files:
+ self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
+
+ def on_tree_click(self, event):
+ """澶勭悊澶嶉�夋鐐瑰嚮浜嬩欢"""
+ region = self.tree.identify("region", event.x, event.y)
+ if region == "cell":
+ col = self.tree.identify_column(event.x)
+ item = self.tree.identify_row(event.y)
+ if col == "#1": # 鐐瑰嚮鐨勬槸閫変腑鍒�
+ current_val = self.tree.item(item, "values")[0]
+ new_val = "[鈭歖" if current_val == "[ ]" else "[ ]"
+ self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
+
+ def remove_selected(self):
+ """绉婚櫎閫変腑鐨勬枃浠�"""
+ to_remove = []
+ for item in self.tree.get_children():
+ if self.tree.item(item, "values")[0] == "[鈭歖":
+ to_remove.append(item)
+ for item in reversed(to_remove):
+ self.tree.delete(item)
+
+ def clear_files(self):
+ """娓呯┖鎵�鏈夋枃浠�"""
+ for item in self.tree.get_children():
+ self.tree.delete(item)
+
+ def select_output_dir(self):
+ path = filedialog.askdirectory()
+ if path:
+ self.output_dir.set(path)
+
+ def redirect_output(self):
+ class StdoutRedirector:
+ def __init__(self, text_widget):
+ self.text_widget = text_widget
+ self.original_stdout = sys.stdout
+
+ def write(self, message):
+ self.text_widget.insert(tk.END, message)
+ self.text_widget.see(tk.END)
+ self.original_stdout.write(message)
+
+ def flush(self):
+ self.original_stdout.flush()
+
+ sys.stdout = StdoutRedirector(self.log_text)
+
+ def start_conversion(self, convert_all):
+ """寮�濮嬭浆鎹�"""
+ input_files = []
+ for item in self.tree.get_children():
+ if convert_all or self.tree.item(item, "values")[0] == "[鈭歖":
+ input_files.append(self.tree.item(item, "tags")[0])
+
+ if not input_files:
+ msg = "娌℃湁鎵惧埌鍙浆鎹㈢殑鏂囦欢" if convert_all else "娌℃湁閫変腑浠讳綍鏂囦欢"
+ messagebox.showwarning("璀﹀憡", msg)
+ return
+
+ os.makedirs(self.output_dir.get(), exist_ok=True)
+
+ try:
+ if self.mode.get() == "audio_to_ogg":
+ target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None
+ thread = threading.Thread(target=self.convert_audio_to_ogg, args=(target_lufs, input_files))
+ else:
+ thread = threading.Thread(target=self.convert_ogg_to_audio, args=(input_files,))
+
+ thread.start()
+ except Exception as e:
+ print(f"杞崲鍒濆鍖栧け璐�: {str(e)}")
+
+ def convert_audio_to_ogg(self, target_lufs, input_files):
+ """闊抽杞埌ogg杞崲閫昏緫"""
+ for input_path in input_files:
+ try:
+ filename = os.path.basename(input_path)
+ base_name = os.path.splitext(filename)[0]
+ output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg")
+
+ print(f"姝e湪杞崲: {filename}")
+ (
+ ffmpeg
+ .input(input_path)
+ .output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60)
+ .run(overwrite_output=True)
+ )
+ print(f"杞崲鎴愬姛: {filename}\n")
+ except Exception as e:
+ print(f"杞崲澶辫触: {str(e)}\n")
+
+ def convert_ogg_to_audio(self, input_files):
+ """ogg杞洖闊抽杞崲閫昏緫"""
+ for input_path in input_files:
+ try:
+ filename = os.path.basename(input_path)
+ base_name = os.path.splitext(filename)[0]
+ output_path = os.path.join(self.output_dir.get(), f"{base_name}.ogg")
+
+ print(f"姝e湪杞崲: {filename}")
+ (
+ ffmpeg
+ .input(input_path)
+ .output(output_path, acodec='libopus', audio_bitrate='16k', ac=1, ar=16000, frame_duration=60)
+ .run(overwrite_output=True)
+ )
+ print(f"杞崲鎴愬姛: {filename}\n")
+ except Exception as e:
+ print(f"杞崲澶辫触: {str(e)}\n")
+
+if __name__ == "__main__":
+ root = tk.Tk()
+ app = AudioConverterApp(root)
+ root.mainloop()
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/README.md b/xiaozhi-esp32-2.2.6/scripts/p3_tools/README.md
new file mode 100644
index 0000000..0ee279c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/README.md
@@ -0,0 +1,95 @@
+# P3闊抽鏍煎紡杞崲涓庢挱鏀惧伐鍏�
+
+杩欎釜鐩綍鍖呭惈涓や釜鐢ㄤ簬澶勭悊P3鏍煎紡闊抽鏂囦欢鐨凱ython鑴氭湰锛�
+
+## 1. 闊抽杞崲宸ュ叿 (convert_audio_to_p3.py)
+
+灏嗘櫘閫氶煶棰戞枃浠惰浆鎹负P3鏍煎紡锛�4瀛楄妭header + Opus鏁版嵁鍖呯殑娴佸紡缁撴瀯锛夊苟杩涜鍝嶅害鏍囧噯鍖栥��
+
+### 浣跨敤鏂规硶
+
+```bash
+python convert_audio_to_p3.py <杈撳叆闊抽鏂囦欢> <杈撳嚭P3鏂囦欢> [-l LUFS] [-d]
+```
+
+鍏朵腑锛屽彲閫夐�夐」 `-l` 鐢ㄤ簬鎸囧畾鍝嶅害鏍囧噯鍖栫殑鐩爣鍝嶅害锛岄粯璁や负 -16 LUFS锛涘彲閫夐�夐」 `-d` 鍙互绂佺敤鍝嶅害鏍囧噯鍖栥��
+
+濡傛灉杈撳叆鐨勯煶棰戞枃浠剁鍚堜笅闈㈢殑浠讳竴鏉′欢锛屽缓璁娇鐢� `-d` 绂佺敤鍝嶅害鏍囧噯鍖栵細
+- 闊抽杩囩煭
+- 闊抽宸茬粡璋冩暣杩囧搷搴�
+- 闊抽鏉ヨ嚜榛樿 TTS 锛堝皬鏅哄綋鍓嶄娇鐢ㄧ殑 TTS 鐨勯粯璁ゅ搷搴﹀凡鏄� -16 LUFS锛�
+
+渚嬪锛�
+```bash
+python convert_audio_to_p3.py input.mp3 output.p3
+```
+
+## 2. P3闊抽鎾斁宸ュ叿 (play_p3.py)
+
+鎾斁P3鏍煎紡鐨勯煶棰戞枃浠躲��
+
+### 鐗规��
+
+- 瑙g爜骞舵挱鏀綪3鏍煎紡鐨勯煶棰戞枃浠�
+- 鍦ㄦ挱鏀剧粨鏉熸垨鐢ㄦ埛涓柇鏃跺簲鐢ㄦ贰鍑烘晥鏋滐紝閬垮厤鐮撮煶
+- 鏀寔閫氳繃鍛戒护琛屽弬鏁版寚瀹氳鎾斁鐨勬枃浠�
+
+### 浣跨敤鏂规硶
+
+```bash
+python play_p3.py <P3鏂囦欢璺緞>
+```
+
+渚嬪锛�
+```bash
+python play_p3.py output.p3
+```
+
+## 3. 闊抽杞洖宸ュ叿 (convert_p3_to_audio.py)
+
+灏哖3鏍煎紡杞崲鍥炴櫘閫氶煶棰戞枃浠躲��
+
+### 浣跨敤鏂规硶
+
+```bash
+python convert_p3_to_audio.py <杈撳叆P3鏂囦欢> <杈撳嚭闊抽鏂囦欢>
+```
+
+杈撳嚭闊抽鏂囦欢闇�瑕佹湁鎵╁睍鍚嶃��
+
+渚嬪锛�
+```bash
+python convert_p3_to_audio.py input.p3 output.wav
+```
+## 4. 闊抽/P3鎵归噺杞崲宸ュ叿
+
+涓�涓浘褰㈠寲鐨勫伐鍏凤紝鏀寔鎵归噺杞崲闊抽鍒癙3锛孭3鍒伴煶棰�
+
+
+
+### 浣跨敤鏂规硶锛�
+```bash
+python batch_convert_gui.py
+```
+
+## 渚濊禆瀹夎
+
+鍦ㄤ娇鐢ㄨ繖浜涜剼鏈墠锛岃纭繚瀹夎浜嗘墍闇�鐨凱ython搴擄細
+
+```bash
+pip install librosa opuslib numpy tqdm sounddevice pyloudnorm soundfile
+```
+
+鎴栬�呬娇鐢ㄦ彁渚涚殑requirements.txt鏂囦欢锛�
+
+```bash
+pip install -r requirements.txt
+```
+
+## P3鏍煎紡璇存槑
+
+P3鏍煎紡鏄竴绉嶇畝鍗曠殑娴佸紡闊抽鏍煎紡锛岀粨鏋勫涓嬶細
+- 姣忎釜闊抽甯х敱涓�涓�4瀛楄妭鐨勫ご閮ㄥ拰涓�涓狾pus缂栫爜鐨勬暟鎹寘缁勬垚
+- 澶撮儴鏍煎紡锛歔1瀛楄妭绫诲瀷, 1瀛楄妭淇濈暀, 2瀛楄妭闀垮害]
+- 閲囨牱鐜囧浐瀹氫负16000Hz锛屽崟澹伴亾
+- 姣忓抚鏃堕暱涓�60ms
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/batch_convert_gui.py b/xiaozhi-esp32-2.2.6/scripts/p3_tools/batch_convert_gui.py
new file mode 100644
index 0000000..8555e55
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/batch_convert_gui.py
@@ -0,0 +1,221 @@
+import tkinter as tk
+from tkinter import ttk, filedialog, messagebox
+import os
+import threading
+import sys
+from convert_audio_to_p3 import encode_audio_to_opus
+from convert_p3_to_audio import decode_p3_to_audio
+
+class AudioConverterApp:
+ def __init__(self, master):
+ self.master = master
+ master.title("闊抽/P3 鎵归噺杞崲宸ュ叿")
+ master.geometry("680x600") # 璋冩暣绐楀彛楂樺害
+
+ # 鍒濆鍖栧彉閲�
+ self.mode = tk.StringVar(value="audio_to_p3")
+ self.output_dir = tk.StringVar()
+ self.output_dir.set(os.path.abspath("output"))
+ self.enable_loudnorm = tk.BooleanVar(value=True)
+ self.target_lufs = tk.DoubleVar(value=-16.0)
+
+ # 鍒涘缓UI缁勪欢
+ self.create_widgets()
+ self.redirect_output()
+
+ def create_widgets(self):
+ # 妯″紡閫夋嫨
+ mode_frame = ttk.LabelFrame(self.master, text="杞崲妯″紡")
+ mode_frame.grid(row=0, column=0, padx=10, pady=5, sticky="ew")
+
+ ttk.Radiobutton(mode_frame, text="闊抽杞琍3", variable=self.mode,
+ value="audio_to_p3", command=self.toggle_settings,
+ width=12).grid(row=0, column=0, padx=5)
+ ttk.Radiobutton(mode_frame, text="P3杞煶棰�", variable=self.mode,
+ value="p3_to_audio", command=self.toggle_settings,
+ width=12).grid(row=0, column=1, padx=5)
+
+ # 鍝嶅害璁剧疆
+ self.loudnorm_frame = ttk.Frame(self.master)
+ self.loudnorm_frame.grid(row=1, column=0, padx=10, pady=5, sticky="ew")
+
+ ttk.Checkbutton(self.loudnorm_frame, text="鍚敤鍝嶅害璋冩暣",
+ variable=self.enable_loudnorm, width=15
+ ).grid(row=0, column=0, padx=2)
+ ttk.Entry(self.loudnorm_frame, textvariable=self.target_lufs,
+ width=6).grid(row=0, column=1, padx=2)
+ ttk.Label(self.loudnorm_frame, text="LUFS").grid(row=0, column=2, padx=2)
+
+ # 鏂囦欢閫夋嫨
+ file_frame = ttk.LabelFrame(self.master, text="杈撳叆鏂囦欢")
+ file_frame.grid(row=2, column=0, padx=10, pady=5, sticky="nsew")
+
+ # 鏂囦欢鎿嶄綔鎸夐挳
+ ttk.Button(file_frame, text="閫夋嫨鏂囦欢", command=self.select_files,
+ width=12).grid(row=0, column=0, padx=5, pady=2)
+ ttk.Button(file_frame, text="绉婚櫎閫変腑", command=self.remove_selected,
+ width=12).grid(row=0, column=1, padx=5, pady=2)
+ ttk.Button(file_frame, text="娓呯┖鍒楄〃", command=self.clear_files,
+ width=12).grid(row=0, column=2, padx=5, pady=2)
+
+ # 鏂囦欢鍒楄〃锛堜娇鐢═reeview锛�
+ self.tree = ttk.Treeview(file_frame, columns=("selected", "filename"),
+ show="headings", height=8)
+ self.tree.heading("selected", text="閫変腑", anchor=tk.W)
+ self.tree.heading("filename", text="鏂囦欢鍚�", anchor=tk.W)
+ self.tree.column("selected", width=60, anchor=tk.W)
+ self.tree.column("filename", width=600, anchor=tk.W)
+ self.tree.grid(row=1, column=0, columnspan=3, sticky="nsew", padx=5, pady=2)
+ self.tree.bind("<ButtonRelease-1>", self.on_tree_click)
+
+ # 杈撳嚭鐩綍
+ output_frame = ttk.LabelFrame(self.master, text="杈撳嚭鐩綍")
+ output_frame.grid(row=3, column=0, padx=10, pady=5, sticky="ew")
+
+ ttk.Entry(output_frame, textvariable=self.output_dir, width=60
+ ).grid(row=0, column=0, padx=5, sticky="ew")
+ ttk.Button(output_frame, text="娴忚", command=self.select_output_dir,
+ width=8).grid(row=0, column=1, padx=5)
+
+ # 杞崲鎸夐挳鍖哄煙
+ button_frame = ttk.Frame(self.master)
+ button_frame.grid(row=4, column=0, padx=10, pady=10, sticky="ew")
+
+ ttk.Button(button_frame, text="杞崲鍏ㄩ儴鏂囦欢", command=lambda: self.start_conversion(True),
+ width=15).pack(side=tk.LEFT, padx=5)
+ ttk.Button(button_frame, text="杞崲閫変腑鏂囦欢", command=lambda: self.start_conversion(False),
+ width=15).pack(side=tk.LEFT, padx=5)
+
+ # 鏃ュ織鍖哄煙
+ log_frame = ttk.LabelFrame(self.master, text="鏃ュ織")
+ log_frame.grid(row=5, column=0, padx=10, pady=5, sticky="nsew")
+
+ self.log_text = tk.Text(log_frame, height=14, width=80)
+ self.log_text.pack(fill=tk.BOTH, expand=True)
+
+ # 閰嶇疆甯冨眬鏉冮噸
+ self.master.columnconfigure(0, weight=1)
+ self.master.rowconfigure(2, weight=1)
+ self.master.rowconfigure(5, weight=3)
+ file_frame.columnconfigure(0, weight=1)
+ file_frame.rowconfigure(1, weight=1)
+
+ def toggle_settings(self):
+ if self.mode.get() == "audio_to_p3":
+ self.loudnorm_frame.grid()
+ else:
+ self.loudnorm_frame.grid_remove()
+
+ def select_files(self):
+ file_types = [
+ ("闊抽鏂囦欢", "*.wav *.mp3 *.ogg *.flac") if self.mode.get() == "audio_to_p3"
+ else ("P3鏂囦欢", "*.p3")
+ ]
+
+ files = filedialog.askopenfilenames(filetypes=file_types)
+ for f in files:
+ self.tree.insert("", tk.END, values=("[ ]", os.path.basename(f)), tags=(f,))
+
+ def on_tree_click(self, event):
+ """澶勭悊澶嶉�夋鐐瑰嚮浜嬩欢"""
+ region = self.tree.identify("region", event.x, event.y)
+ if region == "cell":
+ col = self.tree.identify_column(event.x)
+ item = self.tree.identify_row(event.y)
+ if col == "#1": # 鐐瑰嚮鐨勬槸閫変腑鍒�
+ current_val = self.tree.item(item, "values")[0]
+ new_val = "[鈭歖" if current_val == "[ ]" else "[ ]"
+ self.tree.item(item, values=(new_val, self.tree.item(item, "values")[1]))
+
+ def remove_selected(self):
+ """绉婚櫎閫変腑鐨勬枃浠�"""
+ to_remove = []
+ for item in self.tree.get_children():
+ if self.tree.item(item, "values")[0] == "[鈭歖":
+ to_remove.append(item)
+ for item in reversed(to_remove):
+ self.tree.delete(item)
+
+ def clear_files(self):
+ """娓呯┖鎵�鏈夋枃浠�"""
+ for item in self.tree.get_children():
+ self.tree.delete(item)
+
+ def select_output_dir(self):
+ path = filedialog.askdirectory()
+ if path:
+ self.output_dir.set(path)
+
+ def redirect_output(self):
+ class StdoutRedirector:
+ def __init__(self, text_widget):
+ self.text_widget = text_widget
+ self.original_stdout = sys.stdout
+
+ def write(self, message):
+ self.text_widget.insert(tk.END, message)
+ self.text_widget.see(tk.END)
+ self.original_stdout.write(message)
+
+ def flush(self):
+ self.original_stdout.flush()
+
+ sys.stdout = StdoutRedirector(self.log_text)
+
+ def start_conversion(self, convert_all):
+ """寮�濮嬭浆鎹�"""
+ input_files = []
+ for item in self.tree.get_children():
+ if convert_all or self.tree.item(item, "values")[0] == "[鈭歖":
+ input_files.append(self.tree.item(item, "tags")[0])
+
+ if not input_files:
+ msg = "娌℃湁鎵惧埌鍙浆鎹㈢殑鏂囦欢" if convert_all else "娌℃湁閫変腑浠讳綍鏂囦欢"
+ messagebox.showwarning("璀﹀憡", msg)
+ return
+
+ os.makedirs(self.output_dir.get(), exist_ok=True)
+
+ try:
+ if self.mode.get() == "audio_to_p3":
+ target_lufs = self.target_lufs.get() if self.enable_loudnorm.get() else None
+ thread = threading.Thread(target=self.convert_audio_to_p3, args=(target_lufs, input_files))
+ else:
+ thread = threading.Thread(target=self.convert_p3_to_audio, args=(input_files,))
+
+ thread.start()
+ except Exception as e:
+ print(f"杞崲鍒濆鍖栧け璐�: {str(e)}")
+
+ def convert_audio_to_p3(self, target_lufs, input_files):
+ """闊抽杞琍3杞崲閫昏緫"""
+ for input_path in input_files:
+ try:
+ filename = os.path.basename(input_path)
+ base_name = os.path.splitext(filename)[0]
+ output_path = os.path.join(self.output_dir.get(), f"{base_name}.p3")
+
+ print(f"姝e湪杞崲: {filename}")
+ encode_audio_to_opus(input_path, output_path, target_lufs)
+ print(f"杞崲鎴愬姛: {filename}\n")
+ except Exception as e:
+ print(f"杞崲澶辫触: {str(e)}\n")
+
+ def convert_p3_to_audio(self, input_files):
+ """P3杞煶棰戣浆鎹㈤�昏緫"""
+ for input_path in input_files:
+ try:
+ filename = os.path.basename(input_path)
+ base_name = os.path.splitext(filename)[0]
+ output_path = os.path.join(self.output_dir.get(), f"{base_name}.wav")
+
+ print(f"姝e湪杞崲: {filename}")
+ decode_p3_to_audio(input_path, output_path)
+ print(f"杞崲鎴愬姛: {filename}\n")
+ except Exception as e:
+ print(f"杞崲澶辫触: {str(e)}\n")
+
+if __name__ == "__main__":
+ root = tk.Tk()
+ app = AudioConverterApp(root)
+ root.mainloop()
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_audio_to_p3.py b/xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_audio_to_p3.py
new file mode 100644
index 0000000..519d662
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_audio_to_p3.py
@@ -0,0 +1,62 @@
+# convert audio files to protocol v3 stream
+import librosa
+import opuslib
+import struct
+import sys
+import tqdm
+import numpy as np
+import argparse
+import pyloudnorm as pyln
+
+def encode_audio_to_opus(input_file, output_file, target_lufs=None):
+ # Load audio file using librosa
+ audio, sample_rate = librosa.load(input_file, sr=None, mono=False, dtype=np.float32)
+
+ # Convert to mono if stereo
+ if audio.ndim == 2:
+ audio = librosa.to_mono(audio)
+
+ if target_lufs is not None:
+ print("Note: Automatic loudness adjustment is enabled, which may cause", file=sys.stderr)
+ print(" audio distortion. If the input audio has already been ", file=sys.stderr)
+ print(" loudness-adjusted or if the input audio is TTS audio, ", file=sys.stderr)
+ print(" please use the `-d` parameter to disable loudness adjustment.", file=sys.stderr)
+ meter = pyln.Meter(sample_rate)
+ current_loudness = meter.integrated_loudness(audio)
+ audio = pyln.normalize.loudness(audio, current_loudness, target_lufs)
+ print(f"Adjusted loudness: {current_loudness:.1f} LUFS -> {target_lufs} LUFS")
+
+ # Convert sample rate to 16000Hz if necessary
+ target_sample_rate = 16000
+ if sample_rate != target_sample_rate:
+ audio = librosa.resample(audio, orig_sr=sample_rate, target_sr=target_sample_rate)
+ sample_rate = target_sample_rate
+
+ # Convert audio data back to int16 after processing
+ audio = (audio * 32767).astype(np.int16)
+
+ # Initialize Opus encoder
+ encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_AUDIO)
+
+ # Encode and save
+ with open(output_file, 'wb') as f:
+ duration = 60 # 60ms per frame
+ frame_size = int(sample_rate * duration / 1000)
+ for i in tqdm.tqdm(range(0, len(audio) - frame_size, frame_size)):
+ frame = audio[i:i + frame_size]
+ opus_data = encoder.encode(frame.tobytes(), frame_size=frame_size)
+ packet = struct.pack('>BBH', 0, 0, len(opus_data)) + opus_data
+ f.write(packet)
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser(description='Convert audio to Opus with loudness normalization')
+ parser.add_argument('input_file', help='Input audio file')
+ parser.add_argument('output_file', help='Output .opus file')
+ parser.add_argument('-l', '--lufs', type=float, default=-16.0,
+ help='Target loudness in LUFS (default: -16)')
+ parser.add_argument('-d', '--disable-loudnorm', action='store_true',
+ help='Disable loudness normalization')
+ args = parser.parse_args()
+
+ target_lufs = None if args.disable_loudnorm else args.lufs
+ encode_audio_to_opus(args.input_file, args.output_file, target_lufs)
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_p3_to_audio.py b/xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_p3_to_audio.py
new file mode 100644
index 0000000..f870b01
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/convert_p3_to_audio.py
@@ -0,0 +1,51 @@
+import struct
+import sys
+import opuslib
+import numpy as np
+from tqdm import tqdm
+import soundfile as sf
+
+
+def decode_p3_to_audio(input_file, output_file):
+ sample_rate = 16000
+ channels = 1
+ decoder = opuslib.Decoder(sample_rate, channels)
+
+ pcm_frames = []
+ frame_size = int(sample_rate * 60 / 1000)
+
+ with open(input_file, "rb") as f:
+ f.seek(0, 2)
+ total_size = f.tell()
+ f.seek(0)
+
+ with tqdm(total=total_size, unit="B", unit_scale=True) as pbar:
+ while True:
+ header = f.read(4)
+ if not header or len(header) < 4:
+ break
+
+ pkt_type, reserved, opus_len = struct.unpack(">BBH", header)
+ opus_data = f.read(opus_len)
+ if len(opus_data) != opus_len:
+ break
+
+ pcm = decoder.decode(opus_data, frame_size)
+ pcm_frames.append(np.frombuffer(pcm, dtype=np.int16))
+
+ pbar.update(4 + opus_len)
+
+ if not pcm_frames:
+ raise ValueError("No valid audio data found")
+
+ pcm_data = np.concatenate(pcm_frames)
+
+ sf.write(output_file, pcm_data, sample_rate, subtype="PCM_16")
+
+
+if __name__ == "__main__":
+ if len(sys.argv) != 3:
+ print("Usage: python convert_p3_to_audio.py <input.p3> <output.wav>")
+ sys.exit(1)
+
+ decode_p3_to_audio(sys.argv[1], sys.argv[2])
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/img/img.png b/xiaozhi-esp32-2.2.6/scripts/p3_tools/img/img.png
new file mode 100644
index 0000000..7ee34ee
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/img/img.png
Binary files differ
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/p3_gui_player.py b/xiaozhi-esp32-2.2.6/scripts/p3_tools/p3_gui_player.py
new file mode 100644
index 0000000..3bbc8a3
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/p3_gui_player.py
@@ -0,0 +1,241 @@
+import tkinter as tk
+from tkinter import filedialog, messagebox
+import threading
+import time
+import opuslib
+import struct
+import numpy as np
+import sounddevice as sd
+import os
+
+
+def play_p3_file(input_file, stop_event=None, pause_event=None):
+ """
+ 鎾斁p3鏍煎紡鐨勯煶棰戞枃浠�
+ p3鏍煎紡: [1瀛楄妭绫诲瀷, 1瀛楄妭淇濈暀, 2瀛楄妭闀垮害, Opus鏁版嵁]
+ """
+ # 鍒濆鍖朞pus瑙g爜鍣�
+ sample_rate = 16000 # 閲囨牱鐜囧浐瀹氫负16000Hz
+ channels = 1 # 鍗曞0閬�
+ decoder = opuslib.Decoder(sample_rate, channels)
+
+ # 甯уぇ灏� (60ms)
+ frame_size = int(sample_rate * 60 / 1000)
+
+ # 鎵撳紑闊抽娴�
+ stream = sd.OutputStream(
+ samplerate=sample_rate,
+ channels=channels,
+ dtype='int16'
+ )
+ stream.start()
+
+ try:
+ with open(input_file, 'rb') as f:
+ print(f"姝e湪鎾斁: {input_file}")
+
+ while True:
+ if stop_event and stop_event.is_set():
+ break
+
+ if pause_event and pause_event.is_set():
+ time.sleep(0.1)
+ continue
+
+ # 璇诲彇澶撮儴 (4瀛楄妭)
+ header = f.read(4)
+ if not header or len(header) < 4:
+ break
+
+ # 瑙f瀽澶撮儴
+ packet_type, reserved, data_len = struct.unpack('>BBH', header)
+
+ # 璇诲彇Opus鏁版嵁
+ opus_data = f.read(data_len)
+ if not opus_data or len(opus_data) < data_len:
+ break
+
+ # 瑙g爜Opus鏁版嵁
+ pcm_data = decoder.decode(opus_data, frame_size)
+
+ # 灏嗗瓧鑺傝浆鎹负numpy鏁扮粍
+ audio_array = np.frombuffer(pcm_data, dtype=np.int16)
+
+ # 鎾斁闊抽
+ stream.write(audio_array)
+
+ except KeyboardInterrupt:
+ print("\n鎾斁宸插仠姝�")
+ finally:
+ stream.stop()
+ stream.close()
+ print("鎾斁瀹屾垚")
+
+
+class P3PlayerApp:
+ def __init__(self, root):
+ self.root = root
+ self.root.title("P3 鏂囦欢绠�鏄撴挱鏀惧櫒")
+ self.root.geometry("500x400")
+
+ self.playlist = []
+ self.current_index = 0
+ self.is_playing = False
+ self.is_paused = False
+ self.stop_event = threading.Event()
+ self.pause_event = threading.Event()
+ self.loop_playback = tk.BooleanVar(value=False) # 寰幆鎾斁澶嶉�夋鐨勭姸鎬�
+
+ # 鍒涘缓鐣岄潰缁勪欢
+ self.create_widgets()
+
+ def create_widgets(self):
+ # 鎾斁鍒楄〃
+ self.playlist_label = tk.Label(self.root, text="鎾斁鍒楄〃:")
+ self.playlist_label.pack(pady=5)
+
+ self.playlist_frame = tk.Frame(self.root)
+ self.playlist_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=5)
+
+ self.playlist_listbox = tk.Listbox(self.playlist_frame, selectmode=tk.SINGLE)
+ self.playlist_listbox.pack(fill=tk.BOTH, expand=True)
+
+ # 澶嶉�夋鍜岀Щ闄ゆ寜閽�
+ self.checkbox_frame = tk.Frame(self.root)
+ self.checkbox_frame.pack(pady=5)
+
+ self.remove_button = tk.Button(self.checkbox_frame, text="绉婚櫎鏂囦欢", command=self.remove_files)
+ self.remove_button.pack(side=tk.LEFT, padx=5)
+
+ # 寰幆鎾斁澶嶉�夋
+ self.loop_checkbox = tk.Checkbutton(self.checkbox_frame, text="寰幆鎾斁", variable=self.loop_playback)
+ self.loop_checkbox.pack(side=tk.LEFT, padx=5)
+
+ # 鎺у埗鎸夐挳
+ self.control_frame = tk.Frame(self.root)
+ self.control_frame.pack(pady=10)
+
+ self.add_button = tk.Button(self.control_frame, text="娣诲姞鏂囦欢", command=self.add_file)
+ self.add_button.grid(row=0, column=0, padx=5)
+
+ self.play_button = tk.Button(self.control_frame, text="鎾斁", command=self.play)
+ self.play_button.grid(row=0, column=1, padx=5)
+
+ self.pause_button = tk.Button(self.control_frame, text="鏆傚仠", command=self.pause)
+ self.pause_button.grid(row=0, column=2, padx=5)
+
+ self.stop_button = tk.Button(self.control_frame, text="鍋滄", command=self.stop)
+ self.stop_button.grid(row=0, column=3, padx=5)
+
+ # 鐘舵�佹爣绛�
+ self.status_label = tk.Label(self.root, text="鏈湪鎾斁", fg="blue")
+ self.status_label.pack(pady=10)
+
+ def add_file(self):
+ files = filedialog.askopenfilenames(filetypes=[("P3 鏂囦欢", "*.p3")])
+ if files:
+ self.playlist.extend(files)
+ self.update_playlist()
+
+ def update_playlist(self):
+ self.playlist_listbox.delete(0, tk.END)
+ for file in self.playlist:
+ self.playlist_listbox.insert(tk.END, os.path.basename(file)) # 浠呮樉绀烘枃浠跺悕
+
+ def update_status(self, status_text, color="blue"):
+ """鏇存柊鐘舵�佹爣绛剧殑鍐呭"""
+ self.status_label.config(text=status_text, fg=color)
+
+ def play(self):
+ if not self.playlist:
+ messagebox.showwarning("璀﹀憡", "鎾斁鍒楄〃涓虹┖锛�")
+ return
+
+ if self.is_paused:
+ self.is_paused = False
+ self.pause_event.clear()
+ self.update_status(f"姝e湪鎾斁锛歿os.path.basename(self.playlist[self.current_index])}", "green")
+ return
+
+ if self.is_playing:
+ return
+
+ self.is_playing = True
+ self.stop_event.clear()
+ self.pause_event.clear()
+ self.current_index = self.playlist_listbox.curselection()[0] if self.playlist_listbox.curselection() else 0
+ self.play_thread = threading.Thread(target=self.play_audio, daemon=True)
+ self.play_thread.start()
+ self.update_status(f"姝e湪鎾斁锛歿os.path.basename(self.playlist[self.current_index])}", "green")
+
+ def play_audio(self):
+ while True:
+ if self.stop_event.is_set():
+ break
+
+ if self.pause_event.is_set():
+ time.sleep(0.1)
+ continue
+
+ # 妫�鏌ュ綋鍓嶇储寮曟槸鍚︽湁鏁�
+ if self.current_index >= len(self.playlist):
+ if self.loop_playback.get(): # 濡傛灉鍕鹃�変簡寰幆鎾斁
+ self.current_index = 0 # 鍥炲埌绗竴棣�
+ else:
+ break # 鍚﹀垯鍋滄鎾斁
+
+ file = self.playlist[self.current_index]
+ self.playlist_listbox.selection_clear(0, tk.END)
+ self.playlist_listbox.selection_set(self.current_index)
+ self.playlist_listbox.activate(self.current_index)
+ self.update_status(f"姝e湪鎾斁锛歿os.path.basename(self.playlist[self.current_index])}", "green")
+ play_p3_file(file, self.stop_event, self.pause_event)
+
+ if self.stop_event.is_set():
+ break
+
+ if not self.loop_playback.get(): # 濡傛灉娌℃湁鍕鹃�夊惊鐜挱鏀�
+ break # 鎾斁瀹屽綋鍓嶆枃浠跺悗鍋滄
+
+ self.current_index += 1
+ if self.current_index >= len(self.playlist):
+ if self.loop_playback.get(): # 濡傛灉鍕鹃�変簡寰幆鎾斁
+ self.current_index = 0 # 鍥炲埌绗竴棣�
+
+ self.is_playing = False
+ self.is_paused = False
+ self.update_status("鎾斁宸插仠姝�", "red")
+
+ def pause(self):
+ if self.is_playing:
+ self.is_paused = not self.is_paused
+ if self.is_paused:
+ self.pause_event.set()
+ self.update_status("鎾斁宸叉殏鍋�", "orange")
+ else:
+ self.pause_event.clear()
+ self.update_status(f"姝e湪鎾斁锛歿os.path.basename(self.playlist[self.current_index])}", "green")
+
+ def stop(self):
+ if self.is_playing or self.is_paused:
+ self.is_playing = False
+ self.is_paused = False
+ self.stop_event.set()
+ self.pause_event.clear()
+ self.update_status("鎾斁宸插仠姝�", "red")
+
+ def remove_files(self):
+ selected_indices = self.playlist_listbox.curselection()
+ if not selected_indices:
+ messagebox.showwarning("璀﹀憡", "璇峰厛閫夋嫨瑕佺Щ闄ょ殑鏂囦欢锛�")
+ return
+
+ for index in reversed(selected_indices):
+ self.playlist.pop(index)
+ self.update_playlist()
+
+
+if __name__ == "__main__":
+ root = tk.Tk()
+ app = P3PlayerApp(root)
+ root.mainloop()
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/play_p3.py b/xiaozhi-esp32-2.2.6/scripts/p3_tools/play_p3.py
new file mode 100644
index 0000000..3c9ec81
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/play_p3.py
@@ -0,0 +1,71 @@
+# 鎾斁p3鏍煎紡鐨勯煶棰戞枃浠�
+import opuslib
+import struct
+import numpy as np
+import sounddevice as sd
+import argparse
+
+def play_p3_file(input_file):
+ """
+ 鎾斁p3鏍煎紡鐨勯煶棰戞枃浠�
+ p3鏍煎紡: [1瀛楄妭绫诲瀷, 1瀛楄妭淇濈暀, 2瀛楄妭闀垮害, Opus鏁版嵁]
+ """
+ # 鍒濆鍖朞pus瑙g爜鍣�
+ sample_rate = 16000 # 閲囨牱鐜囧浐瀹氫负16000Hz
+ channels = 1 # 鍗曞0閬�
+ decoder = opuslib.Decoder(sample_rate, channels)
+
+ # 甯уぇ灏� (60ms)
+ frame_size = int(sample_rate * 60 / 1000)
+
+ # 鎵撳紑闊抽娴�
+ stream = sd.OutputStream(
+ samplerate=sample_rate,
+ channels=channels,
+ dtype='int16'
+ )
+ stream.start()
+
+ try:
+ with open(input_file, 'rb') as f:
+ print(f"姝e湪鎾斁: {input_file}")
+
+ while True:
+ # 璇诲彇澶撮儴 (4瀛楄妭)
+ header = f.read(4)
+ if not header or len(header) < 4:
+ break
+
+ # 瑙f瀽澶撮儴
+ packet_type, reserved, data_len = struct.unpack('>BBH', header)
+
+ # 璇诲彇Opus鏁版嵁
+ opus_data = f.read(data_len)
+ if not opus_data or len(opus_data) < data_len:
+ break
+
+ # 瑙g爜Opus鏁版嵁
+ pcm_data = decoder.decode(opus_data, frame_size)
+
+ # 灏嗗瓧鑺傝浆鎹负numpy鏁扮粍
+ audio_array = np.frombuffer(pcm_data, dtype=np.int16)
+
+ # 鎾斁闊抽
+ stream.write(audio_array)
+
+ except KeyboardInterrupt:
+ print("\n鎾斁宸插仠姝�")
+ finally:
+ stream.stop()
+ stream.close()
+ print("鎾斁瀹屾垚")
+
+def main():
+ parser = argparse.ArgumentParser(description='鎾斁p3鏍煎紡鐨勯煶棰戞枃浠�')
+ parser.add_argument('input_file', help='杈撳叆鐨刾3鏂囦欢璺緞')
+ args = parser.parse_args()
+
+ play_p3_file(args.input_file)
+
+if __name__ == "__main__":
+ main()
diff --git a/xiaozhi-esp32-2.2.6/scripts/p3_tools/requirements.txt b/xiaozhi-esp32-2.2.6/scripts/p3_tools/requirements.txt
new file mode 100644
index 0000000..d76d4cd
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/p3_tools/requirements.txt
@@ -0,0 +1,7 @@
+librosa>=0.9.2
+opuslib>=3.0.1
+numpy>=1.20.0
+tqdm>=4.62.0
+sounddevice>=0.4.4
+pyloudnorm>=0.1.1
+soundfile>=0.13.1
diff --git a/xiaozhi-esp32-2.2.6/scripts/release.py b/xiaozhi-esp32-2.2.6/scripts/release.py
new file mode 100755
index 0000000..299d4bc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/release.py
@@ -0,0 +1,456 @@
+import sys
+import os
+import json
+import zipfile
+import argparse
+import re
+from pathlib import Path
+from typing import Optional
+
+# Switch to project root directory
+os.chdir(Path(__file__).resolve().parent.parent)
+
+################################################################################
+# Common utility functions
+################################################################################
+
+def get_board_type_from_compile_commands() -> Optional[str]:
+ """Parse the current compiled BOARD_TYPE from build/compile_commands.json"""
+ compile_file = Path("build/compile_commands.json")
+ if not compile_file.exists():
+ return None
+ with compile_file.open(encoding='utf-8') as f:
+ data = json.load(f)
+ for item in data:
+ if not item["file"].endswith("main.cc"):
+ continue
+ cmd = item["command"]
+ if "-DBOARD_TYPE=\\\"" in cmd:
+ return cmd.split("-DBOARD_TYPE=\\\"")[1].split("\\\"")[0].strip()
+ return None
+
+
+def get_project_version() -> Optional[str]:
+ """Read set(PROJECT_VER "x.y.z") from root CMakeLists.txt"""
+ with Path("CMakeLists.txt").open(encoding='utf-8') as f:
+ for line in f:
+ if line.startswith("set(PROJECT_VER"):
+ return line.split("\"")[1]
+ return None
+
+
+def merge_bin() -> None:
+ if os.system("idf.py merge-bin") != 0:
+ print("merge-bin failed", file=sys.stderr)
+ sys.exit(1)
+
+
+def zip_bin(name: str, version: str) -> None:
+ """Zip build/merged-binary.bin to releases/v{version}_{name}.zip"""
+ out_dir = Path("releases")
+ out_dir.mkdir(exist_ok=True)
+ output_path = out_dir / f"v{version}_{name}.zip"
+
+ if output_path.exists():
+ output_path.unlink()
+
+ with zipfile.ZipFile(output_path, "w", compression=zipfile.ZIP_DEFLATED) as zipf:
+ zipf.write("build/merged-binary.bin", arcname="merged-binary.bin")
+ print(f"zip bin to {output_path} done")
+
+def _get_manufacturer(cfg: dict) -> Optional[str]:
+ """Read manufacturer from config.json"""
+ m = cfg.get("manufacturer")
+ if isinstance(m, str) and m.strip():
+ return m.strip()
+ return None
+
+################################################################################
+# board / variant related functions
+################################################################################
+
+_BOARDS_DIR = Path("main/boards")
+
+def _collect_variants(config_filename: str = "config.json") -> list[dict[str, str]]:
+ """Traverse all boards under main/boards, collect variant information.
+
+ Return example:
+ [{"board": "bread-compact-ml307", "name": "bread-compact-ml307", "full_name": "bread-compact-ml307"}, ...]
+ [{"board": "waveshare/esp32-p4-nano", "name": "esp32-p4-nano-10.1-a", "full_name": "waveshare-esp32-p4-nano-10.1-a"}, ...]
+ """
+ variants: list[dict[str, str]] = []
+ errors: list[str] = []
+
+ for cfg_path in _BOARDS_DIR.rglob(config_filename):
+ board_dir = cfg_path.parent
+ if board_dir.name == "common":
+ continue
+ board = board_dir.relative_to(_BOARDS_DIR).as_posix()
+
+ try:
+ with cfg_path.open(encoding='utf-8') as f:
+ cfg = json.load(f)
+
+ manufacturer = _get_manufacturer(cfg)
+
+ # Check manufacturer consistency with directory structure
+ if "/" in board:
+ # Board is in a subdirectory (e.g., waveshare/esp32-p4-nano)
+ expected_manufacturer = board.split("/")[0]
+ if not manufacturer:
+ errors.append(
+ f"{cfg_path}: Board is in '{expected_manufacturer}/' subdirectory, "
+ f"but config.json is missing \"manufacturer\": \"{expected_manufacturer}\""
+ )
+ elif manufacturer != expected_manufacturer:
+ errors.append(
+ f"{cfg_path}: manufacturer mismatch, "
+ f"directory is '{expected_manufacturer}/' but config.json has \"{manufacturer}\""
+ )
+ else:
+ # Board is directly under boards/ directory
+ if manufacturer:
+ errors.append(
+ f"{cfg_path}: Board is not in a manufacturer subdirectory, "
+ f"but config.json defines manufacturer \"{manufacturer}\", "
+ f"please move board to main/boards/{manufacturer}/{board}/"
+ )
+
+ for build in cfg.get("builds", []):
+ name = build["name"]
+ full_name = f"{manufacturer}-{name}" if manufacturer else name
+ variants.append({
+ "board": board,
+ "name": name,
+ "full_name": full_name
+ })
+
+ except Exception as e:
+ print(f"[ERROR] Failed to parse {cfg_path}: {e}", file=sys.stderr)
+
+ # Report all errors at once
+ if errors:
+ print("\n[ERROR] Found manufacturer configuration issues:", file=sys.stderr)
+ for err in errors:
+ print(f" - {err}", file=sys.stderr)
+ print(file=sys.stderr)
+ sys.exit(1)
+
+ return variants
+
+
+
+def _find_board_config_candidates(board_type: str) -> list[str]:
+ """Find all CONFIG_BOARD_TYPE_xxx candidates for the given board_type."""
+ board_leaf = board_type.split("/")[-1]
+ pattern = f'set(BOARD_TYPE "{board_leaf}")'
+
+ cmake_file = Path("main/CMakeLists.txt")
+ lines = cmake_file.read_text(encoding="utf-8").splitlines()
+ candidates: list[str] = []
+
+ for idx, line in enumerate(lines):
+ if pattern in line:
+ # Found the BOARD_TYPE line, search backwards for the nearest config guard
+ for back_idx in range(idx - 1, -1, -1):
+ back_line = lines[back_idx]
+ if "if(CONFIG_BOARD_TYPE_" in back_line:
+ candidates.append(back_line.strip().split("if(")[1].split(")")[0])
+ break
+ return candidates
+
+
+def _extract_board_config_from_sdkconfig_append(sdkconfig_append: list[str]) -> Optional[str]:
+ """Extract explicit CONFIG_BOARD_TYPE_xxx=y from sdkconfig_append, if present."""
+ pattern = re.compile(r"^(CONFIG_BOARD_TYPE_[A-Z0-9_]+)=y$")
+ matches = []
+ for item in sdkconfig_append:
+ m = pattern.match(item.strip())
+ if m:
+ matches.append(m.group(1))
+ if not matches:
+ return None
+ uniq = list(dict.fromkeys(matches))
+ if len(uniq) > 1:
+ raise ValueError(f"Multiple board type configs found in sdkconfig_append: {uniq}")
+ return uniq[0]
+
+
+def _symbol_supports_target(symbol: str, target: str) -> bool:
+ """Check whether Kconfig symbol depends on given target (e.g. esp32c5)."""
+ kconfig_file = Path("main/Kconfig.projbuild")
+ if not kconfig_file.exists():
+ return False
+
+ target_flag = f"IDF_TARGET_{target.upper()}"
+ lines = kconfig_file.read_text(encoding="utf-8").splitlines()
+
+ in_symbol = False
+ for line in lines:
+ stripped = line.strip()
+ if stripped.startswith("config "):
+ curr_symbol = stripped.split("config ", 1)[1].strip()
+ in_symbol = curr_symbol == symbol
+ continue
+ if in_symbol and stripped.startswith(("config ", "choice ", "endchoice", "menu ", "endmenu")):
+ break
+ if in_symbol and "depends on" in stripped and target_flag in stripped:
+ return True
+ return False
+
+
+def _resolve_board_config(board_type: str, target: str, sdkconfig_append: list[str]) -> str:
+ """Resolve CONFIG_BOARD_TYPE_xxx for current board build."""
+ explicit = _extract_board_config_from_sdkconfig_append(sdkconfig_append)
+ if explicit:
+ return explicit
+
+ candidates = _find_board_config_candidates(board_type)
+ if not candidates:
+ raise ValueError(f"Cannot find board config symbol for {board_type}")
+ if len(candidates) == 1:
+ return candidates[0]
+
+ by_target = [c for c in candidates if _symbol_supports_target(c, target)]
+ if len(by_target) == 1:
+ return by_target[0]
+ if len(by_target) > 1:
+ selected = by_target[0]
+ print(
+ f"[WARN] Ambiguous board config for {board_type} (target={target}), "
+ f"target-matched candidates={by_target}, selecting first: {selected}",
+ file=sys.stderr,
+ )
+ return selected
+
+ target_u = target.upper()
+ target_short = target_u.replace("ESP32", "")
+ by_name = [
+ c for c in candidates
+ if target_u in c or f"_{target_short}" in c
+ ]
+ if len(by_name) == 1:
+ return by_name[0]
+ if len(by_name) > 1:
+ selected = by_name[0]
+ print(
+ f"[WARN] Ambiguous board config for {board_type} (target={target}), "
+ f"name-matched candidates={by_name}, selecting first: {selected}",
+ file=sys.stderr,
+ )
+ return selected
+
+ selected = candidates[0]
+ print(
+ f"[WARN] Ambiguous board config for {board_type} (target={target}), "
+ f"candidates={candidates}, selecting first: {selected}",
+ file=sys.stderr,
+ )
+ return selected
+
+
+# Kconfig "select" entries are not automatically applied when we simply append
+# sdkconfig lines from config.json, so add the required dependencies here to
+# mimic menuconfig behaviour.
+_AUTO_SELECT_RULES: dict[str, list[str]] = {
+ "CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING": [
+ "CONFIG_BT_ENABLED=y",
+ "CONFIG_BT_BLUEDROID_ENABLED=y",
+ "CONFIG_BT_BLE_42_FEATURES_SUPPORTED=y",
+ "CONFIG_BT_BLE_50_FEATURES_SUPPORTED=n",
+ "CONFIG_BT_BLE_BLUFI_ENABLE=y",
+ "CONFIG_MBEDTLS_DHM_C=y",
+ ],
+}
+
+
+def _apply_auto_selects(sdkconfig_append: list[str]) -> list[str]:
+ """Apply hardcoded auto-select rules to sdkconfig_append."""
+ items: list[str] = []
+ existing_keys: set[str] = set()
+
+ def _append_if_missing(entry: str) -> None:
+ key = entry.split("=", 1)[0]
+ if key not in existing_keys:
+ items.append(entry)
+ existing_keys.add(key)
+
+ # Preserve original order while tracking keys
+ for entry in sdkconfig_append:
+ _append_if_missing(entry)
+
+ # Apply auto-select rules
+ for key, deps in _AUTO_SELECT_RULES.items():
+ for entry in sdkconfig_append:
+ name, _, value = entry.partition("=")
+ if name == key and value.lower().startswith("y"):
+ for dep in deps:
+ _append_if_missing(dep)
+ break
+
+ return items
+
+################################################################################
+# Check board_type in CMakeLists
+################################################################################
+
+def _board_type_exists(board_type: str) -> bool:
+ cmake_file = Path("main/CMakeLists.txt").read_text(encoding="utf-8")
+ board_leaf = board_type.split("/")[-1]
+ pattern = f'set(BOARD_TYPE "{board_leaf}")'
+ return pattern in cmake_file
+
+################################################################################
+# Compile implementation
+################################################################################
+
+def release(board_type: str, config_filename: str = "config.json", *, filter_name: Optional[str] = None) -> None:
+ """Compile and package all/specified variants of the specified board_type
+
+ Args:
+ board_type: directory name under main/boards
+ config_filename: config.json name (default: config.json)
+ filter_name: if specified, only compile the build["name"] that matches
+ """
+ cfg_path = _BOARDS_DIR / Path(board_type) / config_filename
+ if not cfg_path.exists():
+ print(f"[WARN] {cfg_path} does not exist, skipping {board_type}")
+ return
+
+ project_version = get_project_version()
+ print(f"Project Version: {project_version} ({cfg_path})")
+
+ with cfg_path.open(encoding='utf-8') as f:
+ cfg = json.load(f)
+ target = cfg["target"]
+ manufacturer = _get_manufacturer(cfg)
+
+ builds = cfg.get("builds", [])
+ if filter_name:
+ builds = [b for b in builds if b["name"] == filter_name]
+ if not builds:
+ print(f"[ERROR] Variant {filter_name} not found in {board_type}'s {config_filename}", file=sys.stderr)
+ sys.exit(1)
+
+ for build in builds:
+ name = build["name"]
+ board_leaf = board_type.split("/")[-1]
+
+ if board_leaf not in name:
+ raise ValueError(f"build.name {name} must contain {board_leaf}")
+
+ final_name = f"{manufacturer}-{name}" if manufacturer else name
+ output_path = Path("releases") / f"v{project_version}_{final_name}.zip"
+ if output_path.exists():
+ print(f"Skipping {final_name} because {output_path} already exists")
+ continue
+
+ # Process sdkconfig_append
+ build_sdkconfig_append = build.get("sdkconfig_append", [])
+ explicit_board_cfg = _extract_board_config_from_sdkconfig_append(build_sdkconfig_append)
+ if explicit_board_cfg:
+ print(
+ f"[INFO] Board config explicitly set in config.json: {explicit_board_cfg}, "
+ "skip auto-select.",
+ )
+ sdkconfig_append = list(build_sdkconfig_append)
+ else:
+ board_type_config = _resolve_board_config(board_type, target, build_sdkconfig_append)
+ sdkconfig_append = [f"{board_type_config}=y"]
+ sdkconfig_append.extend(build_sdkconfig_append)
+ sdkconfig_append = _apply_auto_selects(sdkconfig_append)
+
+ print("-" * 80)
+ print(f"name: {final_name}")
+ print(f"target: {target}")
+ if manufacturer:
+ print(f"manufacturer: {manufacturer}")
+ for item in sdkconfig_append:
+ print(f"sdkconfig_append: {item}")
+
+ os.environ.pop("IDF_TARGET", None)
+
+ # Call set-target
+ if os.system(f"idf.py set-target {target}") != 0:
+ print("set-target failed", file=sys.stderr)
+ sys.exit(1)
+
+ # Append sdkconfig
+ with Path("sdkconfig").open("a", encoding='utf-8') as f:
+ f.write("\n")
+ f.write("# Append by release.py\n")
+ for append in sdkconfig_append:
+ f.write(f"{append}\n")
+ # Build with macro BOARD_NAME defined to name
+ if os.system(f"idf.py -DBOARD_NAME={name} -DBOARD_TYPE={board_type} build") != 0:
+ print("build failed")
+ sys.exit(1)
+
+ # merge-bin
+ merge_bin()
+
+ # Zip
+ zip_bin(final_name, project_version)
+
+################################################################################
+# CLI entry
+################################################################################
+
+if __name__ == "__main__":
+ parser = argparse.ArgumentParser()
+ parser.add_argument("board", nargs="?", default=None, help="Board type or 'all'")
+ parser.add_argument("-c", "--config", default="config.json", help="Config filename (default: config.json)")
+ parser.add_argument("--list-boards", action="store_true", help="List all supported boards and variants")
+ parser.add_argument("--json", action="store_true", help="Output in JSON format (use with --list-boards)")
+ parser.add_argument("--name", help="Variant name to compile (original name without manufacturer prefix)")
+
+ args = parser.parse_args()
+
+ # List mode
+ if args.list_boards:
+ variants = _collect_variants(config_filename=args.config)
+ if args.json:
+ print(json.dumps(variants))
+ else:
+ for v in variants:
+ print(f"{v['board']}: {v['name']}")
+ sys.exit(0)
+
+ # Current directory firmware packaging mode
+ if args.board is None:
+ merge_bin()
+ curr_board_type = get_board_type_from_compile_commands()
+ if curr_board_type is None:
+ print("Failed to parse board_type from compile_commands.json", file=sys.stderr)
+ sys.exit(1)
+ project_ver = get_project_version()
+ zip_bin(curr_board_type, project_ver)
+ sys.exit(0)
+
+ # Compile mode
+ board_type_input: str = args.board
+ name_filter: Optional[str] = args.name
+
+ # Check board_type in CMakeLists
+ if board_type_input != "all" and not _board_type_exists(board_type_input):
+ print(f"[ERROR] board_type {board_type_input} not found in main/CMakeLists.txt", file=sys.stderr)
+ sys.exit(1)
+
+ variants_all = _collect_variants(config_filename=args.config)
+
+ # Filter board_type list
+ target_board_types: set[str]
+ if board_type_input == "all":
+ target_board_types = {v["board"] for v in variants_all}
+ else:
+ target_board_types = {board_type_input}
+
+ for bt in sorted(target_board_types):
+ if not _board_type_exists(bt):
+ print(f"[ERROR] board_type {bt} not found in main/CMakeLists.txt", file=sys.stderr)
+ sys.exit(1)
+ cfg_path = _BOARDS_DIR / bt / args.config
+ if bt == board_type_input and not cfg_path.exists():
+ print(f"Board {bt} has no {args.config} config file, skipping")
+ sys.exit(0)
+ release(bt, config_filename=args.config, filter_name=name_filter if bt == board_type_input else None)
diff --git a/xiaozhi-esp32-2.2.6/scripts/sonic_wifi_config.html b/xiaozhi-esp32-2.2.6/scripts/sonic_wifi_config.html
new file mode 100644
index 0000000..b5c5093
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/sonic_wifi_config.html
@@ -0,0 +1,208 @@
+<!DOCTYPE html>
+<html lang="zh">
+<head>
+ <meta charset="UTF-8" />
+ <title>灏忔櫤澹版尝閰嶇綉</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <style>
+ body {
+ font-family: "Segoe UI", "PingFang SC", sans-serif;
+ background: #f0f2f5;
+ margin: 0;
+ padding: 2rem 1rem;
+ display: flex;
+ justify-content: center;
+ }
+ .card {
+ background: #fff;
+ padding: 2rem 1.5rem;
+ border-radius: 16px;
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
+ max-width: 400px;
+ width: 100%;
+ }
+ h2 {
+ text-align: center;
+ margin-bottom: 2rem;
+ }
+ label {
+ font-weight: bold;
+ display: block;
+ margin: 1rem 0 0.3rem;
+ }
+ input[type="text"],
+ input[type="password"] {
+ width: 100%;
+ padding: 0.75rem;
+ font-size: 1rem;
+ border-radius: 8px;
+ border: 1px solid #ccc;
+ box-sizing: border-box;
+ }
+ input[type="checkbox"] {
+ margin-right: 0.5rem;
+ }
+ .checkbox-container {
+ margin-top: 1rem;
+ font-size: 0.95rem;
+ }
+ button {
+ width: 100%;
+ margin-top: 1rem;
+ padding: 0.8rem;
+ font-size: 1rem;
+ border: none;
+ border-radius: 8px;
+ background-color: #4a90e2;
+ color: #fff;
+ cursor: pointer;
+ transition: background-color 0.2s;
+ }
+ button:hover {
+ background-color: #357ab8;
+ }
+ button:active {
+ background-color: #2f6ea2;
+ }
+ audio {
+ margin-top: 1.5rem;
+ width: 100%;
+ outline: none;
+ }
+ </style>
+</head>
+<body>
+ <div class="card">
+ <h2>馃摱 灏忔櫤澹版尝閰嶇綉</h2>
+ <label for="ssid">WiFi 鍚嶇О</label>
+ <input id="ssid" type="text" value="" placeholder="璇疯緭鍏� WiFi 鍚嶇О" />
+
+ <label for="pwd">WiFi 瀵嗙爜</label>
+ <input id="pwd" type="password" value="" placeholder="璇疯緭鍏� WiFi 瀵嗙爜" />
+
+ <div class="checkbox-container">
+ <label><input type="checkbox" id="loopCheck" checked /> 鑷姩寰幆鎾斁澹版尝</label>
+ </div>
+
+ <button onclick="generate()">馃幍 鐢熸垚骞舵挱鏀惧0娉�</button>
+ <button onclick="stopPlay()">鈴癸笍 鍋滄鎾斁</button>
+ <audio id="player" controls></audio>
+ </div>
+
+ <script>
+ const MARK = 1800;
+ const SPACE = 1500;
+ const SAMPLE_RATE = 44100;
+ const BIT_RATE = 100;
+ const START_BYTES = [0x01, 0x02];
+ const END_BYTES = [0x03, 0x04];
+ let loopTimer = null;
+
+ function checksum(data) {
+ return data.reduce((sum, b) => (sum + b) & 0xff, 0);
+ }
+
+ function toBits(byte) {
+ const bits = [];
+ for (let i = 7; i >= 0; i--) bits.push((byte >> i) & 1);
+ return bits;
+ }
+
+ function afskModulate(bits) {
+ const samplesPerBit = SAMPLE_RATE / BIT_RATE;
+ const totalSamples = Math.floor(bits.length * samplesPerBit);
+ const buffer = new Float32Array(totalSamples);
+ for (let i = 0; i < bits.length; i++) {
+ const freq = bits[i] ? MARK : SPACE;
+ for (let j = 0; j < samplesPerBit; j++) {
+ const t = (i * samplesPerBit + j) / SAMPLE_RATE;
+ buffer[i * samplesPerBit + j] = Math.sin(2 * Math.PI * freq * t);
+ }
+ }
+ return buffer;
+ }
+
+ function floatTo16BitPCM(floatSamples) {
+ const buffer = new Uint8Array(floatSamples.length * 2);
+ for (let i = 0; i < floatSamples.length; i++) {
+ const s = Math.max(-1, Math.min(1, floatSamples[i]));
+ const val = s < 0 ? s * 0x8000 : s * 0x7fff;
+ buffer[i * 2] = val & 0xff;
+ buffer[i * 2 + 1] = (val >> 8) & 0xff;
+ }
+ return buffer;
+ }
+
+ function buildWav(pcm) {
+ const wavHeader = new Uint8Array(44);
+ const dataLen = pcm.length;
+ const fileLen = 36 + dataLen;
+
+ const writeStr = (offset, str) => {
+ for (let i = 0; i < str.length; i++) wavHeader[offset + i] = str.charCodeAt(i);
+ };
+ const write32 = (offset, value) => {
+ wavHeader[offset] = value & 0xff;
+ wavHeader[offset + 1] = (value >> 8) & 0xff;
+ wavHeader[offset + 2] = (value >> 16) & 0xff;
+ wavHeader[offset + 3] = (value >> 24) & 0xff;
+ };
+ const write16 = (offset, value) => {
+ wavHeader[offset] = value & 0xff;
+ wavHeader[offset + 1] = (value >> 8) & 0xff;
+ };
+
+ writeStr(0, 'RIFF');
+ write32(4, fileLen);
+ writeStr(8, 'WAVE');
+ writeStr(12, 'fmt ');
+ write32(16, 16);
+ write16(20, 1);
+ write16(22, 1);
+ write32(24, SAMPLE_RATE);
+ write32(28, SAMPLE_RATE * 2);
+ write16(32, 2);
+ write16(34, 16);
+ writeStr(36, 'data');
+ write32(40, dataLen);
+
+ return new Blob([wavHeader, pcm], { type: 'audio/wav' });
+ }
+
+ function generate() {
+ stopPlay();
+ const ssid = document.getElementById('ssid').value.trim();
+ const pwd = document.getElementById('pwd').value.trim();
+ const dataStr = ssid + '\n' + pwd;
+ const textBytes = Array.from(new TextEncoder().encode(dataStr));
+ const fullBytes = [...START_BYTES, ...textBytes, checksum(textBytes), ...END_BYTES];
+
+ let bits = [];
+ fullBytes.forEach((b) => (bits = bits.concat(toBits(b))));
+
+ const floatBuf = afskModulate(bits);
+ const pcmBuf = floatTo16BitPCM(floatBuf);
+ const wavBlob = buildWav(pcmBuf);
+
+ const audio = document.getElementById('player');
+ audio.src = URL.createObjectURL(wavBlob);
+ audio.load();
+ audio.play();
+
+ // 淇敼浜嗚繖閲岋細浣跨敤 'ended' 浜嬩欢鏉ュ疄鐜板惊鐜挱鏀�
+ if (document.getElementById('loopCheck').checked) {
+ audio.onended = function() {
+ audio.currentTime = 0; // 浠庡ご寮�濮�
+ audio.play(); // 閲嶆柊鎾斁
+ };
+ }
+ }
+
+ function stopPlay() {
+ const audio = document.getElementById('player');
+ audio.pause();
+ audio.onended = null; // 娓呴櫎浜嬩欢鐩戝惉
+ }
+ </script>
+</body>
+</html>
diff --git a/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/README.md b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/README.md
new file mode 100644
index 0000000..9cd0f92
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/README.md
@@ -0,0 +1,110 @@
+# SPIFFS Assets Builder
+
+杩欎釜鑴氭湰鐢ㄤ簬鏋勫缓 ESP32 椤圭洰鐨� SPIFFS 璧勬簮鍒嗗尯锛屽皢鍚勭璧勬簮鏂囦欢鎵撳寘鎴愬彲鍦ㄨ澶囦笂浣跨敤鐨勬牸寮忋��
+
+## 鍔熻兘鐗规��
+
+- 澶勭悊鍞ら啋缃戠粶妯″瀷 (WakeNet Model)
+- 闆嗘垚鏂囨湰瀛椾綋鏂囦欢
+- 澶勭悊琛ㄦ儏绗﹀彿鍥剧墖闆嗗悎
+- 鑷姩鐢熸垚璧勬簮绱㈠紩鏂囦欢
+- 鎵撳寘鐢熸垚鏈�缁堢殑 `assets.bin` 鏂囦欢
+
+## 渚濊禆瑕佹眰
+
+- Python 3.6+
+- 鐩稿叧璧勬簮鏂囦欢
+
+## 浣跨敤鏂规硶
+
+### 鍩烘湰璇硶
+
+```bash
+./build.py --wakenet_model <wakenet_model_dir> \
+ --text_font <text_font_file> \
+ --emoji_collection <emoji_collection_dir>
+```
+
+### 鍙傛暟璇存槑
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呴渶 | 璇存槑 |
+|------|------|------|------|
+| `--wakenet_model` | 鐩綍璺緞 | 鍚� | 鍞ら啋缃戠粶妯″瀷鐩綍璺緞 |
+| `--text_font` | 鏂囦欢璺緞 | 鍚� | 鏂囨湰瀛椾綋鏂囦欢璺緞 |
+| `--emoji_collection` | 鐩綍璺緞 | 鍚� | 琛ㄦ儏绗﹀彿鍥剧墖闆嗗悎鐩綍璺緞 |
+
+### 浣跨敤绀轰緥
+
+```bash
+# 瀹屾暣鍙傛暟绀轰緥
+./build.py \
+ --wakenet_model ../../managed_components/espressif__esp-sr/model/wakenet_model/wn9_nihaoxiaozhi_tts \
+ --text_font ../../components/xiaozhi-fonts/build/font_puhui_common_20_4.bin \
+ --emoji_collection ../../components/xiaozhi-fonts/build/emojis_64/
+
+# 浠呭鐞嗗瓧浣撴枃浠�
+./build.py --text_font ../../components/xiaozhi-fonts/build/font_puhui_common_20_4.bin
+
+# 浠呭鐞嗚〃鎯呯鍙�
+./build.py --emoji_collection ../../components/xiaozhi-fonts/build/emojis_64/
+```
+
+## 宸ヤ綔娴佺▼
+
+1. **鍒涘缓鏋勫缓鐩綍缁撴瀯**
+ - `build/` - 涓绘瀯寤虹洰褰�
+ - `build/assets/` - 璧勬簮鏂囦欢鐩綍
+ - `build/output/` - 杈撳嚭鏂囦欢鐩綍
+
+2. **澶勭悊鍞ら啋缃戠粶妯″瀷**
+ - 澶嶅埗妯″瀷鏂囦欢鍒版瀯寤虹洰褰�
+ - 浣跨敤 `pack_model.py` 鐢熸垚 `srmodels.bin`
+ - 灏嗙敓鎴愮殑妯″瀷鏂囦欢澶嶅埗鍒拌祫婧愮洰褰�
+
+3. **澶勭悊鏂囨湰瀛椾綋**
+ - 澶嶅埗瀛椾綋鏂囦欢鍒拌祫婧愮洰褰�
+ - 鏀寔 `.bin` 鏍煎紡鐨勫瓧浣撴枃浠�
+
+4. **澶勭悊琛ㄦ儏绗﹀彿闆嗗悎**
+ - 鎵弿鎸囧畾鐩綍涓殑鍥剧墖鏂囦欢
+ - 鏀寔 `.png` 鍜� `.gif` 鏍煎紡
+ - 鑷姩鐢熸垚琛ㄦ儏绗﹀彿绱㈠紩
+
+5. **鐢熸垚閰嶇疆鏂囦欢**
+ - `index.json` - 璧勬簮绱㈠紩鏂囦欢
+ - `config.json` - 鏋勫缓閰嶇疆鏂囦欢
+
+6. **鎵撳寘鏈�缁堣祫婧�**
+ - 浣跨敤 `spiffs_assets_gen.py` 鐢熸垚 `assets.bin`
+ - 澶嶅埗鍒版瀯寤烘牴鐩綍
+
+## 杈撳嚭鏂囦欢
+
+鏋勫缓瀹屾垚鍚庯紝浼氬湪 `build/` 鐩綍涓嬬敓鎴愪互涓嬫枃浠讹細
+
+- `assets/` - 鎵�鏈夎祫婧愭枃浠�
+- `assets.bin` - 鏈�缁堢殑 SPIFFS 璧勬簮鏂囦欢
+- `config.json` - 鏋勫缓閰嶇疆
+- `output/` - 涓棿杈撳嚭鏂囦欢
+
+## 鏀寔鐨勮祫婧愭牸寮�
+
+- **妯″瀷鏂囦欢**: `.bin` (閫氳繃 pack_model.py 澶勭悊)
+- **瀛椾綋鏂囦欢**: `.bin`
+- **鍥剧墖鏂囦欢**: `.png`, `.gif`
+- **閰嶇疆鏂囦欢**: `.json`
+
+## 閿欒澶勭悊
+
+鑴氭湰鍖呭惈瀹屽杽鐨勯敊璇鐞嗘満鍒讹細
+
+- 妫�鏌ユ簮鏂囦欢/鐩綍鏄惁瀛樺湪
+- 楠岃瘉瀛愯繘绋嬫墽琛岀粨鏋�
+- 鎻愪緵璇︾粏鐨勯敊璇俊鎭拰璀﹀憡
+
+## 娉ㄦ剰浜嬮」
+
+1. 纭繚鎵�鏈変緷璧栫殑 Python 鑴氭湰閮藉湪鍚屼竴鐩綍涓�
+2. 璧勬簮鏂囦欢璺緞浣跨敤缁濆璺緞鎴栫浉瀵逛簬鑴氭湰鐩綍鐨勮矾寰�
+3. 鏋勫缓杩囩▼浼氭竻鐞嗕箣鍓嶇殑鏋勫缓鏂囦欢
+4. 鐢熸垚鐨� `assets.bin` 鏂囦欢澶у皬鍙� SPIFFS 鍒嗗尯澶у皬闄愬埗
diff --git a/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build.py b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build.py
new file mode 100644
index 0000000..47069d2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build.py
@@ -0,0 +1,400 @@
+#!/usr/bin/env python3
+"""
+Build the spiffs assets partition
+
+Usage:
+ ./build.py --wakenet_model <wakenet_model_dir> \
+ --text_font <text_font_file> \
+ --emoji_collection <emoji_collection_dir>
+
+Example:
+ ./build.py --wakenet_model ../../managed_components/espressif__esp-sr/model/wakenet_model/wn9_nihaoxiaozhi_tts \
+ --text_font ../../components/xiaozhi-fonts/build/font_puhui_common_20_4.bin \
+ --emoji_collection ../../components/xiaozhi-fonts/build/emojis_64/
+"""
+
+import os
+import sys
+import shutil
+import argparse
+import subprocess
+import json
+from pathlib import Path
+
+
+def ensure_dir(directory):
+ """Ensure directory exists, create if not"""
+ os.makedirs(directory, exist_ok=True)
+
+
+def copy_file(src, dst):
+ """Copy file"""
+ if os.path.exists(src):
+ shutil.copy2(src, dst)
+ print(f"Copied: {src} -> {dst}")
+ else:
+ print(f"Warning: Source file does not exist: {src}")
+
+
+def copy_directory(src, dst):
+ """Copy directory"""
+ if os.path.exists(src):
+ shutil.copytree(src, dst, dirs_exist_ok=True)
+ print(f"Copied directory: {src} -> {dst}")
+ else:
+ print(f"Warning: Source directory does not exist: {src}")
+
+
+def process_wakenet_model(wakenet_model_dir, build_dir, assets_dir):
+ """Process wakenet_model parameter"""
+ if not wakenet_model_dir:
+ return None
+
+ # Copy input directory to build directory
+ wakenet_build_dir = os.path.join(build_dir, "wakenet_model")
+ if os.path.exists(wakenet_build_dir):
+ shutil.rmtree(wakenet_build_dir)
+ copy_directory(wakenet_model_dir, os.path.join(wakenet_build_dir, os.path.basename(wakenet_model_dir)))
+
+ # Use pack_model.py to generate srmodels.bin
+ srmodels_output = os.path.join(wakenet_build_dir, "srmodels.bin")
+ try:
+ subprocess.run([
+ sys.executable, "pack_model.py",
+ "-m", wakenet_build_dir,
+ "-o", "srmodels.bin"
+ ], check=True, cwd=os.path.dirname(__file__))
+ print(f"Generated: {srmodels_output}")
+ # Copy srmodels.bin to assets directory
+ copy_file(srmodels_output, os.path.join(assets_dir, "srmodels.bin"))
+ return "srmodels.bin"
+ except subprocess.CalledProcessError as e:
+ print(f"Error: Failed to generate srmodels.bin: {e}")
+ return None
+
+
+def process_text_font(text_font_file, assets_dir):
+ """Process text_font parameter"""
+ if not text_font_file:
+ return None
+
+ # Copy input file to build/assets directory
+ font_filename = os.path.basename(text_font_file)
+ font_dst = os.path.join(assets_dir, font_filename)
+ copy_file(text_font_file, font_dst)
+
+ return font_filename
+
+
+def process_emoji_collection(emoji_collection_dir, assets_dir):
+ """Process emoji_collection parameter"""
+ if not emoji_collection_dir:
+ return []
+
+ emoji_list = []
+
+ # Copy each image from input directory to build/assets directory
+ for root, dirs, files in os.walk(emoji_collection_dir):
+ for file in files:
+ if file.lower().endswith(('.png', '.gif')):
+ # Copy file
+ src_file = os.path.join(root, file)
+ dst_file = os.path.join(assets_dir, file)
+ copy_file(src_file, dst_file)
+
+ # Get filename without extension
+ filename_without_ext = os.path.splitext(file)[0]
+
+ # Add to emoji list
+ emoji_list.append({
+ "name": filename_without_ext,
+ "file": file
+ })
+
+ return emoji_list
+
+def load_emoji_config(emoji_collection_dir):
+ """Load emoji config from config.json file"""
+ config_path = os.path.join(emoji_collection_dir, "emote.json")
+ if not os.path.exists(config_path):
+ print(f"Warning: Config file not found: {config_path}")
+ return {}
+
+ try:
+ with open(config_path, 'r', encoding='utf-8') as f:
+ config_data = json.load(f)
+
+ # Convert list format to dict for easy lookup
+ config_dict = {}
+ for item in config_data:
+ if "emote" in item:
+ config_dict[item["emote"]] = item
+
+ return config_dict
+ except Exception as e:
+ print(f"Error loading config file {config_path}: {e}")
+ return {}
+
+def process_board_emoji_collection(emoji_collection_dir, target_board_dir, assets_dir):
+ """Process emoji_collection parameter"""
+ if not emoji_collection_dir:
+ return []
+
+ emoji_config = load_emoji_config(target_board_dir)
+ print(f"Loaded emoji config with {len(emoji_config)} entries")
+
+ emoji_list = []
+
+ for emote_name, config in emoji_config.items():
+
+ if "src" not in config:
+ print(f"Error: No src field found for emote '{emote_name}' in config")
+ continue
+
+ eaf_file_path = os.path.join(emoji_collection_dir, config["src"])
+ file_exists = os.path.exists(eaf_file_path)
+
+ if not file_exists:
+ print(f"Warning: EAF file not found for emote '{emote_name}': {eaf_file_path}")
+ else:
+ # Copy eaf file to assets directory
+ copy_file(eaf_file_path, os.path.join(assets_dir, config["src"]))
+
+ # Create emoji entry with src as file (merge file and src)
+ emoji_entry = {
+ "name": emote_name,
+ "file": config["src"] # Use src as the actual file
+ }
+
+ eaf_properties = {}
+
+ if not file_exists:
+ eaf_properties["lack"] = True
+
+ if "loop" in config:
+ eaf_properties["loop"] = config["loop"]
+
+ if "fps" in config:
+ eaf_properties["fps"] = config["fps"]
+
+ if eaf_properties:
+ emoji_entry["eaf"] = eaf_properties
+
+ status = "MISSING" if not file_exists else "OK"
+ eaf_info = emoji_entry.get('eaf', {})
+ print(f"emote '{emote_name}': file='{emoji_entry['file']}', status={status}, lack={eaf_info.get('lack', False)}, loop={eaf_info.get('loop', 'none')}, fps={eaf_info.get('fps', 'none')}")
+
+ emoji_list.append(emoji_entry)
+
+ print(f"Successfully processed {len(emoji_list)} emotes from config")
+ return emoji_list
+
+def process_board_icon_collection(icon_collection_dir, assets_dir):
+ """Process emoji_collection parameter"""
+ if not icon_collection_dir:
+ return []
+
+ icon_list = []
+
+ for root, dirs, files in os.walk(icon_collection_dir):
+ for file in files:
+ if file.lower().endswith(('.bin')) or file.lower() == 'listen.eaf':
+ src_file = os.path.join(root, file)
+ dst_file = os.path.join(assets_dir, file)
+ copy_file(src_file, dst_file)
+
+ filename_without_ext = os.path.splitext(file)[0]
+
+ icon_list.append({
+ "name": filename_without_ext,
+ "file": file
+ })
+
+ return icon_list
+def process_board_layout(layout_json_file, assets_dir):
+ """Process layout_json parameter"""
+ if not layout_json_file:
+ print(f"Warning: Layout json file not provided")
+ return []
+
+ print(f"Processing layout_json: {layout_json_file}")
+ print(f"assets_dir: {assets_dir}")
+
+ if os.path.isdir(layout_json_file):
+ layout_json_path = os.path.join(layout_json_file, "layout.json")
+ if not os.path.exists(layout_json_path):
+ print(f"Warning: layout.json not found in directory: {layout_json_file}")
+ return []
+ layout_json_file = layout_json_path
+ elif not os.path.isfile(layout_json_file):
+ print(f"Warning: Layout json file not found: {layout_json_file}")
+ return []
+
+ try:
+ with open(layout_json_file, 'r', encoding='utf-8') as f:
+ layout_data = json.load(f)
+
+ # Layout data is now directly an array, no need to get "layout" key
+ layout_items = layout_data if isinstance(layout_data, list) else layout_data.get("layout", [])
+
+ processed_layout = []
+ for item in layout_items:
+ processed_item = {
+ "name": item.get("name", ""),
+ "align": item.get("align", ""),
+ "x": item.get("x", 0),
+ "y": item.get("y", 0)
+ }
+
+ if "width" in item:
+ processed_item["width"] = item["width"]
+ if "height" in item:
+ processed_item["height"] = item["height"]
+
+ processed_layout.append(processed_item)
+
+ print(f"Processed {len(processed_layout)} layout elements")
+ return processed_layout
+
+ except Exception as e:
+ print(f"Error reading/processing layout.json: {e}")
+ return []
+
+def process_board_collection(target_board_dir, res_path, assets_dir):
+ """Process board collection - merge icon, emoji, and layout processing"""
+
+ # Process all collections
+ if os.path.exists(res_path) and os.path.exists(target_board_dir):
+ emoji_collection = process_board_emoji_collection(res_path, target_board_dir, assets_dir)
+ icon_collection = process_board_icon_collection(res_path, assets_dir)
+ layout_json = process_board_layout(target_board_dir, assets_dir)
+ else:
+ print(f"Warning: EAF directory not found: {res_path} or {target_board_dir}")
+ emoji_collection = []
+ icon_collection = []
+ layout_json = []
+
+ return emoji_collection, icon_collection, layout_json
+
+def generate_index_json(assets_dir, srmodels, text_font, emoji_collection, icon_collection, layout_json):
+ """Generate index.json file"""
+ index_data = {
+ "version": 1
+ }
+
+ if srmodels:
+ index_data["srmodels"] = srmodels
+
+ if text_font:
+ index_data["text_font"] = text_font
+
+ if emoji_collection:
+ index_data["emoji_collection"] = emoji_collection
+
+ if icon_collection:
+ index_data["icon_collection"] = icon_collection
+
+ if layout_json:
+ index_data["layout"] = layout_json
+
+ # Write index.json
+ index_path = os.path.join(assets_dir, "index.json")
+ with open(index_path, 'w', encoding='utf-8') as f:
+ json.dump(index_data, f, indent=4, ensure_ascii=False)
+
+ print(f"Generated: {index_path}")
+
+
+def generate_config_json(build_dir, assets_dir):
+ """Generate config.json file"""
+ # Get absolute path of current working directory
+ workspace_dir = os.path.abspath(os.path.join(os.path.dirname(__file__)))
+
+ config_data = {
+ "include_path": os.path.join(workspace_dir, "build/include"),
+ "assets_path": os.path.join(workspace_dir, "build/assets"),
+ "image_file": os.path.join(workspace_dir, "build/output/assets.bin"),
+ "lvgl_ver": "9.3.0",
+ "assets_size": "0x400000",
+ "support_format": ".png, .gif, .jpg, .bin, .json, .eaf",
+ "name_length": "32",
+ "split_height": "0",
+ "support_qoi": False,
+ "support_spng": False,
+ "support_sjpg": False,
+ "support_sqoi": False,
+ "support_raw": False,
+ "support_raw_dither": False,
+ "support_raw_bgr": False
+ }
+
+ # Write config.json
+ config_path = os.path.join(build_dir, "config.json")
+ with open(config_path, 'w', encoding='utf-8') as f:
+ json.dump(config_data, f, indent=4, ensure_ascii=False)
+
+ print(f"Generated: {config_path}")
+ return config_path
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Build the spiffs assets partition')
+ parser.add_argument('--wakenet_model', help='Path to wakenet model directory')
+ parser.add_argument('--text_font', help='Path to text font file')
+ parser.add_argument('--emoji_collection', help='Path to emoji collection directory')
+
+ parser.add_argument('--res_path', help='Path to res directory')
+ parser.add_argument('--target_board', help='Path to target board directory')
+
+ args = parser.parse_args()
+
+ # Get script directory
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+
+ # Set directory paths
+ build_dir = os.path.join(script_dir, "build")
+ assets_dir = os.path.join(build_dir, "assets")
+ if os.path.exists(assets_dir):
+ shutil.rmtree(assets_dir)
+
+ # Ensure directories exist
+ ensure_dir(build_dir)
+ ensure_dir(assets_dir)
+
+ print("Starting to build SPIFFS assets partition...")
+
+ # Process each parameter
+ srmodels = process_wakenet_model(args.wakenet_model, build_dir, assets_dir)
+ text_font = process_text_font(args.text_font, assets_dir)
+
+ if(args.target_board):
+ emoji_collection, icon_collection, layout_json = process_board_collection(args.target_board, args.res_path, assets_dir)
+ else:
+ emoji_collection = process_emoji_collection(args.emoji_collection, assets_dir)
+ icon_collection = []
+ layout_json = []
+
+ # Generate index.json
+ generate_index_json(assets_dir, srmodels, text_font, emoji_collection, icon_collection, layout_json)
+
+ # Generate config.json
+ config_path = generate_config_json(build_dir, assets_dir)
+
+ # Use spiffs_assets_gen.py to package final build/assets.bin
+ try:
+ subprocess.run([
+ sys.executable, "spiffs_assets_gen.py",
+ "--config", config_path
+ ], check=True, cwd=script_dir)
+ print("Successfully packaged assets.bin")
+ except subprocess.CalledProcessError as e:
+ print(f"Error: Failed to package assets.bin: {e}")
+ sys.exit(1)
+
+ # Copy build/output/assets.bin to build/assets.bin
+ shutil.copy(os.path.join(build_dir, "output", "assets.bin"), os.path.join(build_dir, "assets.bin"))
+ print("Build completed!")
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build_all.py b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build_all.py
new file mode 100644
index 0000000..33efbbc
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/build_all.py
@@ -0,0 +1,148 @@
+#!/usr/bin/env python3
+"""
+Build multiple spiffs assets partitions with different parameter combinations
+
+This script calls build.py with different combinations of:
+- wakenet_models
+- text_fonts
+- emoji_collections
+
+And generates assets.bin files with names like:
+wn9_nihaoxiaozhi_tts-font_puhui_common_20_4-emojis_32.bin
+"""
+
+import os
+import sys
+import shutil
+import subprocess
+
+
+def ensure_dir(directory):
+ """Ensure directory exists, create if not"""
+ os.makedirs(directory, exist_ok=True)
+
+
+def get_file_path(base_dir, filename):
+ """Get full path for a file, handling 'none' case"""
+ if filename == "none":
+ return None
+ return os.path.join(base_dir, f"{filename}.bin" if not filename.startswith("emojis_") else filename)
+
+
+def build_assets(wakenet_model, text_font, emoji_collection, build_dir, final_dir):
+ """Build assets.bin using build.py with given parameters"""
+
+ # Prepare arguments for build.py
+ cmd = [sys.executable, "build.py"]
+
+ if wakenet_model != "none":
+ wakenet_path = os.path.join("../../managed_components/espressif__esp-sr/model/wakenet_model", wakenet_model)
+ cmd.extend(["--wakenet_model", wakenet_path])
+
+ if text_font != "none":
+ text_font_path = os.path.join("../../components/78__xiaozhi-fonts/cbin", f"{text_font}.bin")
+ cmd.extend(["--text_font", text_font_path])
+
+ if emoji_collection != "none":
+ emoji_path = os.path.join("../../components/xiaozhi-fonts/build", emoji_collection)
+ cmd.extend(["--emoji_collection", emoji_path])
+
+ print(f"\n姝e湪鏋勫缓: {wakenet_model}-{text_font}-{emoji_collection}")
+ print(f"鎵ц鍛戒护: {' '.join(cmd)}")
+
+ try:
+ # Run build.py
+ result = subprocess.run(cmd, check=True, cwd=os.path.dirname(__file__))
+
+ # Generate output filename
+ output_name = f"{wakenet_model}-{text_font}-{emoji_collection}.bin"
+
+ # Copy generated assets.bin to final directory with new name
+ src_path = os.path.join(build_dir, "assets.bin")
+ dst_path = os.path.join(final_dir, output_name)
+
+ if os.path.exists(src_path):
+ shutil.copy2(src_path, dst_path)
+ print(f"鉁� 鎴愬姛鐢熸垚: {output_name}")
+ return True
+ else:
+ print(f"鉁� 閿欒: 鏈壘鍒扮敓鎴愮殑 assets.bin 鏂囦欢")
+ return False
+
+ except subprocess.CalledProcessError as e:
+ print(f"鉁� 鏋勫缓澶辫触: {e}")
+ return False
+ except Exception as e:
+ print(f"鉁� 鏈煡閿欒: {e}")
+ return False
+
+
+def main():
+ # Configuration
+ wakenet_models = [
+ "none",
+ "wn9_nihaoxiaozhi_tts",
+ "wn9s_nihaoxiaozhi"
+ ]
+
+ text_fonts = [
+ "none",
+ "font_puhui_common_14_1",
+ "font_puhui_common_16_4",
+ "font_puhui_common_20_4",
+ "font_puhui_common_30_4",
+ ]
+
+ emoji_collections = [
+ "none",
+ "emojis_32",
+ "emojis_64",
+ ]
+
+ # Get script directory
+ script_dir = os.path.dirname(os.path.abspath(__file__))
+
+ # Set directory paths
+ build_dir = os.path.join(script_dir, "build")
+ final_dir = os.path.join(build_dir, "final")
+
+ # Ensure directories exist
+ ensure_dir(build_dir)
+ ensure_dir(final_dir)
+
+ print("寮�濮嬫瀯寤哄涓� SPIFFS assets 鍒嗗尯...")
+ print(f"杈撳嚭鐩綍: {final_dir}")
+
+ # Calculate total combinations
+ total_combinations = len(wakenet_models) * len(text_fonts) * len(emoji_collections)
+
+ # Track successful builds
+ successful_builds = 0
+
+ # Build all combinations with emoji_collections
+ for wakenet_model in wakenet_models:
+ for text_font in text_fonts:
+ for emoji_collection in emoji_collections:
+ if build_assets(wakenet_model, text_font, emoji_collection, build_dir, final_dir):
+ successful_builds += 1
+
+ print(f"\n鏋勫缓瀹屾垚!")
+ print(f"鎴愬姛鏋勫缓: {successful_builds}/{total_combinations}")
+ print(f"杈撳嚭鏂囦欢浣嶇疆: {final_dir}")
+
+ # List generated files
+ if os.path.exists(final_dir):
+ files = [f for f in os.listdir(final_dir) if f.endswith('.bin')]
+ if files:
+ print("\n鐢熸垚鐨勬枃浠�:")
+ for file in sorted(files):
+ file_size = os.path.getsize(os.path.join(final_dir, file))
+ print(f" {file} ({file_size:,} bytes)")
+ else:
+ print("\n鏈壘鍒扮敓鎴愮殑 .bin 鏂囦欢")
+
+
+if __name__ == "__main__":
+ main()
+
+
diff --git a/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/pack_model.py b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/pack_model.py
new file mode 100644
index 0000000..1fc85aa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/pack_model.py
@@ -0,0 +1,123 @@
+import os
+import struct
+import argparse
+
+
+def struct_pack_string(string, max_len=None):
+ """
+ pack string to binary data.
+ if max_len is None, max_len = len(string) + 1
+ else len(string) < max_len, the left will be padded by struct.pack('x')
+
+ string: input python string
+ max_len: output
+ """
+
+ if max_len == None :
+ max_len = len(string)
+ else:
+ assert len(string) <= max_len
+
+ left_num = max_len - len(string)
+ out_bytes = None
+ for char in string:
+ if out_bytes == None:
+ out_bytes = struct.pack('b', ord(char))
+ else:
+ out_bytes += struct.pack('b', ord(char))
+ for i in range(left_num):
+ out_bytes += struct.pack('x')
+ return out_bytes
+
+def read_data(filename):
+ """
+ Read binary data, like index and mndata
+ """
+ data = None
+ with open(filename, "rb") as f:
+ data = f.read()
+ return data
+
+def pack_models(model_path, out_file="srmodels.bin"):
+ """
+ Pack all models into one binary file by the following format:
+ {
+ model_num: int
+ model1_info: model_info_t
+ model2_info: model_info_t
+ ...
+ model1_index,model1_data,model1_MODEL_INFO
+ model1_index,model1_data,model1_MODEL_INFO
+ ...
+ }model_pack_t
+
+ {
+ model_name: char[32]
+ file_number: int
+ file1_name: char[32]
+ file1_start: int
+ file1_len: int
+ file2_name: char[32]
+ file2_start: int // data_len = info_start - data_start
+ file2_len: int
+ ...
+ }model_info_t
+
+ model_path: the path of models
+ out_file: the ouput binary filename
+ """
+
+ models = {}
+ file_num = 0
+ model_num = 0
+ for root, dirs, _ in os.walk(model_path):
+ for model_name in dirs:
+ models[model_name] = {}
+ model_dir = os.path.join(root, model_name)
+ model_num += 1
+ for _, _, files in os.walk(model_dir):
+ for file_name in files:
+ file_num += 1
+ file_path = os.path.join(model_dir, file_name)
+ models[model_name][file_name] = read_data(file_path)
+
+ model_num = len(models)
+ header_len = 4 + model_num*(32+4) + file_num*(32+4+4)
+ out_bin = struct.pack('I', model_num) # model number
+ data_bin = None
+ for key in models:
+ model_bin = struct_pack_string(key, 32) # + model name
+ model_bin += struct.pack('I', len(models[key])) # + file number in this model
+
+ for file_name in models[key]:
+ model_bin += struct_pack_string(file_name, 32) # + file name
+ if data_bin == None:
+ model_bin += struct.pack('I', header_len)
+ data_bin = models[key][file_name]
+ model_bin += struct.pack('I', len(models[key][file_name]))
+ # print(file_name, header_len, len(models[key][file_name]), len(data_bin))
+ else:
+ model_bin += struct.pack('I', header_len+len(data_bin))
+ # print(file_name, header_len+len(data_bin), len(models[key][file_name]))
+ data_bin += models[key][file_name]
+ model_bin += struct.pack('I', len(models[key][file_name]))
+
+ out_bin += model_bin
+ assert len(out_bin) == header_len
+ if data_bin != None:
+ out_bin += data_bin
+
+ out_file = os.path.join(model_path, out_file)
+ with open(out_file, "wb") as f:
+ f.write(out_bin)
+
+
+if __name__ == "__main__":
+ # input parameter
+ parser = argparse.ArgumentParser(description='Model package tool')
+ parser.add_argument('-m', '--model_path', help="the path of model files")
+ parser.add_argument('-o', '--out_file', default="srmodels.bin", help="the path of binary file")
+ args = parser.parse_args()
+
+ # convert(args.model_path, args.out_file)
+ pack_models(model_path=args.model_path, out_file=args.out_file)
diff --git a/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/spiffs_assets_gen.py b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/spiffs_assets_gen.py
new file mode 100644
index 0000000..e07aaaa
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/spiffs_assets/spiffs_assets_gen.py
@@ -0,0 +1,647 @@
+# SPDX-FileCopyrightText: 2024-2025 Espressif Systems (Shanghai) CO LTD
+# SPDX-License-Identifier: Apache-2.0
+import io
+import os
+import argparse
+import json
+import shutil
+import math
+import sys
+import time
+import numpy as np
+import importlib
+import subprocess
+import urllib.request
+
+from PIL import Image
+from datetime import datetime
+from dataclasses import dataclass
+from typing import List
+from pathlib import Path
+from packaging import version
+
+sys.dont_write_bytecode = True
+
+GREEN = '\033[1;32m'
+RED = '\033[1;31m'
+RESET = '\033[0m'
+
+@dataclass
+class AssetCopyConfig:
+ assets_path: str
+ target_path: str
+ spng_enable: bool
+ sjpg_enable: bool
+ qoi_enable: bool
+ sqoi_enable: bool
+ row_enable: bool
+ support_format: List[str]
+ split_height: int
+
+@dataclass
+class PackModelsConfig:
+ target_path: str
+ include_path: str
+ image_file: str
+ assets_path: str
+ name_length: int
+
+def generate_header_filename(path):
+ asset_name = os.path.basename(path)
+
+ header_filename = f'mmap_generate_{asset_name}.h'
+ return header_filename
+
+def compute_checksum(data):
+ checksum = sum(data) & 0xFFFF
+ return checksum
+
+def sort_key(filename):
+ basename, extension = os.path.splitext(filename)
+ return extension, basename
+
+def download_v8_script(convert_path):
+ """
+ Ensure that the lvgl_image_converter repository is present at the specified path.
+ If not, clone the repository. Then, checkout to a specific commit.
+
+ Parameters:
+ - convert_path (str): The directory path where lvgl_image_converter should be located.
+ """
+
+ # Check if convert_path is not empty
+ if convert_path:
+ # If the directory does not exist, create it and clone the repository
+ if not os.path.exists(convert_path):
+ os.makedirs(convert_path, exist_ok=True)
+ try:
+ subprocess.run(
+ ['git', 'clone', 'https://github.com/W-Mai/lvgl_image_converter.git', convert_path],
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ check=True
+ )
+ except subprocess.CalledProcessError as e:
+ print(f'Git clone failed: {e}')
+ sys.exit(1)
+
+ # Checkout to the specific commit
+ try:
+ subprocess.run(
+ ['git', 'checkout', '9174634e9dcc1b21a63668969406897aad650f35'],
+ cwd=convert_path,
+ stdout=subprocess.DEVNULL,
+ stderr=subprocess.DEVNULL,
+ check=True
+ )
+ except subprocess.CalledProcessError as e:
+ print(f'Failed to checkout to the specific commit: {e}')
+ sys.exit(1)
+ else:
+ print('Error: convert_path is NULL')
+ sys.exit(1)
+
+def download_v9_script(url: str, destination: str) -> None:
+ """
+ Download a Python script from a URL to a local destination.
+
+ Parameters:
+ - url (str): URL to download the script from.
+ - destination (str): Local path to save the downloaded script.
+
+ Raises:
+ - Exception: If the download fails.
+ """
+ file_path = Path(destination)
+
+ # Check if the file already exists
+ if file_path.exists():
+ if file_path.is_file():
+ return
+
+ try:
+ # Create the parent directories if they do not exist
+ file_path.parent.mkdir(parents=True, exist_ok=True)
+
+ # Open the URL and retrieve the data
+ with urllib.request.urlopen(url) as response, open(file_path, 'wb') as out_file:
+ data = response.read() # Read the entire response
+ out_file.write(data) # Write data to the local file
+
+ except urllib.error.HTTPError as e:
+ print(f'HTTP Error: {e.code} - {e.reason} when accessing {url}')
+ sys.exit(1)
+ except urllib.error.URLError as e:
+ print(f'URL Error: {e.reason} when accessing {url}')
+ sys.exit(1)
+ except Exception as e:
+ print(f'An unexpected error occurred: {e}')
+ sys.exit(1)
+
+def split_image(im, block_size, input_dir, ext, convert_to_qoi):
+ """Splits the image into blocks based on the block size."""
+ width, height = im.size
+
+ if block_size:
+ splits = math.ceil(height / block_size)
+ else:
+ splits = 1
+
+ for i in range(splits):
+ if i < splits - 1:
+ crop = im.crop((0, i * block_size, width, (i + 1) * block_size))
+ else:
+ crop = im.crop((0, i * block_size, width, height))
+
+ output_path = os.path.join(input_dir, str(i) + ext)
+ crop.save(output_path, quality=100)
+
+ qoi_module = importlib.import_module('qoi-conv.qoi')
+ Qoi = qoi_module.Qoi
+ replace_extension = qoi_module.replace_extension
+
+ if convert_to_qoi:
+ with Image.open(output_path) as img:
+ if img.mode != 'RGBA':
+ img = img.convert('RGBA')
+
+ img_data = np.asarray(img)
+ out_path = qoi_module.replace_extension(output_path, 'qoi')
+ new_image = qoi_module.Qoi().save(out_path, img_data)
+ os.remove(output_path)
+
+
+ return width, height, splits
+
+def create_header(width, height, splits, split_height, lenbuf, ext):
+ """Creates the header for the output file based on the format."""
+ header = bytearray()
+
+ if ext.lower() == '.jpg':
+ header += bytearray('_SJPG__'.encode('UTF-8'))
+ elif ext.lower() == '.png':
+ header += bytearray('_SPNG__'.encode('UTF-8'))
+ elif ext.lower() == '.qoi':
+ header += bytearray('_SQOI__'.encode('UTF-8'))
+
+ # 6 BYTES VERSION
+ header += bytearray(('\x00V1.00\x00').encode('UTF-8'))
+
+ # WIDTH 2 BYTES
+ header += width.to_bytes(2, byteorder='little')
+
+ # HEIGHT 2 BYTES
+ header += height.to_bytes(2, byteorder='little')
+
+ # NUMBER OF ITEMS 2 BYTES
+ header += splits.to_bytes(2, byteorder='little')
+
+ # SPLIT HEIGHT 2 BYTES
+ header += split_height.to_bytes(2, byteorder='little')
+
+ for item_len in lenbuf:
+ # LENGTH 2 BYTES
+ header += item_len.to_bytes(2, byteorder='little')
+
+ return header
+
+def save_image(output_file_path, header, split_data):
+ """Saves the image with the constructed header and split data."""
+ with open(output_file_path, 'wb') as f:
+ if header is not None:
+ f.write(header + split_data)
+ else:
+ f.write(split_data)
+
+def handle_lvgl_version_v9(input_file: str, input_dir: str,
+ input_filename: str, convert_path: str) -> None:
+ """
+ Handle conversion for LVGL versions greater than 9.0.
+
+ Parameters:
+ - input_file (str): Path to the input image file.
+ - input_dir (str): Directory of the input file.
+ - input_filename (str): Name of the input file.
+ - convert_path (str): Path for conversion scripts and outputs.
+ """
+
+ convert_file = os.path.join(convert_path, 'LVGLImage.py')
+ lvgl_image_url = 'https://raw.githubusercontent.com/lvgl/lvgl/master/scripts/LVGLImage.py'
+
+ download_v9_script(url=lvgl_image_url, destination=convert_file)
+ lvgl_script = Path(convert_file)
+
+ cmd = [
+ 'python',
+ str(lvgl_script),
+ '--ofmt', 'BIN',
+ '--cf', config_data['support_raw_cf'],
+ '--compress', 'NONE',
+ '--output', str(input_dir),
+ input_file
+ ]
+
+ try:
+ result = subprocess.run(
+ cmd,
+ check=True,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ text=True
+ )
+ print(f'Completed {input_filename} -> BIN')
+ except subprocess.CalledProcessError as e:
+ print('An error occurred while executing LVGLImage.py:')
+ print(e.stderr)
+ sys.exit(e.returncode)
+
+def handle_lvgl_version_v8(input_file: str, input_dir: str, input_filename: str, convert_path: str) -> None:
+ """
+ Handle conversion for supported LVGL versions (<= 9.0).
+
+ Parameters:
+ - input_file (str): Path to the input image file.
+ - input_dir (str): Directory of the input file.
+ - input_filename (str): Name of the input file.
+ - convert_path (str): Path for conversion scripts and outputs.
+ """
+
+ download_v8_script(convert_path=convert_path)
+
+ if convert_path not in sys.path:
+ sys.path.append(convert_path)
+
+ try:
+ import lv_img_conv
+ except ImportError as e:
+ print(f"Failed to import 'lv_img_conv' from '{convert_path}': {e}")
+ sys.exit(1)
+
+ try:
+ lv_img_conv.conv_one_file(
+ root=Path(input_dir),
+ filepath=Path(input_file),
+ f=config_data['support_raw_ff'],
+ cf=config_data['support_raw_cf'],
+ ff='BIN',
+ dither=config_data['support_raw_dither'],
+ bgr_mode=config_data['support_raw_bgr'],
+ )
+ print(f'Completed {input_filename} -> BIN')
+ except KeyError as e:
+ print(f'Missing configuration key: {e}')
+ sys.exit(1)
+ except Exception as e:
+ print(f'An error occurred during conversion: {e}')
+ sys.exit(1)
+
+def process_image(input_file, height_str, output_extension, convert_to_qoi=False):
+ """Main function to process the image and save it as .sjpg, .spng, or .sqoi."""
+ try:
+ SPLIT_HEIGHT = int(height_str)
+ if SPLIT_HEIGHT < 0:
+ raise ValueError('Height must be a positive integer')
+ except ValueError as e:
+ print('Error: Height must be a positive integer')
+ sys.exit(1)
+
+ input_dir, input_filename = os.path.split(input_file)
+ base_filename, ext = os.path.splitext(input_filename)
+ OUTPUT_FILE_NAME = base_filename
+
+ try:
+ im = Image.open(input_file)
+ except Exception as e:
+ print('Error:', e)
+ sys.exit(0)
+
+ width, height, splits = split_image(im, SPLIT_HEIGHT, input_dir, ext, convert_to_qoi)
+
+ split_data = bytearray()
+ lenbuf = []
+
+ if convert_to_qoi:
+ ext = '.qoi'
+
+ for i in range(splits):
+ with open(os.path.join(input_dir, str(i) + ext), 'rb') as f:
+ a = f.read()
+ split_data += a
+ lenbuf.append(len(a))
+ os.remove(os.path.join(input_dir, str(i) + ext))
+
+ header = None
+ if splits == 1 and convert_to_qoi:
+ output_file_path = os.path.join(input_dir, OUTPUT_FILE_NAME + ext)
+ else:
+ header = create_header(width, height, splits, SPLIT_HEIGHT, lenbuf, ext)
+ output_file_path = os.path.join(input_dir, OUTPUT_FILE_NAME + output_extension)
+
+ save_image(output_file_path, header, split_data)
+
+ print('Completed', input_filename, '->', os.path.basename(output_file_path))
+
+def convert_image_to_qoi(input_file, height_str):
+ process_image(input_file, height_str, '.sqoi', convert_to_qoi=True)
+
+def convert_image_to_simg(input_file, height_str):
+ input_dir, input_filename = os.path.split(input_file)
+ _, ext = os.path.splitext(input_filename)
+ output_extension = '.sjpg' if ext.lower() == '.jpg' else '.spng'
+ process_image(input_file, height_str, output_extension, convert_to_qoi=False)
+
+def convert_image_to_raw(input_file: str) -> None:
+ """
+ Convert an image to raw binary format compatible with LVGL.
+
+ Parameters:
+ - input_file (str): Path to the input image file.
+
+ Raises:
+ - FileNotFoundError: If required scripts are not found.
+ - subprocess.CalledProcessError: If the external conversion script fails.
+ - KeyError: If required keys are missing in config_data.
+ """
+ input_dir, input_filename = os.path.split(input_file)
+ _, ext = os.path.splitext(input_filename)
+ convert_path = os.path.join(os.path.dirname(input_file), 'lvgl_image_converter')
+ lvgl_ver_str = config_data.get('lvgl_ver', '9.0.0')
+
+ try:
+ lvgl_version = version.parse(lvgl_ver_str)
+ except version.InvalidVersion:
+ print(f'Invalid LVGL version format: {lvgl_ver_str}')
+ sys.exit(1)
+
+ if lvgl_version >= version.parse('9.0.0'):
+ handle_lvgl_version_v9(
+ input_file=input_file,
+ input_dir=input_dir,
+ input_filename=input_filename,
+ convert_path=convert_path
+ )
+ else:
+ handle_lvgl_version_v8(
+ input_file=input_file,
+ input_dir=input_dir,
+ input_filename=input_filename,
+ convert_path=convert_path
+ )
+
+def pack_assets(config: PackModelsConfig):
+ """
+ Pack models based on the provided configuration.
+ """
+
+ target_path = config.target_path
+ assets_include_path = config.include_path
+ out_file = config.image_file
+ assets_path = config.assets_path
+ max_name_len = config.name_length
+
+ merged_data = bytearray()
+ file_info_list = []
+ skip_files = ['config.json', 'lvgl_image_converter']
+
+ file_list = sorted(os.listdir(target_path), key=sort_key)
+ for filename in file_list:
+ if filename in skip_files:
+ continue
+
+ file_path = os.path.join(target_path, filename)
+ file_name = os.path.basename(file_path)
+ file_size = os.path.getsize(file_path)
+
+ try:
+ img = Image.open(file_path)
+ width, height = img.size
+ except Exception as e:
+ # print("Error:", e)
+ _, file_extension = os.path.splitext(file_path)
+ if file_extension.lower() in ['.sjpg', '.spng', '.sqoi']:
+ offset = 14
+ with open(file_path, 'rb') as f:
+ f.seek(offset)
+ width_bytes = f.read(2)
+ height_bytes = f.read(2)
+ width = int.from_bytes(width_bytes, byteorder='little')
+ height = int.from_bytes(height_bytes, byteorder='little')
+ else:
+ width, height = 0, 0
+
+ file_info_list.append((file_name, len(merged_data), file_size, width, height))
+ # Add 0x5A5A prefix to merged_data
+ merged_data.extend(b'\x5A' * 2)
+
+ with open(file_path, 'rb') as bin_file:
+ bin_data = bin_file.read()
+
+ merged_data.extend(bin_data)
+
+ total_files = len(file_info_list)
+
+ mmap_table = bytearray()
+ for file_name, offset, file_size, width, height in file_info_list:
+ if len(file_name) > int(max_name_len):
+ print(f'\033[1;33mWarn:\033[0m "{file_name}" exceeds {max_name_len} bytes and will be truncated.')
+ fixed_name = file_name.ljust(int(max_name_len), '\0')[:int(max_name_len)]
+ mmap_table.extend(fixed_name.encode('utf-8'))
+ mmap_table.extend(file_size.to_bytes(4, byteorder='little'))
+ mmap_table.extend(offset.to_bytes(4, byteorder='little'))
+ mmap_table.extend(width.to_bytes(2, byteorder='little'))
+ mmap_table.extend(height.to_bytes(2, byteorder='little'))
+
+ combined_data = mmap_table + merged_data
+ combined_checksum = compute_checksum(combined_data)
+ combined_data_length = len(combined_data).to_bytes(4, byteorder='little')
+ header_data = total_files.to_bytes(4, byteorder='little') + combined_checksum.to_bytes(4, byteorder='little')
+ final_data = header_data + combined_data_length + combined_data
+
+ with open(out_file, 'wb') as output_bin:
+ output_bin.write(final_data)
+
+ os.makedirs(assets_include_path, exist_ok=True)
+ current_year = datetime.now().year
+
+ asset_name = os.path.basename(assets_path)
+ file_path = os.path.join(assets_include_path, f'mmap_generate_{asset_name}.h')
+ with open(file_path, 'w') as output_header:
+ output_header.write('/*\n')
+ output_header.write(' * SPDX-FileCopyrightText: 2022-{} Espressif Systems (Shanghai) CO LTD\n'.format(current_year))
+ output_header.write(' *\n')
+ output_header.write(' * SPDX-License-Identifier: Apache-2.0\n')
+ output_header.write(' */\n\n')
+ output_header.write('/**\n')
+ output_header.write(' * @file\n')
+ output_header.write(" * @brief This file was generated by esp_mmap_assets, don't modify it\n")
+ output_header.write(' */\n\n')
+ output_header.write('#pragma once\n\n')
+ output_header.write("#include \"esp_mmap_assets.h\"\n\n")
+ output_header.write(f'#define MMAP_{asset_name.upper()}_FILES {total_files}\n')
+ output_header.write(f'#define MMAP_{asset_name.upper()}_CHECKSUM 0x{combined_checksum:04X}\n\n')
+ output_header.write(f'enum MMAP_{asset_name.upper()}_LISTS {{\n')
+
+ for i, (file_name, _, _, _, _) in enumerate(file_info_list):
+ enum_name = file_name.replace('.', '_')
+ output_header.write(f' MMAP_{asset_name.upper()}_{enum_name.upper()} = {i}, /*!< {file_name} */\n')
+
+ output_header.write('};\n')
+
+ print(f'All bin files have been merged into {os.path.basename(out_file)}')
+
+def copy_assets(config: AssetCopyConfig):
+ """
+ Copy assets to target_path based on the provided configuration.
+ """
+ format_tuple = tuple(config.support_format)
+ assets_path = config.assets_path
+ target_path = config.target_path
+
+ for filename in os.listdir(assets_path):
+ if any(filename.endswith(suffix) for suffix in format_tuple):
+ source_file = os.path.join(assets_path, filename)
+ target_file = os.path.join(target_path, filename)
+ shutil.copyfile(source_file, target_file)
+
+ conversion_map = {
+ '.jpg': [
+ (config.sjpg_enable, convert_image_to_simg),
+ (config.qoi_enable, convert_image_to_qoi),
+ ],
+ '.png': [
+ (config.spng_enable, convert_image_to_simg),
+ (config.qoi_enable, convert_image_to_qoi),
+ ],
+ }
+
+ file_ext = os.path.splitext(filename)[1].lower()
+ conversions = conversion_map.get(file_ext, [])
+ converted = False
+
+ for enable_flag, convert_func in conversions:
+ if enable_flag:
+ convert_func(target_file, config.split_height)
+ os.remove(target_file)
+ converted = True
+ break
+
+ if not converted and config.row_enable:
+ convert_image_to_raw(target_file)
+ os.remove(target_file)
+ else:
+ print(f'No match found for file: {filename}, format_tuple: {format_tuple}')
+
+def process_assets_build(config_data):
+ assets_path = config_data['assets_path']
+ image_file = config_data['image_file']
+ target_path = os.path.dirname(image_file)
+ include_path = config_data['include_path']
+ name_length = config_data['name_length']
+ split_height = config_data['split_height']
+ support_format = [fmt.strip() for fmt in config_data['support_format'].split(',')]
+
+ copy_config = AssetCopyConfig(
+ assets_path=assets_path,
+ target_path=target_path,
+ spng_enable=config_data['support_spng'],
+ sjpg_enable=config_data['support_sjpg'],
+ qoi_enable=config_data['support_qoi'],
+ sqoi_enable=config_data['support_sqoi'],
+ row_enable=config_data['support_raw'],
+ support_format=support_format,
+ split_height=split_height
+ )
+
+ pack_config = PackModelsConfig(
+ target_path=target_path,
+ include_path=include_path,
+ image_file=image_file,
+ assets_path=assets_path,
+ name_length=name_length
+ )
+
+ print('--support_format:', support_format)
+
+ if '.jpg' in support_format or '.png' in support_format:
+ print('--support_spng:', copy_config.spng_enable)
+ print('--support_sjpg:', copy_config.sjpg_enable)
+ print('--support_qoi:', copy_config.qoi_enable)
+ print('--support_raw:', copy_config.row_enable)
+
+ if copy_config.sqoi_enable:
+ print('--support_sqoi:', copy_config.sqoi_enable)
+ if copy_config.spng_enable or copy_config.sjpg_enable or copy_config.sqoi_enable:
+ print('--split_height:', copy_config.split_height)
+ if copy_config.row_enable:
+ print('--lvgl_version:', config_data['lvgl_ver'])
+
+ if not os.path.exists(target_path):
+ os.makedirs(target_path, exist_ok=True)
+ for filename in os.listdir(target_path):
+ file_path = os.path.join(target_path, filename)
+ if os.path.isfile(file_path) or os.path.islink(file_path):
+ os.unlink(file_path)
+ elif os.path.isdir(file_path):
+ shutil.rmtree(file_path)
+
+ copy_assets(copy_config)
+ pack_assets(pack_config)
+
+ total_size = os.path.getsize(os.path.join(target_path, image_file))
+ recommended_size = math.ceil(total_size / 1024)
+ partition_size = math.ceil(int(config_data['assets_size'], 16))
+
+ print(f'{"Total size:":<30} {GREEN}{total_size / 1024:>8.2f}K ({total_size}){RESET}')
+ print(f'{"Partition size:":<30} {GREEN}{partition_size / 1024:>8.2f}K ({partition_size}){RESET}')
+
+ if int(config_data['assets_size'], 16) <= total_size:
+ print(f'Recommended partition size: {GREEN}{recommended_size}K{RESET}')
+ print(f'{RED}Error:Binary size exceeds partition size.{RESET}')
+ sys.exit(1)
+
+def process_assets_merge(config_data):
+ app_bin_path = config_data['app_bin_path']
+ image_file = config_data['image_file']
+ target_path = os.path.dirname(image_file)
+
+ combined_bin_path = os.path.join(target_path, 'combined.bin')
+ append_bin_path = os.path.join(target_path, image_file)
+
+ app_size = os.path.getsize(app_bin_path)
+ asset_size = os.path.getsize(append_bin_path)
+ total_size = asset_size + app_size
+ recommended_size = math.ceil(total_size / 1024)
+ partition_size = math.ceil(int(config_data['assets_size'], 16))
+
+ print(f'{"Asset size:":<30} {GREEN}{asset_size / 1024:>8.2f}K ({asset_size}){RESET}')
+ print(f'{"App size:":<30} {GREEN}{app_size / 1024:>8.2f}K ({app_size}){RESET}')
+ print(f'{"Total size:":<30} {GREEN}{total_size / 1024:>8.2f}K ({total_size}){RESET}')
+ print(f'{"Partition size:":<30} {GREEN}{partition_size / 1024:>8.2f}K ({partition_size}){RESET}')
+
+ if total_size > partition_size:
+ print(f'Recommended partition size: {GREEN}{recommended_size}K{RESET}')
+ print(f'{RED}Error:Binary size exceeds partition size.{RESET}')
+ sys.exit(1)
+
+ with open(combined_bin_path, 'wb') as combined_bin:
+ with open(app_bin_path, 'rb') as app_bin:
+ combined_bin.write(app_bin.read())
+ with open(append_bin_path, 'rb') as img_bin:
+ combined_bin.write(img_bin.read())
+
+ shutil.move(combined_bin_path, app_bin_path)
+ print(f'Append bin created: {os.path.basename(app_bin_path)}')
+
+if __name__ == '__main__':
+ parser = argparse.ArgumentParser(description='Move and Pack assets.')
+ parser.add_argument('--config', required=True, help='Path to the configuration file')
+ parser.add_argument('--merge', action='store_true', help='Merge assets with app binary')
+ args = parser.parse_args()
+
+ with open(args.config, 'r') as f:
+ config_data = json.load(f)
+
+ if args.merge:
+ process_assets_merge(config_data)
+ else:
+ process_assets_build(config_data)
diff --git a/xiaozhi-esp32-2.2.6/scripts/versions.py b/xiaozhi-esp32-2.2.6/scripts/versions.py
new file mode 100644
index 0000000..a5a115c
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/scripts/versions.py
@@ -0,0 +1,250 @@
+#! /usr/bin/env python3
+from dotenv import load_dotenv
+load_dotenv()
+
+import os
+import struct
+import zipfile
+import oss2
+import json
+import requests
+from requests.exceptions import RequestException
+
+# 鍒囨崲鍒伴」鐩牴鐩綍
+os.chdir(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+def get_chip_id_string(chip_id):
+ return {
+ 0x0000: "esp32",
+ 0x0002: "esp32s2",
+ 0x0005: "esp32c3",
+ 0x0009: "esp32s3",
+ 0x000C: "esp32c2",
+ 0x000D: "esp32c6",
+ 0x0010: "esp32h2",
+ 0x0011: "esp32c5",
+ 0x0012: "esp32p4",
+ 0x0017: "esp32c5",
+ }[chip_id]
+
+def get_flash_size(flash_size):
+ MB = 1024 * 1024
+ return {
+ 0x00: 1 * MB,
+ 0x01: 2 * MB,
+ 0x02: 4 * MB,
+ 0x03: 8 * MB,
+ 0x04: 16 * MB,
+ 0x05: 32 * MB,
+ 0x06: 64 * MB,
+ 0x07: 128 * MB,
+ }[flash_size]
+
+def get_app_desc(data):
+ magic = struct.unpack("<I", data[0x00:0x04])[0]
+ if magic != 0xabcd5432:
+ raise Exception("Invalid app desc magic")
+ version = data[0x10:0x30].decode("utf-8").strip('\0')
+ project_name = data[0x30:0x50].decode("utf-8").strip('\0')
+ time = data[0x50:0x60].decode("utf-8").strip('\0')
+ date = data[0x60:0x70].decode("utf-8").strip('\0')
+ idf_ver = data[0x70:0x90].decode("utf-8").strip('\0')
+ elf_sha256 = data[0x90:0xb0].hex()
+ return {
+ "name": project_name,
+ "version": version,
+ "compile_time": date + "T" + time,
+ "idf_version": idf_ver,
+ "elf_sha256": elf_sha256,
+ }
+
+def get_board_name(folder):
+ basename = os.path.basename(folder)
+ if basename.startswith("v0.2"):
+ return "bread-simple"
+ if basename.startswith("v0.3") or basename.startswith("v0.4") or basename.startswith("v0.5") or basename.startswith("v0.6"):
+ if "ML307" in basename:
+ return "bread-compact-ml307"
+ elif "WiFi" in basename:
+ return "bread-compact-wifi"
+ elif "KevinBox1" in basename:
+ return "kevin-box-1"
+ if basename.startswith("v0.7") or basename.startswith("v0.8") or basename.startswith("v0.9") or basename.startswith("v1.") or basename.startswith("v2."):
+ return basename.split("_")[1]
+ raise Exception(f"Unknown board name: {basename}")
+
+def find_app_partition(data):
+ partition_begin = 0x8000
+ partition_end = partition_begin + 0x4000
+ # find the first parition with type 0x00
+ for i in range(partition_begin, partition_end, 0x20):
+ # magic is aa 50
+ if data[i] == 0xaa and data[i + 1] == 0x50:
+ # type is app
+ if data[i + 2] == 0x00:
+ # read offset and size
+ offset = struct.unpack("<I", data[i + 4:i + 8])[0]
+ size = struct.unpack("<I", data[i + 8:i + 12])[0]
+ # then 16 bytes is label
+ label = data[i + 12:i + 28].decode("utf-8").strip('\0')
+ print(f"found app partition at 0x{i:08x}, offset: 0x{offset:08x}, size: 0x{size:08x}, label: {label}")
+ return {
+ "offset": offset,
+ "size": size,
+ "label": label,
+ }
+ return None
+
+def read_binary(dir_path):
+ merged_bin_path = os.path.join(dir_path, "merged-binary.bin")
+ merged_bin_data = open(merged_bin_path, "rb").read()
+
+ # find app partition
+ app_partition = find_app_partition(merged_bin_data)
+ if app_partition is None:
+ print("no app partition found")
+ return
+ app_data = merged_bin_data[app_partition["offset"]:app_partition["offset"] + app_partition["size"]]
+ # check magic
+ if app_data[0] != 0xE9:
+ print("not a valid image")
+ return
+ # get flash size
+ flash_size = get_flash_size(app_data[0x3] >> 4)
+ chip_id = get_chip_id_string(app_data[0xC])
+ # get segments
+ segment_count = app_data[0x1]
+ segments = []
+ offset = 0x18
+ image_size = 0x18
+ for i in range(segment_count):
+ segment_size = struct.unpack("<I", app_data[offset + 4:offset + 8])[0]
+ image_size += 8 + segment_size
+ offset += 8
+ segment_data = app_data[offset:offset + segment_size]
+ offset += segment_size
+ segments.append(segment_data)
+ assert offset < len(app_data), "offset is out of bounds"
+
+ # add checksum size
+ image_size += 1
+ image_size = (image_size + 15) & ~15
+ # hash appended
+ if app_data[0x17] == 1:
+ image_size += 32
+ print(f"image size: {image_size}")
+
+ # verify the remaining data are all 0xFF
+ for i in range(image_size, len(app_data)):
+ if app_data[i] != 0xFF:
+ print(f"Failed to verify image, data at 0x{i:08x} is not 0xFF")
+ return
+
+ image_data = app_data[:image_size]
+
+ # extract bin file
+ bin_path = os.path.join(dir_path, "xiaozhi.bin")
+ if not os.path.exists(bin_path):
+ print("extract bin file to", bin_path)
+ open(bin_path, "wb").write(image_data)
+
+ # The app desc is in the first segment
+ desc = get_app_desc(segments[0])
+ return {
+ "chip_id": chip_id,
+ "flash_size": flash_size,
+ "board": get_board_name(dir_path),
+ "application": desc,
+ "firmware_size": image_size,
+ }
+
+def extract_zip(zip_path, extract_path):
+ if not os.path.exists(extract_path):
+ os.makedirs(extract_path)
+ print(f"Extracting {zip_path} to {extract_path}")
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
+ zip_ref.extractall(extract_path)
+
+def upload_dir_to_oss(source_dir, target_dir):
+ auth = oss2.Auth(os.environ['OSS_ACCESS_KEY_ID'], os.environ['OSS_ACCESS_KEY_SECRET'])
+ bucket = oss2.Bucket(auth, os.environ['OSS_ENDPOINT'], os.environ['OSS_BUCKET_NAME'])
+ for filename in os.listdir(source_dir):
+ oss_key = os.path.join(target_dir, filename)
+ # check if is file
+ if not os.path.isfile(os.path.join(source_dir, filename)):
+ continue
+ print('uploading', oss_key)
+ bucket.put_object(oss_key, open(os.path.join(source_dir, filename), 'rb'))
+
+def post_info_to_server(info):
+ """
+ 灏嗗浐浠朵俊鎭彂閫佸埌鏈嶅姟鍣�
+
+ Args:
+ info: 鍖呭惈鍥轰欢淇℃伅鐨勫瓧鍏�
+ """
+ try:
+ # 浠庣幆澧冨彉閲忚幏鍙栨湇鍔″櫒URL鍜宼oken
+ server_url = os.environ.get('VERSIONS_SERVER_URL')
+ server_token = os.environ.get('VERSIONS_TOKEN')
+
+ if not server_url or not server_token:
+ raise Exception("Missing SERVER_URL or TOKEN in environment variables")
+
+ # 鍑嗗璇锋眰澶村拰鏁版嵁
+ headers = {
+ 'Authorization': f'Bearer {server_token}',
+ 'Content-Type': 'application/json'
+ }
+
+ # 鍙戦�丳OST璇锋眰
+ response = requests.post(
+ server_url,
+ headers=headers,
+ json={'jsonData': json.dumps(info)}
+ )
+
+ # 妫�鏌ュ搷搴旂姸鎬�
+ response.raise_for_status()
+
+ print(f"Successfully uploaded version info for tag: {info['tag']}")
+
+ except RequestException as e:
+ if hasattr(e.response, 'json'):
+ error_msg = e.response.json().get('error', str(e))
+ else:
+ error_msg = str(e)
+ print(f"Failed to upload version info: {error_msg}")
+ raise
+ except Exception as e:
+ print(f"Error uploading version info: {str(e)}")
+ raise
+
+def main():
+ release_dir = "releases"
+ # look for zip files startswith "v"
+ for name in os.listdir(release_dir):
+ if name.startswith("v") and name.endswith(".zip"):
+ tag = name[:-4]
+ folder = os.path.join(release_dir, tag)
+ info_path = os.path.join(folder, "info.json")
+ if not os.path.exists(info_path):
+ if not os.path.exists(folder):
+ os.makedirs(folder)
+ extract_zip(os.path.join(release_dir, name), folder)
+ info = read_binary(folder)
+ target_dir = os.path.join("firmwares", tag)
+ info["tag"] = tag
+ info["url"] = os.path.join(os.environ['OSS_BUCKET_URL'], target_dir, "xiaozhi.bin")
+ open(info_path, "w", encoding="utf-8").write(json.dumps(info, indent=4))
+ # upload all file to oss
+ upload_dir_to_oss(folder, target_dir)
+ # read info.json
+ info = json.load(open(info_path, encoding="utf-8"))
+ # post info.json to server
+ post_info_to_server(info)
+
+
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults b/xiaozhi-esp32-2.2.6/sdkconfig.defaults
new file mode 100644
index 0000000..ae6b193
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults
@@ -0,0 +1,78 @@
+CONFIG_COMPILER_OPTIMIZATION_SIZE=y
+CONFIG_COMPILER_CXX_EXCEPTIONS=y
+CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024
+CONFIG_COMPILER_CXX_RTTI=y
+
+CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y
+CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y
+CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y
+CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y
+
+CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048
+CONFIG_HTTPD_MAX_URI_LEN=2048
+
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m.csv"
+CONFIG_PARTITION_TABLE_OFFSET=0x8000
+
+CONFIG_ESP_TASK_WDT_TIMEOUT_S=10
+CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
+CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y
+
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=8192
+CONFIG_MBEDTLS_DYNAMIC_BUFFER=y
+CONFIG_MBEDTLS_SSL_KEEP_PEER_CERTIFICATE=n
+CONFIG_ESP_WIFI_IRAM_OPT=n
+CONFIG_ESP_WIFI_RX_IRAM_OPT=n
+CONFIG_ESP_WIFI_DYNAMIC_RX_MGMT_BUFFER=y
+
+# These entries are copied from ESP-HI (ESP32C3) to reduce memory usage
+CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=6
+CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=8
+CONFIG_NEWLIB_NANO_FORMAT=y
+CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n
+
+CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n
+
+# Fix ML307 FIFO Overflow
+CONFIG_UART_ISR_IN_IRAM=y
+
+# Fix ESP_SSL error
+CONFIG_MBEDTLS_SSL_RENEGOTIATION=n
+
+# LVGL 9.2.2
+
+CONFIG_LV_OS_NONE=y
+CONFIG_LV_USE_CLIB_MALLOC=y
+CONFIG_LV_USE_CLIB_STRING=y
+CONFIG_LV_USE_CLIB_SPRINTF=y
+CONFIG_LV_USE_IMGFONT=y
+CONFIG_LV_USE_ASSERT_STYLE=y
+
+# Use compressed font
+CONFIG_LV_FONT_FMT_TXT_LARGE=y
+CONFIG_LV_USE_FONT_COMPRESSED=n
+CONFIG_LV_USE_FONT_PLACEHOLDER=n
+CONFIG_LV_USE_LODEPNG=y
+
+# Disable extra widgets to save flash size
+CONFIG_LV_USE_ANIMIMG=n
+CONFIG_LV_USE_CALENDAR=n
+CONFIG_LV_USE_CALENDAR_HEADER_ARROW=n
+CONFIG_LV_USE_CALENDAR_HEADER_DROPDOWN=n
+CONFIG_LV_USE_CHART=n
+CONFIG_LV_USE_KEYBOARD=n
+CONFIG_LV_USE_LED=n
+CONFIG_LV_USE_LIST=n
+CONFIG_LV_USE_MENU=n
+CONFIG_LV_USE_MSGBOX=n
+CONFIG_LV_USE_SPAN=n
+CONFIG_LV_USE_SPINBOX=n
+CONFIG_LV_USE_SPINNER=n
+CONFIG_LV_USE_TABVIEW=n
+CONFIG_LV_USE_TILEVIEW=n
+CONFIG_LV_USE_WIN=n
+
+CONFIG_LV_BUILD_EXAMPLES=n
+CONFIG_LV_BUILD_DEMOS=n
+
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32 b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32
new file mode 100644
index 0000000..1e363f2
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32
@@ -0,0 +1,6 @@
+
+CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
+CONFIG_PARTITION_TABLE_CUSTOM=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/4m.csv"
+CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
+CONFIG_ESP_TASK_WDT_TIMEOUT_S=20
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c3 b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c3
new file mode 100644
index 0000000..be746bf
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c3
@@ -0,0 +1,15 @@
+
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m_c3.csv"
+CONFIG_SR_WN_WN9S_NIHAOXIAOZHI=y
+
+CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3
+CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6
+CONFIG_ESP_WIFI_RX_BA_WIN=3
+CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16
+CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
+
+CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n
+CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0
+CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768
+CONFIG_LWIP_IPV6=n
\ No newline at end of file
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c5 b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c5
new file mode 100644
index 0000000..d3322df
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c5
@@ -0,0 +1,14 @@
+
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
+
+CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
+
+CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3
+CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6
+CONFIG_ESP_WIFI_RX_BA_WIN=3
+CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16
+CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
+
+CONFIG_SR_WN_WN9S_NIHAOXIAOZHI=y
+CONFIG_USE_ESP_WAKE_WORD=y
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c6 b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c6
new file mode 100644
index 0000000..59ff635
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32c6
@@ -0,0 +1,5 @@
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v2/16m_c3.csv"
+CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
+
+CONFIG_SR_WN_WN9S_NIHAOXIAOZHI=y
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32p4 b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32p4
new file mode 100644
index 0000000..43a36de
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32p4
@@ -0,0 +1,31 @@
+CONFIG_IDF_TARGET="esp32p4"
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
+
+CONFIG_SPIRAM=y
+CONFIG_SPIRAM_SPEED_200M=y
+CONFIG_SPIRAM_XIP_FROM_PSRAM=y
+CONFIG_ESP_MAIN_TASK_STACK_SIZE=10240
+CONFIG_FREERTOS_HZ=1000
+CONFIG_FREERTOS_VTASKLIST_INCLUDE_COREID=y
+CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y
+
+CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=65536
+CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y
+CONFIG_SPIRAM_MEMTEST=n
+CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
+
+CONFIG_SLAVE_IDF_TARGET_ESP32C6=y
+
+CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
+
+CONFIG_IDF_EXPERIMENTAL_FEATURES=y
+
+CONFIG_COMPILER_OPTIMIZATION_PERF=y
+
+CONFIG_ESP_VIDEO_ENABLE_ISP_PIPELINE_CONTROLLER=y
+CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=1024
+
+# LVGL Graphics
+CONFIG_LV_USE_SNAPSHOT=y
diff --git a/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32s3 b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32s3
new file mode 100644
index 0000000..0ac4880
--- /dev/null
+++ b/xiaozhi-esp32-2.2.6/sdkconfig.defaults.esp32s3
@@ -0,0 +1,31 @@
+
+CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
+CONFIG_ESPTOOLPY_FLASHMODE_QIO=y
+
+CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y
+
+CONFIG_SPIRAM=y
+CONFIG_SPIRAM_MODE_OCT=y
+CONFIG_SPIRAM_SPEED_80M=y
+CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=2048
+CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=98304
+CONFIG_SPIRAM_MEMTEST=n
+CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y
+
+CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3
+CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6
+CONFIG_ESP_WIFI_RX_BA_WIN=3
+CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16
+CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
+
+CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y
+CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y
+
+CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
+
+CONFIG_ESP_SYSTEM_EVENT_TASK_STACK_SIZE=4096
+
+CONFIG_USB_HOST_CONTROL_TRANSFER_MAX_SIZE=1536
+
+# LVGL Graphics
+CONFIG_LV_USE_SNAPSHOT=y
--
Gitblit v1.9.1