#include "wifi_board.h" #include "codecs/es8389_audio_codec.h" #include "display/lcd_display.h" #include "system_reset.h" #include "application.h" #include "button.h" #include "config.h" #include "power_save_timer.h" #include "led/single_led.h" #include "assets/lang_config.h" #include "power_manager.h" #include "i2c_device.h" #include #include #include #include #include #include "esp_io_expander_tca95xx_16bit.h" #define TAG "atk_dnesp32s3_box2_wifi" LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); class atk_dnesp32s3_box2_wifi : public WifiBoard { private: i2c_master_bus_handle_t i2c_bus_; LcdDisplay* display_; esp_io_expander_handle_t io_exp_handle; button_handle_t btns; button_driver_t* btn_driver_ = nullptr; static atk_dnesp32s3_box2_wifi* instance_; PowerSaveTimer* power_save_timer_; PowerManager* power_manager_; PowerSupply power_status_; esp_timer_handle_t wake_timer_handle_; esp_lcd_panel_io_handle_t panel_io = nullptr; esp_lcd_panel_handle_t panel = nullptr; int ticks_ = 0; const int kChgCtrlInterval = 5; void InitializeBoardPowerManager() { instance_ = this; if (IoExpanderGetLevel(XIO_CHRG) == 0) { power_status_ = kDeviceTypecSupply; } else { power_status_ = kDeviceBatterySupply; } esp_timer_create_args_t wake_display_timer_args = { .callback = [](void *arg) { atk_dnesp32s3_box2_wifi* self = static_cast(arg); self->ticks_ ++; if (self->ticks_ % self->kChgCtrlInterval == 0) { if (self->IoExpanderGetLevel(XIO_CHRG) == 0) { self->power_status_ = kDeviceTypecSupply; } else { self->power_status_ = kDeviceBatterySupply; } /* 低于某个电量,会自动关机 */ if (self->power_manager_->low_voltage_ < 2630 && self->power_status_ == kDeviceBatterySupply) { esp_timer_stop(self->power_manager_->timer_handle_); esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); vTaskDelay(pdMS_TO_TICKS(100)); esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_INPUT); esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); vTaskDelay(pdMS_TO_TICKS(100)); } } }, .arg = this, .dispatch_method = ESP_TIMER_TASK, .name = "wake_update_timer", .skip_unhandled_events = true, }; ESP_ERROR_CHECK(esp_timer_create(&wake_display_timer_args, &wake_timer_handle_)); ESP_ERROR_CHECK(esp_timer_start_periodic(wake_timer_handle_, 100000)); } void InitializePowerManager() { power_manager_ = new PowerManager(io_exp_handle); power_manager_->OnChargingStatusChanged([this](bool is_charging) { if (is_charging) { power_save_timer_->SetEnabled(false); } else { power_save_timer_->SetEnabled(true); } }); } void InitializePowerSaveTimer() { power_save_timer_ = new PowerSaveTimer(-1, 60, 300); power_save_timer_->OnEnterSleepMode([this]() { display_->SetChatMessage("system", ""); display_->SetEmotion("sleepy"); GetBacklight()->SetBrightness(1); }); power_save_timer_->OnExitSleepMode([this]() { display_->SetChatMessage("system", ""); display_->SetEmotion("neutral"); GetBacklight()->RestoreBrightness(); }); power_save_timer_->OnShutdownRequest([this]() { if (power_status_ == kDeviceBatterySupply) { GetBacklight()->SetBrightness(0); esp_timer_stop(power_manager_->timer_handle_); esp_io_expander_set_dir( io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); esp_io_expander_set_level(io_exp_handle, XIO_CHG_CTRL, 0); vTaskDelay(pdMS_TO_TICKS(100)); esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 0); } }); power_save_timer_->SetEnabled(true); } void audio_volume_change(bool direction) { auto codec = GetAudioCodec(); auto volume = codec->output_volume(); if (direction) { volume += 10; if (volume > 100) { volume = 100; } codec->SetOutputVolume(volume); } else { volume -= 10; if (volume < 0) { volume = 0; } codec->SetOutputVolume(volume); } GetDisplay()->ShowNotification(Lang::Strings::VOLUME + std::to_string(volume)); } void audio_volume_minimum(){ GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); } void audio_volume_maxmum(){ GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); } esp_err_t IoExpanderSetLevel(uint16_t pin_mask, uint8_t level) { return esp_io_expander_set_level(io_exp_handle, pin_mask, level); } uint8_t IoExpanderGetLevel(uint16_t pin_mask) { uint32_t pin_val = 0; esp_io_expander_get_level(io_exp_handle, DRV_IO_EXP_INPUT_MASK, &pin_val); pin_mask &= DRV_IO_EXP_INPUT_MASK; return (uint8_t)((pin_val & pin_mask) ? 1 : 0); } void InitializeIoExpander() { esp_err_t ret = ESP_OK; esp_io_expander_new_i2c_tca95xx_16bit(i2c_bus_, ESP_IO_EXPANDER_I2C_TCA9555_ADDRESS_000, &io_exp_handle); ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_OUTPUT_MASK, IO_EXPANDER_OUTPUT); ret |= esp_io_expander_set_dir(io_exp_handle, DRV_IO_EXP_INPUT_MASK, IO_EXPANDER_INPUT); ret |= esp_io_expander_set_level(io_exp_handle, XIO_SYS_POW, 1); ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_3V3A, 1); ret |= esp_io_expander_set_level(io_exp_handle, XIO_EN_4G, 1); ret |= esp_io_expander_set_level(io_exp_handle, XIO_SPK_EN, 1); ret |= esp_io_expander_set_level(io_exp_handle, XIO_USB_SEL, 1); ret |= esp_io_expander_set_level(io_exp_handle, XIO_VBUS_EN, 0); assert(ret == ESP_OK); } // Initialize I2C peripheral void InitializeI2c() { i2c_master_bus_config_t i2c_bus_cfg = { .i2c_port = (i2c_port_t)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, &i2c_bus_)); } void InitializeButtons() { instance_ = this; button_config_t l_btn_cfg = { .long_press_time = 800, .short_press_time = 500 }; button_config_t m_btn_cfg = { .long_press_time = 800, .short_press_time = 500 }; button_config_t r_btn_cfg = { .long_press_time = 800, .short_press_time = 500 }; button_driver_t* xio_l_btn_driver_ = nullptr; button_driver_t* xio_m_btn_driver_ = nullptr; button_handle_t l_btn_handle = NULL; button_handle_t m_btn_handle = NULL; button_handle_t r_btn_handle = NULL; xio_l_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); xio_l_btn_driver_->enable_power_save = false; xio_l_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { return !instance_->IoExpanderGetLevel(XIO_KEY_L); }; ESP_ERROR_CHECK(iot_button_create(&l_btn_cfg, xio_l_btn_driver_, &l_btn_handle)); xio_m_btn_driver_ = (button_driver_t*)calloc(1, sizeof(button_driver_t)); xio_m_btn_driver_->enable_power_save = false; xio_m_btn_driver_->get_key_level = [](button_driver_t *button_driver) -> uint8_t { return instance_->IoExpanderGetLevel(XIO_KEY_M); }; ESP_ERROR_CHECK(iot_button_create(&m_btn_cfg, xio_m_btn_driver_, &m_btn_handle)); button_gpio_config_t r_cfg = { .gpio_num = R_BUTTON_GPIO, .active_level = BUTTON_INACTIVE, .enable_power_save = false, .disable_pull = false }; ESP_ERROR_CHECK(iot_button_new_gpio_device(&r_btn_cfg, &r_cfg, &r_btn_handle)); iot_button_register_cb(l_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); self->power_save_timer_->WakeUp(); self->audio_volume_change(false); }, this); iot_button_register_cb(l_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); self->power_save_timer_->WakeUp(); self->audio_volume_minimum(); }, this); iot_button_register_cb(m_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); self->power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); app.ToggleChatState(); }, this); iot_button_register_cb(m_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { self->ResetWifiConfiguration(); } if (self->power_status_ == kDeviceBatterySupply) { auto backlight = Board::GetInstance().GetBacklight(); backlight->SetBrightness(0); esp_timer_stop(self->power_manager_->timer_handle_); esp_io_expander_set_dir(self->io_exp_handle, XIO_CHG_CTRL, IO_EXPANDER_OUTPUT); esp_io_expander_set_level(self->io_exp_handle, XIO_CHG_CTRL, 0); vTaskDelay(pdMS_TO_TICKS(100)); esp_io_expander_set_level(self->io_exp_handle, XIO_SYS_POW, 0); vTaskDelay(pdMS_TO_TICKS(100)); } }, this); iot_button_register_cb(r_btn_handle, BUTTON_PRESS_DOWN, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); self->power_save_timer_->WakeUp(); self->audio_volume_change(true); }, this); iot_button_register_cb(r_btn_handle, BUTTON_LONG_PRESS_START, nullptr, [](void* button_handle, void* usr_data) { auto self = static_cast(usr_data); self->power_save_timer_->WakeUp(); self->audio_volume_maxmum(); }, this); } void InitializeSt7789Display() { ESP_LOGI(TAG, "Install panel IO"); /* RD PIN */ gpio_config_t gpio_init_struct; gpio_init_struct.intr_type = GPIO_INTR_DISABLE; gpio_init_struct.mode = GPIO_MODE_INPUT_OUTPUT; gpio_init_struct.pin_bit_mask = 1ull << LCD_PIN_RD; gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; gpio_config(&gpio_init_struct); gpio_set_level(LCD_PIN_RD, 1); /* BL PIN */ gpio_init_struct.pin_bit_mask = 1ull << DISPLAY_BACKLIGHT_PIN; gpio_init_struct.pull_down_en = GPIO_PULLDOWN_DISABLE; gpio_init_struct.pull_up_en = GPIO_PULLUP_ENABLE; gpio_config(&gpio_init_struct); esp_lcd_i80_bus_handle_t i80_bus = NULL; esp_lcd_i80_bus_config_t bus_config = { .dc_gpio_num = LCD_PIN_DC, .wr_gpio_num = LCD_PIN_WR, .clk_src = LCD_CLK_SRC_DEFAULT, .data_gpio_nums = { LCD_PIN_D0, LCD_PIN_D1, LCD_PIN_D2, LCD_PIN_D3, LCD_PIN_D4, LCD_PIN_D5, LCD_PIN_D6, LCD_PIN_D7, }, .bus_width = 8, .max_transfer_bytes = DISPLAY_WIDTH * DISPLAY_HEIGHT * sizeof(uint16_t), .psram_trans_align = 64, .sram_trans_align = 4, }; ESP_ERROR_CHECK(esp_lcd_new_i80_bus(&bus_config, &i80_bus)); esp_lcd_panel_io_i80_config_t io_config = { .cs_gpio_num = LCD_PIN_CS, .pclk_hz = (20 * 1000 * 1000), .trans_queue_depth = 7, .on_color_trans_done = nullptr, .user_ctx = nullptr, .lcd_cmd_bits = 8, .lcd_param_bits = 8, .dc_levels = { .dc_idle_level = 1, .dc_cmd_level = 0, .dc_dummy_level = 0, .dc_data_level = 1, }, .flags = { .cs_active_high = 0, .pclk_active_neg = 0, .pclk_idle_low = 0, }, }; ESP_ERROR_CHECK(esp_lcd_new_panel_io_i80(i80_bus, &io_config, &panel_io)); esp_lcd_panel_dev_config_t panel_config = { .reset_gpio_num = LCD_PIN_RST, .rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB, .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, true); esp_lcd_panel_set_gap(panel, 0, 0); esp_lcd_panel_io_tx_param(panel_io, 0xCF, (uint8_t[]) {0x00,0x83,0x30}, 3); esp_lcd_panel_io_tx_param(panel_io, 0xED, (uint8_t[]) {0x64,0x03,0x12,0x81}, 4); esp_lcd_panel_io_tx_param(panel_io, 0xE8, (uint8_t[]) {0x85,0x01,0x79}, 3); esp_lcd_panel_io_tx_param(panel_io, 0xCB, (uint8_t[]) {0x39,0x2C,0x00,0x34,0x02}, 5); esp_lcd_panel_io_tx_param(panel_io, 0xF7, (uint8_t[]) {0x20}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xEA, (uint8_t[]) {0x00,0x00}, 2); esp_lcd_panel_io_tx_param(panel_io, 0xbb, (uint8_t[]) {0x20}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xc3, (uint8_t[]) {0x00}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xC4, (uint8_t[]) {0x20}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xC5, (uint8_t[]) {0x20}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xC6, (uint8_t[]) {0x10}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xC7, (uint8_t[]) {0xB0}, 1); esp_lcd_panel_io_tx_param(panel_io, 0x36, (uint8_t[]) {0x60}, 1); esp_lcd_panel_io_tx_param(panel_io, 0x3A, (uint8_t[]) {0x55}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xB1, (uint8_t[]) {0x00,0x1B}, 2); esp_lcd_panel_io_tx_param(panel_io, 0xF2, (uint8_t[]) {0x08}, 1); esp_lcd_panel_io_tx_param(panel_io, 0x26, (uint8_t[]) {0x01}, 1); esp_lcd_panel_io_tx_param(panel_io, 0xE0, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x32,0x44,0x42,0x06,0x0E,0x12,0x14,0x17}, 14); esp_lcd_panel_io_tx_param(panel_io, 0xE1, (uint8_t[]) {0xD0,0x00,0x02,0x07,0x0A,0x28,0x31,0x54,0x47,0x0E,0x1C,0x17,0x1B,0x1E}, 14); esp_lcd_panel_io_tx_param(panel_io, 0xB7, (uint8_t[]) {0x07}, 1); 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, { .text_font = &font_puhui_20_4, .icon_font = &font_awesome_20_4, .emoji_font = DISPLAY_HEIGHT >= 240 ? font_emoji_64_init() : font_emoji_32_init(), }); } public: atk_dnesp32s3_box2_wifi() { InitializeI2c(); InitializeIoExpander(); InitializePowerSaveTimer(); InitializePowerManager(); InitializeSt7789Display(); InitializeButtons(); GetBacklight()->RestoreBrightness(); InitializeBoardPowerManager(); } virtual AudioCodec* GetAudioCodec() override { static Es8389AudioCodec audio_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, GPIO_NUM_NC, AUDIO_CODEC_ES8389_ADDR, false); 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 { static bool last_discharging = false; charging = power_manager_->IsCharging(); discharging = power_manager_->IsDischarging(); if (discharging != last_discharging) { power_save_timer_->SetEnabled(discharging); last_discharging = discharging; } level = power_manager_->GetBatteryLevel(); return true; } virtual void SetPowerSaveMode(bool enabled) override { if (!enabled) { power_save_timer_->WakeUp(); } WifiBoard::SetPowerSaveMode(enabled); } }; DECLARE_BOARD(atk_dnesp32s3_box2_wifi); // 定义静态成员变量 atk_dnesp32s3_box2_wifi* atk_dnesp32s3_box2_wifi::instance_ = nullptr;