Initial commit

This commit is contained in:
2026-04-26 21:35:04 +08:00
commit da6ca1b09a
1483 changed files with 115719 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
# 小智云聊 S3
## 简介
小智云聊 S3 是小智 AI 的魔改项目,是首个 2.8 寸护眼大屏+大字体+2000mah 大电池的量产成品,做了大量创新和优化。
## 官方版
官方版代码在小智 AI 主项目中维护跟随主项目的一起版本更新便于用户自行扩展和第三方固件扩展。支持语音唤醒、语音打断、OTA、4G 自由切换等功能。
> ### 按键操作
>
> - **开机**: 关机状态,长按 1 秒后释放按键,自动开机。
> - **关机**: 开机状态,长按 1 秒后释放按键,标题栏会显示'请稍候',再等 2 秒自动关机。
> - **唤醒/打断**: 正常通话环境下,单击按键。
> - **切换 4G/Wifi**: 启动过程或者配网界面1 秒钟内双击按键(需安装 4G 模块)。
> - **切换语音(AEC)打断模式**: 正常启动后在空闲无对话模式下1 秒钟内双击按键,循环切换语音打断模式。
> - **重新配网**: 开机状态1 秒钟内三击按键,会自动重启并进入配网界面。
> ### 语音指令
>
> - **打开/关闭语音(AEC)打断模式**: 在播放音乐时,需要关闭语音打断模式,否则可能会打断音乐播放。
> - **切换 IPS 屏幕显示模式**: 新版小智云聊 S3 升级了 IPS 屏幕,需要切换屏幕显示模式后才能正常显示,可以来回切换。
## 魔改版
魔改版由于底层改动太大,代码单独维护,定期合并主项目代码。
> ### 为什么是魔改
>
> - 首个实现微信二维码配网。
> - 首个支持单手机配网。
> - 首个支持扫二维码访问控制台。
> - 首发支持繁体、日文、英文版界面。
> - 首个全语音操控模式。
> - 独家提供一键刷机脚本等多种刷机方式。
## 版本区别
> | 特性 | 官方版 | 魔改版 |
> | -------------- | ------ | ------ |
> | 语音打断 | ✓ | ✓ |
> | 4G 功能 | ✓ | ✓ |
> | 自动更新固件 | ✓ | X |
> | 第三方固件支持 | ✓ | X |
> | 天气待机界面 | X | ✓ |
> | 闹钟提醒 | X | ✓ |
> | 网络音乐播放 | X | ✓ |
> | 微信扫码配网 | X | ✓ |
> | 单手机配网 | X | ✓ |
> | 扫码访问控制台 | X | ✓ |
> | 繁日英文界面 | X | ✓ |
> | 多语言支持 | 需自行编译 | ✓ |
> | 外接蓝牙音箱/耳机 | ✓ | ✓ |
# 编译配置命令
**克隆工程**
```bash
git clone https://github.com/78/xiaozhi-esp32.git
```
**进入工程**
```bash
cd xiaozhi-esp32
```
**配置编译目标为 ESP32S3**
```bash
idf.py set-target esp32s3
```
**打开 menuconfig**
```bash
idf.py menuconfig
```
**选择板子**
```bash
- `Xiaozhi Assistant``Board Type` → 选择 `小智云聊-S3` → 选择 `Enable Device-Side AEC`
```
**编译**
```ba
idf.py build
```
**下载并打开串口终端**
```bash
idf.py build flash monitor
```

View File

@@ -0,0 +1,61 @@
#ifndef _BOARD_CONFIG_H_
#define _BOARD_CONFIG_H_
#include <driver/gpio.h>
#define AUDIO_INPUT_REFERENCE true
#define AUDIO_INPUT_SAMPLE_RATE 24000
#define AUDIO_OUTPUT_SAMPLE_RATE 24000
#define AUDIO_DEFAULT_OUTPUT_VOLUME 70
#define AUDIO_I2S_GPIO_MCLK GPIO_NUM_14
#define AUDIO_I2S_GPIO_BCLK GPIO_NUM_13
#define AUDIO_I2S_GPIO_WS GPIO_NUM_11
#define AUDIO_I2S_GPIO_DOUT GPIO_NUM_12
#define AUDIO_I2S_GPIO_DIN GPIO_NUM_10
#define AUDIO_CODEC_PA_PIN GPIO_NUM_17
#define AUDIO_CODEC_I2C_SCL_PIN GPIO_NUM_21
#define AUDIO_CODEC_I2C_SDA_PIN GPIO_NUM_18
#define AUDIO_CODEC_ES8388_ADDR ES8388_CODEC_DEFAULT_ADDR
#define BOOT_BUTTON_PIN GPIO_NUM_2
#define BOOT_5V_PIN GPIO_NUM_3 //5V升压输出
#define MON_BTLINK_PIN GPIO_NUM_4 //检测BT连接状态
#define BOOT_4G5V_PIN GPIO_NUM_5 //4G模块供电
#define BOOT_4GEN_PIN GPIO_NUM_6 //4G模块使能
#define MON_BATT_PIN GPIO_NUM_43 //检测PMU电池指示
#define MON_BATT_CNT 70 //检测PMU电池秒数
#define MON_USB_PIN GPIO_NUM_47 //检测USB插入
#define ML307_RX_PIN GPIO_NUM_16
#define ML307_TX_PIN GPIO_NUM_15
#define DISPLAY_SPI_LCD_HOST SPI2_HOST
#define DISPLAY_SPI_CLOCK_HZ (40 * 1000 * 1000)
#define DISPLAY_SPI_PIN_SCLK 42
#define DISPLAY_SPI_PIN_MOSI 40
#define DISPLAY_SPI_PIN_MISO -1
#define DISPLAY_SPI_PIN_LCD_DC 41
#define DISPLAY_SPI_PIN_LCD_RST 45
#define DISPLAY_SPI_PIN_LCD_CS -1
#define DISPLAY_PIN_TOUCH_CS -1
#define DISPLAY_BACKLIGHT_PIN GPIO_NUM_46
#define DISPLAY_BACKLIGHT_OUTPUT_INVERT false
#define DISPLAY_WIDTH 320
#define DISPLAY_HEIGHT 240
#define DISPLAY_SWAP_XY true
#define DISPLAY_MIRROR_X false
#define DISPLAY_MIRROR_Y true
#define DISPLAY_INVERT_COLOR true
#define DISPLAY_RGB_ORDER_COLOR LCD_RGB_ELEMENT_ORDER_RGB
#define DISPLAY_OFFSET_X 0
#define DISPLAY_OFFSET_Y 0
#define KEY_EXPIRE_MS 800
#endif // _BOARD_CONFIG_H_

View File

@@ -0,0 +1,11 @@
{
"target": "esp32s3",
"builds": [
{
"name": "yunliao-s3",
"sdkconfig_append": [
"CONFIG_USE_DEVICE_AEC=y"
]
}
]
}

View File

@@ -0,0 +1,278 @@
#include "power_manager.h"
#include "esp_sleep.h"
#include "driver/rtc_io.h"
#include "esp_log.h"
#include "config.h"
#include <esp_sleep.h>
#include "esp_log.h"
#include "settings.h"
#define TAG "PowerManager"
static QueueHandle_t gpio_evt_queue = NULL;
uint16_t battCnt;//闪灯次数
int battLife = 70; //电量
// 中断服务程序
static void IRAM_ATTR batt_mon_isr_handler(void* arg) {
uint32_t gpio_num = (uint32_t) arg;
xQueueSendFromISR(gpio_evt_queue, &gpio_num, NULL);
}
// 添加任务处理函数
static void batt_mon_task(void* arg) {
uint32_t io_num;
while(1) {
if(xQueueReceive(gpio_evt_queue, &io_num, portMAX_DELAY)) {
battCnt++;
}
}
}
static void calBattLife() {
// 计算电量
battLife = battCnt;
if (battLife > 100){
battLife = 100;
}
// ESP_LOGI(TAG, "Battery life:%d", (int)battLife);
// 重置计数器
battCnt = 0;
}
PowerManager::PowerManager(){
m_bt_task_handle = nullptr;
bt_link_callback_ = nullptr;
}
void PowerManager::Initialize(){
// 初始化5V控制引脚
gpio_config_t io_conf_5v = {
.pin_bit_mask = 1<<BOOT_5V_PIN,
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf_5v));
// 初始化4G控制引脚
gpio_config_t io_conf_4g = {
.pin_bit_mask = (1<<BOOT_4G5V_PIN) | (1<<BOOT_4GEN_PIN),
.mode = GPIO_MODE_OUTPUT,
.pull_up_en = GPIO_PULLUP_DISABLE,
.pull_down_en = GPIO_PULLDOWN_ENABLE,
.intr_type = GPIO_INTR_DISABLE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf_4g));
// 电池电量监测引脚配置
gpio_config_t io_conf_batt_mon = {
.pin_bit_mask = 1ull<<MON_BATT_PIN,
.mode = GPIO_MODE_INPUT,
.pull_up_en = GPIO_PULLUP_ENABLE,
.pull_down_en = GPIO_PULLDOWN_DISABLE,
.intr_type = GPIO_INTR_POSEDGE,
};
ESP_ERROR_CHECK(gpio_config(&io_conf_batt_mon));
// 创建电量GPIO事件队列
gpio_evt_queue = xQueueCreate(2, sizeof(uint32_t));
// 安装电量GPIO ISR服务
ESP_ERROR_CHECK(gpio_install_isr_service(0));
// 添加中断处理
ESP_ERROR_CHECK(gpio_isr_handler_add(MON_BATT_PIN, batt_mon_isr_handler, (void*)MON_BATT_PIN));
// 创建监控任务
xTaskCreate(&batt_mon_task, "batt_mon_task", 1024, NULL, 10, NULL);
// 初始化监测引脚
gpio_config_t mon_conf = {};
mon_conf.pin_bit_mask = 1ULL << MON_USB_PIN;
mon_conf.mode = GPIO_MODE_INPUT;
mon_conf.pull_up_en = GPIO_PULLUP_DISABLE;
mon_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
gpio_config(&mon_conf);
// 创建电池电量检查定时器
esp_timer_create_args_t timer_args = {
.callback = [](void* arg) {
PowerManager* self = static_cast<PowerManager*>(arg);
self->CheckBatteryStatus();
},
.arg = this,
.dispatch_method = ESP_TIMER_TASK,
.name = "battery_check_timer",
.skip_unhandled_events = true,
};
ESP_ERROR_CHECK(esp_timer_create(&timer_args, &timer_handle_));
ESP_ERROR_CHECK(esp_timer_start_periodic(timer_handle_, 1000000));
}
void PowerManager::CheckBatteryStatus(){
call_count_++;
if(call_count_ >= MON_BATT_CNT) {
calBattLife();
call_count_ = 0;
}
bool new_charging_status = IsCharging();
if (new_charging_status != is_charging_) {
is_charging_ = new_charging_status;
if (charging_callback_) {
charging_callback_(is_charging_);
}
}
bool new_discharging_status = IsDischarging();
if (new_discharging_status != is_discharging_) {
is_discharging_ = new_discharging_status;
if (discharging_callback_) {
discharging_callback_(is_discharging_);
}
}
}
bool PowerManager::IsCharging() {
return gpio_get_level(MON_USB_PIN) == 1 && !IsChargingDone();
}
bool PowerManager::IsDischarging() {
return gpio_get_level(MON_USB_PIN) == 0;
}
bool PowerManager::IsChargingDone() {
return battLife >= 95;
}
int PowerManager::GetBatteryLevel() {
return battLife;
}
void PowerManager::OnChargingStatusChanged(std::function<void(bool)> callback) {
charging_callback_ = callback;
}
void PowerManager::OnChargingStatusDisChanged(std::function<void(bool)> callback) {
discharging_callback_ = callback;
}
void PowerManager::CheckStartup() {
Settings settings1("board", true);
if(settings1.GetInt("sleep_flag", 0) > 0){
vTaskDelay(pdMS_TO_TICKS(1000));
if( gpio_get_level(BOOT_BUTTON_PIN) == 1) {
Sleep(); //进入休眠模式
}else{
settings1.SetInt("sleep_flag", 0);
}
}
}
void PowerManager::Start5V() {
gpio_set_level(BOOT_5V_PIN, 1);
}
void PowerManager::Shutdown5V() {
gpio_set_level(BOOT_5V_PIN, 0);
}
void PowerManager::Start4G() {
gpio_set_level(BOOT_4G5V_PIN, 1);
}
void PowerManager::Shutdown4G() {
gpio_set_level(BOOT_4G5V_PIN, 0);
gpio_set_level(ML307_RX_PIN,1);
gpio_set_level(ML307_TX_PIN,1);
}
void PowerManager::Enable4G() {
gpio_set_level(BOOT_4GEN_PIN, 1);
}
void PowerManager::Disable4G() {
gpio_set_level(BOOT_4GEN_PIN, 0);
}
void PowerManager::Sleep() {
ESP_LOGI(TAG, "Entering deep sleep");
Settings settings("board", true);
settings.SetInt("sleep_flag", 1);
Disable4G();
Shutdown4G();
Shutdown5V();
if(gpio_evt_queue) {
vQueueDelete(gpio_evt_queue);
gpio_evt_queue = NULL;
}
ESP_ERROR_CHECK(gpio_isr_handler_remove(BOOT_BUTTON_PIN));
ESP_ERROR_CHECK(esp_sleep_enable_ext0_wakeup(BOOT_BUTTON_PIN, 0));
ESP_ERROR_CHECK(rtc_gpio_pulldown_dis(BOOT_BUTTON_PIN));
ESP_ERROR_CHECK(rtc_gpio_pullup_en(BOOT_BUTTON_PIN));
esp_deep_sleep_start();
}
void PowerManager::InitializeBtModul() {
if (MON_BTLINK_PIN == GPIO_NUM_NC) {
ESP_LOGW(TAG, "MON_BTLINK_PIN not configured, skipping GPIO polling setup");
return;
}
gpio_config_t io_conf = {};
io_conf.intr_type = GPIO_INTR_DISABLE;
io_conf.pin_bit_mask = (1ULL << MON_BTLINK_PIN);
io_conf.mode = GPIO_MODE_INPUT;
io_conf.pull_up_en = GPIO_PULLUP_DISABLE;
io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE;
if (gpio_config(&io_conf) != ESP_OK) {
ESP_LOGE(TAG, "Failed to configure GPIO %d", MON_BTLINK_PIN);
return;
}
BaseType_t ret = xTaskCreate(BtTask, "bt_task", 2048, this, 10, &m_bt_task_handle);
if (ret != pdPASS) {
ESP_LOGE(TAG, "Failed to create BT task");
return;
}
}
void PowerManager::DeinitBtModul() {
if (MON_BTLINK_PIN == GPIO_NUM_NC) {
return;
}
if (m_bt_task_handle != nullptr) {
vTaskDelete(m_bt_task_handle);
m_bt_task_handle = nullptr;
}
gpio_reset_pin(MON_BTLINK_PIN);
}
void PowerManager::BtTask(void *arg) {
PowerManager *instance = static_cast<PowerManager *>(arg);
int last_level = -1;
for (;;) {
vTaskDelay(pdMS_TO_TICKS(100));
int level = gpio_get_level(MON_BTLINK_PIN);
if (level != last_level) {
last_level = level;
if (level == 1) {
ESP_LOGI(TAG, "BTLINK %d high - BT connected", MON_BTLINK_PIN);
if (instance->bt_link_callback_) {
instance->bt_link_callback_(true);
}
} else {
ESP_LOGI(TAG, "BTLINK %d low - BT disconnected", MON_BTLINK_PIN);
if (instance->bt_link_callback_) {
instance->bt_link_callback_(false);
}
}
}
}
vTaskDelete(NULL);
}
void PowerManager::OnBtLinkStatusChanged(std::function<void(bool)> callback) {
bt_link_callback_ = callback;
}

View File

@@ -0,0 +1,45 @@
#ifndef __POWERMANAGER_H__
#define __POWERMANAGER_H__
#include <functional>
#include "driver/gpio.h"
#include "freertos/FreeRTOS.h"
#include "freertos/task.h"
#include "freertos/queue.h"
#include "freertos/timers.h"
class PowerManager{
public:
PowerManager();
void Initialize();
bool IsCharging();
bool IsDischarging();
bool IsChargingDone();
int GetBatteryLevel();
void CheckStartup();
void Start5V();
void Shutdown5V();
void Start4G();
void Shutdown4G();
void Enable4G();
void Disable4G();
void Sleep();
void CheckBatteryStatus();
void OnChargingStatusChanged(std::function<void(bool)> callback);
void OnChargingStatusDisChanged(std::function<void(bool)> callback);
void OnBtLinkStatusChanged(std::function<void(bool)> callback);
void InitializeBtModul();
void DeinitBtModul();
private:
esp_timer_handle_t timer_handle_;
std::function<void(bool)> charging_callback_;
std::function<void(bool)> discharging_callback_;
std::function<void(bool)> bt_link_callback_;
int is_charging_ = -1;
int is_discharging_ = -1;
int call_count_ = 0;
TaskHandle_t m_bt_task_handle;
static void BtTask(void *arg);
};
#endif

View File

@@ -0,0 +1,295 @@
#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"
class YunliaoDisplay : public SpiLcdDisplay {
public:
YunliaoDisplay(esp_lcd_panel_io_handle_t io_handle,
esp_lcd_panel_handle_t panel_handle,
int width,
int height,
int offset_x,
int offset_y,
bool mirror_x,
bool mirror_y,
bool swap_xy)
: SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy) {
}
virtual void SetupUI() override {
SpiLcdDisplay::SetupUI();
auto& theme_manager = LvglThemeManager::GetInstance();
auto theme = theme_manager.GetTheme("dark");
if (theme != nullptr) {
SetTheme(theme);
}
}
};
class YunliaoS3 : public DualNetworkBoard {
private:
i2c_master_bus_handle_t codec_i2c_bus_;
Button boot_button_;
YunliaoDisplay* display_;
PowerSaveTimer* power_save_timer_;
PowerManager* power_manager_;
void InitializePowerSaveTimer() {
power_save_timer_ = new PowerSaveTimer(-1, 60, 600);
power_save_timer_->OnEnterSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(true);
GetBacklight()->SetBrightness(10);
});
power_save_timer_->OnExitSleepMode([this]() {
GetDisplay()->SetPowerSaveMode(false);
GetBacklight()->RestoreBrightness();
});
power_save_timer_->OnShutdownRequest([this]() {
ESP_LOGI(TAG, "Shutting down");
power_manager_->Sleep();
});
power_save_timer_->SetEnabled(true);
}
void InitializeI2c() {
// Initialize I2C peripheral
i2c_master_bus_config_t i2c_bus_cfg = {
.i2c_port = I2C_NUM_0,
.sda_io_num = AUDIO_CODEC_I2C_SDA_PIN,
.scl_io_num = AUDIO_CODEC_I2C_SCL_PIN,
.clk_source = I2C_CLK_SRC_DEFAULT,
.glitch_ignore_cnt = 7,
.intr_priority = 0,
.trans_queue_depth = 0,
.flags =
{
.enable_internal_pullup = 1,
},
};
ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &codec_i2c_bus_));
}
void InitializeSpi() {
spi_bus_config_t buscfg = {};
buscfg.mosi_io_num = DISPLAY_SPI_PIN_MOSI;
buscfg.miso_io_num = DISPLAY_SPI_PIN_MISO;
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));
}
void InitializeButtons() {
boot_button_.OnClick([this]() {
power_save_timer_->WakeUp();
auto& app = Application::GetInstance();
app.ToggleChatState();
});
boot_button_.OnDoubleClick([this]() {
ESP_LOGI(TAG, "Button OnDoubleClick");
auto& app = Application::GetInstance();
if (app.GetDeviceState() == kDeviceStateStarting ||
app.GetDeviceState() == kDeviceStateWifiConfiguring) {
SwitchNetworkType();
}else if(app.GetDeviceState() == kDeviceStateIdle){
bool enableAec = app.GetAecMode() == kAecOff;
SetAecMode(enableAec);
Settings settings("aec", true);
settings.SetInt("mode", enableAec);
}
});
boot_button_.OnMultipleClick(
[this]() {
ESP_LOGI(TAG, "Button OnThreeClick");
if (GetNetworkType() == NetworkType::WIFI) {
auto& wifi_board =
static_cast<WifiBoard&>(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;
esp_lcd_panel_handle_t panel = nullptr;
// 液晶屏控制IO初始化
ESP_LOGD(TAG, "Install panel IO");
esp_lcd_panel_io_spi_config_t io_config = {};
io_config.cs_gpio_num = DISPLAY_SPI_PIN_LCD_CS;
io_config.dc_gpio_num = DISPLAY_SPI_PIN_LCD_DC;
io_config.spi_mode = 3;
io_config.pclk_hz = DISPLAY_SPI_CLOCK_HZ;
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));
// 初始化液晶屏驱动芯片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_lcd_panel_reset(panel);
esp_lcd_panel_init(panel);
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 YunliaoDisplay(panel_io, panel, DISPLAY_WIDTH,
DISPLAY_HEIGHT, DISPLAY_OFFSET_X,
DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X,
DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY);
}
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);
Settings settings("aec", true);
settings.SetInt("mode", enable);
return true;
});
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);
}
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();
power_manager_->CheckStartup();
InitializePowerSaveTimer();
InitializeSpi();
InitializeSt7789Display();
power_manager_->OnChargingStatusDisChanged([this](bool is_discharging) {
if (power_save_timer_) {
if (is_discharging) {
power_save_timer_->SetEnabled(true);
} else {
power_save_timer_->SetEnabled(false);
}
}
});
GetBacklight()->RestoreBrightness();
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);
power_manager_->Start4G();
if (GetNetworkType() == NetworkType::WIFI) {
power_manager_->Disable4G();
}else{
power_manager_->Enable4G();
}
power_manager_->OnBtLinkStatusChanged([this](bool is_connected) {
auto& app = Application::GetInstance();
if (is_connected) {
if (app.GetAecMode() != kAecOff) {
SetAecMode(false);
}
} else {
Settings settings("aec", false);
int storedMode = settings.GetInt("mode", kAecOnDeviceSide);
if (storedMode != kAecOff && app.GetAecMode() == kAecOff) {
SetAecMode(storedMode == kAecOnDeviceSide);
}
}
});
power_manager_->InitializeBtModul();
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);
return &audio_codec;
}
virtual Display* GetDisplay() override { return display_; }
virtual Backlight* GetBacklight() override {
static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN,
DISPLAY_BACKLIGHT_OUTPUT_INVERT);
return &backlight;
}
virtual bool GetBatteryLevel(int& level, bool& charging,
bool& discharging) override {
level = power_manager_->GetBatteryLevel();
charging = power_manager_->IsCharging();
discharging = power_manager_->IsDischarging();
return true;
}
virtual void SetPowerSaveLevel(PowerSaveLevel level) override {
if (level != PowerSaveLevel::LOW_POWER) {
power_save_timer_->WakeUp();
}
DualNetworkBoard::SetPowerSaveLevel(level);
}
};
DECLARE_BOARD(YunliaoS3);