├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── README_en.md ├── README_ja.md ├── components └── tracked_chassis_control │ ├── CMakeLists.txt │ ├── include │ └── tracked_chassis_control.h │ └── tracked_chassis_control.c ├── convert_audio_to_p3.py ├── docs ├── atoms3r-echo-base.jpg ├── esp-sparkbot.jpg ├── esp32s3-box3.jpg ├── lichuang-s3.jpg ├── m5stack-cores3.jpg ├── magiclick-2p4.jpg ├── waveshare-esp32-s3-touch-amoled-1.8.jpg ├── wiring.jpg ├── wiring2.jpg └── xmini-c3.jpg ├── flash.sh ├── main ├── CMakeLists.txt ├── Kconfig.projbuild ├── application.cc ├── application.h ├── assets │ ├── err_pin.p3 │ ├── err_reg.p3 │ ├── upgrade.p3 │ └── wificonfig.p3 ├── audio_codecs │ ├── audio_codec.cc │ ├── audio_codec.h │ ├── box_audio_codec.cc │ ├── box_audio_codec.h │ ├── cores3_audio_codec.cc │ ├── cores3_audio_codec.h │ ├── es8311_audio_codec.cc │ ├── es8311_audio_codec.h │ ├── no_audio_codec.cc │ └── no_audio_codec.h ├── audio_processing │ ├── audio_processor.cc │ ├── audio_processor.h │ ├── wake_word_detect.cc │ └── wake_word_detect.h ├── background_task.cc ├── background_task.h ├── boards │ ├── atoms3r-echo-base │ │ ├── README.md │ │ ├── atoms3r_echo_base.cc │ │ └── config.h │ ├── bread-compact-ml307 │ │ ├── compact_ml307_board.cc │ │ └── config.h │ ├── bread-compact-wifi │ │ ├── compact_wifi_board.cc │ │ └── config.h │ ├── common │ │ ├── board.cc │ │ ├── board.h │ │ ├── button.cc │ │ ├── button.h │ │ ├── i2c_device.cc │ │ ├── i2c_device.h │ │ ├── ml307_board.cc │ │ ├── ml307_board.h │ │ ├── system_reset.cc │ │ ├── system_reset.h │ │ ├── wifi_board.cc │ │ └── wifi_board.h │ ├── esp-box-3 │ │ ├── config.h │ │ └── esp_box3_board.cc │ ├── esp-sparkbot │ │ ├── config.h │ │ └── esp_sparkbot_board.cc │ ├── esp32-s3-touch-amoled-1.8 │ │ ├── config.h │ │ └── esp32-s3-touch-amoled-1.8.cc │ ├── esp32s3-korvo2-v3 │ │ ├── config.h │ │ └── esp32s3_korvo2_v3_board.cc │ ├── kevin-box-1 │ │ ├── config.h │ │ └── kevin_box_board.cc │ ├── kevin-box-2 │ │ ├── axp2101.cc │ │ ├── axp2101.h │ │ ├── config.h │ │ └── kevin_box_board.cc │ ├── kevin-c3 │ │ ├── config.h │ │ └── kevin_c3_board.cc │ ├── lichuang-c3-dev │ │ ├── README.md │ │ ├── config.h │ │ └── lichuang_c3_dev_board.cc │ ├── lichuang-dev │ │ ├── config.h │ │ └── lichuang_dev_board.cc │ ├── m5stack-core-s3 │ │ ├── README.md │ │ ├── config.h │ │ └── m5stack_core_s3.cc │ ├── magiclick-2p4 │ │ ├── config.h │ │ └── magiclick_2p4_board.cc │ └── xmini-c3 │ │ ├── config.h │ │ └── xmini_c3_board.cc ├── display │ ├── display.cc │ ├── display.h │ ├── lcd_display.cc │ ├── lcd_display.h │ ├── no_display.cc │ ├── no_display.h │ ├── ssd1306_display.cc │ └── ssd1306_display.h ├── fonts │ ├── GB2312.TXT │ ├── font_awesome_14_1.c │ ├── font_awesome_30_1.c │ ├── font_awesome_symbols.h │ └── font_puhui_14_1.c ├── idf_component.yml ├── iot │ ├── sample_interface.json │ ├── thing.cc │ ├── thing.h │ ├── thing_manager.cc │ ├── thing_manager.h │ └── things │ │ ├── lamp.cc │ │ ├── speaker.cc │ │ └── tank.cc ├── led │ ├── circular_strip.cc │ ├── circular_strip.h │ ├── led.cc │ ├── led.h │ ├── single_led.cc │ └── single_led.h ├── main.cc ├── ota.cc ├── ota.h ├── protocols │ ├── mqtt_protocol.cc │ ├── mqtt_protocol.h │ ├── protocol.cc │ ├── protocol.h │ ├── websocket_protocol.cc │ └── websocket_protocol.h ├── settings.cc ├── settings.h ├── system_info.cc └── system_info.h ├── partitions.csv ├── partitions_4M.csv ├── partitions_8M.csv ├── release.py ├── sdkconfig.defaults ├── sdkconfig.defaults.esp32c3 ├── sdkconfig.defaults.esp32s3 └── versions.py /.gitignore: -------------------------------------------------------------------------------- 1 | tmp/ 2 | managed_components/ 3 | build/ 4 | .vscode/ 5 | .devcontainer/ 6 | sdkconfig.old 7 | sdkconfig 8 | dependencies.lock 9 | .env 10 | releases/ -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # For more information about build system see 2 | # https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html 3 | # The following five lines of boilerplate have to be in your project's 4 | # CMakeLists in this exact order for cmake to work correctly 5 | cmake_minimum_required(VERSION 3.16) 6 | 7 | set(PROJECT_VER "0.9.9") 8 | 9 | # Add this line to disable the specific warning 10 | add_compile_options(-Wno-missing-field-initializers) 11 | 12 | include($ENV{IDF_PATH}/tools/cmake/project.cmake) 13 | project(xiaozhi) 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Xiaoxia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 小智 AI 聊天机器人 (XiaoZhi AI Chatbot) 2 | 3 | (中文 | [English](README_en.md) | [日本語](README_ja.md)) 4 | 5 | 这是虾哥的第一个硬件作品。 6 | 7 | 👉 [ESP32+SenseVoice+Qwen72B打造你的AI聊天伴侣!【bilibili】](https://www.bilibili.com/video/BV11msTenEH3/?share_source=copy_web&vd_source=ee1aafe19d6e60cf22e60a93881faeba) 8 | 9 | 👉 [手工打造你的 AI 女友,新手入门教程【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/) 10 | 11 | ## 项目目的 12 | 13 | 本项目基于乐鑫的 ESP-IDF 进行开发。 14 | 15 | 本项目是一个开源项目,主要用于教学目的。我们希望通过这个项目,能够帮助更多人入门 AI 硬件开发,了解如何将当下飞速发展的大语言模型应用到实际的硬件设备中。无论你是对 AI 感兴趣的学生,还是想要探索新技术的开发者,都可以通过这个项目获得宝贵的学习经验。 16 | 17 | 欢迎所有人参与到项目的开发和改进中来。如果你有任何想法或建议,请随时提出 Issue 或加入群聊。 18 | 19 | 学习交流 QQ 群:946599635 20 | 21 | ## 已实现功能 22 | 23 | - Wi-Fi / ML307 Cat.1 4G 24 | - BOOT 键唤醒和打断,支持点击和长按两种触发方式 25 | - 离线语音唤醒 [ESP-SR](https://github.com/espressif/esp-sr) 26 | - 流式语音对话(WebSocket 或 UDP 协议) 27 | - 支持国语、粤语、英语、日语、韩语 5 种语言识别 [SenseVoice](https://github.com/FunAudioLLM/SenseVoice) 28 | - 声纹识别,识别是谁在喊 AI 的名字 [3D Speaker](https://github.com/modelscope/3D-Speaker) 29 | - 大模型 TTS(火山引擎 或 CosyVoice) 30 | - 大模型 LLM(Qwen2.5 72B 或 豆包 API) 31 | - 可配置的提示词和音色(自定义角色) 32 | - 短期记忆,每轮对话后自我总结 33 | - OLED / LCD 显示屏,显示信号强弱或对话内容 34 | 35 | ## 硬件部分 36 | 37 | ### 面包板手工制作实践 38 | 39 | 详见飞书文档教程: 40 | 41 | 👉 [《小智 AI 聊天机器人百科全书》](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) 42 | 43 | 面包板效果图如下: 44 | 45 | ![面包板效果图](docs/wiring2.jpg) 46 | 47 | ### 已支持的开源硬件 48 | 49 | - 立创·实战派 ESP32-S3 开发板 50 | - 乐鑫 ESP32-S3-BOX3 51 | - M5Stack CoreS3 52 | - AtomS3R + Echo Base 53 | - 神奇按钮 2.4 54 | - 虾哥 Mini C3 55 | - 微雪电子 ESP32-S3-Touch-AMOLED-1.8 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | ## 固件部分 79 | 80 | ### 免开发环境烧录 81 | 82 | 新手第一次操作建议先不要搭建开发环境,直接使用免开发环境烧录的固件。固件使用的是作者友情提供的测试服,目前开放免费使用,请勿用于商业用途。 83 | 84 | 👉 [Flash烧录固件(无IDF开发环境)](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) 85 | 86 | 87 | ### 开发环境 88 | 89 | - Cursor 或 VSCode 90 | - 安装 ESP-IDF 插件,选择 SDK 版本 5.3 或以上 91 | - Linux 比 Windows 更好,编译速度快,也免去驱动问题的困扰 92 | 93 | 94 | ## AI 角色配置 95 | 96 | 如果你已经拥有一个小智 AI 聊天机器人,可以参考 👉 [后台操作视频教程](https://www.bilibili.com/video/BV1jUCUY2EKM/) 97 | 98 | 详细的使用说明以及测试服的注意事项,请参考 👉 [小智测试服的帮助说明](https://xiaozhi.me/help)。 99 | 100 | ## Star History 101 | 102 | 103 | 104 | 105 | 106 | Star History Chart 107 | 108 | 109 | -------------------------------------------------------------------------------- /README_en.md: -------------------------------------------------------------------------------- 1 | # XiaoZhi AI Chatbot 2 | 3 | ([中文](README.md) | English | [日本語](README_ja.md)) 4 | 5 | This is Terrence's first hardware project. 6 | 7 | 👉 [Build your AI chat companion with ESP32+SenseVoice+Qwen72B! [bilibili]](https://www.bilibili.com/video/BV11msTenEH3/?share_source=copy_web&vd_source=ee1aafe19d6e60cf22e60a93881faeba) 8 | 9 | 👉 [DIY Your AI Companion - Beginner's Tutorial [bilibili]](https://www.bilibili.com/video/BV1XnmFYLEJN/) 10 | 11 | ## Project Purpose 12 | 13 | This project is developed based on Espressif's ESP-IDF. 14 | 15 | This is an open-source project primarily for educational purposes. Through this project, we aim to help more people get started with AI hardware development and understand how to integrate rapidly evolving large language models into actual hardware devices. Whether you're a student interested in AI or a developer looking to explore new technologies, this project offers valuable learning experiences. 16 | 17 | Everyone is welcome to participate in the project's development and improvement. If you have any ideas or suggestions, please feel free to raise an Issue or join our chat group. 18 | 19 | Learning & Discussion QQ Group: 946599635 20 | 21 | ## Implemented Features 22 | 23 | - Wi-Fi / ML307 Cat.1 4G 24 | - BOOT button wake-up and interrupt, supporting both click and long-press triggers 25 | - Offline voice wake-up [ESP-SR](https://github.com/espressif/esp-sr) 26 | - Streaming voice dialogue (WebSocket or UDP protocol) 27 | - Support for 5 languages: Mandarin, Cantonese, English, Japanese, Korean [SenseVoice](https://github.com/FunAudioLLM/SenseVoice) 28 | - Voice print recognition to identify who's calling AI's name [3D Speaker](https://github.com/modelscope/3D-Speaker) 29 | - Large model TTS (Volcengine or CosyVoice) 30 | - Large Language Model (Qwen2.5 72B or Doubao API) 31 | - Configurable prompts and voice tones (custom characters) 32 | - Short-term memory with self-summary after each conversation round 33 | - OLED / LCD display showing signal strength or conversation content 34 | 35 | ## Hardware Section 36 | 37 | ### Breadboard Practice 38 | 39 | For detailed tutorial, see the Feishu document: 40 | 41 | 👉 [XiaoZhi AI Chatbot Encyclopedia](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) 42 | 43 | Breadboard setup shown below: 44 | 45 | ![Breadboard Setup](docs/wiring2.jpg) 46 | 47 | ### Supported Open-Source Hardware 48 | 49 | - LiChuang ESP32-S3 Development Board 50 | - Espressif ESP32-S3-BOX3 51 | - M5Stack CoreS3 52 | - AtomS3R + Echo Base 53 | - MagiClick 2.4 54 | - Xmini C3 55 | - Waveshare ESP32-S3-Touch-AMOLED-1.8 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | ## Firmware Section 79 | 80 | ### Flashing Without Development Environment 81 | 82 | For beginners, it's recommended to first try flashing the firmware without setting up a development environment. The firmware uses a test server provided by the author, currently available for free use (not for commercial purposes). 83 | 84 | 👉 [Flash Firmware Guide (No IDF Environment Required)](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) 85 | 86 | ### Development Environment 87 | 88 | - Cursor or VSCode 89 | - Install ESP-IDF plugin, select SDK version 5.3 or above 90 | - Linux is preferred over Windows for faster compilation and fewer driver issues 91 | 92 | ## AI Character Configuration 93 | 94 | If you already have a XiaoZhi AI chatbot, please refer to 👉 [Backend Operation Video Tutorial](https://www.bilibili.com/video/BV1jUCUY2EKM/) 95 | 96 | For detailed usage instructions and test server notes, please refer to 👉 [XiaoZhi Test Server Help Guide](https://xiaozhi.me/help). 97 | 98 | ## Star History 99 | 100 | 101 | 102 | 103 | 104 | Star History Chart 105 | 106 | 107 | -------------------------------------------------------------------------------- /README_ja.md: -------------------------------------------------------------------------------- 1 | # XiaoZhi AI チャットボット 2 | 3 | ([中文](README.md) | [English](README_en.md) | 日本語) 4 | 5 | これはテレンスの最初のハードウェアプロジェクトです。 6 | 7 | 👉 [ESP32+SenseVoice+Qwen72BでAIチャットコンパニオンを作ろう!【bilibili】](https://www.bilibili.com/video/BV11msTenEH3/?share_source=copy_web&vd_source=ee1aafe19d6e60cf22e60a93881faeba) 8 | 9 | 👉 [AIコンパニオンをDIYする - 初心者向けチュートリアル【bilibili】](https://www.bilibili.com/video/BV1XnmFYLEJN/) 10 | 11 | ## プロジェクトの目的 12 | 13 | このプロジェクトはEspressifのESP-IDFに基づいて開発されています。 14 | 15 | このプロジェクトは主に教育目的のためのオープンソースプロジェクトです。このプロジェクトを通じて、より多くの人々がAIハードウェア開発を始め、急速に進化する大規模言語モデルを実際のハードウェアデバイスに統合する方法を理解する手助けをすることを目指しています。AIに興味のある学生や新しい技術を探求したい開発者にとって、このプロジェクトは貴重な学習体験を提供します。 16 | 17 | プロジェクトの開発と改善に参加することを歓迎します。アイデアや提案があれば、Issueを提起するか、チャットグループに参加してください。 18 | 19 | 学習・ディスカッションQQグループ: 946599635 20 | 21 | ## 実装された機能 22 | 23 | - Wi-Fi / ML307 Cat.1 4G 24 | - BOOTボタンのウェイクアップと割り込み、クリックと長押しの両方のトリガーをサポート 25 | - オフライン音声ウェイクアップ [ESP-SR](https://github.com/espressif/esp-sr) 26 | - ストリーミング音声対話(WebSocketまたはUDPプロトコル) 27 | - 5つの言語をサポート:標準中国語、広東語、英語、日本語、韓国語 [SenseVoice](https://github.com/FunAudioLLM/SenseVoice) 28 | - 音声認識でAIの名前を呼んでいる人を識別 [3D Speaker](https://github.com/modelscope/3D-Speaker) 29 | - 大規模モデルTTS(VolcengineまたはCosyVoice) 30 | - 大規模言語モデル(Qwen2.5 72BまたはDoubao API) 31 | - カスタマイズ可能なプロンプトと音声トーン(カスタムキャラクター) 32 | - 短期記憶、各対話ラウンド後の自己要約 33 | - 信号強度や対話内容を表示するOLED / LCDディスプレイ 34 | 35 | ## ハードウェアセクション 36 | 37 | ### ブレッドボードの練習 38 | 39 | 詳細なチュートリアルについては、Feishuドキュメントを参照してください: 40 | 41 | 👉 [XiaoZhi AI チャットボット百事典](https://ccnphfhqs21z.feishu.cn/wiki/F5krwD16viZoF0kKkvDcrZNYnhb?from=from_copylink) 42 | 43 | 以下にブレッドボードのセットアップを示します: 44 | 45 | ![ブレッドボードのセットアップ](docs/wiring2.jpg) 46 | 47 | ### サポートされているオープンソースハードウェア 48 | 49 | - LiChuang ESP32-S3 開発ボード 50 | - Espressif ESP32-S3-BOX3 51 | - M5Stack CoreS3 52 | - AtomS3R + Echo Base 53 | - MagiClick 2.4 54 | - Xmini C3 55 | - Waveshare ESP32-S3-Touch-AMOLED-1.8 56 | 57 |
58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 | 78 | ## ファームウェアセクション 79 | 80 | ### 開発環境なしでのフラッシュ 81 | 82 | 初心者には、最初に開発環境を設定せずにファームウェアをフラッシュすることをお勧めします。ファームウェアは著者が提供するテストサーバーを使用しており、現在無料で使用できます(商業目的では使用しないでください)。 83 | 84 | 👉 [開発環境なしでのフラッシュガイド](https://ccnphfhqs21z.feishu.cn/wiki/Zpz4wXBtdimBrLk25WdcXzxcnNS) 85 | 86 | ### 開発環境 87 | 88 | - CursorまたはVSCode 89 | - ESP-IDFプラグインをインストールし、SDKバージョン5.3以上を選択 90 | - LinuxはWindowsよりも優れており、コンパイルが速く、ドライバの問題も少ない 91 | 92 | ## AIキャラクターの設定 93 | 94 | すでにXiaoZhi AIチャットボットをお持ちの場合は、👉 [バックエンド操作ビデオチュートリアル](https://www.bilibili.com/video/BV1jUCUY2EKM/)を参照してください。 95 | 96 | 詳細な使用方法とテストサーバーの注意事項については、👉 [XiaoZhiテストサーバーヘルプガイド](https://xiaozhi.me/help)を参照してください。 97 | 98 | ## Star History 99 | 100 | 101 | 102 | 103 | 104 | Star History Chart 105 | 106 | 107 | -------------------------------------------------------------------------------- /components/tracked_chassis_control/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | idf_component_register(SRCS "tracked_chassis_control.c" 2 | INCLUDE_DIRS "include" 3 | PRIV_REQUIRES driver) 4 | -------------------------------------------------------------------------------- /components/tracked_chassis_control/include/tracked_chassis_control.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "driver/gpio.h" 4 | 5 | #ifdef __cplusplus 6 | extern "C" { 7 | #endif 8 | 9 | #define UART_ECHO_TXD (GPIO_NUM_38) 10 | #define UART_ECHO_RXD (GPIO_NUM_48) 11 | #define UART_ECHO_RTS (-1) 12 | #define UART_ECHO_CTS (-1) 13 | 14 | #define MOTOR_SPEED_MAX 100 15 | #define MOTOR_SPEED_80 80 16 | #define MOTOR_SPEED_60 60 17 | #define MOTOR_SPEED_MIN 0 18 | 19 | void tracked_chassis_control_start(void); 20 | void tracked_chassis_motion_control(const char* command_str); 21 | void tracked_chassis_rgb_light_control(uint8_t rgb_mode); 22 | void tracked_chassis_set_dance_mode(uint8_t dance_mode); 23 | 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | -------------------------------------------------------------------------------- /components/tracked_chassis_control/tracked_chassis_control.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include "freertos/FreeRTOS.h" 5 | #include "freertos/task.h" 6 | #include "driver/uart.h" 7 | #include "driver/gpio.h" 8 | #include "sdkconfig.h" 9 | #include "esp_log.h" 10 | #include "tracked_chassis_control.h" 11 | 12 | static const char *TAG = "tracked_chassis_control"; 13 | 14 | #define ECHO_UART_PORT_NUM (1) 15 | #define ECHO_UART_BAUD_RATE (115200) 16 | #define BUF_SIZE (1024) 17 | 18 | static void echo_uart_init(void) 19 | { 20 | /* Configure parameters of an UART driver, 21 | * communication pins and install the driver */ 22 | uart_config_t uart_config = { 23 | .baud_rate = ECHO_UART_BAUD_RATE, 24 | .data_bits = UART_DATA_8_BITS, 25 | .parity = UART_PARITY_DISABLE, 26 | .stop_bits = UART_STOP_BITS_1, 27 | .flow_ctrl = UART_HW_FLOWCTRL_DISABLE, 28 | .source_clk = UART_SCLK_DEFAULT, 29 | }; 30 | int intr_alloc_flags = 0; 31 | 32 | #if CONFIG_UART_ISR_IN_IRAM 33 | intr_alloc_flags = ESP_INTR_FLAG_IRAM; 34 | #endif 35 | 36 | ESP_ERROR_CHECK(uart_driver_install(ECHO_UART_PORT_NUM, BUF_SIZE * 2, 0, 0, NULL, intr_alloc_flags)); 37 | ESP_ERROR_CHECK(uart_param_config(ECHO_UART_PORT_NUM, &uart_config)); 38 | ESP_ERROR_CHECK(uart_set_pin(ECHO_UART_PORT_NUM, UART_ECHO_TXD, UART_ECHO_RXD, UART_ECHO_RTS, UART_ECHO_CTS)); 39 | } 40 | 41 | void tracked_chassis_control_start(void) 42 | { 43 | ESP_LOGI(TAG, "The tracked chassis can now be controlled."); 44 | echo_uart_init(); 45 | } 46 | 47 | void tracked_chassis_motion_control(const char* command_str) 48 | { 49 | uint8_t len = strlen(command_str); 50 | 51 | uart_write_bytes(ECHO_UART_PORT_NUM, command_str, len); 52 | 53 | ESP_LOGI(TAG, "Sent command: %s", command_str); 54 | } 55 | 56 | 57 | void tracked_chassis_rgb_light_control(uint8_t rgb_mode) 58 | { 59 | char command = 'w'; 60 | 61 | ESP_LOGI(TAG, "command: %c, rgb mode: %d", command, rgb_mode); 62 | char data[10] = {0}; 63 | sprintf(data, "%c%d", command, rgb_mode); 64 | uint8_t len = strlen(data); 65 | uart_write_bytes(ECHO_UART_PORT_NUM, data, len); 66 | } 67 | 68 | void tracked_chassis_set_dance_mode(uint8_t dance_mode) 69 | { 70 | char command = 'd'; 71 | 72 | ESP_LOGI(TAG, "command: %c, dance mode: %d", command, dance_mode); 73 | char data[10] = {0}; 74 | sprintf(data, "%c%d", command, dance_mode); 75 | uint8_t len = strlen(data); 76 | uart_write_bytes(ECHO_UART_PORT_NUM, data, len); 77 | } 78 | 79 | -------------------------------------------------------------------------------- /convert_audio_to_p3.py: -------------------------------------------------------------------------------- 1 | # convert audio files to protocol v3 stream 2 | import librosa 3 | import opuslib 4 | import struct 5 | import sys 6 | import tqdm 7 | import numpy as np 8 | 9 | def encode_audio_to_opus(input_file, output_file): 10 | # Load audio file using librosa 11 | audio, sample_rate = librosa.load(input_file, sr=None, mono=False, dtype=np.int16) 12 | 13 | # Get left channel if stereo 14 | if audio.ndim == 2: 15 | audio = audio[0] 16 | 17 | # Initialize Opus encoder 18 | encoder = opuslib.Encoder(sample_rate, 1, opuslib.APPLICATION_VOIP) 19 | 20 | # Encode audio data to Opus packets 21 | # Save encoded data to file 22 | with open(output_file, 'wb') as f: 23 | sample_rate = 16000 # 16000Hz 24 | duration = 60 # 60ms every frame 25 | frame_size = int(sample_rate * duration / 1000) 26 | for i in tqdm.tqdm(range(0, len(audio) - frame_size, frame_size)): 27 | frame = audio[i:i + frame_size] 28 | opus_data = encoder.encode(frame.tobytes(), frame_size=frame_size) 29 | # protocol format, [1u type, 1u reserved, 2u len, data] 30 | packet = struct.pack('>BBH', 0, 0, len(opus_data)) + opus_data 31 | f.write(packet) 32 | 33 | # Example usage 34 | if len(sys.argv) != 3: 35 | print('Usage: python convert.py ') 36 | sys.exit(1) 37 | 38 | input_file = sys.argv[1] 39 | output_file = sys.argv[2] 40 | encode_audio_to_opus(input_file, output_file) 41 | -------------------------------------------------------------------------------- /docs/atoms3r-echo-base.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/atoms3r-echo-base.jpg -------------------------------------------------------------------------------- /docs/esp-sparkbot.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/esp-sparkbot.jpg -------------------------------------------------------------------------------- /docs/esp32s3-box3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/esp32s3-box3.jpg -------------------------------------------------------------------------------- /docs/lichuang-s3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/lichuang-s3.jpg -------------------------------------------------------------------------------- /docs/m5stack-cores3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/m5stack-cores3.jpg -------------------------------------------------------------------------------- /docs/magiclick-2p4.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/magiclick-2p4.jpg -------------------------------------------------------------------------------- /docs/waveshare-esp32-s3-touch-amoled-1.8.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/waveshare-esp32-s3-touch-amoled-1.8.jpg -------------------------------------------------------------------------------- /docs/wiring.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/wiring.jpg -------------------------------------------------------------------------------- /docs/wiring2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/wiring2.jpg -------------------------------------------------------------------------------- /docs/xmini-c3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/docs/xmini-c3.jpg -------------------------------------------------------------------------------- /flash.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | esptool.py -p /dev/ttyACM0 -b 2000000 write_flash 0 releases/v0.9.9_bread-compact-wifi/merged-binary.bin 3 | -------------------------------------------------------------------------------- /main/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCES "audio_codecs/audio_codec.cc" 2 | "audio_codecs/no_audio_codec.cc" 3 | "audio_codecs/box_audio_codec.cc" 4 | "audio_codecs/es8311_audio_codec.cc" 5 | "audio_codecs/cores3_audio_codec.cc" 6 | "led/single_led.cc" 7 | "led/circular_strip.cc" 8 | "display/display.cc" 9 | "display/no_display.cc" 10 | "display/lcd_display.cc" 11 | "display/ssd1306_display.cc" 12 | "protocols/protocol.cc" 13 | "protocols/mqtt_protocol.cc" 14 | "protocols/websocket_protocol.cc" 15 | "iot/thing.cc" 16 | "iot/thing_manager.cc" 17 | "system_info.cc" 18 | "application.cc" 19 | "ota.cc" 20 | "settings.cc" 21 | "background_task.cc" 22 | "main.cc" 23 | ) 24 | 25 | set(INCLUDE_DIRS "." "display" "audio_codecs" "protocols" "audio_processing") 26 | 27 | # 添加 IOT 相关文件 28 | file(GLOB IOT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/iot/things/*.cc) 29 | list(APPEND SOURCES ${IOT_SOURCES}) 30 | 31 | # 字体 32 | file(GLOB FONT_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/fonts/*.c) 33 | list(APPEND SOURCES ${FONT_SOURCES}) 34 | list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/fonts) 35 | 36 | # 添加板级公共文件 37 | file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc) 38 | list(APPEND SOURCES ${BOARD_COMMON_SOURCES}) 39 | list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common) 40 | 41 | # 根据 BOARD_TYPE 配置添加对应的板级文件 42 | if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI) 43 | set(BOARD_TYPE "bread-compact-wifi") 44 | elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307) 45 | set(BOARD_TYPE "bread-compact-ml307") 46 | elseif(CONFIG_BOARD_TYPE_ESP_BOX_3) 47 | set(BOARD_TYPE "esp-box-3") 48 | elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_1) 49 | set(BOARD_TYPE "kevin-box-1") 50 | elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2) 51 | set(BOARD_TYPE "kevin-box-2") 52 | elseif(CONFIG_BOARD_TYPE_KEVIN_C3) 53 | set(BOARD_TYPE "kevin-c3") 54 | elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV) 55 | set(BOARD_TYPE "lichuang-dev") 56 | elseif(CONFIG_BOARD_TYPE_LICHUANG_C3_DEV) 57 | set(BOARD_TYPE "lichuang-c3-dev") 58 | elseif(CONFIG_BOARD_TYPE_MAGICLICK_2P4) 59 | set(BOARD_TYPE "magiclick-2p4") 60 | elseif(CONFIG_BOARD_TYPE_M5STACK_CORE_S3) 61 | set(BOARD_TYPE "m5stack-core-s3") 62 | elseif(CONFIG_BOARD_TYPE_ATOMS3R_ECHO_BASE) 63 | set(BOARD_TYPE "atoms3r-echo-base") 64 | elseif(CONFIG_BOARD_TYPE_XMINI_C3) 65 | set(BOARD_TYPE "xmini-c3") 66 | elseif(CONFIG_BOARD_TYPE_ESP32S3_KORVO2_V3) 67 | set(BOARD_TYPE "esp32s3-korvo2-v3") 68 | elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT) 69 | set(BOARD_TYPE "esp-sparkbot") 70 | elseif(CONFIG_BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8) 71 | set(BOARD_TYPE "esp32-s3-touch-amoled-1.8") 72 | endif() 73 | file(GLOB BOARD_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/${BOARD_TYPE}/*.cc) 74 | list(APPEND SOURCES ${BOARD_SOURCES}) 75 | 76 | if(CONFIG_IDF_TARGET_ESP32S3) 77 | list(APPEND SOURCES "audio_processing/audio_processor.cc" "audio_processing/wake_word_detect.cc") 78 | endif() 79 | 80 | idf_component_register(SRCS ${SOURCES} 81 | EMBED_FILES "assets/err_reg.p3" "assets/err_pin.p3" "assets/wificonfig.p3" "assets/upgrade.p3" 82 | INCLUDE_DIRS ${INCLUDE_DIRS} 83 | WHOLE_ARCHIVE 84 | ) 85 | 86 | # 使用 target_compile_definitions 来定义 BOARD_TYPE 87 | target_compile_definitions(${COMPONENT_LIB} 88 | PRIVATE BOARD_TYPE=\"${BOARD_TYPE}\" 89 | ) 90 | -------------------------------------------------------------------------------- /main/Kconfig.projbuild: -------------------------------------------------------------------------------- 1 | menu "Xiaozhi Assistant" 2 | 3 | config OTA_VERSION_URL 4 | string "OTA Version URL" 5 | default "https://api.tenclass.net/xiaozhi/ota/" 6 | help 7 | The application will access this URL to check for updates. 8 | 9 | choice CONNECTION_TYPE 10 | prompt "Connection Type" 11 | default CONNECTION_TYPE_MQTT_UDP 12 | help 13 | 网络数据传输协议 14 | config CONNECTION_TYPE_MQTT_UDP 15 | bool "MQTT + UDP" 16 | config CONNECTION_TYPE_WEBSOCKET 17 | bool "Websocket" 18 | endchoice 19 | 20 | config WEBSOCKET_URL 21 | depends on CONNECTION_TYPE_WEBSOCKET 22 | string "Websocket URL" 23 | default "wss://api.tenclass.net/xiaozhi/v1/" 24 | help 25 | Communication with the server through websocket after wake up. 26 | 27 | config WEBSOCKET_ACCESS_TOKEN 28 | depends on CONNECTION_TYPE_WEBSOCKET 29 | string "Websocket Access Token" 30 | default "test-token" 31 | help 32 | Access token for websocket communication. 33 | 34 | choice BOARD_TYPE 35 | prompt "Board Type" 36 | default BOARD_TYPE_BREAD_COMPACT_WIFI 37 | help 38 | Board type. 开发板类型 39 | config BOARD_TYPE_BREAD_COMPACT_WIFI 40 | bool "面包板新版接线(WiFi)" 41 | config BOARD_TYPE_BREAD_COMPACT_ML307 42 | bool "面包板新版接线(ML307 AT)" 43 | config BOARD_TYPE_ESP_BOX_3 44 | bool "ESP BOX 3" 45 | config BOARD_TYPE_KEVIN_BOX_1 46 | bool "Kevin Box 1" 47 | config BOARD_TYPE_KEVIN_BOX_2 48 | bool "Kevin Box 2" 49 | config BOARD_TYPE_KEVIN_C3 50 | bool "Kevin C3" 51 | config BOARD_TYPE_LICHUANG_DEV 52 | bool "立创·实战派ESP32-S3开发板" 53 | config BOARD_TYPE_LICHUANG_C3_DEV 54 | bool "立创·实战派ESP32-C3开发板" 55 | config BOARD_TYPE_MAGICLICK_2P4 56 | bool "神奇按钮 Magiclick_2.4" 57 | config BOARD_TYPE_M5STACK_CORE_S3 58 | bool "M5Stack CoreS3" 59 | config BOARD_TYPE_ATOMS3R_ECHO_BASE 60 | bool "AtomS3R + Echo Base" 61 | config BOARD_TYPE_XMINI_C3 62 | bool "虾哥 Mini C3" 63 | config BOARD_TYPE_ESP32S3_KORVO2_V3 64 | bool "ESP32S3_KORVO2_V3开发板" 65 | config BOARD_TYPE_ESP_SPARKBOT 66 | bool "ESP-SparkBot开发板" 67 | config BOARD_TYPE_ESP32S3_Touch_AMOLED_1_8 68 | bool "Waveshare ESP32-S3-Touch-AMOLED-1.8" 69 | endchoice 70 | 71 | endmenu 72 | -------------------------------------------------------------------------------- /main/application.h: -------------------------------------------------------------------------------- 1 | #ifndef _APPLICATION_H_ 2 | #define _APPLICATION_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include "protocol.h" 17 | #include "ota.h" 18 | #include "background_task.h" 19 | 20 | #if CONFIG_IDF_TARGET_ESP32S3 21 | #include "wake_word_detect.h" 22 | #include "audio_processor.h" 23 | #endif 24 | 25 | #define SCHEDULE_EVENT (1 << 0) 26 | #define AUDIO_INPUT_READY_EVENT (1 << 1) 27 | #define AUDIO_OUTPUT_READY_EVENT (1 << 2) 28 | 29 | enum DeviceState { 30 | kDeviceStateUnknown, 31 | kDeviceStateStarting, 32 | kDeviceStateWifiConfiguring, 33 | kDeviceStateIdle, 34 | kDeviceStateConnecting, 35 | kDeviceStateListening, 36 | kDeviceStateSpeaking, 37 | kDeviceStateUpgrading, 38 | kDeviceStateFatalError 39 | }; 40 | 41 | #define OPUS_FRAME_DURATION_MS 60 42 | 43 | class Application { 44 | public: 45 | static Application& GetInstance() { 46 | static Application instance; 47 | return instance; 48 | } 49 | // 删除拷贝构造函数和赋值运算符 50 | Application(const Application&) = delete; 51 | Application& operator=(const Application&) = delete; 52 | 53 | void Start(); 54 | DeviceState GetDeviceState() const { return device_state_; } 55 | bool IsVoiceDetected() const { return voice_detected_; } 56 | void Schedule(std::function callback); 57 | void SetDeviceState(DeviceState state); 58 | void Alert(const std::string& title, const std::string& message); 59 | void AbortSpeaking(AbortReason reason); 60 | void ToggleChatState(); 61 | void StartListening(); 62 | void StopListening(); 63 | void UpdateIotStates(); 64 | 65 | private: 66 | Application(); 67 | ~Application(); 68 | 69 | #if CONFIG_IDF_TARGET_ESP32S3 70 | WakeWordDetect wake_word_detect_; 71 | AudioProcessor audio_processor_; 72 | #endif 73 | Ota ota_; 74 | std::mutex mutex_; 75 | std::list> main_tasks_; 76 | std::unique_ptr protocol_; 77 | EventGroupHandle_t event_group_; 78 | volatile DeviceState device_state_ = kDeviceStateUnknown; 79 | bool keep_listening_ = false; 80 | bool aborted_ = false; 81 | bool voice_detected_ = false; 82 | std::string last_iot_states_; 83 | 84 | // Audio encode / decode 85 | BackgroundTask* background_task_ = nullptr; 86 | std::chrono::steady_clock::time_point last_output_time_; 87 | std::list> audio_decode_queue_; 88 | 89 | std::unique_ptr opus_encoder_; 90 | std::unique_ptr opus_decoder_; 91 | 92 | int opus_decode_sample_rate_ = -1; 93 | OpusResampler input_resampler_; 94 | OpusResampler reference_resampler_; 95 | OpusResampler output_resampler_; 96 | 97 | void MainLoop(); 98 | void InputAudio(); 99 | void OutputAudio(); 100 | void ResetDecoder(); 101 | void SetDecodeSampleRate(int sample_rate); 102 | void CheckNewVersion(); 103 | 104 | void PlayLocalFile(const char* data, size_t size); 105 | }; 106 | 107 | #endif // _APPLICATION_H_ 108 | -------------------------------------------------------------------------------- /main/assets/err_pin.p3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/main/assets/err_pin.p3 -------------------------------------------------------------------------------- /main/assets/err_reg.p3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/main/assets/err_reg.p3 -------------------------------------------------------------------------------- /main/assets/upgrade.p3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/main/assets/upgrade.p3 -------------------------------------------------------------------------------- /main/assets/wificonfig.p3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iotsystem/xiaozhi-esp32/001c10f04a2f30f98e881781e83f3313a78d13d6/main/assets/wificonfig.p3 -------------------------------------------------------------------------------- /main/audio_codecs/audio_codec.cc: -------------------------------------------------------------------------------- 1 | #include "audio_codec.h" 2 | #include "board.h" 3 | #include "settings.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define TAG "AudioCodec" 10 | 11 | AudioCodec::AudioCodec() { 12 | } 13 | 14 | AudioCodec::~AudioCodec() { 15 | } 16 | 17 | void AudioCodec::OnInputReady(std::function callback) { 18 | on_input_ready_ = callback; 19 | } 20 | 21 | void AudioCodec::OnOutputReady(std::function callback) { 22 | on_output_ready_ = callback; 23 | } 24 | 25 | void AudioCodec::OutputData(std::vector& data) { 26 | Write(data.data(), data.size()); 27 | } 28 | 29 | bool AudioCodec::InputData(std::vector& data) { 30 | int duration = 30; 31 | int input_frame_size = input_sample_rate_ / 1000 * duration * input_channels_; 32 | 33 | data.resize(input_frame_size); 34 | int samples = Read(data.data(), data.size()); 35 | if (samples > 0) { 36 | return true; 37 | } 38 | return false; 39 | } 40 | 41 | IRAM_ATTR bool AudioCodec::on_sent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) { 42 | auto audio_codec = (AudioCodec*)user_ctx; 43 | if (audio_codec->output_enabled_ && audio_codec->on_output_ready_) { 44 | return audio_codec->on_output_ready_(); 45 | } 46 | return false; 47 | } 48 | 49 | IRAM_ATTR bool AudioCodec::on_recv(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx) { 50 | auto audio_codec = (AudioCodec*)user_ctx; 51 | if (audio_codec->input_enabled_ && audio_codec->on_input_ready_) { 52 | return audio_codec->on_input_ready_(); 53 | } 54 | return false; 55 | } 56 | 57 | void AudioCodec::Start() { 58 | Settings settings("audio", false); 59 | output_volume_ = settings.GetInt("output_volume", output_volume_); 60 | 61 | // 注册音频数据回调 62 | i2s_event_callbacks_t rx_callbacks = {}; 63 | rx_callbacks.on_recv = on_recv; 64 | i2s_channel_register_event_callback(rx_handle_, &rx_callbacks, this); 65 | 66 | i2s_event_callbacks_t tx_callbacks = {}; 67 | tx_callbacks.on_sent = on_sent; 68 | i2s_channel_register_event_callback(tx_handle_, &tx_callbacks, this); 69 | 70 | ESP_ERROR_CHECK(i2s_channel_enable(tx_handle_)); 71 | ESP_ERROR_CHECK(i2s_channel_enable(rx_handle_)); 72 | 73 | EnableInput(true); 74 | EnableOutput(true); 75 | } 76 | 77 | void AudioCodec::SetOutputVolume(int volume) { 78 | output_volume_ = volume; 79 | ESP_LOGI(TAG, "Set output volume to %d", output_volume_); 80 | 81 | Settings settings("audio", true); 82 | settings.SetInt("output_volume", output_volume_); 83 | } 84 | 85 | void AudioCodec::EnableInput(bool enable) { 86 | if (enable == input_enabled_) { 87 | return; 88 | } 89 | input_enabled_ = enable; 90 | ESP_LOGI(TAG, "Set input enable to %s", enable ? "true" : "false"); 91 | } 92 | 93 | void AudioCodec::EnableOutput(bool enable) { 94 | if (enable == output_enabled_) { 95 | return; 96 | } 97 | output_enabled_ = enable; 98 | ESP_LOGI(TAG, "Set output enable to %s", enable ? "true" : "false"); 99 | } 100 | -------------------------------------------------------------------------------- /main/audio_codecs/audio_codec.h: -------------------------------------------------------------------------------- 1 | #ifndef _AUDIO_CODEC_H 2 | #define _AUDIO_CODEC_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include "board.h" 13 | 14 | class AudioCodec { 15 | public: 16 | AudioCodec(); 17 | virtual ~AudioCodec(); 18 | 19 | virtual void SetOutputVolume(int volume); 20 | virtual void EnableInput(bool enable); 21 | virtual void EnableOutput(bool enable); 22 | 23 | void Start(); 24 | void OutputData(std::vector& data); 25 | bool InputData(std::vector& data); 26 | void OnOutputReady(std::function callback); 27 | void OnInputReady(std::function callback); 28 | 29 | inline bool duplex() const { return duplex_; } 30 | inline bool input_reference() const { return input_reference_; } 31 | inline int input_sample_rate() const { return input_sample_rate_; } 32 | inline int output_sample_rate() const { return output_sample_rate_; } 33 | inline int input_channels() const { return input_channels_; } 34 | inline int output_channels() const { return output_channels_; } 35 | inline int output_volume() const { return output_volume_; } 36 | 37 | private: 38 | std::function on_input_ready_; 39 | std::function on_output_ready_; 40 | 41 | IRAM_ATTR static bool on_recv(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx); 42 | IRAM_ATTR static bool on_sent(i2s_chan_handle_t handle, i2s_event_data_t *event, void *user_ctx); 43 | 44 | protected: 45 | i2s_chan_handle_t tx_handle_ = nullptr; 46 | i2s_chan_handle_t rx_handle_ = nullptr; 47 | 48 | bool duplex_ = false; 49 | bool input_reference_ = false; 50 | bool input_enabled_ = false; 51 | bool output_enabled_ = false; 52 | int input_sample_rate_ = 0; 53 | int output_sample_rate_ = 0; 54 | int input_channels_ = 1; 55 | int output_channels_ = 1; 56 | int output_volume_ = 70; 57 | 58 | virtual int Read(int16_t* dest, int samples) = 0; 59 | virtual int Write(const int16_t* data, int samples) = 0; 60 | }; 61 | 62 | #endif // _AUDIO_CODEC_H 63 | -------------------------------------------------------------------------------- /main/audio_codecs/box_audio_codec.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOX_AUDIO_CODEC_H 2 | #define _BOX_AUDIO_CODEC_H 3 | 4 | #include "audio_codec.h" 5 | 6 | #include 7 | #include 8 | 9 | class BoxAudioCodec : public AudioCodec { 10 | private: 11 | const audio_codec_data_if_t* data_if_ = nullptr; 12 | const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; 13 | const audio_codec_if_t* out_codec_if_ = nullptr; 14 | const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; 15 | const audio_codec_if_t* in_codec_if_ = nullptr; 16 | const audio_codec_gpio_if_t* gpio_if_ = nullptr; 17 | 18 | esp_codec_dev_handle_t output_dev_ = nullptr; 19 | esp_codec_dev_handle_t input_dev_ = nullptr; 20 | 21 | void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); 22 | 23 | virtual int Read(int16_t* dest, int samples) override; 24 | virtual int Write(const int16_t* data, int samples) override; 25 | 26 | public: 27 | BoxAudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, 28 | gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, 29 | gpio_num_t pa_pin, uint8_t es8311_addr, uint8_t es7210_addr, bool input_reference); 30 | virtual ~BoxAudioCodec(); 31 | 32 | virtual void SetOutputVolume(int volume) override; 33 | virtual void EnableInput(bool enable) override; 34 | virtual void EnableOutput(bool enable) override; 35 | }; 36 | 37 | #endif // _BOX_AUDIO_CODEC_H 38 | -------------------------------------------------------------------------------- /main/audio_codecs/cores3_audio_codec.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOX_AUDIO_CODEC_H 2 | #define _BOX_AUDIO_CODEC_H 3 | 4 | #include "audio_codec.h" 5 | 6 | #include 7 | #include 8 | 9 | class CoreS3AudioCodec : public AudioCodec { 10 | private: 11 | const audio_codec_data_if_t* data_if_ = nullptr; 12 | const audio_codec_ctrl_if_t* out_ctrl_if_ = nullptr; 13 | const audio_codec_if_t* out_codec_if_ = nullptr; 14 | const audio_codec_ctrl_if_t* in_ctrl_if_ = nullptr; 15 | const audio_codec_if_t* in_codec_if_ = nullptr; 16 | const audio_codec_gpio_if_t* gpio_if_ = nullptr; 17 | 18 | esp_codec_dev_handle_t output_dev_ = nullptr; 19 | esp_codec_dev_handle_t input_dev_ = nullptr; 20 | 21 | void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); 22 | 23 | virtual int Read(int16_t* dest, int samples) override; 24 | virtual int Write(const int16_t* data, int samples) override; 25 | 26 | public: 27 | CoreS3AudioCodec(void* i2c_master_handle, int input_sample_rate, int output_sample_rate, 28 | gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, 29 | uint8_t aw88298_addr, uint8_t es7210_addr, bool input_reference); 30 | virtual ~CoreS3AudioCodec(); 31 | 32 | virtual void SetOutputVolume(int volume) override; 33 | virtual void EnableInput(bool enable) override; 34 | virtual void EnableOutput(bool enable) override; 35 | }; 36 | 37 | #endif // _BOX_AUDIO_CODEC_H 38 | -------------------------------------------------------------------------------- /main/audio_codecs/es8311_audio_codec.h: -------------------------------------------------------------------------------- 1 | #ifndef _ES8311_AUDIO_CODEC_H 2 | #define _ES8311_AUDIO_CODEC_H 3 | 4 | #include "audio_codec.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | class Es8311AudioCodec : public AudioCodec { 11 | private: 12 | const audio_codec_data_if_t* data_if_ = nullptr; 13 | const audio_codec_ctrl_if_t* ctrl_if_ = nullptr; 14 | const audio_codec_if_t* codec_if_ = nullptr; 15 | const audio_codec_gpio_if_t* gpio_if_ = nullptr; 16 | 17 | esp_codec_dev_handle_t output_dev_ = nullptr; 18 | esp_codec_dev_handle_t input_dev_ = nullptr; 19 | 20 | void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din); 21 | 22 | virtual int Read(int16_t* dest, int samples) override; 23 | virtual int Write(const int16_t* data, int samples) override; 24 | 25 | public: 26 | Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, 27 | gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, 28 | gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true); 29 | virtual ~Es8311AudioCodec(); 30 | 31 | virtual void SetOutputVolume(int volume) override; 32 | virtual void EnableInput(bool enable) override; 33 | virtual void EnableOutput(bool enable) override; 34 | }; 35 | 36 | #endif // _ES8311_AUDIO_CODEC_H -------------------------------------------------------------------------------- /main/audio_codecs/no_audio_codec.h: -------------------------------------------------------------------------------- 1 | #ifndef _NO_AUDIO_CODEC_H 2 | #define _NO_AUDIO_CODEC_H 3 | 4 | #include "audio_codec.h" 5 | 6 | #include 7 | #include 8 | 9 | class NoAudioCodec : public AudioCodec { 10 | private: 11 | virtual int Write(const int16_t* data, int samples) override; 12 | virtual int Read(int16_t* dest, int samples) override; 13 | 14 | public: 15 | virtual ~NoAudioCodec(); 16 | }; 17 | 18 | class NoAudioCodecDuplex : public NoAudioCodec { 19 | public: 20 | 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); 21 | }; 22 | 23 | class NoAudioCodecSimplex : public NoAudioCodec { 24 | public: 25 | 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); 26 | }; 27 | 28 | class NoAudioCodecSimplexPdm : public NoAudioCodec { 29 | public: 30 | 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); 31 | }; 32 | 33 | #endif // _NO_AUDIO_CODEC_H 34 | -------------------------------------------------------------------------------- /main/audio_processing/audio_processor.cc: -------------------------------------------------------------------------------- 1 | #include "audio_processor.h" 2 | #include 3 | 4 | #define PROCESSOR_RUNNING 0x01 5 | 6 | static const char* TAG = "AudioProcessor"; 7 | 8 | AudioProcessor::AudioProcessor() 9 | : afe_communication_data_(nullptr) { 10 | event_group_ = xEventGroupCreate(); 11 | } 12 | 13 | void AudioProcessor::Initialize(int channels, bool reference) { 14 | channels_ = channels; 15 | reference_ = reference; 16 | int ref_num = reference_ ? 1 : 0; 17 | 18 | afe_config_t afe_config = { 19 | .aec_init = false, 20 | .se_init = true, 21 | .vad_init = false, 22 | .wakenet_init = false, 23 | .voice_communication_init = true, 24 | .voice_communication_agc_init = true, 25 | .voice_communication_agc_gain = 10, 26 | .vad_mode = VAD_MODE_3, 27 | .wakenet_model_name = NULL, 28 | .wakenet_model_name_2 = NULL, 29 | .wakenet_mode = DET_MODE_90, 30 | .afe_mode = SR_MODE_HIGH_PERF, 31 | .afe_perferred_core = 1, 32 | .afe_perferred_priority = 1, 33 | .afe_ringbuf_size = 50, 34 | .memory_alloc_mode = AFE_MEMORY_ALLOC_MORE_PSRAM, 35 | .afe_linear_gain = 1.0, 36 | .agc_mode = AFE_MN_PEAK_AGC_MODE_2, 37 | .pcm_config = { 38 | .total_ch_num = channels_, 39 | .mic_num = channels_ - ref_num, 40 | .ref_num = ref_num, 41 | .sample_rate = 16000, 42 | }, 43 | .debug_init = false, 44 | .debug_hook = {{ AFE_DEBUG_HOOK_MASE_TASK_IN, NULL }, { AFE_DEBUG_HOOK_FETCH_TASK_IN, NULL }}, 45 | .afe_ns_mode = NS_MODE_SSP, 46 | .afe_ns_model_name = NULL, 47 | .fixed_first_channel = true, 48 | }; 49 | 50 | afe_communication_data_ = esp_afe_vc_v1.create_from_config(&afe_config); 51 | 52 | xTaskCreate([](void* arg) { 53 | auto this_ = (AudioProcessor*)arg; 54 | this_->AudioProcessorTask(); 55 | vTaskDelete(NULL); 56 | }, "audio_communication", 4096 * 2, this, 1, NULL); 57 | } 58 | 59 | AudioProcessor::~AudioProcessor() { 60 | if (afe_communication_data_ != nullptr) { 61 | esp_afe_vc_v1.destroy(afe_communication_data_); 62 | } 63 | vEventGroupDelete(event_group_); 64 | } 65 | 66 | void AudioProcessor::Input(const std::vector& data) { 67 | input_buffer_.insert(input_buffer_.end(), data.begin(), data.end()); 68 | 69 | auto feed_size = esp_afe_vc_v1.get_feed_chunksize(afe_communication_data_) * channels_; 70 | while (input_buffer_.size() >= feed_size) { 71 | auto chunk = input_buffer_.data(); 72 | esp_afe_vc_v1.feed(afe_communication_data_, chunk); 73 | input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + feed_size); 74 | } 75 | } 76 | 77 | void AudioProcessor::Start() { 78 | xEventGroupSetBits(event_group_, PROCESSOR_RUNNING); 79 | } 80 | 81 | void AudioProcessor::Stop() { 82 | xEventGroupClearBits(event_group_, PROCESSOR_RUNNING); 83 | } 84 | 85 | bool AudioProcessor::IsRunning() { 86 | return xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING; 87 | } 88 | 89 | void AudioProcessor::OnOutput(std::function&& data)> callback) { 90 | output_callback_ = callback; 91 | } 92 | 93 | void AudioProcessor::AudioProcessorTask() { 94 | auto fetch_size = esp_afe_sr_v1.get_fetch_chunksize(afe_communication_data_); 95 | auto feed_size = esp_afe_sr_v1.get_feed_chunksize(afe_communication_data_); 96 | ESP_LOGI(TAG, "Audio communication task started, feed size: %d fetch size: %d", 97 | feed_size, fetch_size); 98 | 99 | while (true) { 100 | xEventGroupWaitBits(event_group_, PROCESSOR_RUNNING, pdFALSE, pdTRUE, portMAX_DELAY); 101 | 102 | auto res = esp_afe_vc_v1.fetch(afe_communication_data_); 103 | if ((xEventGroupGetBits(event_group_) & PROCESSOR_RUNNING) == 0) { 104 | continue; 105 | } 106 | if (res == nullptr || res->ret_value == ESP_FAIL) { 107 | if (res != nullptr) { 108 | ESP_LOGI(TAG, "Error code: %d", res->ret_value); 109 | } 110 | continue; 111 | } 112 | 113 | if (output_callback_) { 114 | output_callback_(std::vector(res->data, res->data + res->data_size / sizeof(int16_t))); 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /main/audio_processing/audio_processor.h: -------------------------------------------------------------------------------- 1 | #ifndef AUDIO_PROCESSOR_H 2 | #define AUDIO_PROCESSOR_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | class AudioProcessor { 14 | public: 15 | AudioProcessor(); 16 | ~AudioProcessor(); 17 | 18 | void Initialize(int channels, bool reference); 19 | void Input(const std::vector& data); 20 | void Start(); 21 | void Stop(); 22 | bool IsRunning(); 23 | void OnOutput(std::function&& data)> callback); 24 | 25 | private: 26 | EventGroupHandle_t event_group_ = nullptr; 27 | esp_afe_sr_data_t* afe_communication_data_ = nullptr; 28 | std::vector input_buffer_; 29 | std::function&& data)> output_callback_; 30 | int channels_; 31 | bool reference_; 32 | 33 | void AudioProcessorTask(); 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /main/audio_processing/wake_word_detect.h: -------------------------------------------------------------------------------- 1 | #ifndef WAKE_WORD_DETECT_H 2 | #define WAKE_WORD_DETECT_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | 19 | class WakeWordDetect { 20 | public: 21 | WakeWordDetect(); 22 | ~WakeWordDetect(); 23 | 24 | void Initialize(int channels, bool reference); 25 | void Feed(const std::vector& data); 26 | void OnWakeWordDetected(std::function callback); 27 | void OnVadStateChange(std::function callback); 28 | void StartDetection(); 29 | void StopDetection(); 30 | bool IsDetectionRunning(); 31 | void EncodeWakeWordData(); 32 | bool GetWakeWordOpus(std::vector& opus); 33 | const std::string& GetLastDetectedWakeWord() const { return last_detected_wake_word_; } 34 | 35 | private: 36 | esp_afe_sr_data_t* afe_detection_data_ = nullptr; 37 | char* wakenet_model_ = NULL; 38 | std::vector wake_words_; 39 | std::vector input_buffer_; 40 | EventGroupHandle_t event_group_; 41 | std::function wake_word_detected_callback_; 42 | std::function vad_state_change_callback_; 43 | bool is_speaking_ = false; 44 | int channels_; 45 | bool reference_; 46 | std::string last_detected_wake_word_; 47 | 48 | TaskHandle_t wake_word_encode_task_ = nullptr; 49 | StaticTask_t wake_word_encode_task_buffer_; 50 | StackType_t* wake_word_encode_task_stack_ = nullptr; 51 | std::list> wake_word_pcm_; 52 | std::list> wake_word_opus_; 53 | std::mutex wake_word_mutex_; 54 | std::condition_variable wake_word_cv_; 55 | 56 | void StoreWakeWordData(uint16_t* data, size_t size); 57 | void AudioDetectionTask(); 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /main/background_task.cc: -------------------------------------------------------------------------------- 1 | #include "background_task.h" 2 | 3 | #include 4 | 5 | #define TAG "BackgroundTask" 6 | 7 | BackgroundTask::BackgroundTask(uint32_t stack_size) { 8 | #if CONFIG_IDF_TARGET_ESP32S3 9 | task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM); 10 | background_task_handle_ = xTaskCreateStatic([](void* arg) { 11 | BackgroundTask* task = (BackgroundTask*)arg; 12 | task->BackgroundTaskLoop(); 13 | }, "background_task", stack_size, this, 1, task_stack_, &task_buffer_); 14 | #else 15 | xTaskCreate([](void* arg) { 16 | BackgroundTask* task = (BackgroundTask*)arg; 17 | task->BackgroundTaskLoop(); 18 | }, "background_task", stack_size, this, 1, &background_task_handle_); 19 | #endif 20 | } 21 | 22 | BackgroundTask::~BackgroundTask() { 23 | if (background_task_handle_ != nullptr) { 24 | vTaskDelete(background_task_handle_); 25 | } 26 | if (task_stack_ != nullptr) { 27 | heap_caps_free(task_stack_); 28 | } 29 | } 30 | 31 | void BackgroundTask::Schedule(std::function callback) { 32 | std::lock_guard lock(mutex_); 33 | if (active_tasks_ >= 30) { 34 | ESP_LOGW(TAG, "active_tasks_ == %u", active_tasks_.load()); 35 | } 36 | active_tasks_++; 37 | main_tasks_.emplace_back([this, cb = std::move(callback)]() { 38 | cb(); 39 | { 40 | std::lock_guard lock(mutex_); 41 | active_tasks_--; 42 | if (main_tasks_.empty() && active_tasks_ == 0) { 43 | condition_variable_.notify_all(); 44 | } 45 | } 46 | }); 47 | condition_variable_.notify_all(); 48 | } 49 | 50 | void BackgroundTask::WaitForCompletion() { 51 | std::unique_lock lock(mutex_); 52 | condition_variable_.wait(lock, [this]() { 53 | return main_tasks_.empty() && active_tasks_ == 0; 54 | }); 55 | } 56 | 57 | void BackgroundTask::BackgroundTaskLoop() { 58 | ESP_LOGI(TAG, "background_task started"); 59 | while (true) { 60 | std::unique_lock lock(mutex_); 61 | condition_variable_.wait(lock, [this]() { return !main_tasks_.empty(); }); 62 | 63 | std::list> tasks = std::move(main_tasks_); 64 | lock.unlock(); 65 | 66 | for (auto& task : tasks) { 67 | task(); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /main/background_task.h: -------------------------------------------------------------------------------- 1 | #ifndef BACKGROUND_TASK_H 2 | #define BACKGROUND_TASK_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class BackgroundTask { 12 | public: 13 | BackgroundTask(uint32_t stack_size = 4096 * 2); 14 | ~BackgroundTask(); 15 | 16 | void Schedule(std::function callback); 17 | void WaitForCompletion(); 18 | 19 | private: 20 | std::mutex mutex_; 21 | std::list> main_tasks_; 22 | std::condition_variable condition_variable_; 23 | TaskHandle_t background_task_handle_ = nullptr; 24 | std::atomic active_tasks_{0}; 25 | 26 | TaskHandle_t task_ = nullptr; 27 | StaticTask_t task_buffer_; 28 | StackType_t* task_stack_ = nullptr; 29 | 30 | void BackgroundTaskLoop(); 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /main/boards/atoms3r-echo-base/README.md: -------------------------------------------------------------------------------- 1 | # 编译配置命令 2 | 3 | **配置编译目标为 ESP32S3:** 4 | 5 | ```bash 6 | idf.py set-target esp32s3 7 | ``` 8 | 9 | **打开 menuconfig:** 10 | 11 | ```bash 12 | idf.py menuconfig 13 | ``` 14 | 15 | **选择板子:** 16 | 17 | ``` 18 | Xiaozhi Assistant -> Board Type -> AtomS3R + Echo Base 19 | ``` 20 | 21 | **修改 flash 大小:** 22 | 23 | ``` 24 | Serial flasher config -> Flash size -> 8 MB 25 | ``` 26 | 27 | **修改分区表:** 28 | 29 | ``` 30 | Partition Table -> Custom partition CSV file -> partitions_8M.csv 31 | ``` 32 | 33 | **修改 psram 配置:** 34 | 35 | ``` 36 | Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Octal Mode PSRAM 37 | ``` 38 | 39 | **编译:** 40 | 41 | ```bash 42 | idf.py build 43 | ``` -------------------------------------------------------------------------------- /main/boards/atoms3r-echo-base/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | // AtomS3R+EchoBase Board configuration 5 | 6 | #include 7 | 8 | #define AUDIO_INPUT_REFERENCE true 9 | #define AUDIO_INPUT_SAMPLE_RATE 24000 10 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 11 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 80 12 | 13 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_NC 14 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_6 15 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 16 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 17 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 18 | 19 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_38 20 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_39 21 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 22 | #define AUDIO_CODEC_GPIO_PA GPIO_NUM_NC 23 | 24 | #define BUILTIN_LED_GPIO GPIO_NUM_NC 25 | #define BOOT_BUTTON_GPIO GPIO_NUM_41 26 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 27 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 28 | 29 | #define DISPLAY_SDA_PIN GPIO_NUM_NC 30 | #define DISPLAY_SCL_PIN GPIO_NUM_NC 31 | #define DISPLAY_WIDTH 128 32 | #define DISPLAY_HEIGHT 128 33 | #define DISPLAY_MIRROR_X false 34 | #define DISPLAY_MIRROR_Y false 35 | #define DISPLAY_SWAP_XY false 36 | 37 | #define DISPLAY_OFFSET_X 0 38 | #define DISPLAY_OFFSET_Y 32 39 | 40 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC 41 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT true 42 | 43 | 44 | #endif // _BOARD_CONFIG_H_ 45 | -------------------------------------------------------------------------------- /main/boards/bread-compact-ml307/compact_ml307_board.cc: -------------------------------------------------------------------------------- 1 | #include "ml307_board.h" 2 | #include "audio_codecs/no_audio_codec.h" 3 | #include "display/ssd1306_display.h" 4 | #include "system_reset.h" 5 | #include "application.h" 6 | #include "button.h" 7 | #include "config.h" 8 | #include "iot/thing_manager.h" 9 | #include "led/single_led.h" 10 | 11 | #include 12 | #include 13 | 14 | #define TAG "CompactMl307Board" 15 | 16 | class CompactMl307Board : public Ml307Board { 17 | private: 18 | i2c_master_bus_handle_t display_i2c_bus_; 19 | Button boot_button_; 20 | Button touch_button_; 21 | Button volume_up_button_; 22 | Button volume_down_button_; 23 | SystemReset system_reset_; 24 | 25 | void InitializeDisplayI2c() { 26 | i2c_master_bus_config_t bus_config = { 27 | .i2c_port = (i2c_port_t)0, 28 | .sda_io_num = DISPLAY_SDA_PIN, 29 | .scl_io_num = DISPLAY_SCL_PIN, 30 | .clk_source = I2C_CLK_SRC_DEFAULT, 31 | .glitch_ignore_cnt = 7, 32 | .intr_priority = 0, 33 | .trans_queue_depth = 0, 34 | .flags = { 35 | .enable_internal_pullup = 1, 36 | }, 37 | }; 38 | ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); 39 | } 40 | 41 | void InitializeButtons() { 42 | boot_button_.OnClick([this]() { 43 | Application::GetInstance().ToggleChatState(); 44 | }); 45 | touch_button_.OnPressDown([this]() { 46 | Application::GetInstance().StartListening(); 47 | }); 48 | touch_button_.OnPressUp([this]() { 49 | Application::GetInstance().StopListening(); 50 | }); 51 | 52 | volume_up_button_.OnClick([this]() { 53 | auto codec = GetAudioCodec(); 54 | auto volume = codec->output_volume() + 10; 55 | if (volume > 100) { 56 | volume = 100; 57 | } 58 | codec->SetOutputVolume(volume); 59 | GetDisplay()->ShowNotification("音量 " + std::to_string(volume)); 60 | }); 61 | 62 | volume_up_button_.OnLongPress([this]() { 63 | GetAudioCodec()->SetOutputVolume(100); 64 | GetDisplay()->ShowNotification("最大音量"); 65 | }); 66 | 67 | volume_down_button_.OnClick([this]() { 68 | auto codec = GetAudioCodec(); 69 | auto volume = codec->output_volume() - 10; 70 | if (volume < 0) { 71 | volume = 0; 72 | } 73 | codec->SetOutputVolume(volume); 74 | GetDisplay()->ShowNotification("音量 " + std::to_string(volume)); 75 | }); 76 | 77 | volume_down_button_.OnLongPress([this]() { 78 | GetAudioCodec()->SetOutputVolume(0); 79 | GetDisplay()->ShowNotification("已静音"); 80 | }); 81 | } 82 | 83 | // 物联网初始化,添加对 AI 可见设备 84 | void InitializeIot() { 85 | auto& thing_manager = iot::ThingManager::GetInstance(); 86 | thing_manager.AddThing(iot::CreateThing("Speaker")); 87 | thing_manager.AddThing(iot::CreateThing("Lamp")); 88 | } 89 | 90 | public: 91 | CompactMl307Board() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096), 92 | boot_button_(BOOT_BUTTON_GPIO), 93 | touch_button_(TOUCH_BUTTON_GPIO), 94 | volume_up_button_(VOLUME_UP_BUTTON_GPIO), 95 | volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), 96 | system_reset_(RESET_NVS_BUTTON_GPIO, RESET_FACTORY_BUTTON_GPIO) { 97 | // Check if the reset button is pressed 98 | system_reset_.CheckButtons(); 99 | 100 | InitializeDisplayI2c(); 101 | InitializeButtons(); 102 | InitializeIot(); 103 | } 104 | 105 | virtual Led* GetLed() override { 106 | static SingleLed led(BUILTIN_LED_GPIO); 107 | return &led; 108 | } 109 | 110 | virtual AudioCodec* GetAudioCodec() override { 111 | #ifdef AUDIO_I2S_METHOD_SIMPLEX 112 | static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 113 | 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); 114 | #else 115 | static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 116 | AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); 117 | #endif 118 | return &audio_codec; 119 | } 120 | 121 | virtual Display* GetDisplay() override { 122 | static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 123 | return &display; 124 | } 125 | }; 126 | 127 | DECLARE_BOARD(CompactMl307Board); 128 | -------------------------------------------------------------------------------- /main/boards/bread-compact-ml307/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 16000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | // 如果使用 Duplex I2S 模式,请注释下面一行 10 | #define AUDIO_I2S_METHOD_SIMPLEX 11 | 12 | #ifdef AUDIO_I2S_METHOD_SIMPLEX 13 | 14 | #define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 15 | #define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 16 | #define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 17 | #define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 18 | #define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 19 | #define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 20 | 21 | #else 22 | 23 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_4 24 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 25 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 26 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 27 | 28 | #endif 29 | 30 | #define BUILTIN_LED_GPIO GPIO_NUM_48 31 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 32 | #define TOUCH_BUTTON_GPIO GPIO_NUM_47 33 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 34 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 35 | #define RESET_NVS_BUTTON_GPIO GPIO_NUM_1 36 | #define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_2 37 | 38 | #define DISPLAY_SDA_PIN GPIO_NUM_41 39 | #define DISPLAY_SCL_PIN GPIO_NUM_42 40 | #define DISPLAY_WIDTH 128 41 | #define DISPLAY_HEIGHT 32 42 | #define DISPLAY_MIRROR_X true 43 | #define DISPLAY_MIRROR_Y true 44 | 45 | 46 | #define ML307_RX_PIN GPIO_NUM_11 47 | #define ML307_TX_PIN GPIO_NUM_12 48 | 49 | 50 | #endif // _BOARD_CONFIG_H_ 51 | -------------------------------------------------------------------------------- /main/boards/bread-compact-wifi/compact_wifi_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/no_audio_codec.h" 3 | #include "display/ssd1306_display.h" 4 | #include "system_reset.h" 5 | #include "application.h" 6 | #include "button.h" 7 | #include "config.h" 8 | #include "iot/thing_manager.h" 9 | #include "led/single_led.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | #define TAG "CompactWifiBoard" 16 | 17 | class CompactWifiBoard : public WifiBoard { 18 | private: 19 | i2c_master_bus_handle_t display_i2c_bus_; 20 | Button boot_button_; 21 | Button touch_button_; 22 | Button volume_up_button_; 23 | Button volume_down_button_; 24 | SystemReset system_reset_; 25 | 26 | void InitializeDisplayI2c() { 27 | i2c_master_bus_config_t bus_config = { 28 | .i2c_port = (i2c_port_t)0, 29 | .sda_io_num = DISPLAY_SDA_PIN, 30 | .scl_io_num = DISPLAY_SCL_PIN, 31 | .clk_source = I2C_CLK_SRC_DEFAULT, 32 | .glitch_ignore_cnt = 7, 33 | .intr_priority = 0, 34 | .trans_queue_depth = 0, 35 | .flags = { 36 | .enable_internal_pullup = 1, 37 | }, 38 | }; 39 | ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); 40 | } 41 | 42 | void InitializeButtons() { 43 | boot_button_.OnClick([this]() { 44 | auto& app = Application::GetInstance(); 45 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 46 | ResetWifiConfiguration(); 47 | } 48 | app.ToggleChatState(); 49 | }); 50 | touch_button_.OnPressDown([this]() { 51 | Application::GetInstance().StartListening(); 52 | }); 53 | touch_button_.OnPressUp([this]() { 54 | Application::GetInstance().StopListening(); 55 | }); 56 | 57 | volume_up_button_.OnClick([this]() { 58 | auto codec = GetAudioCodec(); 59 | auto volume = codec->output_volume() + 10; 60 | if (volume > 100) { 61 | volume = 100; 62 | } 63 | codec->SetOutputVolume(volume); 64 | GetDisplay()->ShowNotification("音量 " + std::to_string(volume)); 65 | }); 66 | 67 | volume_up_button_.OnLongPress([this]() { 68 | GetAudioCodec()->SetOutputVolume(100); 69 | GetDisplay()->ShowNotification("最大音量"); 70 | }); 71 | 72 | volume_down_button_.OnClick([this]() { 73 | auto codec = GetAudioCodec(); 74 | auto volume = codec->output_volume() - 10; 75 | if (volume < 0) { 76 | volume = 0; 77 | } 78 | codec->SetOutputVolume(volume); 79 | GetDisplay()->ShowNotification("音量 " + std::to_string(volume)); 80 | }); 81 | 82 | volume_down_button_.OnLongPress([this]() { 83 | GetAudioCodec()->SetOutputVolume(0); 84 | GetDisplay()->ShowNotification("已静音"); 85 | }); 86 | } 87 | 88 | // 物联网初始化,添加对 AI 可见设备 89 | void InitializeIot() { 90 | auto& thing_manager = iot::ThingManager::GetInstance(); 91 | thing_manager.AddThing(iot::CreateThing("Speaker")); 92 | thing_manager.AddThing(iot::CreateThing("Lamp")); 93 | } 94 | 95 | public: 96 | CompactWifiBoard() : 97 | boot_button_(BOOT_BUTTON_GPIO), 98 | touch_button_(TOUCH_BUTTON_GPIO), 99 | volume_up_button_(VOLUME_UP_BUTTON_GPIO), 100 | volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), 101 | system_reset_(RESET_NVS_BUTTON_GPIO, RESET_FACTORY_BUTTON_GPIO) { 102 | // Check if the reset button is pressed 103 | system_reset_.CheckButtons(); 104 | 105 | InitializeDisplayI2c(); 106 | InitializeButtons(); 107 | InitializeIot(); 108 | } 109 | 110 | virtual Led* GetLed() override { 111 | static SingleLed led(BUILTIN_LED_GPIO); 112 | return &led; 113 | } 114 | 115 | virtual AudioCodec* GetAudioCodec() override { 116 | #ifdef AUDIO_I2S_METHOD_SIMPLEX 117 | static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 118 | 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); 119 | #else 120 | static NoAudioCodecDuplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 121 | AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN); 122 | #endif 123 | return &audio_codec; 124 | } 125 | 126 | virtual Display* GetDisplay() override { 127 | static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 128 | return &display; 129 | } 130 | }; 131 | 132 | DECLARE_BOARD(CompactWifiBoard); 133 | -------------------------------------------------------------------------------- /main/boards/bread-compact-wifi/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 16000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | // 如果使用 Duplex I2S 模式,请注释下面一行 10 | #define AUDIO_I2S_METHOD_SIMPLEX 11 | 12 | #ifdef AUDIO_I2S_METHOD_SIMPLEX 13 | 14 | #define AUDIO_I2S_MIC_GPIO_WS GPIO_NUM_4 15 | #define AUDIO_I2S_MIC_GPIO_SCK GPIO_NUM_5 16 | #define AUDIO_I2S_MIC_GPIO_DIN GPIO_NUM_6 17 | #define AUDIO_I2S_SPK_GPIO_DOUT GPIO_NUM_7 18 | #define AUDIO_I2S_SPK_GPIO_BCLK GPIO_NUM_15 19 | #define AUDIO_I2S_SPK_GPIO_LRCK GPIO_NUM_16 20 | 21 | #else 22 | 23 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_4 24 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_5 25 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_6 26 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_7 27 | 28 | #endif 29 | 30 | 31 | #define BUILTIN_LED_GPIO GPIO_NUM_48 32 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 33 | #define TOUCH_BUTTON_GPIO GPIO_NUM_47 34 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_40 35 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_39 36 | #define RESET_NVS_BUTTON_GPIO GPIO_NUM_1 37 | #define RESET_FACTORY_BUTTON_GPIO GPIO_NUM_2 38 | 39 | #define DISPLAY_SDA_PIN GPIO_NUM_41 40 | #define DISPLAY_SCL_PIN GPIO_NUM_42 41 | #define DISPLAY_WIDTH 128 42 | #define DISPLAY_HEIGHT 32 43 | #define DISPLAY_MIRROR_X true 44 | #define DISPLAY_MIRROR_Y true 45 | 46 | #endif // _BOARD_CONFIG_H_ 47 | -------------------------------------------------------------------------------- /main/boards/common/board.cc: -------------------------------------------------------------------------------- 1 | #include "board.h" 2 | #include "system_info.h" 3 | #include "display/no_display.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #define TAG "Board" 10 | 11 | Board::Board() { 12 | } 13 | 14 | bool Board::GetBatteryLevel(int &level, bool& charging) { 15 | return false; 16 | } 17 | 18 | Display* Board::GetDisplay() { 19 | static NoDisplay display; 20 | return &display; 21 | } 22 | 23 | Led* Board::GetLed() { 24 | static NoLed led; 25 | return &led; 26 | } 27 | 28 | std::string Board::GetJson() { 29 | /* 30 | { 31 | "flash_size": 4194304, 32 | "psram_size": 0, 33 | "minimum_free_heap_size": 123456, 34 | "mac_address": "00:00:00:00:00:00", 35 | "chip_model_name": "esp32s3", 36 | "chip_info": { 37 | "model": 1, 38 | "cores": 2, 39 | "revision": 0, 40 | "features": 0 41 | }, 42 | "application": { 43 | "name": "my-app", 44 | "version": "1.0.0", 45 | "compile_time": "2021-01-01T00:00:00Z" 46 | "idf_version": "4.2-dev" 47 | "elf_sha256": "" 48 | }, 49 | "partition_table": [ 50 | "app": { 51 | "label": "app", 52 | "type": 1, 53 | "subtype": 2, 54 | "address": 0x10000, 55 | "size": 0x100000 56 | } 57 | ], 58 | "ota": { 59 | "label": "ota_0" 60 | } 61 | } 62 | */ 63 | std::string json = "{"; 64 | json += "\"flash_size\":" + std::to_string(SystemInfo::GetFlashSize()) + ","; 65 | json += "\"minimum_free_heap_size\":" + std::to_string(SystemInfo::GetMinimumFreeHeapSize()) + ","; 66 | json += "\"mac_address\":\"" + SystemInfo::GetMacAddress() + "\","; 67 | json += "\"chip_model_name\":\"" + SystemInfo::GetChipModelName() + "\","; 68 | json += "\"chip_info\":{"; 69 | 70 | esp_chip_info_t chip_info; 71 | esp_chip_info(&chip_info); 72 | json += "\"model\":" + std::to_string(chip_info.model) + ","; 73 | json += "\"cores\":" + std::to_string(chip_info.cores) + ","; 74 | json += "\"revision\":" + std::to_string(chip_info.revision) + ","; 75 | json += "\"features\":" + std::to_string(chip_info.features); 76 | json += "},"; 77 | 78 | json += "\"application\":{"; 79 | auto app_desc = esp_app_get_description(); 80 | json += "\"name\":\"" + std::string(app_desc->project_name) + "\","; 81 | json += "\"version\":\"" + std::string(app_desc->version) + "\","; 82 | json += "\"compile_time\":\"" + std::string(app_desc->date) + "T" + std::string(app_desc->time) + "Z\","; 83 | json += "\"idf_version\":\"" + std::string(app_desc->idf_ver) + "\","; 84 | 85 | char sha256_str[65]; 86 | for (int i = 0; i < 32; i++) { 87 | snprintf(sha256_str + i * 2, sizeof(sha256_str) - i * 2, "%02x", app_desc->app_elf_sha256[i]); 88 | } 89 | json += "\"elf_sha256\":\"" + std::string(sha256_str) + "\""; 90 | json += "},"; 91 | 92 | json += "\"partition_table\": ["; 93 | esp_partition_iterator_t it = esp_partition_find(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, NULL); 94 | while (it) { 95 | const esp_partition_t *partition = esp_partition_get(it); 96 | json += "{"; 97 | json += "\"label\":\"" + std::string(partition->label) + "\","; 98 | json += "\"type\":" + std::to_string(partition->type) + ","; 99 | json += "\"subtype\":" + std::to_string(partition->subtype) + ","; 100 | json += "\"address\":" + std::to_string(partition->address) + ","; 101 | json += "\"size\":" + std::to_string(partition->size); 102 | json += "},"; 103 | it = esp_partition_next(it); 104 | } 105 | json.pop_back(); // Remove the last comma 106 | json += "],"; 107 | 108 | json += "\"ota\":{"; 109 | auto ota_partition = esp_ota_get_running_partition(); 110 | json += "\"label\":\"" + std::string(ota_partition->label) + "\""; 111 | json += "},"; 112 | 113 | json += "\"board\":" + GetBoardJson(); 114 | 115 | // Close the JSON object 116 | json += "}"; 117 | return json; 118 | } -------------------------------------------------------------------------------- /main/boards/common/board.h: -------------------------------------------------------------------------------- 1 | #ifndef BOARD_H 2 | #define BOARD_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "led/led.h" 11 | 12 | void* create_board(); 13 | class AudioCodec; 14 | class Display; 15 | class Board { 16 | private: 17 | Board(const Board&) = delete; // 禁用拷贝构造函数 18 | Board& operator=(const Board&) = delete; // 禁用赋值操作 19 | virtual std::string GetBoardJson() = 0; 20 | 21 | protected: 22 | Board(); 23 | 24 | public: 25 | static Board& GetInstance() { 26 | static Board* instance = nullptr; 27 | if (nullptr == instance) { 28 | instance = static_cast(create_board()); 29 | } 30 | return *instance; 31 | } 32 | 33 | virtual void StartNetwork() = 0; 34 | virtual ~Board() = default; 35 | virtual Led* GetLed(); 36 | virtual AudioCodec* GetAudioCodec() = 0; 37 | virtual Display* GetDisplay(); 38 | virtual Http* CreateHttp() = 0; 39 | virtual WebSocket* CreateWebSocket() = 0; 40 | virtual Mqtt* CreateMqtt() = 0; 41 | virtual Udp* CreateUdp() = 0; 42 | virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) = 0; 43 | virtual const char* GetNetworkStateIcon() = 0; 44 | virtual bool GetBatteryLevel(int &level, bool& charging); 45 | virtual std::string GetJson(); 46 | virtual void SetPowerSaveMode(bool enabled) = 0; 47 | }; 48 | 49 | #define DECLARE_BOARD(BOARD_CLASS_NAME) \ 50 | void* create_board() { \ 51 | return new BOARD_CLASS_NAME(); \ 52 | } 53 | 54 | #endif // BOARD_H 55 | -------------------------------------------------------------------------------- /main/boards/common/button.cc: -------------------------------------------------------------------------------- 1 | #include "button.h" 2 | 3 | #include 4 | 5 | static const char* TAG = "Button"; 6 | 7 | Button::Button(gpio_num_t gpio_num, bool active_high) : gpio_num_(gpio_num) { 8 | if (gpio_num == GPIO_NUM_NC) { 9 | return; 10 | } 11 | button_config_t button_config = { 12 | .type = BUTTON_TYPE_GPIO, 13 | .long_press_time = 1000, 14 | .short_press_time = 50, 15 | .gpio_button_config = { 16 | .gpio_num = gpio_num, 17 | .active_level = static_cast(active_high ? 1 : 0) 18 | } 19 | }; 20 | button_handle_ = iot_button_create(&button_config); 21 | if (button_handle_ == NULL) { 22 | ESP_LOGE(TAG, "Failed to create button handle"); 23 | return; 24 | } 25 | } 26 | 27 | Button::~Button() { 28 | if (button_handle_ != NULL) { 29 | iot_button_delete(button_handle_); 30 | } 31 | } 32 | 33 | void Button::OnPressDown(std::function callback) { 34 | if (button_handle_ == nullptr) { 35 | return; 36 | } 37 | on_press_down_ = callback; 38 | iot_button_register_cb(button_handle_, BUTTON_PRESS_DOWN, [](void* handle, void* usr_data) { 39 | Button* button = static_cast(usr_data); 40 | if (button->on_press_down_) { 41 | button->on_press_down_(); 42 | } 43 | }, this); 44 | } 45 | 46 | void Button::OnPressUp(std::function callback) { 47 | if (button_handle_ == nullptr) { 48 | return; 49 | } 50 | on_press_up_ = callback; 51 | iot_button_register_cb(button_handle_, BUTTON_PRESS_UP, [](void* handle, void* usr_data) { 52 | Button* button = static_cast(usr_data); 53 | if (button->on_press_up_) { 54 | button->on_press_up_(); 55 | } 56 | }, this); 57 | } 58 | 59 | void Button::OnLongPress(std::function callback) { 60 | if (button_handle_ == nullptr) { 61 | return; 62 | } 63 | on_long_press_ = callback; 64 | iot_button_register_cb(button_handle_, BUTTON_LONG_PRESS_START, [](void* handle, void* usr_data) { 65 | Button* button = static_cast(usr_data); 66 | if (button->on_long_press_) { 67 | button->on_long_press_(); 68 | } 69 | }, this); 70 | } 71 | 72 | void Button::OnClick(std::function callback) { 73 | if (button_handle_ == nullptr) { 74 | return; 75 | } 76 | on_click_ = callback; 77 | iot_button_register_cb(button_handle_, BUTTON_SINGLE_CLICK, [](void* handle, void* usr_data) { 78 | Button* button = static_cast(usr_data); 79 | if (button->on_click_) { 80 | button->on_click_(); 81 | } 82 | }, this); 83 | } 84 | 85 | void Button::OnDoubleClick(std::function callback) { 86 | if (button_handle_ == nullptr) { 87 | return; 88 | } 89 | on_double_click_ = callback; 90 | iot_button_register_cb(button_handle_, BUTTON_DOUBLE_CLICK, [](void* handle, void* usr_data) { 91 | Button* button = static_cast(usr_data); 92 | if (button->on_double_click_) { 93 | button->on_double_click_(); 94 | } 95 | }, this); 96 | } 97 | -------------------------------------------------------------------------------- /main/boards/common/button.h: -------------------------------------------------------------------------------- 1 | #ifndef BUTTON_H_ 2 | #define BUTTON_H_ 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Button { 9 | public: 10 | Button(gpio_num_t gpio_num, bool active_high = false); 11 | ~Button(); 12 | 13 | void OnPressDown(std::function callback); 14 | void OnPressUp(std::function callback); 15 | void OnLongPress(std::function callback); 16 | void OnClick(std::function callback); 17 | void OnDoubleClick(std::function callback); 18 | private: 19 | gpio_num_t gpio_num_; 20 | button_handle_t button_handle_; 21 | 22 | 23 | std::function on_press_down_; 24 | std::function on_press_up_; 25 | std::function on_long_press_; 26 | std::function on_click_; 27 | std::function on_double_click_; 28 | }; 29 | 30 | #endif // BUTTON_H_ 31 | -------------------------------------------------------------------------------- /main/boards/common/i2c_device.cc: -------------------------------------------------------------------------------- 1 | #include "i2c_device.h" 2 | 3 | #include 4 | 5 | #define TAG "I2cDevice" 6 | 7 | 8 | I2cDevice::I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr) { 9 | i2c_device_config_t i2c_device_cfg = { 10 | .dev_addr_length = I2C_ADDR_BIT_LEN_7, 11 | .device_address = addr, 12 | .scl_speed_hz = 100000, 13 | .scl_wait_us = 0, 14 | .flags = { 15 | .disable_ack_check = 0, 16 | }, 17 | }; 18 | ESP_ERROR_CHECK(i2c_master_bus_add_device(i2c_bus, &i2c_device_cfg, &i2c_device_)); 19 | assert(i2c_device_ != NULL); 20 | } 21 | 22 | void I2cDevice::WriteReg(uint8_t reg, uint8_t value) { 23 | uint8_t buffer[2] = {reg, value}; 24 | ESP_ERROR_CHECK(i2c_master_transmit(i2c_device_, buffer, 2, 100)); 25 | } 26 | 27 | uint8_t I2cDevice::ReadReg(uint8_t reg) { 28 | uint8_t buffer[1]; 29 | ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, 1, 100)); 30 | return buffer[0]; 31 | } 32 | 33 | void I2cDevice::ReadRegs(uint8_t reg, uint8_t* buffer, size_t length) { 34 | ESP_ERROR_CHECK(i2c_master_transmit_receive(i2c_device_, ®, 1, buffer, length, 100)); 35 | } -------------------------------------------------------------------------------- /main/boards/common/i2c_device.h: -------------------------------------------------------------------------------- 1 | #ifndef I2C_DEVICE_H 2 | #define I2C_DEVICE_H 3 | 4 | #include 5 | 6 | class I2cDevice { 7 | public: 8 | I2cDevice(i2c_master_bus_handle_t i2c_bus, uint8_t addr); 9 | 10 | protected: 11 | i2c_master_dev_handle_t i2c_device_; 12 | 13 | void WriteReg(uint8_t reg, uint8_t value); 14 | uint8_t ReadReg(uint8_t reg); 15 | void ReadRegs(uint8_t reg, uint8_t* buffer, size_t length); 16 | }; 17 | 18 | #endif // I2C_DEVICE_H 19 | -------------------------------------------------------------------------------- /main/boards/common/ml307_board.cc: -------------------------------------------------------------------------------- 1 | #include "ml307_board.h" 2 | 3 | #include "application.h" 4 | #include "display.h" 5 | #include "font_awesome_symbols.h" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | static const char *TAG = "Ml307Board"; 16 | 17 | static std::string csq_to_string(int csq) { 18 | if (csq == -1) { 19 | return "No network"; 20 | } else if (csq >= 0 && csq <= 9) { 21 | return "Very bad"; 22 | } else if (csq >= 10 && csq <= 14) { 23 | return "Bad"; 24 | } else if (csq >= 15 && csq <= 19) { 25 | return "Fair"; 26 | } else if (csq >= 20 && csq <= 24) { 27 | return "Good"; 28 | } else if (csq >= 25 && csq <= 31) { 29 | return "Very good"; 30 | } 31 | return "Invalid"; 32 | } 33 | 34 | 35 | Ml307Board::Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size) : modem_(tx_pin, rx_pin, rx_buffer_size) { 36 | } 37 | 38 | void Ml307Board::StartNetwork() { 39 | auto display = Board::GetInstance().GetDisplay(); 40 | display->SetStatus("初始化模块"); 41 | modem_.SetDebug(false); 42 | modem_.SetBaudRate(921600); 43 | 44 | auto& application = Application::GetInstance(); 45 | // If low power, the material ready event will be triggered by the modem because of a reset 46 | modem_.OnMaterialReady([this, &application]() { 47 | ESP_LOGI(TAG, "ML307 material ready"); 48 | application.Schedule([this, &application]() { 49 | application.SetDeviceState(kDeviceStateIdle); 50 | WaitForNetworkReady(); 51 | }); 52 | }); 53 | 54 | WaitForNetworkReady(); 55 | } 56 | 57 | void Ml307Board::WaitForNetworkReady() { 58 | auto& application = Application::GetInstance(); 59 | auto display = Board::GetInstance().GetDisplay(); 60 | display->SetStatus("等待网络..."); 61 | int result = modem_.WaitForNetworkReady(); 62 | if (result == -1) { 63 | application.Alert("Error", "请插入SIM卡"); 64 | return; 65 | } else if (result == -2) { 66 | application.Alert("Error", "无法接入网络,请检查流量卡状态"); 67 | return; 68 | } 69 | 70 | // Print the ML307 modem information 71 | std::string module_name = modem_.GetModuleName(); 72 | std::string imei = modem_.GetImei(); 73 | std::string iccid = modem_.GetIccid(); 74 | ESP_LOGI(TAG, "ML307 Module: %s", module_name.c_str()); 75 | ESP_LOGI(TAG, "ML307 IMEI: %s", imei.c_str()); 76 | ESP_LOGI(TAG, "ML307 ICCID: %s", iccid.c_str()); 77 | 78 | // Close all previous connections 79 | modem_.ResetConnections(); 80 | } 81 | 82 | Http* Ml307Board::CreateHttp() { 83 | return new Ml307Http(modem_); 84 | } 85 | 86 | WebSocket* Ml307Board::CreateWebSocket() { 87 | return new WebSocket(new Ml307SslTransport(modem_, 0)); 88 | } 89 | 90 | Mqtt* Ml307Board::CreateMqtt() { 91 | return new Ml307Mqtt(modem_, 0); 92 | } 93 | 94 | Udp* Ml307Board::CreateUdp() { 95 | return new Ml307Udp(modem_, 0); 96 | } 97 | 98 | bool Ml307Board::GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) { 99 | if (!modem_.network_ready()) { 100 | return false; 101 | } 102 | network_name = modem_.GetCarrierName(); 103 | signal_quality = modem_.GetCsq(); 104 | signal_quality_text = csq_to_string(signal_quality); 105 | return signal_quality != -1; 106 | } 107 | 108 | const char* Ml307Board::GetNetworkStateIcon() { 109 | if (!modem_.network_ready()) { 110 | return FONT_AWESOME_SIGNAL_OFF; 111 | } 112 | int csq = modem_.GetCsq(); 113 | if (csq == -1) { 114 | return FONT_AWESOME_SIGNAL_OFF; 115 | } else if (csq >= 0 && csq <= 9) { 116 | return FONT_AWESOME_SIGNAL_1; 117 | } else if (csq >= 10 && csq <= 14) { 118 | return FONT_AWESOME_SIGNAL_2; 119 | } else if (csq >= 15 && csq <= 19) { 120 | return FONT_AWESOME_SIGNAL_3; 121 | } else if (csq >= 20 && csq <= 24) { 122 | return FONT_AWESOME_SIGNAL_4; 123 | } else if (csq >= 25 && csq <= 31) { 124 | return FONT_AWESOME_SIGNAL_FULL; 125 | } 126 | 127 | ESP_LOGW(TAG, "Invalid CSQ: %d", csq); 128 | return FONT_AWESOME_SIGNAL_OFF; 129 | } 130 | 131 | std::string Ml307Board::GetBoardJson() { 132 | // Set the board type for OTA 133 | std::string board_type = BOARD_TYPE; 134 | std::string board_json = std::string("{\"type\":\"" + board_type + "\","); 135 | board_json += "\"revision\":\"" + modem_.GetModuleName() + "\","; 136 | board_json += "\"carrier\":\"" + modem_.GetCarrierName() + "\","; 137 | board_json += "\"csq\":\"" + std::to_string(modem_.GetCsq()) + "\","; 138 | board_json += "\"imei\":\"" + modem_.GetImei() + "\","; 139 | board_json += "\"iccid\":\"" + modem_.GetIccid() + "\"}"; 140 | return board_json; 141 | } 142 | 143 | void Ml307Board::SetPowerSaveMode(bool enabled) { 144 | // TODO: Implement power save mode for ML307 145 | } 146 | -------------------------------------------------------------------------------- /main/boards/common/ml307_board.h: -------------------------------------------------------------------------------- 1 | #ifndef ML307_BOARD_H 2 | #define ML307_BOARD_H 3 | 4 | #include "board.h" 5 | #include 6 | 7 | class Ml307Board : public Board { 8 | protected: 9 | Ml307AtModem modem_; 10 | 11 | virtual std::string GetBoardJson() override; 12 | void WaitForNetworkReady(); 13 | 14 | public: 15 | Ml307Board(gpio_num_t tx_pin, gpio_num_t rx_pin, size_t rx_buffer_size = 4096); 16 | virtual void StartNetwork() override; 17 | virtual Http* CreateHttp() override; 18 | virtual WebSocket* CreateWebSocket() override; 19 | virtual Mqtt* CreateMqtt() override; 20 | virtual Udp* CreateUdp() override; 21 | virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override; 22 | virtual const char* GetNetworkStateIcon() override; 23 | virtual void SetPowerSaveMode(bool enabled) override; 24 | }; 25 | 26 | #endif // ML307_BOARD_H 27 | -------------------------------------------------------------------------------- /main/boards/common/system_reset.cc: -------------------------------------------------------------------------------- 1 | #include "system_reset.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | 11 | #define TAG "SystemReset" 12 | 13 | 14 | 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) { 15 | // Configure GPIO1, GPIO2 as INPUT, reset NVS flash if the button is pressed 16 | gpio_config_t io_conf; 17 | io_conf.intr_type = GPIO_INTR_DISABLE; 18 | io_conf.mode = GPIO_MODE_INPUT; 19 | io_conf.pin_bit_mask = (1ULL << reset_nvs_pin_) | (1ULL << reset_factory_pin_); 20 | io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; 21 | io_conf.pull_up_en = GPIO_PULLUP_ENABLE; 22 | gpio_config(&io_conf); 23 | } 24 | 25 | 26 | void SystemReset::CheckButtons() { 27 | if (gpio_get_level(reset_factory_pin_) == 0) { 28 | ESP_LOGI(TAG, "Button is pressed, reset to factory"); 29 | ResetNvsFlash(); 30 | ResetToFactory(); 31 | } 32 | 33 | if (gpio_get_level(reset_nvs_pin_) == 0) { 34 | ESP_LOGI(TAG, "Button is pressed, reset NVS flash"); 35 | ResetNvsFlash(); 36 | } 37 | } 38 | 39 | void SystemReset::ResetNvsFlash() { 40 | ESP_LOGI(TAG, "Resetting NVS flash"); 41 | esp_err_t ret = nvs_flash_erase(); 42 | if (ret != ESP_OK) { 43 | ESP_LOGE(TAG, "Failed to erase NVS flash"); 44 | } 45 | ret = nvs_flash_init(); 46 | if (ret != ESP_OK) { 47 | ESP_LOGE(TAG, "Failed to initialize NVS flash"); 48 | } 49 | } 50 | 51 | void SystemReset::ResetToFactory() { 52 | ESP_LOGI(TAG, "Resetting to factory"); 53 | // Erase otadata partition 54 | const esp_partition_t* partition = esp_partition_find_first(ESP_PARTITION_TYPE_DATA, ESP_PARTITION_SUBTYPE_DATA_OTA, NULL); 55 | if (partition == NULL) { 56 | ESP_LOGE(TAG, "Failed to find otadata partition"); 57 | return; 58 | } 59 | esp_partition_erase_range(partition, 0, partition->size); 60 | ESP_LOGI(TAG, "Erased otadata partition"); 61 | 62 | // Reboot in 3 seconds 63 | RestartInSeconds(3); 64 | } 65 | 66 | void SystemReset::RestartInSeconds(int seconds) { 67 | for (int i = seconds; i > 0; i--) { 68 | ESP_LOGI(TAG, "Resetting in %d seconds", i); 69 | vTaskDelay(1000 / portTICK_PERIOD_MS); 70 | } 71 | esp_restart(); 72 | } 73 | -------------------------------------------------------------------------------- /main/boards/common/system_reset.h: -------------------------------------------------------------------------------- 1 | #ifndef _SYSTEM_RESET_H 2 | #define _SYSTEM_RESET_H 3 | 4 | #include 5 | 6 | class SystemReset { 7 | public: 8 | SystemReset(gpio_num_t reset_nvs_pin, gpio_num_t reset_factory_pin); // 构造函数私有化 9 | void CheckButtons(); 10 | 11 | private: 12 | gpio_num_t reset_nvs_pin_; 13 | gpio_num_t reset_factory_pin_; 14 | 15 | void ResetNvsFlash(); 16 | void ResetToFactory(); 17 | void RestartInSeconds(int seconds); 18 | }; 19 | 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /main/boards/common/wifi_board.h: -------------------------------------------------------------------------------- 1 | #ifndef WIFI_BOARD_H 2 | #define WIFI_BOARD_H 3 | 4 | #include "board.h" 5 | 6 | class WifiBoard : public Board { 7 | protected: 8 | bool wifi_config_mode_ = false; 9 | 10 | WifiBoard(); 11 | void EnterWifiConfigMode(); 12 | virtual std::string GetBoardJson() override; 13 | 14 | public: 15 | virtual void StartNetwork() override; 16 | virtual Http* CreateHttp() override; 17 | virtual WebSocket* CreateWebSocket() override; 18 | virtual Mqtt* CreateMqtt() override; 19 | virtual Udp* CreateUdp() override; 20 | virtual bool GetNetworkState(std::string& network_name, int& signal_quality, std::string& signal_quality_text) override; 21 | virtual const char* GetNetworkStateIcon() override; 22 | virtual void SetPowerSaveMode(bool enabled) override; 23 | virtual void ResetWifiConfiguration(); 24 | }; 25 | 26 | #endif // WIFI_BOARD_H 27 | -------------------------------------------------------------------------------- /main/boards/esp-box-3/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 80 9 | 10 | #define AUDIO_INPUT_REFERENCE true 11 | 12 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_2 13 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_45 14 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_17 15 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_16 16 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_15 17 | 18 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_46 19 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_8 20 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 21 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 22 | #define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR 23 | 24 | #define BUILTIN_LED_GPIO GPIO_NUM_NC 25 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 26 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 27 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 28 | 29 | #define DISPLAY_WIDTH 320 30 | #define DISPLAY_HEIGHT 240 31 | #define DISPLAY_MIRROR_X true 32 | #define DISPLAY_MIRROR_Y true 33 | #define DISPLAY_SWAP_XY false 34 | 35 | #define DISPLAY_OFFSET_X 0 36 | #define DISPLAY_OFFSET_Y 0 37 | 38 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_47 39 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT false 40 | 41 | 42 | #endif // _BOARD_CONFIG_H_ 43 | -------------------------------------------------------------------------------- /main/boards/esp-sparkbot/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 16000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 16000 8 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 80 9 | 10 | #define AUDIO_INPUT_REFERENCE false 11 | 12 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_45 13 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_41 14 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_39 15 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_40 16 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_42 17 | 18 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_46 19 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_4 20 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_5 21 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 22 | 23 | #define BUILTIN_LED_GPIO GPIO_NUM_NC 24 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 25 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 26 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 27 | 28 | #define DISPLAY_WIDTH 240 29 | #define DISPLAY_HEIGHT 240 30 | #define DISPLAY_MIRROR_X true 31 | #define DISPLAY_MIRROR_Y true 32 | #define DISPLAY_SWAP_XY false 33 | 34 | #define DISPLAY_OFFSET_X 0 35 | #define DISPLAY_OFFSET_Y 0 36 | 37 | #define DISPLAY_DC_GPIO GPIO_NUM_43 38 | #define DISPLAY_CS_GPIO GPIO_NUM_44 39 | #define DISPLAY_CLK_GPIO GPIO_NUM_21 40 | #define DISPLAY_MOSI_GPIO GPIO_NUM_47 41 | #define DISPLAY_RST_GPIO GPIO_NUM_NC 42 | 43 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46 44 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT false 45 | 46 | 47 | #endif // _BOARD_CONFIG_H_ 48 | -------------------------------------------------------------------------------- /main/boards/esp-sparkbot/esp_sparkbot_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/es8311_audio_codec.h" 3 | #include "display/lcd_display.h" 4 | #include "font_awesome_symbols.h" 5 | #include "application.h" 6 | #include "button.h" 7 | #include "config.h" 8 | #include "iot/thing_manager.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define TAG "esp_sparkbot" 17 | 18 | class SparkBotEs8311AudioCodec : public Es8311AudioCodec { 19 | private: 20 | 21 | public: 22 | SparkBotEs8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate, 23 | gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din, 24 | gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true) 25 | : Es8311AudioCodec(i2c_master_handle, i2c_port, input_sample_rate, output_sample_rate, 26 | mclk, bclk, ws, dout, din,pa_pin, es8311_addr, use_mclk = true) {} 27 | 28 | void EnableOutput(bool enable) override { 29 | if (enable == output_enabled_) { 30 | return; 31 | } 32 | if (enable) { 33 | Es8311AudioCodec::EnableOutput(enable); 34 | } else { 35 | // Nothing todo because the display io and PA io conflict 36 | } 37 | } 38 | }; 39 | 40 | class EspSparkBot : public WifiBoard { 41 | private: 42 | i2c_master_bus_handle_t i2c_bus_; 43 | Button boot_button_; 44 | Display* display_; 45 | 46 | void InitializeI2c() { 47 | // Initialize I2C peripheral 48 | i2c_master_bus_config_t i2c_bus_cfg = { 49 | .i2c_port = I2C_NUM_0, 50 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 51 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 52 | .clk_source = I2C_CLK_SRC_DEFAULT, 53 | .glitch_ignore_cnt = 7, 54 | .intr_priority = 0, 55 | .trans_queue_depth = 0, 56 | .flags = { 57 | .enable_internal_pullup = 1, 58 | }, 59 | }; 60 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); 61 | } 62 | 63 | void InitializeSpi() { 64 | spi_bus_config_t buscfg = {}; 65 | buscfg.mosi_io_num = DISPLAY_MOSI_GPIO; 66 | buscfg.miso_io_num = GPIO_NUM_NC; 67 | buscfg.sclk_io_num = DISPLAY_CLK_GPIO; 68 | buscfg.quadwp_io_num = GPIO_NUM_NC; 69 | buscfg.quadhd_io_num = GPIO_NUM_NC; 70 | buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); 71 | ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); 72 | } 73 | 74 | void InitializeButtons() { 75 | boot_button_.OnClick([this]() { 76 | auto& app = Application::GetInstance(); 77 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 78 | ResetWifiConfiguration(); 79 | } 80 | app.ToggleChatState(); 81 | }); 82 | } 83 | 84 | void InitializeDisplay() { 85 | esp_lcd_panel_io_handle_t panel_io = nullptr; 86 | esp_lcd_panel_handle_t panel = nullptr; 87 | 88 | // 液晶屏控制IO初始化 89 | ESP_LOGD(TAG, "Install panel IO"); 90 | esp_lcd_panel_io_spi_config_t io_config = {}; 91 | io_config.cs_gpio_num = DISPLAY_CS_GPIO; 92 | io_config.dc_gpio_num = DISPLAY_DC_GPIO; 93 | io_config.spi_mode = 0; 94 | io_config.pclk_hz = 40 * 1000 * 1000; 95 | io_config.trans_queue_depth = 10; 96 | io_config.lcd_cmd_bits = 8; 97 | io_config.lcd_param_bits = 8; 98 | ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); 99 | 100 | // 初始化液晶屏驱动芯片 101 | ESP_LOGD(TAG, "Install LCD driver"); 102 | 103 | esp_lcd_panel_dev_config_t panel_config = {}; 104 | panel_config.reset_gpio_num = GPIO_NUM_NC; 105 | panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; 106 | panel_config.bits_per_pixel = 16; 107 | ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); 108 | 109 | esp_lcd_panel_reset(panel); 110 | esp_lcd_panel_init(panel); 111 | esp_lcd_panel_invert_color(panel, false); 112 | esp_lcd_panel_disp_on_off(panel, true); 113 | display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 114 | DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); 115 | } 116 | 117 | // 物联网初始化,添加对 AI 可见设备 118 | void InitializeIot() { 119 | auto& thing_manager = iot::ThingManager::GetInstance(); 120 | thing_manager.AddThing(iot::CreateThing("Speaker")); 121 | } 122 | 123 | public: 124 | EspSparkBot() : boot_button_(BOOT_BUTTON_GPIO) { 125 | InitializeI2c(); 126 | InitializeSpi(); 127 | InitializeDisplay(); 128 | InitializeButtons(); 129 | InitializeIot(); 130 | } 131 | 132 | virtual AudioCodec* GetAudioCodec() override { 133 | static SparkBotEs8311AudioCodec audio_codec(i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 134 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 135 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); 136 | return &audio_codec; 137 | } 138 | 139 | virtual Display* GetDisplay() override { 140 | return display_; 141 | } 142 | }; 143 | 144 | DECLARE_BOARD(EspSparkBot); 145 | -------------------------------------------------------------------------------- /main/boards/esp32-s3-touch-amoled-1.8/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 90 9 | 10 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 11 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_45 12 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 13 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 14 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 15 | 16 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_46 17 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_15 18 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_14 19 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 20 | 21 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 22 | 23 | #define EXAMPLE_PIN_NUM_LCD_CS GPIO_NUM_12 24 | #define EXAMPLE_PIN_NUM_LCD_PCLK GPIO_NUM_11 25 | #define EXAMPLE_PIN_NUM_LCD_DATA0 GPIO_NUM_4 26 | #define EXAMPLE_PIN_NUM_LCD_DATA1 GPIO_NUM_5 27 | #define EXAMPLE_PIN_NUM_LCD_DATA2 GPIO_NUM_6 28 | #define EXAMPLE_PIN_NUM_LCD_DATA3 GPIO_NUM_7 29 | #define EXAMPLE_PIN_NUM_LCD_RST GPIO_NUM_NC 30 | #define DISPLAY_WIDTH 368 31 | #define DISPLAY_HEIGHT 448 32 | #define DISPLAY_MIRROR_X false 33 | #define DISPLAY_MIRROR_Y false 34 | #define DISPLAY_SWAP_XY false 35 | 36 | #define DISPLAY_OFFSET_X 0 37 | #define DISPLAY_OFFSET_Y 0 38 | 39 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC 40 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT false 41 | #endif // _BOARD_CONFIG_H_ 42 | -------------------------------------------------------------------------------- /main/boards/esp32s3-korvo2-v3/config.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef _BOARD_CONFIG_H_ 3 | #define _BOARD_CONFIG_H_ 4 | 5 | #include 6 | 7 | #define AUDIO_INPUT_SAMPLE_RATE 24000 8 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 9 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 90 10 | 11 | #define AUDIO_INPUT_REFERENCE true 12 | 13 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_16 14 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_45 15 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 16 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 17 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_8 18 | 19 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_48 20 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_17 21 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_18 22 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 23 | #define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR 24 | 25 | #define BUILTIN_LED_GPIO GPIO_NUM_NC 26 | #define BOOT_BUTTON_GPIO GPIO_NUM_5 27 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 28 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 29 | 30 | #define DISPLAY_SDA_PIN GPIO_NUM_NC 31 | #define DISPLAY_SCL_PIN GPIO_NUM_NC 32 | #define DISPLAY_WIDTH 280 33 | #define DISPLAY_HEIGHT 240 34 | #define DISPLAY_SWAP_XY true 35 | #define DISPLAY_MIRROR_X false 36 | #define DISPLAY_MIRROR_Y true 37 | #define BACKLIGHT_INVERT false 38 | 39 | #define DISPLAY_OFFSET_X 20 40 | #define DISPLAY_OFFSET_Y 0 41 | 42 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC 43 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT false 44 | 45 | #endif // _BOARD_CONFIG_H_ 46 | -------------------------------------------------------------------------------- /main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/box_audio_codec.h" 3 | #include "display/lcd_display.h" 4 | #include "application.h" 5 | #include "button.h" 6 | 7 | #include "config.h" 8 | #include "i2c_device.h" 9 | #include "iot/thing_manager.h" 10 | 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | #define TAG "esp32s3_korvo2_v3" 18 | 19 | class esp32s3_korvo2_v3_board : public WifiBoard 20 | { 21 | private: 22 | Button boot_button_; 23 | i2c_master_bus_handle_t i2c_bus_; 24 | LcdDisplay* display_; 25 | 26 | void InitializeI2c() { 27 | // Initialize I2C peripheral 28 | i2c_master_bus_config_t i2c_bus_cfg = { 29 | .i2c_port = (i2c_port_t)1, 30 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 31 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 32 | .clk_source = I2C_CLK_SRC_DEFAULT, 33 | .glitch_ignore_cnt = 7, 34 | .intr_priority = 0, 35 | .trans_queue_depth = 0, 36 | .flags = { 37 | .enable_internal_pullup = 1, 38 | }, 39 | }; 40 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); 41 | } 42 | 43 | void InitializeSpi() { 44 | spi_bus_config_t buscfg = {}; 45 | buscfg.mosi_io_num = GPIO_NUM_0; 46 | buscfg.miso_io_num = GPIO_NUM_NC; 47 | buscfg.sclk_io_num = GPIO_NUM_1; 48 | buscfg.quadwp_io_num = GPIO_NUM_NC; 49 | buscfg.quadhd_io_num = GPIO_NUM_NC; 50 | buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); 51 | ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); 52 | } 53 | 54 | void InitializeButtons() { 55 | boot_button_.OnClick([this]() { 56 | auto& app = Application::GetInstance(); 57 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 58 | ResetWifiConfiguration(); 59 | } 60 | }); 61 | boot_button_.OnPressDown([this]() { 62 | Application::GetInstance().StartListening(); 63 | }); 64 | boot_button_.OnPressUp([this]() { 65 | Application::GetInstance().StopListening(); 66 | }); 67 | } 68 | 69 | void InitializeSt7789Display() { 70 | esp_lcd_panel_io_handle_t panel_io = nullptr; 71 | esp_lcd_panel_handle_t panel = nullptr; 72 | // 液晶屏控制IO初始化 73 | ESP_LOGD(TAG, "Install panel IO"); 74 | esp_lcd_panel_io_spi_config_t io_config = {}; 75 | io_config.cs_gpio_num = GPIO_NUM_46; 76 | io_config.dc_gpio_num = GPIO_NUM_2; 77 | io_config.spi_mode = 0; 78 | io_config.pclk_hz = 60 * 1000 * 1000; 79 | io_config.trans_queue_depth = 10; 80 | io_config.lcd_cmd_bits = 8; 81 | io_config.lcd_param_bits = 8; 82 | ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); 83 | 84 | // 初始化液晶屏驱动芯片ST7789 85 | ESP_LOGD(TAG, "Install LCD driver"); 86 | esp_lcd_panel_dev_config_t panel_config = {}; 87 | panel_config.reset_gpio_num = GPIO_NUM_NC; 88 | panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; 89 | panel_config.bits_per_pixel = 16; 90 | ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); 91 | ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); 92 | ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); 93 | ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); 94 | ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); 95 | ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); 96 | 97 | display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 98 | DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); 99 | } 100 | 101 | // 物联网初始化,添加对 AI 可见设备 102 | void InitializeIot() { 103 | auto& thing_manager = iot::ThingManager::GetInstance(); 104 | thing_manager.AddThing(iot::CreateThing("Speaker")); 105 | 106 | } 107 | 108 | public: 109 | esp32s3_korvo2_v3_board() : boot_button_(BOOT_BUTTON_GPIO) 110 | { 111 | ESP_LOGI(TAG, "Initializing esp32s3_korvo2_v3 Board"); 112 | InitializeI2c(); 113 | InitializeSpi(); 114 | InitializeButtons(); 115 | InitializeSt7789Display(); 116 | InitializeIot(); 117 | } 118 | 119 | virtual AudioCodec* GetAudioCodec() override { 120 | static BoxAudioCodec* audio_codec = nullptr; 121 | if (audio_codec == nullptr) { 122 | audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 123 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 124 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); 125 | audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME); 126 | } 127 | return audio_codec; 128 | } 129 | 130 | virtual Display *GetDisplay() override 131 | { 132 | return display_; 133 | } 134 | }; 135 | 136 | DECLARE_BOARD(esp32s3_korvo2_v3_board); 137 | -------------------------------------------------------------------------------- /main/boards/kevin-box-1/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | #define AUDIO_INPUT_REFERENCE true 10 | 11 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_42 12 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_47 13 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_48 14 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_45 15 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_21 16 | 17 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_17 18 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_39 19 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_38 20 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 21 | #define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR 22 | 23 | #define BUILTIN_LED_GPIO GPIO_NUM_8 24 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 25 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_6 26 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_7 27 | 28 | #define DISPLAY_SDA_PIN GPIO_NUM_4 29 | #define DISPLAY_SCL_PIN GPIO_NUM_5 30 | #define DISPLAY_WIDTH 128 31 | #define DISPLAY_HEIGHT 64 32 | #define DISPLAY_MIRROR_X false 33 | #define DISPLAY_MIRROR_Y false 34 | 35 | #define ML307_RX_PIN GPIO_NUM_20 36 | #define ML307_TX_PIN GPIO_NUM_19 37 | 38 | 39 | #endif // _BOARD_CONFIG_H_ 40 | -------------------------------------------------------------------------------- /main/boards/kevin-box-1/kevin_box_board.cc: -------------------------------------------------------------------------------- 1 | #include "ml307_board.h" 2 | #include "audio_codecs/box_audio_codec.h" 3 | #include "display/ssd1306_display.h" 4 | #include "application.h" 5 | #include "button.h" 6 | #include "config.h" 7 | #include "iot/thing_manager.h" 8 | #include "led_strip/single_led.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define TAG "KevinBoxBoard" 16 | 17 | class KevinBoxBoard : public Ml307Board { 18 | private: 19 | i2c_master_bus_handle_t display_i2c_bus_; 20 | i2c_master_bus_handle_t codec_i2c_bus_; 21 | Button boot_button_; 22 | Button volume_up_button_; 23 | Button volume_down_button_; 24 | 25 | void MountStorage() { 26 | // Mount the storage partition 27 | esp_vfs_spiffs_conf_t conf = { 28 | .base_path = "/storage", 29 | .partition_label = "storage", 30 | .max_files = 5, 31 | .format_if_mount_failed = true, 32 | }; 33 | esp_vfs_spiffs_register(&conf); 34 | } 35 | 36 | void Enable4GModule() { 37 | // Make GPIO15 HIGH to enable the 4G module 38 | gpio_config_t ml307_enable_config = { 39 | .pin_bit_mask = (1ULL << 15) | (1ULL << 18), 40 | .mode = GPIO_MODE_OUTPUT, 41 | .pull_up_en = GPIO_PULLUP_DISABLE, 42 | .pull_down_en = GPIO_PULLDOWN_DISABLE, 43 | .intr_type = GPIO_INTR_DISABLE, 44 | }; 45 | gpio_config(&ml307_enable_config); 46 | gpio_set_level(GPIO_NUM_15, 1); 47 | gpio_set_level(GPIO_NUM_18, 1); 48 | } 49 | 50 | void InitializeDisplayI2c() { 51 | i2c_master_bus_config_t bus_config = { 52 | .i2c_port = (i2c_port_t)0, 53 | .sda_io_num = DISPLAY_SDA_PIN, 54 | .scl_io_num = DISPLAY_SCL_PIN, 55 | .clk_source = I2C_CLK_SRC_DEFAULT, 56 | .glitch_ignore_cnt = 7, 57 | .intr_priority = 0, 58 | .trans_queue_depth = 0, 59 | .flags = { 60 | .enable_internal_pullup = 1, 61 | }, 62 | }; 63 | ESP_ERROR_CHECK(i2c_new_master_bus(&bus_config, &display_i2c_bus_)); 64 | } 65 | 66 | void InitializeCodecI2c() { 67 | // Initialize I2C peripheral 68 | i2c_master_bus_config_t i2c_bus_cfg = { 69 | .i2c_port = (i2c_port_t)1, 70 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 71 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 72 | .clk_source = I2C_CLK_SRC_DEFAULT, 73 | .glitch_ignore_cnt = 7, 74 | .intr_priority = 0, 75 | .trans_queue_depth = 0, 76 | .flags = { 77 | .enable_internal_pullup = 1, 78 | }, 79 | }; 80 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); 81 | } 82 | 83 | void InitializeButtons() { 84 | boot_button_.OnClick([this]() { 85 | Application::GetInstance().ToggleChatState(); 86 | }); 87 | 88 | volume_up_button_.OnClick([this]() { 89 | auto codec = GetAudioCodec(); 90 | auto volume = codec->output_volume() + 10; 91 | if (volume > 100) { 92 | volume = 100; 93 | } 94 | codec->SetOutputVolume(volume); 95 | GetDisplay()->ShowNotification("音量 " + std::to_string(volume)); 96 | }); 97 | 98 | volume_up_button_.OnLongPress([this]() { 99 | GetAudioCodec()->SetOutputVolume(100); 100 | GetDisplay()->ShowNotification("最大音量"); 101 | }); 102 | 103 | volume_down_button_.OnClick([this]() { 104 | auto codec = GetAudioCodec(); 105 | auto volume = codec->output_volume() - 10; 106 | if (volume < 0) { 107 | volume = 0; 108 | } 109 | codec->SetOutputVolume(volume); 110 | GetDisplay()->ShowNotification("音量 " + std::to_string(volume)); 111 | }); 112 | 113 | volume_down_button_.OnLongPress([this]() { 114 | GetAudioCodec()->SetOutputVolume(0); 115 | GetDisplay()->ShowNotification("已静音"); 116 | }); 117 | } 118 | 119 | // 物联网初始化,添加对 AI 可见设备 120 | void InitializeIot() { 121 | auto& thing_manager = iot::ThingManager::GetInstance(); 122 | thing_manager.AddThing(iot::CreateThing("Speaker")); 123 | } 124 | 125 | public: 126 | KevinBoxBoard() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096), 127 | boot_button_(BOOT_BUTTON_GPIO), 128 | volume_up_button_(VOLUME_UP_BUTTON_GPIO), 129 | volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { 130 | InitializeDisplayI2c(); 131 | InitializeCodecI2c(); 132 | MountStorage(); 133 | Enable4GModule(); 134 | 135 | InitializeButtons(); 136 | InitializeIot(); 137 | } 138 | 139 | virtual Led* GetLed() override { 140 | static SingleLed led(BUILTIN_LED_GPIO); 141 | return &led; 142 | } 143 | 144 | virtual AudioCodec* GetAudioCodec() override { 145 | static BoxAudioCodec audio_codec(codec_i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 146 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 147 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); 148 | return &audio_codec; 149 | } 150 | 151 | virtual Display* GetDisplay() override { 152 | static Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 153 | return &display; 154 | } 155 | }; 156 | 157 | DECLARE_BOARD(KevinBoxBoard); -------------------------------------------------------------------------------- /main/boards/kevin-box-2/axp2101.cc: -------------------------------------------------------------------------------- 1 | #include "axp2101.h" 2 | #include "board.h" 3 | #include "display.h" 4 | 5 | #include 6 | 7 | #define TAG "Axp2101" 8 | 9 | Axp2101::Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { 10 | // ** EFUSE defaults ** 11 | WriteReg(0x22, 0b110); // PWRON > OFFLEVEL as POWEROFF Source enable 12 | WriteReg(0x27, 0x10); // hold 4s to power off 13 | 14 | WriteReg(0x93, 0x1C); // 配置 aldo2 输出为 3.3V 15 | 16 | uint8_t value = ReadReg(0x90); // XPOWERS_AXP2101_LDO_ONOFF_CTRL0 17 | value = value | 0x02; // set bit 1 (ALDO2) 18 | WriteReg(0x90, value); // and power channels now enabled 19 | 20 | WriteReg(0x64, 0x03); // CV charger voltage setting to 4.2V 21 | 22 | WriteReg(0x61, 0x05); // set Main battery precharge current to 125mA 23 | WriteReg(0x62, 0x0A); // set Main battery charger current to 400mA ( 0x08-200mA, 0x09-300mA, 0x0A-400mA ) 24 | WriteReg(0x63, 0x15); // set Main battery term charge current to 125mA 25 | 26 | WriteReg(0x14, 0x00); // set minimum system voltage to 4.1V (default 4.7V), for poor USB cables 27 | WriteReg(0x15, 0x00); // set input voltage limit to 3.88v, for poor USB cables 28 | WriteReg(0x16, 0x05); // set input current limit to 2000mA 29 | 30 | WriteReg(0x24, 0x01); // set Vsys for PWROFF threshold to 3.2V (default - 2.6V and kill battery) 31 | WriteReg(0x50, 0x14); // set TS pin to EXTERNAL input (not temperature) 32 | } 33 | 34 | int Axp2101::GetBatteryCurrentDirection() { 35 | return (ReadReg(0x01) & 0b01100000) >> 5; 36 | } 37 | 38 | bool Axp2101::IsCharging() { 39 | return GetBatteryCurrentDirection() == 1; 40 | } 41 | 42 | bool Axp2101::IsDischarging() { 43 | return GetBatteryCurrentDirection() == 2; 44 | } 45 | 46 | bool Axp2101::IsChargingDone() { 47 | uint8_t value = ReadReg(0x01); 48 | return (value & 0b00000111) == 0b00000100; 49 | } 50 | 51 | int Axp2101::GetBatteryLevel() { 52 | return ReadReg(0xA4); 53 | } 54 | 55 | void Axp2101::PowerOff() { 56 | uint8_t value = ReadReg(0x10); 57 | value = value | 0x01; 58 | WriteReg(0x10, value); 59 | } 60 | -------------------------------------------------------------------------------- /main/boards/kevin-box-2/axp2101.h: -------------------------------------------------------------------------------- 1 | #ifndef __AXP2101_H__ 2 | #define __AXP2101_H__ 3 | 4 | #include "i2c_device.h" 5 | 6 | class Axp2101 : public I2cDevice { 7 | public: 8 | Axp2101(i2c_master_bus_handle_t i2c_bus, uint8_t addr); 9 | bool IsCharging(); 10 | bool IsDischarging(); 11 | bool IsChargingDone(); 12 | int GetBatteryLevel(); 13 | void PowerOff(); 14 | 15 | private: 16 | int GetBatteryCurrentDirection(); 17 | }; 18 | 19 | #endif 20 | -------------------------------------------------------------------------------- /main/boards/kevin-box-2/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | #define AUDIO_INPUT_REFERENCE true 10 | 11 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_40 12 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_47 13 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_38 14 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_39 15 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_48 16 | 17 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_9 18 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_42 19 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_41 20 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 21 | #define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR 22 | 23 | #define BUILTIN_LED_GPIO GPIO_NUM_3 24 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 25 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_1 26 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_2 27 | 28 | #define DISPLAY_SDA_PIN GPIO_NUM_7 29 | #define DISPLAY_SCL_PIN GPIO_NUM_8 30 | #define DISPLAY_WIDTH 128 31 | #define DISPLAY_HEIGHT 64 32 | #define DISPLAY_MIRROR_X false 33 | #define DISPLAY_MIRROR_Y false 34 | 35 | #define ML307_RX_PIN GPIO_NUM_5 36 | #define ML307_TX_PIN GPIO_NUM_6 37 | 38 | #define AXP2101_I2C_ADDR 0x34 39 | 40 | 41 | #endif // _BOARD_CONFIG_H_ 42 | -------------------------------------------------------------------------------- /main/boards/kevin-c3/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 10 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_12 11 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 12 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 13 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 14 | 15 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_13 16 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 17 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 18 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 19 | 20 | #define BUILTIN_LED_GPIO GPIO_NUM_5 21 | #define BOOT_BUTTON_GPIO GPIO_NUM_6 22 | 23 | 24 | #endif // _BOARD_CONFIG_H_ 25 | -------------------------------------------------------------------------------- /main/boards/kevin-c3/kevin_c3_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/es8311_audio_codec.h" 3 | #include "application.h" 4 | #include "button.h" 5 | #include "config.h" 6 | #include "iot/thing_manager.h" 7 | #include "led/circular_strip.h" 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #define TAG "KevinBoxBoard" 15 | 16 | class KevinBoxBoard : public WifiBoard { 17 | private: 18 | i2c_master_bus_handle_t codec_i2c_bus_; 19 | Button boot_button_; 20 | 21 | void InitializeCodecI2c() { 22 | // Initialize I2C peripheral 23 | i2c_master_bus_config_t i2c_bus_cfg = { 24 | .i2c_port = I2C_NUM_0, 25 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 26 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 27 | .clk_source = I2C_CLK_SRC_DEFAULT, 28 | .glitch_ignore_cnt = 7, 29 | .intr_priority = 0, 30 | .trans_queue_depth = 0, 31 | .flags = { 32 | .enable_internal_pullup = 1, 33 | }, 34 | }; 35 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); 36 | } 37 | 38 | void InitializeButtons() { 39 | boot_button_.OnClick([this]() { 40 | auto& app = Application::GetInstance(); 41 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 42 | ResetWifiConfiguration(); 43 | } 44 | }); 45 | boot_button_.OnPressDown([this]() { 46 | Application::GetInstance().StartListening(); 47 | }); 48 | boot_button_.OnPressUp([this]() { 49 | Application::GetInstance().StopListening(); 50 | }); 51 | } 52 | 53 | // 物联网初始化,添加对 AI 可见设备 54 | void InitializeIot() { 55 | auto& thing_manager = iot::ThingManager::GetInstance(); 56 | thing_manager.AddThing(iot::CreateThing("Speaker")); 57 | } 58 | 59 | public: 60 | KevinBoxBoard() : boot_button_(BOOT_BUTTON_GPIO) { 61 | // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 62 | esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); 63 | 64 | InitializeCodecI2c(); 65 | InitializeButtons(); 66 | InitializeIot(); 67 | } 68 | 69 | virtual Led* GetLed() override { 70 | static CircularStrip led(BUILTIN_LED_GPIO, 8); 71 | return &led; 72 | } 73 | 74 | virtual AudioCodec* GetAudioCodec() override { 75 | static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 76 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 77 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); 78 | return &audio_codec; 79 | } 80 | }; 81 | 82 | DECLARE_BOARD(KevinBoxBoard); 83 | -------------------------------------------------------------------------------- /main/boards/lichuang-c3-dev/README.md: -------------------------------------------------------------------------------- 1 | ## 立创·实战派ESP32-C3开发板 2 | 3 | 1、开发板资料:https://wiki.lckfb.com/zh-hans/szpi-esp32c3 4 | 5 | 2、该开发板 flash 大小为 8MB,编译时注意选择合适的分区表: 6 | 7 | ``` 8 | Partition Table ---> 9 | Partition Table (Custom partition table CSV) ---> 10 | (partitions_4M.csv) Custom partition CSV file 11 | ``` 12 | -------------------------------------------------------------------------------- /main/boards/lichuang-c3-dev/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 80 9 | #define AUDIO_INPUT_REFERENCE true 10 | 11 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 12 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_12 13 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 14 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 15 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_11 16 | 17 | #define AUDIO_CODEC_USE_PCA9557 18 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_13 19 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_0 20 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_1 21 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 22 | #define AUDIO_CODEC_ES7210_ADDR 0x82 23 | 24 | #define BUILTIN_LED_GPIO GPIO_NUM_NC 25 | #define BOOT_BUTTON_GPIO GPIO_NUM_9 26 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 27 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 28 | 29 | #define DISPLAY_SPI_SCK_PIN GPIO_NUM_3 30 | #define DISPLAY_SPI_MOSI_PIN GPIO_NUM_5 31 | #define DISPLAY_DC_PIN GPIO_NUM_6 32 | #define DISPLAY_SPI_CS_PIN GPIO_NUM_4 33 | 34 | #define DISPLAY_WIDTH 320 35 | #define DISPLAY_HEIGHT 240 36 | #define DISPLAY_MIRROR_X true 37 | #define DISPLAY_MIRROR_Y false 38 | #define DISPLAY_SWAP_XY true 39 | 40 | #define DISPLAY_OFFSET_X 0 41 | #define DISPLAY_OFFSET_Y 0 42 | 43 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_2 44 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT true 45 | 46 | 47 | #endif // _BOARD_CONFIG_H_ 48 | -------------------------------------------------------------------------------- /main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/es8311_audio_codec.h" 3 | #include "display/lcd_display.h" 4 | #include "application.h" 5 | #include "button.h" 6 | #include "config.h" 7 | #include "i2c_device.h" 8 | #include "iot/thing_manager.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define TAG "LichuangC3DevBoard" 17 | 18 | class LichuangC3DevBoard : public WifiBoard { 19 | private: 20 | i2c_master_bus_handle_t codec_i2c_bus_; 21 | Button boot_button_; 22 | LcdDisplay* display_; 23 | 24 | void InitializeI2c() { 25 | // Initialize I2C peripheral 26 | i2c_master_bus_config_t i2c_bus_cfg = { 27 | .i2c_port = I2C_NUM_0, 28 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 29 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 30 | .clk_source = I2C_CLK_SRC_DEFAULT, 31 | .glitch_ignore_cnt = 7, 32 | .intr_priority = 0, 33 | .trans_queue_depth = 0, 34 | .flags = { 35 | .enable_internal_pullup = 1, 36 | }, 37 | }; 38 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); 39 | } 40 | 41 | void InitializeSpi() { 42 | spi_bus_config_t buscfg = {}; 43 | buscfg.mosi_io_num = DISPLAY_SPI_MOSI_PIN; 44 | buscfg.miso_io_num = GPIO_NUM_NC; 45 | buscfg.sclk_io_num = DISPLAY_SPI_SCK_PIN; 46 | buscfg.quadwp_io_num = GPIO_NUM_NC; 47 | buscfg.quadhd_io_num = GPIO_NUM_NC; 48 | buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); 49 | ESP_ERROR_CHECK(spi_bus_initialize(SPI2_HOST, &buscfg, SPI_DMA_CH_AUTO)); 50 | } 51 | 52 | void InitializeButtons() { 53 | boot_button_.OnClick([this]() { 54 | auto& app = Application::GetInstance(); 55 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 56 | ResetWifiConfiguration(); 57 | } 58 | }); 59 | boot_button_.OnPressDown([this]() { 60 | Application::GetInstance().StartListening(); 61 | }); 62 | boot_button_.OnPressUp([this]() { 63 | Application::GetInstance().StopListening(); 64 | }); 65 | } 66 | 67 | void InitializeSt7789Display() { 68 | esp_lcd_panel_io_handle_t panel_io = nullptr; 69 | esp_lcd_panel_handle_t panel = nullptr; 70 | // 液晶屏控制IO初始化 71 | ESP_LOGD(TAG, "Install panel IO"); 72 | esp_lcd_panel_io_spi_config_t io_config = {}; 73 | io_config.cs_gpio_num = DISPLAY_SPI_CS_PIN; 74 | io_config.dc_gpio_num = DISPLAY_DC_PIN; 75 | io_config.spi_mode = 2; 76 | io_config.pclk_hz = 80 * 1000 * 1000; 77 | io_config.trans_queue_depth = 10; 78 | io_config.lcd_cmd_bits = 8; 79 | io_config.lcd_param_bits = 8; 80 | ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI2_HOST, &io_config, &panel_io)); 81 | 82 | // 初始化液晶屏驱动芯片ST7789 83 | ESP_LOGD(TAG, "Install LCD driver"); 84 | esp_lcd_panel_dev_config_t panel_config = {}; 85 | panel_config.reset_gpio_num = GPIO_NUM_NC; 86 | panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; 87 | panel_config.bits_per_pixel = 16; 88 | ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); 89 | 90 | esp_lcd_panel_reset(panel); 91 | 92 | esp_lcd_panel_init(panel); 93 | esp_lcd_panel_invert_color(panel, true); 94 | esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); 95 | esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 96 | display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 97 | DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); 98 | } 99 | 100 | // 物联网初始化,添加对 AI 可见设备 101 | void InitializeIot() { 102 | auto& thing_manager = iot::ThingManager::GetInstance(); 103 | thing_manager.AddThing(iot::CreateThing("Speaker")); 104 | } 105 | 106 | public: 107 | LichuangC3DevBoard() : boot_button_(BOOT_BUTTON_GPIO) { 108 | InitializeI2c(); 109 | InitializeSpi(); 110 | InitializeSt7789Display(); 111 | InitializeButtons(); 112 | InitializeIot(); 113 | } 114 | 115 | virtual AudioCodec* GetAudioCodec() override { 116 | static Es8311AudioCodec* audio_codec = nullptr; 117 | if (audio_codec == nullptr) { 118 | audio_codec = new Es8311AudioCodec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 119 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 120 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); 121 | audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME); 122 | } 123 | return audio_codec; 124 | } 125 | 126 | virtual Display* GetDisplay() override { 127 | return display_; 128 | } 129 | }; 130 | 131 | DECLARE_BOARD(LichuangC3DevBoard); 132 | -------------------------------------------------------------------------------- /main/boards/lichuang-dev/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 80 9 | 10 | #define AUDIO_INPUT_REFERENCE true 11 | 12 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_38 13 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_13 14 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_14 15 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_12 16 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_45 17 | 18 | #define AUDIO_CODEC_USE_PCA9557 19 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_1 20 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_2 21 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 22 | #define AUDIO_CODEC_ES7210_ADDR 0x82 23 | 24 | #define BUILTIN_LED_GPIO GPIO_NUM_48 25 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 26 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 27 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 28 | 29 | #define DISPLAY_WIDTH 320 30 | #define DISPLAY_HEIGHT 240 31 | #define DISPLAY_MIRROR_X true 32 | #define DISPLAY_MIRROR_Y false 33 | #define DISPLAY_SWAP_XY true 34 | 35 | #define DISPLAY_OFFSET_X 0 36 | #define DISPLAY_OFFSET_Y 0 37 | 38 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_42 39 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT true 40 | 41 | 42 | #endif // _BOARD_CONFIG_H_ 43 | -------------------------------------------------------------------------------- /main/boards/lichuang-dev/lichuang_dev_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/box_audio_codec.h" 3 | #include "display/lcd_display.h" 4 | #include "application.h" 5 | #include "button.h" 6 | #include "config.h" 7 | #include "i2c_device.h" 8 | #include "iot/thing_manager.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | #define TAG "LichuangDevBoard" 17 | 18 | 19 | class Pca9557 : public I2cDevice { 20 | public: 21 | Pca9557(i2c_master_bus_handle_t i2c_bus, uint8_t addr) : I2cDevice(i2c_bus, addr) { 22 | WriteReg(0x01, 0x03); 23 | WriteReg(0x03, 0xf8); 24 | } 25 | 26 | void SetOutputState(uint8_t bit, uint8_t level) { 27 | uint8_t data = ReadReg(0x01); 28 | data = (data & ~(1 << bit)) | (level << bit); 29 | WriteReg(0x01, data); 30 | } 31 | }; 32 | 33 | 34 | class LichuangDevBoard : public WifiBoard { 35 | private: 36 | i2c_master_bus_handle_t i2c_bus_; 37 | i2c_master_dev_handle_t pca9557_handle_; 38 | Button boot_button_; 39 | LcdDisplay* display_; 40 | Pca9557* pca9557_; 41 | 42 | void InitializeI2c() { 43 | // Initialize I2C peripheral 44 | i2c_master_bus_config_t i2c_bus_cfg = { 45 | .i2c_port = (i2c_port_t)1, 46 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 47 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 48 | .clk_source = I2C_CLK_SRC_DEFAULT, 49 | .glitch_ignore_cnt = 7, 50 | .intr_priority = 0, 51 | .trans_queue_depth = 0, 52 | .flags = { 53 | .enable_internal_pullup = 1, 54 | }, 55 | }; 56 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); 57 | 58 | // Initialize PCA9557 59 | pca9557_ = new Pca9557(i2c_bus_, 0x19); 60 | } 61 | 62 | void InitializeSpi() { 63 | spi_bus_config_t buscfg = {}; 64 | buscfg.mosi_io_num = GPIO_NUM_40; 65 | buscfg.miso_io_num = GPIO_NUM_NC; 66 | buscfg.sclk_io_num = GPIO_NUM_41; 67 | buscfg.quadwp_io_num = GPIO_NUM_NC; 68 | buscfg.quadhd_io_num = GPIO_NUM_NC; 69 | buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); 70 | ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); 71 | } 72 | 73 | void InitializeButtons() { 74 | boot_button_.OnClick([this]() { 75 | auto& app = Application::GetInstance(); 76 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 77 | ResetWifiConfiguration(); 78 | } 79 | }); 80 | boot_button_.OnPressDown([this]() { 81 | Application::GetInstance().StartListening(); 82 | }); 83 | boot_button_.OnPressUp([this]() { 84 | Application::GetInstance().StopListening(); 85 | }); 86 | } 87 | 88 | void InitializeSt7789Display() { 89 | esp_lcd_panel_io_handle_t panel_io = nullptr; 90 | esp_lcd_panel_handle_t panel = nullptr; 91 | // 液晶屏控制IO初始化 92 | ESP_LOGD(TAG, "Install panel IO"); 93 | esp_lcd_panel_io_spi_config_t io_config = {}; 94 | io_config.cs_gpio_num = GPIO_NUM_NC; 95 | io_config.dc_gpio_num = GPIO_NUM_39; 96 | io_config.spi_mode = 2; 97 | io_config.pclk_hz = 80 * 1000 * 1000; 98 | io_config.trans_queue_depth = 10; 99 | io_config.lcd_cmd_bits = 8; 100 | io_config.lcd_param_bits = 8; 101 | ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); 102 | 103 | // 初始化液晶屏驱动芯片ST7789 104 | ESP_LOGD(TAG, "Install LCD driver"); 105 | esp_lcd_panel_dev_config_t panel_config = {}; 106 | panel_config.reset_gpio_num = GPIO_NUM_NC; 107 | panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; 108 | panel_config.bits_per_pixel = 16; 109 | ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); 110 | 111 | esp_lcd_panel_reset(panel); 112 | pca9557_->SetOutputState(0, 0); 113 | 114 | esp_lcd_panel_init(panel); 115 | esp_lcd_panel_invert_color(panel, true); 116 | esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); 117 | esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 118 | display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 119 | DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); 120 | } 121 | 122 | // 物联网初始化,添加对 AI 可见设备 123 | void InitializeIot() { 124 | auto& thing_manager = iot::ThingManager::GetInstance(); 125 | thing_manager.AddThing(iot::CreateThing("Speaker")); 126 | } 127 | 128 | public: 129 | LichuangDevBoard() : boot_button_(BOOT_BUTTON_GPIO) { 130 | InitializeI2c(); 131 | InitializeSpi(); 132 | InitializeSt7789Display(); 133 | InitializeButtons(); 134 | InitializeIot(); 135 | } 136 | 137 | virtual AudioCodec* GetAudioCodec() override { 138 | static BoxAudioCodec* audio_codec = nullptr; 139 | if (audio_codec == nullptr) { 140 | audio_codec = new BoxAudioCodec(i2c_bus_, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 141 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 142 | GPIO_NUM_NC, AUDIO_CODEC_ES8311_ADDR, AUDIO_CODEC_ES7210_ADDR, AUDIO_INPUT_REFERENCE); 143 | audio_codec->SetOutputVolume(AUDIO_DEFAULT_OUTPUT_VOLUME); 144 | } 145 | return audio_codec; 146 | } 147 | 148 | virtual Display* GetDisplay() override { 149 | return display_; 150 | } 151 | }; 152 | 153 | DECLARE_BOARD(LichuangDevBoard); 154 | -------------------------------------------------------------------------------- /main/boards/m5stack-core-s3/README.md: -------------------------------------------------------------------------------- 1 | # 编译配置命令 2 | 3 | **配置编译目标为 ESP32S3:** 4 | 5 | ```bash 6 | idf.py set-target esp32s3 7 | ``` 8 | 9 | **打开 menuconfig:** 10 | 11 | ```bash 12 | idf.py menuconfig 13 | ``` 14 | 15 | **选择板子:** 16 | 17 | ``` 18 | Xiaozhi Assistant -> Board Type -> M5Stack CoreS3 19 | ``` 20 | 21 | **修改 psram 配置:** 22 | 23 | ``` 24 | Component config -> ESP PSRAM -> SPI RAM config -> Mode (QUAD/OCT) -> Quad Mode PSRAM 25 | ``` 26 | 27 | **编译:** 28 | 29 | ```bash 30 | idf.py build 31 | ``` -------------------------------------------------------------------------------- /main/boards/m5stack-core-s3/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | // M5Stack CoreS3 Board configuration 5 | 6 | #include 7 | 8 | #define AUDIO_INPUT_REFERENCE true 9 | #define AUDIO_INPUT_SAMPLE_RATE 24000 10 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 11 | #define AUDIO_DEFAULT_OUTPUT_VOLUME 80 12 | 13 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_0 14 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_33 15 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_34 16 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_14 17 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_13 18 | 19 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_12 20 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_11 21 | #define AUDIO_CODEC_AW88298_ADDR AW88298_CODEC_DEFAULT_ADDR 22 | #define AUDIO_CODEC_ES7210_ADDR ES7210_CODEC_DEFAULT_ADDR 23 | 24 | #define BUILTIN_LED_GPIO GPIO_NUM_NC 25 | #define BOOT_BUTTON_GPIO GPIO_NUM_0 26 | #define VOLUME_UP_BUTTON_GPIO GPIO_NUM_NC 27 | #define VOLUME_DOWN_BUTTON_GPIO GPIO_NUM_NC 28 | 29 | #define DISPLAY_SDA_PIN GPIO_NUM_NC 30 | #define DISPLAY_SCL_PIN GPIO_NUM_NC 31 | #define DISPLAY_WIDTH 320 32 | #define DISPLAY_HEIGHT 240 33 | #define DISPLAY_MIRROR_X false 34 | #define DISPLAY_MIRROR_Y false 35 | #define DISPLAY_SWAP_XY false 36 | 37 | #define DISPLAY_OFFSET_X 0 38 | #define DISPLAY_OFFSET_Y 0 39 | 40 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_NC 41 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT true 42 | 43 | 44 | #endif // _BOARD_CONFIG_H_ 45 | -------------------------------------------------------------------------------- /main/boards/magiclick-2p4/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_8 10 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_11 11 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_9 12 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_10 13 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12 14 | 15 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_4 // pcb v2.4不起作用,适用于2.4A 16 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_5 17 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_6 18 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 19 | 20 | //led power 21 | #define BUILTIN_LED_POWER GPIO_NUM_39 // 低电平有效 22 | #define BUILTIN_LED_POWER_OUTPUT_INVERT true 23 | 24 | #define BUILTIN_LED_NUM 2 25 | #define BUILTIN_LED_GPIO GPIO_NUM_38 26 | 27 | #define BOOT_BUTTON_GPIO GPIO_NUM_21 28 | 29 | // display 30 | #define DISPLAY_SDA_PIN GPIO_NUM_15 31 | #define DISPLAY_SCL_PIN GPIO_NUM_16 32 | #define DISPLAY_CS_PIN GPIO_NUM_17 33 | #define DISPLAY_DC_PIN GPIO_NUM_18 34 | #define DISPLAY_RST_PIN GPIO_NUM_14 35 | 36 | #define DISPLAY_WIDTH 128 37 | #define DISPLAY_HEIGHT 128 38 | #define DISPLAY_MIRROR_X false 39 | #define DISPLAY_MIRROR_Y true 40 | #define DISPLAY_SWAP_XY false 41 | 42 | #define DISPLAY_OFFSET_X 0 43 | #define DISPLAY_OFFSET_Y 0 44 | 45 | #define DISPLAY_BACKLIGHT_PIN GPIO_NUM_13 46 | #define DISPLAY_BACKLIGHT_OUTPUT_INVERT false 47 | 48 | #endif // _BOARD_CONFIG_H_ 49 | -------------------------------------------------------------------------------- /main/boards/magiclick-2p4/magiclick_2p4_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "display/lcd_display.h" 3 | #include "audio_codecs/es8311_audio_codec.h" 4 | #include "application.h" 5 | #include "button.h" 6 | #include "led/single_led.h" 7 | #include "iot/thing_manager.h" 8 | #include "config.h" 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include "esp_lcd_nv3023.h" 15 | #define TAG "magiclick_2p4" 16 | 17 | class magiclick_2p4 : public WifiBoard { 18 | private: 19 | i2c_master_bus_handle_t codec_i2c_bus_; 20 | Button boot_button_; 21 | LcdDisplay* display_; 22 | 23 | void InitializeCodecI2c() { 24 | // Initialize I2C peripheral 25 | i2c_master_bus_config_t i2c_bus_cfg = { 26 | .i2c_port = I2C_NUM_0, 27 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 28 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 29 | .clk_source = I2C_CLK_SRC_DEFAULT, 30 | .glitch_ignore_cnt = 7, 31 | .intr_priority = 0, 32 | .trans_queue_depth = 0, 33 | .flags = { 34 | .enable_internal_pullup = 1, 35 | }, 36 | }; 37 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); 38 | } 39 | 40 | void InitializeButtons() { 41 | boot_button_.OnClick([this]() { 42 | auto& app = Application::GetInstance(); 43 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 44 | ResetWifiConfiguration(); 45 | } 46 | }); 47 | boot_button_.OnPressDown([this]() { 48 | Application::GetInstance().StartListening(); 49 | }); 50 | boot_button_.OnPressUp([this]() { 51 | Application::GetInstance().StopListening(); 52 | }); 53 | } 54 | 55 | void InitializeLedPower() { 56 | // 设置GPIO模式 57 | gpio_reset_pin(BUILTIN_LED_POWER); 58 | gpio_set_direction(BUILTIN_LED_POWER, GPIO_MODE_OUTPUT); 59 | gpio_set_level(BUILTIN_LED_POWER, BUILTIN_LED_POWER_OUTPUT_INVERT ? 0 : 1); 60 | } 61 | 62 | void InitializeSpi() { 63 | spi_bus_config_t buscfg = {}; 64 | buscfg.mosi_io_num = DISPLAY_SDA_PIN; 65 | buscfg.miso_io_num = GPIO_NUM_NC; 66 | buscfg.sclk_io_num = DISPLAY_SCL_PIN; 67 | buscfg.quadwp_io_num = GPIO_NUM_NC; 68 | buscfg.quadhd_io_num = GPIO_NUM_NC; 69 | buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); 70 | ESP_ERROR_CHECK(spi_bus_initialize(SPI3_HOST, &buscfg, SPI_DMA_CH_AUTO)); 71 | } 72 | 73 | void InitializeNv3023Display(){ 74 | esp_lcd_panel_io_handle_t panel_io = nullptr; 75 | esp_lcd_panel_handle_t panel = nullptr; 76 | // 液晶屏控制IO初始化 77 | ESP_LOGD(TAG, "Install panel IO"); 78 | esp_lcd_panel_io_spi_config_t io_config = {}; 79 | io_config.cs_gpio_num = DISPLAY_CS_PIN; 80 | io_config.dc_gpio_num = DISPLAY_DC_PIN; 81 | io_config.spi_mode = 0; 82 | io_config.pclk_hz = 20 * 1000 * 1000; 83 | io_config.trans_queue_depth = 10; 84 | io_config.lcd_cmd_bits = 8; 85 | io_config.lcd_param_bits = 8; 86 | ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io)); 87 | 88 | // 初始化液晶屏驱动芯片NV3023 89 | ESP_LOGD(TAG, "Install LCD driver"); 90 | esp_lcd_panel_dev_config_t panel_config = {}; 91 | panel_config.reset_gpio_num = DISPLAY_RST_PIN; 92 | panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; 93 | panel_config.rgb_endian = LCD_RGB_ENDIAN_RGB; 94 | panel_config.bits_per_pixel = 16; 95 | ESP_ERROR_CHECK(esp_lcd_new_panel_nv3023(panel_io, &panel_config, &panel)); 96 | 97 | esp_lcd_panel_reset(panel); 98 | 99 | esp_lcd_panel_init(panel); 100 | esp_lcd_panel_invert_color(panel, true); 101 | esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); 102 | esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 103 | ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); 104 | display_ = new LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, 105 | DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); 106 | } 107 | 108 | // 物联网初始化,添加对 AI 可见设备 109 | void InitializeIot() { 110 | auto& thing_manager = iot::ThingManager::GetInstance(); 111 | thing_manager.AddThing(iot::CreateThing("Speaker")); 112 | } 113 | 114 | public: 115 | magiclick_2p4() : 116 | boot_button_(BOOT_BUTTON_GPIO) { 117 | InitializeCodecI2c(); 118 | InitializeButtons(); 119 | InitializeLedPower(); 120 | InitializeSpi(); 121 | InitializeNv3023Display(); 122 | InitializeIot(); 123 | } 124 | 125 | virtual Led* GetLed() override { 126 | static SingleLed led(BUILTIN_LED_GPIO); 127 | return &led; 128 | } 129 | 130 | virtual AudioCodec* GetAudioCodec() override { 131 | static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 132 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 133 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); 134 | return &audio_codec; 135 | } 136 | 137 | virtual Display* GetDisplay() override { 138 | return display_; 139 | } 140 | }; 141 | 142 | DECLARE_BOARD(magiclick_2p4); 143 | -------------------------------------------------------------------------------- /main/boards/xmini-c3/config.h: -------------------------------------------------------------------------------- 1 | #ifndef _BOARD_CONFIG_H_ 2 | #define _BOARD_CONFIG_H_ 3 | 4 | #include 5 | 6 | #define AUDIO_INPUT_SAMPLE_RATE 24000 7 | #define AUDIO_OUTPUT_SAMPLE_RATE 24000 8 | 9 | #define AUDIO_I2S_GPIO_MCLK GPIO_NUM_10 10 | #define AUDIO_I2S_GPIO_WS GPIO_NUM_6 11 | #define AUDIO_I2S_GPIO_BCLK GPIO_NUM_8 12 | #define AUDIO_I2S_GPIO_DIN GPIO_NUM_7 13 | #define AUDIO_I2S_GPIO_DOUT GPIO_NUM_5 14 | 15 | #define AUDIO_CODEC_PA_PIN GPIO_NUM_11 16 | #define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_3 17 | #define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_4 18 | #define AUDIO_CODEC_ES8311_ADDR ES8311_CODEC_DEFAULT_ADDR 19 | 20 | #define BUILTIN_LED_GPIO GPIO_NUM_2 21 | #define BOOT_BUTTON_GPIO GPIO_NUM_9 22 | 23 | #define DISPLAY_WIDTH 128 24 | #define DISPLAY_HEIGHT 64 25 | #define DISPLAY_MIRROR_X true 26 | #define DISPLAY_MIRROR_Y true 27 | 28 | #endif // _BOARD_CONFIG_H_ 29 | -------------------------------------------------------------------------------- /main/boards/xmini-c3/xmini_c3_board.cc: -------------------------------------------------------------------------------- 1 | #include "wifi_board.h" 2 | #include "audio_codecs/es8311_audio_codec.h" 3 | #include "display/ssd1306_display.h" 4 | #include "application.h" 5 | #include "button.h" 6 | #include "config.h" 7 | #include "iot/thing_manager.h" 8 | #include "led/single_led.h" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define TAG "XminiC3Board" 16 | 17 | class XminiC3Board : public WifiBoard { 18 | private: 19 | i2c_master_bus_handle_t codec_i2c_bus_; 20 | Button boot_button_; 21 | 22 | void InitializeCodecI2c() { 23 | // Initialize I2C peripheral 24 | i2c_master_bus_config_t i2c_bus_cfg = { 25 | .i2c_port = I2C_NUM_0, 26 | .sda_io_num = AUDIO_CODEC_I2C_SDA_PIN, 27 | .scl_io_num = AUDIO_CODEC_I2C_SCL_PIN, 28 | .clk_source = I2C_CLK_SRC_DEFAULT, 29 | .glitch_ignore_cnt = 7, 30 | .intr_priority = 0, 31 | .trans_queue_depth = 0, 32 | .flags = { 33 | .enable_internal_pullup = 1, 34 | }, 35 | }; 36 | ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_)); 37 | } 38 | 39 | void InitializeButtons() { 40 | boot_button_.OnClick([this]() { 41 | auto& app = Application::GetInstance(); 42 | if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { 43 | ResetWifiConfiguration(); 44 | } 45 | app.ToggleChatState(); 46 | }); 47 | boot_button_.OnPressDown([this]() { 48 | Application::GetInstance().StartListening(); 49 | }); 50 | boot_button_.OnPressUp([this]() { 51 | Application::GetInstance().StopListening(); 52 | }); 53 | } 54 | 55 | // 物联网初始化,添加对 AI 可见设备 56 | void InitializeIot() { 57 | auto& thing_manager = iot::ThingManager::GetInstance(); 58 | thing_manager.AddThing(iot::CreateThing("Speaker")); 59 | } 60 | 61 | public: 62 | XminiC3Board() : boot_button_(BOOT_BUTTON_GPIO) { 63 | // 把 ESP32C3 的 VDD SPI 引脚作为普通 GPIO 口使用 64 | esp_efuse_write_field_bit(ESP_EFUSE_VDD_SPI_AS_GPIO); 65 | 66 | InitializeCodecI2c(); 67 | InitializeButtons(); 68 | InitializeIot(); 69 | } 70 | 71 | virtual Led* GetLed() override { 72 | static SingleLed led_strip(BUILTIN_LED_GPIO); 73 | return &led_strip; 74 | } 75 | 76 | virtual Display* GetDisplay() override { 77 | static Ssd1306Display display(codec_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); 78 | return &display; 79 | } 80 | 81 | virtual AudioCodec* GetAudioCodec() override { 82 | static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, 83 | AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK, AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN, 84 | AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); 85 | return &audio_codec; 86 | } 87 | }; 88 | 89 | DECLARE_BOARD(XminiC3Board); 90 | -------------------------------------------------------------------------------- /main/display/display.h: -------------------------------------------------------------------------------- 1 | #ifndef DISPLAY_H 2 | #define DISPLAY_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | 10 | class Display { 11 | public: 12 | Display(); 13 | virtual ~Display(); 14 | 15 | virtual void SetStatus(const std::string &status); 16 | virtual void ShowNotification(const std::string ¬ification, int duration_ms = 3000); 17 | virtual void SetEmotion(const std::string &emotion); 18 | virtual void SetChatMessage(const std::string &role, const std::string &content); 19 | virtual void SetIcon(const char* icon); 20 | 21 | int width() const { return width_; } 22 | int height() const { return height_; } 23 | 24 | protected: 25 | int width_ = 0; 26 | int height_ = 0; 27 | 28 | lv_disp_t *disp_ = nullptr; 29 | 30 | lv_obj_t *emotion_label_ = nullptr; 31 | lv_obj_t *network_label_ = nullptr; 32 | lv_obj_t *status_label_ = nullptr; 33 | lv_obj_t *notification_label_ = nullptr; 34 | lv_obj_t *mute_label_ = nullptr; 35 | lv_obj_t *battery_label_ = nullptr; 36 | const char* battery_icon_ = nullptr; 37 | const char* network_icon_ = nullptr; 38 | bool muted_ = false; 39 | 40 | esp_timer_handle_t notification_timer_ = nullptr; 41 | esp_timer_handle_t update_timer_ = nullptr; 42 | 43 | friend class DisplayLockGuard; 44 | virtual bool Lock(int timeout_ms = 0) = 0; 45 | virtual void Unlock() = 0; 46 | 47 | virtual void Update(); 48 | }; 49 | 50 | 51 | class DisplayLockGuard { 52 | public: 53 | DisplayLockGuard(Display *display) : display_(display) { 54 | display_->Lock(); 55 | } 56 | ~DisplayLockGuard() { 57 | display_->Unlock(); 58 | } 59 | 60 | private: 61 | Display *display_; 62 | }; 63 | 64 | #endif 65 | -------------------------------------------------------------------------------- /main/display/lcd_display.h: -------------------------------------------------------------------------------- 1 | #ifndef LCD_DISPLAY_H 2 | #define LCD_DISPLAY_H 3 | 4 | #include "display.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | class LcdDisplay : public Display { 14 | protected: 15 | esp_lcd_panel_io_handle_t panel_io_ = nullptr; 16 | esp_lcd_panel_handle_t panel_ = nullptr; 17 | gpio_num_t backlight_pin_ = GPIO_NUM_NC; 18 | bool backlight_output_invert_ = false; 19 | bool mirror_x_ = false; 20 | bool mirror_y_ = false; 21 | bool swap_xy_ = false; 22 | int offset_x_ = 0; 23 | int offset_y_ = 0; 24 | SemaphoreHandle_t lvgl_mutex_ = nullptr; 25 | esp_timer_handle_t lvgl_tick_timer_ = nullptr; 26 | 27 | lv_obj_t* status_bar_ = nullptr; 28 | lv_obj_t* content_ = nullptr; 29 | lv_obj_t* container_ = nullptr; 30 | lv_obj_t* side_bar_ = nullptr; 31 | lv_obj_t* chat_message_label_ = nullptr; 32 | 33 | void InitializeBacklight(gpio_num_t backlight_pin); 34 | void SetBacklight(uint8_t brightness); 35 | void LvglTask(); 36 | 37 | virtual void SetupUI(); 38 | virtual bool Lock(int timeout_ms = 0) override; 39 | virtual void Unlock() override; 40 | 41 | public: 42 | LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, 43 | gpio_num_t backlight_pin, bool backlight_output_invert, 44 | int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy); 45 | ~LcdDisplay(); 46 | 47 | void SetChatMessage(const std::string &role, const std::string &content) override; 48 | }; 49 | 50 | #endif // LCD_DISPLAY_H 51 | -------------------------------------------------------------------------------- /main/display/no_display.cc: -------------------------------------------------------------------------------- 1 | #include "no_display.h" 2 | 3 | NoDisplay::NoDisplay() {} 4 | 5 | NoDisplay::~NoDisplay() {} 6 | 7 | bool NoDisplay::Lock(int timeout_ms) { 8 | return true; 9 | } 10 | 11 | void NoDisplay::Unlock() {} 12 | -------------------------------------------------------------------------------- /main/display/no_display.h: -------------------------------------------------------------------------------- 1 | #ifndef _NO_DISPLAY_H_ 2 | #define _NO_DISPLAY_H_ 3 | 4 | #include "display.h" 5 | 6 | class NoDisplay : public Display { 7 | private: 8 | virtual bool Lock(int timeout_ms = 0) override; 9 | virtual void Unlock() override; 10 | 11 | public: 12 | NoDisplay(); 13 | ~NoDisplay(); 14 | }; 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /main/display/ssd1306_display.h: -------------------------------------------------------------------------------- 1 | #ifndef SSD1306_DISPLAY_H 2 | #define SSD1306_DISPLAY_H 3 | 4 | #include "display.h" 5 | 6 | #include 7 | #include 8 | 9 | class Ssd1306Display : public Display { 10 | private: 11 | esp_lcd_panel_io_handle_t panel_io_ = nullptr; 12 | esp_lcd_panel_handle_t panel_ = nullptr; 13 | bool mirror_x_ = false; 14 | bool mirror_y_ = false; 15 | 16 | lv_obj_t* status_bar_ = nullptr; 17 | lv_obj_t* content_ = nullptr; 18 | lv_obj_t* container_ = nullptr; 19 | lv_obj_t* side_bar_ = nullptr; 20 | 21 | virtual bool Lock(int timeout_ms = 0) override; 22 | virtual void Unlock() override; 23 | 24 | void SetupUI_128x64(); 25 | void SetupUI_128x32(); 26 | 27 | public: 28 | Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x = false, bool mirror_y = false); 29 | ~Ssd1306Display(); 30 | }; 31 | 32 | #endif // SSD1306_DISPLAY_H 33 | -------------------------------------------------------------------------------- /main/fonts/font_awesome_symbols.h: -------------------------------------------------------------------------------- 1 | #ifndef FONT_AWESOME_SYMBOLS_H 2 | #define FONT_AWESOME_SYMBOLS_H 3 | 4 | #define FONT_AWESOME_EMOJI_NEUTRAL "\xef\x96\xa4" 5 | #define FONT_AWESOME_EMOJI_HAPPY "\xef\x84\x98" 6 | #define FONT_AWESOME_EMOJI_LAUGHING "\xef\x96\x9b" 7 | #define FONT_AWESOME_EMOJI_FUNNY "\xef\x96\x88" 8 | #define FONT_AWESOME_EMOJI_SAD "\xee\x8e\x84" 9 | #define FONT_AWESOME_EMOJI_ANGRY "\xef\x95\x96" 10 | #define FONT_AWESOME_EMOJI_CRYING "\xef\x96\xb3" 11 | #define FONT_AWESOME_EMOJI_LOVING "\xef\x96\x84" 12 | #define FONT_AWESOME_EMOJI_EMBARRASSED "\xef\x95\xb9" 13 | #define FONT_AWESOME_EMOJI_SURPRISED "\xee\x8d\xab" 14 | #define FONT_AWESOME_EMOJI_SHOCKED "\xee\x8d\xb5" 15 | #define FONT_AWESOME_EMOJI_THINKING "\xee\x8e\x9b" 16 | #define FONT_AWESOME_EMOJI_WINKING "\xef\x93\x9a" 17 | #define FONT_AWESOME_EMOJI_COOL "\xee\x8e\x98" 18 | #define FONT_AWESOME_EMOJI_RELAXED "\xee\x8e\x92" 19 | #define FONT_AWESOME_EMOJI_DELICIOUS "\xee\x8d\xb2" 20 | #define FONT_AWESOME_EMOJI_KISSY "\xef\x96\x98" 21 | #define FONT_AWESOME_EMOJI_CONFIDENT "\xee\x90\x89" 22 | #define FONT_AWESOME_EMOJI_SLEEPY "\xee\x8e\x8d" 23 | #define FONT_AWESOME_EMOJI_SILLY "\xee\x8e\xa4" 24 | #define FONT_AWESOME_EMOJI_CONFUSED "\xee\x8d\xad" 25 | #define FONT_AWESOME_BATTERY_FULL "\xef\x89\x80" 26 | #define FONT_AWESOME_BATTERY_3 "\xef\x89\x81" 27 | #define FONT_AWESOME_BATTERY_2 "\xef\x89\x82" 28 | #define FONT_AWESOME_BATTERY_1 "\xef\x89\x83" 29 | #define FONT_AWESOME_BATTERY_EMPTY "\xef\x89\x84" 30 | #define FONT_AWESOME_BATTERY_SLASH "\xef\x8d\xb7" 31 | #define FONT_AWESOME_BATTERY_CHARGING "\xef\x8d\xb6" 32 | #define FONT_AWESOME_WIFI "\xef\x87\xab" 33 | #define FONT_AWESOME_WIFI_FAIR "\xef\x9a\xab" 34 | #define FONT_AWESOME_WIFI_WEAK "\xef\x9a\xaa" 35 | #define FONT_AWESOME_WIFI_OFF "\xef\x9a\xac" 36 | #define FONT_AWESOME_SIGNAL_FULL "\xef\x80\x92" 37 | #define FONT_AWESOME_SIGNAL_4 "\xef\x9a\x8f" 38 | #define FONT_AWESOME_SIGNAL_3 "\xef\x9a\x8e" 39 | #define FONT_AWESOME_SIGNAL_2 "\xef\x9a\x8d" 40 | #define FONT_AWESOME_SIGNAL_1 "\xef\x9a\x8c" 41 | #define FONT_AWESOME_SIGNAL_OFF "\xef\x9a\x95" 42 | #define FONT_AWESOME_VOLUME_HIGH "\xef\x80\xa8" 43 | #define FONT_AWESOME_VOLUME_MEDIUM "\xef\x9a\xa8" 44 | #define FONT_AWESOME_VOLUME_LOW "\xef\x80\xa7" 45 | #define FONT_AWESOME_VOLUME_MUTE "\xef\x9a\xa9" 46 | #define FONT_AWESOME_MUSIC "\xef\x80\x81" 47 | #define FONT_AWESOME_CHECK "\xef\x80\x8c" 48 | #define FONT_AWESOME_XMARK "\xef\x80\x8d" 49 | #define FONT_AWESOME_POWER "\xef\x80\x91" 50 | #define FONT_AWESOME_GEAR "\xef\x80\x93" 51 | #define FONT_AWESOME_TRASH "\xef\x87\xb8" 52 | #define FONT_AWESOME_HOME "\xef\x80\x95" 53 | #define FONT_AWESOME_IMAGE "\xef\x80\xbe" 54 | #define FONT_AWESOME_EDIT "\xef\x81\x84" 55 | #define FONT_AWESOME_PREV "\xef\x81\x88" 56 | #define FONT_AWESOME_NEXT "\xef\x81\x91" 57 | #define FONT_AWESOME_PLAY "\xef\x81\x8b" 58 | #define FONT_AWESOME_PAUSE "\xef\x81\x8c" 59 | #define FONT_AWESOME_STOP "\xef\x81\x8d" 60 | #define FONT_AWESOME_MICARROW_LEFT "\xef\x81\xa0" 61 | #define FONT_AWESOME_ARROW_RIGHT "\xef\x81\xa1" 62 | #define FONT_AWESOME_ARROW_UP "\xef\x81\xa2" 63 | #define FONT_AWESOME_ARROW_DOWN "\xef\x81\xa3" 64 | #define FONT_AWESOME_WARNING "\xef\x81\xb1" 65 | #define FONT_AWESOME_BELL "\xef\x83\xb3" 66 | #define FONT_AWESOME_LOCATION "\xef\x8f\x85" 67 | #define FONT_AWESOME_GLOBE "\xef\x82\xac" 68 | #define FONT_AWESOME_LOCATION_ARROW "\xef\x84\xa4" 69 | #define FONT_AWESOME_SD_CARD "\xef\x9f\x82" 70 | #define FONT_AWESOME_BLUETOOTH "\xef\x8a\x93" 71 | #define FONT_AWESOME_COMMENT "\xef\x81\xb5" 72 | #define FONT_AWESOME_AI_CHIP "\xee\x87\xac" 73 | #define FONT_AWESOME_USER "\xef\x80\x87" 74 | #define FONT_AWESOME_USER_ROBOT "\xee\x81\x8b" 75 | #define FONT_AWESOME_DOWNLOAD "\xef\x80\x99" 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /main/idf_component.yml: -------------------------------------------------------------------------------- 1 | ## IDF Component Manager Manifest File 2 | dependencies: 3 | waveshare/esp_lcd_sh8601: 4 | version: "1.0.2" 5 | public: true 6 | espressif/esp_lcd_ili9341: "==1.2.0" 7 | espressif/esp_lcd_gc9a01: "^2.0.1" 8 | 78/esp_lcd_nv3023: "~1.0.0" 9 | 78/esp-wifi-connect: "~2.0.2" 10 | 78/esp-opus-encoder: "~2.1.0" 11 | 78/esp-ml307: "~1.7.1" 12 | espressif/led_strip: "^2.4.1" 13 | espressif/esp_codec_dev: "~1.3.2" 14 | espressif/esp-sr: "^1.9.0" 15 | espressif/button: "^3.3.1" 16 | lvgl/lvgl: "~8.4.0" 17 | esp_lvgl_port: "~2.4.1" 18 | ## Required IDF version 19 | idf: 20 | version: ">=5.3" 21 | 22 | -------------------------------------------------------------------------------- /main/iot/sample_interface.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "tank", 4 | "description": "A tank", 5 | "properties": { 6 | "power": { 7 | "type": "boolean", 8 | "description": "Whether the tank is on or off" 9 | }, 10 | "direction": { 11 | "type": "int", 12 | "description": "Tank direction" 13 | }, 14 | "speed": { 15 | "type": "int", 16 | "description": "Tank speed" 17 | } 18 | }, 19 | "methods": { 20 | "TurnOn": { 21 | "description": "Turns the tank LED on" 22 | }, 23 | "GoForward": { 24 | "description": "Go forward" 25 | }, 26 | "GoBack": { 27 | "description": "Go back" 28 | }, 29 | "GoLeft": { 30 | "description": "Go left" 31 | }, 32 | "GoRight": { 33 | "description": "Go right" 34 | }, 35 | "GoDance": { 36 | "description": "Go dance" 37 | }, 38 | "SetSpeed": { 39 | "description": "Tank speed", 40 | "parameters": { 41 | "speed": { 42 | "type": "number", 43 | "description": "The speed of the tank" 44 | } 45 | } 46 | }, 47 | "SetBrightness": { 48 | "description": "Tank speed", 49 | "parameters": { 50 | "brightness": { 51 | "type": "number", 52 | "description": "The brightness of the tank light" 53 | } 54 | } 55 | }, 56 | "LightShow": { 57 | "description": "Tank light show" 58 | } 59 | } 60 | }, 61 | { 62 | "name": "lamp", 63 | "description": "A lamp", 64 | "properties": { 65 | "power": { 66 | "type": "boolean", 67 | "description": "Whether the lamp is on or off" 68 | } 69 | }, 70 | "methods": { 71 | "TurnOn": { 72 | "description": "Turns the lamp on" 73 | } 74 | } 75 | }, 76 | { 77 | "name": "speaker", 78 | "description": "当前 AI 机器人的扬声器", 79 | "properties": { 80 | "volume": { 81 | "type": "number", 82 | "description": "当前扬声器的音量(0-100)" 83 | } 84 | }, 85 | "methods": { 86 | "SetVolume": { 87 | "description": "设置当前扬声器的音量", 88 | "parameters": { 89 | "volume": { 90 | "type": "number", 91 | "description": "The volume of the speaker (0-100)" 92 | } 93 | } 94 | } 95 | } 96 | } 97 | ] -------------------------------------------------------------------------------- /main/iot/thing.cc: -------------------------------------------------------------------------------- 1 | #include "thing.h" 2 | #include "application.h" 3 | 4 | #include 5 | 6 | #define TAG "Thing" 7 | 8 | 9 | namespace iot { 10 | 11 | static std::map>* thing_creators = nullptr; 12 | 13 | void RegisterThing(const std::string& type, std::function creator) { 14 | if (thing_creators == nullptr) { 15 | thing_creators = new std::map>(); 16 | } 17 | (*thing_creators)[type] = creator; 18 | } 19 | 20 | Thing* CreateThing(const std::string& type) { 21 | auto creator = thing_creators->find(type); 22 | if (creator == thing_creators->end()) { 23 | ESP_LOGE(TAG, "Thing type not found: %s", type.c_str()); 24 | return nullptr; 25 | } 26 | return creator->second(); 27 | } 28 | 29 | std::string Thing::GetDescriptorJson() { 30 | std::string json_str = "{"; 31 | json_str += "\"name\":\"" + name_ + "\","; 32 | json_str += "\"description\":\"" + description_ + "\","; 33 | json_str += "\"properties\":" + properties_.GetDescriptorJson() + ","; 34 | json_str += "\"methods\":" + methods_.GetDescriptorJson(); 35 | json_str += "}"; 36 | return json_str; 37 | } 38 | 39 | std::string Thing::GetStateJson() { 40 | std::string json_str = "{"; 41 | json_str += "\"name\":\"" + name_ + "\","; 42 | json_str += "\"state\":" + properties_.GetStateJson(); 43 | json_str += "}"; 44 | return json_str; 45 | } 46 | 47 | void Thing::Invoke(const cJSON* command) { 48 | auto method_name = cJSON_GetObjectItem(command, "method"); 49 | auto input_params = cJSON_GetObjectItem(command, "parameters"); 50 | 51 | try { 52 | auto& method = methods_[method_name->valuestring]; 53 | for (auto& param : method.parameters()) { 54 | auto input_param = cJSON_GetObjectItem(input_params, param.name().c_str()); 55 | if (param.required() && input_param == nullptr) { 56 | throw std::runtime_error("Parameter " + param.name() + " is required"); 57 | } 58 | if (param.type() == kValueTypeNumber) { 59 | param.set_number(input_param->valueint); 60 | } else if (param.type() == kValueTypeString) { 61 | param.set_string(input_param->valuestring); 62 | } else if (param.type() == kValueTypeBoolean) { 63 | param.set_boolean(input_param->valueint == 1); 64 | } 65 | } 66 | 67 | Application::GetInstance().Schedule([&method]() { 68 | method.Invoke(); 69 | }); 70 | } catch (const std::runtime_error& e) { 71 | ESP_LOGE(TAG, "Method not found: %s", method_name->valuestring); 72 | return; 73 | } 74 | } 75 | 76 | 77 | } // namespace iot 78 | -------------------------------------------------------------------------------- /main/iot/thing_manager.cc: -------------------------------------------------------------------------------- 1 | #include "thing_manager.h" 2 | 3 | #include 4 | 5 | #define TAG "ThingManager" 6 | 7 | namespace iot { 8 | 9 | void ThingManager::AddThing(Thing* thing) { 10 | things_.push_back(thing); 11 | } 12 | 13 | std::string ThingManager::GetDescriptorsJson() { 14 | std::string json_str = "["; 15 | for (auto& thing : things_) { 16 | json_str += thing->GetDescriptorJson() + ","; 17 | } 18 | if (json_str.back() == ',') { 19 | json_str.pop_back(); 20 | } 21 | json_str += "]"; 22 | return json_str; 23 | } 24 | 25 | std::string ThingManager::GetStatesJson() { 26 | std::string json_str = "["; 27 | for (auto& thing : things_) { 28 | json_str += thing->GetStateJson() + ","; 29 | } 30 | if (json_str.back() == ',') { 31 | json_str.pop_back(); 32 | } 33 | json_str += "]"; 34 | return json_str; 35 | } 36 | 37 | void ThingManager::Invoke(const cJSON* command) { 38 | auto name = cJSON_GetObjectItem(command, "name"); 39 | for (auto& thing : things_) { 40 | if (thing->name() == name->valuestring) { 41 | thing->Invoke(command); 42 | return; 43 | } 44 | } 45 | } 46 | 47 | } // namespace iot 48 | -------------------------------------------------------------------------------- /main/iot/thing_manager.h: -------------------------------------------------------------------------------- 1 | #ifndef THING_MANAGER_H 2 | #define THING_MANAGER_H 3 | 4 | 5 | #include "thing.h" 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | namespace iot { 15 | 16 | class ThingManager { 17 | public: 18 | static ThingManager& GetInstance() { 19 | static ThingManager instance; 20 | return instance; 21 | } 22 | ThingManager(const ThingManager&) = delete; 23 | ThingManager& operator=(const ThingManager&) = delete; 24 | 25 | void AddThing(Thing* thing); 26 | 27 | std::string GetDescriptorsJson(); 28 | std::string GetStatesJson(); 29 | void Invoke(const cJSON* command); 30 | 31 | private: 32 | ThingManager() = default; 33 | ~ThingManager() = default; 34 | 35 | std::vector things_; 36 | }; 37 | 38 | 39 | } // namespace iot 40 | 41 | #endif // THING_MANAGER_H 42 | -------------------------------------------------------------------------------- /main/iot/things/lamp.cc: -------------------------------------------------------------------------------- 1 | #include "iot/thing.h" 2 | #include "board.h" 3 | #include "audio_codec.h" 4 | 5 | #include 6 | #include 7 | 8 | #define TAG "Lamp" 9 | 10 | namespace iot { 11 | 12 | // 这里仅定义 Lamp 的属性和方法,不包含具体的实现 13 | class Lamp : public Thing { 14 | private: 15 | gpio_num_t gpio_num_ = GPIO_NUM_18; 16 | bool power_ = false; 17 | 18 | void InitializeGpio() { 19 | gpio_config_t config = { 20 | .pin_bit_mask = (1ULL << gpio_num_), 21 | .mode = GPIO_MODE_OUTPUT, 22 | .pull_up_en = GPIO_PULLUP_DISABLE, 23 | .pull_down_en = GPIO_PULLDOWN_DISABLE, 24 | .intr_type = GPIO_INTR_DISABLE, 25 | }; 26 | ESP_ERROR_CHECK(gpio_config(&config)); 27 | gpio_set_level(gpio_num_, 0); 28 | } 29 | 30 | public: 31 | Lamp() : Thing("Lamp", "一个测试用的灯"), power_(false) { 32 | InitializeGpio(); 33 | 34 | // 定义设备的属性 35 | properties_.AddBooleanProperty("power", "灯是否打开", [this]() -> bool { 36 | return power_; 37 | }); 38 | 39 | // 定义设备可以被远程执行的指令 40 | methods_.AddMethod("TurnOn", "打开灯", ParameterList(), [this](const ParameterList& parameters) { 41 | power_ = true; 42 | gpio_set_level(gpio_num_, 1); 43 | }); 44 | 45 | methods_.AddMethod("TurnOff", "关闭灯", ParameterList(), [this](const ParameterList& parameters) { 46 | power_ = false; 47 | gpio_set_level(gpio_num_, 0); 48 | }); 49 | } 50 | }; 51 | 52 | } // namespace iot 53 | 54 | DECLARE_THING(Lamp); 55 | -------------------------------------------------------------------------------- /main/iot/things/speaker.cc: -------------------------------------------------------------------------------- 1 | #include "iot/thing.h" 2 | #include "board.h" 3 | #include "audio_codec.h" 4 | 5 | #include 6 | 7 | #define TAG "Speaker" 8 | 9 | namespace iot { 10 | 11 | // 这里仅定义 Speaker 的属性和方法,不包含具体的实现 12 | class Speaker : public Thing { 13 | public: 14 | Speaker() : Thing("Speaker", "当前 AI 机器人的扬声器") { 15 | // 定义设备的属性 16 | properties_.AddNumberProperty("volume", "当前音量值", [this]() -> int { 17 | auto codec = Board::GetInstance().GetAudioCodec(); 18 | return codec->output_volume(); 19 | }); 20 | 21 | // 定义设备可以被远程执行的指令 22 | methods_.AddMethod("SetVolume", "设置音量", ParameterList({ 23 | Parameter("volume", "0到100之间的整数", kValueTypeNumber, true) 24 | }), [this](const ParameterList& parameters) { 25 | auto codec = Board::GetInstance().GetAudioCodec(); 26 | codec->SetOutputVolume(static_cast(parameters["volume"].number())); 27 | }); 28 | } 29 | }; 30 | 31 | } // namespace iot 32 | 33 | DECLARE_THING(Speaker); 34 | -------------------------------------------------------------------------------- /main/iot/things/tank.cc: -------------------------------------------------------------------------------- 1 | #include "iot/thing.h" 2 | #include "board.h" 3 | #include "audio_codec.h" 4 | 5 | #include 6 | #include 7 | #include "tracked_chassis_control.h" 8 | 9 | #define TAG "Tank" 10 | 11 | namespace iot { 12 | 13 | // 这里仅定义 Tank 的属性和方法,不包含具体的实现 14 | class Tank : public Thing { 15 | private: 16 | bool power_ = false; 17 | int direction_ = 0; 18 | int speed_ = 0; 19 | 20 | 21 | public: 22 | Tank() : Thing("Tank", "坦克控制"), power_(false) { 23 | tracked_chassis_control_start(); 24 | // 定义设备的属性 25 | properties_.AddBooleanProperty("power", "坦克灯光是否打开", [this]() -> bool { 26 | return power_; 27 | }); 28 | properties_.AddNumberProperty("speed", "坦克运行速度", [this]() -> int { 29 | return speed_; 30 | }); 31 | 32 | // 定义设备可以被远程执行的指令 33 | methods_.AddMethod("TurnOn", "打开坦克灯光", ParameterList(), [this](const ParameterList& parameters) { 34 | power_ = true; 35 | tracked_chassis_rgb_light_control(2); 36 | }); 37 | 38 | methods_.AddMethod("TurnOff", "关闭坦克灯光", ParameterList(), [this](const ParameterList& parameters) { 39 | power_ = false; 40 | tracked_chassis_rgb_light_control(8); 41 | }); 42 | methods_.AddMethod("GoForward", "向前", ParameterList(), [this](const ParameterList& parameters) { 43 | direction_ = 1; 44 | tracked_chassis_motion_control("x0.0y1.0"); 45 | vTaskDelay(500 / portTICK_PERIOD_MS); 46 | tracked_chassis_motion_control("x0.0y0.0"); 47 | }); 48 | methods_.AddMethod("GoBack", "向后", ParameterList(), [this](const ParameterList& parameters) { 49 | direction_ = 2; 50 | tracked_chassis_motion_control("x0.0y-1.0"); 51 | vTaskDelay(500 / portTICK_PERIOD_MS); 52 | tracked_chassis_motion_control("x0.0y0.0"); 53 | }); 54 | 55 | methods_.AddMethod("GoLeft", "向左", ParameterList(), [this](const ParameterList& parameters) { 56 | direction_ = 3; 57 | tracked_chassis_motion_control("x-1.0y0.0"); 58 | vTaskDelay(600 / portTICK_PERIOD_MS); 59 | tracked_chassis_motion_control("x0.0y0.0"); 60 | }); 61 | methods_.AddMethod("GoRight", "向右", ParameterList(), [this](const ParameterList& parameters) { 62 | direction_ = 4; 63 | tracked_chassis_motion_control("x1.0y0.0"); 64 | vTaskDelay(600 / portTICK_PERIOD_MS); 65 | tracked_chassis_motion_control("x0.0y0.0"); 66 | }); 67 | methods_.AddMethod("GoDance", "跳个舞", ParameterList(), [this](const ParameterList& parameters) { 68 | tracked_chassis_set_dance_mode(1); 69 | }); 70 | 71 | methods_.AddMethod("SetSpeed", "设置坦克运行速度", ParameterList({ 72 | Parameter("speed", "1到100之间的整数", kValueTypeNumber, true) 73 | }), [this](const ParameterList& parameters) { 74 | int speed = static_cast(parameters["speed"].number()); 75 | ESP_LOGI(TAG,"速度->%d",speed); 76 | }); 77 | 78 | methods_.AddMethod("LightShow", "灯光秀", ParameterList(), [this](const ParameterList& parameters) { 79 | tracked_chassis_rgb_light_control(7); 80 | vTaskDelay(10000 / portTICK_PERIOD_MS); 81 | tracked_chassis_rgb_light_control(8); 82 | }); 83 | methods_.AddMethod("SetBrightness", "设置坦克灯光亮度", ParameterList({ 84 | Parameter("brightness", "1到100之间的整数", kValueTypeNumber, true) 85 | }), [this](const ParameterList& parameters) { 86 | int brightness = static_cast(parameters["brightness"].number()); 87 | ESP_LOGI(TAG,"亮度->%d",brightness); 88 | }); 89 | } 90 | }; 91 | 92 | } // namespace iot 93 | 94 | DECLARE_THING(Tank); 95 | -------------------------------------------------------------------------------- /main/led/circular_strip.h: -------------------------------------------------------------------------------- 1 | #ifndef _CIRCULAR_STRIP_H_ 2 | #define _CIRCULAR_STRIP_H_ 3 | 4 | #include "led.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | struct StripColor { 13 | uint8_t red = 0, green = 0, blue = 0; 14 | }; 15 | 16 | class CircularStrip : public Led { 17 | public: 18 | CircularStrip(gpio_num_t gpio, uint8_t max_leds); 19 | virtual ~CircularStrip(); 20 | 21 | void OnStateChanged() override; 22 | 23 | private: 24 | std::mutex mutex_; 25 | TaskHandle_t blink_task_ = nullptr; 26 | led_strip_handle_t led_strip_ = nullptr; 27 | int max_leds_ = 0; 28 | std::vector colors_; 29 | int blink_counter_ = 0; 30 | int blink_interval_ms_ = 0; 31 | esp_timer_handle_t strip_timer_ = nullptr; 32 | std::function strip_callback_ = nullptr; 33 | 34 | void StartStripTask(int interval_ms, std::function cb); 35 | 36 | void StaticColor(StripColor color); 37 | void Blink(StripColor color, int interval_ms); 38 | void Breathe(StripColor low, StripColor high, int interval_ms); 39 | void Rainbow(StripColor low, StripColor high, int interval_ms); 40 | void Scroll(StripColor low, StripColor high, int length, int interval_ms); 41 | void FadeOut(int interval_ms); 42 | }; 43 | 44 | #endif // _CIRCULAR_STRIP_H_ 45 | -------------------------------------------------------------------------------- /main/led/led.h: -------------------------------------------------------------------------------- 1 | #ifndef _LED_H_ 2 | #define _LED_H_ 3 | 4 | class Led { 5 | public: 6 | virtual ~Led() = default; 7 | // Set the led state based on the device state 8 | virtual void OnStateChanged() = 0; 9 | }; 10 | 11 | 12 | class NoLed : public Led { 13 | public: 14 | virtual void OnStateChanged() override {} 15 | }; 16 | 17 | #endif // _LED_H_ 18 | -------------------------------------------------------------------------------- /main/led/single_led.cc: -------------------------------------------------------------------------------- 1 | #include "single_led.h" 2 | #include "application.h" 3 | #include 4 | 5 | #define TAG "SingleLed" 6 | 7 | #define DEFAULT_BRIGHTNESS 4 8 | #define HIGH_BRIGHTNESS 16 9 | #define LOW_BRIGHTNESS 2 10 | 11 | #define BLINK_INFINITE -1 12 | 13 | 14 | SingleLed::SingleLed(gpio_num_t gpio) { 15 | // If the gpio is not connected, you should use NoLed class 16 | assert(gpio != GPIO_NUM_NC); 17 | 18 | led_strip_config_t strip_config = {}; 19 | strip_config.strip_gpio_num = gpio; 20 | strip_config.max_leds = 1; 21 | strip_config.led_pixel_format = LED_PIXEL_FORMAT_GRB; 22 | strip_config.led_model = LED_MODEL_WS2812; 23 | 24 | led_strip_rmt_config_t rmt_config = {}; 25 | rmt_config.resolution_hz = 10 * 1000 * 1000; // 10MHz 26 | 27 | ESP_ERROR_CHECK(led_strip_new_rmt_device(&strip_config, &rmt_config, &led_strip_)); 28 | led_strip_clear(led_strip_); 29 | 30 | esp_timer_create_args_t blink_timer_args = { 31 | .callback = [](void *arg) { 32 | auto led = static_cast(arg); 33 | led->OnBlinkTimer(); 34 | }, 35 | .arg = this, 36 | .dispatch_method = ESP_TIMER_TASK, 37 | .name = "Blink Timer", 38 | .skip_unhandled_events = false, 39 | }; 40 | ESP_ERROR_CHECK(esp_timer_create(&blink_timer_args, &blink_timer_)); 41 | } 42 | 43 | SingleLed::~SingleLed() { 44 | esp_timer_stop(blink_timer_); 45 | if (led_strip_ != nullptr) { 46 | led_strip_del(led_strip_); 47 | } 48 | } 49 | 50 | 51 | void SingleLed::SetColor(uint8_t r, uint8_t g, uint8_t b) { 52 | r_ = r; 53 | g_ = g; 54 | b_ = b; 55 | } 56 | 57 | void SingleLed::TurnOn() { 58 | if (led_strip_ == nullptr) { 59 | return; 60 | } 61 | 62 | std::lock_guard lock(mutex_); 63 | esp_timer_stop(blink_timer_); 64 | led_strip_set_pixel(led_strip_, 0, r_, g_, b_); 65 | led_strip_refresh(led_strip_); 66 | } 67 | 68 | void SingleLed::TurnOff() { 69 | if (led_strip_ == nullptr) { 70 | return; 71 | } 72 | 73 | std::lock_guard lock(mutex_); 74 | esp_timer_stop(blink_timer_); 75 | led_strip_clear(led_strip_); 76 | } 77 | 78 | void SingleLed::BlinkOnce() { 79 | Blink(1, 100); 80 | } 81 | 82 | void SingleLed::Blink(int times, int interval_ms) { 83 | StartBlinkTask(times, interval_ms); 84 | } 85 | 86 | void SingleLed::StartContinuousBlink(int interval_ms) { 87 | StartBlinkTask(BLINK_INFINITE, interval_ms); 88 | } 89 | 90 | void SingleLed::StartBlinkTask(int times, int interval_ms) { 91 | if (led_strip_ == nullptr) { 92 | return; 93 | } 94 | 95 | std::lock_guard lock(mutex_); 96 | esp_timer_stop(blink_timer_); 97 | 98 | blink_counter_ = times * 2; 99 | blink_interval_ms_ = interval_ms; 100 | esp_timer_start_periodic(blink_timer_, interval_ms * 1000); 101 | } 102 | 103 | void SingleLed::OnBlinkTimer() { 104 | std::lock_guard lock(mutex_); 105 | blink_counter_--; 106 | if (blink_counter_ & 1) { 107 | led_strip_set_pixel(led_strip_, 0, r_, g_, b_); 108 | led_strip_refresh(led_strip_); 109 | } else { 110 | led_strip_clear(led_strip_); 111 | 112 | if (blink_counter_ == 0) { 113 | esp_timer_stop(blink_timer_); 114 | } 115 | } 116 | } 117 | 118 | 119 | void SingleLed::OnStateChanged() { 120 | auto& app = Application::GetInstance(); 121 | auto device_state = app.GetDeviceState(); 122 | switch (device_state) { 123 | case kDeviceStateStarting: 124 | SetColor(0, 0, DEFAULT_BRIGHTNESS); 125 | StartContinuousBlink(100); 126 | break; 127 | case kDeviceStateWifiConfiguring: 128 | SetColor(0, 0, DEFAULT_BRIGHTNESS); 129 | StartContinuousBlink(500); 130 | break; 131 | case kDeviceStateIdle: 132 | TurnOff(); 133 | break; 134 | case kDeviceStateConnecting: 135 | SetColor(0, 0, DEFAULT_BRIGHTNESS); 136 | TurnOn(); 137 | break; 138 | case kDeviceStateListening: 139 | if (app.IsVoiceDetected()) { 140 | SetColor(HIGH_BRIGHTNESS, 0, 0); 141 | } else { 142 | SetColor(LOW_BRIGHTNESS, 0, 0); 143 | } 144 | TurnOn(); 145 | break; 146 | case kDeviceStateSpeaking: 147 | SetColor(0, DEFAULT_BRIGHTNESS, 0); 148 | TurnOn(); 149 | break; 150 | case kDeviceStateUpgrading: 151 | SetColor(0, DEFAULT_BRIGHTNESS, 0); 152 | StartContinuousBlink(100); 153 | break; 154 | default: 155 | ESP_LOGE(TAG, "Invalid led strip event: %d", device_state); 156 | return; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /main/led/single_led.h: -------------------------------------------------------------------------------- 1 | #ifndef _SINGLE_LED_H_ 2 | #define _SINGLE_LED_H_ 3 | 4 | #include "led.h" 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | class SingleLed : public Led { 12 | public: 13 | SingleLed(gpio_num_t gpio); 14 | virtual ~SingleLed(); 15 | 16 | void OnStateChanged() override; 17 | 18 | private: 19 | std::mutex mutex_; 20 | TaskHandle_t blink_task_ = nullptr; 21 | led_strip_handle_t led_strip_ = nullptr; 22 | uint8_t r_ = 0, g_ = 0, b_ = 0; 23 | int blink_counter_ = 0; 24 | int blink_interval_ms_ = 0; 25 | esp_timer_handle_t blink_timer_ = nullptr; 26 | 27 | void StartBlinkTask(int times, int interval_ms); 28 | void OnBlinkTimer(); 29 | 30 | void BlinkOnce(); 31 | void Blink(int times, int interval_ms); 32 | void StartContinuousBlink(int interval_ms); 33 | void TurnOn(); 34 | void TurnOff(); 35 | void SetColor(uint8_t r, uint8_t g, uint8_t b); 36 | }; 37 | 38 | #endif // _SINGLE_LED_H_ 39 | -------------------------------------------------------------------------------- /main/main.cc: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "application.h" 9 | #include "system_info.h" 10 | 11 | #define TAG "main" 12 | 13 | extern "C" void app_main(void) 14 | { 15 | // Initialize the default event loop 16 | ESP_ERROR_CHECK(esp_event_loop_create_default()); 17 | 18 | // Initialize NVS flash for WiFi configuration 19 | esp_err_t ret = nvs_flash_init(); 20 | if (ret == ESP_ERR_NVS_NO_FREE_PAGES || ret == ESP_ERR_NVS_NEW_VERSION_FOUND) { 21 | ESP_LOGW(TAG, "Erasing NVS flash to fix corruption"); 22 | ESP_ERROR_CHECK(nvs_flash_erase()); 23 | ret = nvs_flash_init(); 24 | } 25 | ESP_ERROR_CHECK(ret); 26 | 27 | // Launch the application 28 | Application::GetInstance().Start(); 29 | 30 | // Dump CPU usage every 10 second 31 | while (true) { 32 | vTaskDelay(10000 / portTICK_PERIOD_MS); 33 | // SystemInfo::PrintRealTimeStats(pdMS_TO_TICKS(1000)); 34 | int free_sram = heap_caps_get_free_size(MALLOC_CAP_INTERNAL); 35 | int min_free_sram = heap_caps_get_minimum_free_size(MALLOC_CAP_INTERNAL); 36 | ESP_LOGI(TAG, "Free internal: %u minimal internal: %u", free_sram, min_free_sram); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /main/ota.h: -------------------------------------------------------------------------------- 1 | #ifndef _OTA_H 2 | #define _OTA_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | class Ota { 9 | public: 10 | Ota(); 11 | ~Ota(); 12 | 13 | void SetCheckVersionUrl(std::string check_version_url); 14 | void SetHeader(const std::string& key, const std::string& value); 15 | void SetPostData(const std::string& post_data); 16 | bool CheckVersion(); 17 | bool HasNewVersion() { return has_new_version_; } 18 | bool HasMqttConfig() { return has_mqtt_config_; } 19 | void StartUpgrade(std::function callback); 20 | void MarkCurrentVersionValid(); 21 | 22 | const std::string& GetFirmwareVersion() const { return firmware_version_; } 23 | const std::string& GetCurrentVersion() const { return current_version_; } 24 | 25 | private: 26 | std::string check_version_url_; 27 | bool has_new_version_ = false; 28 | bool has_mqtt_config_ = false; 29 | std::string current_version_; 30 | std::string firmware_version_; 31 | std::string firmware_url_; 32 | std::string post_data_; 33 | std::map headers_; 34 | 35 | void Upgrade(const std::string& firmware_url); 36 | std::function upgrade_callback_; 37 | std::vector ParseVersion(const std::string& version); 38 | bool IsNewVersionAvailable(const std::string& currentVersion, const std::string& newVersion); 39 | }; 40 | 41 | #endif // _OTA_H 42 | -------------------------------------------------------------------------------- /main/protocols/mqtt_protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef MQTT_PROTOCOL_H 2 | #define MQTT_PROTOCOL_H 3 | 4 | 5 | #include "protocol.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #define MQTT_PING_INTERVAL_SECONDS 90 19 | #define MQTT_RECONNECT_INTERVAL_MS 10000 20 | 21 | #define MQTT_PROTOCOL_SERVER_HELLO_EVENT (1 << 0) 22 | 23 | class MqttProtocol : public Protocol { 24 | public: 25 | MqttProtocol(); 26 | ~MqttProtocol(); 27 | 28 | void SendAudio(const std::vector& data) override; 29 | bool OpenAudioChannel() override; 30 | void CloseAudioChannel() override; 31 | bool IsAudioChannelOpened() const override; 32 | 33 | private: 34 | EventGroupHandle_t event_group_handle_; 35 | 36 | std::string endpoint_; 37 | std::string client_id_; 38 | std::string username_; 39 | std::string password_; 40 | std::string subscribe_topic_; 41 | std::string publish_topic_; 42 | 43 | std::mutex channel_mutex_; 44 | Mqtt* mqtt_ = nullptr; 45 | Udp* udp_ = nullptr; 46 | mbedtls_aes_context aes_ctx_; 47 | std::string aes_nonce_; 48 | std::string udp_server_; 49 | int udp_port_; 50 | uint32_t local_sequence_; 51 | uint32_t remote_sequence_; 52 | 53 | bool StartMqttClient(); 54 | void ParseServerHello(const cJSON* root); 55 | std::string DecodeHexString(const std::string& hex_string); 56 | 57 | void SendText(const std::string& text) override; 58 | }; 59 | 60 | 61 | #endif // MQTT_PROTOCOL_H 62 | -------------------------------------------------------------------------------- /main/protocols/protocol.cc: -------------------------------------------------------------------------------- 1 | #include "protocol.h" 2 | 3 | #include 4 | 5 | #define TAG "Protocol" 6 | 7 | void Protocol::OnIncomingJson(std::function callback) { 8 | on_incoming_json_ = callback; 9 | } 10 | 11 | void Protocol::OnIncomingAudio(std::function&& data)> callback) { 12 | on_incoming_audio_ = callback; 13 | } 14 | 15 | void Protocol::OnAudioChannelOpened(std::function callback) { 16 | on_audio_channel_opened_ = callback; 17 | } 18 | 19 | void Protocol::OnAudioChannelClosed(std::function callback) { 20 | on_audio_channel_closed_ = callback; 21 | } 22 | 23 | void Protocol::OnNetworkError(std::function callback) { 24 | on_network_error_ = callback; 25 | } 26 | 27 | void Protocol::SendAbortSpeaking(AbortReason reason) { 28 | std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"abort\""; 29 | if (reason == kAbortReasonWakeWordDetected) { 30 | message += ",\"reason\":\"wake_word_detected\""; 31 | } 32 | message += "}"; 33 | SendText(message); 34 | } 35 | 36 | void Protocol::SendWakeWordDetected(const std::string& wake_word) { 37 | std::string json = "{\"session_id\":\"" + session_id_ + 38 | "\",\"type\":\"listen\",\"state\":\"detect\",\"text\":\"" + wake_word + "\"}"; 39 | SendText(json); 40 | } 41 | 42 | void Protocol::SendStartListening(ListeningMode mode) { 43 | std::string message = "{\"session_id\":\"" + session_id_ + "\""; 44 | message += ",\"type\":\"listen\",\"state\":\"start\""; 45 | if (mode == kListeningModeAlwaysOn) { 46 | message += ",\"mode\":\"realtime\""; 47 | } else if (mode == kListeningModeAutoStop) { 48 | message += ",\"mode\":\"auto\""; 49 | } else { 50 | message += ",\"mode\":\"manual\""; 51 | } 52 | message += "}"; 53 | SendText(message); 54 | } 55 | 56 | void Protocol::SendStopListening() { 57 | std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"listen\",\"state\":\"stop\"}"; 58 | SendText(message); 59 | } 60 | 61 | void Protocol::SendIotDescriptors(const std::string& descriptors) { 62 | std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"iot\",\"descriptors\":" + descriptors + "}"; 63 | SendText(message); 64 | } 65 | 66 | void Protocol::SendIotStates(const std::string& states) { 67 | std::string message = "{\"session_id\":\"" + session_id_ + "\",\"type\":\"iot\",\"states\":" + states + "}"; 68 | SendText(message); 69 | } 70 | 71 | -------------------------------------------------------------------------------- /main/protocols/protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef PROTOCOL_H 2 | #define PROTOCOL_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct BinaryProtocol3 { 9 | uint8_t type; 10 | uint8_t reserved; 11 | uint16_t payload_size; 12 | uint8_t payload[]; 13 | } __attribute__((packed)); 14 | 15 | enum AbortReason { 16 | kAbortReasonNone, 17 | kAbortReasonWakeWordDetected 18 | }; 19 | 20 | enum ListeningMode { 21 | kListeningModeAutoStop, 22 | kListeningModeManualStop, 23 | kListeningModeAlwaysOn // 需要 AEC 支持 24 | }; 25 | 26 | class Protocol { 27 | public: 28 | virtual ~Protocol() = default; 29 | 30 | inline int server_sample_rate() const { 31 | return server_sample_rate_; 32 | } 33 | 34 | void OnIncomingAudio(std::function&& data)> callback); 35 | void OnIncomingJson(std::function callback); 36 | void OnAudioChannelOpened(std::function callback); 37 | void OnAudioChannelClosed(std::function callback); 38 | void OnNetworkError(std::function callback); 39 | 40 | virtual bool OpenAudioChannel() = 0; 41 | virtual void CloseAudioChannel() = 0; 42 | virtual bool IsAudioChannelOpened() const = 0; 43 | virtual void SendAudio(const std::vector& data) = 0; 44 | virtual void SendWakeWordDetected(const std::string& wake_word); 45 | virtual void SendStartListening(ListeningMode mode); 46 | virtual void SendStopListening(); 47 | virtual void SendAbortSpeaking(AbortReason reason); 48 | virtual void SendIotDescriptors(const std::string& descriptors); 49 | virtual void SendIotStates(const std::string& states); 50 | 51 | protected: 52 | std::function on_incoming_json_; 53 | std::function&& data)> on_incoming_audio_; 54 | std::function on_audio_channel_opened_; 55 | std::function on_audio_channel_closed_; 56 | std::function on_network_error_; 57 | 58 | int server_sample_rate_ = 16000; 59 | std::string session_id_; 60 | 61 | virtual void SendText(const std::string& text) = 0; 62 | }; 63 | 64 | #endif // PROTOCOL_H 65 | 66 | -------------------------------------------------------------------------------- /main/protocols/websocket_protocol.cc: -------------------------------------------------------------------------------- 1 | #include "websocket_protocol.h" 2 | #include "board.h" 3 | #include "system_info.h" 4 | #include "application.h" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #define TAG "WS" 12 | 13 | #ifdef CONFIG_CONNECTION_TYPE_WEBSOCKET 14 | 15 | WebsocketProtocol::WebsocketProtocol() { 16 | event_group_handle_ = xEventGroupCreate(); 17 | } 18 | 19 | WebsocketProtocol::~WebsocketProtocol() { 20 | if (websocket_ != nullptr) { 21 | delete websocket_; 22 | } 23 | vEventGroupDelete(event_group_handle_); 24 | } 25 | 26 | void WebsocketProtocol::SendAudio(const std::vector& data) { 27 | if (websocket_ == nullptr) { 28 | return; 29 | } 30 | 31 | websocket_->Send(data.data(), data.size(), true); 32 | } 33 | 34 | void WebsocketProtocol::SendText(const std::string& text) { 35 | if (websocket_ == nullptr) { 36 | return; 37 | } 38 | 39 | websocket_->Send(text); 40 | } 41 | 42 | bool WebsocketProtocol::IsAudioChannelOpened() const { 43 | return websocket_ != nullptr; 44 | } 45 | 46 | void WebsocketProtocol::CloseAudioChannel() { 47 | if (websocket_ != nullptr) { 48 | delete websocket_; 49 | websocket_ = nullptr; 50 | } 51 | } 52 | 53 | bool WebsocketProtocol::OpenAudioChannel() { 54 | if (websocket_ != nullptr) { 55 | delete websocket_; 56 | } 57 | 58 | std::string url = CONFIG_WEBSOCKET_URL; 59 | std::string token = "Bearer " + std::string(CONFIG_WEBSOCKET_ACCESS_TOKEN); 60 | websocket_ = Board::GetInstance().CreateWebSocket(); 61 | websocket_->SetHeader("Authorization", token.c_str()); 62 | websocket_->SetHeader("Protocol-Version", "1"); 63 | websocket_->SetHeader("Device-Id", SystemInfo::GetMacAddress().c_str()); 64 | 65 | websocket_->OnData([this](const char* data, size_t len, bool binary) { 66 | if (binary) { 67 | if (on_incoming_audio_ != nullptr) { 68 | on_incoming_audio_(std::vector((uint8_t*)data, (uint8_t*)data + len)); 69 | } 70 | } else { 71 | // Parse JSON data 72 | auto root = cJSON_Parse(data); 73 | auto type = cJSON_GetObjectItem(root, "type"); 74 | if (type != NULL) { 75 | if (strcmp(type->valuestring, "hello") == 0) { 76 | ParseServerHello(root); 77 | } else { 78 | if (on_incoming_json_ != nullptr) { 79 | on_incoming_json_(root); 80 | } 81 | } 82 | } else { 83 | ESP_LOGE(TAG, "Missing message type, data: %s", data); 84 | } 85 | cJSON_Delete(root); 86 | } 87 | }); 88 | 89 | websocket_->OnDisconnected([this]() { 90 | ESP_LOGI(TAG, "Websocket disconnected"); 91 | if (on_audio_channel_closed_ != nullptr) { 92 | on_audio_channel_closed_(); 93 | } 94 | }); 95 | 96 | if (!websocket_->Connect(url.c_str())) { 97 | ESP_LOGE(TAG, "Failed to connect to websocket server"); 98 | if (on_network_error_ != nullptr) { 99 | on_network_error_("无法连接服务"); 100 | } 101 | return false; 102 | } 103 | 104 | // Send hello message to describe the client 105 | // keys: message type, version, audio_params (format, sample_rate, channels) 106 | std::string message = "{"; 107 | message += "\"type\":\"hello\","; 108 | message += "\"version\": 1,"; 109 | message += "\"transport\":\"websocket\","; 110 | message += "\"audio_params\":{"; 111 | message += "\"format\":\"opus\", \"sample_rate\":16000, \"channels\":1, \"frame_duration\":" + std::to_string(OPUS_FRAME_DURATION_MS); 112 | message += "}}"; 113 | websocket_->Send(message); 114 | 115 | // Wait for server hello 116 | EventBits_t bits = xEventGroupWaitBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT, pdTRUE, pdFALSE, pdMS_TO_TICKS(10000)); 117 | if (!(bits & WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT)) { 118 | ESP_LOGE(TAG, "Failed to receive server hello"); 119 | if (on_network_error_ != nullptr) { 120 | on_network_error_("等待响应超时"); 121 | } 122 | return false; 123 | } 124 | 125 | if (on_audio_channel_opened_ != nullptr) { 126 | on_audio_channel_opened_(); 127 | } 128 | 129 | return true; 130 | } 131 | 132 | void WebsocketProtocol::ParseServerHello(const cJSON* root) { 133 | auto transport = cJSON_GetObjectItem(root, "transport"); 134 | if (transport == nullptr || strcmp(transport->valuestring, "websocket") != 0) { 135 | ESP_LOGE(TAG, "Unsupported transport: %s", transport->valuestring); 136 | return; 137 | } 138 | 139 | auto audio_params = cJSON_GetObjectItem(root, "audio_params"); 140 | if (audio_params != NULL) { 141 | auto sample_rate = cJSON_GetObjectItem(audio_params, "sample_rate"); 142 | if (sample_rate != NULL) { 143 | server_sample_rate_ = sample_rate->valueint; 144 | } 145 | } 146 | 147 | xEventGroupSetBits(event_group_handle_, WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT); 148 | } 149 | 150 | #endif 151 | -------------------------------------------------------------------------------- /main/protocols/websocket_protocol.h: -------------------------------------------------------------------------------- 1 | #ifndef _WEBSOCKET_PROTOCOL_H_ 2 | #define _WEBSOCKET_PROTOCOL_H_ 3 | 4 | 5 | #include "protocol.h" 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #define WEBSOCKET_PROTOCOL_SERVER_HELLO_EVENT (1 << 0) 12 | 13 | class WebsocketProtocol : public Protocol { 14 | public: 15 | WebsocketProtocol(); 16 | ~WebsocketProtocol(); 17 | 18 | void SendAudio(const std::vector& data) override; 19 | bool OpenAudioChannel() override; 20 | void CloseAudioChannel() override; 21 | bool IsAudioChannelOpened() const override; 22 | 23 | private: 24 | EventGroupHandle_t event_group_handle_; 25 | WebSocket* websocket_ = nullptr; 26 | 27 | void ParseServerHello(const cJSON* root); 28 | void SendText(const std::string& text) override; 29 | }; 30 | 31 | #endif 32 | -------------------------------------------------------------------------------- /main/settings.cc: -------------------------------------------------------------------------------- 1 | #include "settings.h" 2 | 3 | #include 4 | #include 5 | 6 | #define TAG "Settings" 7 | 8 | Settings::Settings(const std::string& ns, bool read_write) : ns_(ns), read_write_(read_write) { 9 | nvs_open(ns.c_str(), read_write_ ? NVS_READWRITE : NVS_READONLY, &nvs_handle_); 10 | } 11 | 12 | Settings::~Settings() { 13 | if (nvs_handle_ != 0) { 14 | if (read_write_ && dirty_) { 15 | ESP_ERROR_CHECK(nvs_commit(nvs_handle_)); 16 | } 17 | nvs_close(nvs_handle_); 18 | } 19 | } 20 | 21 | std::string Settings::GetString(const std::string& key, const std::string& default_value) { 22 | if (nvs_handle_ == 0) { 23 | return default_value; 24 | } 25 | 26 | size_t length = 0; 27 | if (nvs_get_str(nvs_handle_, key.c_str(), nullptr, &length) != ESP_OK) { 28 | return default_value; 29 | } 30 | 31 | std::string value; 32 | value.resize(length); 33 | ESP_ERROR_CHECK(nvs_get_str(nvs_handle_, key.c_str(), value.data(), &length)); 34 | while (value.back() == '\0') { 35 | value.pop_back(); 36 | } 37 | return value; 38 | } 39 | 40 | void Settings::SetString(const std::string& key, const std::string& value) { 41 | if (read_write_) { 42 | ESP_ERROR_CHECK(nvs_set_str(nvs_handle_, key.c_str(), value.c_str())); 43 | dirty_ = true; 44 | } else { 45 | ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); 46 | } 47 | } 48 | 49 | int32_t Settings::GetInt(const std::string& key, int32_t default_value) { 50 | if (nvs_handle_ == 0) { 51 | return default_value; 52 | } 53 | 54 | int32_t value; 55 | if (nvs_get_i32(nvs_handle_, key.c_str(), &value) != ESP_OK) { 56 | return default_value; 57 | } 58 | return value; 59 | } 60 | 61 | void Settings::SetInt(const std::string& key, int32_t value) { 62 | if (read_write_) { 63 | ESP_ERROR_CHECK(nvs_set_i32(nvs_handle_, key.c_str(), value)); 64 | dirty_ = true; 65 | } else { 66 | ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); 67 | } 68 | } 69 | 70 | void Settings::EraseKey(const std::string& key) { 71 | if (read_write_) { 72 | auto ret = nvs_erase_key(nvs_handle_, key.c_str()); 73 | if (ret != ESP_ERR_NVS_NOT_FOUND) { 74 | ESP_ERROR_CHECK(ret); 75 | } 76 | } else { 77 | ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); 78 | } 79 | } 80 | 81 | void Settings::EraseAll() { 82 | if (read_write_) { 83 | ESP_ERROR_CHECK(nvs_erase_all(nvs_handle_)); 84 | } else { 85 | ESP_LOGW(TAG, "Namespace %s is not open for writing", ns_.c_str()); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /main/settings.h: -------------------------------------------------------------------------------- 1 | #ifndef SETTINGS_H 2 | #define SETTINGS_H 3 | 4 | #include 5 | #include 6 | 7 | class Settings { 8 | public: 9 | Settings(const std::string& ns, bool read_write = false); 10 | ~Settings(); 11 | 12 | std::string GetString(const std::string& key, const std::string& default_value = ""); 13 | void SetString(const std::string& key, const std::string& value); 14 | int32_t GetInt(const std::string& key, int32_t default_value = 0); 15 | void SetInt(const std::string& key, int32_t value); 16 | void EraseKey(const std::string& key); 17 | void EraseAll(); 18 | 19 | private: 20 | std::string ns_; 21 | nvs_handle_t nvs_handle_ = 0; 22 | bool read_write_ = false; 23 | bool dirty_ = false; 24 | }; 25 | 26 | #endif 27 | -------------------------------------------------------------------------------- /main/system_info.cc: -------------------------------------------------------------------------------- 1 | #include "system_info.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | 13 | #define TAG "SystemInfo" 14 | 15 | size_t SystemInfo::GetFlashSize() { 16 | uint32_t flash_size; 17 | if (esp_flash_get_size(NULL, &flash_size) != ESP_OK) { 18 | ESP_LOGE(TAG, "Failed to get flash size"); 19 | return 0; 20 | } 21 | return (size_t)flash_size; 22 | } 23 | 24 | size_t SystemInfo::GetMinimumFreeHeapSize() { 25 | return esp_get_minimum_free_heap_size(); 26 | } 27 | 28 | size_t SystemInfo::GetFreeHeapSize() { 29 | return esp_get_free_heap_size(); 30 | } 31 | 32 | std::string SystemInfo::GetMacAddress() { 33 | uint8_t mac[6]; 34 | esp_read_mac(mac, ESP_MAC_WIFI_STA); 35 | char mac_str[18]; 36 | snprintf(mac_str, sizeof(mac_str), "%02x:%02x:%02x:%02x:%02x:%02x", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); 37 | return std::string(mac_str); 38 | } 39 | 40 | std::string SystemInfo::GetChipModelName() { 41 | return std::string(CONFIG_IDF_TARGET); 42 | } 43 | 44 | esp_err_t SystemInfo::PrintRealTimeStats(TickType_t xTicksToWait) { 45 | #define ARRAY_SIZE_OFFSET 5 46 | TaskStatus_t *start_array = NULL, *end_array = NULL; 47 | UBaseType_t start_array_size, end_array_size; 48 | configRUN_TIME_COUNTER_TYPE start_run_time, end_run_time; 49 | esp_err_t ret; 50 | uint32_t total_elapsed_time; 51 | 52 | //Allocate array to store current task states 53 | start_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; 54 | start_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * start_array_size); 55 | if (start_array == NULL) { 56 | ret = ESP_ERR_NO_MEM; 57 | goto exit; 58 | } 59 | //Get current task states 60 | start_array_size = uxTaskGetSystemState(start_array, start_array_size, &start_run_time); 61 | if (start_array_size == 0) { 62 | ret = ESP_ERR_INVALID_SIZE; 63 | goto exit; 64 | } 65 | 66 | vTaskDelay(xTicksToWait); 67 | 68 | //Allocate array to store tasks states post delay 69 | end_array_size = uxTaskGetNumberOfTasks() + ARRAY_SIZE_OFFSET; 70 | end_array = (TaskStatus_t*)malloc(sizeof(TaskStatus_t) * end_array_size); 71 | if (end_array == NULL) { 72 | ret = ESP_ERR_NO_MEM; 73 | goto exit; 74 | } 75 | //Get post delay task states 76 | end_array_size = uxTaskGetSystemState(end_array, end_array_size, &end_run_time); 77 | if (end_array_size == 0) { 78 | ret = ESP_ERR_INVALID_SIZE; 79 | goto exit; 80 | } 81 | 82 | //Calculate total_elapsed_time in units of run time stats clock period. 83 | total_elapsed_time = (end_run_time - start_run_time); 84 | if (total_elapsed_time == 0) { 85 | ret = ESP_ERR_INVALID_STATE; 86 | goto exit; 87 | } 88 | 89 | printf("| Task | Run Time | Percentage\n"); 90 | //Match each task in start_array to those in the end_array 91 | for (int i = 0; i < start_array_size; i++) { 92 | int k = -1; 93 | for (int j = 0; j < end_array_size; j++) { 94 | if (start_array[i].xHandle == end_array[j].xHandle) { 95 | k = j; 96 | //Mark that task have been matched by overwriting their handles 97 | start_array[i].xHandle = NULL; 98 | end_array[j].xHandle = NULL; 99 | break; 100 | } 101 | } 102 | //Check if matching task found 103 | if (k >= 0) { 104 | uint32_t task_elapsed_time = end_array[k].ulRunTimeCounter - start_array[i].ulRunTimeCounter; 105 | uint32_t percentage_time = (task_elapsed_time * 100UL) / (total_elapsed_time * CONFIG_FREERTOS_NUMBER_OF_CORES); 106 | printf("| %-16s | %8lu | %4lu%%\n", start_array[i].pcTaskName, task_elapsed_time, percentage_time); 107 | } 108 | } 109 | 110 | //Print unmatched tasks 111 | for (int i = 0; i < start_array_size; i++) { 112 | if (start_array[i].xHandle != NULL) { 113 | printf("| %s | Deleted\n", start_array[i].pcTaskName); 114 | } 115 | } 116 | for (int i = 0; i < end_array_size; i++) { 117 | if (end_array[i].xHandle != NULL) { 118 | printf("| %s | Created\n", end_array[i].pcTaskName); 119 | } 120 | } 121 | ret = ESP_OK; 122 | 123 | exit: //Common return path 124 | free(start_array); 125 | free(end_array); 126 | return ret; 127 | } 128 | 129 | -------------------------------------------------------------------------------- /main/system_info.h: -------------------------------------------------------------------------------- 1 | #ifndef _SYSTEM_INFO_H_ 2 | #define _SYSTEM_INFO_H_ 3 | 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | class SystemInfo { 10 | public: 11 | static size_t GetFlashSize(); 12 | static size_t GetMinimumFreeHeapSize(); 13 | static size_t GetFreeHeapSize(); 14 | static std::string GetMacAddress(); 15 | static std::string GetChipModelName(); 16 | static esp_err_t PrintRealTimeStats(TickType_t xTicksToWait); 17 | }; 18 | 19 | #endif // _SYSTEM_INFO_H_ 20 | -------------------------------------------------------------------------------- /partitions.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x9000, 0x4000, 4 | otadata, data, ota, 0xd000, 0x2000, 5 | phy_init, data, phy, 0xf000, 0x1000, 6 | model, data, spiffs, 0x10000, 0xF0000, 7 | storage, data, spiffs, 0x100000, 1M, 8 | factory, app, factory, 0x200000, 4M, 9 | ota_0, app, ota_0, 0x600000, 4M, 10 | ota_1, app, ota_1, 0xA00000, 4M, 11 | -------------------------------------------------------------------------------- /partitions_4M.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x9000, 0x4000, 4 | otadata, data, ota, 0xd000, 0x2000, 5 | phy_init, data, phy, 0xf000, 0x1000, 6 | model, data, spiffs, 0x10000, 0xF0000, 7 | factory, app, factory, 0x100000, 3M, 8 | -------------------------------------------------------------------------------- /partitions_8M.csv: -------------------------------------------------------------------------------- 1 | # ESP-IDF Partition Table 2 | # Name, Type, SubType, Offset, Size, Flags 3 | nvs, data, nvs, 0x9000, 0x4000, 4 | otadata, data, ota, 0xd000, 0x2000, 5 | phy_init, data, phy, 0xf000, 0x1000, 6 | model, data, spiffs, 0x10000, 0xF0000, 7 | storage, data, spiffs, 0x100000, 1M, 8 | ota_0, app, ota_0, 0x200000, 3M, 9 | ota_1, app, ota_1, 0x500000, 3M, 10 | -------------------------------------------------------------------------------- /release.py: -------------------------------------------------------------------------------- 1 | import sys 2 | import os 3 | import json 4 | 5 | 6 | def get_board_type(): 7 | with open("build/compile_commands.json") as f: 8 | data = json.load(f) 9 | for item in data: 10 | if not item["file"].endswith("main.cc"): 11 | continue 12 | command = item["command"] 13 | # extract -DBOARD_TYPE=xxx 14 | board_type = command.split("-DBOARD_TYPE=\\\"")[1].split("\\\"")[0].strip() 15 | return board_type 16 | return None 17 | 18 | def get_project_version(): 19 | with open("CMakeLists.txt") as f: 20 | for line in f: 21 | if line.startswith("set(PROJECT_VER"): 22 | return line.split("\"")[1].split("\"")[0].strip() 23 | return None 24 | 25 | def merge_bin(): 26 | if os.system("idf.py merge-bin") != 0: 27 | print("merge bin failed") 28 | sys.exit(1) 29 | 30 | def zip_bin(board_type, project_version): 31 | if not os.path.exists("releases"): 32 | os.makedirs("releases") 33 | output_path = f"releases/v{project_version}_{board_type}.zip" 34 | if os.path.exists(output_path): 35 | os.remove(output_path) 36 | if os.system(f"zip -j {output_path} build/merged-binary.bin") != 0: 37 | print("zip bin failed") 38 | sys.exit(1) 39 | print(f"zip bin to {output_path} done") 40 | 41 | 42 | if __name__ == "__main__": 43 | merge_bin() 44 | board_type = get_board_type() 45 | print("board type:", board_type) 46 | project_version = get_project_version() 47 | print("project version:", project_version) 48 | zip_bin(board_type, project_version) 49 | -------------------------------------------------------------------------------- /sdkconfig.defaults: -------------------------------------------------------------------------------- 1 | CONFIG_COMPILER_CXX_EXCEPTIONS=y 2 | CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE=1024 3 | 4 | CONFIG_BOOTLOADER_COMPILER_OPTIMIZATION_PERF=y 5 | CONFIG_BOOTLOADER_LOG_LEVEL_NONE=y 6 | CONFIG_BOOTLOADER_SKIP_VALIDATE_ALWAYS=y 7 | CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y 8 | 9 | CONFIG_HTTPD_MAX_REQ_HDR_LEN=2048 10 | CONFIG_HTTPD_MAX_URI_LEN=2048 11 | 12 | CONFIG_PARTITION_TABLE_CUSTOM=y 13 | CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions.csv" 14 | CONFIG_PARTITION_TABLE_OFFSET=0x8000 15 | 16 | ESP_TASK_WDT_TIMEOUT_S=10 17 | CONFIG_FREERTOS_GENERATE_RUN_TIME_STATS=y 18 | CONFIG_FREERTOS_USE_STATS_FORMATTING_FUNCTIONS=y 19 | 20 | CONFIG_LV_COLOR_16_SWAP=y 21 | CONFIG_LV_MEM_CUSTOM=y 22 | 23 | CONFIG_MBEDTLS_DYNAMIC_BUFFER=y 24 | CONFIG_ESP_WIFI_IRAM_OPT=n 25 | CONFIG_ESP_WIFI_RX_IRAM_OPT=n 26 | 27 | CONFIG_CODEC_I2C_BACKWARD_COMPATIBLE=n 28 | -------------------------------------------------------------------------------- /sdkconfig.defaults.esp32c3: -------------------------------------------------------------------------------- 1 | 2 | CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y 3 | -------------------------------------------------------------------------------- /sdkconfig.defaults.esp32s3: -------------------------------------------------------------------------------- 1 | 2 | CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y 3 | CONFIG_ESPTOOLPY_FLASHMODE_QIO=y 4 | 5 | CONFIG_ESP_DEFAULT_CPU_FREQ_MHZ_240=y 6 | 7 | CONFIG_SPIRAM=y 8 | CONFIG_SPIRAM_MODE_OCT=y 9 | CONFIG_SPIRAM_SPEED_80M=y 10 | CONFIG_SPIRAM_MALLOC_ALWAYSINTERNAL=4096 11 | CONFIG_SPIRAM_TRY_ALLOCATE_WIFI_LWIP=y 12 | CONFIG_SPIRAM_MALLOC_RESERVE_INTERNAL=32768 13 | CONFIG_SPIRAM_MEMTEST=n 14 | CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC=y 15 | 16 | CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB=y 17 | CONFIG_ESP32S3_DATA_CACHE_64KB=y 18 | CONFIG_ESP32S3_DATA_CACHE_LINE_64B=y 19 | 20 | CONFIG_USE_WAKENET=y 21 | CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y 22 | CONFIG_USE_MULTINET=n 23 | -------------------------------------------------------------------------------- /versions.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | from dotenv import load_dotenv 3 | load_dotenv() 4 | 5 | import os 6 | import struct 7 | import zipfile 8 | import oss2 9 | import json 10 | 11 | def get_chip_id_string(chip_id): 12 | return { 13 | 0x0000: "esp32", 14 | 0x0002: "esp32s2", 15 | 0x0005: "esp32c3", 16 | 0x0009: "esp32s3", 17 | 0x000C: "esp32c2", 18 | 0x000D: "esp32c6", 19 | 0x0010: "esp32h2", 20 | 0x0011: "esp32c5", 21 | 0x0012: "esp32p4", 22 | 0x0017: "esp32c5", 23 | }[chip_id] 24 | 25 | def get_flash_size(flash_size): 26 | MB = 1024 * 1024 27 | return { 28 | 0x00: 1 * MB, 29 | 0x01: 2 * MB, 30 | 0x02: 4 * MB, 31 | 0x03: 8 * MB, 32 | 0x04: 16 * MB, 33 | 0x05: 32 * MB, 34 | 0x06: 64 * MB, 35 | 0x07: 128 * MB, 36 | }[flash_size] 37 | 38 | def get_app_desc(data): 39 | magic = struct.unpack("> 4) 79 | chip_id = get_chip_id_string(data[0xC]) 80 | # get segments 81 | segment_count = data[0x1] 82 | segments = [] 83 | offset = 0x18 84 | for i in range(segment_count): 85 | segment_size = struct.unpack("