Compare commits

...

13 Commits

Author SHA1 Message Date
Ky1eYang
8e572296d7 refactor for windows (#1021)
Co-authored-by: yangkaiyue <yangkaiyue1@tenclass.com>
2025-08-02 14:11:47 +08:00
Terrence
ee4a2ab5fc Add license_tools 2025-06-12 21:54:10 +08:00
Terrence
f8c9126442 Bump to 1.7.4 2025-06-12 21:53:26 +08:00
Lucinhu
a118e8f786 feat: es8311添加功放使能引脚的反向控制 (#808) 2025-06-12 20:37:15 +08:00
Terrence
85f3f1ba9f 修复model分区未初始化导致重启 2025-06-12 15:06:31 +08:00
Terrence
e2777cc16b esp-hi special app partition 2025-06-12 15:06:31 +08:00
wuxingzhong
5bb7c6deb8 fix: 修复修改vad模型为:vadnet1 medium时, 不生效问题. (#802) 2025-06-12 15:02:48 +08:00
netseye
895a3cfa72 fix: 修复tab5 esp-hosted 升级带来的crash问题 (#795)
Co-authored-by: Jeakin <Jeakin@botu.cc>
2025-06-11 12:14:22 +08:00
laride
c9dec29d73 fix: delay WebServer startup to mitigate stack overflow (#797) 2025-06-11 12:12:39 +08:00
Terrence
968ed1fae3 v1.7.3: 参考ESP-HI,为所有C3板子增加10多KB可用SRAM 2025-06-10 01:33:20 +08:00
Terrence
f8cd0d30cd fix idf_component.yml 2025-06-09 12:11:51 +08:00
laride
01215d77ed fix: 修复 ESP-Hi 在联网时 crash 的问题 (#790)
* fix: resolve crash issue during network connection on ESP-Hi

* fix: adjust dependency rules for some components
2025-06-09 11:48:20 +08:00
wdmomoxx
3df2f3970a 添加esp32支持唤醒词 (#782)
* Update README.md

* Update config.h

增加MCP控制方式

* Update esp32_cgc_board.cc

增加MCP控制方式

* Update CMakeLists.txt

增加ESP32 CGC 144开发板

* Update Kconfig.projbuild

增加ESP32 CGC 144开发板

* Create README.md

增加ESP32 CGC 144开发板

* Add files via upload

* Update config.h

修改注释

* Update Kconfig.projbuild

增加ESP32语言唤醒支持(目前需要开启PSRAM)

* Add files via upload

Add wake word to esp32

* Update sdkconfig.defaults.esp32

增加看门狗超时
2025-06-09 04:43:46 +08:00
22 changed files with 2122 additions and 190 deletions

View File

@@ -4,7 +4,7 @@
# CMakeLists in this exact order for cmake to work correctly
cmake_minimum_required(VERSION 3.16)
set(PROJECT_VER "1.7.2")
set(PROJECT_VER "1.7.4")
# Add this line to disable the specific warning
add_compile_options(-Wno-missing-field-initializers)

View File

@@ -360,9 +360,9 @@ config USE_WECHAT_MESSAGE_STYLE
config USE_ESP_WAKE_WORD
bool "Enable Wake Word Detection (without AFE)"
default n
depends on IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5
depends on IDF_TARGET_ESP32C3 || IDF_TARGET_ESP32C5 || IDF_TARGET_ESP32
help
支持 ESP32 C3 与 ESP32 C5
支持 ESP32 C3 与 ESP32 C5增加ESP32支持需要开启PSRAM
config USE_AFE_WAKE_WORD
bool "Enable Wake Word Detection (AFE)"

View File

@@ -6,13 +6,14 @@
Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk) {
gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk, bool pa_inverted) {
duplex_ = true; // 是否双工
input_reference_ = false; // 是否使用参考输入,实现回声消除
input_channels_ = 1; // 输入通道数
input_sample_rate_ = input_sample_rate;
output_sample_rate_ = output_sample_rate;
pa_pin_ = pa_pin;
pa_inverted_ = pa_inverted;
CreateDuplexChannels(mclk, bclk, ws, dout, din);
// Do initialize of related interface: data_if, ctrl_if and gpio_if
@@ -44,6 +45,7 @@ Es8311AudioCodec::Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port,
es8311_cfg.use_mclk = use_mclk;
es8311_cfg.hw_gain.pa_voltage = 5.0;
es8311_cfg.hw_gain.codec_dac_voltage = 3.3;
es8311_cfg.pa_reverted = pa_inverted_;
codec_if_ = es8311_codec_new(&es8311_cfg);
assert(codec_if_ != NULL);
@@ -171,12 +173,12 @@ void Es8311AudioCodec::EnableOutput(bool enable) {
ESP_ERROR_CHECK(esp_codec_dev_open(output_dev_, &fs));
ESP_ERROR_CHECK(esp_codec_dev_set_out_vol(output_dev_, output_volume_));
if (pa_pin_ != GPIO_NUM_NC) {
gpio_set_level(pa_pin_, 1);
gpio_set_level(pa_pin_, !pa_inverted_ ? 1 : 0);
}
} else {
ESP_ERROR_CHECK(esp_codec_dev_close(output_dev_));
if (pa_pin_ != GPIO_NUM_NC) {
gpio_set_level(pa_pin_, 0);
gpio_set_level(pa_pin_, !pa_inverted_ ? 0 : 1);
}
}
AudioCodec::EnableOutput(enable);

View File

@@ -18,6 +18,7 @@ private:
esp_codec_dev_handle_t output_dev_ = nullptr;
esp_codec_dev_handle_t input_dev_ = nullptr;
gpio_num_t pa_pin_ = GPIO_NUM_NC;
bool pa_inverted_ = false;
void CreateDuplexChannels(gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din);
@@ -27,7 +28,7 @@ private:
public:
Es8311AudioCodec(void* i2c_master_handle, i2c_port_t i2c_port, int input_sample_rate, int output_sample_rate,
gpio_num_t mclk, gpio_num_t bclk, gpio_num_t ws, gpio_num_t dout, gpio_num_t din,
gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true);
gpio_num_t pa_pin, uint8_t es8311_addr, bool use_mclk = true, bool pa_inverted = false);
virtual ~Es8311AudioCodec();
virtual void SetOutputVolume(int volume) override;

View File

@@ -24,11 +24,15 @@ void AfeAudioProcessor::Initialize(AudioCodec* codec) {
srmodel_list_t *models = esp_srmodel_init("model");
char* ns_model_name = esp_srmodel_filter(models, ESP_NSNET_PREFIX, NULL);
char* vad_model_name = esp_srmodel_filter(models, ESP_VADN_PREFIX, NULL);
afe_config_t* afe_config = afe_config_init(input_format.c_str(), NULL, AFE_TYPE_VC, AFE_MODE_HIGH_PERF);
afe_config->aec_mode = AEC_MODE_VOIP_HIGH_PERF;
afe_config->vad_mode = VAD_MODE_0;
afe_config->vad_min_noise_ms = 100;
if (vad_model_name != nullptr) {
afe_config->vad_model_name = vad_model_name;
}
if (ns_model_name != nullptr) {
afe_config->ns_init = true;

View File

@@ -35,6 +35,10 @@ void AfeWakeWord::Initialize(AudioCodec* codec) {
int ref_num = codec_->input_reference() ? 1 : 0;
srmodel_list_t *models = esp_srmodel_init("model");
if (models == nullptr || models->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return;
}
for (int i = 0; i < models->num; i++) {
ESP_LOGI(TAG, "Model %d: %s", i, models->model_name[i]);
if (strstr(models->model_name[i], ESP_WN_PREFIX) != NULL) {

View File

@@ -27,7 +27,10 @@ void EspWakeWord::Initialize(AudioCodec* codec) {
codec_ = codec;
wakenet_model_ = esp_srmodel_init("model");
if (wakenet_model_ == nullptr || wakenet_model_->num == -1) {
ESP_LOGE(TAG, "Failed to initialize wakenet model");
return;
}
if(wakenet_model_->num > 1) {
ESP_LOGW(TAG, "More than one model found, using the first one");
} else if (wakenet_model_->num == 0) {

View File

@@ -16,7 +16,7 @@
"CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0",
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y",
"CONFIG_ESP_MAIN_TASK_STACK_SIZE=6144",
"CONFIG_ESP_MAIN_TASK_STACK_SIZE=7168",
"CONFIG_FREERTOS_HZ=1000",
"CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768",
"CONFIG_LWIP_MAX_SOCKETS=10",
@@ -28,7 +28,8 @@
"CONFIG_MMAP_FILE_NAME_LENGTH=25",
"CONFIG_ESP_CONSOLE_NONE=y",
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_IOT_PROTOCOL_MCP=y"
"CONFIG_IOT_PROTOCOL_MCP=y",
"CONFIG_COMPILER_OPTIMIZATION_SIZE=y"
]
}
]

View File

@@ -85,17 +85,28 @@ private:
int32_t event_id, void* event_data)
{
if (event_base == WIFI_EVENT && event_id == WIFI_EVENT_STA_CONNECTED) {
EspHi* instance = static_cast<EspHi*>(arg);
if (!instance->web_server_initialized_) {
ESP_LOGI(TAG, "WiFi connected, init web control server");
esp_err_t err = esp_hi_web_control_server_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize web control server: %d", err);
} else {
ESP_LOGI(TAG, "Web control server initialized");
instance->web_server_initialized_ = true;
}
}
xTaskCreate(
[](void* arg) {
EspHi* instance = static_cast<EspHi*>(arg);
vTaskDelay(5000 / portTICK_PERIOD_MS);
if (!instance->web_server_initialized_) {
ESP_LOGI(TAG, "WiFi connected, init web control server");
esp_err_t err = esp_hi_web_control_server_init();
if (err != ESP_OK) {
ESP_LOGE(TAG, "Failed to initialize web control server: %d", err);
} else {
ESP_LOGI(TAG, "Web control server initialized");
instance->web_server_initialized_ = true;
}
}
vTaskDelete(NULL);
},
"web_server_init",
1024 * 10, arg, 5, nullptr);
}
}
#endif //CONFIG_ESP_HI_WEB_CONTROL_ENABLED

View File

@@ -6,9 +6,8 @@
"sdkconfig_append": [
"CONFIG_ESPTOOLPY_FLASHSIZE_8MB=y",
"CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\"partitions/v1/8m.csv\"",
"CONFIG_ESP_WIFI_ENTERPRISE_SUPPORT=n",
"CONFIG_LWIP_IPV6=n",
"CONFIG_USE_ESP_WAKE_WORD=y"
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
]
}
]

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,8 @@
"sdkconfig_append": [
"CONFIG_PM_ENABLE=y",
"CONFIG_FREERTOS_USE_TICKLESS_IDLE=y",
"CONFIG_USE_ESP_WAKE_WORD=y"
"CONFIG_USE_ESP_WAKE_WORD=y",
"CONFIG_ESP_CONSOLE_USB_SERIAL_JTAG=y"
]
}
]

View File

@@ -5,7 +5,7 @@ dependencies:
espressif/esp_lcd_gc9a01: ==2.0.1
espressif/esp_lcd_st77916: ^1.0.1
espressif/esp_lcd_st7796:
version: '1.3.2'
version: 1.3.2
rules:
- if: target not in [esp32c3]
espressif/esp_lcd_spd2010: ==1.0.2
@@ -30,9 +30,19 @@ dependencies:
espressif/esp_io_expander_tca95xx_16bit: ^2.0.0
espressif2022/image_player: ^1.1.0
espressif/adc_mic: ^0.2.0
espressif/esp_mmap_assets: ">=1.2"
espressif/esp_mmap_assets: '>=1.2'
txp666/otto-emoji-gif-component: ~1.0.2
# SenseCAP Watcher Board
wvirgil123/esp_jpeg_simd:
version: 1.0.0
rules:
- if: target in [esp32s3]
wvirgil123/sscma_client:
version: 1.0.2
rules:
- if: target in [esp32s3]
tny-robotics/sh1106-esp-idf:
version: ^1.0.0
rules:
@@ -45,7 +55,7 @@ dependencies:
waveshare/esp_lcd_st7703:
version: '*'
rules:
- if: target in [esp32p4]
- if: target in [esp32p4]
espressif/esp_lcd_ili9881c:
version: ^1.0.1
rules:
@@ -55,19 +65,9 @@ dependencies:
rules:
- if: target in [esp32p4]
lijunru-hub/servo_dog_ctrl:
version: '^0.1.6'
version: ^0.1.6
rules:
- if: target in [esp32c3]
sscma_client:
git: https://github.com/Seeed-Studio/SenseCAP-Watcher-Firmware.git
path: components/sscma_client
esp_jpeg_simd:
git: https://github.com/Seeed-Studio/SenseCAP-Watcher-Firmware.git
path: components/esp_jpeg_simd
rules:
- if: target not in [esp32p4]
## Required IDF version
idf:

3
scripts/license_tools/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
config.json
__pycache__/
firmware_cache/

View File

@@ -0,0 +1,193 @@
# ESP32生产固件烧录工具
这是一个基于tkinter的ESP32生产用途的固件烧录工具支持自动检测USB设备、读取MAC地址、获取授权信息、下载并烧录固件。
## 功能特性
- **自动设备检测**: 自动监控USB设备插入支持 `/dev/ttyACMx``/dev/ttyUSBx`
- **设备信息读取**: 使用esptool读取ESP32设备的MAC地址
- **授权管理**: 通过API获取设备授权信息和固件下载链接
- **固件烧录**: 自动下载并烧录固件到ESP32设备
- **序列号烧录**: 将序列号烧录到BLOCK_USR_DATA
- **授权密钥烧录**: 将授权密钥烧录到BLOCK_KEY0
- **GUI界面**: 友好的图形用户界面,实时显示操作日志
- **可配置网格布局**: 支持自定义端口显示布局默认4x4
- **批量烧录**: 支持同时烧录多个设备
- **固件缓存**: 自动缓存下载的固件,避免重复下载
## 配置文件
工具使用 `config.json` 文件保存配置,支持以下配置项:
```json
{
"license_url": "https://xiaozhi.me/api/developers/generate-license?token=YOUR_TOKEN&seed=00:00:00:00:00:00",
"grid_rows": 4,
"grid_cols": 4,
"fullscreen_on_startup": true
}
```
### 网格布局配置
通过修改 `grid_rows``grid_cols` 可以调整端口显示布局:
- **4x4布局** (默认): `{"grid_rows": 4, "grid_cols": 4}` - 16个端口4行4列
- **8x1布局**: `{"grid_rows": 8, "grid_cols": 1}` - 8个端口8行1列
- **2x8布局**: `{"grid_rows": 2, "grid_cols": 8}` - 16个端口2行8列
- **1x16布局**: `{"grid_rows": 1, "grid_cols": 16}` - 16个端口1行16列
配置文件会在程序首次运行时自动创建,也可以手动创建。修改配置后重启程序生效。
### 配置项说明
- **license_url**: 授权API链接`seed`参数会被自动替换为设备MAC地址
- **grid_rows**: 网格行数,控制端口显示的行数
- **grid_cols**: 网格列数,控制端口显示的列数
- **fullscreen_on_startup**: 启动时是否全屏,`true`为全屏,`false`为窗口模式
## 安装依赖
```bash
pip3 install -r requirements.txt
```
或者使用启动脚本自动安装:
```bash
./run_tool.sh
```
## 使用方法
### 1. 启动工具
```bash
# 方式1: 直接运行Python脚本
python3 esp32_production_tool.py
# 方式2: 使用启动脚本(推荐)
./run_tool.sh
```
### 2. 配置授权链接
#### 首次运行配置
首次运行工具时,如果配置文件不存在或授权链接为空,程序会自动进入配置模式,弹出配置对话框:
- **授权链接**: 输入完整的授权API链接
- **网格布局**: 选择端口显示的行列数(支持预设布局)
- **全屏设置**: 选择启动时是否全屏显示
配置完成后会自动保存到 `config.json` 文件。
#### 手动配置
也可以直接编辑 `config.json` 文件:
```json
{
"license_url": "https://xiaozhi.me/api/developers/generate-license?token=YOUR_TOKEN&seed=00:00:00:00:00:00",
"grid_rows": 4,
"grid_cols": 4,
"fullscreen_on_startup": true
}
```
注意:`seed`参数会被自动替换为设备的MAC地址。授权链接只能通过配置文件设置不能在界面上修改。
### 3. 连接ESP32设备
将ESP32设备通过USB连接到计算机。工具会自动检测到新设备并显示在日志中。
### 4. 读取设备信息
点击"读取设备信息"按钮,工具会:
- 读取设备的MAC地址
- 获取芯片信息
- 检查是否已烧录序列号
### 5. 开始烧录
如果设备未烧录过,"开始烧录"按钮会变为可用状态。点击后工具会自动执行:
1. **获取授权信息**: 使用设备MAC地址替换seed参数请求授权API
2. **下载固件**: 从授权响应中获取固件下载链接并下载
3. **烧录固件**: 使用esptool烧录固件到设备
4. **烧录序列号**: 将序列号烧录到BLOCK_USR_DATA
5. **烧录授权密钥**: 将授权密钥烧录到BLOCK_KEY0
## 授权API响应格式
工具期望的授权API响应格式
```json
{
"success": true,
"message": "授权生成成功",
"data": {
"product_name": "小智AI语音盒子",
"board_name": "kevin-box-2",
"serial_number": "TCXZ_KEVINBOX2__38FBDDE984330E50",
"license_key": "ioyI4KWmB41BXu0FgfqioLRncXinfttv",
"license_algorithm": "hmac-sha256",
"created_at": "2025-06-11T18:40:27.000Z",
"firmware": {
"version": "1.8.1",
"image_url": "http://example.com/firmware.bin",
"image_size": 3851712,
"image_sha256": "ccf0c610643ec35819b360b081ed3bf16b53396c82558e8e050fa791947b03de"
}
}
}
```
## 注意事项
1. **权限要求**: 工具需要访问串口设备在Linux系统上可能需要将用户添加到`dialout`组:
```bash
sudo usermod -a -G dialout $USER
```
2. **设备驱动**: 确保已安装ESP32设备的USB驱动程序
3. **网络连接**: 烧录过程需要网络连接来获取授权信息和下载固件
4. **安全性**: 序列号和授权密钥一旦烧录无法更改,请确保数据正确
5. **固件缓存**: 下载的固件会保存在 `firmware_cache/` 目录中,相同文件名的固件会被复用以提高效率
## 故障排除
### 设备检测失败
- 检查USB连接
- 确认设备驱动已安装
- 检查串口权限
### 授权请求失败
- 检查网络连接
- 验证授权链接是否正确
- 确认API服务器可访问
### 烧录失败
- 确保设备未被其他程序占用
- 检查设备是否处于下载模式
- 验证固件文件完整性
## 开发说明
工具基于以下技术栈开发:
- **GUI框架**: tkinter
- **ESP32工具**: esptool, espefuse
- **网络请求**: requests
- **串口检测**: pyserial
主要模块:
- `ESP32ProductionTool`: 主应用类
- `monitor_devices()`: USB设备监控
- `read_device_info()`: 设备信息读取
- `get_license_info()`: 授权信息获取
- `download_firmware()`: 固件下载
- `flash_firmware()`: 固件烧录
- `flash_license_info()`: 授权信息烧录

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,3 @@
esptool>=4.0.0
pyserial>=3.5
requests>=2.25.0

View File

@@ -0,0 +1,15 @@
@echo off
setlocal enabledelayedexpansion
:: <20><>ȡ<EFBFBD>ű<EFBFBD><C5B1><EFBFBD><EFBFBD><EFBFBD>Ŀ¼
set "SCRIPT_DIR=%~dp0"
set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
:: <20><><EFBFBD><EFBFBD>Python<6F><6E><EFBFBD><EFBFBD>
echo <20><><EFBFBD><EFBFBD>Python<6F><6E><EFBFBD><EFBFBD>...
pip install -r "%SCRIPT_DIR%\requirements.txt"
:: <20><><EFBFBD><EFBFBD>GUI<55><49><EFBFBD><EFBFBD>
echo <20><><EFBFBD><EFBFBD>ESP32<33><32><EFBFBD><EFBFBD><EFBFBD>̼<EFBFBD><CCBC><EFBFBD>¼<EFBFBD><C2BC><EFBFBD><EFBFBD>...
python "%SCRIPT_DIR%\esp32_production_tool.py"

View File

@@ -0,0 +1,14 @@
#!/bin/bash
# ESP32生产固件烧录工具启动脚本
# 获取脚本所在目录
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# 检查是否安装了所需依赖
echo "检查Python依赖..."
pip3 install -r "$SCRIPT_DIR/requirements.txt"
# 启动GUI工具
echo "启动ESP32生产固件烧录工具..."
python3 "$SCRIPT_DIR/esp32_production_tool.py"

View File

@@ -82,6 +82,8 @@ def read_binary(dir_path):
data = merged_bin_data[0x100000:]
elif merged_bin_data[0x200000] == 0xE9:
data = merged_bin_data[0x200000:]
elif merged_bin_data[0xe0000] == 0xE9:
data = merged_bin_data[0xe0000:]
else:
print(dir_path, "is not a valid image")
return

7
sdkconfig.defaults.esp32 Normal file
View File

@@ -0,0 +1,7 @@
CONFIG_ESPTOOLPY_FLASHSIZE_4MB=y
CONFIG_PARTITION_TABLE_CUSTOM=y
CONFIG_PARTITION_TABLE_CUSTOM_FILENAME="partitions/v1/4m.csv"
CONFIG_PARTITION_TABLE_FILENAME="partitions/v1/4m.csv"
CONFIG_SR_WN_WN9_NIHAOXIAOZHI_TTS=y
CONFIG_ESP_TASK_WDT_TIMEOUT_S=20

View File

@@ -1,3 +1,13 @@
CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y
CONFIG_SR_WN_WN9S_NIHAOXIAOZHI=y
CONFIG_ESP_WIFI_STATIC_RX_BUFFER_NUM=3
CONFIG_ESP_WIFI_DYNAMIC_RX_BUFFER_NUM=6
CONFIG_ESP_WIFI_RX_BA_WIN=3
CONFIG_MBEDTLS_DYNAMIC_FREE_CONFIG_DATA=y
CONFIG_ESP_WIFI_ENABLE_WPA3_SAE=n
CONFIG_ESP_WIFI_ESPNOW_MAX_ENCRYPT_NUM=0
CONFIG_FREERTOS_IDLE_TASK_STACKSIZE=768
CONFIG_LWIP_IPV6=n
CONFIG_LWIP_TCPIP_RECVMBOX_SIZE=16