├── .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 | 
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 |
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 |
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 | 
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 |
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 |
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 | 
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 |
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 |
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