2025-12-26 03:01:18 +08:00
# include <esp_lcd_panel_vendor.h>
# include <esp_log.h>
# include "settings.h"
2025-09-13 14:38:16 +08:00
# include "application.h"
2025-12-26 03:01:18 +08:00
# include "assets/lang_config.h"
2025-09-13 14:38:16 +08:00
# include "button.h"
2025-12-26 03:01:18 +08:00
# include "codecs/es8388_audio_codec.h"
2025-09-13 14:38:16 +08:00
# include "config.h"
2025-12-26 03:01:18 +08:00
# include "display/lcd_display.h"
# include "dual_network_board.h"
# include "lvgl_theme.h"
2025-09-13 14:38:16 +08:00
# include "power_manager.h"
2025-12-26 03:01:18 +08:00
# include "power_save_timer.h"
# include "mcp_server.h"
2025-09-13 14:38:16 +08:00
# define TAG "YunliaoS3"
class YunliaoS3 : public DualNetworkBoard {
2025-12-26 03:01:18 +08:00
private :
2025-09-13 14:38:16 +08:00
i2c_master_bus_handle_t codec_i2c_bus_ ;
Button boot_button_ ;
SpiLcdDisplay * 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 ,
2025-12-26 03:01:18 +08:00
. flags =
{
. enable_internal_pullup = 1 ,
} ,
2025-09-13 14:38:16 +08:00
} ;
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 ;
2025-12-26 03:01:18 +08:00
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 ) ) ;
2025-09-13 14:38:16 +08:00
}
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 ( ) ;
2025-12-26 03:01:18 +08:00
if ( app . GetDeviceState ( ) = = kDeviceStateStarting | |
app . GetDeviceState ( ) = = kDeviceStateWifiConfiguring ) {
2025-09-13 14:38:16 +08:00
SwitchNetworkType ( ) ;
}
2025-12-26 03:01:18 +08:00
} ) ;
boot_button_ . OnMultipleClick (
[ this ] ( ) {
ESP_LOGI ( TAG , " Button OnThreeClick " ) ;
if ( GetNetworkType ( ) = = NetworkType : : WIFI ) {
auto & wifi_board =
static_cast < WifiBoard & > ( GetCurrentBoard ( ) ) ;
wifi_board . EnterWifiConfigMode ( ) ;
}
} , 3 ) ;
2025-09-13 14:38:16 +08:00
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 ( ) ;
2025-12-26 03:01:18 +08:00
} ) ;
2025-09-13 14:38:16 +08:00
}
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 ;
2025-12-26 03:01:18 +08:00
ESP_ERROR_CHECK ( esp_lcd_new_panel_io_spi ( DISPLAY_SPI_LCD_HOST ,
& io_config , & panel_io ) ) ;
2025-09-13 14:38:16 +08:00
// 初始化液晶屏驱动芯片ST7789
ESP_LOGD ( TAG , " Install LCD driver " ) ;
2025-12-26 03:01:18 +08:00
Settings settings ( " display " , false ) ;
bool currentIpsMode = settings . GetBool ( " ips_mode " , DISPLAY_INVERT_COLOR ) ;
2025-09-13 14:38:16 +08:00
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 ;
2025-12-26 03:01:18 +08:00
ESP_ERROR_CHECK (
esp_lcd_new_panel_st7789 ( panel_io , & panel_config , & panel ) ) ;
2025-09-13 14:38:16 +08:00
esp_lcd_panel_reset ( panel ) ;
esp_lcd_panel_init ( panel ) ;
2025-12-26 03:01:18 +08:00
esp_lcd_panel_invert_color ( panel , currentIpsMode ) ;
2025-09-13 14:38:16 +08:00
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 ,
DISPLAY_HEIGHT , DISPLAY_OFFSET_X ,
DISPLAY_OFFSET_Y , DISPLAY_MIRROR_X ,
DISPLAY_MIRROR_Y , DISPLAY_SWAP_XY ) ;
2025-09-14 13:51:19 +08:00
auto & theme_manager = LvglThemeManager : : GetInstance ( ) ;
auto theme = theme_manager . GetTheme ( " dark " ) ;
if ( theme ! = nullptr ) {
display_ - > SetTheme ( theme ) ;
}
2025-09-13 14:38:16 +08:00
}
2025-12-26 03:01:18 +08:00
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 ;
} ) ;
2025-09-13 14:38:16 +08:00
2025-12-26 03:01:18 +08:00
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 ( ) ) {
2025-09-13 14:38:16 +08:00
power_manager_ - > Start5V ( ) ;
power_manager_ - > Initialize ( ) ;
InitializeI2c ( ) ;
power_manager_ - > CheckStartup ( ) ;
InitializePowerSaveTimer ( ) ;
InitializeSpi ( ) ;
InitializeSt7789Display ( ) ;
power_manager_ - > OnChargingStatusDisChanged ( [ this ] ( bool is_discharging ) {
2025-12-26 03:01:18 +08:00
if ( power_save_timer_ ) {
2025-09-13 14:38:16 +08:00
if ( is_discharging ) {
power_save_timer_ - > SetEnabled ( true ) ;
} else {
power_save_timer_ - > SetEnabled ( false ) ;
}
}
} ) ;
2025-12-26 03:01:18 +08:00
if ( GetNetworkType ( ) = = NetworkType : : WIFI ) {
2025-09-13 14:38:16 +08:00
power_manager_ - > Shutdown4G ( ) ;
2025-12-26 03:01:18 +08:00
} else {
2025-09-26 05:12:35 +08:00
power_manager_ - > Start4G ( ) ;
2025-09-13 14:38:16 +08:00
}
GetBacklight ( ) - > RestoreBrightness ( ) ;
2025-12-26 03:01:18 +08:00
while ( gpio_get_level ( BOOT_BUTTON_PIN ) = = 0 ) {
2025-09-26 05:12:35 +08:00
vTaskDelay ( pdMS_TO_TICKS ( 10 ) ) ;
}
InitializeButtons ( ) ;
2025-12-26 03:01:18 +08:00
Settings settings ( " aec " , false ) ;
auto & app = Application : : GetInstance ( ) ;
app . SetAecMode ( settings . GetInt ( " mode " , kAecOnDeviceSide ) = = kAecOnDeviceSide ? kAecOnDeviceSide : kAecOff ) ;
InitializeTools ( ) ;
2025-09-13 14:38:16 +08:00
}
virtual AudioCodec * GetAudioCodec ( ) override {
static Es8388AudioCodec audio_codec (
2025-12-26 03:01:18 +08:00
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 ) ;
2025-09-13 14:38:16 +08:00
return & audio_codec ;
}
2025-12-26 03:01:18 +08:00
virtual Display * GetDisplay ( ) override { return display_ ; }
2025-09-13 14:38:16 +08:00
virtual Backlight * GetBacklight ( ) override {
2025-12-26 03:01:18 +08:00
static PwmBacklight backlight ( DISPLAY_BACKLIGHT_PIN ,
DISPLAY_BACKLIGHT_OUTPUT_INVERT ) ;
2025-09-13 14:38:16 +08:00
return & backlight ;
}
2025-12-26 03:01:18 +08:00
virtual bool GetBatteryLevel ( int & level , bool & charging ,
bool & discharging ) override {
2025-09-13 14:38:16 +08:00
level = power_manager_ - > GetBatteryLevel ( ) ;
charging = power_manager_ - > IsCharging ( ) ;
discharging = power_manager_ - > IsDischarging ( ) ;
return true ;
}
2025-12-09 09:24:56 +08:00
virtual void SetPowerSaveLevel ( PowerSaveLevel level ) override {
if ( level ! = PowerSaveLevel : : LOW_POWER ) {
2025-09-13 14:38:16 +08:00
power_save_timer_ - > WakeUp ( ) ;
}
2025-12-09 09:24:56 +08:00
DualNetworkBoard : : SetPowerSaveLevel ( level ) ;
2025-09-13 14:38:16 +08:00
}
} ;
DECLARE_BOARD ( YunliaoS3 ) ;