forked from xiaozhi/xiaozhi-esp32
feat: 小智云聊增加两个语音指令 (#1596)
This commit is contained in:
@@ -1,46 +1,56 @@
|
|||||||
# 小智云聊S3
|
# 小智云聊 S3
|
||||||
|
|
||||||
## 简介
|
## 简介
|
||||||
小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。
|
|
||||||
|
小智云聊 S3 是小智 AI 的魔改项目,是首个 2.8 寸护眼大屏+大字体+2000mah 大电池的量产成品,做了大量创新和优化。
|
||||||
|
|
||||||
## 合并版
|
## 合并版
|
||||||
合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。
|
|
||||||
|
|
||||||
>### 按键操作
|
合并版代码在小智 AI 主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G 自由切换等功能。
|
||||||
>- **开机**: 关机状态,长按1秒后释放按键,自动开机
|
|
||||||
>- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机
|
> ### 按键操作
|
||||||
>- **唤醒/打断**: 正常通话环境下,单击按键
|
>
|
||||||
>- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块)
|
> - **开机**: 关机状态,长按 1 秒后释放按键,自动开机。
|
||||||
>- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面
|
> - **关机**: 开机状态,长按 1 秒后释放按键,标题栏会显示'请稍候',再等 2 秒自动关机。
|
||||||
|
> - **唤醒/打断**: 正常通话环境下,单击按键。
|
||||||
|
> - **切换 4G/Wifi**: 启动过程或者配网界面,1 秒钟内双击按键(需安装 4G 模块)。
|
||||||
|
> - **重新配网**: 开机状态,1 秒钟内三击按键,会自动重启并进入配网界面。
|
||||||
|
|
||||||
|
> ### 语音指令
|
||||||
|
>
|
||||||
|
> - **打开/关闭语音打断模式**: 在播放音乐时,需要关闭语音打断模式,否则可能会打断音乐播放。
|
||||||
|
> - **切换 IPS 屏幕显示模式**: 新版小智云聊 S3 升级了 IPS 屏幕,需要切换屏幕显示模式后才能正常显示,可以来回切换。
|
||||||
|
|
||||||
## 魔改版
|
## 魔改版
|
||||||
|
|
||||||
魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。
|
魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。
|
||||||
|
|
||||||
>### 为什么是魔改
|
> ### 为什么是魔改
|
||||||
>- 首个实现微信二维码配网。
|
>
|
||||||
>- 首个支持单手机配网。
|
> - 首个实现微信二维码配网。
|
||||||
>- 首个支持扫二维码访问控制台。
|
> - 首个支持单手机配网。
|
||||||
>- 首发支持繁体、日文、英文版界面
|
> - 首个支持扫二维码访问控制台。
|
||||||
>- 首个全语音操控模式
|
> - 首发支持繁体、日文、英文版界面。
|
||||||
>- 独家提供一键刷机脚本等多种刷机方式
|
> - 首个全语音操控模式。
|
||||||
|
> - 独家提供一键刷机脚本等多种刷机方式。
|
||||||
|
|
||||||
## 版本区别
|
## 版本区别
|
||||||
>| 特性 | 合并版 | 魔改版 |
|
|
||||||
>| --- | --- | --- |
|
|
||||||
>| 语音打断 | ✓ | ✓ |
|
|
||||||
>| 4G功能 | ✓ | ✓ |
|
|
||||||
>| 自动更新固件 | ✓ | X |
|
|
||||||
>| 第三方固件支持 | ✓ | X |
|
|
||||||
>| 天气待机界面 | X | ✓ |
|
|
||||||
>| 闹钟提醒 | X | ✓ |
|
|
||||||
>| 网络音乐播放 | X | ✓ |
|
|
||||||
>| 微信扫码配网 | X | ✓ |
|
|
||||||
>| 单手机配网 | X | ✓ |
|
|
||||||
>| 扫码访问控制台 | X | ✓ |
|
|
||||||
>| 繁日英文界面 | X | ✓ |
|
|
||||||
>| 多语言支持 | X | ✓ |
|
|
||||||
>| 外接蓝牙音箱 | X | ✓ |
|
|
||||||
|
|
||||||
|
> | 特性 | 合并版 | 魔改版 |
|
||||||
|
> | -------------- | ------ | ------ |
|
||||||
|
> | 语音打断 | ✓ | ✓ |
|
||||||
|
> | 4G 功能 | ✓ | ✓ |
|
||||||
|
> | 自动更新固件 | ✓ | X |
|
||||||
|
> | 第三方固件支持 | ✓ | X |
|
||||||
|
> | 天气待机界面 | X | ✓ |
|
||||||
|
> | 闹钟提醒 | X | ✓ |
|
||||||
|
> | 网络音乐播放 | X | ✓ |
|
||||||
|
> | 微信扫码配网 | X | ✓ |
|
||||||
|
> | 单手机配网 | X | ✓ |
|
||||||
|
> | 扫码访问控制台 | X | ✓ |
|
||||||
|
> | 繁日英文界面 | X | ✓ |
|
||||||
|
> | 多语言支持 | X | ✓ |
|
||||||
|
> | 外接蓝牙音箱 | X | ✓ |
|
||||||
|
|
||||||
# 编译配置命令
|
# 编译配置命令
|
||||||
|
|
||||||
@@ -85,4 +95,3 @@ idf.py build
|
|||||||
```bash
|
```bash
|
||||||
idf.py build flash monitor
|
idf.py build flash monitor
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,22 @@
|
|||||||
#include "lvgl_theme.h"
|
|
||||||
#include "dual_network_board.h"
|
|
||||||
#include "codecs/es8388_audio_codec.h"
|
|
||||||
#include "display/lcd_display.h"
|
|
||||||
#include "application.h"
|
|
||||||
#include "button.h"
|
|
||||||
#include "config.h"
|
|
||||||
#include "power_save_timer.h"
|
|
||||||
#include "power_manager.h"
|
|
||||||
#include "assets/lang_config.h"
|
|
||||||
#include <esp_log.h>
|
|
||||||
#include <esp_lcd_panel_vendor.h>
|
#include <esp_lcd_panel_vendor.h>
|
||||||
|
#include <esp_log.h>
|
||||||
|
#include "settings.h"
|
||||||
|
#include "application.h"
|
||||||
|
#include "assets/lang_config.h"
|
||||||
|
#include "button.h"
|
||||||
|
#include "codecs/es8388_audio_codec.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "display/lcd_display.h"
|
||||||
|
#include "dual_network_board.h"
|
||||||
|
#include "lvgl_theme.h"
|
||||||
|
#include "power_manager.h"
|
||||||
|
#include "power_save_timer.h"
|
||||||
|
#include "mcp_server.h"
|
||||||
|
|
||||||
#define TAG "YunliaoS3"
|
#define TAG "YunliaoS3"
|
||||||
|
|
||||||
class YunliaoS3 : public DualNetworkBoard {
|
class YunliaoS3 : public DualNetworkBoard {
|
||||||
private:
|
private:
|
||||||
i2c_master_bus_handle_t codec_i2c_bus_;
|
i2c_master_bus_handle_t codec_i2c_bus_;
|
||||||
Button boot_button_;
|
Button boot_button_;
|
||||||
SpiLcdDisplay* display_;
|
SpiLcdDisplay* display_;
|
||||||
@@ -49,9 +50,10 @@ private:
|
|||||||
.glitch_ignore_cnt = 7,
|
.glitch_ignore_cnt = 7,
|
||||||
.intr_priority = 0,
|
.intr_priority = 0,
|
||||||
.trans_queue_depth = 0,
|
.trans_queue_depth = 0,
|
||||||
.flags = {
|
.flags =
|
||||||
.enable_internal_pullup = 1,
|
{
|
||||||
},
|
.enable_internal_pullup = 1,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
|
||||||
}
|
}
|
||||||
@@ -63,8 +65,10 @@ private:
|
|||||||
buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK;
|
buscfg.sclk_io_num = DISPLAY_SPI_PIN_SCLK;
|
||||||
buscfg.quadwp_io_num = GPIO_NUM_NC;
|
buscfg.quadwp_io_num = GPIO_NUM_NC;
|
||||||
buscfg.quadhd_io_num = GPIO_NUM_NC;
|
buscfg.quadhd_io_num = GPIO_NUM_NC;
|
||||||
buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
|
buscfg.max_transfer_sz =
|
||||||
ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t);
|
||||||
|
ESP_ERROR_CHECK(
|
||||||
|
spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO));
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitializeButtons() {
|
void InitializeButtons() {
|
||||||
@@ -76,23 +80,26 @@ private:
|
|||||||
boot_button_.OnDoubleClick([this]() {
|
boot_button_.OnDoubleClick([this]() {
|
||||||
ESP_LOGI(TAG, "Button OnDoubleClick");
|
ESP_LOGI(TAG, "Button OnDoubleClick");
|
||||||
auto& app = Application::GetInstance();
|
auto& app = Application::GetInstance();
|
||||||
if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
if (app.GetDeviceState() == kDeviceStateStarting ||
|
||||||
|
app.GetDeviceState() == kDeviceStateWifiConfiguring) {
|
||||||
SwitchNetworkType();
|
SwitchNetworkType();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
boot_button_.OnMultipleClick([this]() {
|
boot_button_.OnMultipleClick(
|
||||||
ESP_LOGI(TAG, "Button OnThreeClick");
|
[this]() {
|
||||||
if (GetNetworkType() == NetworkType::WIFI) {
|
ESP_LOGI(TAG, "Button OnThreeClick");
|
||||||
auto& wifi_board = static_cast<WifiBoard&>(GetCurrentBoard());
|
if (GetNetworkType() == NetworkType::WIFI) {
|
||||||
wifi_board.EnterWifiConfigMode();
|
auto& wifi_board =
|
||||||
}
|
static_cast<WifiBoard&>(GetCurrentBoard());
|
||||||
},3);
|
wifi_board.EnterWifiConfigMode();
|
||||||
|
}
|
||||||
|
}, 3);
|
||||||
boot_button_.OnLongPress([this]() {
|
boot_button_.OnLongPress([this]() {
|
||||||
ESP_LOGI(TAG, "Button LongPress to Sleep");
|
ESP_LOGI(TAG, "Button LongPress to Sleep");
|
||||||
display_->SetStatus(Lang::Strings::PLEASE_WAIT);
|
display_->SetStatus(Lang::Strings::PLEASE_WAIT);
|
||||||
vTaskDelay(pdMS_TO_TICKS(2000));
|
vTaskDelay(pdMS_TO_TICKS(2000));
|
||||||
power_manager_->Sleep();
|
power_manager_->Sleep();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
void InitializeSt7789Display() {
|
void InitializeSt7789Display() {
|
||||||
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
esp_lcd_panel_io_handle_t panel_io = nullptr;
|
||||||
@@ -107,19 +114,23 @@ private:
|
|||||||
io_config.trans_queue_depth = 10;
|
io_config.trans_queue_depth = 10;
|
||||||
io_config.lcd_cmd_bits = 8;
|
io_config.lcd_cmd_bits = 8;
|
||||||
io_config.lcd_param_bits = 8;
|
io_config.lcd_param_bits = 8;
|
||||||
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST, &io_config, &panel_io));
|
ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(DISPLAY_SPI_LCD_HOST,
|
||||||
|
&io_config, &panel_io));
|
||||||
|
|
||||||
// 初始化液晶屏驱动芯片ST7789
|
// 初始化液晶屏驱动芯片ST7789
|
||||||
ESP_LOGD(TAG, "Install LCD driver");
|
ESP_LOGD(TAG, "Install LCD driver");
|
||||||
|
Settings settings("display", false);
|
||||||
|
bool currentIpsMode = settings.GetBool("ips_mode", DISPLAY_INVERT_COLOR);
|
||||||
esp_lcd_panel_dev_config_t panel_config = {};
|
esp_lcd_panel_dev_config_t panel_config = {};
|
||||||
panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST;
|
panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST;
|
||||||
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR;
|
panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR;
|
||||||
panel_config.bits_per_pixel = 16;
|
panel_config.bits_per_pixel = 16;
|
||||||
ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
ESP_ERROR_CHECK(
|
||||||
|
esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel));
|
||||||
|
|
||||||
esp_lcd_panel_reset(panel);
|
esp_lcd_panel_reset(panel);
|
||||||
esp_lcd_panel_init(panel);
|
esp_lcd_panel_init(panel);
|
||||||
esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR);
|
esp_lcd_panel_invert_color(panel, currentIpsMode);
|
||||||
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY);
|
||||||
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y);
|
||||||
display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH,
|
display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH,
|
||||||
@@ -132,12 +143,50 @@ private:
|
|||||||
display_->SetTheme(theme);
|
display_->SetTheme(theme);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
void InitializeTools(){
|
||||||
|
auto& mcp_server = McpServer::GetInstance();
|
||||||
|
|
||||||
|
mcp_server.AddTool("self.system.set_aec",
|
||||||
|
"Enable or disable voice interruption mode (AEC:Acoustic Echo Cancellation). When enabled, the device can detect voice interruptions and respond accordingly.",
|
||||||
|
PropertyList({
|
||||||
|
Property("enable", kPropertyTypeBoolean)
|
||||||
|
}), [this](const PropertyList& properties) {
|
||||||
|
bool enable = properties["enable"].value<bool>();
|
||||||
|
SetAecMode(enable);
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
|
||||||
public:
|
mcp_server.AddTool("self.system.switch_TFT",
|
||||||
YunliaoS3() :
|
"Switch TFT display mode between normal and inverted colors. This will toggle the IPS mode and reboot the device.",
|
||||||
DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
|
PropertyList(), [this](const PropertyList& properties) {
|
||||||
boot_button_(BOOT_BUTTON_PIN),
|
SwitchTFT();
|
||||||
power_manager_(new PowerManager()){
|
return true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
void SetAecMode(bool enable) {
|
||||||
|
AecMode newMode = enable ? kAecOnDeviceSide : kAecOff;
|
||||||
|
auto& app = Application::GetInstance();
|
||||||
|
app.StopListening();
|
||||||
|
app.SetDeviceState(kDeviceStateIdle);
|
||||||
|
app.SetAecMode(newMode);
|
||||||
|
Settings settings("aec", true);
|
||||||
|
settings.SetInt("mode", newMode);
|
||||||
|
}
|
||||||
|
void SwitchTFT() {
|
||||||
|
Settings settings("display", true);
|
||||||
|
bool currentIpsMode = settings.GetBool("ips_mode", false);
|
||||||
|
settings.SetBool("ips_mode", !currentIpsMode);
|
||||||
|
ESP_LOGI(TAG, "IPS mode toggled to %s", !currentIpsMode ? "enabled" : "disabled");
|
||||||
|
vTaskDelay(pdMS_TO_TICKS(1000));
|
||||||
|
auto& app = Application::GetInstance();
|
||||||
|
app.Reboot();
|
||||||
|
}
|
||||||
|
|
||||||
|
public:
|
||||||
|
YunliaoS3()
|
||||||
|
: DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0),
|
||||||
|
boot_button_(BOOT_BUTTON_PIN),
|
||||||
|
power_manager_(new PowerManager()) {
|
||||||
power_manager_->Start5V();
|
power_manager_->Start5V();
|
||||||
power_manager_->Initialize();
|
power_manager_->Initialize();
|
||||||
InitializeI2c();
|
InitializeI2c();
|
||||||
@@ -146,7 +195,7 @@ public:
|
|||||||
InitializeSpi();
|
InitializeSpi();
|
||||||
InitializeSt7789Display();
|
InitializeSt7789Display();
|
||||||
power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
|
power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
|
||||||
if(power_save_timer_){
|
if (power_save_timer_) {
|
||||||
if (is_discharging) {
|
if (is_discharging) {
|
||||||
power_save_timer_->SetEnabled(true);
|
power_save_timer_->SetEnabled(true);
|
||||||
} else {
|
} else {
|
||||||
@@ -154,46 +203,41 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
if(GetNetworkType() == NetworkType::WIFI){
|
if (GetNetworkType() == NetworkType::WIFI) {
|
||||||
power_manager_->Shutdown4G();
|
power_manager_->Shutdown4G();
|
||||||
}else{
|
} else {
|
||||||
power_manager_->Start4G();
|
power_manager_->Start4G();
|
||||||
}
|
}
|
||||||
GetBacklight()->RestoreBrightness();
|
GetBacklight()->RestoreBrightness();
|
||||||
while(gpio_get_level(BOOT_BUTTON_PIN) == 0){
|
while (gpio_get_level(BOOT_BUTTON_PIN) == 0) {
|
||||||
vTaskDelay(pdMS_TO_TICKS(10));
|
vTaskDelay(pdMS_TO_TICKS(10));
|
||||||
}
|
}
|
||||||
InitializeButtons();
|
InitializeButtons();
|
||||||
|
Settings settings("aec", false);
|
||||||
|
auto& app = Application::GetInstance();
|
||||||
|
app.SetAecMode(settings.GetInt("mode",kAecOnDeviceSide) == kAecOnDeviceSide ? kAecOnDeviceSide : kAecOff);
|
||||||
|
InitializeTools();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual AudioCodec* GetAudioCodec() override {
|
virtual AudioCodec* GetAudioCodec() override {
|
||||||
static Es8388AudioCodec audio_codec(
|
static Es8388AudioCodec audio_codec(
|
||||||
codec_i2c_bus_,
|
codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE,
|
||||||
I2C_NUM_0,
|
AUDIO_OUTPUT_SAMPLE_RATE, AUDIO_I2S_GPIO_MCLK, AUDIO_I2S_GPIO_BCLK,
|
||||||
AUDIO_INPUT_SAMPLE_RATE,
|
AUDIO_I2S_GPIO_WS, AUDIO_I2S_GPIO_DOUT, AUDIO_I2S_GPIO_DIN,
|
||||||
AUDIO_OUTPUT_SAMPLE_RATE,
|
AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8388_ADDR, AUDIO_INPUT_REFERENCE);
|
||||||
AUDIO_I2S_GPIO_MCLK,
|
|
||||||
AUDIO_I2S_GPIO_BCLK,
|
|
||||||
AUDIO_I2S_GPIO_WS,
|
|
||||||
AUDIO_I2S_GPIO_DOUT,
|
|
||||||
AUDIO_I2S_GPIO_DIN,
|
|
||||||
AUDIO_CODEC_PA_PIN,
|
|
||||||
AUDIO_CODEC_ES8388_ADDR,
|
|
||||||
AUDIO_INPUT_REFERENCE
|
|
||||||
);
|
|
||||||
return &audio_codec;
|
return &audio_codec;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Display* GetDisplay() override {
|
virtual Display* GetDisplay() override { return display_; }
|
||||||
return display_;
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Backlight* GetBacklight() override {
|
virtual Backlight* GetBacklight() override {
|
||||||
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN,
|
||||||
|
DISPLAY_BACKLIGHT_OUTPUT_INVERT);
|
||||||
return &backlight;
|
return &backlight;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override {
|
virtual bool GetBatteryLevel(int& level, bool& charging,
|
||||||
|
bool& discharging) override {
|
||||||
level = power_manager_->GetBatteryLevel();
|
level = power_manager_->GetBatteryLevel();
|
||||||
charging = power_manager_->IsCharging();
|
charging = power_manager_->IsCharging();
|
||||||
discharging = power_manager_->IsDischarging();
|
discharging = power_manager_->IsDischarging();
|
||||||
|
|||||||
Reference in New Issue
Block a user