forked from xiaozhi/xiaozhi-esp32
Compare commits
62 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e96f0f027 | ||
|
|
f39c112970 | ||
|
|
f7e258979e | ||
|
|
37110a9d05 | ||
|
|
796312db4c | ||
|
|
9e1724e892 | ||
|
|
0b3b98eca7 | ||
|
|
abd62648cb | ||
|
|
0883a36537 | ||
|
|
b6c61fe390 | ||
|
|
f7284a57df | ||
|
|
96f34ec70f | ||
|
|
aad2f60b87 | ||
|
|
5b874bc3ad | ||
|
|
7d515a8345 | ||
|
|
d9f7682b2d | ||
|
|
f6ca040d19 | ||
|
|
7ad22d49af | ||
|
|
7fa9056527 | ||
|
|
62b93f986f | ||
|
|
b9617368a0 | ||
|
|
b217cddbd8 | ||
|
|
3a52761d30 | ||
|
|
6b3659c2f5 | ||
|
|
734b5b410a | ||
|
|
d5ec8f7081 | ||
|
|
89674f8838 | ||
|
|
ed51705240 | ||
|
|
c9fa5fabc5 | ||
|
|
e3ed350b8b | ||
|
|
d963e120db | ||
|
|
2ff3796289 | ||
|
|
b48506171b | ||
|
|
7240ea99f1 | ||
|
|
1e8fefbede | ||
|
|
906d819454 | ||
|
|
be88719932 | ||
|
|
213117ded2 | ||
|
|
76ff1cf0dc | ||
|
|
5d44633687 | ||
|
|
5113a5f4bb | ||
|
|
f501a5f440 | ||
|
|
e9649cfc58 | ||
|
|
ee5587019b | ||
|
|
ebdd58748a | ||
|
|
99c32d9331 | ||
|
|
cccaf71c3e | ||
|
|
564018c762 | ||
|
|
0ccdc082b5 | ||
|
|
4b582f8074 | ||
|
|
1f0d2e993b | ||
|
|
b7db68457c | ||
|
|
11c79a7003 | ||
|
|
f9de29519b | ||
|
|
d7c1aef77a | ||
|
|
33c2fe90a8 | ||
|
|
e8f68a331f | ||
|
|
2d15bef298 | ||
|
|
28db4bd60a | ||
|
|
01a12b325f | ||
|
|
c87b1eabf4 | ||
|
|
908c9d5708 |
126
.clang-format
Normal file
126
.clang-format
Normal file
@@ -0,0 +1,126 @@
|
||||
---
|
||||
Language: Cpp
|
||||
BasedOnStyle: Google
|
||||
AccessModifierOffset: -4
|
||||
AlignAfterOpenBracket: Align
|
||||
AlignConsecutiveAssignments: false
|
||||
AlignConsecutiveDeclarations: false
|
||||
AlignEscapedNewlines: Left
|
||||
AlignOperands: true
|
||||
AlignTrailingComments: true
|
||||
AllowAllParametersOfDeclarationOnNextLine: true
|
||||
AllowShortBlocksOnASingleLine: false
|
||||
AllowShortCaseLabelsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: All
|
||||
AllowShortIfStatementsOnASingleLine: false
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AlwaysBreakAfterReturnType: ExceptShortType
|
||||
AlwaysBreakBeforeMultilineStrings: true
|
||||
AlwaysBreakTemplateDeclarations: Yes
|
||||
BinPackArguments: true
|
||||
BinPackParameters: true
|
||||
BraceWrapping:
|
||||
AfterClass: false
|
||||
AfterControlStatement: false
|
||||
AfterEnum: false
|
||||
AfterFunction: false
|
||||
AfterNamespace: false
|
||||
AfterObjCDeclaration: false
|
||||
AfterStruct: false
|
||||
AfterUnion: false
|
||||
AfterExternBlock: false
|
||||
BeforeCatch: false
|
||||
BeforeElse: false
|
||||
IndentBraces: false
|
||||
SplitEmptyFunction: false
|
||||
SplitEmptyRecord: false
|
||||
SplitEmptyNamespace: false
|
||||
BreakBeforeBinaryOperators: None
|
||||
BreakBeforeBraces: Attach
|
||||
BreakBeforeInheritanceComma: false
|
||||
BreakInheritanceList: BeforeColon
|
||||
BreakBeforeTernaryOperators: true
|
||||
BreakConstructorInitializersBeforeComma: false
|
||||
BreakConstructorInitializers: BeforeColon
|
||||
BreakAfterJavaFieldAnnotations: false
|
||||
BreakStringLiterals: true
|
||||
ColumnLimit: 100
|
||||
CommentPragmas: '^ IWYU pragma:'
|
||||
CompactNamespaces: false
|
||||
ConstructorInitializerAllOnOneLineOrOnePerLine: true
|
||||
ConstructorInitializerIndentWidth: 4
|
||||
ContinuationIndentWidth: 4
|
||||
Cpp11BracedListStyle: true
|
||||
DerivePointerAlignment: false
|
||||
DisableFormat: false
|
||||
ExperimentalAutoDetectBinPacking: false
|
||||
FixNamespaceComments: true
|
||||
ForEachMacros:
|
||||
- cJSON_ArrayForEach
|
||||
IncludeBlocks: Preserve
|
||||
IncludeCategories:
|
||||
- Regex: '^<esp_.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<driver/.*\.h>'
|
||||
Priority: 1
|
||||
- Regex: '^<.*\.h>'
|
||||
Priority: 2
|
||||
- Regex: '^<.*'
|
||||
Priority: 3
|
||||
- Regex: '.*'
|
||||
Priority: 4
|
||||
IncludeIsMainRegex: '([-_](test|unittest))?$'
|
||||
IndentCaseLabels: true
|
||||
IndentPPDirectives: None
|
||||
IndentWidth: 4
|
||||
IndentWrappedFunctionNames: false
|
||||
KeepEmptyLinesAtTheStartOfBlocks: false
|
||||
MacroBlockBegin: ''
|
||||
MacroBlockEnd: ''
|
||||
MaxEmptyLinesToKeep: 1
|
||||
NamespaceIndentation: None
|
||||
PenaltyBreakAssignment: 2
|
||||
PenaltyBreakBeforeFirstCallParameter: 1
|
||||
PenaltyBreakComment: 300
|
||||
PenaltyBreakFirstLessLess: 120
|
||||
PenaltyBreakString: 1000
|
||||
PenaltyBreakTemplateDeclaration: 10
|
||||
PenaltyExcessCharacter: 1000000
|
||||
PenaltyReturnTypeOnItsOwnLine: 200
|
||||
PointerAlignment: Left
|
||||
RawStringFormats:
|
||||
- Language: Cpp
|
||||
Delimiters:
|
||||
- cc
|
||||
- CC
|
||||
- cpp
|
||||
- Cpp
|
||||
- CPP
|
||||
- 'c++'
|
||||
- 'C++'
|
||||
CanonicalDelimiter: ''
|
||||
BasedOnStyle: google
|
||||
ReflowComments: true
|
||||
SortIncludes: true
|
||||
SortUsingDeclarations: true
|
||||
SpaceAfterCStyleCast: false
|
||||
SpaceAfterTemplateKeyword: true
|
||||
SpaceBeforeAssignmentOperators: true
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
SpaceBeforeCtorInitializerColon: true
|
||||
SpaceBeforeInheritanceColon: true
|
||||
SpaceBeforeParens: ControlStatements
|
||||
SpaceBeforeRangeBasedForLoopColon: true
|
||||
SpaceInEmptyParentheses: false
|
||||
SpacesBeforeTrailingComments: 2
|
||||
SpacesInAngles: false
|
||||
SpacesInContainerLiterals: true
|
||||
SpacesInCStyleCastParentheses: false
|
||||
SpacesInParentheses: false
|
||||
SpacesInSquareBrackets: false
|
||||
Standard: Latest
|
||||
StatementMacros:
|
||||
- Q_UNUSED
|
||||
- QT_REQUIRE_VERSION
|
||||
TabWidth: 4
|
||||
UseTab: Never
|
||||
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@@ -92,7 +92,7 @@ jobs:
|
||||
include: ${{ fromJson(needs.prepare.outputs.variants) }}
|
||||
runs-on: ubuntu-latest
|
||||
container:
|
||||
image: espressif/idf:release-v5.5
|
||||
image: espressif/idf:v5.5.2
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -2,6 +2,7 @@ tmp/
|
||||
components/
|
||||
managed_components/
|
||||
build/
|
||||
dist/
|
||||
.vscode/
|
||||
.devcontainer/
|
||||
sdkconfig.old
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
# For more information about build system see
|
||||
# https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/build-system.html
|
||||
# The following five lines of boilerplate have to be in your project's
|
||||
# The following lines of boilerplate have to be in your project's
|
||||
# CMakeLists in this exact order for cmake to work correctly
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
|
||||
set(PROJECT_VER "2.0.5")
|
||||
|
||||
# Add this line to disable the specific warning
|
||||
add_compile_options(-Wno-missing-field-initializers)
|
||||
|
||||
include($ENV{IDF_PATH}/tools/cmake/project.cmake)
|
||||
project(xiaozhi)
|
||||
# "Trim" the build. Include the minimal set of components, main, and anything it depends on.
|
||||
idf_build_set_property(MINIMAL_BUILD ON)
|
||||
|
||||
set(PROJECT_VER "2.2.2")
|
||||
project(xiaozhi)
|
||||
|
||||
@@ -140,6 +140,7 @@ For server deployment on personal computers, refer to the following open-source
|
||||
- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python server
|
||||
- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java server
|
||||
- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang server
|
||||
- [hackers365/xiaozhi-esp32-server-golang](https://github.com/hackers365/xiaozhi-esp32-server-golang) Golang server
|
||||
|
||||
Other client projects using the XiaoZhi communication protocol:
|
||||
|
||||
@@ -159,7 +160,7 @@ This is an open-source ESP32 project, released under the MIT license, allowing a
|
||||
|
||||
We hope this project helps everyone understand AI hardware development and apply rapidly evolving large language models to real hardware devices.
|
||||
|
||||
If you have any ideas or suggestions, please feel free to raise Issues or join the QQ group: 1011329060
|
||||
If you have any ideas or suggestions, please feel free to raise Issues or join our [Discord](https://discord.gg/bXqgAfRm) or QQ group: 994694848
|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ Feishuドキュメントチュートリアルをご覧ください:
|
||||
- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Pythonサーバー
|
||||
- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Javaサーバー
|
||||
- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golangサーバー
|
||||
- [hackers365/xiaozhi-esp32-server-golang](https://github.com/hackers365/xiaozhi-esp32-server-golang) Golangサーバー
|
||||
|
||||
シャオジー通信プロトコルを利用した他のクライアントプロジェクト:
|
||||
|
||||
@@ -155,7 +156,7 @@ Feishuドキュメントチュートリアルをご覧ください:
|
||||
|
||||
このプロジェクトを通じて、AIハードウェア開発を理解し、急速に進化する大規模言語モデルを実際のハードウェアデバイスに応用できるようになることを目指しています。
|
||||
|
||||
ご意見やご提案があれば、いつでもIssueを提出するか、QQグループ:1011329060 にご参加ください。
|
||||
ご意見やご提案があれば、いつでもIssueを提出するか、[Discord](https://discord.gg/bXqgAfRm) または QQグループ:1011329060 にご参加ください。
|
||||
|
||||
## スター履歴
|
||||
|
||||
|
||||
@@ -140,6 +140,7 @@ v1 的稳定版本为 1.9.2,可以通过 `git checkout v1` 来切换到 v1 版
|
||||
- [xinnan-tech/xiaozhi-esp32-server](https://github.com/xinnan-tech/xiaozhi-esp32-server) Python 服务器
|
||||
- [joey-zhou/xiaozhi-esp32-server-java](https://github.com/joey-zhou/xiaozhi-esp32-server-java) Java 服务器
|
||||
- [AnimeAIChat/xiaozhi-server-go](https://github.com/AnimeAIChat/xiaozhi-server-go) Golang 服务器
|
||||
- [hackers365/xiaozhi-esp32-server-golang](https://github.com/hackers365/xiaozhi-esp32-server-golang) Golang 服务器
|
||||
|
||||
使用小智通信协议的第三方客户端项目:
|
||||
|
||||
@@ -155,7 +156,7 @@ v1 的稳定版本为 1.9.2,可以通过 `git checkout v1` 来切换到 v1 版
|
||||
|
||||
我们希望通过这个项目,能够帮助大家了解 AI 硬件开发,将当下飞速发展的大语言模型应用到实际的硬件设备中。
|
||||
|
||||
如果你有任何想法或建议,请随时提出 Issues 或加入 QQ 群:1011329060
|
||||
如果你有任何想法或建议,请随时提出 Issues 或加入 [Discord](https://discord.gg/bXqgAfRm) 或 QQ 群:1011329060
|
||||
|
||||
## Star History
|
||||
|
||||
|
||||
1
_codeql_detected_source_root
Symbolic link
1
_codeql_detected_source_root
Symbolic link
@@ -0,0 +1 @@
|
||||
.
|
||||
37
docs/blufi.md
Normal file
37
docs/blufi.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# BluFi 配网(集成 esp-wifi-connect)
|
||||
|
||||
本文档说明如何在小智固件中启用和使用 BluFi(BLE Wi‑Fi 配网),并结合项目内置的 `esp-wifi-connect` 组件完成 Wi‑Fi 连接与存储。官方
|
||||
BluFi
|
||||
协议说明请参考 [Espressif 文档](https://docs.espressif.com/projects/esp-idf/zh_CN/stable/esp32/api-guides/ble/blufi.html)。
|
||||
|
||||
## 前置条件
|
||||
|
||||
- 需要支持 BLE 的芯片与固件配置。
|
||||
- 在 `idf.py menuconfig` 中启用 `WiFi Configuration Method -> Esp Blufi`(`CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING=y`
|
||||
)。如果想使用 BluFi,必须关闭同一菜单下的 Hotspot 选项,否则默认使用 Hotspot 配网模式。
|
||||
|
||||
- 保持默认的 NVS 与事件循环初始化(项目的 `app_main` 已处理)。
|
||||
- CONFIG_BT_BLUEDROID_ENABLED、CONFIG_BT_NIMBLE_ENABLED这两个宏应二选一,不能同时启用。
|
||||
## 工作流程
|
||||
|
||||
1) 手机端通过 BluFi(如官方 EspBlufi App 或自研客户端)连接设备,发送 Wi‑Fi SSID/密码,手机端可以通过blufi协议获取设备端扫描到的WiFi列表。
|
||||
2) 设备侧在 `ESP_BLUFI_EVENT_REQ_CONNECT_TO_AP` 中将凭据写入 `SsidManager`(存储到 NVS,属于 `esp-wifi-connect` 组件)。
|
||||
3) 随后启动 `WifiStation` 扫描并连接;状态通过 BluFi 返回。
|
||||
4) 配网成功后设备会自动连接新 Wi‑Fi;失败则返回失败状态。
|
||||
|
||||
## 使用步骤
|
||||
|
||||
1. 配置:在 menuconfig 开启 `Esp Blufi`。编译并烧录固件。
|
||||
2. 触发配网:设备首次启动且没有已保存的 Wi‑Fi 时会自动进入配网。
|
||||
3. 手机端操作:打开 EspBlufi App(或其他 BluFi 客户端),搜索并连接设备,可以选择是否加密,按提示输入 Wi‑Fi SSID/密码并发送。
|
||||
4. 观察结果:
|
||||
- 成功:BluFi 报告连接成功,设备自动连接 Wi‑Fi。
|
||||
- 失败:BluFi 返回失败状态,可重新发送或检查路由器。
|
||||
|
||||
## 注意事项
|
||||
|
||||
- BluFi 配网不支持与热点配网同时开启。如果热点配网已经启动,则默认使用热点配网。请在 menuconfig 中只保留一种配网方式。
|
||||
- 若多次测试,建议清除或覆盖存储的 SSID(`wifi` 命名空间),避免旧配置干扰。
|
||||
- 如果使用自定义 BluFi 客户端,需遵循官方协议帧格式,参考上文官方文档链接。
|
||||
- 官方文档中已提供EspBlufi APP下载地址
|
||||
- 由于IDF5.5.2的blufi接口发生变化,5.5.2版本编译后蓝牙名称为"Xiaozhi-Blufi",5.5.1版本中蓝牙名称为"BLUFI_DEVICE"
|
||||
91
docs/code_style.md
Normal file
91
docs/code_style.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# 代码风格指南
|
||||
|
||||
## 代码格式化工具
|
||||
|
||||
本项目使用 clang-format 工具来统一代码风格。我们已经在项目根目录下提供了 `.clang-format` 配置文件,该配置基于 Google C++ 风格指南,并做了一些自定义调整。
|
||||
|
||||
### 安装 clang-format
|
||||
|
||||
在使用之前,请确保你已经安装了 clang-format 工具:
|
||||
|
||||
- **Windows**:
|
||||
```powershell
|
||||
winget install LLVM
|
||||
# 或者使用 Chocolatey
|
||||
choco install llvm
|
||||
```
|
||||
|
||||
- **Linux**:
|
||||
```bash
|
||||
sudo apt install clang-format # Ubuntu/Debian
|
||||
sudo dnf install clang-tools-extra # Fedora
|
||||
```
|
||||
|
||||
- **macOS**:
|
||||
```bash
|
||||
brew install clang-format
|
||||
```
|
||||
|
||||
### 使用方法
|
||||
|
||||
1. **格式化单个文件**:
|
||||
```bash
|
||||
clang-format -i path/to/your/file.cpp
|
||||
```
|
||||
|
||||
2. **格式化整个项目**:
|
||||
```bash
|
||||
# 在项目根目录下执行
|
||||
find main -iname *.h -o -iname *.cc | xargs clang-format -i
|
||||
```
|
||||
|
||||
3. **在提交代码前检查格式**:
|
||||
```bash
|
||||
# 检查文件格式是否符合规范(不修改文件)
|
||||
clang-format --dry-run -Werror path/to/your/file.cpp
|
||||
```
|
||||
|
||||
### IDE 集成
|
||||
|
||||
- **Visual Studio Code**:
|
||||
1. 安装 C/C++ 扩展
|
||||
2. 在设置中启用 `C_Cpp.formatting` 为 `clang-format`
|
||||
3. 可以设置保存时自动格式化:`editor.formatOnSave: true`
|
||||
|
||||
- **CLion**:
|
||||
1. 在设置中选择 `Editor > Code Style > C/C++`
|
||||
2. 将 `Formatter` 设置为 `clang-format`
|
||||
3. 选择使用项目中的 `.clang-format` 配置文件
|
||||
|
||||
### 主要格式规则
|
||||
|
||||
- 缩进使用 4 个空格
|
||||
- 行宽限制为 100 字符
|
||||
- 大括号采用 Attach 风格(与控制语句在同一行)
|
||||
- 指针和引用符号靠左对齐
|
||||
- 自动排序头文件包含
|
||||
- 类访问修饰符缩进为 -4 空格
|
||||
|
||||
### 注意事项
|
||||
|
||||
1. 提交代码前请确保代码已经过格式化
|
||||
2. 不要手动调整已格式化的代码对齐
|
||||
3. 如果某段代码不希望被格式化,可以使用以下注释包围:
|
||||
```cpp
|
||||
// clang-format off
|
||||
// 你的代码
|
||||
// clang-format on
|
||||
```
|
||||
|
||||
### 常见问题
|
||||
|
||||
1. **格式化失败**:
|
||||
- 检查 clang-format 版本是否过低
|
||||
- 确认文件编码为 UTF-8
|
||||
- 验证 .clang-format 文件语法是否正确
|
||||
|
||||
2. **与期望格式不符**:
|
||||
- 检查是否使用了项目根目录下的 .clang-format 配置
|
||||
- 确认没有其他位置的 .clang-format 文件被优先使用
|
||||
|
||||
如有任何问题或建议,欢迎提出 issue 或 pull request。
|
||||
@@ -197,8 +197,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -33,7 +33,7 @@ set(SOURCES "audio/audio_codec.cc"
|
||||
"application.cc"
|
||||
"ota.cc"
|
||||
"settings.cc"
|
||||
"device_state_event.cc"
|
||||
"device_state_machine.cc"
|
||||
"assets.cc"
|
||||
"main.cc"
|
||||
)
|
||||
@@ -41,10 +41,28 @@ set(SOURCES "audio/audio_codec.cc"
|
||||
set(INCLUDE_DIRS "." "display" "display/lvgl_display" "display/lvgl_display/jpg" "audio" "protocols")
|
||||
|
||||
# Add board common files
|
||||
file(GLOB BOARD_COMMON_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/boards/common/*.cc)
|
||||
list(APPEND SOURCES ${BOARD_COMMON_SOURCES})
|
||||
list(APPEND SOURCES
|
||||
"boards/common/board.cc"
|
||||
"boards/common/wifi_board.cc"
|
||||
"boards/common/ml307_board.cc"
|
||||
"boards/common/nt26_board.cc"
|
||||
"boards/common/dual_network_board.cc"
|
||||
"boards/common/adc_battery_monitor.cc"
|
||||
"boards/common/afsk_demod.cc"
|
||||
"boards/common/axp2101.cc"
|
||||
"boards/common/backlight.cc"
|
||||
"boards/common/button.cc"
|
||||
"boards/common/i2c_device.cc"
|
||||
"boards/common/knob.cc"
|
||||
"boards/common/power_save_timer.cc"
|
||||
"boards/common/press_to_talk_mcp_tool.cc"
|
||||
"boards/common/sleep_timer.cc"
|
||||
"boards/common/sy6970.cc"
|
||||
"boards/common/system_reset.cc"
|
||||
)
|
||||
list(APPEND INCLUDE_DIRS ${CMAKE_CURRENT_SOURCE_DIR}/boards/common)
|
||||
|
||||
|
||||
idf_build_get_property(build_components BUILD_COMPONENTS)
|
||||
# Function to find component dynamically by pattern
|
||||
function(find_component_by_pattern PATTERN COMPONENT_VAR PATH_VAR)
|
||||
@@ -62,6 +80,8 @@ endfunction()
|
||||
set(BUILTIN_TEXT_FONT font_puhui_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
|
||||
set(EMOTE_RESOLUTION "320_240")
|
||||
|
||||
# Add board files according to BOARD_TYPE
|
||||
# Set default assets if the board uses partition table V2
|
||||
if(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI)
|
||||
@@ -72,6 +92,10 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ML307)
|
||||
set(BOARD_TYPE "bread-compact-ml307")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_NT26)
|
||||
set(BOARD_TYPE "bread-compact-nt26")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32)
|
||||
set(BOARD_TYPE "bread-compact-esp32")
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
|
||||
@@ -80,26 +104,28 @@ elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_ESP32_LCD)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
elseif(CONFIG_BOARD_TYPE_DF_K10)
|
||||
set(BOARD_TYPE "df-k10")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_DF_S3_AI_CAM)
|
||||
set(BOARD_TYPE "df-s3-ai-cam")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_3)
|
||||
set(BOARD_TYPE "esp-box-3")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
set(EMOTE_RESOLUTION "320_240")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX)
|
||||
set(BOARD_TYPE "esp-box")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
set(EMOTE_RESOLUTION "320_240")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_BOX_LITE)
|
||||
set(BOARD_TYPE "esp-box-lite")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_BOX_2)
|
||||
set(BOARD_TYPE "kevin-box-2")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
@@ -108,14 +134,14 @@ elseif(CONFIG_BOARD_TYPE_KEVIN_C3)
|
||||
set(BOARD_TYPE "kevin-c3")
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V3_DEV)
|
||||
set(BOARD_TYPE "kevin-sp-v3-dev")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_SP_V4_DEV)
|
||||
set(BOARD_TYPE "kevin-sp-v4-dev")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
|
||||
set(BOARD_TYPE "kevin-yuying-313lcd")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@@ -123,9 +149,9 @@ elseif(CONFIG_BOARD_TYPE_KEVIN_YUYING_313LCD)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_S3)
|
||||
set(BOARD_TYPE "lichuang-dev")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_LICHUANG_DEV_C3)
|
||||
set(BOARD_TYPE "lichuang-c3-dev")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
@@ -175,6 +201,11 @@ elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_S3R_CAM_M12_ECHO_BASE)
|
||||
set(BOARD_TYPE "atoms3r-cam-m12-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_ECHOS3R)
|
||||
set(BOARD_TYPE "atom-echos3r")
|
||||
elseif(CONFIG_BOARD_TYPE_M5STACK_CARDPUTER_ADV)
|
||||
set(BOARD_TYPE "m5stack-cardputer-adv")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
|
||||
elseif(CONFIG_BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE)
|
||||
set(BOARD_TYPE "atommatrix-echo-base")
|
||||
elseif(CONFIG_BOARD_TYPE_XMINI_C3_V3)
|
||||
@@ -194,13 +225,20 @@ elseif(CONFIG_BOARD_TYPE_ESP_KORVO2_V3)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_KORVO2_V3_RNDIS)
|
||||
set(BOARD_TYPE "esp32s3-korvo2-v3-rndis")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_SPARKBOT)
|
||||
set(BOARD_TYPE "esp-sparkbot")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_S3)
|
||||
set(BOARD_TYPE "esp-spot-s3")
|
||||
set(BOARD_TYPE "esp-spot")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_SPOT_C5)
|
||||
set(BOARD_TYPE "esp-spot")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_HI)
|
||||
set(BOARD_TYPE "esp-hi")
|
||||
# Set ESP_HI emoji directory for DEFAULT_ASSETS_EXTRA_FILES
|
||||
@@ -210,6 +248,13 @@ elseif(CONFIG_BOARD_TYPE_ECHOEAR)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(EMOTE_RESOLUTION "360_360")
|
||||
# set(EMOTE_EXTERNAL_PATH "${CMAKE_CURRENT_SOURCE_DIR}/boards/echoear/assets")
|
||||
elseif(CONFIG_BOARD_TYPE_ESP_SENSAIRSHUTTLE)
|
||||
set(BOARD_TYPE "esp-sensairshuttle")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_16_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_AUDIO_BOARD)
|
||||
set(BOARD_TYPE "waveshare-s3-audio-board")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
@@ -220,6 +265,11 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_8)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_30_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_8)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-amoled-1.8")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_30_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_2_06)
|
||||
set(BOARD_TYPE "waveshare-s3-touch-amoled-2.06")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@@ -235,6 +285,11 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_30_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4_3C)
|
||||
set(BOARD_TYPE "waveshare-s3-touch-lcd-4.3c")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_30_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75)
|
||||
set(BOARD_TYPE "waveshare-s3-touch-amoled-1.75")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@@ -274,6 +329,10 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_ePaper_1_54)
|
||||
set(BOARD_TYPE "waveshare-s3-epaper-1.54")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_RLCD_4_2)
|
||||
set(BOARD_TYPE "waveshare-s3-rlcd-4.2")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_30_4)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49)
|
||||
set(BOARD_TYPE "waveshare-s3-touch-lcd-3.49")
|
||||
set(LVGL_TEXT_FONT font_puhui_basic_30_4)
|
||||
@@ -284,6 +343,11 @@ elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_LCD_1_69)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-lcd-1.83")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_16_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43)
|
||||
set(BOARD_TYPE "waveshare-c6-touch-amoled-1.43")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
@@ -365,6 +429,11 @@ elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI_ESP32S3)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_MOVECALL_MOJI2_ESP32C5)
|
||||
set(BOARD_TYPE "movecall-moji2-esp32c5")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
|
||||
set(BOARD_TYPE "movecall-cuican-esp32s3")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
@@ -372,29 +441,29 @@ elseif(CONFIG_BOARD_TYPE_MOVECALL_CUICAN_ESP32S3)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3)
|
||||
set(BOARD_TYPE "atk-dnesp32s3")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX)
|
||||
set(BOARD_TYPE "atk-dnesp32s3-box")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX0)
|
||||
set(BOARD_TYPE "atk-dnesp32s3-box0")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_WIFI)
|
||||
set(BOARD_TYPE "atk-dnesp32s3-box2-wifi")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3_BOX2_4G)
|
||||
set(BOARD_TYPE "atk-dnesp32s3-box2-4g")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_ATK_DNESP32S3M_WIFI)
|
||||
set(BOARD_TYPE "atk-dnesp32s3m-wifi")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
@@ -435,19 +504,24 @@ elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_0_96OLED_ML307)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_WIFI)
|
||||
set(BOARD_TYPE "xingzhi-cube-1.54tft-wifi")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307)
|
||||
set(BOARD_TYPE "xingzhi-cube-1.54tft-ml307")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_XINGZHI_METAL_1_54_WIFI)
|
||||
set(BOARD_TYPE "xingzhi-metal-1.54-wifi")
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER)
|
||||
set(BOARD_TYPE "sensecap-watcher")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_30_4)
|
||||
set(BUILTIN_TEXT_FONT font_noto_basic_30_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
set(DEFAULT_EMOJI_COLLECTION noto-emoji_128)
|
||||
elseif(CONFIG_BOARD_TYPE_DOIT_S3_AIBOX)
|
||||
set(BOARD_TYPE "doit-s3-aibox")
|
||||
elseif(CONFIG_BOARD_TYPE_MIXGO_NOVA)
|
||||
@@ -493,6 +567,16 @@ elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_CAM)
|
||||
set(BOARD_TYPE "zhengchen-cam")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_ZHENGCHEN_CAM_ML307)
|
||||
set(BOARD_TYPE "zhengchen-cam-ml307")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_64)
|
||||
elseif(CONFIG_BOARD_TYPE_SPOTPEAR_ESP32_S3_1_54_MUMA)
|
||||
set(BOARD_TYPE "sp-esp32-s3-1.54-muma")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
@@ -507,10 +591,12 @@ elseif(CONFIG_BOARD_TYPE_OTTO_ROBOT)
|
||||
set(BOARD_TYPE "otto-robot")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_16_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_16_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION otto-gif)
|
||||
elseif(CONFIG_BOARD_TYPE_ELECTRON_BOT)
|
||||
set(BOARD_TYPE "electron-bot")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_20_4)
|
||||
set(BUILTIN_ICON_FONT font_awesome_20_4)
|
||||
set(DEFAULT_EMOJI_COLLECTION otto-gif)
|
||||
elseif(CONFIG_BOARD_TYPE_BREAD_COMPACT_WIFI_CAM)
|
||||
set(BOARD_TYPE "bread-compact-wifi-s3cam")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_16_4)
|
||||
@@ -551,6 +637,10 @@ elseif(CONFIG_BOARD_TYPE_AIPI_LITE)
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
set(DEFAULT_EMOJI_COLLECTION twemoji_32)
|
||||
elseif(CONFIG_BOARD_TYPE_HU_087)
|
||||
set(BOARD_TYPE "hu-087")
|
||||
set(BUILTIN_TEXT_FONT font_puhui_basic_14_1)
|
||||
set(BUILTIN_ICON_FONT font_awesome_14_1)
|
||||
endif()
|
||||
|
||||
file(GLOB BOARD_SOURCES
|
||||
@@ -572,6 +662,10 @@ else()
|
||||
list(APPEND SOURCES "audio/wake_words/esp_wake_word.cc")
|
||||
endif()
|
||||
|
||||
# Auto Select Additional Sources
|
||||
if (CONFIG_USE_ESP_BLUFI_WIFI_PROVISIONING)
|
||||
list(APPEND SOURCES "boards/common/blufi.cpp")
|
||||
endif ()
|
||||
# Select language directory according to Kconfig
|
||||
if(CONFIG_LANGUAGE_ZH_CN)
|
||||
set(LANG_DIR "zh-CN")
|
||||
@@ -687,16 +781,47 @@ if(CONFIG_IDF_TARGET_ESP32)
|
||||
"audio/codecs/es8388_audio_codec.cc"
|
||||
"audio/codecs/es8389_audio_codec.cc"
|
||||
"led/gpio_led.cc"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/boards/common/esp32_camera.cc"
|
||||
"display/lvgl_display/jpg/image_to_jpeg.cpp"
|
||||
"display/lvgl_display/jpg/jpeg_to_image.c"
|
||||
"boards/common/nt26_board.cc"
|
||||
"boards/common/ml307_board.cc"
|
||||
"boards/common/dual_network_board.cc"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Include EspVideo if target is ESP32S3 or ESP32P4
|
||||
if(CONFIG_IDF_TARGET_ESP32S3 OR CONFIG_IDF_TARGET_ESP32P4)
|
||||
list(APPEND SOURCES "boards/common/esp_video.cc"
|
||||
"boards/common/rndis_board.cc"
|
||||
)
|
||||
endif()
|
||||
|
||||
# Include Esp32Camera if target is ESP32S3
|
||||
if(CONFIG_IDF_TARGET_ESP32S3)
|
||||
list(APPEND SOURCES "boards/common/esp32_camera.cc")
|
||||
endif()
|
||||
|
||||
idf_component_register(SRCS ${SOURCES}
|
||||
EMBED_FILES ${LANG_SOUNDS} ${COMMON_SOUNDS}
|
||||
INCLUDE_DIRS ${INCLUDE_DIRS}
|
||||
WHOLE_ARCHIVE
|
||||
PRIV_REQUIRES
|
||||
esp_pm
|
||||
esp_psram
|
||||
esp_netif
|
||||
esp_driver_gpio
|
||||
esp_driver_uart
|
||||
esp_driver_spi
|
||||
esp_driver_i2c
|
||||
esp_driver_i2s
|
||||
esp_driver_jpeg
|
||||
esp_driver_ppa
|
||||
esp_app_format
|
||||
app_update
|
||||
spi_flash
|
||||
console
|
||||
efuse
|
||||
bt
|
||||
)
|
||||
|
||||
# Use target_compile_definitions to define BOARD_TYPE, BOARD_NAME
|
||||
@@ -898,6 +1023,14 @@ if ("${size}" AND "${offset}")
|
||||
get_assets_local_file("${CONFIG_CUSTOM_ASSETS_FILE}" ASSETS_LOCAL_FILE)
|
||||
esptool_py_flash_to_partition(flash "assets" "${ASSETS_LOCAL_FILE}")
|
||||
message(STATUS "Custom assets flash configured: ${ASSETS_LOCAL_FILE} -> assets partition")
|
||||
elseif(CONFIG_FLASH_EXPRESSION_ASSETS)
|
||||
set(ASSETS_NAME "expression_assets")
|
||||
set(ASSETS_PARTITION "assets")
|
||||
set(ASSETS_FILE "${CMAKE_BINARY_DIR}/${ASSETS_NAME}.bin")
|
||||
|
||||
build_speaker_assets_bin("${ASSETS_PARTITION}" ${EMOTE_RESOLUTION} ${ASSETS_FILE} ${CONFIG_MMAP_FILE_NAME_LENGTH})
|
||||
message(STATUS "Generated emote assets: ${ASSETS_FILE} -> ${ASSETS_PARTITION} partition")
|
||||
esptool_py_flash_to_partition(flash "${ASSETS_PARTITION}" "${ASSETS_FILE}")
|
||||
elseif(CONFIG_FLASH_NONE_ASSETS)
|
||||
message(STATUS "Assets flashing disabled (FLASH_NONE_ASSETS)")
|
||||
endif()
|
||||
|
||||
@@ -8,7 +8,8 @@ config OTA_URL
|
||||
|
||||
choice
|
||||
prompt "Flash Assets"
|
||||
default FLASH_DEFAULT_ASSETS
|
||||
default FLASH_DEFAULT_ASSETS if !USE_EMOTE_MESSAGE_STYLE
|
||||
default FLASH_EXPRESSION_ASSETS if USE_EMOTE_MESSAGE_STYLE
|
||||
help
|
||||
Select the assets to flash.
|
||||
|
||||
@@ -16,8 +17,12 @@ choice
|
||||
bool "Do not flash assets"
|
||||
config FLASH_DEFAULT_ASSETS
|
||||
bool "Flash Default Assets"
|
||||
depends on !USE_EMOTE_MESSAGE_STYLE
|
||||
config FLASH_CUSTOM_ASSETS
|
||||
bool "Flash Custom Assets"
|
||||
config FLASH_EXPRESSION_ASSETS
|
||||
bool "Flash Emote Assets"
|
||||
depends on USE_EMOTE_MESSAGE_STYLE
|
||||
endchoice
|
||||
|
||||
config CUSTOM_ASSETS_FILE
|
||||
@@ -129,6 +134,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_BREAD_COMPACT_ML307
|
||||
bool "Bread Compact ML307/EC801E (面包板 4G)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_BREAD_COMPACT_NT26
|
||||
bool "Bread Compact NT26 (面包板 4G)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_BREAD_COMPACT_ESP32
|
||||
bool "Bread Compact ESP32 DevKit (面包板)"
|
||||
depends on IDF_TARGET_ESP32
|
||||
@@ -147,12 +155,21 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_ESP_KORVO2_V3
|
||||
bool "Espressif Korvo2 V3"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP_KORVO2_V3_RNDIS
|
||||
bool "Espressif Korvo2 V3 RNDIS"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP_SPARKBOT
|
||||
bool "Espressif SparkBot"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP_SENSAIRSHUTTLE
|
||||
bool "Espressif ESP-SensairShuttle"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_ESP_SPOT_S3
|
||||
bool "Espressif Spot-S3"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ESP_SPOT_C5
|
||||
bool "Espressif Spot-C5"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_ESP_HI
|
||||
bool "Espressif ESP-HI"
|
||||
depends on IDF_TARGET_ESP32C3
|
||||
@@ -234,6 +251,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_M5STACK_ATOM_ECHOS3R
|
||||
bool "M5Stack AtomEchoS3R"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_M5STACK_CARDPUTER_ADV
|
||||
bool "M5Stack Cardputer Adv"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_M5STACK_ATOM_MATRIX_ECHO_BASE
|
||||
bool "M5Stack AtomMatrix + Echo Base"
|
||||
depends on IDF_TARGET_ESP32
|
||||
@@ -258,6 +278,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B
|
||||
bool "Waveshare ESP32-S3-Touch-LCD-4B"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4_3C
|
||||
bool "Waveshare ESP32-S3-Touch-LCD-4.3C"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_85C
|
||||
bool "Waveshare ESP32-S3-Touch-LCD-1.85C"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -270,12 +293,18 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_C6_LCD_1_69
|
||||
bool "Waveshare ESP32-C6-LCD-1.69"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_LCD_1_83
|
||||
bool "Waveshare ESP32-C6-Touch-LCD-1.83"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_43
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.43"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_32
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLOED-1.32"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_C6_TOUCH_AMOLED_1_8
|
||||
bool "Waveshare ESP32-C6-Touch-AMOLED-1.8"
|
||||
depends on IDF_TARGET_ESP32C6
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_32
|
||||
bool "Waveshare ESP32-S3-Touch-AMOLOED-1.32"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -288,6 +317,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_WAVESHARE_S3_ePaper_1_54
|
||||
bool "Waveshare ESP32-S3-ePaper-1.54"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_WAVESHARE_S3_RLCD_4_2
|
||||
bool "Waveshare ESP32-S3-RLCD-4.2"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_5B
|
||||
bool "Waveshare ESP32-S3-Touch-LCD-3.5B"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -327,6 +359,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_MOVECALL_MOJI_ESP32S3
|
||||
bool "Movecall Moji 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_MOVECALL_MOJI2_ESP32C5
|
||||
bool "Movecall Moji2.0 小智AI衍生版"
|
||||
depends on IDF_TARGET_ESP32C5
|
||||
config BOARD_TYPE_MOVECALL_CUICAN_ESP32S3
|
||||
bool "Movecall CuiCan 璀璨·AI吊坠"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -375,6 +410,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_XINGZHI_CUBE_1_54TFT_ML307
|
||||
bool "无名科技星智1.54(ML307)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_XINGZHI_METAL_1_54_WIFI
|
||||
bool "无名科技星智1.54 METAL(wifi)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_SEEED_STUDIO_SENSECAP_WATCHER
|
||||
bool "Seeed Studio SenseCAP Watcher"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -399,6 +437,12 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_ZHENGCHEN_1_54TFT_ML307
|
||||
bool "征辰科技1.54(ML307)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ZHENGCHEN_CAM
|
||||
bool "征辰科技AI Camera"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_ZHENGCHEN_CAM_ML307
|
||||
bool "征辰科技AI Camera(ML307)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_MINSI_K08_DUAL
|
||||
bool "敏思科技K08(DUAL)"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
@@ -435,6 +479,9 @@ choice BOARD_TYPE
|
||||
config BOARD_TYPE_AIPI_LITE
|
||||
bool "AIPI-Lite"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
config BOARD_TYPE_HU_087
|
||||
bool "HU-087"
|
||||
depends on IDF_TARGET_ESP32S3
|
||||
endchoice
|
||||
|
||||
choice
|
||||
@@ -468,7 +515,7 @@ choice ESP_S3_LCD_EV_Board_Version_TYPE
|
||||
endchoice
|
||||
|
||||
choice DISPLAY_OLED_TYPE
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_ESP32
|
||||
depends on BOARD_TYPE_BREAD_COMPACT_WIFI || BOARD_TYPE_BREAD_COMPACT_ML307 || BOARD_TYPE_BREAD_COMPACT_NT26 || BOARD_TYPE_BREAD_COMPACT_ESP32 || BOARD_TYPE_HU_087
|
||||
prompt "OLED Type"
|
||||
default OLED_SSD1306_128X32
|
||||
help
|
||||
@@ -530,7 +577,7 @@ choice DISPLAY_LCD_TYPE
|
||||
endchoice
|
||||
|
||||
choice DISPLAY_ESP32S3_KORVO2_V3
|
||||
depends on BOARD_TYPE_ESP_KORVO2_V3
|
||||
depends on BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_ESP_KORVO2_V3_RNDIS
|
||||
prompt "ESP32S3_KORVO2_V3 LCD Type"
|
||||
default ESP32S3_KORVO2_V3_LCD_ST7789
|
||||
help
|
||||
@@ -567,7 +614,9 @@ choice DISPLAY_STYLE
|
||||
|
||||
config USE_EMOTE_MESSAGE_STYLE
|
||||
bool "Emote animation style"
|
||||
depends on BOARD_TYPE_ESP_BOX_3 || BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3
|
||||
depends on BOARD_TYPE_ESP_BOX || BOARD_TYPE_ESP_BOX_3 \
|
||||
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_LICHUANG_DEV_S3 \
|
||||
|| BOARD_TYPE_ESP_SENSAIRSHUTTLE
|
||||
endchoice
|
||||
|
||||
choice WAKE_WORD_TYPE
|
||||
@@ -631,6 +680,16 @@ config SEND_WAKE_WORD_DATA
|
||||
help
|
||||
Send wake word data to the server as the first message of the conversation and wait for response
|
||||
|
||||
config WAKE_WORD_DETECTION_IN_LISTENING
|
||||
bool "Enable Wake Word Detection in Listening Mode"
|
||||
default n
|
||||
depends on USE_AFE_WAKE_WORD || USE_CUSTOM_WAKE_WORD
|
||||
help
|
||||
Enable wake word detection while in listening mode.
|
||||
When enabled, the device can detect wake word during listening,
|
||||
which allows interrupting the current conversation.
|
||||
When disabled (default), wake word detection is turned off during listening.
|
||||
|
||||
config USE_AUDIO_PROCESSOR
|
||||
bool "Enable Audio Noise Reduction"
|
||||
default y
|
||||
@@ -645,7 +704,8 @@ config USE_DEVICE_AEC
|
||||
|| BOARD_TYPE_LICHUANG_DEV_S3 || BOARD_TYPE_ESP_KORVO2_V3 || BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_1_75 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_1_83\
|
||||
|| BOARD_TYPE_WAVESHARE_S3_TOUCH_AMOLED_2_06 || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_4B || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_7B \
|
||||
|| BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_XC || BOARD_TYPE_ESP_S3_LCD_EV_Board_2 || BOARD_TYPE_YUNLIAO_S3 \
|
||||
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49)
|
||||
|| BOARD_TYPE_ECHOEAR || BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_3_49 || BOARD_TYPE_WAVESHARE_S3_RLCD_4_2 || BOARD_TYPE_ZHENGCHEN_CAM || BOARD_TYPE_ZHENGCHEN_CAM_ML307 \
|
||||
|| BOARD_TYPE_WAVESHARE_S3_TOUCH_LCD_4_3C)
|
||||
help
|
||||
To work properly, device-side AEC requires a clean output reference path from the speaker signal and physical acoustic isolation between the microphone and speaker.
|
||||
|
||||
@@ -662,6 +722,29 @@ config USE_AUDIO_DEBUGGER
|
||||
help
|
||||
Enable audio debugger, send audio data through UDP to the host machine
|
||||
|
||||
menu "WiFi Configuration Method"
|
||||
help
|
||||
WiFi Configuration Method Selection
|
||||
config USE_HOTSPOT_WIFI_PROVISIONING
|
||||
bool "Hotspot"
|
||||
default y
|
||||
help
|
||||
Use WiFi Hotspot to transmit WiFi configuration data
|
||||
config USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
bool "Acoustic"
|
||||
help
|
||||
Use audio signal to transmit WiFi configuration data
|
||||
|
||||
config USE_ESP_BLUFI_WIFI_PROVISIONING
|
||||
bool "Esp Blufi"
|
||||
help
|
||||
Use esp blufi protocol to transmit WiFi configuration data
|
||||
select BT_ENABLED
|
||||
select BT_BLE_42_FEATURES_SUPPORTED
|
||||
select BT_BLE_BLUFI_ENABLE
|
||||
select MBEDTLS_DHM_C
|
||||
endmenu
|
||||
|
||||
config AUDIO_DEBUG_UDP_SERVER
|
||||
string "Audio Debug UDP Server Address"
|
||||
default "192.168.2.100:8000"
|
||||
@@ -669,12 +752,6 @@ config AUDIO_DEBUG_UDP_SERVER
|
||||
help
|
||||
UDP server address, format: IP:PORT, used to receive audio debugging data
|
||||
|
||||
config USE_ACOUSTIC_WIFI_PROVISIONING
|
||||
bool "Enable Acoustic WiFi Provisioning"
|
||||
default n
|
||||
help
|
||||
Enable acoustic WiFi provisioning, use audio signal to transmit WiFi configuration data
|
||||
|
||||
config RECEIVE_CUSTOM_MESSAGE
|
||||
bool "Enable Custom Message Reception"
|
||||
default n
|
||||
@@ -685,7 +762,7 @@ menu "Camera Configuration"
|
||||
depends on !IDF_TARGET_ESP32
|
||||
|
||||
comment "Warning: Please read the help text before modifying these settings."
|
||||
|
||||
|
||||
config XIAOZHI_CAMERA_ALLOW_JPEG_INPUT
|
||||
bool "Allow JPEG Input"
|
||||
default n
|
||||
|
||||
1164
main/application.cc
1164
main/application.cc
File diff suppressed because it is too large
Load Diff
@@ -14,16 +14,23 @@
|
||||
#include "protocol.h"
|
||||
#include "ota.h"
|
||||
#include "audio_service.h"
|
||||
#include "device_state_event.h"
|
||||
#include "device_state.h"
|
||||
#include "device_state_machine.h"
|
||||
|
||||
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_CHECK_NEW_VERSION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
// Main event bits
|
||||
#define MAIN_EVENT_SCHEDULE (1 << 0)
|
||||
#define MAIN_EVENT_SEND_AUDIO (1 << 1)
|
||||
#define MAIN_EVENT_WAKE_WORD_DETECTED (1 << 2)
|
||||
#define MAIN_EVENT_VAD_CHANGE (1 << 3)
|
||||
#define MAIN_EVENT_ERROR (1 << 4)
|
||||
#define MAIN_EVENT_ACTIVATION_DONE (1 << 5)
|
||||
#define MAIN_EVENT_CLOCK_TICK (1 << 6)
|
||||
#define MAIN_EVENT_NETWORK_CONNECTED (1 << 7)
|
||||
#define MAIN_EVENT_NETWORK_DISCONNECTED (1 << 8)
|
||||
#define MAIN_EVENT_TOGGLE_CHAT (1 << 9)
|
||||
#define MAIN_EVENT_START_LISTENING (1 << 10)
|
||||
#define MAIN_EVENT_STOP_LISTENING (1 << 11)
|
||||
#define MAIN_EVENT_STATE_CHANGED (1 << 12)
|
||||
|
||||
|
||||
enum AecMode {
|
||||
@@ -38,31 +45,80 @@ public:
|
||||
static Application instance;
|
||||
return instance;
|
||||
}
|
||||
// 删除拷贝构造函数和赋值运算符
|
||||
// Delete copy constructor and assignment operator
|
||||
Application(const Application&) = delete;
|
||||
Application& operator=(const Application&) = delete;
|
||||
|
||||
void Start();
|
||||
void MainEventLoop();
|
||||
DeviceState GetDeviceState() const { return device_state_; }
|
||||
/**
|
||||
* Initialize the application
|
||||
* This sets up display, audio, network callbacks, etc.
|
||||
* Network connection starts asynchronously.
|
||||
*/
|
||||
void Initialize();
|
||||
|
||||
/**
|
||||
* Run the main event loop
|
||||
* This function runs in the main task and never returns.
|
||||
* It handles all events including network, state changes, and user interactions.
|
||||
*/
|
||||
void Run();
|
||||
|
||||
DeviceState GetDeviceState() const { return state_machine_.GetState(); }
|
||||
bool IsVoiceDetected() const { return audio_service_.IsVoiceDetected(); }
|
||||
void Schedule(std::function<void()> callback);
|
||||
void SetDeviceState(DeviceState state);
|
||||
|
||||
/**
|
||||
* Request state transition
|
||||
* Returns true if transition was successful
|
||||
*/
|
||||
bool SetDeviceState(DeviceState state);
|
||||
|
||||
/**
|
||||
* Schedule a callback to be executed in the main task
|
||||
*/
|
||||
void Schedule(std::function<void()>&& callback);
|
||||
|
||||
/**
|
||||
* Alert with status, message, emotion and optional sound
|
||||
*/
|
||||
void Alert(const char* status, const char* message, const char* emotion = "", const std::string_view& sound = "");
|
||||
void DismissAlert();
|
||||
|
||||
void AbortSpeaking(AbortReason reason);
|
||||
|
||||
/**
|
||||
* Toggle chat state (event-based, thread-safe)
|
||||
* Sends MAIN_EVENT_TOGGLE_CHAT to be handled in Run()
|
||||
*/
|
||||
void ToggleChatState();
|
||||
|
||||
/**
|
||||
* Start listening (event-based, thread-safe)
|
||||
* Sends MAIN_EVENT_START_LISTENING to be handled in Run()
|
||||
*/
|
||||
void StartListening();
|
||||
|
||||
/**
|
||||
* Stop listening (event-based, thread-safe)
|
||||
* Sends MAIN_EVENT_STOP_LISTENING to be handled in Run()
|
||||
*/
|
||||
void StopListening();
|
||||
|
||||
void Reboot();
|
||||
void WakeWordInvoke(const std::string& wake_word);
|
||||
bool UpgradeFirmware(Ota& ota, const std::string& url = "");
|
||||
bool UpgradeFirmware(const std::string& url, const std::string& version = "");
|
||||
bool CanEnterSleepMode();
|
||||
void SendMcpMessage(const std::string& payload);
|
||||
void SetAecMode(AecMode mode);
|
||||
AecMode GetAecMode() const { return aec_mode_; }
|
||||
void PlaySound(const std::string_view& sound);
|
||||
AudioService& GetAudioService() { return audio_service_; }
|
||||
|
||||
/**
|
||||
* Reset protocol resources (thread-safe)
|
||||
* Can be called from any task to release resources allocated after network connected
|
||||
* This includes closing audio channel, resetting protocol and ota objects
|
||||
*/
|
||||
void ResetProtocol();
|
||||
|
||||
private:
|
||||
Application();
|
||||
@@ -73,23 +129,46 @@ private:
|
||||
std::unique_ptr<Protocol> protocol_;
|
||||
EventGroupHandle_t event_group_ = nullptr;
|
||||
esp_timer_handle_t clock_timer_handle_ = nullptr;
|
||||
volatile DeviceState device_state_ = kDeviceStateUnknown;
|
||||
DeviceStateMachine state_machine_;
|
||||
ListeningMode listening_mode_ = kListeningModeAutoStop;
|
||||
AecMode aec_mode_ = kAecOff;
|
||||
std::string last_error_message_;
|
||||
AudioService audio_service_;
|
||||
std::unique_ptr<Ota> ota_;
|
||||
|
||||
bool has_server_time_ = false;
|
||||
bool aborted_ = false;
|
||||
bool assets_version_checked_ = false;
|
||||
bool play_popup_on_listening_ = false; // Flag to play popup sound after state changes to listening
|
||||
int clock_ticks_ = 0;
|
||||
TaskHandle_t check_new_version_task_handle_ = nullptr;
|
||||
TaskHandle_t main_event_loop_task_handle_ = nullptr;
|
||||
TaskHandle_t activation_task_handle_ = nullptr;
|
||||
|
||||
void OnWakeWordDetected();
|
||||
void CheckNewVersion(Ota& ota);
|
||||
|
||||
// Event handlers
|
||||
void HandleStateChangedEvent();
|
||||
void HandleToggleChatEvent();
|
||||
void HandleStartListeningEvent();
|
||||
void HandleStopListeningEvent();
|
||||
void HandleNetworkConnectedEvent();
|
||||
void HandleNetworkDisconnectedEvent();
|
||||
void HandleActivationDoneEvent();
|
||||
void HandleWakeWordDetectedEvent();
|
||||
void ContinueOpenAudioChannel(ListeningMode mode);
|
||||
void ContinueWakeWordInvoke(const std::string& wake_word);
|
||||
|
||||
// Activation task (runs in background)
|
||||
void ActivationTask();
|
||||
|
||||
// Helper methods
|
||||
void CheckAssetsVersion();
|
||||
void CheckNewVersion();
|
||||
void InitializeProtocol();
|
||||
void ShowActivationCode(const std::string& code, const std::string& message);
|
||||
void SetListeningMode(ListeningMode mode);
|
||||
ListeningMode GetDefaultListeningMode() const;
|
||||
|
||||
// State change handler called by state machine
|
||||
void OnStateChanged(DeviceState old_state, DeviceState new_state);
|
||||
};
|
||||
|
||||
|
||||
|
||||
404
main/assets.cc
404
main/assets.cc
@@ -4,17 +4,20 @@
|
||||
#include "application.h"
|
||||
#include "lvgl_theme.h"
|
||||
#include "emote_display.h"
|
||||
#ifdef HAVE_LVGL
|
||||
#include "expression_emote.h"
|
||||
#if HAVE_LVGL
|
||||
#include "display/lcd_display.h"
|
||||
#include <spi_flash_mmap.h>
|
||||
#endif
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <spi_flash_mmap.h>
|
||||
#include <esp_timer.h>
|
||||
#include <esp_heap_caps.h>
|
||||
#include <cbin_font.h>
|
||||
|
||||
|
||||
#define TAG "Assets"
|
||||
#define PARTITION_LABEL "assets"
|
||||
|
||||
struct mmap_assets_table {
|
||||
char asset_name[32]; /*!< Name of the asset */
|
||||
@@ -24,19 +27,99 @@ struct mmap_assets_table {
|
||||
uint16_t asset_height; /*!< Height of the asset */
|
||||
};
|
||||
|
||||
|
||||
Assets::Assets() {
|
||||
#if HAVE_LVGL
|
||||
strategy_ = std::make_unique<Assets::LvglStrategy>();
|
||||
#else
|
||||
strategy_ = std::make_unique<Assets::EmoteStrategy>();
|
||||
#endif
|
||||
// Initialize the partition
|
||||
InitializePartition();
|
||||
}
|
||||
|
||||
Assets::~Assets() {
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
UnApplyPartition();
|
||||
}
|
||||
|
||||
bool Assets::FindPartition(Assets* assets) {
|
||||
assets->partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, PARTITION_LABEL);
|
||||
if (assets->partition_ == nullptr) {
|
||||
ESP_LOGI(TAG, "No assets partition found");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::Apply() {
|
||||
return strategy_ ? strategy_->Apply(this) : false;
|
||||
}
|
||||
|
||||
bool Assets::InitializePartition() {
|
||||
return strategy_ ? strategy_->InitializePartition(this) : false;
|
||||
}
|
||||
|
||||
void Assets::UnApplyPartition() {
|
||||
if (strategy_) {
|
||||
strategy_->UnApplyPartition(this);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t Assets::CalculateChecksum(const char* data, uint32_t length) {
|
||||
bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) {
|
||||
return strategy_ ? strategy_->GetAssetData(this, name, ptr, size) : false;
|
||||
}
|
||||
|
||||
bool Assets::LoadSrmodelsFromIndex(Assets* assets, cJSON* root) {
|
||||
void* ptr = nullptr;
|
||||
size_t size = 0;
|
||||
bool need_delete_root = false;
|
||||
|
||||
// If root is not provided, parse index.json
|
||||
if (root == nullptr) {
|
||||
if (!assets->GetAssetData("index.json", ptr, size)) {
|
||||
ESP_LOGE(TAG, "The index.json file is not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
root = cJSON_ParseWithLength(static_cast<char*>(ptr), size);
|
||||
if (root == nullptr) {
|
||||
ESP_LOGE(TAG, "The index.json file is not valid");
|
||||
return false;
|
||||
}
|
||||
need_delete_root = true;
|
||||
}
|
||||
|
||||
cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels");
|
||||
if (cJSON_IsString(srmodels)) {
|
||||
std::string srmodels_file = srmodels->valuestring;
|
||||
if (assets->GetAssetData(srmodels_file, ptr, size)) {
|
||||
if (assets->models_list_ != nullptr) {
|
||||
esp_srmodel_deinit(assets->models_list_);
|
||||
assets->models_list_ = nullptr;
|
||||
}
|
||||
assets->models_list_ = srmodel_load(static_cast<uint8_t*>(ptr));
|
||||
if (assets->models_list_ != nullptr) {
|
||||
auto& app = Application::GetInstance();
|
||||
app.GetAudioService().SetModelsList(assets->models_list_);
|
||||
if (need_delete_root) {
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to load srmodels.bin");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
if (need_delete_root) {
|
||||
cJSON_Delete(root);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
#if HAVE_LVGL
|
||||
uint32_t Assets::LvglStrategy::CalculateChecksum(const char* data, uint32_t length) {
|
||||
uint32_t checksum = 0;
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
checksum += data[i];
|
||||
@@ -44,40 +127,37 @@ uint32_t Assets::CalculateChecksum(const char* data, uint32_t length) {
|
||||
return checksum & 0xFFFF;
|
||||
}
|
||||
|
||||
bool Assets::InitializePartition() {
|
||||
partition_valid_ = false;
|
||||
checksum_valid_ = false;
|
||||
bool Assets::LvglStrategy::InitializePartition(Assets* assets) {
|
||||
assets->partition_valid_ = false;
|
||||
assets_.clear();
|
||||
|
||||
partition_ = esp_partition_find_first(ESP_PARTITION_TYPE_ANY, ESP_PARTITION_SUBTYPE_ANY, "assets");
|
||||
if (partition_ == nullptr) {
|
||||
ESP_LOGI(TAG, "No assets partition found");
|
||||
if (!Assets::FindPartition(assets)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int free_pages = spi_flash_mmap_get_free_pages(SPI_FLASH_MMAP_DATA);
|
||||
uint32_t storage_size = free_pages * 64 * 1024;
|
||||
ESP_LOGI(TAG, "The storage free size is %ld KB", storage_size / 1024);
|
||||
ESP_LOGI(TAG, "The partition size is %ld KB", partition_->size / 1024);
|
||||
if (storage_size < partition_->size) {
|
||||
ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, partition_->size / 1024);
|
||||
ESP_LOGI(TAG, "The partition size is %ld KB", assets->partition_->size / 1024);
|
||||
if (storage_size < assets->partition_->size) {
|
||||
ESP_LOGE(TAG, "The free size %ld KB is less than assets partition required %ld KB", storage_size / 1024, assets->partition_->size / 1024);
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t err = esp_partition_mmap(partition_, 0, partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_);
|
||||
esp_err_t err = esp_partition_mmap(assets->partition_, 0, assets->partition_->size, ESP_PARTITION_MMAP_DATA, (const void**)&mmap_root_, &mmap_handle_);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to mmap assets partition: %s", esp_err_to_name(err));
|
||||
return false;
|
||||
}
|
||||
|
||||
partition_valid_ = true;
|
||||
assets->partition_valid_ = true;
|
||||
|
||||
uint32_t stored_files = *(uint32_t*)(mmap_root_ + 0);
|
||||
uint32_t stored_chksum = *(uint32_t*)(mmap_root_ + 4);
|
||||
uint32_t stored_len = *(uint32_t*)(mmap_root_ + 8);
|
||||
|
||||
if (stored_len > partition_->size - 12) {
|
||||
ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, partition_->size);
|
||||
if (stored_len > assets->partition_->size - 12) {
|
||||
ESP_LOGD(TAG, "The stored_len (0x%lx) is greater than the partition size (0x%lx) - 12", stored_len, assets->partition_->size);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -104,10 +184,37 @@ bool Assets::InitializePartition() {
|
||||
return checksum_valid_;
|
||||
}
|
||||
|
||||
bool Assets::Apply() {
|
||||
void Assets::LvglStrategy::UnApplyPartition(Assets* assets) {
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
mmap_handle_ = 0;
|
||||
mmap_root_ = nullptr;
|
||||
}
|
||||
checksum_valid_ = false;
|
||||
assets_.clear();
|
||||
(void)assets; // Unused parameter
|
||||
}
|
||||
|
||||
bool Assets::LvglStrategy::GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) {
|
||||
auto asset = assets_.find(name);
|
||||
if (asset == assets_.end()) {
|
||||
return false;
|
||||
}
|
||||
auto data = (const char*)(mmap_root_ + asset->second.offset);
|
||||
if (data[0] != 'Z' || data[1] != 'Z') {
|
||||
ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = static_cast<void*>(const_cast<char*>(data + 2));
|
||||
size = asset->second.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::LvglStrategy::Apply(Assets* assets) {
|
||||
void* ptr = nullptr;
|
||||
size_t size = 0;
|
||||
if (!GetAssetData("index.json", ptr, size)) {
|
||||
if (!assets->GetAssetData("index.json", ptr, size)) {
|
||||
ESP_LOGE(TAG, "The index.json file is not found");
|
||||
return false;
|
||||
}
|
||||
@@ -125,28 +232,9 @@ bool Assets::Apply() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* srmodels = cJSON_GetObjectItem(root, "srmodels");
|
||||
if (cJSON_IsString(srmodels)) {
|
||||
std::string srmodels_file = srmodels->valuestring;
|
||||
if (GetAssetData(srmodels_file, ptr, size)) {
|
||||
if (models_list_ != nullptr) {
|
||||
esp_srmodel_deinit(models_list_);
|
||||
models_list_ = nullptr;
|
||||
}
|
||||
models_list_ = srmodel_load(static_cast<uint8_t*>(ptr));
|
||||
if (models_list_ != nullptr) {
|
||||
auto& app = Application::GetInstance();
|
||||
app.GetAudioService().SetModelsList(models_list_);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to load srmodels.bin");
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The srmodels file %s is not found", srmodels_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef HAVE_LVGL
|
||||
Assets::LoadSrmodelsFromIndex(assets, root);
|
||||
|
||||
auto& theme_manager = LvglThemeManager::GetInstance();
|
||||
auto light_theme = theme_manager.GetTheme("light");
|
||||
auto dark_theme = theme_manager.GetTheme("dark");
|
||||
@@ -154,7 +242,7 @@ bool Assets::Apply() {
|
||||
cJSON* font = cJSON_GetObjectItem(root, "text_font");
|
||||
if (cJSON_IsString(font)) {
|
||||
std::string fonts_text_file = font->valuestring;
|
||||
if (GetAssetData(fonts_text_file, ptr, size)) {
|
||||
if (assets->GetAssetData(fonts_text_file, ptr, size)) {
|
||||
auto text_font = std::make_shared<LvglCBinFont>(ptr);
|
||||
if (text_font->font() == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to load fonts.bin");
|
||||
@@ -182,7 +270,7 @@ bool Assets::Apply() {
|
||||
cJSON* file = cJSON_GetObjectItem(emoji, "file");
|
||||
cJSON* eaf = cJSON_GetObjectItem(emoji, "eaf");
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file) && (NULL== eaf)) {
|
||||
if (!GetAssetData(file->valuestring, ptr, size)) {
|
||||
if (!assets->GetAssetData(file->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "Emoji %s image file %s is not found", name->valuestring, file->valuestring);
|
||||
continue;
|
||||
}
|
||||
@@ -213,7 +301,7 @@ bool Assets::Apply() {
|
||||
light_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_image)) {
|
||||
if (!GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
if (!assets->GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
|
||||
return false;
|
||||
}
|
||||
@@ -234,7 +322,7 @@ bool Assets::Apply() {
|
||||
dark_theme->set_chat_background_color(LvglTheme::ParseColor(background_color->valuestring));
|
||||
}
|
||||
if (cJSON_IsString(background_image)) {
|
||||
if (!GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
if (!assets->GetAssetData(background_image->valuestring, ptr, size)) {
|
||||
ESP_LOGE(TAG, "The background image file %s is not found", background_image->valuestring);
|
||||
return false;
|
||||
}
|
||||
@@ -262,137 +350,84 @@ bool Assets::Apply() {
|
||||
ESP_LOGI(TAG, "Set hide_subtitle to %s", hide ? "true" : "false");
|
||||
}
|
||||
}
|
||||
|
||||
#elif defined(CONFIG_USE_EMOTE_MESSAGE_STYLE)
|
||||
auto &board = Board::GetInstance();
|
||||
auto display = board.GetDisplay();
|
||||
auto emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
|
||||
|
||||
cJSON* font = cJSON_GetObjectItem(root, "text_font");
|
||||
if (cJSON_IsString(font)) {
|
||||
std::string fonts_text_file = font->valuestring;
|
||||
if (GetAssetData(fonts_text_file, ptr, size)) {
|
||||
auto text_font = std::make_shared<LvglCBinFont>(ptr);
|
||||
if (text_font->font() == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to load fonts.bin");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (emote_display) {
|
||||
emote_display->AddTextFont(text_font);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGE(TAG, "The font file %s is not found", fonts_text_file.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* emoji_collection = cJSON_GetObjectItem(root, "emoji_collection");
|
||||
if (cJSON_IsArray(emoji_collection)) {
|
||||
int emoji_count = cJSON_GetArraySize(emoji_collection);
|
||||
if (emote_display) {
|
||||
for (int i = 0; i < emoji_count; i++) {
|
||||
cJSON* icon = cJSON_GetArrayItem(emoji_collection, i);
|
||||
if (cJSON_IsObject(icon)) {
|
||||
cJSON* name = cJSON_GetObjectItem(icon, "name");
|
||||
cJSON* file = cJSON_GetObjectItem(icon, "file");
|
||||
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file)) {
|
||||
if (GetAssetData(file->valuestring, ptr, size)) {
|
||||
cJSON* eaf = cJSON_GetObjectItem(icon, "eaf");
|
||||
bool lack_value = false;
|
||||
bool loop_value = false;
|
||||
int fps_value = 0;
|
||||
|
||||
if (cJSON_IsObject(eaf)) {
|
||||
cJSON* lack = cJSON_GetObjectItem(eaf, "lack");
|
||||
cJSON* loop = cJSON_GetObjectItem(eaf, "loop");
|
||||
cJSON* fps = cJSON_GetObjectItem(eaf, "fps");
|
||||
|
||||
lack_value = lack ? cJSON_IsTrue(lack) : false;
|
||||
loop_value = loop ? cJSON_IsTrue(loop) : false;
|
||||
fps_value = fps ? fps->valueint : 0;
|
||||
|
||||
emote_display->AddEmojiData(name->valuestring, ptr, size,
|
||||
static_cast<uint8_t>(fps_value),
|
||||
loop_value, lack_value);
|
||||
}
|
||||
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Emoji \"%10s\" image file %s is not found", name->valuestring, file->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* icon_collection = cJSON_GetObjectItem(root, "icon_collection");
|
||||
if (cJSON_IsArray(icon_collection)) {
|
||||
if (emote_display) {
|
||||
int icon_count = cJSON_GetArraySize(icon_collection);
|
||||
for (int i = 0; i < icon_count; i++) {
|
||||
cJSON* icon = cJSON_GetArrayItem(icon_collection, i);
|
||||
if (cJSON_IsObject(icon)) {
|
||||
cJSON* name = cJSON_GetObjectItem(icon, "name");
|
||||
cJSON* file = cJSON_GetObjectItem(icon, "file");
|
||||
|
||||
if (cJSON_IsString(name) && cJSON_IsString(file)) {
|
||||
if (GetAssetData(file->valuestring, ptr, size)) {
|
||||
emote_display->AddIconData(name->valuestring, ptr, size);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Icon \"%10s\" image file %s is not found", name->valuestring, file->valuestring);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cJSON* layout_json = cJSON_GetObjectItem(root, "layout");
|
||||
if (cJSON_IsArray(layout_json)) {
|
||||
int layout_count = cJSON_GetArraySize(layout_json);
|
||||
|
||||
for (int i = 0; i < layout_count; i++) {
|
||||
cJSON* layout_item = cJSON_GetArrayItem(layout_json, i);
|
||||
if (cJSON_IsObject(layout_item)) {
|
||||
cJSON* name = cJSON_GetObjectItem(layout_item, "name");
|
||||
cJSON* align = cJSON_GetObjectItem(layout_item, "align");
|
||||
cJSON* x = cJSON_GetObjectItem(layout_item, "x");
|
||||
cJSON* y = cJSON_GetObjectItem(layout_item, "y");
|
||||
cJSON* width = cJSON_GetObjectItem(layout_item, "width");
|
||||
cJSON* height = cJSON_GetObjectItem(layout_item, "height");
|
||||
|
||||
if (cJSON_IsString(name) && cJSON_IsString(align) && cJSON_IsNumber(x) && cJSON_IsNumber(y)) {
|
||||
int width_val = cJSON_IsNumber(width) ? width->valueint : 0;
|
||||
int height_val = cJSON_IsNumber(height) ? height->valueint : 0;
|
||||
|
||||
if (emote_display) {
|
||||
emote_display->AddLayoutData(name->valuestring, align->valuestring,
|
||||
x->valueint, y->valueint, width_val, height_val);
|
||||
}
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid layout item %d: missing required fields", i);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
cJSON_Delete(root);
|
||||
return true;
|
||||
}
|
||||
#endif // HAVE_LVGL
|
||||
|
||||
bool Assets::EmoteStrategy::InitializePartition(Assets* assets) {
|
||||
assets->partition_valid_ = false;
|
||||
|
||||
if (!Assets::FindPartition(assets)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
esp_err_t ret = ESP_ERR_INVALID_STATE;
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
|
||||
if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
|
||||
const emote_data_t data = {
|
||||
.type = EMOTE_SOURCE_PARTITION,
|
||||
.source = {
|
||||
.partition_label = PARTITION_LABEL,
|
||||
},
|
||||
.flags = {
|
||||
.mmap_enable = true, //must be true here!!!
|
||||
},
|
||||
};
|
||||
ret = emote_mount_assets(emote_display->GetEmoteHandle(), &data);
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Emote display is not initialized");
|
||||
}
|
||||
assets->partition_valid_ = ((ret == ESP_OK) ? true : false);
|
||||
return assets->partition_valid_;
|
||||
}
|
||||
|
||||
void Assets::EmoteStrategy::UnApplyPartition(Assets* assets) {
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
|
||||
if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
|
||||
emote_unmount_assets(emote_display->GetEmoteHandle());
|
||||
}
|
||||
(void)assets; // Unused parameter
|
||||
}
|
||||
|
||||
bool Assets::EmoteStrategy::GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) {
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
|
||||
if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
|
||||
const uint8_t* data = nullptr;
|
||||
size_t data_size = 0;
|
||||
if (ESP_OK == emote_get_asset_data_by_name(emote_display->GetEmoteHandle(), name.c_str(), &data, &data_size)) {
|
||||
ptr = const_cast<void*>(static_cast<const void*>(data));
|
||||
size = data_size;
|
||||
return true;
|
||||
}
|
||||
ESP_LOGE(TAG, "Failed to get asset data by name: %s", name.c_str());
|
||||
return false;
|
||||
}
|
||||
(void)assets; // Unused parameter
|
||||
return false;
|
||||
}
|
||||
|
||||
bool Assets::EmoteStrategy::Apply(Assets* assets) {
|
||||
Assets::LoadSrmodelsFromIndex(assets);
|
||||
|
||||
auto display = Board::GetInstance().GetDisplay();
|
||||
auto* emote_display = dynamic_cast<emote::EmoteDisplay*>(display);
|
||||
|
||||
if (emote_display && emote_display->GetEmoteHandle() != nullptr) {
|
||||
emote_load_assets(emote_display->GetEmoteHandle());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::Download(std::string url, std::function<void(int progress, size_t speed)> progress_callback) {
|
||||
ESP_LOGI(TAG, "Downloading new version of assets from %s", url.c_str());
|
||||
|
||||
|
||||
// 取消当前资源分区的内存映射
|
||||
if (mmap_handle_ != 0) {
|
||||
esp_partition_munmap(mmap_handle_);
|
||||
mmap_handle_ = 0;
|
||||
mmap_root_ = nullptr;
|
||||
}
|
||||
checksum_valid_ = false;
|
||||
assets_.clear();
|
||||
UnApplyPartition();
|
||||
|
||||
// 下载新的资源文件
|
||||
auto network = Board::GetInstance().GetNetwork();
|
||||
@@ -430,16 +465,21 @@ bool Assets::Download(std::string url, std::function<void(int progress, size_t s
|
||||
SECTOR_SIZE, content_length, sectors_to_erase, total_erase_size);
|
||||
|
||||
// 写入新的资源文件到分区,一边erase一边写入
|
||||
char buffer[512];
|
||||
char* buffer = (char*)heap_caps_malloc(SECTOR_SIZE, MALLOC_CAP_INTERNAL);
|
||||
if (buffer == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to allocate buffer");
|
||||
return false;
|
||||
}
|
||||
size_t total_written = 0;
|
||||
size_t recent_written = 0;
|
||||
size_t current_sector = 0;
|
||||
auto last_calc_time = esp_timer_get_time();
|
||||
|
||||
while (true) {
|
||||
int ret = http->Read(buffer, sizeof(buffer));
|
||||
int ret = http->Read(buffer, SECTOR_SIZE);
|
||||
if (ret < 0) {
|
||||
ESP_LOGE(TAG, "Failed to read HTTP data: %s", esp_err_to_name(ret));
|
||||
heap_caps_free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -459,6 +499,7 @@ bool Assets::Download(std::string url, std::function<void(int progress, size_t s
|
||||
// 确保擦除范围不超过分区大小
|
||||
if (sector_end > partition_->size) {
|
||||
ESP_LOGE(TAG, "Sector end (%u) exceeds partition size (%lu)", sector_end, partition_->size);
|
||||
heap_caps_free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -466,6 +507,7 @@ bool Assets::Download(std::string url, std::function<void(int progress, size_t s
|
||||
esp_err_t err = esp_partition_erase_range(partition_, sector_start, SECTOR_SIZE);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to erase sector %u at offset %u: %s", current_sector, sector_start, esp_err_to_name(err));
|
||||
heap_caps_free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -476,6 +518,7 @@ bool Assets::Download(std::string url, std::function<void(int progress, size_t s
|
||||
esp_err_t err = esp_partition_write(partition_, total_written, buffer, ret);
|
||||
if (err != ESP_OK) {
|
||||
ESP_LOGE(TAG, "Failed to write to assets partition at offset %u: %s", total_written, esp_err_to_name(err));
|
||||
heap_caps_free(buffer);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -497,6 +540,7 @@ bool Assets::Download(std::string url, std::function<void(int progress, size_t s
|
||||
}
|
||||
|
||||
http->Close();
|
||||
heap_caps_free(buffer);
|
||||
|
||||
if (total_written != content_length) {
|
||||
ESP_LOGE(TAG, "Downloaded size (%u) does not match expected size (%u)", total_written, content_length);
|
||||
@@ -514,19 +558,3 @@ bool Assets::Download(std::string url, std::function<void(int progress, size_t s
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Assets::GetAssetData(const std::string& name, void*& ptr, size_t& size) {
|
||||
auto asset = assets_.find(name);
|
||||
if (asset == assets_.end()) {
|
||||
return false;
|
||||
}
|
||||
auto data = (const char*)(mmap_root_ + asset->second.offset);
|
||||
if (data[0] != 'Z' || data[1] != 'Z') {
|
||||
ESP_LOGE(TAG, "The asset %s is not valid with magic %02x%02x", name.c_str(), data[0], data[1]);
|
||||
return false;
|
||||
}
|
||||
|
||||
ptr = static_cast<void*>(const_cast<char*>(data + 2));
|
||||
size = asset->second.size;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,19 @@
|
||||
#ifndef ASSETS_H
|
||||
#define ASSETS_H
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
|
||||
#include <cJSON.h>
|
||||
#include <esp_partition.h>
|
||||
#include <model_path.h>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#if HAVE_LVGL
|
||||
#include <spi_flash_mmap.h>
|
||||
#endif
|
||||
|
||||
struct Asset {
|
||||
size_t size;
|
||||
@@ -28,7 +33,6 @@ public:
|
||||
bool GetAssetData(const std::string& name, void*& ptr, size_t& size);
|
||||
|
||||
inline bool partition_valid() const { return partition_valid_; }
|
||||
inline bool checksum_valid() const { return checksum_valid_; }
|
||||
inline std::string default_assets_url() const { return default_assets_url_; }
|
||||
|
||||
private:
|
||||
@@ -37,16 +41,49 @@ private:
|
||||
Assets& operator=(const Assets&) = delete;
|
||||
|
||||
bool InitializePartition();
|
||||
uint32_t CalculateChecksum(const char* data, uint32_t length);
|
||||
void UnApplyPartition();
|
||||
static bool FindPartition(Assets* assets);
|
||||
static bool LoadSrmodelsFromIndex(Assets* assets, cJSON* root = nullptr);
|
||||
|
||||
class AssetStrategy {
|
||||
public:
|
||||
virtual ~AssetStrategy() = default;
|
||||
virtual bool Apply(Assets* assets) = 0;
|
||||
virtual bool InitializePartition(Assets* assets) = 0;
|
||||
virtual void UnApplyPartition(Assets* assets) = 0;
|
||||
virtual bool GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) = 0;
|
||||
};
|
||||
|
||||
class LvglStrategy : public AssetStrategy {
|
||||
public:
|
||||
bool Apply(Assets* assets) override;
|
||||
bool InitializePartition(Assets* assets) override;
|
||||
void UnApplyPartition(Assets* assets) override;
|
||||
bool GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) override;
|
||||
private:
|
||||
static uint32_t CalculateChecksum(const char* data, uint32_t length);
|
||||
std::map<std::string, Asset> assets_;
|
||||
esp_partition_mmap_handle_t mmap_handle_ = 0;
|
||||
const char* mmap_root_ = nullptr;
|
||||
bool checksum_valid_ = false;
|
||||
};
|
||||
|
||||
class EmoteStrategy : public AssetStrategy {
|
||||
public:
|
||||
bool Apply(Assets* assets) override;
|
||||
bool InitializePartition(Assets* assets) override;
|
||||
void UnApplyPartition(Assets* assets) override;
|
||||
bool GetAssetData(Assets* assets, const std::string& name, void*& ptr, size_t& size) override;
|
||||
};
|
||||
|
||||
// Strategy instance
|
||||
std::unique_ptr<AssetStrategy> strategy_;
|
||||
|
||||
protected:
|
||||
const esp_partition_t* partition_ = nullptr;
|
||||
esp_partition_mmap_handle_t mmap_handle_ = 0;
|
||||
const char* mmap_root_ = nullptr;
|
||||
bool partition_valid_ = false;
|
||||
bool checksum_valid_ = false;
|
||||
std::string default_assets_url_;
|
||||
srmodel_list_t* models_list_ = nullptr;
|
||||
std::map<std::string, Asset> assets_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "جاري تحميل الموارد...",
|
||||
"PLEASE_WAIT": "يرجى الانتظار...",
|
||||
"FOUND_NEW_ASSETS": "تم العثور على موارد جديدة: %s",
|
||||
"HELLO_MY_FRIEND": "مرحباً، صديقي!"
|
||||
"HELLO_MY_FRIEND": "مرحباً، صديقي!",
|
||||
"CONNECTION_SUCCESSFUL": "تم الاتصال بنجاح",
|
||||
"FLIGHT_MODE_OFF": "وضع الطيران معطل",
|
||||
"FLIGHT_MODE_ON": "وضع الطيران قيد التشغيل",
|
||||
"MODEM_INIT_ERROR": "فشل تهيئة المودم"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Намерени нови ресурси: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Неуспешно изтегляне на ресурси",
|
||||
"LOADING_ASSETS": "Зареждане на ресурси...",
|
||||
"HELLO_MY_FRIEND": "Здравей, мой приятел!"
|
||||
"HELLO_MY_FRIEND": "Здравей, мой приятел!",
|
||||
"FLIGHT_MODE_OFF": "Режим на самолет е изключен",
|
||||
"FLIGHT_MODE_ON": "Режим на самолет е включен",
|
||||
"MODEM_INIT_ERROR": "Неуспешна инициализация на модема"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "S'han trobat nous recursos: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "No s'han pogut descarregar els recursos",
|
||||
"LOADING_ASSETS": "Carregant recursos...",
|
||||
"HELLO_MY_FRIEND": "Hola, amic meu!"
|
||||
"HELLO_MY_FRIEND": "Hola, amic meu!",
|
||||
"FLIGHT_MODE_OFF": "El mode avió està desactivat",
|
||||
"FLIGHT_MODE_ON": "El mode avió està activat",
|
||||
"MODEM_INIT_ERROR": "Error d'inicialització del mòdem"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Načítání prostředků...",
|
||||
"PLEASE_WAIT": "Prosím čekejte...",
|
||||
"FOUND_NEW_ASSETS": "Nalezeny nové prostředky: %s",
|
||||
"HELLO_MY_FRIEND": "Ahoj, můj příteli!"
|
||||
"HELLO_MY_FRIEND": "Ahoj, můj příteli!",
|
||||
"CONNECTION_SUCCESSFUL": "Připojení úspěšné",
|
||||
"FLIGHT_MODE_OFF": "Letecký režim je vypnutý",
|
||||
"FLIGHT_MODE_ON": "Letecký režim je zapnutý",
|
||||
"MODEM_INIT_ERROR": "Chyba inicializace modemu"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Fandt nye ressourcer: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Download af ressourcer mislykkedes",
|
||||
"LOADING_ASSETS": "Indlæser ressourcer...",
|
||||
"HELLO_MY_FRIEND": "Hej, min ven!"
|
||||
"HELLO_MY_FRIEND": "Hej, min ven!",
|
||||
"FLIGHT_MODE_OFF": "Flytilstand er slukket",
|
||||
"FLIGHT_MODE_ON": "Flytilstand er tændt",
|
||||
"MODEM_INIT_ERROR": "Modeminitialisering mislykkedes"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Ressourcen werden geladen...",
|
||||
"PLEASE_WAIT": "Bitte warten...",
|
||||
"FOUND_NEW_ASSETS": "Neue Ressourcen gefunden: %s",
|
||||
"HELLO_MY_FRIEND": "Hallo, mein Freund!"
|
||||
"HELLO_MY_FRIEND": "Hallo, mein Freund!",
|
||||
"CONNECTION_SUCCESSFUL": "Verbindung erfolgreich",
|
||||
"FLIGHT_MODE_OFF": "Flugmodus ist deaktiviert",
|
||||
"FLIGHT_MODE_ON": "Flugmodus ist aktiviert",
|
||||
"MODEM_INIT_ERROR": "Modem-Initialisierung fehlgeschlagen"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Βρέθηκαν νέοι πόροι: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Αποτυχία λήψης πόρων",
|
||||
"LOADING_ASSETS": "Φόρτωση πόρων...",
|
||||
"HELLO_MY_FRIEND": "Γεια σου, φίλε μου!"
|
||||
"HELLO_MY_FRIEND": "Γεια σου, φίλε μου!",
|
||||
"FLIGHT_MODE_OFF": "Η λειτουργία πτήσης είναι απενεργοποιημένη",
|
||||
"FLIGHT_MODE_ON": "Η λειτουργία πτήσης είναι ενεργή",
|
||||
"MODEM_INIT_ERROR": "Αποτυχία αρχικοποίησης modem"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,7 +11,10 @@
|
||||
"INITIALIZING": "Initializing...",
|
||||
"PIN_ERROR": "Please insert SIM card",
|
||||
"REG_ERROR": "Unable to access network, please check SIM card status",
|
||||
"MODEM_INIT_ERROR": "Modem initialization failed",
|
||||
"DETECTING_MODULE": "Detecting module...",
|
||||
"FLIGHT_MODE_ON": "Flight mode is on",
|
||||
"FLIGHT_MODE_OFF": "Flight mode is off",
|
||||
"REGISTERING_NETWORK": "Waiting for network...",
|
||||
"CHECKING_NEW_VERSION": "Checking for new version...",
|
||||
"CHECK_NEW_VERSION_FAILED": "Check for new version failed, will retry in %d seconds: %s",
|
||||
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Cargando recursos...",
|
||||
"PLEASE_WAIT": "Por favor espere...",
|
||||
"FOUND_NEW_ASSETS": "Encontrados nuevos recursos: %s",
|
||||
"HELLO_MY_FRIEND": "¡Hola, mi amigo!"
|
||||
"HELLO_MY_FRIEND": "¡Hola, mi amigo!",
|
||||
"CONNECTION_SUCCESSFUL": "Conexión exitosa",
|
||||
"FLIGHT_MODE_OFF": "El modo avión está desactivado",
|
||||
"FLIGHT_MODE_ON": "El modo avión está activado",
|
||||
"MODEM_INIT_ERROR": "Error de inicialización del módem"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "منابع جدید یافت شد: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "دانلود منابع ناموفق بود",
|
||||
"LOADING_ASSETS": "بارگذاری منابع...",
|
||||
"HELLO_MY_FRIEND": "سلام، دوست من!"
|
||||
"HELLO_MY_FRIEND": "سلام، دوست من!",
|
||||
"FLIGHT_MODE_OFF": "حالت پرواز خاموش است",
|
||||
"FLIGHT_MODE_ON": "حالت پرواز روشن است",
|
||||
"MODEM_INIT_ERROR": "خطا در راهاندازی مودم"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Ladataan resursseja...",
|
||||
"PLEASE_WAIT": "Odota hetki...",
|
||||
"FOUND_NEW_ASSETS": "Löydetty uusia resursseja: %s",
|
||||
"HELLO_MY_FRIEND": "Hei, ystäväni!"
|
||||
"HELLO_MY_FRIEND": "Hei, ystäväni!",
|
||||
"CONNECTION_SUCCESSFUL": "Yhteys onnistui",
|
||||
"FLIGHT_MODE_OFF": "Lentotila on pois päältä",
|
||||
"FLIGHT_MODE_ON": "Lentotila on päällä",
|
||||
"MODEM_INIT_ERROR": "Modeemin alustus epäonnistui"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Nakahanap ng mga bagong assets: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nabigo ang pag-download ng mga assets",
|
||||
"LOADING_ASSETS": "Nilo-load ang mga assets...",
|
||||
"HELLO_MY_FRIEND": "Kumusta, kaibigan ko!"
|
||||
"HELLO_MY_FRIEND": "Kumusta, kaibigan ko!",
|
||||
"FLIGHT_MODE_OFF": "Naka-off ang flight mode",
|
||||
"FLIGHT_MODE_ON": "Naka-on ang flight mode",
|
||||
"MODEM_INIT_ERROR": "Nabigo ang pag-initialize ng modem"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Chargement des ressources...",
|
||||
"PLEASE_WAIT": "Veuillez patienter...",
|
||||
"FOUND_NEW_ASSETS": "Nouvelles ressources trouvées: %s",
|
||||
"HELLO_MY_FRIEND": "Bonjour, mon ami !"
|
||||
"HELLO_MY_FRIEND": "Bonjour, mon ami !",
|
||||
"CONNECTION_SUCCESSFUL": "Connexion réussie",
|
||||
"FLIGHT_MODE_OFF": "Le mode avion est désactivé",
|
||||
"FLIGHT_MODE_ON": "Le mode avion est activé",
|
||||
"MODEM_INIT_ERROR": "Échec de l'initialisation du modem"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "נמצאו משאבים חדשים: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "הורדת משאבים נכשלה",
|
||||
"LOADING_ASSETS": "טוען משאבים...",
|
||||
"HELLO_MY_FRIEND": "שלום, ידידי!"
|
||||
"HELLO_MY_FRIEND": "שלום, ידידי!",
|
||||
"FLIGHT_MODE_OFF": "מצב טיסה כבוי",
|
||||
"FLIGHT_MODE_ON": "מצב טיסה מופעל",
|
||||
"MODEM_INIT_ERROR": "אתחול המודם נכשל"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "संसाधन लोड हो रहे हैं...",
|
||||
"PLEASE_WAIT": "कृपया प्रतीक्षा करें...",
|
||||
"FOUND_NEW_ASSETS": "नए संसाधन मिले: %s",
|
||||
"HELLO_MY_FRIEND": "नमस्ते, मेरे दोस्त!"
|
||||
"HELLO_MY_FRIEND": "नमस्ते, मेरे दोस्त!",
|
||||
"CONNECTION_SUCCESSFUL": "कनेक्शन सफल",
|
||||
"FLIGHT_MODE_OFF": "फ़्लाइट मोड बंद है",
|
||||
"FLIGHT_MODE_ON": "फ़्लाइट मोड चालू है",
|
||||
"MODEM_INIT_ERROR": "मॉडेम आरंभीकरण विफल"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Pronađeni novi resursi: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Preuzimanje resursa nije uspjelo",
|
||||
"LOADING_ASSETS": "Učitavanje resursa...",
|
||||
"HELLO_MY_FRIEND": "Bok, moj prijatelju!"
|
||||
"HELLO_MY_FRIEND": "Bok, moj prijatelju!",
|
||||
"FLIGHT_MODE_OFF": "Način rada u zrakoplovu je isključen",
|
||||
"FLIGHT_MODE_ON": "Način rada u zrakoplovu je uključen",
|
||||
"MODEM_INIT_ERROR": "Neuspjela inicijalizacija modema"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Új erőforrások találva: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Az erőforrások letöltése sikertelen",
|
||||
"LOADING_ASSETS": "Erőforrások betöltése...",
|
||||
"HELLO_MY_FRIEND": "Helló, barátom!"
|
||||
"HELLO_MY_FRIEND": "Helló, barátom!",
|
||||
"FLIGHT_MODE_OFF": "A repülési mód ki van kapcsolva",
|
||||
"FLIGHT_MODE_ON": "A repülési mód be van kapcsolva",
|
||||
"MODEM_INIT_ERROR": "A modem inicializálása sikertelen"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Memuat aset...",
|
||||
"PLEASE_WAIT": "Mohon tunggu...",
|
||||
"FOUND_NEW_ASSETS": "Ditemukan aset baru: %s",
|
||||
"HELLO_MY_FRIEND": "Halo, teman saya!"
|
||||
"HELLO_MY_FRIEND": "Halo, teman saya!",
|
||||
"CONNECTION_SUCCESSFUL": "Koneksi berhasil",
|
||||
"FLIGHT_MODE_OFF": "Mode pesawat nonaktif",
|
||||
"FLIGHT_MODE_ON": "Mode pesawat aktif",
|
||||
"MODEM_INIT_ERROR": "Gagal menginisialisasi modem"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Caricamento risorse...",
|
||||
"PLEASE_WAIT": "Attendere prego...",
|
||||
"FOUND_NEW_ASSETS": "Trovate nuove risorse: %s",
|
||||
"HELLO_MY_FRIEND": "Ciao, amico mio!"
|
||||
"HELLO_MY_FRIEND": "Ciao, amico mio!",
|
||||
"CONNECTION_SUCCESSFUL": "Connessione riuscita",
|
||||
"FLIGHT_MODE_OFF": "La modalità aereo è disattivata",
|
||||
"FLIGHT_MODE_ON": "La modalità aereo è attiva",
|
||||
"MODEM_INIT_ERROR": "Inizializzazione modem non riuscita"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "アセットを読み込み中...",
|
||||
"PLEASE_WAIT": "お待ちください...",
|
||||
"FOUND_NEW_ASSETS": "新しいアセットが見つかりました: %s",
|
||||
"HELLO_MY_FRIEND": "こんにちは、友達!"
|
||||
"HELLO_MY_FRIEND": "こんにちは、友達!",
|
||||
"CONNECTION_SUCCESSFUL": "接続成功",
|
||||
"FLIGHT_MODE_OFF": "機内モードがオフです",
|
||||
"FLIGHT_MODE_ON": "機内モードがオンです",
|
||||
"MODEM_INIT_ERROR": "モデムの初期化に失敗しました"
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,9 @@
|
||||
"LOADING_ASSETS": "에셋 로딩 중...",
|
||||
"PLEASE_WAIT": "잠시 기다려 주세요...",
|
||||
"FOUND_NEW_ASSETS": "새로운 에셋을 발견했습니다: %s",
|
||||
"HELLO_MY_FRIEND": "안녕하세요, 친구!"
|
||||
"HELLO_MY_FRIEND": "안녕하세요, 친구!",
|
||||
"FLIGHT_MODE_OFF": "비행기 모드가 꺼져 있습니다",
|
||||
"FLIGHT_MODE_ON": "비행기 모드가 켜져 있습니다",
|
||||
"MODEM_INIT_ERROR": "모뎀 초기화 실패"
|
||||
}
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Menemui aset baharu: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Gagal memuat turun aset",
|
||||
"LOADING_ASSETS": "Memuatkan aset...",
|
||||
"HELLO_MY_FRIEND": "Hai, kawan saya!"
|
||||
"HELLO_MY_FRIEND": "Hai, kawan saya!",
|
||||
"FLIGHT_MODE_OFF": "Mod penerbangan dimatikan",
|
||||
"FLIGHT_MODE_ON": "Mod penerbangan dihidupkan",
|
||||
"MODEM_INIT_ERROR": "Modem gagal dimulakan"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Fant nye ressurser: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nedlasting av ressurser mislyktes",
|
||||
"LOADING_ASSETS": "Laster ressurser...",
|
||||
"HELLO_MY_FRIEND": "Hei, min venn!"
|
||||
"HELLO_MY_FRIEND": "Hei, min venn!",
|
||||
"FLIGHT_MODE_OFF": "Flymodus er av",
|
||||
"FLIGHT_MODE_ON": "Flymodus er på",
|
||||
"MODEM_INIT_ERROR": "Modeminitialisering mislyktes"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Nieuwe bronnen gevonden: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Downloaden van bronnen mislukt",
|
||||
"LOADING_ASSETS": "Bronnen laden...",
|
||||
"HELLO_MY_FRIEND": "Hallo, mijn vriend!"
|
||||
"HELLO_MY_FRIEND": "Hallo, mijn vriend!",
|
||||
"FLIGHT_MODE_OFF": "Vliegtuigmodus is uitgeschakeld",
|
||||
"FLIGHT_MODE_ON": "Vliegtuigmodus is ingeschakeld",
|
||||
"MODEM_INIT_ERROR": "Modeminitialisatie mislukt"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Ładowanie zasobów...",
|
||||
"PLEASE_WAIT": "Proszę czekać...",
|
||||
"FOUND_NEW_ASSETS": "Znaleziono nowe zasoby: %s",
|
||||
"HELLO_MY_FRIEND": "Cześć, mój przyjacielu!"
|
||||
"HELLO_MY_FRIEND": "Cześć, mój przyjacielu!",
|
||||
"CONNECTION_SUCCESSFUL": "Połączenie udane",
|
||||
"FLIGHT_MODE_OFF": "Tryb samolotowy jest wyłączony",
|
||||
"FLIGHT_MODE_ON": "Tryb samolotowy jest włączony",
|
||||
"MODEM_INIT_ERROR": "Inicjalizacja modemu nie powiodła się"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "A carregar recursos...",
|
||||
"PLEASE_WAIT": "Por favor aguarde...",
|
||||
"FOUND_NEW_ASSETS": "Encontrados novos recursos: %s",
|
||||
"HELLO_MY_FRIEND": "Olá, meu amigo!"
|
||||
"HELLO_MY_FRIEND": "Olá, meu amigo!",
|
||||
"CONNECTION_SUCCESSFUL": "Ligação bem-sucedida",
|
||||
"FLIGHT_MODE_OFF": "O modo avião está desativado",
|
||||
"FLIGHT_MODE_ON": "O modo avião está ativado",
|
||||
"MODEM_INIT_ERROR": "Falha na inicialização do modem"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Se încarcă resursele...",
|
||||
"PLEASE_WAIT": "Vă rugăm să așteptați...",
|
||||
"FOUND_NEW_ASSETS": "S-au găsit resurse noi: %s",
|
||||
"HELLO_MY_FRIEND": "Salut, prietenul meu!"
|
||||
"HELLO_MY_FRIEND": "Salut, prietenul meu!",
|
||||
"CONNECTION_SUCCESSFUL": "Conexiune reușită",
|
||||
"FLIGHT_MODE_OFF": "Modul avion este dezactivat",
|
||||
"FLIGHT_MODE_ON": "Modul avion este activat",
|
||||
"MODEM_INIT_ERROR": "Inițializarea modemului a eșuat"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Загрузка ресурсов...",
|
||||
"PLEASE_WAIT": "Пожалуйста, подождите...",
|
||||
"FOUND_NEW_ASSETS": "Найдены новые ресурсы: %s",
|
||||
"HELLO_MY_FRIEND": "Привет, мой друг!"
|
||||
"HELLO_MY_FRIEND": "Привет, мой друг!",
|
||||
"CONNECTION_SUCCESSFUL": "Подключение успешно",
|
||||
"FLIGHT_MODE_OFF": "Режим полета выключен",
|
||||
"FLIGHT_MODE_ON": "Режим полета включен",
|
||||
"MODEM_INIT_ERROR": "Ошибка инициализации модема"
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Nájdené nové zdroje: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Sťahovanie zdrojov zlyhalo",
|
||||
"LOADING_ASSETS": "Načítavanie zdrojov...",
|
||||
"HELLO_MY_FRIEND": "Ahoj, môj priateľ!"
|
||||
"HELLO_MY_FRIEND": "Ahoj, môj priateľ!",
|
||||
"FLIGHT_MODE_OFF": "Letecký režim je vypnutý",
|
||||
"FLIGHT_MODE_ON": "Letecký režim je zapnutý",
|
||||
"MODEM_INIT_ERROR": "Chyba inicializácie modemu"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Najdeni novi viri: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Prenos virov ni uspel",
|
||||
"LOADING_ASSETS": "Nalaganje virov...",
|
||||
"HELLO_MY_FRIEND": "Pozdravljeni, moj prijatelj!"
|
||||
"HELLO_MY_FRIEND": "Pozdravljeni, moj prijatelj!",
|
||||
"FLIGHT_MODE_OFF": "Način leta je izklopljen",
|
||||
"FLIGHT_MODE_ON": "Način leta je vklopljen",
|
||||
"MODEM_INIT_ERROR": "Inicializacija modema ni uspela"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Пронађени нови ресурси: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Преузимање ресурса није успело",
|
||||
"LOADING_ASSETS": "Учитавање ресурса...",
|
||||
"HELLO_MY_FRIEND": "Здраво, пријатељу!"
|
||||
"HELLO_MY_FRIEND": "Здраво, пријатељу!",
|
||||
"FLIGHT_MODE_OFF": "Режим лета је искључен",
|
||||
"FLIGHT_MODE_ON": "Режим лета је укључен",
|
||||
"MODEM_INIT_ERROR": "Иницијализација модема није успела"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,7 +51,9 @@
|
||||
"FOUND_NEW_ASSETS": "Hittade nya resurser: %s",
|
||||
"DOWNLOAD_ASSETS_FAILED": "Nedladdning av resurser misslyckades",
|
||||
"LOADING_ASSETS": "Laddar resurser...",
|
||||
"HELLO_MY_FRIEND": "Hej, min vän!"
|
||||
"HELLO_MY_FRIEND": "Hej, min vän!",
|
||||
"FLIGHT_MODE_OFF": "Flygläge är av",
|
||||
"FLIGHT_MODE_ON": "Flygläge är på",
|
||||
"MODEM_INIT_ERROR": "Modeminitiering misslyckades"
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -51,6 +51,9 @@
|
||||
"LOADING_ASSETS": "กำลังโหลดทรัพยากร...",
|
||||
"PLEASE_WAIT": "กรุณารอสักครู่...",
|
||||
"FOUND_NEW_ASSETS": "พบทรัพยากรใหม่: %s",
|
||||
"HELLO_MY_FRIEND": "สวัสดี เพื่อนของฉัน!"
|
||||
"HELLO_MY_FRIEND": "สวัสดี เพื่อนของฉัน!",
|
||||
"FLIGHT_MODE_OFF": "โหมดเครื่องบินปิดอยู่",
|
||||
"FLIGHT_MODE_ON": "โหมดเครื่องบินเปิดอยู่",
|
||||
"MODEM_INIT_ERROR": "การเริ่มต้นโมเด็มล้มเหลว"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Varlıklar yükleniyor...",
|
||||
"PLEASE_WAIT": "Lütfen bekleyin...",
|
||||
"FOUND_NEW_ASSETS": "Yeni varlıklar bulundu: %s",
|
||||
"HELLO_MY_FRIEND": "Merhaba, arkadaşım!"
|
||||
"HELLO_MY_FRIEND": "Merhaba, arkadaşım!",
|
||||
"CONNECTION_SUCCESSFUL": "Bağlantı başarılı",
|
||||
"FLIGHT_MODE_OFF": "Uçak modu kapalı",
|
||||
"FLIGHT_MODE_ON": "Uçak modu açık",
|
||||
"MODEM_INIT_ERROR": "Modem başlatma hatası"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "Завантаження ресурсів...",
|
||||
"PLEASE_WAIT": "Будь ласка, зачекайте...",
|
||||
"FOUND_NEW_ASSETS": "Знайдено нові ресурси: %s",
|
||||
"HELLO_MY_FRIEND": "Привіт, мій друже!"
|
||||
"HELLO_MY_FRIEND": "Привіт, мій друже!",
|
||||
"CONNECTION_SUCCESSFUL": "Підключення успішне",
|
||||
"FLIGHT_MODE_OFF": "Режим польоту вимкнено",
|
||||
"FLIGHT_MODE_ON": "Режим польоту увімкнено",
|
||||
"MODEM_INIT_ERROR": "Помилка ініціалізації модему"
|
||||
}
|
||||
}
|
||||
@@ -51,6 +51,9 @@
|
||||
"LOADING_ASSETS": "Đang tải tài nguyên...",
|
||||
"PLEASE_WAIT": "Vui lòng đợi...",
|
||||
"FOUND_NEW_ASSETS": "Tìm thấy tài nguyên mới: %s",
|
||||
"HELLO_MY_FRIEND": "Xin chào, bạn của tôi!"
|
||||
"HELLO_MY_FRIEND": "Xin chào, bạn của tôi!",
|
||||
"FLIGHT_MODE_OFF": "Chế độ máy bay đang tắt",
|
||||
"FLIGHT_MODE_ON": "Chế độ máy bay đang bật",
|
||||
"MODEM_INIT_ERROR": "Khởi tạo modem thất bại"
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@
|
||||
"INITIALIZING": "正在初始化...",
|
||||
"PIN_ERROR": "请插入 SIM 卡",
|
||||
"REG_ERROR": "无法接入网络,请检查流量卡状态",
|
||||
"MODEM_INIT_ERROR": "模组初始化失败",
|
||||
"DETECTING_MODULE": "检测模组...",
|
||||
"REGISTERING_NETWORK": "等待网络...",
|
||||
"CHECKING_NEW_VERSION": "检查新版本...",
|
||||
@@ -50,6 +51,9 @@
|
||||
"LOADING_ASSETS": "加载资源...",
|
||||
"PLEASE_WAIT": "请稍候...",
|
||||
"FOUND_NEW_ASSETS": "发现新资源: %s",
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!"
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!",
|
||||
"CONNECTION_SUCCESSFUL": "连接成功",
|
||||
"FLIGHT_MODE_OFF": "飞行模式已关闭",
|
||||
"FLIGHT_MODE_ON": "飞行模式已开启"
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@
|
||||
"LOADING_ASSETS": "載入資源...",
|
||||
"PLEASE_WAIT": "請稍候...",
|
||||
"FOUND_NEW_ASSETS": "發現新資源: %s",
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!"
|
||||
"HELLO_MY_FRIEND": "你好,我的朋友!",
|
||||
"CONNECTION_SUCCESSFUL": "連線成功",
|
||||
"FLIGHT_MODE_OFF": "飛航模式已關閉",
|
||||
"FLIGHT_MODE_ON": "飛航模式已開啟",
|
||||
"MODEM_INIT_ERROR": "模組初始化失敗"
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,26 @@
|
||||
#include <esp_log.h>
|
||||
#include <cstring>
|
||||
|
||||
#define RATE_CVT_CFG(_src_rate, _dest_rate, _channel) \
|
||||
(esp_ae_rate_cvt_cfg_t) \
|
||||
{ \
|
||||
.src_rate = (uint32_t)(_src_rate), \
|
||||
.dest_rate = (uint32_t)(_dest_rate), \
|
||||
.channel = (uint8_t)(_channel), \
|
||||
.bits_per_sample = ESP_AUDIO_BIT16, \
|
||||
.complexity = 2, \
|
||||
.perf_type = ESP_AE_RATE_CVT_PERF_TYPE_SPEED, \
|
||||
}
|
||||
|
||||
#define OPUS_DEC_CFG(_sample_rate, _frame_duration_ms) \
|
||||
(esp_opus_dec_cfg_t) \
|
||||
{ \
|
||||
.sample_rate = (uint32_t)(_sample_rate), \
|
||||
.channel = ESP_AUDIO_MONO, \
|
||||
.frame_duration = (esp_opus_dec_frame_duration_t)AS_OPUS_GET_FRAME_DRU_ENUM(_frame_duration_ms), \
|
||||
.self_delimited = false, \
|
||||
}
|
||||
|
||||
#if CONFIG_USE_AUDIO_PROCESSOR
|
||||
#include "processors/afe_audio_processor.h"
|
||||
#else
|
||||
@@ -17,7 +37,6 @@
|
||||
|
||||
#define TAG "AudioService"
|
||||
|
||||
|
||||
AudioService::AudioService() {
|
||||
event_group_ = xEventGroupCreate();
|
||||
}
|
||||
@@ -26,21 +45,51 @@ AudioService::~AudioService() {
|
||||
if (event_group_ != nullptr) {
|
||||
vEventGroupDelete(event_group_);
|
||||
}
|
||||
if (opus_encoder_ != nullptr) {
|
||||
esp_opus_enc_close(opus_encoder_);
|
||||
}
|
||||
if (opus_decoder_ != nullptr) {
|
||||
esp_opus_dec_close(opus_decoder_);
|
||||
}
|
||||
if (input_resampler_ != nullptr) {
|
||||
esp_ae_rate_cvt_close(input_resampler_);
|
||||
}
|
||||
if (output_resampler_ != nullptr) {
|
||||
esp_ae_rate_cvt_close(output_resampler_);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void AudioService::Initialize(AudioCodec* codec) {
|
||||
codec_ = codec;
|
||||
codec_->Start();
|
||||
|
||||
/* Setup the audio codec */
|
||||
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(codec->output_sample_rate(), 1, OPUS_FRAME_DURATION_MS);
|
||||
opus_encoder_ = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
opus_encoder_->SetComplexity(0);
|
||||
esp_opus_dec_cfg_t opus_dec_cfg = OPUS_DEC_CFG(codec->output_sample_rate(), OPUS_FRAME_DURATION_MS);
|
||||
auto ret = esp_opus_dec_open(&opus_dec_cfg, sizeof(esp_opus_dec_cfg_t), &opus_decoder_);
|
||||
if (opus_decoder_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio decoder, error code: %d", ret);
|
||||
} else {
|
||||
decoder_sample_rate_ = codec->output_sample_rate();
|
||||
decoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
|
||||
decoder_frame_size_ = decoder_sample_rate_ / 1000 * OPUS_FRAME_DURATION_MS;
|
||||
}
|
||||
esp_opus_enc_config_t opus_enc_cfg = AS_OPUS_ENC_CONFIG();
|
||||
ret = esp_opus_enc_open(&opus_enc_cfg, sizeof(esp_opus_enc_config_t), &opus_encoder_);
|
||||
if (opus_encoder_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", ret);
|
||||
} else {
|
||||
encoder_sample_rate_ = 16000;
|
||||
encoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
|
||||
esp_opus_enc_get_frame_size(opus_encoder_, &encoder_frame_size_, &encoder_outbuf_size_);
|
||||
encoder_frame_size_ = encoder_frame_size_ / sizeof(int16_t);
|
||||
}
|
||||
|
||||
if (codec->input_sample_rate() != 16000) {
|
||||
input_resampler_.Configure(codec->input_sample_rate(), 16000);
|
||||
reference_resampler_.Configure(codec->input_sample_rate(), 16000);
|
||||
esp_ae_rate_cvt_cfg_t input_resampler_cfg = RATE_CVT_CFG(
|
||||
codec->input_sample_rate(), ESP_AUDIO_SAMPLE_RATE_16K, codec->input_channels());
|
||||
auto resampler_ret = esp_ae_rate_cvt_open(&input_resampler_cfg, &input_resampler_);
|
||||
if (input_resampler_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create input resampler, error code: %d", resampler_ret);
|
||||
}
|
||||
}
|
||||
|
||||
#if CONFIG_USE_AUDIO_PROCESSOR
|
||||
@@ -114,7 +163,7 @@ void AudioService::Start() {
|
||||
AudioService* audio_service = (AudioService*)arg;
|
||||
audio_service->OpusCodecTask();
|
||||
vTaskDelete(NULL);
|
||||
}, "opus_codec", 2048 * 13, this, 2, &opus_codec_task_handle_);
|
||||
}, "opus_codec", 2048 * 12, this, 2, &opus_codec_task_handle_);
|
||||
}
|
||||
|
||||
void AudioService::Stop() {
|
||||
@@ -144,25 +193,16 @@ bool AudioService::ReadAudioData(std::vector<int16_t>& data, int sample_rate, in
|
||||
if (!codec_->InputData(data)) {
|
||||
return false;
|
||||
}
|
||||
if (codec_->input_channels() == 2) {
|
||||
auto mic_channel = std::vector<int16_t>(data.size() / 2);
|
||||
auto reference_channel = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mic_channel.size(); ++i, j += 2) {
|
||||
mic_channel[i] = data[j];
|
||||
reference_channel[i] = data[j + 1];
|
||||
}
|
||||
auto resampled_mic = std::vector<int16_t>(input_resampler_.GetOutputSamples(mic_channel.size()));
|
||||
auto resampled_reference = std::vector<int16_t>(reference_resampler_.GetOutputSamples(reference_channel.size()));
|
||||
input_resampler_.Process(mic_channel.data(), mic_channel.size(), resampled_mic.data());
|
||||
reference_resampler_.Process(reference_channel.data(), reference_channel.size(), resampled_reference.data());
|
||||
data.resize(resampled_mic.size() + resampled_reference.size());
|
||||
for (size_t i = 0, j = 0; i < resampled_mic.size(); ++i, j += 2) {
|
||||
data[j] = resampled_mic[i];
|
||||
data[j + 1] = resampled_reference[i];
|
||||
}
|
||||
} else {
|
||||
auto resampled = std::vector<int16_t>(input_resampler_.GetOutputSamples(data.size()));
|
||||
input_resampler_.Process(data.data(), data.size(), resampled.data());
|
||||
if (input_resampler_ != nullptr) {
|
||||
std::lock_guard<std::mutex> lock(input_resampler_mutex_);
|
||||
uint32_t in_sample_num = data.size() / codec_->input_channels();
|
||||
uint32_t output_samples = 0;
|
||||
esp_ae_rate_cvt_get_max_out_sample_num(input_resampler_, in_sample_num, &output_samples);
|
||||
auto resampled = std::vector<int16_t>(output_samples * codec_->input_channels());
|
||||
uint32_t actual_output = output_samples;
|
||||
esp_ae_rate_cvt_process(input_resampler_, (esp_ae_sample_t)data.data(), in_sample_num,
|
||||
(esp_ae_sample_t)resampled.data(), &actual_output);
|
||||
resampled.resize(actual_output * codec_->input_channels());
|
||||
data = std::move(resampled);
|
||||
}
|
||||
} else {
|
||||
@@ -225,27 +265,18 @@ void AudioService::AudioInputTask() {
|
||||
}
|
||||
}
|
||||
|
||||
/* Feed the wake word */
|
||||
if (bits & AS_EVENT_WAKE_WORD_RUNNING) {
|
||||
/* Feed the wake word and/or audio processor */
|
||||
if (bits & (AS_EVENT_WAKE_WORD_RUNNING | AS_EVENT_AUDIO_PROCESSOR_RUNNING)) {
|
||||
int samples = 160; // 10ms
|
||||
std::vector<int16_t> data;
|
||||
int samples = wake_word_->GetFeedSize();
|
||||
if (samples > 0) {
|
||||
if (ReadAudioData(data, 16000, samples)) {
|
||||
if (ReadAudioData(data, 16000, samples)) {
|
||||
if (bits & AS_EVENT_WAKE_WORD_RUNNING) {
|
||||
wake_word_->Feed(data);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Feed the audio processor */
|
||||
if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) {
|
||||
std::vector<int16_t> data;
|
||||
int samples = audio_processor_->GetFeedSize();
|
||||
if (samples > 0) {
|
||||
if (ReadAudioData(data, 16000, samples)) {
|
||||
if (bits & AS_EVENT_AUDIO_PROCESSOR_RUNNING) {
|
||||
audio_processor_->Feed(std::move(data));
|
||||
continue;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -316,25 +347,49 @@ void AudioService::OpusCodecTask() {
|
||||
task->timestamp = packet->timestamp;
|
||||
|
||||
SetDecodeSampleRate(packet->sample_rate, packet->frame_duration);
|
||||
if (opus_decoder_->Decode(std::move(packet->payload), task->pcm)) {
|
||||
// Resample if the sample rate is different
|
||||
if (opus_decoder_->sample_rate() != codec_->output_sample_rate()) {
|
||||
int target_size = output_resampler_.GetOutputSamples(task->pcm.size());
|
||||
std::vector<int16_t> resampled(target_size);
|
||||
output_resampler_.Process(task->pcm.data(), task->pcm.size(), resampled.data());
|
||||
task->pcm = std::move(resampled);
|
||||
if (opus_decoder_ != nullptr) {
|
||||
task->pcm.resize(decoder_frame_size_);
|
||||
esp_audio_dec_in_raw_t raw = {
|
||||
.buffer = (uint8_t *)(packet->payload.data()),
|
||||
.len = (uint32_t)(packet->payload.size()),
|
||||
.consumed = 0,
|
||||
.frame_recover = ESP_AUDIO_DEC_RECOVERY_NONE,
|
||||
};
|
||||
esp_audio_dec_out_frame_t out_frame = {
|
||||
.buffer = (uint8_t *)(task->pcm.data()),
|
||||
.len = (uint32_t)(task->pcm.size() * sizeof(int16_t)),
|
||||
.decoded_size = 0,
|
||||
};
|
||||
esp_audio_dec_info_t dec_info = {};
|
||||
std::unique_lock<std::mutex> decoder_lock(decoder_mutex_);
|
||||
auto ret = esp_opus_dec_decode(opus_decoder_, &raw, &out_frame, &dec_info);
|
||||
decoder_lock.unlock();
|
||||
if (ret == ESP_AUDIO_ERR_OK) {
|
||||
task->pcm.resize(out_frame.decoded_size / sizeof(int16_t));
|
||||
if (decoder_sample_rate_ != codec_->output_sample_rate() && output_resampler_ != nullptr) {
|
||||
uint32_t target_size = 0;
|
||||
esp_ae_rate_cvt_get_max_out_sample_num(output_resampler_, task->pcm.size(), &target_size);
|
||||
std::vector<int16_t> resampled(target_size);
|
||||
uint32_t actual_output = target_size;
|
||||
esp_ae_rate_cvt_process(output_resampler_, (esp_ae_sample_t)task->pcm.data(), task->pcm.size(),
|
||||
(esp_ae_sample_t)resampled.data(), &actual_output);
|
||||
resampled.resize(actual_output);
|
||||
task->pcm = std::move(resampled);
|
||||
}
|
||||
lock.lock();
|
||||
audio_playback_queue_.push_back(std::move(task));
|
||||
audio_queue_cv_.notify_all();
|
||||
debug_statistics_.decode_count++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to decode audio after resize, error code: %d", ret);
|
||||
lock.lock();
|
||||
}
|
||||
|
||||
lock.lock();
|
||||
audio_playback_queue_.push_back(std::move(task));
|
||||
audio_queue_cv_.notify_all();
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to decode audio");
|
||||
ESP_LOGE(TAG, "Audio decoder is not configured");
|
||||
lock.lock();
|
||||
}
|
||||
debug_statistics_.decode_count++;
|
||||
}
|
||||
|
||||
/* Encode the audio to send queue */
|
||||
if (!audio_encode_queue_.empty() && audio_send_queue_.size() < MAX_SEND_PACKETS_IN_QUEUE) {
|
||||
auto task = std::move(audio_encode_queue_.front());
|
||||
@@ -346,24 +401,42 @@ void AudioService::OpusCodecTask() {
|
||||
packet->frame_duration = OPUS_FRAME_DURATION_MS;
|
||||
packet->sample_rate = 16000;
|
||||
packet->timestamp = task->timestamp;
|
||||
if (!opus_encoder_->Encode(std::move(task->pcm), packet->payload)) {
|
||||
ESP_LOGE(TAG, "Failed to encode audio");
|
||||
continue;
|
||||
}
|
||||
|
||||
if (task->type == kAudioTaskTypeEncodeToSendQueue) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
|
||||
audio_send_queue_.push_back(std::move(packet));
|
||||
if (opus_encoder_ != nullptr && task->pcm.size() == encoder_frame_size_) {
|
||||
std::vector<uint8_t> buf(encoder_outbuf_size_);
|
||||
esp_audio_enc_in_frame_t in = {
|
||||
.buffer = (uint8_t *)(task->pcm.data()),
|
||||
.len = (uint32_t)(encoder_frame_size_ * sizeof(int16_t)),
|
||||
};
|
||||
esp_audio_enc_out_frame_t out = {
|
||||
.buffer = buf.data(),
|
||||
.len = (uint32_t)encoder_outbuf_size_,
|
||||
.encoded_bytes = 0,
|
||||
};
|
||||
auto ret = esp_opus_enc_process(opus_encoder_, &in, &out);
|
||||
if (ret == ESP_AUDIO_ERR_OK) {
|
||||
packet->payload.assign(buf.data(), buf.data() + out.encoded_bytes);
|
||||
|
||||
if (task->type == kAudioTaskTypeEncodeToSendQueue) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock2(audio_queue_mutex_);
|
||||
audio_send_queue_.push_back(std::move(packet));
|
||||
}
|
||||
if (callbacks_.on_send_queue_available) {
|
||||
callbacks_.on_send_queue_available();
|
||||
}
|
||||
} else if (task->type == kAudioTaskTypeEncodeToTestingQueue) {
|
||||
std::lock_guard<std::mutex> lock2(audio_queue_mutex_);
|
||||
audio_testing_queue_.push_back(std::move(packet));
|
||||
}
|
||||
debug_statistics_.encode_count++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to encode audio, error code: %d", ret);
|
||||
}
|
||||
if (callbacks_.on_send_queue_available) {
|
||||
callbacks_.on_send_queue_available();
|
||||
}
|
||||
} else if (task->type == kAudioTaskTypeEncodeToTestingQueue) {
|
||||
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
|
||||
audio_testing_queue_.push_back(std::move(packet));
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to encode audio: encoder not configured or invalid frame size (got %u, expected %u)",
|
||||
task->pcm.size(), encoder_frame_size_);
|
||||
}
|
||||
debug_statistics_.encode_count++;
|
||||
lock.lock();
|
||||
}
|
||||
}
|
||||
@@ -372,17 +445,38 @@ void AudioService::OpusCodecTask() {
|
||||
}
|
||||
|
||||
void AudioService::SetDecodeSampleRate(int sample_rate, int frame_duration) {
|
||||
if (opus_decoder_->sample_rate() == sample_rate && opus_decoder_->duration_ms() == frame_duration) {
|
||||
if (decoder_sample_rate_ == sample_rate && decoder_duration_ms_ == frame_duration) {
|
||||
return;
|
||||
}
|
||||
|
||||
opus_decoder_.reset();
|
||||
opus_decoder_ = std::make_unique<OpusDecoderWrapper>(sample_rate, 1, frame_duration);
|
||||
std::unique_lock<std::mutex> decoder_lock(decoder_mutex_);
|
||||
if (opus_decoder_ != nullptr) {
|
||||
esp_opus_dec_close(opus_decoder_);
|
||||
opus_decoder_ = nullptr;
|
||||
}
|
||||
decoder_lock.unlock();
|
||||
esp_opus_dec_cfg_t opus_dec_cfg = OPUS_DEC_CFG(sample_rate, frame_duration);
|
||||
auto ret = esp_opus_dec_open(&opus_dec_cfg, sizeof(esp_opus_dec_cfg_t), &opus_decoder_);
|
||||
if (opus_decoder_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio decoder, error code: %d", ret);
|
||||
return;
|
||||
}
|
||||
decoder_sample_rate_ = sample_rate;
|
||||
decoder_duration_ms_ = frame_duration;
|
||||
decoder_frame_size_ = decoder_sample_rate_ / 1000 * frame_duration;
|
||||
|
||||
auto codec = Board::GetInstance().GetAudioCodec();
|
||||
if (opus_decoder_->sample_rate() != codec->output_sample_rate()) {
|
||||
ESP_LOGI(TAG, "Resampling audio from %d to %d", opus_decoder_->sample_rate(), codec->output_sample_rate());
|
||||
output_resampler_.Configure(opus_decoder_->sample_rate(), codec->output_sample_rate());
|
||||
if (decoder_sample_rate_ != codec->output_sample_rate()) {
|
||||
ESP_LOGI(TAG, "Resampling audio from %d to %d", decoder_sample_rate_, codec->output_sample_rate());
|
||||
if (output_resampler_ != nullptr) {
|
||||
esp_ae_rate_cvt_close(output_resampler_);
|
||||
output_resampler_ = nullptr;
|
||||
}
|
||||
esp_ae_rate_cvt_cfg_t output_resampler_cfg = RATE_CVT_CFG(
|
||||
decoder_sample_rate_, codec->output_sample_rate(), ESP_AUDIO_MONO);
|
||||
auto resampler_ret = esp_ae_rate_cvt_open(&output_resampler_cfg, &output_resampler_);
|
||||
if (output_resampler_ == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create output resampler, error code: %d", resampler_ret);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -390,7 +484,6 @@ void AudioService::PushTaskToEncodeQueue(AudioTaskType type, std::vector<int16_t
|
||||
auto task = std::make_unique<AudioTask>();
|
||||
task->type = type;
|
||||
task->pcm = std::move(pcm);
|
||||
|
||||
/* Push the task to the encode queue */
|
||||
std::unique_lock<std::mutex> lock(audio_queue_mutex_);
|
||||
|
||||
@@ -466,6 +559,14 @@ void AudioService::EnableWakeWordDetection(bool enable) {
|
||||
}
|
||||
wake_word_initialized_ = true;
|
||||
}
|
||||
// Reset input resampler to clear cached data from previous mode (e.g. AudioProcessor)
|
||||
// This prevents buffer overflow when switching between different feed sizes
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(input_resampler_mutex_);
|
||||
if (input_resampler_ != nullptr) {
|
||||
esp_ae_rate_cvt_reset(input_resampler_);
|
||||
}
|
||||
}
|
||||
wake_word_->Start();
|
||||
xEventGroupSetBits(event_group_, AS_EVENT_WAKE_WORD_RUNNING);
|
||||
} else {
|
||||
@@ -485,6 +586,14 @@ void AudioService::EnableVoiceProcessing(bool enable) {
|
||||
/* We should make sure no audio is playing */
|
||||
ResetDecoder();
|
||||
audio_input_need_warmup_ = true;
|
||||
// Reset input resampler to clear cached data from previous mode (e.g. WakeWord)
|
||||
// This prevents buffer overflow when switching between different feed sizes
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(input_resampler_mutex_);
|
||||
if (input_resampler_ != nullptr) {
|
||||
esp_ae_rate_cvt_reset(input_resampler_);
|
||||
}
|
||||
}
|
||||
audio_processor_->Start();
|
||||
xEventGroupSetBits(event_group_, AS_EVENT_AUDIO_PROCESSOR_RUNNING);
|
||||
} else {
|
||||
@@ -580,18 +689,16 @@ void AudioService::PlaySound(const std::string_view& ogg) {
|
||||
// 解析OpusHead包
|
||||
if (pkt_len >= 19 && std::memcmp(pkt_ptr, "OpusHead", 8) == 0) {
|
||||
seen_head = true;
|
||||
|
||||
// OpusHead结构:[0-7] "OpusHead", [8] version, [9] channel_count, [10-11] pre_skip
|
||||
// [12-15] input_sample_rate, [16-17] output_gain, [18] mapping_family
|
||||
if (pkt_len >= 12) {
|
||||
uint8_t version = pkt_ptr[8];
|
||||
uint8_t channel_count = pkt_ptr[9];
|
||||
|
||||
if (pkt_len >= 16) {
|
||||
// 读取输入采样率 (little-endian)
|
||||
sample_rate = pkt_ptr[12] | (pkt_ptr[13] << 8) |
|
||||
sample_rate = pkt_ptr[12] | (pkt_ptr[13] << 8) |
|
||||
(pkt_ptr[14] << 16) | (pkt_ptr[15] << 24);
|
||||
ESP_LOGI(TAG, "OpusHead: version=%d, channels=%d, sample_rate=%d",
|
||||
ESP_LOGI(TAG, "OpusHead: version=%d, channels=%d, sample_rate=%d",
|
||||
version, channel_count, sample_rate);
|
||||
}
|
||||
}
|
||||
@@ -624,9 +731,20 @@ bool AudioService::IsIdle() {
|
||||
return audio_encode_queue_.empty() && audio_decode_queue_.empty() && audio_playback_queue_.empty() && audio_testing_queue_.empty();
|
||||
}
|
||||
|
||||
void AudioService::WaitForPlaybackQueueEmpty() {
|
||||
std::unique_lock<std::mutex> lock(audio_queue_mutex_);
|
||||
audio_queue_cv_.wait(lock, [this]() {
|
||||
return service_stopped_ || (audio_decode_queue_.empty() && audio_playback_queue_.empty());
|
||||
});
|
||||
}
|
||||
|
||||
void AudioService::ResetDecoder() {
|
||||
std::lock_guard<std::mutex> lock(audio_queue_mutex_);
|
||||
opus_decoder_->ResetState();
|
||||
std::unique_lock<std::mutex> decoder_lock(decoder_mutex_);
|
||||
if (opus_decoder_ != nullptr) {
|
||||
esp_opus_dec_reset(opus_decoder_);
|
||||
}
|
||||
decoder_lock.unlock();
|
||||
timestamp_queue_.clear();
|
||||
audio_decode_queue_.clear();
|
||||
audio_playback_queue_.clear();
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
#include <freertos/event_groups.h>
|
||||
#include <esp_timer.h>
|
||||
#include <model_path.h>
|
||||
|
||||
#include <opus_encoder.h>
|
||||
#include <opus_decoder.h>
|
||||
#include <opus_resampler.h>
|
||||
#include "esp_audio_enc.h"
|
||||
#include "esp_opus_enc.h"
|
||||
#include "esp_opus_dec.h"
|
||||
#include "esp_ae_rate_cvt.h"
|
||||
#include "esp_audio_types.h"
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "audio_processor.h"
|
||||
@@ -46,12 +47,34 @@
|
||||
#define AUDIO_POWER_TIMEOUT_MS 15000
|
||||
#define AUDIO_POWER_CHECK_INTERVAL_MS 1000
|
||||
|
||||
|
||||
#define AS_EVENT_AUDIO_TESTING_RUNNING (1 << 0)
|
||||
#define AS_EVENT_WAKE_WORD_RUNNING (1 << 1)
|
||||
#define AS_EVENT_AUDIO_PROCESSOR_RUNNING (1 << 2)
|
||||
#define AS_EVENT_PLAYBACK_NOT_EMPTY (1 << 3)
|
||||
|
||||
#define AS_OPUS_GET_FRAME_DRU_ENUM(duration_ms) \
|
||||
((duration_ms) == 5 ? ESP_OPUS_ENC_FRAME_DURATION_5_MS : \
|
||||
(duration_ms) == 10 ? ESP_OPUS_ENC_FRAME_DURATION_10_MS : \
|
||||
(duration_ms) == 20 ? ESP_OPUS_ENC_FRAME_DURATION_20_MS : \
|
||||
(duration_ms) == 40 ? ESP_OPUS_ENC_FRAME_DURATION_40_MS : \
|
||||
(duration_ms) == 60 ? ESP_OPUS_ENC_FRAME_DURATION_60_MS : \
|
||||
(duration_ms) == 80 ? ESP_OPUS_ENC_FRAME_DURATION_80_MS : \
|
||||
(duration_ms) == 100 ? ESP_OPUS_ENC_FRAME_DURATION_100_MS : \
|
||||
(duration_ms) == 120 ? ESP_OPUS_ENC_FRAME_DURATION_120_MS : -1)
|
||||
|
||||
#define AS_OPUS_ENC_CONFIG() { \
|
||||
.sample_rate = ESP_AUDIO_SAMPLE_RATE_16K, \
|
||||
.channel = ESP_AUDIO_MONO, \
|
||||
.bits_per_sample = ESP_AUDIO_BIT16, \
|
||||
.bitrate = ESP_OPUS_BITRATE_AUTO, \
|
||||
.frame_duration = (esp_opus_enc_frame_duration_t)AS_OPUS_GET_FRAME_DRU_ENUM(OPUS_FRAME_DURATION_MS), \
|
||||
.application_mode = ESP_OPUS_ENC_APPLICATION_AUDIO, \
|
||||
.complexity = 0, \
|
||||
.enable_fec = false, \
|
||||
.enable_dtx = true, \
|
||||
.enable_vbr = true, \
|
||||
}
|
||||
|
||||
struct AudioServiceCallbacks {
|
||||
std::function<void(void)> on_send_queue_available;
|
||||
std::function<void(const std::string&)> on_wake_word_detected;
|
||||
@@ -92,6 +115,7 @@ public:
|
||||
const std::string& GetLastWakeWord() const;
|
||||
bool IsVoiceDetected() const { return voice_detected_; }
|
||||
bool IsIdle();
|
||||
void WaitForPlaybackQueueEmpty();
|
||||
bool IsWakeWordRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_WAKE_WORD_RUNNING; }
|
||||
bool IsAudioProcessorRunning() const { return xEventGroupGetBits(event_group_) & AS_EVENT_AUDIO_PROCESSOR_RUNNING; }
|
||||
bool IsAfeWakeWord();
|
||||
@@ -116,11 +140,21 @@ private:
|
||||
std::unique_ptr<AudioProcessor> audio_processor_;
|
||||
std::unique_ptr<WakeWord> wake_word_;
|
||||
std::unique_ptr<AudioDebugger> audio_debugger_;
|
||||
std::unique_ptr<OpusEncoderWrapper> opus_encoder_;
|
||||
std::unique_ptr<OpusDecoderWrapper> opus_decoder_;
|
||||
OpusResampler input_resampler_;
|
||||
OpusResampler reference_resampler_;
|
||||
OpusResampler output_resampler_;
|
||||
void* opus_encoder_ = nullptr;
|
||||
void* opus_decoder_ = nullptr;
|
||||
std::mutex decoder_mutex_;
|
||||
std::mutex input_resampler_mutex_;
|
||||
esp_ae_rate_cvt_handle_t input_resampler_ = nullptr;
|
||||
esp_ae_rate_cvt_handle_t output_resampler_ = nullptr;
|
||||
|
||||
// Encoder/Decoder state
|
||||
int encoder_sample_rate_ = 16000;
|
||||
int encoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
|
||||
int encoder_frame_size_ = 0;
|
||||
int encoder_outbuf_size_ = 0;
|
||||
int decoder_sample_rate_ = 0;
|
||||
int decoder_duration_ms_ = OPUS_FRAME_DURATION_MS;
|
||||
int decoder_frame_size_ = 0;
|
||||
DebugStatistics debug_statistics_;
|
||||
srmodel_list_t* models_list_ = nullptr;
|
||||
|
||||
|
||||
@@ -92,7 +92,18 @@ void AfeAudioProcessor::Feed(std::vector<int16_t>&& data) {
|
||||
if (afe_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
afe_iface_->feed(afe_data_, data.data());
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
// Check running state inside lock to avoid TOCTOU race with Stop()
|
||||
if (!IsRunning()) {
|
||||
return;
|
||||
}
|
||||
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
|
||||
size_t chunk_size = afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
|
||||
while (input_buffer_.size() >= chunk_size) {
|
||||
afe_iface_->feed(afe_data_, input_buffer_.data());
|
||||
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
void AfeAudioProcessor::Start() {
|
||||
@@ -101,9 +112,12 @@ void AfeAudioProcessor::Start() {
|
||||
|
||||
void AfeAudioProcessor::Stop() {
|
||||
xEventGroupClearBits(event_group_, PROCESSOR_RUNNING);
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->reset_buffer(afe_data_);
|
||||
}
|
||||
input_buffer_.clear();
|
||||
}
|
||||
|
||||
bool AfeAudioProcessor::IsRunning() {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_processor.h"
|
||||
#include "audio_codec.h"
|
||||
@@ -37,6 +38,8 @@ private:
|
||||
AudioCodec* codec_ = nullptr;
|
||||
int frame_samples_ = 0;
|
||||
bool is_speaking_ = false;
|
||||
std::vector<int16_t> input_buffer_;
|
||||
std::mutex input_buffer_mutex_;
|
||||
std::vector<int16_t> output_buffer_;
|
||||
|
||||
void AudioProcessorTask();
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
#include "audio_processor.h"
|
||||
#include "audio_codec.h"
|
||||
@@ -27,7 +28,7 @@ private:
|
||||
int frame_samples_ = 0;
|
||||
std::function<void(std::vector<int16_t>&& data)> output_callback_;
|
||||
std::function<void(bool speaking)> vad_state_change_callback_;
|
||||
bool is_running_ = false;
|
||||
std::atomic<bool> is_running_ = false;
|
||||
};
|
||||
|
||||
#endif
|
||||
@@ -1,6 +1,5 @@
|
||||
#include "afe_wake_word.h"
|
||||
#include "audio_service.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <sstream>
|
||||
|
||||
@@ -100,16 +99,30 @@ void AfeWakeWord::Start() {
|
||||
|
||||
void AfeWakeWord::Stop() {
|
||||
xEventGroupClearBits(event_group_, DETECTION_RUNNING_EVENT);
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
if (afe_data_ != nullptr) {
|
||||
afe_iface_->reset_buffer(afe_data_);
|
||||
}
|
||||
input_buffer_.clear();
|
||||
}
|
||||
|
||||
void AfeWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (afe_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
afe_iface_->feed(afe_data_, data.data());
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
// Check running state inside lock to avoid TOCTOU race with Stop()
|
||||
if (!(xEventGroupGetBits(event_group_) & DETECTION_RUNNING_EVENT)) {
|
||||
return;
|
||||
}
|
||||
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
|
||||
size_t chunk_size = afe_iface_->get_feed_chunksize(afe_data_) * codec_->input_channels();
|
||||
while (input_buffer_.size() >= chunk_size) {
|
||||
afe_iface_->feed(afe_data_, input_buffer_.data());
|
||||
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunk_size);
|
||||
}
|
||||
}
|
||||
|
||||
size_t AfeWakeWord::GetFeedSize() {
|
||||
@@ -157,7 +170,7 @@ void AfeWakeWord::StoreWakeWordData(const int16_t* data, size_t samples) {
|
||||
}
|
||||
|
||||
void AfeWakeWord::EncodeWakeWordData() {
|
||||
const size_t stack_size = 4096 * 7;
|
||||
const size_t stack_size = 4096 * 6;
|
||||
wake_word_opus_.clear();
|
||||
if (wake_word_encode_task_stack_ == nullptr) {
|
||||
wake_word_encode_task_stack_ = (StackType_t*)heap_caps_malloc(stack_size, MALLOC_CAP_SPIRAM);
|
||||
@@ -172,20 +185,62 @@ void AfeWakeWord::EncodeWakeWordData() {
|
||||
auto this_ = (AfeWakeWord*)arg;
|
||||
{
|
||||
auto start_time = esp_timer_get_time();
|
||||
auto encoder = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
encoder->SetComplexity(0); // 0 is the fastest
|
||||
|
||||
// Create encoder
|
||||
esp_opus_enc_config_t opus_enc_cfg = AS_OPUS_ENC_CONFIG();
|
||||
void* encoder_handle = nullptr;
|
||||
auto ret = esp_opus_enc_open(&opus_enc_cfg, sizeof(esp_opus_enc_config_t), &encoder_handle);
|
||||
if (encoder_handle == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", ret);
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back(std::vector<uint8_t>());
|
||||
this_->wake_word_cv_.notify_all();
|
||||
return;
|
||||
}
|
||||
|
||||
// Get frame size
|
||||
int frame_size = 0;
|
||||
int outbuf_size = 0;
|
||||
esp_opus_enc_get_frame_size(encoder_handle, &frame_size, &outbuf_size);
|
||||
frame_size = frame_size / sizeof(int16_t);
|
||||
|
||||
// Encode all PCM data
|
||||
int packets = 0;
|
||||
std::vector<int16_t> in_buffer;
|
||||
esp_audio_enc_in_frame_t in = {};
|
||||
esp_audio_enc_out_frame_t out = {};
|
||||
|
||||
for (auto& pcm: this_->wake_word_pcm_) {
|
||||
encoder->Encode(std::move(pcm), [this_](std::vector<uint8_t>&& opus) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::move(opus));
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
packets++;
|
||||
if (in_buffer.empty()) {
|
||||
in_buffer = std::move(pcm);
|
||||
} else {
|
||||
in_buffer.reserve(in_buffer.size() + pcm.size());
|
||||
in_buffer.insert(in_buffer.end(), pcm.begin(), pcm.end());
|
||||
}
|
||||
|
||||
while (in_buffer.size() >= frame_size) {
|
||||
std::vector<uint8_t> opus_buf(outbuf_size);
|
||||
in.buffer = (uint8_t *)(in_buffer.data());
|
||||
in.len = (uint32_t)(frame_size * sizeof(int16_t));
|
||||
out.buffer = opus_buf.data();
|
||||
out.len = outbuf_size;
|
||||
out.encoded_bytes = 0;
|
||||
|
||||
ret = esp_opus_enc_process(encoder_handle, &in, &out);
|
||||
if (ret == ESP_AUDIO_ERR_OK) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(opus_buf.data(), opus_buf.data() + out.encoded_bytes);
|
||||
this_->wake_word_cv_.notify_all();
|
||||
packets++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to encode audio, error code: %d", ret);
|
||||
}
|
||||
|
||||
in_buffer.erase(in_buffer.begin(), in_buffer.begin() + frame_size);
|
||||
}
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
// Close encoder
|
||||
esp_opus_enc_close(encoder_handle);
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
|
||||
|
||||
|
||||
@@ -44,6 +44,8 @@ private:
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
std::vector<int16_t> input_buffer_;
|
||||
std::mutex input_buffer_mutex_;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
|
||||
|
||||
@@ -9,10 +9,8 @@
|
||||
#include <esp_mn_speech_commands.h>
|
||||
#include <cJSON.h>
|
||||
|
||||
|
||||
#define TAG "CustomWakeWord"
|
||||
|
||||
|
||||
CustomWakeWord::CustomWakeWord()
|
||||
: wake_word_pcm_(), wake_word_opus_() {
|
||||
}
|
||||
@@ -140,49 +138,64 @@ void CustomWakeWord::Start() {
|
||||
|
||||
void CustomWakeWord::Stop() {
|
||||
running_ = false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
input_buffer_.clear();
|
||||
}
|
||||
|
||||
void CustomWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (multinet_model_data_ == nullptr || !running_) {
|
||||
if (multinet_model_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
// Check running state inside lock to avoid TOCTOU race with Stop()
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
esp_mn_state_t mn_state;
|
||||
// If input channels is 2, we need to fetch the left channel data
|
||||
if (codec_->input_channels() == 2) {
|
||||
auto mono_data = std::vector<int16_t>(data.size() / 2);
|
||||
for (size_t i = 0, j = 0; i < mono_data.size(); ++i, j += 2) {
|
||||
mono_data[i] = data[j];
|
||||
for (size_t i = 0; i < data.size(); i += 2) {
|
||||
input_buffer_.push_back(data[i]);
|
||||
}
|
||||
|
||||
StoreWakeWordData(mono_data);
|
||||
mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(mono_data.data()));
|
||||
} else {
|
||||
StoreWakeWordData(data);
|
||||
mn_state = multinet_->detect(multinet_model_data_, const_cast<int16_t*>(data.data()));
|
||||
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
|
||||
}
|
||||
|
||||
if (mn_state == ESP_MN_STATE_DETECTING) {
|
||||
return;
|
||||
} else if (mn_state == ESP_MN_STATE_DETECTED) {
|
||||
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
|
||||
for (int i = 0; i < mn_result->num && running_; i++) {
|
||||
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
|
||||
mn_result->command_id[i], mn_result->string, mn_result->prob[i]);
|
||||
auto& command = commands_[mn_result->command_id[i] - 1];
|
||||
if (command.action == "wake") {
|
||||
last_detected_wake_word_ = command.text;
|
||||
running_ = false;
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
int chunksize = multinet_->get_samp_chunksize(multinet_model_data_);
|
||||
while (input_buffer_.size() >= chunksize) {
|
||||
std::vector<int16_t> chunk(input_buffer_.begin(), input_buffer_.begin() + chunksize);
|
||||
StoreWakeWordData(chunk);
|
||||
|
||||
esp_mn_state_t mn_state = multinet_->detect(multinet_model_data_, chunk.data());
|
||||
|
||||
if (mn_state == ESP_MN_STATE_DETECTED) {
|
||||
esp_mn_results_t *mn_result = multinet_->get_results(multinet_model_data_);
|
||||
for (int i = 0; i < mn_result->num && running_; i++) {
|
||||
ESP_LOGI(TAG, "Custom wake word detected: command_id=%d, string=%s, prob=%f",
|
||||
mn_result->command_id[i], mn_result->string, mn_result->prob[i]);
|
||||
auto& command = commands_[mn_result->command_id[i] - 1];
|
||||
if (command.action == "wake") {
|
||||
last_detected_wake_word_ = command.text;
|
||||
running_ = false;
|
||||
input_buffer_.clear();
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
}
|
||||
}
|
||||
multinet_->clean(multinet_model_data_);
|
||||
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
|
||||
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
|
||||
multinet_->clean(multinet_model_data_);
|
||||
}
|
||||
multinet_->clean(multinet_model_data_);
|
||||
} else if (mn_state == ESP_MN_STATE_TIMEOUT) {
|
||||
ESP_LOGD(TAG, "Command word detection timeout, cleaning state");
|
||||
multinet_->clean(multinet_model_data_);
|
||||
|
||||
if (!running_) {
|
||||
break;
|
||||
}
|
||||
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunksize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,20 +231,56 @@ void CustomWakeWord::EncodeWakeWordData() {
|
||||
auto this_ = (CustomWakeWord*)arg;
|
||||
{
|
||||
auto start_time = esp_timer_get_time();
|
||||
auto encoder = std::make_unique<OpusEncoderWrapper>(16000, 1, OPUS_FRAME_DURATION_MS);
|
||||
encoder->SetComplexity(0); // 0 is the fastest
|
||||
|
||||
// Create encoder
|
||||
esp_opus_enc_config_t opus_enc_cfg = AS_OPUS_ENC_CONFIG();
|
||||
void* encoder_handle = nullptr;
|
||||
auto ret = esp_opus_enc_open(&opus_enc_cfg, sizeof(esp_opus_enc_config_t), &encoder_handle);
|
||||
if (encoder_handle == nullptr) {
|
||||
ESP_LOGE(TAG, "Failed to create audio encoder, error code: %d", ret);
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.push_back(std::vector<uint8_t>());
|
||||
this_->wake_word_cv_.notify_all();
|
||||
return;
|
||||
}
|
||||
// Get frame size
|
||||
int frame_size = 0;
|
||||
int outbuf_size = 0;
|
||||
esp_opus_enc_get_frame_size(encoder_handle, &frame_size, &outbuf_size);
|
||||
frame_size = frame_size / sizeof(int16_t);
|
||||
// Encode all PCM data
|
||||
int packets = 0;
|
||||
std::vector<int16_t> in_buffer;
|
||||
esp_audio_enc_in_frame_t in = {};
|
||||
esp_audio_enc_out_frame_t out = {};
|
||||
for (auto& pcm: this_->wake_word_pcm_) {
|
||||
encoder->Encode(std::move(pcm), [this_](std::vector<uint8_t>&& opus) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(std::move(opus));
|
||||
this_->wake_word_cv_.notify_all();
|
||||
});
|
||||
packets++;
|
||||
if (in_buffer.empty()) {
|
||||
in_buffer = std::move(pcm);
|
||||
} else {
|
||||
in_buffer.reserve(in_buffer.size() + pcm.size());
|
||||
in_buffer.insert(in_buffer.end(), pcm.begin(), pcm.end());
|
||||
}
|
||||
while (in_buffer.size() >= frame_size) {
|
||||
std::vector<uint8_t> opus_buf(outbuf_size);
|
||||
in.buffer = (uint8_t *)(in_buffer.data());
|
||||
in.len = (uint32_t)(frame_size * sizeof(int16_t));
|
||||
out.buffer = opus_buf.data();
|
||||
out.len = outbuf_size;
|
||||
out.encoded_bytes = 0;
|
||||
ret = esp_opus_enc_process(encoder_handle, &in, &out);
|
||||
if (ret == ESP_AUDIO_ERR_OK) {
|
||||
std::lock_guard<std::mutex> lock(this_->wake_word_mutex_);
|
||||
this_->wake_word_opus_.emplace_back(opus_buf.data(), opus_buf.data() + out.encoded_bytes);
|
||||
this_->wake_word_cv_.notify_all();
|
||||
packets++;
|
||||
} else {
|
||||
ESP_LOGE(TAG, "Failed to encode audio, error code: %d", ret);
|
||||
}
|
||||
in_buffer.erase(in_buffer.begin(), in_buffer.begin() + frame_size);
|
||||
}
|
||||
}
|
||||
this_->wake_word_pcm_.clear();
|
||||
|
||||
// Close encoder
|
||||
esp_opus_enc_close(encoder_handle);
|
||||
auto end_time = esp_timer_get_time();
|
||||
ESP_LOGI(TAG, "Encode wake word opus %d packets in %ld ms", packets, (long)((end_time - start_time) / 1000));
|
||||
|
||||
|
||||
@@ -53,6 +53,8 @@ private:
|
||||
AudioCodec* codec_ = nullptr;
|
||||
std::string last_detected_wake_word_;
|
||||
std::atomic<bool> running_ = false;
|
||||
std::vector<int16_t> input_buffer_;
|
||||
std::mutex input_buffer_mutex_;
|
||||
|
||||
TaskHandle_t wake_word_encode_task_ = nullptr;
|
||||
StaticTask_t* wake_word_encode_task_buffer_ = nullptr;
|
||||
|
||||
@@ -54,21 +54,44 @@ void EspWakeWord::Start() {
|
||||
|
||||
void EspWakeWord::Stop() {
|
||||
running_ = false;
|
||||
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
input_buffer_.clear();
|
||||
}
|
||||
|
||||
void EspWakeWord::Feed(const std::vector<int16_t>& data) {
|
||||
if (wakenet_data_ == nullptr || !running_) {
|
||||
if (wakenet_data_ == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
int res = wakenet_iface_->detect(wakenet_data_, (int16_t *)data.data());
|
||||
if (res > 0) {
|
||||
last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res);
|
||||
running_ = false;
|
||||
std::lock_guard<std::mutex> lock(input_buffer_mutex_);
|
||||
// Check running state inside lock to avoid TOCTOU race with Stop()
|
||||
if (!running_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
if (codec_->input_channels() == 2) {
|
||||
for (size_t i = 0; i < data.size(); i += 2) {
|
||||
input_buffer_.push_back(data[i]);
|
||||
}
|
||||
} else {
|
||||
input_buffer_.insert(input_buffer_.end(), data.begin(), data.end());
|
||||
}
|
||||
|
||||
int chunksize = wakenet_iface_->get_samp_chunksize(wakenet_data_);
|
||||
while (input_buffer_.size() >= chunksize) {
|
||||
int res = wakenet_iface_->detect(wakenet_data_, input_buffer_.data());
|
||||
if (res > 0) {
|
||||
last_detected_wake_word_ = wakenet_iface_->get_word_name(wakenet_data_, res);
|
||||
running_ = false;
|
||||
input_buffer_.clear();
|
||||
|
||||
if (wake_word_detected_callback_) {
|
||||
wake_word_detected_callback_(last_detected_wake_word_);
|
||||
}
|
||||
break;
|
||||
}
|
||||
input_buffer_.erase(input_buffer_.begin(), input_buffer_.begin() + chunksize);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <vector>
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
#include <mutex>
|
||||
|
||||
#include "audio_codec.h"
|
||||
#include "wake_word.h"
|
||||
@@ -37,6 +38,8 @@ private:
|
||||
|
||||
std::function<void(const std::string& wake_word)> wake_word_detected_callback_;
|
||||
std::string last_detected_wake_word_;
|
||||
std::vector<int16_t> input_buffer_;
|
||||
std::mutex input_buffer_mutex_;
|
||||
};
|
||||
|
||||
#endif
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <esp_log.h>
|
||||
#include <esp_sleep.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include "application.h"
|
||||
#include "button.h"
|
||||
@@ -136,9 +135,9 @@ class AIPILite : public WifiBoard {
|
||||
boot_button_.OnClick([this]() {
|
||||
power_save_timer_->WakeUp();
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting &&
|
||||
!WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@@ -154,7 +153,7 @@ class AIPILite : public WifiBoard {
|
||||
app.SetDeviceState(kDeviceStateWifiConfiguring);
|
||||
|
||||
// 重置WiFi配置以确保进入配网模式
|
||||
ResetWifiConfiguration();
|
||||
EnterWifiConfigMode();
|
||||
});
|
||||
|
||||
power_button_.OnClick([this]() { power_save_timer_->WakeUp(); });
|
||||
@@ -236,11 +235,11 @@ class AIPILite : public WifiBoard {
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "i2c_device.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <freertos/FreeRTOS.h>
|
||||
@@ -244,8 +243,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
@@ -227,8 +226,9 @@ private:
|
||||
|
||||
middle_button_.OnLongPress([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (app.GetDeviceState() != kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
@@ -377,11 +377,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
@@ -280,9 +279,10 @@ private:
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (self->GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
auto& wifi_board = static_cast<WifiBoard&>(self->GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -463,11 +463,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
DualNetworkBoard::SetPowerSaveMode(enabled);
|
||||
DualNetworkBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
#include "i2c_device.h"
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#include <driver/rtc_io.h>
|
||||
#include <esp_sleep.h>
|
||||
@@ -262,8 +261,9 @@ private:
|
||||
auto self = static_cast<atk_dnesp32s3_box2_wifi*>(usr_data);
|
||||
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
self->ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
self->EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
|
||||
if (self->power_status_ == kDeviceBatterySupply) {
|
||||
@@ -442,11 +442,11 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
virtual void SetPowerSaveMode(bool enabled) override {
|
||||
if (!enabled) {
|
||||
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
|
||||
if (level != PowerSaveLevel::LOW_POWER) {
|
||||
power_save_timer_->WakeUp();
|
||||
}
|
||||
WifiBoard::SetPowerSaveMode(enabled);
|
||||
WifiBoard::SetPowerSaveLevel(level);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@
|
||||
#include "config.h"
|
||||
#include "i2c_device.h"
|
||||
#include "led/single_led.h"
|
||||
#include "esp32_camera.h"
|
||||
#include "esp_video.h"
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <driver/spi_common.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "atk_dnesp32s3"
|
||||
|
||||
@@ -50,7 +49,7 @@ private:
|
||||
Button boot_button_;
|
||||
LcdDisplay* display_;
|
||||
XL9555* xl9555_;
|
||||
Esp32Camera* camera_;
|
||||
EspVideo* camera_;
|
||||
|
||||
void InitializeI2c() {
|
||||
// Initialize I2C peripheral
|
||||
@@ -87,8 +86,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
@@ -179,7 +179,7 @@ private:
|
||||
.dvp = &dvp_config,
|
||||
};
|
||||
|
||||
camera_ = new Esp32Camera(video_config);
|
||||
camera_ = new EspVideo(video_config);
|
||||
}
|
||||
|
||||
public:
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "driver/gpio.h"
|
||||
#include "assets/lang_config.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@@ -60,8 +59,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "AtomEchoS3R"
|
||||
|
||||
@@ -57,8 +56,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "led/circular_strip.h"
|
||||
|
||||
#define TAG "XX+EchoBase"
|
||||
@@ -91,8 +90,9 @@ private:
|
||||
|
||||
ESP_LOGI(TAG, " ===>>> face_button_.OnClick ");
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
@@ -180,8 +179,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -14,12 +14,6 @@ AtomS3R CAM、AtomS3R M12 是 M5Stack 推出的基于 ESP32-S3-PICO-1-N8R8 的
|
||||
|
||||
两款开发版均**不带屏幕、不带额外按键**,需要使用语音唤醒。必要时,需要使用 `idf.py monitor` 查看 log 以确定运行状态。
|
||||
|
||||
> ![NOTE]
|
||||
>
|
||||
> 自版本 [待定] 起,由于依赖库不支持 OV3660 传感器,AtomS3R M12 无法使用摄像头识别功能。
|
||||
>
|
||||
> AtomS3R CAM 不受影响;使用旧版本小智固件的 AtomS3R M12 不受影响。
|
||||
|
||||
## 配置、编译命令
|
||||
|
||||
**配置编译目标为 ESP32S3**
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include "esp32_camera.h"
|
||||
|
||||
#define TAG "AtomS3R CAM/M12 + EchoBase"
|
||||
@@ -127,47 +126,33 @@ private:
|
||||
}
|
||||
|
||||
void InitializeCamera() {
|
||||
static esp_cam_ctlr_dvp_pin_config_t dvp_pin_config = {
|
||||
.data_width = CAM_CTLR_DATA_WIDTH_8,
|
||||
.data_io = {
|
||||
[0] = CAMERA_PIN_D0,
|
||||
[1] = CAMERA_PIN_D1,
|
||||
[2] = CAMERA_PIN_D2,
|
||||
[3] = CAMERA_PIN_D3,
|
||||
[4] = CAMERA_PIN_D4,
|
||||
[5] = CAMERA_PIN_D5,
|
||||
[6] = CAMERA_PIN_D6,
|
||||
[7] = CAMERA_PIN_D7,
|
||||
},
|
||||
.vsync_io = CAMERA_PIN_VSYNC,
|
||||
.de_io = CAMERA_PIN_HREF,
|
||||
.pclk_io = CAMERA_PIN_PCLK,
|
||||
.xclk_io = CAMERA_PIN_XCLK,
|
||||
};
|
||||
camera_config_t config = {};
|
||||
config.pin_d0 = CAMERA_PIN_D0;
|
||||
config.pin_d1 = CAMERA_PIN_D1;
|
||||
config.pin_d2 = CAMERA_PIN_D2;
|
||||
config.pin_d3 = CAMERA_PIN_D3;
|
||||
config.pin_d4 = CAMERA_PIN_D4;
|
||||
config.pin_d5 = CAMERA_PIN_D5;
|
||||
config.pin_d6 = CAMERA_PIN_D6;
|
||||
config.pin_d7 = CAMERA_PIN_D7;
|
||||
config.pin_xclk = CAMERA_PIN_XCLK;
|
||||
config.pin_pclk = CAMERA_PIN_PCLK;
|
||||
config.pin_vsync = CAMERA_PIN_VSYNC;
|
||||
config.pin_href = CAMERA_PIN_HREF;
|
||||
config.pin_sccb_sda = CAMERA_PIN_SIOD;
|
||||
config.pin_sccb_scl = CAMERA_PIN_SIOC;
|
||||
config.sccb_i2c_port = 1;
|
||||
config.pin_pwdn = CAMERA_PIN_PWDN;
|
||||
config.pin_reset = CAMERA_PIN_RESET;
|
||||
config.xclk_freq_hz = XCLK_FREQ_HZ;
|
||||
config.pixel_format = PIXFORMAT_RGB565;
|
||||
config.frame_size = FRAMESIZE_QVGA;
|
||||
config.jpeg_quality = 12;
|
||||
config.fb_count = 1;
|
||||
config.fb_location = CAMERA_FB_IN_PSRAM;
|
||||
config.grab_mode = CAMERA_GRAB_WHEN_EMPTY;
|
||||
|
||||
esp_video_init_sccb_config_t sccb_config = {
|
||||
.init_sccb = true,
|
||||
.i2c_config = {
|
||||
.port = 1,
|
||||
.scl_pin = CAMERA_PIN_SIOC,
|
||||
.sda_pin = CAMERA_PIN_SIOD,
|
||||
},
|
||||
.freq = 100000,
|
||||
};
|
||||
|
||||
esp_video_init_dvp_config_t dvp_config = {
|
||||
.sccb_config = sccb_config,
|
||||
.reset_pin = CAMERA_PIN_RESET,
|
||||
.pwdn_pin = CAMERA_PIN_PWDN,
|
||||
.dvp_pin = dvp_pin_config,
|
||||
.xclk_freq = XCLK_FREQ_HZ,
|
||||
};
|
||||
|
||||
esp_video_init_config_t video_config = {
|
||||
.dvp = &dvp_config,
|
||||
};
|
||||
|
||||
camera_ = new Esp32Camera(video_config);
|
||||
camera_ = new Esp32Camera(config);
|
||||
camera_->SetHMirror(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
"name": "atoms3r-cam-m12-echo-base",
|
||||
"sdkconfig_append": [
|
||||
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\"",
|
||||
"CONFIG_CAMERA_GC0308=y",
|
||||
"CONFIG_CAMERA_GC0308_AUTO_DETECT_DVP_INTERFACE_SENSOR=y",
|
||||
"CONFIG_CAMERA_GC0308_DVP_YUV422_320X240_20FPS=y"
|
||||
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v2/8m.csv\""
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -9,7 +9,6 @@
|
||||
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <wifi_station.h>
|
||||
#include <esp_lcd_panel_io.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_gc9a01.h>
|
||||
@@ -258,8 +257,9 @@ private:
|
||||
void InitializeButtons() {
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
ResetWifiConfiguration();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "dual_network_board.h"
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "display/lcd_display.h"
|
||||
#include "system_reset.h"
|
||||
@@ -7,7 +7,6 @@
|
||||
#include "config.h"
|
||||
#include "led/single_led.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
@@ -58,7 +57,7 @@ static const gc9a01_lcd_init_cmd_t gc9107_lcd_init_cmds[] = {
|
||||
|
||||
#define TAG "ESP32-LCD-MarsbearSupport"
|
||||
|
||||
class CompactWifiBoardLCD : public DualNetworkBoard {
|
||||
class CompactWifiBoardLCD : public WifiBoard {
|
||||
private:
|
||||
Button boot_button_;
|
||||
Button touch_button_;
|
||||
@@ -137,25 +136,14 @@ private:
|
||||
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
}
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 1);
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
|
||||
boot_button_.OnDoubleClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
SwitchNetworkType();
|
||||
}
|
||||
});
|
||||
|
||||
asr_button_.OnClick([this]() {
|
||||
std::string wake_word="你好小智";
|
||||
Application::GetInstance().WakeWordInvoke(wake_word);
|
||||
@@ -174,8 +162,7 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
CompactWifiBoardLCD() :
|
||||
DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN),
|
||||
CompactWifiBoardLCD() : WifiBoard(),
|
||||
boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO) {
|
||||
InitializeSpi();
|
||||
InitializeLcdDisplay();
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
#elif CONFIG_OLED_SSD1306_128X64
|
||||
#define DISPLAY_HEIGHT 64
|
||||
#else
|
||||
#error "未选择 OLED 屏幕类型"
|
||||
#error "OLED display type is not selected"
|
||||
#endif
|
||||
|
||||
#define DISPLAY_MIRROR_X true
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#include "dual_network_board.h"
|
||||
#include "wifi_board.h"
|
||||
#include "codecs/no_audio_codec.h"
|
||||
#include "system_reset.h"
|
||||
#include "application.h"
|
||||
@@ -9,7 +9,6 @@
|
||||
#include "led/single_led.h"
|
||||
#include "display/oled_display.h"
|
||||
|
||||
#include <wifi_station.h>
|
||||
#include <esp_log.h>
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
@@ -17,7 +16,7 @@
|
||||
|
||||
#define TAG "ESP32-MarsbearSupport"
|
||||
|
||||
class CompactWifiBoard : public DualNetworkBoard {
|
||||
class CompactWifiBoard : public WifiBoard {
|
||||
private:
|
||||
Button boot_button_;
|
||||
Button touch_button_;
|
||||
@@ -105,25 +104,14 @@ private:
|
||||
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
}
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
gpio_set_level(BUILTIN_LED_GPIO, 1);
|
||||
app.ToggleChatState();
|
||||
});
|
||||
|
||||
|
||||
boot_button_.OnDoubleClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||
SwitchNetworkType();
|
||||
}
|
||||
});
|
||||
|
||||
asr_button_.OnClick([this]() {
|
||||
std::string wake_word="你好小智";
|
||||
Application::GetInstance().WakeWordInvoke(wake_word);
|
||||
@@ -145,7 +133,7 @@ private:
|
||||
}
|
||||
|
||||
public:
|
||||
CompactWifiBoard() : DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN), boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO)
|
||||
CompactWifiBoard() : WifiBoard(), boot_button_(BOOT_BUTTON_GPIO), touch_button_(TOUCH_BUTTON_GPIO), asr_button_(ASR_BUTTON_GPIO)
|
||||
{
|
||||
InitializeDisplayI2c();
|
||||
InitializeSsd1306Display();
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#include <driver/i2c_master.h>
|
||||
#include <esp_lcd_panel_ops.h>
|
||||
#include <esp_lcd_panel_vendor.h>
|
||||
#include <wifi_station.h>
|
||||
|
||||
#define TAG "CompactMl307Board"
|
||||
|
||||
@@ -96,10 +95,11 @@ private:
|
||||
boot_button_.OnClick([this]() {
|
||||
auto& app = Application::GetInstance();
|
||||
if (GetNetworkType() == NetworkType::WIFI) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) {
|
||||
if (app.GetDeviceState() == kDeviceStateStarting) {
|
||||
// cast to WifiBoard
|
||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
||||
wifi_board.ResetWifiConfiguration();
|
||||
wifi_board.EnterWifiConfigMode();
|
||||
return;
|
||||
}
|
||||
}
|
||||
app.ToggleChatState();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user