From 5d446336876e6822105add0479718acd80dc4830 Mon Sep 17 00:00:00 2001 From: zczc365 <158059821+Zc365@users.noreply.github.com> Date: Fri, 26 Dec 2025 03:01:18 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=20=E5=B0=8F=E6=99=BA=E4=BA=91=E8=81=8A?= =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=A4=E4=B8=AA=E8=AF=AD=E9=9F=B3=E6=8C=87?= =?UTF-8?q?=E4=BB=A4=20(#1596)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main/boards/yunliao-s3/README.md | 73 ++++++------ main/boards/yunliao-s3/yunliao_s3.cc | 164 +++++++++++++++++---------- 2 files changed, 145 insertions(+), 92 deletions(-) diff --git a/main/boards/yunliao-s3/README.md b/main/boards/yunliao-s3/README.md index 011cc549..a4946b44 100644 --- a/main/boards/yunliao-s3/README.md +++ b/main/boards/yunliao-s3/README.md @@ -1,46 +1,56 @@ -# 小智云聊S3 +# 小智云聊 S3 ## 简介 -小智云聊S3是小智AI的魔改项目,是首个2.8寸护眼大屏+大字体+2000mah大电池的量产成品,做了大量创新和优化。 + +小智云聊 S3 是小智 AI 的魔改项目,是首个 2.8 寸护眼大屏+大字体+2000mah 大电池的量产成品,做了大量创新和优化。 ## 合并版 -合并版代码在小智AI主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G自由切换等功能。 ->### 按键操作 ->- **开机**: 关机状态,长按1秒后释放按键,自动开机 ->- **关机**: 开机状态,长按1秒后释放按键,标题栏会显示'请稍候',再等2秒自动关机 ->- **唤醒/打断**: 正常通话环境下,单击按键 ->- **切换4G/Wifi**: 启动过程或者配网界面,1秒钟内双击按键(需安装4G模块) ->- **重新配网**: 开机状态,1秒钟内三击按键,会自动重启并进入配网界面 +合并版代码在小智 AI 主项目中维护,跟随主项目的一起版本更新,便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G 自由切换等功能。 + +> ### 按键操作 +> +> - **开机**: 关机状态,长按 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 idf.py build flash monitor ``` - diff --git a/main/boards/yunliao-s3/yunliao_s3.cc b/main/boards/yunliao-s3/yunliao_s3.cc index 81f07aad..57db4985 100644 --- a/main/boards/yunliao-s3/yunliao_s3.cc +++ b/main/boards/yunliao-s3/yunliao_s3.cc @@ -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 #include - +#include +#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" class YunliaoS3 : public DualNetworkBoard { -private: + private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; SpiLcdDisplay* display_; @@ -49,9 +50,10 @@ private: .glitch_ignore_cnt = 7, .intr_priority = 0, .trans_queue_depth = 0, - .flags = { - .enable_internal_pullup = 1, - }, + .flags = + { + .enable_internal_pullup = 1, + }, }; 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.quadwp_io_num = GPIO_NUM_NC; buscfg.quadhd_io_num = GPIO_NUM_NC; - buscfg.max_transfer_sz = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); - ESP_ERROR_CHECK(spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); + buscfg.max_transfer_sz = + DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t); + ESP_ERROR_CHECK( + spi_bus_initialize(DISPLAY_SPI_LCD_HOST, &buscfg, SPI_DMA_CH_AUTO)); } void InitializeButtons() { @@ -76,23 +80,26 @@ private: boot_button_.OnDoubleClick([this]() { ESP_LOGI(TAG, "Button OnDoubleClick"); auto& app = Application::GetInstance(); - if (app.GetDeviceState() == kDeviceStateStarting || app.GetDeviceState() == kDeviceStateWifiConfiguring) { + if (app.GetDeviceState() == kDeviceStateStarting || + app.GetDeviceState() == kDeviceStateWifiConfiguring) { SwitchNetworkType(); } - }); - boot_button_.OnMultipleClick([this]() { - ESP_LOGI(TAG, "Button OnThreeClick"); - if (GetNetworkType() == NetworkType::WIFI) { - auto& wifi_board = static_cast(GetCurrentBoard()); - wifi_board.EnterWifiConfigMode(); - } - },3); + }); + boot_button_.OnMultipleClick( + [this]() { + ESP_LOGI(TAG, "Button OnThreeClick"); + if (GetNetworkType() == NetworkType::WIFI) { + auto& wifi_board = + static_cast(GetCurrentBoard()); + wifi_board.EnterWifiConfigMode(); + } + }, 3); boot_button_.OnLongPress([this]() { ESP_LOGI(TAG, "Button LongPress to Sleep"); display_->SetStatus(Lang::Strings::PLEASE_WAIT); vTaskDelay(pdMS_TO_TICKS(2000)); power_manager_->Sleep(); - }); + }); } void InitializeSt7789Display() { esp_lcd_panel_io_handle_t panel_io = nullptr; @@ -107,19 +114,23 @@ private: io_config.trans_queue_depth = 10; io_config.lcd_cmd_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 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 = {}; panel_config.reset_gpio_num = DISPLAY_SPI_PIN_LCD_RST; panel_config.rgb_ele_order = DISPLAY_RGB_ORDER_COLOR; 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_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_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_WIDTH, @@ -132,12 +143,50 @@ private: 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(); + SetAecMode(enable); + return true; + }); -public: - YunliaoS3() : - DualNetworkBoard(ML307_TX_PIN, ML307_RX_PIN, GPIO_NUM_NC, 0), - boot_button_(BOOT_BUTTON_PIN), - power_manager_(new PowerManager()){ + mcp_server.AddTool("self.system.switch_TFT", + "Switch TFT display mode between normal and inverted colors. This will toggle the IPS mode and reboot the device.", + PropertyList(), [this](const PropertyList& properties) { + SwitchTFT(); + 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_->Initialize(); InitializeI2c(); @@ -146,7 +195,7 @@ public: InitializeSpi(); InitializeSt7789Display(); power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) { - if(power_save_timer_){ + if (power_save_timer_) { if (is_discharging) { power_save_timer_->SetEnabled(true); } else { @@ -154,46 +203,41 @@ public: } } }); - if(GetNetworkType() == NetworkType::WIFI){ + if (GetNetworkType() == NetworkType::WIFI) { power_manager_->Shutdown4G(); - }else{ + } else { power_manager_->Start4G(); } GetBacklight()->RestoreBrightness(); - while(gpio_get_level(BOOT_BUTTON_PIN) == 0){ + while (gpio_get_level(BOOT_BUTTON_PIN) == 0) { vTaskDelay(pdMS_TO_TICKS(10)); } InitializeButtons(); + Settings settings("aec", false); + auto& app = Application::GetInstance(); + app.SetAecMode(settings.GetInt("mode",kAecOnDeviceSide) == kAecOnDeviceSide ? kAecOnDeviceSide : kAecOff); + InitializeTools(); } virtual AudioCodec* GetAudioCodec() override { static Es8388AudioCodec audio_codec( - codec_i2c_bus_, - I2C_NUM_0, - AUDIO_INPUT_SAMPLE_RATE, - AUDIO_OUTPUT_SAMPLE_RATE, - 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 - ); + codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, + AUDIO_OUTPUT_SAMPLE_RATE, 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; } - virtual Display* GetDisplay() override { - return display_; - } - + virtual Display* GetDisplay() override { return display_; } + 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; } - virtual bool GetBatteryLevel(int& level, bool& charging, bool& discharging) override { + virtual bool GetBatteryLevel(int& level, bool& charging, + bool& discharging) override { level = power_manager_->GetBatteryLevel(); charging = power_manager_->IsCharging(); discharging = power_manager_->IsDischarging();