From cead024698ae9abdd933b556f6a97772cf616eeb Mon Sep 17 00:00:00 2001 From: Terrence Date: Wed, 5 Mar 2025 09:37:13 +0800 Subject: [PATCH] Add Backlight and PowerSaveTimer --- CMakeLists.txt | 2 +- main/application.cc | 13 + main/application.h | 1 + .../atk-dnesp32s3-box/atk_dnesp32s3_box.cc | 2 +- main/boards/atk-dnesp32s3/atk_dnesp32s3.cc | 2 +- .../atoms3-echo-base/atoms3_echo_base.cc | 8 +- .../atoms3r-echo-base/atoms3r_echo_base.cc | 2 +- .../compact_wifi_board_lcd.cc | 15 +- main/boards/common/backlight.cc | 113 +++ main/boards/common/backlight.h | 36 + main/boards/common/board.h | 2 + main/boards/common/power_save_timer.cc | 100 +++ main/boards/common/power_save_timer.h | 33 + main/boards/du-chatx/du-chatx-wifi.cc | 8 +- main/boards/esp-box-3/esp_box3_board.cc | 8 +- main/boards/esp-box/esp_box_board.cc | 8 +- .../boards/esp-sparkbot/esp_sparkbot_board.cc | 8 +- .../board_control.cc | 18 - .../esp32-s3-touch-amoled-1.8.cc | 34 +- .../esp32-s3-touch-lcd-1.46.cc | 23 +- .../esp32-s3-touch-lcd-1.85.cc | 11 +- .../esp32-s3-touch-lcd-1.85c.cc | 8 +- .../esp32s3_korvo2_v3_board.cc | 2 +- main/boards/kevin-box-2/kevin_box_board.cc | 88 ++- .../kevin-sp-v3-dev/kevin-sp-v3_board.cc | 9 +- .../kevin_yuying_313lcd.cc | 14 +- .../lichuang-c3-dev/lichuang_c3_dev_board.cc | 8 +- .../boards/lichuang-dev/lichuang_dev_board.cc | 8 +- .../lilygo-t-cameraplus-s3.cc | 8 +- .../lilygo-t-circle-s3/lilygo-t-circle-s3.cc | 8 +- .../boards/m5stack-core-s3/m5stack_core_s3.cc | 2 +- .../magiclick-2p4/magiclick_2p4_board.cc | 12 +- .../magiclick-2p5/magiclick_2p5_board.cc | 12 +- .../magiclick-c3-v2/magiclick_c3_v2_board.cc | 94 +-- .../boards/magiclick-c3/magiclick_c3_board.cc | 95 +-- .../movecall_moji_esp32s3.cc | 14 +- .../sensecap-watcher/sensecap_watcher.cc | 8 +- main/boards/taiji-pi-s3/taiji_pi_s3.cc | 8 +- main/boards/tudouzi/kevin_box_board.cc | 130 ++-- .../xingzhi-cube-0.96oled-ml307.cc | 136 +++- .../xingzhi_ssd1306_display.cc | 654 ----------------- .../xingzhi_ssd1306_display.h | 86 --- .../xingzhi-cube-0.96oled-wifi.cc | 137 +++- .../xingzhi_ssd1306_display.cc | 654 ----------------- .../xingzhi_ssd1306_display.h | 86 --- .../xingzhi-cube-1.54tft-ml307.cc | 173 ++++- .../xingzhi_lcd_display.cc | 659 ------------------ .../xingzhi_lcd_display.h | 94 --- .../xingzhi-cube-1.54tft-wifi/power_manager.h | 112 +++ .../xingzhi-cube-1.54tft-wifi.cc | 175 ++++- .../xingzhi_lcd_display.cc | 659 ------------------ .../xingzhi_lcd_display.h | 94 --- main/boards/xmini-c3/xmini_c3_board.cc | 80 +-- main/display/display.cc | 14 +- main/display/display.h | 4 - main/display/lcd_display.cc | 115 +-- main/display/lcd_display.h | 26 +- main/iot/things/battery.cc | 2 +- main/iot/things/blaklight.cc | 15 +- main/iot/things/speaker.cc | 2 +- main/led/led.cc | 219 ------ 61 files changed, 1331 insertions(+), 3840 deletions(-) create mode 100644 main/boards/common/backlight.cc create mode 100644 main/boards/common/backlight.h create mode 100644 main/boards/common/power_save_timer.cc create mode 100644 main/boards/common/power_save_timer.h delete mode 100644 main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc delete mode 100644 main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h delete mode 100644 main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc delete mode 100644 main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h delete mode 100644 main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc delete mode 100644 main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h create mode 100644 main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h delete mode 100644 main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc delete mode 100644 main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h delete mode 100644 main/led/led.cc diff --git a/CMakeLists.txt b/CMakeLists.txt index 3b03f42a..0f4aa676 100755 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,7 +4,7 @@ # CMakeLists in this exact order for cmake to work correctly cmake_minimum_required(VERSION 3.16) -set(PROJECT_VER "1.4.1") +set(PROJECT_VER "1.4.2") # Add this line to disable the specific warning add_compile_options(-Wno-missing-field-initializers) diff --git a/main/application.cc b/main/application.cc index 94e397f8..2e05ffec 100644 --- a/main/application.cc +++ b/main/application.cc @@ -820,3 +820,16 @@ void Application::WakeWordInvoke(const std::string& wake_word) { }); } } + +bool Application::CanEnterSleepMode() { + if (device_state_ != kDeviceStateIdle) { + return false; + } + + if (protocol_ && protocol_->IsAudioChannelOpened()) { + return false; + } + + // Now it is safe to enter sleep mode + return true; +} diff --git a/main/application.h b/main/application.h index c92938a9..f0aa8b83 100644 --- a/main/application.h +++ b/main/application.h @@ -69,6 +69,7 @@ public: void Reboot(); void WakeWordInvoke(const std::string& wake_word); void PlaySound(const std::string_view& sound); + bool CanEnterSleepMode(); private: Application(); diff --git a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc index a5eeec76..0807f6fa 100644 --- a/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc +++ b/main/boards/atk-dnesp32s3-box/atk_dnesp32s3_box.cc @@ -152,7 +152,7 @@ private: esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, diff --git a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc index 7ed2c8bc..0a07aae2 100644 --- a/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc +++ b/main/boards/atk-dnesp32s3/atk_dnesp32s3.cc @@ -132,7 +132,7 @@ private: esp_lcd_panel_invert_color(panel, true); 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, diff --git a/main/boards/atoms3-echo-base/atoms3_echo_base.cc b/main/boards/atoms3-echo-base/atoms3_echo_base.cc index 69b16942..8a2646e2 100644 --- a/main/boards/atoms3-echo-base/atoms3_echo_base.cc +++ b/main/boards/atoms3-echo-base/atoms3_echo_base.cc @@ -177,7 +177,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - display_ = new SpiLcdDisplay(io_handle, panel_handle, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new SpiLcdDisplay(io_handle, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, { .text_font = &font_puhui_16_4, @@ -211,6 +211,7 @@ public: InitializeGc9107Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -233,6 +234,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(AtomS3EchoBaseBoard); \ No newline at end of file diff --git a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc index 3b60def8..3f026f58 100644 --- a/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc +++ b/main/boards/atoms3r-echo-base/atoms3r_echo_base.cc @@ -237,7 +237,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_init(panel_handle)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - display_ = new SpiLcdDisplay(io_handle, panel_handle, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new SpiLcdDisplay(io_handle, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, { .text_font = &font_puhui_16_4, diff --git a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc index e2830d00..cc2f990b 100644 --- a/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc +++ b/main/boards/bread-compact-wifi-lcd/compact_wifi_board_lcd.cc @@ -122,7 +122,7 @@ private: #ifdef LCD_TYPE_GC9A01_SERIAL panel_config.vendor_config = &gc9107_vendor_config; #endif - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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_16_4, @@ -147,7 +147,9 @@ private: void InitializeIot() { auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); +#if DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC thing_manager.AddThing(iot::CreateThing("Backlight")); +#endif } public: @@ -157,6 +159,10 @@ public: InitializeLcdDisplay(); InitializeButtons(); InitializeIot(); + +#if DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC + GetBacklight()->RestoreBrightness(); +#endif } virtual Led* GetLed() override { @@ -178,6 +184,13 @@ public: virtual Display* GetDisplay() override { return display_; } + +#if DISPLAY_BACKLIGHT_PIN != GPIO_NUM_NC + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } +#endif }; DECLARE_BOARD(CompactWifiBoardLCD); diff --git a/main/boards/common/backlight.cc b/main/boards/common/backlight.cc new file mode 100644 index 00000000..bb80687b --- /dev/null +++ b/main/boards/common/backlight.cc @@ -0,0 +1,113 @@ +#include "backlight.h" +#include "settings.h" + +#include +#include + +#define TAG "Backlight" + + +Backlight::Backlight() { + // 创建背光渐变定时器 + const esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto self = static_cast(arg); + self->OnTransitionTimer(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "backlight_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &transition_timer_)); +} + +Backlight::~Backlight() { + if (transition_timer_ != nullptr) { + esp_timer_stop(transition_timer_); + esp_timer_delete(transition_timer_); + } +} + +void Backlight::RestoreBrightness() { + // Load brightness from settings + Settings settings("display"); + SetBrightness(settings.GetInt("brightness", 75)); +} + +void Backlight::SetBrightness(uint8_t brightness, bool permanent) { + if (brightness > 100) { + brightness = 100; + } + + if (brightness_ == brightness) { + return; + } + + if (permanent) { + Settings settings("display", true); + settings.SetInt("brightness", brightness); + } + + target_brightness_ = brightness; + step_ = (target_brightness_ > brightness_) ? 1 : -1; + + if (transition_timer_ != nullptr) { + // 启动定时器,每 5ms 更新一次 + esp_timer_start_periodic(transition_timer_, 5 * 1000); + } + ESP_LOGI(TAG, "Set brightness to %d", brightness); +} + +void Backlight::OnTransitionTimer() { + if (brightness_ == target_brightness_) { + esp_timer_stop(transition_timer_); + return; + } + + brightness_ += step_; + SetBrightnessImpl(brightness_); + + if (brightness_ == target_brightness_) { + esp_timer_stop(transition_timer_); + } +} + +PwmBacklight::PwmBacklight(gpio_num_t pin, bool output_invert) : Backlight() { + const ledc_timer_config_t backlight_timer = { + .speed_mode = LEDC_LOW_SPEED_MODE, + .duty_resolution = LEDC_TIMER_10_BIT, + .timer_num = LEDC_TIMER_0, + .freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫 + .clk_cfg = LEDC_AUTO_CLK, + .deconfigure = false + }; + ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); + + // Setup LEDC peripheral for PWM backlight control + const ledc_channel_config_t backlight_channel = { + .gpio_num = pin, + .speed_mode = LEDC_LOW_SPEED_MODE, + .channel = LEDC_CHANNEL_0, + .intr_type = LEDC_INTR_DISABLE, + .timer_sel = LEDC_TIMER_0, + .duty = 0, + .hpoint = 0, + .flags = { + .output_invert = output_invert, + } + }; + ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); +} + +PwmBacklight::~PwmBacklight() { + ledc_stop(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, 0); +} + +void PwmBacklight::SetBrightnessImpl(uint8_t brightness) { + // LEDC resolution set to 10bits, thus: 100% = 1023 + uint32_t duty_cycle = (1023 * brightness) / 100; + ledc_set_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0, duty_cycle); + ledc_update_duty(LEDC_LOW_SPEED_MODE, LEDC_CHANNEL_0); +} + diff --git a/main/boards/common/backlight.h b/main/boards/common/backlight.h new file mode 100644 index 00000000..4fd2ceca --- /dev/null +++ b/main/boards/common/backlight.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include +#include + + +class Backlight { +public: + Backlight(); + ~Backlight(); + + void RestoreBrightness(); + void SetBrightness(uint8_t brightness, bool permanent = false); + inline uint8_t brightness() const { return brightness_; } + +protected: + void OnTransitionTimer(); + virtual void SetBrightnessImpl(uint8_t brightness) = 0; + + esp_timer_handle_t transition_timer_ = nullptr; + uint8_t brightness_ = 0; + uint8_t target_brightness_ = 0; + uint8_t step_ = 1; +}; + + +class PwmBacklight : public Backlight { +public: + PwmBacklight(gpio_num_t pin, bool output_invert = false); + ~PwmBacklight(); + + void SetBrightnessImpl(uint8_t brightness) override; +}; diff --git a/main/boards/common/board.h b/main/boards/common/board.h index f1504b7c..a6baf35b 100644 --- a/main/boards/common/board.h +++ b/main/boards/common/board.h @@ -8,6 +8,7 @@ #include #include "led/led.h" +#include "backlight.h" void* create_board(); class AudioCodec; @@ -34,6 +35,7 @@ public: virtual ~Board() = default; virtual std::string GetBoardType() = 0; virtual std::string GetUuid() { return uuid_; } + virtual Backlight* GetBacklight() { return nullptr; } virtual Led* GetLed(); virtual AudioCodec* GetAudioCodec() = 0; virtual Display* GetDisplay(); diff --git a/main/boards/common/power_save_timer.cc b/main/boards/common/power_save_timer.cc new file mode 100644 index 00000000..d43c872c --- /dev/null +++ b/main/boards/common/power_save_timer.cc @@ -0,0 +1,100 @@ +#include "power_save_timer.h" +#include "application.h" + +#include + +#define TAG "PowerSaveTimer" + + +PowerSaveTimer::PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep, int seconds_to_shutdown) + : cpu_max_freq_(cpu_max_freq), seconds_to_sleep_(seconds_to_sleep), seconds_to_shutdown_(seconds_to_shutdown) { + esp_timer_create_args_t timer_args = { + .callback = [](void* arg) { + auto self = static_cast(arg); + self->PowerSaveCheck(); + }, + .arg = this, + .dispatch_method = ESP_TIMER_TASK, + .name = "power_save_timer", + .skip_unhandled_events = true, + }; + ESP_ERROR_CHECK(esp_timer_create(&timer_args, &power_save_timer_)); +} + +PowerSaveTimer::~PowerSaveTimer() { + esp_timer_stop(power_save_timer_); + esp_timer_delete(power_save_timer_); +} + +void PowerSaveTimer::SetEnabled(bool enabled) { + if (enabled && !enabled_) { + ticks_ = 0; + enabled_ = enabled; + ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); + } else if (!enabled && enabled_) { + ESP_ERROR_CHECK(esp_timer_stop(power_save_timer_)); + enabled_ = enabled; + } +} + +void PowerSaveTimer::OnEnterSleepMode(std::function callback) { + on_enter_sleep_mode_ = callback; +} + +void PowerSaveTimer::OnExitSleepMode(std::function callback) { + on_exit_sleep_mode_ = callback; +} + +void PowerSaveTimer::OnShutdownRequest(std::function callback) { + on_shutdown_request_ = callback; +} + +void PowerSaveTimer::PowerSaveCheck() { + auto& app = Application::GetInstance(); + if (!in_sleep_mode_ && !app.CanEnterSleepMode()) { + ticks_ = 0; + return; + } + + ticks_++; + if (seconds_to_sleep_ != -1 && ticks_ >= seconds_to_sleep_) { + if (!in_sleep_mode_) { + in_sleep_mode_ = true; + if (on_enter_sleep_mode_) { + on_enter_sleep_mode_(); + } + + if (cpu_max_freq_ != -1) { + esp_pm_config_t pm_config = { + .max_freq_mhz = cpu_max_freq_, + .min_freq_mhz = 40, + .light_sleep_enable = true, + }; + esp_pm_configure(&pm_config); + } + } + } + if (seconds_to_shutdown_ != -1 && ticks_ >= seconds_to_shutdown_ && on_shutdown_request_) { + on_shutdown_request_(); + } +} + +void PowerSaveTimer::WakeUp() { + ticks_ = 0; + if (in_sleep_mode_) { + in_sleep_mode_ = false; + + if (cpu_max_freq_ != -1) { + esp_pm_config_t pm_config = { + .max_freq_mhz = cpu_max_freq_, + .min_freq_mhz = cpu_max_freq_, + .light_sleep_enable = false, + }; + esp_pm_configure(&pm_config); + } + + if (on_exit_sleep_mode_) { + on_exit_sleep_mode_(); + } + } +} diff --git a/main/boards/common/power_save_timer.h b/main/boards/common/power_save_timer.h new file mode 100644 index 00000000..1b527f2a --- /dev/null +++ b/main/boards/common/power_save_timer.h @@ -0,0 +1,33 @@ +#pragma once + +#include + +#include +#include + +class PowerSaveTimer { +public: + PowerSaveTimer(int cpu_max_freq, int seconds_to_sleep = 20, int seconds_to_shutdown = -1); + ~PowerSaveTimer(); + + void SetEnabled(bool enabled); + void OnEnterSleepMode(std::function callback); + void OnExitSleepMode(std::function callback); + void OnShutdownRequest(std::function callback); + void WakeUp(); + +private: + void PowerSaveCheck(); + + esp_timer_handle_t power_save_timer_ = nullptr; + bool enabled_ = false; + bool in_sleep_mode_ = false; + int ticks_ = 0; + int cpu_max_freq_; + int seconds_to_sleep_; + int seconds_to_shutdown_; + + std::function on_enter_sleep_mode_; + std::function on_exit_sleep_mode_; + std::function on_shutdown_request_; +}; diff --git a/main/boards/du-chatx/du-chatx-wifi.cc b/main/boards/du-chatx/du-chatx-wifi.cc index 85521bc0..e8e7023a 100644 --- a/main/boards/du-chatx/du-chatx-wifi.cc +++ b/main/boards/du-chatx/du-chatx-wifi.cc @@ -74,7 +74,7 @@ private: esp_lcd_panel_invert_color(panel, DISPLAY_INVERT_COLOR); 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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_16_4, @@ -109,6 +109,7 @@ public: InitializeLcdDisplay(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual Led* GetLed() override { @@ -125,6 +126,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(DuChatX); diff --git a/main/boards/esp-box-3/esp_box3_board.cc b/main/boards/esp-box-3/esp_box3_board.cc index 07631fd6..522de630 100644 --- a/main/boards/esp-box-3/esp_box3_board.cc +++ b/main/boards/esp-box-3/esp_box3_board.cc @@ -121,7 +121,7 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, @@ -144,6 +144,7 @@ public: InitializeIli9341Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -166,6 +167,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(EspBox3Board); diff --git a/main/boards/esp-box/esp_box_board.cc b/main/boards/esp-box/esp_box_board.cc index e22c33d9..81bbb86d 100644 --- a/main/boards/esp-box/esp_box_board.cc +++ b/main/boards/esp-box/esp_box_board.cc @@ -121,7 +121,7 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, @@ -144,6 +144,7 @@ public: InitializeIli9341Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -166,6 +167,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(EspBox3Board); diff --git a/main/boards/esp-sparkbot/esp_sparkbot_board.cc b/main/boards/esp-sparkbot/esp_sparkbot_board.cc index 66ea484b..7b1cd8dd 100644 --- a/main/boards/esp-sparkbot/esp_sparkbot_board.cc +++ b/main/boards/esp-sparkbot/esp_sparkbot_board.cc @@ -113,7 +113,7 @@ private: esp_lcd_panel_init(panel); esp_lcd_panel_invert_color(panel, true); esp_lcd_panel_disp_on_off(panel, true); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, @@ -137,6 +137,7 @@ public: InitializeDisplay(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -149,6 +150,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(EspSparkBot); diff --git a/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc b/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc index 0630e4a5..b3f2163e 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/board_control.cc @@ -15,24 +15,6 @@ namespace iot { class BoardControl : public Thing { public: BoardControl() : Thing("BoardControl", "当前 AI 机器人管理和控制") { - // 添加电池电量属性 - properties_.AddNumberProperty("BatteryLevel", "当前电池电量百分比", [this]() -> int { - int level = 0; - bool charging = false; - Board::GetInstance().GetBatteryLevel(level, charging); - ESP_LOGI(TAG, "当前电池电量: %d%%, 充电状态: %s", level, charging ? "充电中" : "未充电"); - return level; - }); - - // 添加充电状态属性 - properties_.AddBooleanProperty("Charging", "是否正在充电", [this]() -> bool { - int level = 0; - bool charging = false; - Board::GetInstance().GetBatteryLevel(level, charging); - ESP_LOGI(TAG, "当前电池电量: %d%%, 充电状态: %s", level, charging ? "充电中" : "未充电"); - return charging; - }); - // 修改重新配网 methods_.AddMethod("ResetWifiConfiguration", "重新配网", ParameterList(), [this](const ParameterList& parameters) { diff --git a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc index 13796d0e..8c438b38 100644 --- a/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc +++ b/main/boards/esp32-s3-touch-amoled-1.8/esp32-s3-touch-amoled-1.8.cc @@ -46,8 +46,6 @@ class CustomLcdDisplay : public SpiLcdDisplay { public: CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, - gpio_num_t backlight_pin, - bool backlight_output_invert, int width, int height, int offset_x, @@ -55,7 +53,7 @@ public: bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, backlight_pin, backlight_output_invert, + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_30_4, @@ -65,21 +63,17 @@ public: DisplayLockGuard lock(this); lv_obj_set_style_pad_left(status_bar_, LV_HOR_RES * 0.1, 0); lv_obj_set_style_pad_right(status_bar_, LV_HOR_RES * 0.1, 0); - - SetBacklight(100); } +}; - virtual void SetBacklight(uint8_t brightness) override { - if (brightness > 100) - { - brightness = 100; - } +class CustomBacklight : public Backlight { +public: + CustomBacklight(esp_lcd_panel_io_handle_t panel_io) : Backlight(), panel_io_(panel_io) {} - brightness_ = brightness; - - Settings settings("display", true); - settings.SetInt("bright", brightness_); +protected: + esp_lcd_panel_io_handle_t panel_io_; + virtual void SetBrightnessImpl(uint8_t brightness) override { uint8_t data[1] = {((uint8_t)((255 * brightness) / 100))}; int lcd_cmd = 0x51; lcd_cmd &= 0xff; @@ -96,7 +90,8 @@ private: esp_timer_handle_t power_save_timer_ = nullptr; Button boot_button_; - LcdDisplay* display_; + CustomLcdDisplay* display_; + CustomBacklight* backlight_; esp_io_expander_handle_t io_expander = NULL; void InitializeCodecI2c() { @@ -233,8 +228,10 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); esp_lcd_panel_disp_on_off(panel, true); - display_ = new CustomLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new CustomLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + backlight_ = new CustomBacklight(panel_io); + backlight_->RestoreBrightness(); } // 物联网初始化,添加对 AI 可见设备 @@ -243,6 +240,7 @@ private: thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("BoardControl")); thing_manager.AddThing(iot::CreateThing("Backlight")); + thing_manager.AddThing(iot::CreateThing("Battery")); } public: @@ -281,6 +279,10 @@ public: } return true; } + + virtual Backlight* GetBacklight() override { + return backlight_; + } }; DECLARE_BOARD(waveshare_amoled_1_8); diff --git a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc index ebf61e66..51bcf8fd 100644 --- a/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc +++ b/main/boards/esp32-s3-touch-lcd-1.46/esp32-s3-touch-lcd-1.46.cc @@ -27,8 +27,7 @@ LV_FONT_DECLARE(font_awesome_16_4); // 在waveshare_lcd_1_46类之前添加新的显示类 class CustomLcdDisplay : public SpiLcdDisplay { public: - static void rounder_event_cb(lv_event_t * e) - { + static void rounder_event_cb(lv_event_t * e) { lv_area_t * area = (lv_area_t *)lv_event_get_param(e); uint16_t x1 = area->x1; uint16_t x2 = area->x2; @@ -39,8 +38,6 @@ public: CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, - gpio_num_t backlight_pin, - bool backlight_output_invert, int width, int height, int offset_x, @@ -48,25 +45,20 @@ public: bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, backlight_pin, backlight_output_invert, + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_16_4, .icon_font = &font_awesome_16_4, .emoji_font = font_emoji_64_init(), }) { - DisplayLockGuard lock(this); - lv_display_add_event_cb(display_, rounder_event_cb, LV_EVENT_INVALIDATE_AREA, NULL); - - } }; class CustomBoard : public WifiBoard { private: - Button boot_button_; i2c_master_bus_handle_t i2c_bus_; esp_io_expander_handle_t io_expander = NULL; @@ -83,8 +75,7 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); } - void InitializeTca9554(void) - { + void InitializeTca9554(void) { esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); if(ret != ESP_OK) ESP_LOGE(TAG, "TCA9554 create returned error"); @@ -150,7 +141,7 @@ private: esp_lcd_panel_disp_on_off(panel, true); esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); - display_ = new CustomLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new CustomLcdDisplay(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } @@ -180,6 +171,7 @@ public: Initializespd2010Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -189,11 +181,14 @@ public: 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; + } }; DECLARE_BOARD(CustomBoard); diff --git a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc index 9ee5ad84..93d880fc 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85/esp32-s3-touch-lcd-1.85.cc @@ -42,8 +42,7 @@ private: ESP_ERROR_CHECK(i2c_new_master_bus(&i2c_bus_cfg, &i2c_bus_)); } - void InitializeTca9554(void) - { + void InitializeTca9554(void) { esp_err_t ret = esp_io_expander_new_i2c_tca9554(i2c_bus_, I2C_ADDRESS, &io_expander); if(ret != ESP_OK) ESP_LOGE(TAG, "TCA9554 create returned error"); @@ -111,7 +110,7 @@ private: 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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_16_4, @@ -146,6 +145,7 @@ public: Initializest77916Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -158,6 +158,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(CustomBoard); diff --git a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc index ecc18138..59f3d1b7 100644 --- a/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc +++ b/main/boards/esp32-s3-touch-lcd-1.85c/esp32-s3-touch-lcd-1.85c.cc @@ -110,7 +110,7 @@ private: 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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_16_4, @@ -145,6 +145,7 @@ public: Initializest77916Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -157,6 +158,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(CustomBoard); diff --git a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc index 7ba1e664..febb10ed 100644 --- a/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc +++ b/main/boards/esp32s3-korvo2-v3/esp32s3_korvo2_v3_board.cc @@ -152,7 +152,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, diff --git a/main/boards/kevin-box-2/kevin_box_board.cc b/main/boards/kevin-box-2/kevin_box_board.cc index 225b9e72..61a6d267 100644 --- a/main/boards/kevin-box-2/kevin_box_board.cc +++ b/main/boards/kevin-box-2/kevin_box_board.cc @@ -3,16 +3,16 @@ #include "display/ssd1306_display.h" #include "application.h" #include "button.h" -#include "config.h" -#include "axp2101.h" -#include "iot/thing_manager.h" #include "led/single_led.h" +#include "iot/thing_manager.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" #include "assets/lang_config.h" #include #include #include -#include #define TAG "KevinBoxBoard" @@ -27,52 +27,14 @@ private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - esp_timer_handle_t power_save_timer_ = nullptr; - bool show_low_power_warning_ = false; + PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - esp_timer_create_args_t power_save_timer_args = { - .callback = [](void *arg) { - auto board = static_cast(arg); - board->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "power_save_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - } - - void PowerSaveCheck() { - // 电池放电模式下,如果待机超过一定时间,则自动关机 - const int seconds_to_shutdown = 600; - static int seconds = 0; - auto& app = Application::GetInstance(); - if (app.GetDeviceState() != kDeviceStateIdle) { - seconds = 0; - return; - } - if (axp2101_->IsDischarging()) { - // 电量低于 10% 时,显示低电量警告 - if (!show_low_power_warning_ && axp2101_->GetBatteryLevel() <= 10) { - app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); - show_low_power_warning_ = true; - } - } else { - seconds = 0; - if (show_low_power_warning_) { - app.DismissAlert(); - show_low_power_warning_ = false; - } - return; - } - - seconds++; - if (seconds >= seconds_to_shutdown) { + power_save_timer_ = new PowerSaveTimer(240, -1, 600); + power_save_timer_->OnShutdownRequest([this]() { axp2101_->PowerOff(); - } + }); + power_save_timer_->SetEnabled(true); } void Enable4GModule() { @@ -123,6 +85,7 @@ private: void InitializeButtons() { boot_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); Application::GetInstance().StartListening(); }); boot_button_.OnPressUp([this]() { @@ -130,6 +93,7 @@ private: }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -140,11 +104,13 @@ private: }); volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -155,6 +121,7 @@ private: }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); @@ -182,7 +149,7 @@ public: InitializePowerSaveTimer(); InitializeIot(); } - + virtual Led* GetLed() override { static SingleLed led(BUILTIN_LED_GPIO); return &led; @@ -204,13 +171,36 @@ public: virtual bool GetBatteryLevel(int &level, bool& charging) override { static int last_level = 0; static bool last_charging = false; - level = axp2101_->GetBatteryLevel(); + charging = axp2101_->IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); + } + + level = axp2101_->GetBatteryLevel(); if (level != last_level || charging != last_charging) { last_level = level; last_charging = charging; ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); } + + static bool show_low_power_warning_ = false; + if (axp2101_->IsDischarging()) { + // 电量低于 10% 时,显示低电量警告 + if (!show_low_power_warning_ && level <= 10) { + auto& app = Application::GetInstance(); + app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); + show_low_power_warning_ = true; + } + power_save_timer_->SetEnabled(true); + } else { + if (show_low_power_warning_) { + auto& app = Application::GetInstance(); + app.DismissAlert(); + show_low_power_warning_ = false; + } + power_save_timer_->SetEnabled(false); + } return true; } }; diff --git a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc index 152d7ef0..9935f5d8 100644 --- a/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc +++ b/main/boards/kevin-sp-v3-dev/kevin-sp-v3_board.cc @@ -82,7 +82,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, @@ -109,6 +109,7 @@ public: InitializeButtons(); InitializeSt7789Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } @@ -123,10 +124,14 @@ public: 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; + } }; DECLARE_BOARD(KEVIN_SP_V3Board); diff --git a/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc b/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc index 7abb0813..4c8d48fc 100644 --- a/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc +++ b/main/boards/kevin-yuying-313lcd/kevin_yuying_313lcd.cc @@ -27,7 +27,6 @@ private: Button boot_button_; LcdDisplay* display_; - void InitializeRGB_GC9503V_Display() { ESP_LOGI(TAG, "Init GC9503V"); @@ -103,7 +102,7 @@ private: (esp_lcd_panel_reset(panel_handle)); (esp_lcd_panel_init(panel_handle)); - display_ = new RgbLcdDisplay(panel_io, panel_handle, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new RgbLcdDisplay(panel_io, panel_handle, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, { @@ -157,6 +156,7 @@ public: InitializeButtons(); InitializeIot(); InitializeRGB_GC9503V_Display(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -165,13 +165,15 @@ public: AUDIO_CODEC_PA_PIN, AUDIO_CODEC_ES8311_ADDR); return &audio_codec; } + virtual Display* GetDisplay() override { return display_; } - - // virtual Display* GetDisplayType() override { - // return display_; - // } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(Yuying_313lcd); diff --git a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc index b0c4bfa1..9841378e 100644 --- a/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc +++ b/main/boards/lichuang-c3-dev/lichuang_c3_dev_board.cc @@ -91,7 +91,7 @@ private: esp_lcd_panel_invert_color(panel, true); 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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_16_4, @@ -114,6 +114,7 @@ public: InitializeSt7789Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->SetBrightness(100); } virtual AudioCodec* GetAudioCodec() override { @@ -135,6 +136,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(LichuangC3DevBoard); diff --git a/main/boards/lichuang-dev/lichuang_dev_board.cc b/main/boards/lichuang-dev/lichuang_dev_board.cc index 9cd0949a..b8c15d75 100644 --- a/main/boards/lichuang-dev/lichuang_dev_board.cc +++ b/main/boards/lichuang-dev/lichuang_dev_board.cc @@ -117,7 +117,7 @@ private: esp_lcd_panel_invert_color(panel, true); 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, @@ -140,6 +140,7 @@ public: InitializeSt7789Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -162,6 +163,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(LichuangDevBoard); diff --git a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc index 7a1aea73..0bdc00a6 100644 --- a/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc +++ b/main/boards/lilygo-t-cameraplus-s3/lilygo-t-cameraplus-s3.cc @@ -164,7 +164,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); - display_ = new SpiLcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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_16_4, @@ -198,6 +198,7 @@ public: InitializeSt7789Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec *GetAudioCodec() override { @@ -217,6 +218,11 @@ public: virtual Display *GetDisplay() override{ return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } Cst816x *GetTouchpad() { return cst816d_; diff --git a/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc b/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc index 62062f48..c53404a3 100644 --- a/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc +++ b/main/boards/lilygo-t-circle-s3/lilygo-t-circle-s3.cc @@ -169,7 +169,7 @@ private: 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, { @@ -216,6 +216,7 @@ public: InitGc9d01nDisplay(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec *GetAudioCodec() override { @@ -235,6 +236,11 @@ public: virtual Display *GetDisplay() override{ return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } Cst816x *GetTouchpad() { return cst816d_; diff --git a/main/boards/m5stack-core-s3/m5stack_core_s3.cc b/main/boards/m5stack-core-s3/m5stack_core_s3.cc index 86bc6cd3..294ac2b5 100644 --- a/main/boards/m5stack-core-s3/m5stack_core_s3.cc +++ b/main/boards/m5stack-core-s3/m5stack_core_s3.cc @@ -262,7 +262,7 @@ private: 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, diff --git a/main/boards/magiclick-2p4/magiclick_2p4_board.cc b/main/boards/magiclick-2p4/magiclick_2p4_board.cc index 4a9273b5..46768889 100644 --- a/main/boards/magiclick-2p4/magiclick_2p4_board.cc +++ b/main/boards/magiclick-2p4/magiclick_2p4_board.cc @@ -24,10 +24,8 @@ LV_FONT_DECLARE(font_awesome_16_4); class NV3023Display : public SpiLcdDisplay { public: NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_16_4, .icon_font = &font_awesome_16_4, @@ -174,7 +172,7 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new NV3023Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new NV3023Display(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } @@ -196,6 +194,7 @@ public: InitializeSpi(); InitializeNv3023Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual Led* GetLed() override { @@ -213,6 +212,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(magiclick_2p4); diff --git a/main/boards/magiclick-2p5/magiclick_2p5_board.cc b/main/boards/magiclick-2p5/magiclick_2p5_board.cc index a5a45b29..4e279b4b 100644 --- a/main/boards/magiclick-2p5/magiclick_2p5_board.cc +++ b/main/boards/magiclick-2p5/magiclick_2p5_board.cc @@ -26,10 +26,8 @@ LV_FONT_DECLARE(font_awesome_16_4); class GC9107Display : public SpiLcdDisplay { public: GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_16_4, .icon_font = &font_awesome_16_4, @@ -218,7 +216,7 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel, true)); - display_ = new GC9107Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new GC9107Display(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } @@ -240,6 +238,7 @@ public: InitializeSpi(); InitializeGc9107Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual Led* GetLed() override { @@ -257,6 +256,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(magiclick_2p5); diff --git a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc index b37e4709..a754b35e 100644 --- a/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc +++ b/main/boards/magiclick-c3-v2/magiclick_c3_v2_board.cc @@ -6,18 +6,18 @@ #include "led/single_led.h" #include "iot/thing_manager.h" #include "config.h" -#include +#include "power_save_timer.h" +#include "font_awesome_symbols.h" + #include #include #include #include +#include #include #include #include -#include "font_awesome_symbols.h" #include -#include -#include #define TAG "magiclick_c3_v2" @@ -27,10 +27,8 @@ LV_FONT_DECLARE(font_awesome_16_4); class GC9107Display : public SpiLcdDisplay { public: GC9107Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_16_4, .icon_font = &font_awesome_16_4, @@ -100,80 +98,30 @@ private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; GC9107Display* display_; - esp_timer_handle_t power_save_timer_ = nullptr; - bool sleep_mode_enabled_ = false; - int power_save_ticks_ = 0; + PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - esp_timer_create_args_t power_save_timer_args = { - .callback = [](void *arg) { - auto board = static_cast(arg); - board->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "power_save_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - } - - void PowerSaveCheck() { - // 如果待机超过一定时间,则进入睡眠模式 - const int seconds_to_sleep = 120; - auto& app = Application::GetInstance(); - if (app.GetDeviceState() != kDeviceStateIdle) { - power_save_ticks_ = 0; - return; - } - - power_save_ticks_++; - if (power_save_ticks_ >= seconds_to_sleep) { - EnableSleepMode(true); - } - } - - void EnableSleepMode(bool enable) { - power_save_ticks_ = 0; - if (!sleep_mode_enabled_ && enable) { + power_save_timer_ = new PowerSaveTimer(160); + power_save_timer_->OnEnterSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); auto display = GetDisplay(); display->SetChatMessage("system", ""); display->SetEmotion("sleepy"); - // 如果是LCD,还可以调节屏幕亮度 - display->SetBacklight(1); + GetBacklight()->SetBrightness(10); auto codec = GetAudioCodec(); codec->EnableInput(false); - - esp_pm_config_t pm_config = { - .max_freq_mhz = 160, - .min_freq_mhz = 40, - .light_sleep_enable = true, - }; - esp_pm_configure(&pm_config); - sleep_mode_enabled_ = true; - } else if (sleep_mode_enabled_ && !enable) { - esp_pm_config_t pm_config = { - .max_freq_mhz = 160, - .min_freq_mhz = 160, - .light_sleep_enable = false, - }; - esp_pm_configure(&pm_config); - ESP_LOGI(TAG, "Disabling sleep mode"); - + }); + power_save_timer_->OnExitSleepMode([this]() { auto codec = GetAudioCodec(); codec->EnableInput(true); auto display = GetDisplay(); display->SetChatMessage("system", ""); - display->SetEmotion("happy"); - // 如果是LCD,还可以调节屏幕亮度 - display->SetBacklight(display->brightness()); - - sleep_mode_enabled_ = false; - } + display->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); } void InitializeCodecI2c() { @@ -201,7 +149,7 @@ private: } }); boot_button_.OnPressDown([this]() { - EnableSleepMode(false); + power_save_timer_->WakeUp(); Application::GetInstance().StartListening(); }); boot_button_.OnPressUp([this]() { @@ -255,8 +203,8 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); esp_lcd_panel_disp_on_off(panel, true); - display_ = new GC9107Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, - DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); + display_ = new GC9107Display(panel_io, panel, + DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } // 物联网初始化,添加对 AI 可见设备 @@ -277,6 +225,7 @@ public: InitializeSpi(); InitializeGc9107Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual Led* GetLed() override { @@ -294,6 +243,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(magiclick_c3_v2); diff --git a/main/boards/magiclick-c3/magiclick_c3_board.cc b/main/boards/magiclick-c3/magiclick_c3_board.cc index c94ecd9b..b2c8a735 100644 --- a/main/boards/magiclick-c3/magiclick_c3_board.cc +++ b/main/boards/magiclick-c3/magiclick_c3_board.cc @@ -6,16 +6,17 @@ #include "led/single_led.h" #include "iot/thing_manager.h" #include "config.h" -#include +#include "power_save_timer.h" +#include "font_awesome_symbols.h" + #include #include #include #include -#include "esp_lcd_nv3023.h" -#include "font_awesome_symbols.h" +#include +#include #include -#include -#include + #define TAG "magiclick_c3" LV_FONT_DECLARE(font_puhui_16_4); @@ -24,10 +25,8 @@ LV_FONT_DECLARE(font_awesome_16_4); class NV3023Display : public SpiLcdDisplay { public: NV3023Display(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_16_4, .icon_font = &font_awesome_16_4, @@ -63,80 +62,30 @@ private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; NV3023Display* display_; - esp_timer_handle_t power_save_timer_ = nullptr; - bool sleep_mode_enabled_ = false; - int power_save_ticks_ = 0; + PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - esp_timer_create_args_t power_save_timer_args = { - .callback = [](void *arg) { - auto board = static_cast(arg); - board->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "power_save_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - } - - void PowerSaveCheck() { - // 如果待机超过一定时间,则进入睡眠模式 - const int seconds_to_sleep = 120; - auto& app = Application::GetInstance(); - if (app.GetDeviceState() != kDeviceStateIdle) { - power_save_ticks_ = 0; - return; - } - - power_save_ticks_++; - if (power_save_ticks_ >= seconds_to_sleep) { - EnableSleepMode(true); - } - } - - void EnableSleepMode(bool enable) { - power_save_ticks_ = 0; - if (!sleep_mode_enabled_ && enable) { + power_save_timer_ = new PowerSaveTimer(160); + power_save_timer_->OnEnterSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); auto display = GetDisplay(); display->SetChatMessage("system", ""); display->SetEmotion("sleepy"); - // 如果是LCD,还可以调节屏幕亮度 - display->SetBacklight(1); + GetBacklight()->SetBrightness(10); auto codec = GetAudioCodec(); codec->EnableInput(false); - - esp_pm_config_t pm_config = { - .max_freq_mhz = 160, - .min_freq_mhz = 40, - .light_sleep_enable = true, - }; - esp_pm_configure(&pm_config); - sleep_mode_enabled_ = true; - } else if (sleep_mode_enabled_ && !enable) { - esp_pm_config_t pm_config = { - .max_freq_mhz = 160, - .min_freq_mhz = 160, - .light_sleep_enable = false, - }; - esp_pm_configure(&pm_config); - ESP_LOGI(TAG, "Disabling sleep mode"); - + }); + power_save_timer_->OnExitSleepMode([this]() { auto codec = GetAudioCodec(); codec->EnableInput(true); auto display = GetDisplay(); display->SetChatMessage("system", ""); - display->SetEmotion("happy"); - // 如果是LCD,还可以调节屏幕亮度 - display->SetBacklight(display->brightness()); - - sleep_mode_enabled_ = false; - } + display->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->SetEnabled(true); } void InitializeCodecI2c() { @@ -164,7 +113,7 @@ private: } }); boot_button_.OnPressDown([this]() { - EnableSleepMode(false); + power_save_timer_->WakeUp(); Application::GetInstance().StartListening(); }); boot_button_.OnPressUp([this]() { @@ -212,7 +161,7 @@ private: esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY); esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y); esp_lcd_panel_disp_on_off(panel, true); - display_ = new NV3023Display(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new NV3023Display(panel_io, panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } @@ -234,6 +183,7 @@ public: InitializeSpi(); InitializeNv3023Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual Led* GetLed() override { @@ -251,6 +201,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(magiclick_c3); diff --git a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc index beb5c798..7fad91b9 100644 --- a/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc +++ b/main/boards/movecall-moji-esp32s3/movecall_moji_esp32s3.cc @@ -30,8 +30,6 @@ class CustomLcdDisplay : public SpiLcdDisplay { public: CustomLcdDisplay(esp_lcd_panel_io_handle_t io_handle, esp_lcd_panel_handle_t panel_handle, - gpio_num_t backlight_pin, - bool backlight_output_invert, int width, int height, int offset_x, @@ -39,8 +37,7 @@ public: bool mirror_x, bool mirror_y, bool swap_xy) - : SpiLcdDisplay(io_handle, panel_handle, backlight_pin, backlight_output_invert, - width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + : SpiLcdDisplay(io_handle, panel_handle, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, { .text_font = &font_puhui_20_4, .icon_font = &font_awesome_20_4, @@ -109,7 +106,7 @@ private: ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_handle, true, false)); ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_handle, true)); - display_ = new SpiLcdDisplay(io_handle, panel_handle, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new SpiLcdDisplay(io_handle, panel_handle, 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, @@ -137,12 +134,12 @@ private: public: MovecallMojiESP32S3() : boot_button_(BOOT_BUTTON_GPIO) { - InitializeCodecI2c(); InitializeSpi(); InitializeGc9a01Display(); InitializeButtons(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual Led* GetLed() override { @@ -153,6 +150,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } virtual AudioCodec* GetAudioCodec() override { static Es8311AudioCodec audio_codec(codec_i2c_bus_, I2C_NUM_0, AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, diff --git a/main/boards/sensecap-watcher/sensecap_watcher.cc b/main/boards/sensecap-watcher/sensecap_watcher.cc index 23380978..d9cf7d06 100644 --- a/main/boards/sensecap-watcher/sensecap_watcher.cc +++ b/main/boards/sensecap-watcher/sensecap_watcher.cc @@ -169,7 +169,7 @@ private: esp_lcd_panel_disp_on_off(ret_panel, true); //TODO - display_ = new SpiLcdDisplay(ret_io, ret_panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + display_ = new SpiLcdDisplay(ret_io, ret_panel, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY, { .text_font = &font_puhui_30_4, @@ -194,6 +194,7 @@ public: InitializeButton(); Initializespd2010Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -216,6 +217,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } }; DECLARE_BOARD(SensecapWatcher); diff --git a/main/boards/taiji-pi-s3/taiji_pi_s3.cc b/main/boards/taiji-pi-s3/taiji_pi_s3.cc index b202123d..1f1c98f7 100644 --- a/main/boards/taiji-pi-s3/taiji_pi_s3.cc +++ b/main/boards/taiji-pi-s3/taiji_pi_s3.cc @@ -185,7 +185,7 @@ private: 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_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, + 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, @@ -215,6 +215,7 @@ public: Initializest77916Display(); InitializeIot(); InitializeMute(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -234,6 +235,11 @@ public: virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static PwmBacklight backlight(DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT); + return &backlight; + } Cst816s* GetTouchpad() { return cst816s_; diff --git a/main/boards/tudouzi/kevin_box_board.cc b/main/boards/tudouzi/kevin_box_board.cc index ec75ed16..e1528269 100644 --- a/main/boards/tudouzi/kevin_box_board.cc +++ b/main/boards/tudouzi/kevin_box_board.cc @@ -3,19 +3,17 @@ #include "display/ssd1306_display.h" #include "application.h" #include "button.h" -#include "config.h" -#include "axp2101.h" -#include "iot/thing_manager.h" #include "led/single_led.h" +#include "iot/thing_manager.h" +#include "config.h" +#include "power_save_timer.h" +#include "axp2101.h" #include "assets/lang_config.h" #include "font_awesome_symbols.h" #include #include #include -#include -#include -#include #define TAG "KevinBoxBoard" @@ -30,86 +28,32 @@ private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - esp_timer_handle_t power_save_timer_ = nullptr; - bool show_low_power_warning_ = false; - bool sleep_mode_enabled_ = false; - int power_save_ticks_ = 0; + PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - esp_timer_create_args_t power_save_timer_args = { - .callback = [](void *arg) { - auto board = static_cast(arg); - board->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "power_save_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - } - - void PowerSaveCheck() { - // 电池放电模式下,如果待机超过一定时间,则进入睡眠模式 - const int seconds_to_sleep = 120; - auto& app = Application::GetInstance(); - if (app.GetDeviceState() != kDeviceStateIdle) { - power_save_ticks_ = 0; - return; - } - - if (axp2101_->IsDischarging()) { - // 电量低于 10% 时,显示低电量警告 - if (!show_low_power_warning_ && axp2101_->GetBatteryLevel() <= 10) { - EnableSleepMode(false); - app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); - show_low_power_warning_ = true; - } - } else { - if (show_low_power_warning_) { - app.DismissAlert(); - show_low_power_warning_ = false; - } - } - - power_save_ticks_++; - if (power_save_ticks_ >= seconds_to_sleep) { - EnableSleepMode(true); - } - } - - void EnableSleepMode(bool enable) { - power_save_ticks_ = 0; - if (!sleep_mode_enabled_ && enable) { + power_save_timer_ = new PowerSaveTimer(240, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); + if (!modem_.Command("AT+MLPMCFG=\"sleepmode\",2,0")) { + ESP_LOGE(TAG, "Failed to enable module sleep mode"); + } + auto display = GetDisplay(); display->SetChatMessage("system", ""); display->SetEmotion("sleepy"); auto codec = GetAudioCodec(); codec->EnableInput(false); - - esp_pm_config_t pm_config = { - .max_freq_mhz = 240, - .min_freq_mhz = 40, - .light_sleep_enable = true, - }; - esp_pm_configure(&pm_config); - sleep_mode_enabled_ = true; - } else if (sleep_mode_enabled_ && !enable) { - esp_pm_config_t pm_config = { - .max_freq_mhz = 240, - .min_freq_mhz = 240, - .light_sleep_enable = false, - }; - esp_pm_configure(&pm_config); - ESP_LOGI(TAG, "Disabling sleep mode"); - + }); + power_save_timer_->OnExitSleepMode([this]() { auto codec = GetAudioCodec(); codec->EnableInput(true); - sleep_mode_enabled_ = false; - } + + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + }); + power_save_timer_->SetEnabled(true); } void Enable4GModule() { @@ -159,13 +103,8 @@ private: } void InitializeButtons() { - gpio_wakeup_enable(BOOT_BUTTON_GPIO, GPIO_INTR_LOW_LEVEL); - gpio_wakeup_enable(VOLUME_UP_BUTTON_GPIO, GPIO_INTR_LOW_LEVEL); - gpio_wakeup_enable(VOLUME_DOWN_BUTTON_GPIO, GPIO_INTR_LOW_LEVEL); - esp_sleep_enable_gpio_wakeup(); - boot_button_.OnPressDown([this]() { - EnableSleepMode(false); + power_save_timer_->WakeUp(); Application::GetInstance().StartListening(); }); boot_button_.OnPressUp([this]() { @@ -173,6 +112,7 @@ private: }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -183,11 +123,13 @@ private: }); volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -198,6 +140,7 @@ private: }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); @@ -247,13 +190,36 @@ public: virtual bool GetBatteryLevel(int &level, bool& charging) override { static int last_level = 0; static bool last_charging = false; - level = axp2101_->GetBatteryLevel(); + charging = axp2101_->IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); + } + + level = axp2101_->GetBatteryLevel(); if (level != last_level || charging != last_charging) { last_level = level; last_charging = charging; ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); } + + static bool show_low_power_warning_ = false; + if (axp2101_->IsDischarging()) { + // 电量低于 10% 时,显示低电量警告 + if (!show_low_power_warning_ && level <= 10) { + auto& app = Application::GetInstance(); + app.Alert(Lang::Strings::WARNING, Lang::Strings::BATTERY_LOW, "sad", Lang::Sounds::P3_VIBRATION); + show_low_power_warning_ = true; + } + power_save_timer_->SetEnabled(true); + } else { + if (show_low_power_warning_) { + auto& app = Application::GetInstance(); + app.DismissAlert(); + show_low_power_warning_ = false; + } + power_save_timer_->SetEnabled(false); + } return true; } }; diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc index f2663eca..4360eae3 100644 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc +++ b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi-cube-0.96oled-ml307.cc @@ -1,6 +1,6 @@ #include "ml307_board.h" #include "audio_codecs/no_audio_codec.h" -#include "xingzhi_ssd1306_display.h" +#include "display/ssd1306_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -8,7 +8,11 @@ #include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" +#include "power_save_timer.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" +#include +#include #include #include @@ -17,12 +21,87 @@ LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); + +class CustomDisplay : public Ssd1306Display { +private: + lv_obj_t* low_battery_popup_ = nullptr; + +public: + CustomDisplay(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, + const lv_font_t* text_font, const lv_font_t* icon_font) + : Ssd1306Display(i2c_master_handle, width, height, mirror_x, mirror_y, text_font, icon_font) { + } + + void ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + + if (low_battery_popup_ == nullptr) { + // 创建弹出窗口 + low_battery_popup_ = lv_obj_create(lv_scr_act()); + lv_obj_set_size(low_battery_popup_, 120, 30); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + // 创建提示文本标签 + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + + // 显示弹出窗口 + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + + void HideLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } +}; + + class XINGZHI_CUBE_0_96OLED_ML307 : public Ml307Board { private: i2c_master_bus_handle_t display_i2c_bus_; Button boot_button_; Button volume_up_button_; Button volume_down_button_; + CustomDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + }); + power_save_timer_->OnExitSleepMode([this]() { + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } void InitializeDisplayI2c() { i2c_master_bus_config_t bus_config = { @@ -42,11 +121,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); app.ToggleChatState(); }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -57,11 +138,13 @@ private: }); volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -72,6 +155,7 @@ private: }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); @@ -80,13 +164,16 @@ private: void InitializeIot() { auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); + thing_manager.AddThing(iot::CreateThing("Battery")); } public: XINGZHI_CUBE_0_96OLED_ML307() : Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096), boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), + power_manager_(GPIO_NUM_38) { + InitializePowerSaveTimer(); InitializeDisplayI2c(); InitializeButtons(); InitializeIot(); @@ -104,10 +191,53 @@ public: } virtual Display* GetDisplay() override { - static XINGZHI_Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + static CustomDisplay display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, &font_puhui_14_1, &font_awesome_14_1); return &display; } + + virtual bool GetBatteryLevel(int& level, bool& charging) override { + static int last_level = 0; + static bool last_charging = false; + + charging = power_manager_.IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); + } + + level = power_manager_.ReadBatteryLevel(charging != last_charging); + if (level != last_level || charging != last_charging) { + last_level = level; + last_charging = charging; + ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + + static bool show_low_power_warning_ = false; + if (power_manager_.IsBatteryLevelSteady()) { + if (!charging) { + // 电量低于 15% 时,显示低电量警告 + if (!show_low_power_warning_ && level <= 15) { + display_->ShowLowBatteryPopup(); + show_low_power_warning_ = true; + } + power_save_timer_->SetEnabled(true); + } else { + if (show_low_power_warning_) { + display_->HideLowBatteryPopup(); + show_low_power_warning_ = false; + } + power_save_timer_->SetEnabled(false); + } + } + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + Ml307Board::SetPowerSaveMode(enabled); + } }; DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_ML307); diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc deleted file mode 100644 index f998f3b4..00000000 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.cc +++ /dev/null @@ -1,654 +0,0 @@ -#include "xingzhi_ssd1306_display.h" -#include "font_awesome_symbols.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include "board.h" -#include - -#include "esp_adc/adc_oneshot.h" - -#include "button.h" -#include -#include "config.h" -#include "settings.h" -#include "esp_sleep.h" -#include "application.h" -#include "driver/rtc_io.h" -#include "led/single_led.h" -#include "assets/lang_config.h" - -#define TAG "XINGZHI_Ssd1306Display" - -LV_FONT_DECLARE(font_awesome_30_1); - -XINGZHI_Ssd1306Display::XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font) - : text_font_(text_font), icon_font_(icon_font), last_interaction_time_(esp_timer_get_time()), boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ - width_ = width; - height_ = height; - - // 创建充电检测定时器 - esp_timer_create_args_t charging_timer_args = { - .callback = &XINGZHI_Ssd1306Display::ChargingTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "charging_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); - - // 创建电量检测定时器 - esp_timer_create_args_t battery_timer_args = { - .callback = &XINGZHI_Ssd1306Display::BatteryTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); - - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - boot_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_up_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_down_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - ESP_LOGI(TAG, "Initialize LVGL"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - lvgl_port_init(&port_cfg); - - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(height_), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Adding LCD screen"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * height_), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = true, - .rotation = { - .swap_xy = false, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, - .full_refresh = 0, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (height_ == 64) { - SetupUI_128x64(); - } else { - SetupUI_128x32(); - } - StartChargingTimer(); - StartBatteryTimer(); -} - -XINGZHI_Ssd1306Display::~XINGZHI_Ssd1306Display() { - if (content_ != nullptr) { - lv_obj_del(content_); - } - if (status_bar_ != nullptr) { - lv_obj_del(status_bar_); - } - if (side_bar_ != nullptr) { - lv_obj_del(side_bar_); - } - if (container_ != nullptr) { - lv_obj_del(container_); - } - - if (panel_ != nullptr) { - esp_lcd_panel_del(panel_); - } - if (panel_io_ != nullptr) { - esp_lcd_panel_io_del(panel_io_); - } - lvgl_port_deinit(); -} - -void XINGZHI_Ssd1306Display::UpdateInteractionTime() { - last_interaction_time_ = esp_timer_get_time(); - if (is_light_sleep_) { - // 从浅睡眠中唤醒,打开显示 - esp_lcd_panel_disp_on_off(panel_, true); - is_light_sleep_ = false; - } -} - -void XINGZHI_Ssd1306Display::CheckSleepState() { - int64_t current_time = esp_timer_get_time(); - int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 - - int charging_level = gpio_get_level(charging_pin_); - bool is_charging = (charging_level == 1); - - if (is_charging) { - // 正在充电,不进入睡眠 - return; - } - - if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { - is_light_sleep_ = true; - // 关闭显示 - esp_lcd_panel_disp_on_off(panel_, false); - } else if (elapsed_time >= 300 && is_light_sleep_) { - is_deep_sleep_ = true; // 深睡眠 - is_light_sleep_ = false; - // 初始化GPIO 21为RTC GPIO,关闭4g模块 - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_deep_sleep_start(); - } -} - -bool XINGZHI_Ssd1306Display::Lock(int timeout_ms) { - return lvgl_port_lock(timeout_ms); -} - -void XINGZHI_Ssd1306Display::Unlock() { - lvgl_port_unlock(); -} - -uint16_t XINGZHI_Ssd1306Display::ReadBatteryLevel() { - adc_oneshot_unit_handle_t adc_handle; - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - // 初始化 ADC 单元 - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - // 配置 ADC 通道 - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); - int adc_value; - // 读取 ADC 值 - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); - adc_oneshot_del_unit(adc_handle); - return adc_value; -} - -void XINGZHI_Ssd1306Display::BatteryTimerCallback(void* arg) { - XINGZHI_Ssd1306Display* display = static_cast(arg); - uint16_t adc_value = display->ReadBatteryLevel(); - if (display->first_battery_invert_) { - display->adc_samp_interval = 180000000; // adc值采样的时间间隔 - // 停止当前定时器 - esp_timer_stop(display->battery_timer_); - // 重新启动定时器,使用新的时间间隔 - ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); - } - ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); - ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); - display->adc_values.push_back(adc_value); - display->adc_count++; - - if (display->adc_count >= 1) { - uint32_t sum = 0; - for (uint16_t value : display->adc_values) { - sum += value; - } - display->average_adc = sum / display->adc_values.size(); - display->first_battery_invert_ = true; - } -} - -void XINGZHI_Ssd1306Display::StartChargingTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); -} - -void XINGZHI_Ssd1306Display::StartBatteryTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); -} - -void XINGZHI_Ssd1306Display::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { - return; - } - if (content_right_ == nullptr) { - lv_label_set_text(chat_message_label_, content); - } else { - if (content == nullptr || content[0] == '\0') { - lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - } else { - lv_label_set_text(chat_message_label_, content); - lv_obj_clear_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - } - } -} - -void XINGZHI_Ssd1306Display::SetupUI_128x64() { - DisplayLockGuard lock(this); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, 16); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_radius(status_bar_, 0, 0); - - /* Content */ - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0); - - // 创建左侧固定宽度的容器 - content_left_ = lv_obj_create(content_); - lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素 - lv_obj_set_style_pad_all(content_left_, 0, 0); - lv_obj_set_style_border_width(content_left_, 0, 0); - - emotion_label_ = lv_label_create(content_left_); - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - lv_obj_center(emotion_label_); - lv_obj_set_style_pad_top(emotion_label_, 8, 0); - - // 创建右侧可扩展的容器 - content_right_ = lv_obj_create(content_); - lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(content_right_, 0, 0); - lv_obj_set_style_border_width(content_right_, 0, 0); - lv_obj_set_flex_grow(content_right_, 1); - lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - - chat_message_label_ = lv_label_create(content_right_); - lv_label_set_text(chat_message_label_, ""); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0); - lv_obj_set_width(chat_message_label_, width_ - 32); - lv_obj_set_style_pad_top(chat_message_label_, 14, 0); - - // 延迟一定的时间后开始滚动字幕 - static lv_anim_t a; - lv_anim_init(&a); - lv_anim_set_delay(&a, 1000); - lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); - lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN); - lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN); - - /* Status bar */ - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_label_set_text(notification_label_, ""); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); - - /* 充电状态标签 */ - charging_label_ = lv_label_create(status_bar_); - lv_obj_set_style_text_font(charging_label_, icon_font_, 0); - lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); - lv_obj_set_flex_grow(charging_label_, 0); - lv_label_set_text(charging_label_, ""); - - // 检查充电状态 - int charging_level = gpio_get_level(charging_pin_); - if (charging_level == 1) { - lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } -} - -void XINGZHI_Ssd1306Display::SetupUI_128x32() { - DisplayLockGuard lock(this); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_column(container_, 0, 0); - - /* Left side */ - side_bar_ = lv_obj_create(container_); - lv_obj_set_flex_grow(side_bar_, 1); - lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(side_bar_, 0, 0); - lv_obj_set_style_border_width(side_bar_, 0, 0); - lv_obj_set_style_radius(side_bar_, 0, 0); - lv_obj_set_style_pad_row(side_bar_, 0, 0); - - /* Emotion label on the right side */ - content_ = lv_obj_create(container_); - lv_obj_set_size(content_, 32, 32); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_radius(content_, 0, 0); - - emotion_label_ = lv_label_create(content_); - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - lv_obj_center(emotion_label_); - - /* Status bar */ - status_bar_ = lv_obj_create(side_bar_); - lv_obj_set_size(status_bar_, LV_SIZE_CONTENT, 16); - lv_obj_set_style_radius(status_bar_, 0, 0); - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_style_pad_left(status_label_, 2, 0); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - - notification_label_ = lv_label_create(status_bar_); - lv_label_set_text(notification_label_, ""); - lv_obj_set_style_pad_left(notification_label_, 2, 0); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - chat_message_label_ = lv_label_create(side_bar_); - lv_obj_set_flex_grow(chat_message_label_, 1); - lv_obj_set_width(chat_message_label_, width_ - 32); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_label_set_text(chat_message_label_, ""); - - /* 充电状态标签 */ - charging_label_ = lv_label_create(status_bar_); - lv_obj_set_style_text_font(charging_label_, icon_font_, 0); - lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); - lv_obj_set_flex_grow(charging_label_, 0); - lv_label_set_text(charging_label_, ""); - - // 检查充电状态 - int charging_level = gpio_get_level(charging_pin_); - if (charging_level == 1) { - lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } -} - -void XINGZHI_Ssd1306Display::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { - DisplayLockGuard lock(this); - - // 未充电时,显示电池图标 - if (charging_label_ != nullptr) { - lv_label_set_text(charging_label_, ""); - } - - uint8_t battery_level = 0; - if (average_adc < 2000) { - battery_level = 0; - // 显示电量过低提示窗口 - ShowLowBatteryPopup(); - } else if (average_adc >= 2000 && average_adc < 2100) { - battery_level = 1; - // 如果电量回升,隐藏提示窗口 - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2100 && average_adc < 2200) { - battery_level = 2; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2200 && average_adc < 2300) { - battery_level = 3; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else { - battery_level = 4; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } - - const char* battery_icon; - switch (battery_level) { - case 0: - battery_icon = FONT_AWESOME_BATTERY_EMPTY; - break; - case 1: - battery_icon = FONT_AWESOME_BATTERY_1; - break; - case 2: - battery_icon = FONT_AWESOME_BATTERY_2; - break; - case 3: - battery_icon = FONT_AWESOME_BATTERY_3; - break; - case 4: - battery_icon = FONT_AWESOME_BATTERY_FULL; - break; - default: - battery_icon = FONT_AWESOME_BATTERY_SLASH; - break; - } - - if (battery_label_ != nullptr) { - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); - lv_label_set_text(battery_label_, battery_icon); - } -} - -void XINGZHI_Ssd1306Display::ChargingTimerCallback(void* arg) { - XINGZHI_Ssd1306Display* display = static_cast(arg); - DisplayLockGuard lock(display); - - // 检查充电状态 - int charging_level = gpio_get_level(display->charging_pin_); - bool is_charging = (charging_level == 1); - display->OnStateChanged();//检测当前对对话状态 - // 检查电池是否充满,adc值超过2430,判定为充满 - bool is_battery_full = 0; - if (display->average_adc > 2430) - { - is_battery_full = 1; - } - if (is_charging) { - // 正在充电,更新交互时间,防止进入睡眠 - display->UpdateInteractionTime(); - if (is_battery_full) { - if (display->charging_label_ != nullptr) { - lv_label_set_text(display->charging_label_, ""); - } - if (display->battery_label_ != nullptr) { - lv_obj_set_style_text_font(display->battery_label_, display->icon_font_, 0); - lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); - } - } else { - if (display->charging_label_ != nullptr) { - lv_obj_set_style_text_font(display->charging_label_, display->icon_font_, 0); - lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } - if (display->battery_label_ != nullptr) { - lv_label_set_text(display->battery_label_, ""); - } - } - // 如果正在充电,隐藏电量过低提示窗口 - if (display->low_battery_popup_ != nullptr) { - lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - display->was_charging = true; // 更新上一次的充电状态为正在充电 - } else { - if (display->was_charging) { - // 充电状态从充电变为未充电,立即读取并更新电池电量 - display->average_adc = display->ReadBatteryLevel(); - } else { - // 一直处于未充电状态,正常显示电池图标 - if (display->charging_label_ != nullptr) { - if (!display->first_battery_invert_) { - display->average_adc = display->ReadBatteryLevel(); - } - display->UpdateBatteryAndChargingDisplay(display->average_adc); - // 清空数组和计数器 - display->adc_values.clear(); - display->adc_count = 0; - } - } - display->was_charging = false; // 更新上一次的充电状态为未充电 - } - // 检查睡眠状态 - display->CheckSleepState(); -} - -void XINGZHI_Ssd1306Display::ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - - if (low_battery_popup_ == nullptr) { - // 创建弹出窗口 - low_battery_popup_ = lv_obj_create(lv_scr_act()); - lv_obj_set_size(low_battery_popup_, 120, 30); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - // 创建提示文本标签 - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - - // 显示弹出窗口 - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); -} - -void XINGZHI_Ssd1306Display::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - if (device_state != kDeviceStateIdle && !this->was_charging) { - UpdateInteractionTime(); - } -} - diff --git a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h b/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h deleted file mode 100644 index 69976516..00000000 --- a/main/boards/xingzhi-cube-0.96oled-ml307/xingzhi_ssd1306_display.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef SSD1306_DISPLAY_H -#define SSD1306_DISPLAY_H - -#include "display/display.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -// 新增的头文件包含语句 -#include -#include "button.h" - - -class XINGZHI_Ssd1306Display : public Display { -private: - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - lv_obj_t* status_bar_ = nullptr; - lv_obj_t* content_ = nullptr; - lv_obj_t* content_left_ = nullptr; - lv_obj_t* content_right_ = nullptr; - lv_obj_t* container_ = nullptr; - lv_obj_t* side_bar_ = nullptr; - - const lv_font_t* text_font_ = nullptr; - const lv_font_t* icon_font_ = nullptr; - - DisplayFonts fonts_; - - lv_obj_t* charging_label_ = nullptr; // 充电状态标签 - lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 - lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 - int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 - uint16_t average_adc = 0; // adc平均值 - esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 - esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 - gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 - std::vector adc_values; // 用于存储读取的ADC值 - int adc_count = 0; // 记录已检测的ADC值数量 - bool was_charging = false; // 上一次的充电状态 - bool first_battery_invert_ = false; //首次上电直接检测电量 - void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 - uint16_t ReadBatteryLevel(); // 读取电量的方法 - - int64_t last_interaction_time_; // 上次交互时间 - bool is_light_sleep_ = false; // 浅睡眠 - bool is_deep_sleep_ = false; // 深睡眠 - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - void SetupUI_128x64(); - void SetupUI_128x32(); - -public: - XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font); - ~XINGZHI_Ssd1306Display(); - - - static void ChargingTimerCallback(void* arg); - static void BatteryTimerCallback(void* arg); - void StartChargingTimer(); - void StartBatteryTimer(); - void UpdateBatteryAndChargingDisplay(uint16_t average_adc); - void OnStateChanged(); - - void UpdateInteractionTime(); - void CheckSleepState(); - - - virtual void SetChatMessage(const char* role, const char* content) override; -}; - -#endif // SSD1306_DISPLAY_H diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc index 10ec1ae7..5f5b89d7 100644 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc +++ b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi-cube-0.96oled-wifi.cc @@ -1,6 +1,6 @@ #include "wifi_board.h" #include "audio_codecs/no_audio_codec.h" -#include "xingzhi_ssd1306_display.h" +#include "display/ssd1306_display.h" #include "system_reset.h" #include "application.h" #include "button.h" @@ -8,8 +8,13 @@ #include "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" +#include "power_save_timer.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" #include + +#include +#include #include #include @@ -18,12 +23,87 @@ LV_FONT_DECLARE(font_puhui_14_1); LV_FONT_DECLARE(font_awesome_14_1); + +class CustomDisplay : public Ssd1306Display { +private: + lv_obj_t* low_battery_popup_ = nullptr; + +public: + CustomDisplay(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, + const lv_font_t* text_font, const lv_font_t* icon_font) + : Ssd1306Display(i2c_master_handle, width, height, mirror_x, mirror_y, text_font, icon_font) { + } + + void ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + + if (low_battery_popup_ == nullptr) { + // 创建弹出窗口 + low_battery_popup_ = lv_obj_create(lv_scr_act()); + lv_obj_set_size(low_battery_popup_, 120, 30); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + // 创建提示文本标签 + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + + // 显示弹出窗口 + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + + void HideLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } +}; + + class XINGZHI_CUBE_0_96OLED_WIFI : public WifiBoard { private: i2c_master_bus_handle_t display_i2c_bus_; Button boot_button_; Button volume_up_button_; Button volume_down_button_; + CustomDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + }); + power_save_timer_->OnExitSleepMode([this]() { + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } void InitializeDisplayI2c() { i2c_master_bus_config_t bus_config = { @@ -43,6 +123,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); @@ -51,6 +132,7 @@ private: }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -61,11 +143,13 @@ private: }); volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -76,6 +160,7 @@ private: }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); @@ -84,13 +169,16 @@ private: void InitializeIot() { auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); + thing_manager.AddThing(iot::CreateThing("Battery")); } public: XINGZHI_CUBE_0_96OLED_WIFI() : boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), + power_manager_(GPIO_NUM_38) { + InitializePowerSaveTimer(); InitializeDisplayI2c(); InitializeButtons(); InitializeIot(); @@ -108,10 +196,53 @@ public: } virtual Display* GetDisplay() override { - static XINGZHI_Ssd1306Display display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, + static CustomDisplay display(display_i2c_bus_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, &font_puhui_14_1, &font_awesome_14_1); return &display; } + + virtual bool GetBatteryLevel(int& level, bool& charging) override { + static int last_level = 0; + static bool last_charging = false; + + charging = power_manager_.IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); + } + + level = power_manager_.ReadBatteryLevel(charging != last_charging); + if (level != last_level || charging != last_charging) { + last_level = level; + last_charging = charging; + ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + + static bool show_low_power_warning_ = false; + if (power_manager_.IsBatteryLevelSteady()) { + if (!charging) { + // 电量低于 15% 时,显示低电量警告 + if (!show_low_power_warning_ && level <= 15) { + display_->ShowLowBatteryPopup(); + show_low_power_warning_ = true; + } + power_save_timer_->SetEnabled(true); + } else { + if (show_low_power_warning_) { + display_->HideLowBatteryPopup(); + show_low_power_warning_ = false; + } + power_save_timer_->SetEnabled(false); + } + } + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } }; DECLARE_BOARD(XINGZHI_CUBE_0_96OLED_WIFI); diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc deleted file mode 100644 index f998f3b4..00000000 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.cc +++ /dev/null @@ -1,654 +0,0 @@ -#include "xingzhi_ssd1306_display.h" -#include "font_awesome_symbols.h" - -#include -#include -#include -#include -#include - -#include -#include -#include -#include "board.h" -#include - -#include "esp_adc/adc_oneshot.h" - -#include "button.h" -#include -#include "config.h" -#include "settings.h" -#include "esp_sleep.h" -#include "application.h" -#include "driver/rtc_io.h" -#include "led/single_led.h" -#include "assets/lang_config.h" - -#define TAG "XINGZHI_Ssd1306Display" - -LV_FONT_DECLARE(font_awesome_30_1); - -XINGZHI_Ssd1306Display::XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font) - : text_font_(text_font), icon_font_(icon_font), last_interaction_time_(esp_timer_get_time()), boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ - width_ = width; - height_ = height; - - // 创建充电检测定时器 - esp_timer_create_args_t charging_timer_args = { - .callback = &XINGZHI_Ssd1306Display::ChargingTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "charging_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); - - // 创建电量检测定时器 - esp_timer_create_args_t battery_timer_args = { - .callback = &XINGZHI_Ssd1306Display::BatteryTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); - - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - boot_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_up_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_down_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - ESP_LOGI(TAG, "Initialize LVGL"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - lvgl_port_init(&port_cfg); - - // SSD1306 config - esp_lcd_panel_io_i2c_config_t io_config = { - .dev_addr = 0x3C, - .on_color_trans_done = nullptr, - .user_ctx = nullptr, - .control_phase_bytes = 1, - .dc_bit_offset = 6, - .lcd_cmd_bits = 8, - .lcd_param_bits = 8, - .flags = { - .dc_low_on_data = 0, - .disable_control_phase = 0, - }, - .scl_speed_hz = 400 * 1000, - }; - - ESP_ERROR_CHECK(esp_lcd_new_panel_io_i2c_v2((i2c_master_bus_t*)i2c_master_handle, &io_config, &panel_io_)); - - ESP_LOGI(TAG, "Install SSD1306 driver"); - esp_lcd_panel_dev_config_t panel_config = {}; - panel_config.reset_gpio_num = -1; - panel_config.bits_per_pixel = 1; - - esp_lcd_panel_ssd1306_config_t ssd1306_config = { - .height = static_cast(height_), - }; - panel_config.vendor_config = &ssd1306_config; - - ESP_ERROR_CHECK(esp_lcd_new_panel_ssd1306(panel_io_, &panel_config, &panel_)); - ESP_LOGI(TAG, "SSD1306 driver installed"); - - // Reset the display - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); - if (esp_lcd_panel_init(panel_) != ESP_OK) { - ESP_LOGE(TAG, "Failed to initialize display"); - return; - } - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Adding LCD screen"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * height_), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = true, - .rotation = { - .swap_xy = false, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, - .full_refresh = 0, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (height_ == 64) { - SetupUI_128x64(); - } else { - SetupUI_128x32(); - } - StartChargingTimer(); - StartBatteryTimer(); -} - -XINGZHI_Ssd1306Display::~XINGZHI_Ssd1306Display() { - if (content_ != nullptr) { - lv_obj_del(content_); - } - if (status_bar_ != nullptr) { - lv_obj_del(status_bar_); - } - if (side_bar_ != nullptr) { - lv_obj_del(side_bar_); - } - if (container_ != nullptr) { - lv_obj_del(container_); - } - - if (panel_ != nullptr) { - esp_lcd_panel_del(panel_); - } - if (panel_io_ != nullptr) { - esp_lcd_panel_io_del(panel_io_); - } - lvgl_port_deinit(); -} - -void XINGZHI_Ssd1306Display::UpdateInteractionTime() { - last_interaction_time_ = esp_timer_get_time(); - if (is_light_sleep_) { - // 从浅睡眠中唤醒,打开显示 - esp_lcd_panel_disp_on_off(panel_, true); - is_light_sleep_ = false; - } -} - -void XINGZHI_Ssd1306Display::CheckSleepState() { - int64_t current_time = esp_timer_get_time(); - int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 - - int charging_level = gpio_get_level(charging_pin_); - bool is_charging = (charging_level == 1); - - if (is_charging) { - // 正在充电,不进入睡眠 - return; - } - - if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { - is_light_sleep_ = true; - // 关闭显示 - esp_lcd_panel_disp_on_off(panel_, false); - } else if (elapsed_time >= 300 && is_light_sleep_) { - is_deep_sleep_ = true; // 深睡眠 - is_light_sleep_ = false; - // 初始化GPIO 21为RTC GPIO,关闭4g模块 - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_deep_sleep_start(); - } -} - -bool XINGZHI_Ssd1306Display::Lock(int timeout_ms) { - return lvgl_port_lock(timeout_ms); -} - -void XINGZHI_Ssd1306Display::Unlock() { - lvgl_port_unlock(); -} - -uint16_t XINGZHI_Ssd1306Display::ReadBatteryLevel() { - adc_oneshot_unit_handle_t adc_handle; - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - // 初始化 ADC 单元 - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - // 配置 ADC 通道 - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); - int adc_value; - // 读取 ADC 值 - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); - adc_oneshot_del_unit(adc_handle); - return adc_value; -} - -void XINGZHI_Ssd1306Display::BatteryTimerCallback(void* arg) { - XINGZHI_Ssd1306Display* display = static_cast(arg); - uint16_t adc_value = display->ReadBatteryLevel(); - if (display->first_battery_invert_) { - display->adc_samp_interval = 180000000; // adc值采样的时间间隔 - // 停止当前定时器 - esp_timer_stop(display->battery_timer_); - // 重新启动定时器,使用新的时间间隔 - ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); - } - ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); - ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); - display->adc_values.push_back(adc_value); - display->adc_count++; - - if (display->adc_count >= 1) { - uint32_t sum = 0; - for (uint16_t value : display->adc_values) { - sum += value; - } - display->average_adc = sum / display->adc_values.size(); - display->first_battery_invert_ = true; - } -} - -void XINGZHI_Ssd1306Display::StartChargingTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); -} - -void XINGZHI_Ssd1306Display::StartBatteryTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); -} - -void XINGZHI_Ssd1306Display::SetChatMessage(const char* role, const char* content) { - DisplayLockGuard lock(this); - if (chat_message_label_ == nullptr) { - return; - } - if (content_right_ == nullptr) { - lv_label_set_text(chat_message_label_, content); - } else { - if (content == nullptr || content[0] == '\0') { - lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - } else { - lv_label_set_text(chat_message_label_, content); - lv_obj_clear_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - } - } -} - -void XINGZHI_Ssd1306Display::SetupUI_128x64() { - DisplayLockGuard lock(this); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, 16); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_radius(status_bar_, 0, 0); - - /* Content */ - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_flex_main_place(content_, LV_FLEX_ALIGN_CENTER, 0); - - // 创建左侧固定宽度的容器 - content_left_ = lv_obj_create(content_); - lv_obj_set_size(content_left_, 32, LV_SIZE_CONTENT); // 固定宽度32像素 - lv_obj_set_style_pad_all(content_left_, 0, 0); - lv_obj_set_style_border_width(content_left_, 0, 0); - - emotion_label_ = lv_label_create(content_left_); - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - lv_obj_center(emotion_label_); - lv_obj_set_style_pad_top(emotion_label_, 8, 0); - - // 创建右侧可扩展的容器 - content_right_ = lv_obj_create(content_); - lv_obj_set_size(content_right_, LV_SIZE_CONTENT, LV_SIZE_CONTENT); - lv_obj_set_style_pad_all(content_right_, 0, 0); - lv_obj_set_style_border_width(content_right_, 0, 0); - lv_obj_set_flex_grow(content_right_, 1); - lv_obj_add_flag(content_right_, LV_OBJ_FLAG_HIDDEN); - - chat_message_label_ = lv_label_create(content_right_); - lv_label_set_text(chat_message_label_, ""); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_LEFT, 0); - lv_obj_set_width(chat_message_label_, width_ - 32); - lv_obj_set_style_pad_top(chat_message_label_, 14, 0); - - // 延迟一定的时间后开始滚动字幕 - static lv_anim_t a; - lv_anim_init(&a); - lv_anim_set_delay(&a, 1000); - lv_anim_set_repeat_count(&a, LV_ANIM_REPEAT_INFINITE); - lv_obj_set_style_anim(chat_message_label_, &a, LV_PART_MAIN); - lv_obj_set_style_anim_duration(chat_message_label_, lv_anim_speed_clamped(60, 300, 60000), LV_PART_MAIN); - - /* Status bar */ - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_label_set_text(notification_label_, ""); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); - - /* 充电状态标签 */ - charging_label_ = lv_label_create(status_bar_); - lv_obj_set_style_text_font(charging_label_, icon_font_, 0); - lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); - lv_obj_set_flex_grow(charging_label_, 0); - lv_label_set_text(charging_label_, ""); - - // 检查充电状态 - int charging_level = gpio_get_level(charging_pin_); - if (charging_level == 1) { - lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } -} - -void XINGZHI_Ssd1306Display::SetupUI_128x32() { - DisplayLockGuard lock(this); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, text_font_, 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_column(container_, 0, 0); - - /* Left side */ - side_bar_ = lv_obj_create(container_); - lv_obj_set_flex_grow(side_bar_, 1); - lv_obj_set_flex_flow(side_bar_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(side_bar_, 0, 0); - lv_obj_set_style_border_width(side_bar_, 0, 0); - lv_obj_set_style_radius(side_bar_, 0, 0); - lv_obj_set_style_pad_row(side_bar_, 0, 0); - - /* Emotion label on the right side */ - content_ = lv_obj_create(container_); - lv_obj_set_size(content_, 32, 32); - lv_obj_set_style_pad_all(content_, 0, 0); - lv_obj_set_style_border_width(content_, 0, 0); - lv_obj_set_style_radius(content_, 0, 0); - - emotion_label_ = lv_label_create(content_); - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_1, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - lv_obj_center(emotion_label_); - - /* Status bar */ - status_bar_ = lv_obj_create(side_bar_); - lv_obj_set_size(status_bar_, LV_SIZE_CONTENT, 16); - lv_obj_set_style_radius(status_bar_, 0, 0); - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, icon_font_, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, icon_font_, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_style_pad_left(status_label_, 2, 0); - lv_label_set_text(status_label_, Lang::Strings::INITIALIZING); - - notification_label_ = lv_label_create(status_bar_); - lv_label_set_text(notification_label_, ""); - lv_obj_set_style_pad_left(notification_label_, 2, 0); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - chat_message_label_ = lv_label_create(side_bar_); - lv_obj_set_flex_grow(chat_message_label_, 1); - lv_obj_set_width(chat_message_label_, width_ - 32); - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_label_set_text(chat_message_label_, ""); - - /* 充电状态标签 */ - charging_label_ = lv_label_create(status_bar_); - lv_obj_set_style_text_font(charging_label_, icon_font_, 0); - lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); - lv_obj_set_flex_grow(charging_label_, 0); - lv_label_set_text(charging_label_, ""); - - // 检查充电状态 - int charging_level = gpio_get_level(charging_pin_); - if (charging_level == 1) { - lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } -} - -void XINGZHI_Ssd1306Display::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { - DisplayLockGuard lock(this); - - // 未充电时,显示电池图标 - if (charging_label_ != nullptr) { - lv_label_set_text(charging_label_, ""); - } - - uint8_t battery_level = 0; - if (average_adc < 2000) { - battery_level = 0; - // 显示电量过低提示窗口 - ShowLowBatteryPopup(); - } else if (average_adc >= 2000 && average_adc < 2100) { - battery_level = 1; - // 如果电量回升,隐藏提示窗口 - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2100 && average_adc < 2200) { - battery_level = 2; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2200 && average_adc < 2300) { - battery_level = 3; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else { - battery_level = 4; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } - - const char* battery_icon; - switch (battery_level) { - case 0: - battery_icon = FONT_AWESOME_BATTERY_EMPTY; - break; - case 1: - battery_icon = FONT_AWESOME_BATTERY_1; - break; - case 2: - battery_icon = FONT_AWESOME_BATTERY_2; - break; - case 3: - battery_icon = FONT_AWESOME_BATTERY_3; - break; - case 4: - battery_icon = FONT_AWESOME_BATTERY_FULL; - break; - default: - battery_icon = FONT_AWESOME_BATTERY_SLASH; - break; - } - - if (battery_label_ != nullptr) { - lv_obj_set_style_text_font(battery_label_, icon_font_, 0); - lv_label_set_text(battery_label_, battery_icon); - } -} - -void XINGZHI_Ssd1306Display::ChargingTimerCallback(void* arg) { - XINGZHI_Ssd1306Display* display = static_cast(arg); - DisplayLockGuard lock(display); - - // 检查充电状态 - int charging_level = gpio_get_level(display->charging_pin_); - bool is_charging = (charging_level == 1); - display->OnStateChanged();//检测当前对对话状态 - // 检查电池是否充满,adc值超过2430,判定为充满 - bool is_battery_full = 0; - if (display->average_adc > 2430) - { - is_battery_full = 1; - } - if (is_charging) { - // 正在充电,更新交互时间,防止进入睡眠 - display->UpdateInteractionTime(); - if (is_battery_full) { - if (display->charging_label_ != nullptr) { - lv_label_set_text(display->charging_label_, ""); - } - if (display->battery_label_ != nullptr) { - lv_obj_set_style_text_font(display->battery_label_, display->icon_font_, 0); - lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); - } - } else { - if (display->charging_label_ != nullptr) { - lv_obj_set_style_text_font(display->charging_label_, display->icon_font_, 0); - lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } - if (display->battery_label_ != nullptr) { - lv_label_set_text(display->battery_label_, ""); - } - } - // 如果正在充电,隐藏电量过低提示窗口 - if (display->low_battery_popup_ != nullptr) { - lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - display->was_charging = true; // 更新上一次的充电状态为正在充电 - } else { - if (display->was_charging) { - // 充电状态从充电变为未充电,立即读取并更新电池电量 - display->average_adc = display->ReadBatteryLevel(); - } else { - // 一直处于未充电状态,正常显示电池图标 - if (display->charging_label_ != nullptr) { - if (!display->first_battery_invert_) { - display->average_adc = display->ReadBatteryLevel(); - } - display->UpdateBatteryAndChargingDisplay(display->average_adc); - // 清空数组和计数器 - display->adc_values.clear(); - display->adc_count = 0; - } - } - display->was_charging = false; // 更新上一次的充电状态为未充电 - } - // 检查睡眠状态 - display->CheckSleepState(); -} - -void XINGZHI_Ssd1306Display::ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - - if (low_battery_popup_ == nullptr) { - // 创建弹出窗口 - low_battery_popup_ = lv_obj_create(lv_scr_act()); - lv_obj_set_size(low_battery_popup_, 120, 30); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - // 创建提示文本标签 - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - - // 显示弹出窗口 - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); -} - -void XINGZHI_Ssd1306Display::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - if (device_state != kDeviceStateIdle && !this->was_charging) { - UpdateInteractionTime(); - } -} - diff --git a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h b/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h deleted file mode 100644 index 69976516..00000000 --- a/main/boards/xingzhi-cube-0.96oled-wifi/xingzhi_ssd1306_display.h +++ /dev/null @@ -1,86 +0,0 @@ -#ifndef SSD1306_DISPLAY_H -#define SSD1306_DISPLAY_H - -#include "display/display.h" - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -// 新增的头文件包含语句 -#include -#include "button.h" - - -class XINGZHI_Ssd1306Display : public Display { -private: - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - - lv_obj_t* status_bar_ = nullptr; - lv_obj_t* content_ = nullptr; - lv_obj_t* content_left_ = nullptr; - lv_obj_t* content_right_ = nullptr; - lv_obj_t* container_ = nullptr; - lv_obj_t* side_bar_ = nullptr; - - const lv_font_t* text_font_ = nullptr; - const lv_font_t* icon_font_ = nullptr; - - DisplayFonts fonts_; - - lv_obj_t* charging_label_ = nullptr; // 充电状态标签 - lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 - lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 - int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 - uint16_t average_adc = 0; // adc平均值 - esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 - esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 - gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 - std::vector adc_values; // 用于存储读取的ADC值 - int adc_count = 0; // 记录已检测的ADC值数量 - bool was_charging = false; // 上一次的充电状态 - bool first_battery_invert_ = false; //首次上电直接检测电量 - void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 - uint16_t ReadBatteryLevel(); // 读取电量的方法 - - int64_t last_interaction_time_; // 上次交互时间 - bool is_light_sleep_ = false; // 浅睡眠 - bool is_deep_sleep_ = false; // 深睡眠 - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - void SetupUI_128x64(); - void SetupUI_128x32(); - -public: - XINGZHI_Ssd1306Display(void* i2c_master_handle, int width, int height, bool mirror_x, bool mirror_y, - const lv_font_t* text_font, const lv_font_t* icon_font); - ~XINGZHI_Ssd1306Display(); - - - static void ChargingTimerCallback(void* arg); - static void BatteryTimerCallback(void* arg); - void StartChargingTimer(); - void StartBatteryTimer(); - void UpdateBatteryAndChargingDisplay(uint16_t average_adc); - void OnStateChanged(); - - void UpdateInteractionTime(); - void CheckSleepState(); - - - virtual void SetChatMessage(const char* role, const char* content) override; -}; - -#endif // SSD1306_DISPLAY_H diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc index a5797810..a0f9e22a 100644 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc +++ b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi-cube-1.54tft-ml307.cc @@ -1,29 +1,109 @@ #include "ml307_board.h" #include "audio_codecs/no_audio_codec.h" -#include "xingzhi_lcd_display.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 "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" +#include "../xingzhi-cube-1.54tft-wifi/power_manager.h" #include #include +#include +#include + #define TAG "XINGZHI_CUBE_1_54TFT_ML307" LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); -class XINGZHI_CUBE_1_54TFT_ML307 : public Ml307Board { +class CustomDisplay : public SpiLcdDisplay { +private: + lv_obj_t* low_battery_popup_ = nullptr; + +public: + CustomDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + { + .text_font = &font_puhui_20_4, + .icon_font = &font_awesome_20_4, + .emoji_font = font_emoji_64_init(), + }) { + } + + void ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ == nullptr) { + low_battery_popup_ = lv_obj_create(lv_screen_active()); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + + void HideLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } +}; + + +class XINGZHI_CUBE_1_54TFT_ML307 : public Ml307Board { private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - XINGZHI_1_54_TFT_LcdDisplay* display_; + CustomDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } void InitializeSpi() { spi_bus_config_t buscfg = {}; @@ -38,11 +118,13 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); app.ToggleChatState(); }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -53,11 +135,13 @@ private: }); volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -68,15 +152,13 @@ private: }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); } void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - ESP_LOGD(TAG, "Install panel IO"); esp_lcd_panel_io_spi_config_t io_config = {}; io_config.cs_gpio_num = DISPLAY_CS; @@ -86,33 +168,29 @@ 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(SPI3_HOST, &io_config, &panel_io)); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); ESP_LOGD(TAG, "Install LCD driver"); esp_lcd_panel_dev_config_t panel_config = {}; panel_config.reset_gpio_num = DISPLAY_RES; panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - display_ = new XINGZHI_1_54_TFT_LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, - 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(), - }); + display_ = new CustomDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } void InitializeIot() { auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Backlight")); + thing_manager.AddThing(iot::CreateThing("Battery")); } public: @@ -120,11 +198,14 @@ public: Ml307Board(ML307_TX_PIN, ML307_RX_PIN, 4096), boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), + power_manager_(GPIO_NUM_38) { + InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); InitializeSt7789Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } virtual AudioCodec* GetAudioCodec() override { @@ -133,9 +214,57 @@ public: return &audio_codec; } - virtual Display *GetDisplay() override { + 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) override { + static int last_level = 0; + static bool last_charging = false; + + charging = power_manager_.IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); + } + + level = power_manager_.ReadBatteryLevel(charging != last_charging); + if (level != last_level || charging != last_charging) { + last_level = level; + last_charging = charging; + ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + + static bool show_low_power_warning_ = false; + if (power_manager_.IsBatteryLevelSteady()) { + if (!charging) { + // 电量低于 15% 时,显示低电量警告 + if (!show_low_power_warning_ && level <= 15) { + display_->ShowLowBatteryPopup(); + show_low_power_warning_ = true; + } + power_save_timer_->SetEnabled(true); + } else { + if (show_low_power_warning_) { + display_->HideLowBatteryPopup(); + show_low_power_warning_ = false; + } + power_save_timer_->SetEnabled(false); + } + } + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + Ml307Board::SetPowerSaveMode(enabled); + } }; DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_ML307); diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc deleted file mode 100644 index 4959647c..00000000 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.cc +++ /dev/null @@ -1,659 +0,0 @@ -#include "xingzhi_lcd_display.h" - -#include -#include -#include -#include -#include -#include -#include -#include "assets/lang_config.h" - -#include "board.h" - -#include "esp_adc/adc_oneshot.h" -#include "button.h" -#include -#include "config.h" -#include "settings.h" -#include "esp_sleep.h" -#include "application.h" -#include "driver/rtc_io.h" -#include - -#define TAG "XINGZHI_1_54_TFT_LcdDisplay" -#define LCD_LEDC_CH LEDC_CHANNEL_0 -#define PIN_NUMBER GPIO_NUM_21 - -LV_FONT_DECLARE(font_awesome_30_4); - -XINGZHI_1_54_TFT_LcdDisplay::XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, - DisplayFonts fonts) - : panel_io_(panel_io), panel_(panel), backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert), - fonts_(fonts),last_interaction_time_(esp_timer_get_time()),boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ - width_ = width; - height_ = height; - - // 创建背光渐变定时器 - const esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); - display->OnBacklightTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "backlight_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &backlight_timer_)); - InitializeBacklight(backlight_pin); - - // draw white - std::vector buffer(width_, 0xFFFF); - for (int y = 0; y < height_; y++) { - esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); - } - - // 创建充电检测定时器 - esp_timer_create_args_t charging_timer_args = { - .callback = &XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "charging_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); - - // 创建电量检测定时器 - esp_timer_create_args_t battery_timer_args = { - .callback = &XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); - - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - boot_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_up_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_down_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Initialize LVGL library"); - lv_init(); - - ESP_LOGI(TAG, "Initialize LVGL port"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - lvgl_port_init(&port_cfg); - - ESP_LOGI(TAG, "Adding LCD screen"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * 10), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = false, - .rotation = { - .swap_xy = swap_xy, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .color_format = LV_COLOR_FORMAT_RGB565, - .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, - .swap_bytes = 1, - .full_refresh = 0, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (offset_x != 0 || offset_y != 0) { - lv_display_set_offset(display_, offset_x, offset_y); - } - - // 读取初始亮度值 - Settings settings("display", true); - brightness_ = settings.GetInt("brightness", 75); - - SetBacklight(brightness_); - - SetupUI(); - StartChargingTimer(); - StartBatteryTimer(); -} - -XINGZHI_1_54_TFT_LcdDisplay::~XINGZHI_1_54_TFT_LcdDisplay() { - if (backlight_timer_ != nullptr) { - esp_timer_stop(backlight_timer_); - esp_timer_delete(backlight_timer_); - } - // 然后再清理 LVGL 对象 - if (content_ != nullptr) { - lv_obj_del(content_); - } - if (status_bar_ != nullptr) { - lv_obj_del(status_bar_); - } - if (side_bar_ != nullptr) { - lv_obj_del(side_bar_); - } - if (container_ != nullptr) { - lv_obj_del(container_); - } - if (display_ != nullptr) { - lv_display_delete(display_); - } - - if (panel_ != nullptr) { - esp_lcd_panel_del(panel_); - } - if (panel_io_ != nullptr) { - esp_lcd_panel_io_del(panel_io_); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetBacklight(uint8_t brightness) { - if (backlight_pin_ == GPIO_NUM_NC) { - return; - } - - if (brightness > 100) { - brightness = 100; - } - - ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness); - // 停止现有的定时器(如果正在运行) - esp_timer_stop(backlight_timer_); - - Settings settings("display", true); - if (is_light_sleep_) { - // 处于浅睡眠状态,保存浅睡眠亮度 - settings.SetInt("sleep_bright", brightness); - brightness_ = brightness; - } else { - // 正常状态,保存睡眠前亮度 - settings.SetInt("brightness", brightness); - brightness_ = brightness; - } - - // 启动定时器,每 5ms 更新一次 - ESP_ERROR_CHECK(esp_timer_start_periodic(backlight_timer_, 5 * 1000)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::InitializeBacklight(gpio_num_t backlight_pin) { - if (backlight_pin == GPIO_NUM_NC) { - return; - } - - // Setup LEDC peripheral for PWM backlight control - const ledc_channel_config_t backlight_channel = { - .gpio_num = backlight_pin, - .speed_mode = LEDC_LOW_SPEED_MODE, - .channel = LCD_LEDC_CH, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LEDC_TIMER_0, - .duty = 0, - .hpoint = 0, - .flags = { - .output_invert = backlight_output_invert_, - } - }; - const ledc_timer_config_t backlight_timer = { - .speed_mode = LEDC_LOW_SPEED_MODE, - .duty_resolution = LEDC_TIMER_10_BIT, - .timer_num = LEDC_TIMER_0, - .freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫 - .clk_cfg = LEDC_AUTO_CLK, - .deconfigure = false - }; - - ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); - ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::OnBacklightTimer() { - if (current_brightness_ < brightness_) { - current_brightness_++; - } else if (current_brightness_ > brightness_) { - current_brightness_--; - } - - // LEDC resolution set to 10bits, thus: 100% = 1023 - uint32_t duty_cycle = (1023 * current_brightness_) / 100; - ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle); - ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH); - - if (current_brightness_ == brightness_) { - esp_timer_stop(backlight_timer_); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::UpdateInteractionTime() { - last_interaction_time_ = esp_timer_get_time(); - if (is_light_sleep_) { - // 从浅睡眠中唤醒,恢复亮度 - Settings settings("display", true); - uint8_t norm_bright = settings.GetInt("brightness", 75); - SetBacklight(norm_bright); - is_light_sleep_ = false; - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::CheckSleepState() { - int64_t current_time = esp_timer_get_time(); - int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 - - int charging_level = gpio_get_level(charging_pin_); - bool is_charging = (charging_level == 1); - - if (is_charging) { - // 正在充电,不进入睡眠 - return; - } - - if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { - is_light_sleep_ = true; - SetBacklight(1); - } else if (elapsed_time >= 300 && is_light_sleep_) - { - is_deep_sleep_ = true; - is_light_sleep_ = false; - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - } -} - -bool XINGZHI_1_54_TFT_LcdDisplay::Lock(int timeout_ms) { - return lvgl_port_lock(timeout_ms); -} - -void XINGZHI_1_54_TFT_LcdDisplay::Unlock() { - lvgl_port_unlock(); -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetupUI() { - DisplayLockGuard lock(this); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, fonts_.text_font, 0); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height); - lv_obj_set_style_radius(status_bar_, 0, 0); - - /* Content */ - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下) - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布 - - emotion_label_ = lv_label_create(content_); - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - - chat_message_label_ = lv_label_create(content_); - lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90% - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式 - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐 - - /* Status bar */ - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - lv_obj_set_style_pad_left(status_bar_, 2, 0); - lv_obj_set_style_pad_right(status_bar_, 2, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_label_set_text(notification_label_, "通知"); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_label_set_text(status_label_, "正在初始化"); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); - - // 充电状态标签 - charging_label_ = lv_label_create(status_bar_); - lv_obj_set_style_text_font(charging_label_, fonts_.icon_font, 0); - lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); - lv_obj_set_flex_grow(charging_label_, 0); - lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); - - // 检查充电状态,如果未充电,设置充电图标为空 - int charging_level = gpio_get_level(charging_pin_); - if (charging_level == 0) { - lv_label_set_text(charging_label_, ""); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { - DisplayLockGuard lock(this); - - // 未充电时,显示电池图标 - if (charging_label_ != nullptr) { - lv_label_set_text(charging_label_, ""); - } - - uint8_t battery_level = 0; - if (average_adc < 1970) { - battery_level = 0; - // 显示电量过低提示窗口 - ShowLowBatteryPopup(); - } else if (average_adc >= 1970 && average_adc < 2100) { - battery_level = 1; - // 如果电量回升,隐藏提示窗口 - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2100 && average_adc < 2200) { - battery_level = 2; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2200 && average_adc < 2300) { - battery_level = 3; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else { - battery_level = 4; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } - - const char* battery_icon; - switch (battery_level) { - case 0: - battery_icon = FONT_AWESOME_BATTERY_EMPTY; - break; - case 1: - battery_icon = FONT_AWESOME_BATTERY_1; - break; - case 2: - battery_icon = FONT_AWESOME_BATTERY_2; - break; - case 3: - battery_icon = FONT_AWESOME_BATTERY_3; - break; - case 4: - battery_icon = FONT_AWESOME_BATTERY_FULL; - break; - default: - battery_icon = FONT_AWESOME_BATTERY_SLASH; - break; - } - - if (battery_label_ != nullptr) { - lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); - lv_label_set_text(battery_label_, battery_icon); - } -} - -// lcd_display.c -void XINGZHI_1_54_TFT_LcdDisplay::ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ == nullptr) { - low_battery_popup_ = lv_obj_create(lv_scr_act()); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); -} - -uint16_t XINGZHI_1_54_TFT_LcdDisplay::ReadBatteryLevel() { - adc_oneshot_unit_handle_t adc_handle; - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - // 初始化 ADC 单元 - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - // 配置 ADC 通道 - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); - int adc_value; - // 读取 ADC 值 - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); - adc_oneshot_del_unit(adc_handle); - return adc_value; -} - -void XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback(void* arg) { - XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); - DisplayLockGuard lock(display); - - // 检查充电状态 - int charging_level = gpio_get_level(display->charging_pin_); - bool is_charging = (charging_level == 1); - display->OnStateChanged(); // 检测当前对对话状态 - // 检查电池是否充满,adc值超过2430,判定为充满 - bool is_battery_full = 0; - if (display->average_adc > 2430) - { - is_battery_full = 1; - } - if (is_charging) { - // 正在充电,更新交互时间,防止进入睡眠 - display->UpdateInteractionTime(); - if (is_battery_full) { - if (display->charging_label_ != nullptr) { - lv_label_set_text(display->charging_label_, ""); - } - if (display->battery_label_ != nullptr) { - lv_obj_set_style_text_font(display->battery_label_, display->fonts_.icon_font, 0); - lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); - } - } else { - if (display->charging_label_ != nullptr) { - lv_obj_set_style_text_font(display->charging_label_, display->fonts_.icon_font, 0); - lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } - if (display->battery_label_ != nullptr) { - lv_label_set_text(display->battery_label_, ""); - } - } - // 如果正在充电,隐藏电量过低提示窗口 - if (display->low_battery_popup_ != nullptr) { - lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - display->was_charging = true; // 更新上一次的充电状态为正在充电 - } else { - if (display->was_charging) { - // 充电状态从充电变为未充电,立即读取并更新电池电量 - display->average_adc = display->ReadBatteryLevel(); - } else { - // 一直处于未充电状态,正常显示电池图标 - if (display->charging_label_ != nullptr) { - if (!display->first_battery_invert_) { - display->average_adc = display->ReadBatteryLevel(); - } - display->UpdateBatteryAndChargingDisplay(display->average_adc); - display->adc_values.clear(); - display->adc_count = 0; - } - } - display->was_charging = false; // 更新上一次的充电状态为未充电 - } - // 检查睡眠状态 - display->CheckSleepState(); -} - -void XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback(void* arg) { - XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); - uint16_t adc_value = display->ReadBatteryLevel(); - if (display->first_battery_invert_) { - display->adc_samp_interval = 180000000; // adc值采样的时间间隔 - // 停止当前定时器 - esp_timer_stop(display->battery_timer_); - // 重新启动定时器,使用新的时间间隔 - ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); - } - ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); - ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); - display->adc_values.push_back(adc_value); - display->adc_count++; - - if (display->adc_count >= 1) { - uint32_t sum = 0; - for (uint16_t value : display->adc_values) { - sum += value; - } - display->average_adc = sum / display->adc_values.size(); - display->first_battery_invert_ = true; - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::StartChargingTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::StartBatteryTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetEmotion(const char* emotion) { - struct Emotion { - const char* icon; - const char* text; - }; - - static const std::vector emotions = { - {"😶", "neutral"}, - {"🙂", "happy"}, - {"😆", "laughing"}, - {"😂", "funny"}, - {"😔", "sad"}, - {"😠", "angry"}, - {"😭", "crying"}, - {"😍", "loving"}, - {"😳", "embarrassed"}, - {"😯", "surprised"}, - {"😱", "shocked"}, - {"🤔", "thinking"}, - {"😉", "winking"}, - {"😎", "cool"}, - {"😌", "relaxed"}, - {"🤤", "delicious"}, - {"😘", "kissy"}, - {"😏", "confident"}, - {"😴", "sleepy"}, - {"😜", "silly"}, - {"🙄", "confused"} - }; - - // 查找匹配的表情 - std::string_view emotion_view(emotion); - auto it = std::find_if(emotions.begin(), emotions.end(), - [&emotion_view](const Emotion& e) { return e.text == emotion_view; }); - - DisplayLockGuard lock(this); - if (emotion_label_ == nullptr) { - return; - } - - // 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情 - lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0); - if (it != emotions.end()) { - lv_label_set_text(emotion_label_, it->icon); - } else { - lv_label_set_text(emotion_label_, "😶"); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetIcon(const char* icon) { - DisplayLockGuard lock(this); - if (emotion_label_ == nullptr) { - return; - } - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); - lv_label_set_text(emotion_label_, icon); -} - -void XINGZHI_1_54_TFT_LcdDisplay::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - if (device_state != kDeviceStateIdle && !this->was_charging) { - UpdateInteractionTime(); - } -} \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h b/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h deleted file mode 100644 index 2a7581f3..00000000 --- a/main/boards/xingzhi-cube-1.54tft-ml307/xingzhi_lcd_display.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef LCD_DISPLAY_H -#define LCD_DISPLAY_H - -#include "display/display.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -// 新增的头文件包含语句 -#include -#include "button.h" - -class XINGZHI_1_54_TFT_LcdDisplay : public Display { -protected: - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - gpio_num_t backlight_pin_ = GPIO_NUM_NC; - bool backlight_output_invert_ = false; - - lv_draw_buf_t draw_buf_; - lv_obj_t* status_bar_ = nullptr; - lv_obj_t* content_ = nullptr; - lv_obj_t* container_ = nullptr; - lv_obj_t* side_bar_ = nullptr; - - DisplayFonts fonts_; - - esp_timer_handle_t backlight_timer_ = nullptr; - uint8_t current_brightness_ = 0; - - - lv_obj_t* charging_label_ = nullptr; // 充电状态标签 - lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 - lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 - int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 - uint16_t average_adc = 0; // adc平均值 - esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 - esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 - gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 - std::vector adc_values; // 用于存储读取的ADC值 - int adc_count = 0; // 记录已检测的ADC值数量 - bool was_charging = false; // 上一次的充电状态 - bool first_battery_invert_ = false; //首次上电直接检测电量 - void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 - uint16_t ReadBatteryLevel(); // 读取电量的方法 - - int64_t last_interaction_time_; // 上次交互时间 - bool is_light_sleep_ = false; // 浅睡眠 - bool is_deep_sleep_ = false; // 深睡眠 - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - - - void OnBacklightTimer(); - void InitializeBacklight(gpio_num_t backlight_pin); - virtual void SetupUI(); - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - -public: - XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, - DisplayFonts fonts); - ~XINGZHI_1_54_TFT_LcdDisplay(); - - - static void ChargingTimerCallback(void* arg); - static void BatteryTimerCallback(void* arg); - void StartChargingTimer(); - void StartBatteryTimer(); - void UpdateBatteryAndChargingDisplay(uint16_t average_adc); - void OnStateChanged(); - - void UpdateInteractionTime(); - void CheckSleepState(); - - - virtual void SetEmotion(const char* emotion) override; - virtual void SetIcon(const char* icon) override; - virtual void SetBacklight(uint8_t brightness) override; - -}; - -#endif // LCD_DISPLAY_H diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h b/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h new file mode 100644 index 00000000..6f32f06f --- /dev/null +++ b/main/boards/xingzhi-cube-1.54tft-wifi/power_manager.h @@ -0,0 +1,112 @@ +#pragma once +#include + +#include +#include + + +class PowerManager { +private: + gpio_num_t charging_pin_ = GPIO_NUM_NC; + std::vector adc_values_; + uint32_t battery_level_ = 0; + int ticks_ = 0; + const int kBatteryCheckInterval = 60; + const int kBatteryAdcDataCount = 3; + +public: + PowerManager(gpio_num_t pin) : charging_pin_(pin) { + // 初始化充电引脚 + gpio_config_t io_conf = {}; + io_conf.intr_type = GPIO_INTR_DISABLE; + io_conf.mode = GPIO_MODE_INPUT; + io_conf.pin_bit_mask = (1ULL << charging_pin_); + io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; + io_conf.pull_up_en = GPIO_PULLUP_DISABLE; + gpio_config(&io_conf); + } + + uint16_t ReadBatteryAdcData() { + adc_oneshot_unit_handle_t adc_handle; + adc_oneshot_unit_init_cfg_t init_config = { + .unit_id = ADC_UNIT_2, + .ulp_mode = ADC_ULP_MODE_DISABLE, + }; + // 初始化 ADC 单元 + ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); + adc_oneshot_chan_cfg_t chan_config = { + .atten = ADC_ATTEN_DB_12, + .bitwidth = ADC_BITWIDTH_12, + }; + // 配置 ADC 通道 + ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); + int adc_value; + // 读取 ADC 值 + ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); + adc_oneshot_del_unit(adc_handle); + return adc_value; + } + + bool IsBatteryLevelSteady() { + return adc_values_.size() >= kBatteryAdcDataCount; + } + + uint8_t ReadBatteryLevel(bool update_immediately = false) { + ticks_++; + if (!update_immediately && adc_values_.size() >= kBatteryAdcDataCount) { + // 每隔一段时间检查一次电量 + if (ticks_ % kBatteryCheckInterval != 0) { + return battery_level_; + } + } + + uint16_t adc_value = ReadBatteryAdcData(); + adc_values_.push_back(adc_value); + if (adc_values_.size() > kBatteryAdcDataCount) { + adc_values_.erase(adc_values_.begin()); + } + uint32_t average_adc = 0; + for (auto value : adc_values_) { + average_adc += value; + } + average_adc /= adc_values_.size(); + + // 定义电池电量区间 + const struct { + uint16_t adc; + uint8_t level; + } levels[] = { + {1900, 0}, // 小于1900时为0% + {2000, 20}, // 1970起点为20% + {2100, 40}, // 2100为40% + {2200, 60}, // 2200为60% + {2300, 80}, // 2300为80% + {2400, 100} // 2400及以上为100% + }; + + // 低于最低值时 + if (average_adc < levels[0].adc) { + battery_level_ = 0; + } + // 高于最高值时 + else if (average_adc >= levels[5].adc) { + battery_level_ = 100; + } else { + // 线性插值计算中间值 + for (int i = 0; i < 5; i++) { + if (average_adc >= levels[i].adc && average_adc < levels[i+1].adc) { + float ratio = static_cast(average_adc - levels[i].adc) / (levels[i+1].adc - levels[i].adc); + battery_level_ = levels[i].level + ratio * (levels[i+1].level - levels[i].level); + break; + } + } + } + return battery_level_; + } + + bool IsCharging() { + int charging_level = gpio_get_level(charging_pin_); + return (charging_level == 1); + } +}; + \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc index 8e98d8cd..e3c07d68 100644 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc +++ b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi-cube-1.54tft-wifi.cc @@ -1,30 +1,110 @@ #include "wifi_board.h" #include "audio_codecs/no_audio_codec.h" -#include "xingzhi_lcd_display.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 "iot/thing_manager.h" #include "led/single_led.h" #include "assets/lang_config.h" +#include "power_manager.h" #include #include #include +#include +#include + #define TAG "XINGZHI_CUBE_1_54TFT_WIFI" LV_FONT_DECLARE(font_puhui_20_4); LV_FONT_DECLARE(font_awesome_20_4); -class XINGZHI_CUBE_1_54TFT_WIFI : public WifiBoard { +class CustomDisplay : public SpiLcdDisplay { +private: + lv_obj_t* low_battery_popup_ = nullptr; + +public: + CustomDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, + int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy) + : SpiLcdDisplay(panel_io, panel, width, height, offset_x, offset_y, mirror_x, mirror_y, swap_xy, + { + .text_font = &font_puhui_20_4, + .icon_font = &font_awesome_20_4, + .emoji_font = font_emoji_64_init(), + }) { + } + + void ShowLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ == nullptr) { + low_battery_popup_ = lv_obj_create(lv_screen_active()); + lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); + lv_obj_center(low_battery_popup_); + lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); + lv_obj_set_style_radius(low_battery_popup_, 10, 0); + + lv_obj_t* label = lv_label_create(low_battery_popup_); + lv_label_set_text(label, "电量过低,请充电"); + lv_obj_set_style_text_color(label, lv_color_white(), 0); + lv_obj_center(label); + } + lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + + void HideLowBatteryPopup() { + DisplayLockGuard lock(this); + if (low_battery_popup_ != nullptr) { + lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); + } + } +}; + + +class XINGZHI_CUBE_1_54TFT_WIFI : public WifiBoard { private: Button boot_button_; Button volume_up_button_; Button volume_down_button_; - XINGZHI_1_54_TFT_LcdDisplay* display_; + CustomDisplay* display_; + PowerSaveTimer* power_save_timer_; + PowerManager power_manager_; + esp_lcd_panel_io_handle_t panel_io_ = nullptr; + esp_lcd_panel_handle_t panel_ = nullptr; + + void InitializePowerSaveTimer() { + rtc_gpio_init(GPIO_NUM_21); + rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); + rtc_gpio_set_level(GPIO_NUM_21, 1); + + power_save_timer_ = new PowerSaveTimer(-1, 60, 300); + power_save_timer_->OnEnterSleepMode([this]() { + ESP_LOGI(TAG, "Enabling sleep mode"); + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("sleepy"); + GetBacklight()->SetBrightness(1); + }); + power_save_timer_->OnExitSleepMode([this]() { + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + GetBacklight()->RestoreBrightness(); + }); + power_save_timer_->OnShutdownRequest([this]() { + ESP_LOGI(TAG, "Shutting down"); + rtc_gpio_set_level(GPIO_NUM_21, 0); + // 启用保持功能,确保睡眠期间电平不变 + rtc_gpio_hold_en(GPIO_NUM_21); + esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 + esp_deep_sleep_start(); + }); + power_save_timer_->SetEnabled(true); + } void InitializeSpi() { spi_bus_config_t buscfg = {}; @@ -39,6 +119,7 @@ private: void InitializeButtons() { boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting && !WifiStation::GetInstance().IsConnected()) { ResetWifiConfiguration(); @@ -47,6 +128,7 @@ private: }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -57,11 +139,13 @@ private: }); volume_up_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -72,15 +156,13 @@ private: }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); } void InitializeSt7789Display() { - esp_lcd_panel_io_handle_t panel_io = nullptr; - esp_lcd_panel_handle_t panel = nullptr; - ESP_LOGD(TAG, "Install panel IO"); esp_lcd_panel_io_spi_config_t io_config = {}; io_config.cs_gpio_num = DISPLAY_CS; @@ -90,55 +172,102 @@ 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(SPI3_HOST, &io_config, &panel_io)); + ESP_ERROR_CHECK(esp_lcd_new_panel_io_spi(SPI3_HOST, &io_config, &panel_io_)); ESP_LOGD(TAG, "Install LCD driver"); esp_lcd_panel_dev_config_t panel_config = {}; panel_config.reset_gpio_num = DISPLAY_RES; panel_config.rgb_ele_order = LCD_RGB_ELEMENT_ORDER_RGB; panel_config.bits_per_pixel = 16; - ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io, &panel_config, &panel)); - ESP_ERROR_CHECK(esp_lcd_panel_reset(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_init(panel)); - ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel, DISPLAY_SWAP_XY)); - ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); - ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel, true)); + ESP_ERROR_CHECK(esp_lcd_new_panel_st7789(panel_io_, &panel_config, &panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_reset(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_init(panel_)); + ESP_ERROR_CHECK(esp_lcd_panel_swap_xy(panel_, DISPLAY_SWAP_XY)); + ESP_ERROR_CHECK(esp_lcd_panel_mirror(panel_, DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y)); + ESP_ERROR_CHECK(esp_lcd_panel_invert_color(panel_, true)); - display_ = new XINGZHI_1_54_TFT_LcdDisplay(panel_io, panel, DISPLAY_BACKLIGHT_PIN, DISPLAY_BACKLIGHT_OUTPUT_INVERT, - 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(), - }); + display_ = new CustomDisplay(panel_io_, panel_, DISPLAY_WIDTH, DISPLAY_HEIGHT, DISPLAY_OFFSET_X, DISPLAY_OFFSET_Y, + DISPLAY_MIRROR_X, DISPLAY_MIRROR_Y, DISPLAY_SWAP_XY); } void InitializeIot() { auto& thing_manager = iot::ThingManager::GetInstance(); thing_manager.AddThing(iot::CreateThing("Speaker")); thing_manager.AddThing(iot::CreateThing("Backlight")); + thing_manager.AddThing(iot::CreateThing("Battery")); } public: XINGZHI_CUBE_1_54TFT_WIFI() : boot_button_(BOOT_BUTTON_GPIO), volume_up_button_(VOLUME_UP_BUTTON_GPIO), - volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { + volume_down_button_(VOLUME_DOWN_BUTTON_GPIO), + power_manager_(GPIO_NUM_38) { + InitializePowerSaveTimer(); InitializeSpi(); InitializeButtons(); InitializeSt7789Display(); InitializeIot(); + GetBacklight()->RestoreBrightness(); } - virtual AudioCodec *GetAudioCodec() override { + virtual AudioCodec* GetAudioCodec() override { static NoAudioCodecSimplex audio_codec(AUDIO_INPUT_SAMPLE_RATE, AUDIO_OUTPUT_SAMPLE_RATE, AUDIO_I2S_SPK_GPIO_BCLK, AUDIO_I2S_SPK_GPIO_LRCK, AUDIO_I2S_SPK_GPIO_DOUT, AUDIO_I2S_MIC_GPIO_SCK, AUDIO_I2S_MIC_GPIO_WS, AUDIO_I2S_MIC_GPIO_DIN); return &audio_codec; } - virtual Display *GetDisplay() override { + 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) override { + static int last_level = 0; + static bool last_charging = false; + + charging = power_manager_.IsCharging(); + if (charging != last_charging) { + power_save_timer_->WakeUp(); + } + + level = power_manager_.ReadBatteryLevel(charging != last_charging); + if (level != last_level || charging != last_charging) { + last_level = level; + last_charging = charging; + ESP_LOGI(TAG, "Battery level: %d, charging: %d", level, charging); + } + + static bool show_low_power_warning_ = false; + if (power_manager_.IsBatteryLevelSteady()) { + if (!charging) { + // 电量低于 15% 时,显示低电量警告 + if (!show_low_power_warning_ && level <= 15) { + display_->ShowLowBatteryPopup(); + show_low_power_warning_ = true; + } + power_save_timer_->SetEnabled(true); + } else { + if (show_low_power_warning_) { + display_->HideLowBatteryPopup(); + show_low_power_warning_ = false; + } + power_save_timer_->SetEnabled(false); + } + } + return true; + } + + virtual void SetPowerSaveMode(bool enabled) override { + if (!enabled) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveMode(enabled); + } }; DECLARE_BOARD(XINGZHI_CUBE_1_54TFT_WIFI); diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc deleted file mode 100644 index 4959647c..00000000 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.cc +++ /dev/null @@ -1,659 +0,0 @@ -#include "xingzhi_lcd_display.h" - -#include -#include -#include -#include -#include -#include -#include -#include "assets/lang_config.h" - -#include "board.h" - -#include "esp_adc/adc_oneshot.h" -#include "button.h" -#include -#include "config.h" -#include "settings.h" -#include "esp_sleep.h" -#include "application.h" -#include "driver/rtc_io.h" -#include - -#define TAG "XINGZHI_1_54_TFT_LcdDisplay" -#define LCD_LEDC_CH LEDC_CHANNEL_0 -#define PIN_NUMBER GPIO_NUM_21 - -LV_FONT_DECLARE(font_awesome_30_4); - -XINGZHI_1_54_TFT_LcdDisplay::XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, - DisplayFonts fonts) - : panel_io_(panel_io), panel_(panel), backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert), - fonts_(fonts),last_interaction_time_(esp_timer_get_time()),boot_button_(BOOT_BUTTON_GPIO),volume_up_button_(VOLUME_UP_BUTTON_GPIO), volume_down_button_(VOLUME_DOWN_BUTTON_GPIO){ - width_ = width; - height_ = height; - - // 创建背光渐变定时器 - const esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); - display->OnBacklightTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "backlight_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &backlight_timer_)); - InitializeBacklight(backlight_pin); - - // draw white - std::vector buffer(width_, 0xFFFF); - for (int y = 0; y < height_; y++) { - esp_lcd_panel_draw_bitmap(panel_, 0, y, width_, y + 1, buffer.data()); - } - - // 创建充电检测定时器 - esp_timer_create_args_t charging_timer_args = { - .callback = &XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "charging_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&charging_timer_args, &charging_timer_)); - - // 创建电量检测定时器 - esp_timer_create_args_t battery_timer_args = { - .callback = &XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "battery_timer" - }; - ESP_ERROR_CHECK(esp_timer_create(&battery_timer_args, &battery_timer_)); - - // 初始化充电引脚 - gpio_config_t io_conf = {}; - io_conf.intr_type = GPIO_INTR_DISABLE; - io_conf.mode = GPIO_MODE_INPUT; - io_conf.pin_bit_mask = (1ULL << charging_pin_); - io_conf.pull_down_en = GPIO_PULLDOWN_DISABLE; - io_conf.pull_up_en = GPIO_PULLUP_DISABLE; - gpio_config(&io_conf); - - rtc_gpio_init(GPIO_NUM_21); - rtc_gpio_set_direction(GPIO_NUM_21, RTC_GPIO_MODE_OUTPUT_ONLY); - rtc_gpio_set_level(GPIO_NUM_21, 1); - - boot_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_up_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - volume_down_button_.OnPressDown([this]() { - this->UpdateInteractionTime(); - }); - - // Set the display to on - ESP_LOGI(TAG, "Turning display on"); - ESP_ERROR_CHECK(esp_lcd_panel_disp_on_off(panel_, true)); - - ESP_LOGI(TAG, "Initialize LVGL library"); - lv_init(); - - ESP_LOGI(TAG, "Initialize LVGL port"); - lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); - lvgl_port_init(&port_cfg); - - ESP_LOGI(TAG, "Adding LCD screen"); - const lvgl_port_display_cfg_t display_cfg = { - .io_handle = panel_io_, - .panel_handle = panel_, - .control_handle = nullptr, - .buffer_size = static_cast(width_ * 10), - .double_buffer = false, - .trans_size = 0, - .hres = static_cast(width_), - .vres = static_cast(height_), - .monochrome = false, - .rotation = { - .swap_xy = swap_xy, - .mirror_x = mirror_x, - .mirror_y = mirror_y, - }, - .color_format = LV_COLOR_FORMAT_RGB565, - .flags = { - .buff_dma = 1, - .buff_spiram = 0, - .sw_rotate = 0, - .swap_bytes = 1, - .full_refresh = 0, - .direct_mode = 0, - }, - }; - - display_ = lvgl_port_add_disp(&display_cfg); - if (display_ == nullptr) { - ESP_LOGE(TAG, "Failed to add display"); - return; - } - - if (offset_x != 0 || offset_y != 0) { - lv_display_set_offset(display_, offset_x, offset_y); - } - - // 读取初始亮度值 - Settings settings("display", true); - brightness_ = settings.GetInt("brightness", 75); - - SetBacklight(brightness_); - - SetupUI(); - StartChargingTimer(); - StartBatteryTimer(); -} - -XINGZHI_1_54_TFT_LcdDisplay::~XINGZHI_1_54_TFT_LcdDisplay() { - if (backlight_timer_ != nullptr) { - esp_timer_stop(backlight_timer_); - esp_timer_delete(backlight_timer_); - } - // 然后再清理 LVGL 对象 - if (content_ != nullptr) { - lv_obj_del(content_); - } - if (status_bar_ != nullptr) { - lv_obj_del(status_bar_); - } - if (side_bar_ != nullptr) { - lv_obj_del(side_bar_); - } - if (container_ != nullptr) { - lv_obj_del(container_); - } - if (display_ != nullptr) { - lv_display_delete(display_); - } - - if (panel_ != nullptr) { - esp_lcd_panel_del(panel_); - } - if (panel_io_ != nullptr) { - esp_lcd_panel_io_del(panel_io_); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetBacklight(uint8_t brightness) { - if (backlight_pin_ == GPIO_NUM_NC) { - return; - } - - if (brightness > 100) { - brightness = 100; - } - - ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness); - // 停止现有的定时器(如果正在运行) - esp_timer_stop(backlight_timer_); - - Settings settings("display", true); - if (is_light_sleep_) { - // 处于浅睡眠状态,保存浅睡眠亮度 - settings.SetInt("sleep_bright", brightness); - brightness_ = brightness; - } else { - // 正常状态,保存睡眠前亮度 - settings.SetInt("brightness", brightness); - brightness_ = brightness; - } - - // 启动定时器,每 5ms 更新一次 - ESP_ERROR_CHECK(esp_timer_start_periodic(backlight_timer_, 5 * 1000)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::InitializeBacklight(gpio_num_t backlight_pin) { - if (backlight_pin == GPIO_NUM_NC) { - return; - } - - // Setup LEDC peripheral for PWM backlight control - const ledc_channel_config_t backlight_channel = { - .gpio_num = backlight_pin, - .speed_mode = LEDC_LOW_SPEED_MODE, - .channel = LCD_LEDC_CH, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LEDC_TIMER_0, - .duty = 0, - .hpoint = 0, - .flags = { - .output_invert = backlight_output_invert_, - } - }; - const ledc_timer_config_t backlight_timer = { - .speed_mode = LEDC_LOW_SPEED_MODE, - .duty_resolution = LEDC_TIMER_10_BIT, - .timer_num = LEDC_TIMER_0, - .freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫 - .clk_cfg = LEDC_AUTO_CLK, - .deconfigure = false - }; - - ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); - ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::OnBacklightTimer() { - if (current_brightness_ < brightness_) { - current_brightness_++; - } else if (current_brightness_ > brightness_) { - current_brightness_--; - } - - // LEDC resolution set to 10bits, thus: 100% = 1023 - uint32_t duty_cycle = (1023 * current_brightness_) / 100; - ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle); - ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH); - - if (current_brightness_ == brightness_) { - esp_timer_stop(backlight_timer_); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::UpdateInteractionTime() { - last_interaction_time_ = esp_timer_get_time(); - if (is_light_sleep_) { - // 从浅睡眠中唤醒,恢复亮度 - Settings settings("display", true); - uint8_t norm_bright = settings.GetInt("brightness", 75); - SetBacklight(norm_bright); - is_light_sleep_ = false; - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::CheckSleepState() { - int64_t current_time = esp_timer_get_time(); - int64_t elapsed_time = (current_time - last_interaction_time_) / 1000000; // 转换为秒 - - int charging_level = gpio_get_level(charging_pin_); - bool is_charging = (charging_level == 1); - - if (is_charging) { - // 正在充电,不进入睡眠 - return; - } - - if (elapsed_time >= 60 && !is_light_sleep_ && !is_deep_sleep_) { - is_light_sleep_ = true; - SetBacklight(1); - } else if (elapsed_time >= 300 && is_light_sleep_) - { - is_deep_sleep_ = true; - is_light_sleep_ = false; - rtc_gpio_set_level(GPIO_NUM_21, 0); - // 启用保持功能,确保睡眠期间电平不变 - rtc_gpio_hold_en(GPIO_NUM_21); - esp_lcd_panel_disp_on_off(panel_, false); //关闭显示 - esp_deep_sleep_start(); - } -} - -bool XINGZHI_1_54_TFT_LcdDisplay::Lock(int timeout_ms) { - return lvgl_port_lock(timeout_ms); -} - -void XINGZHI_1_54_TFT_LcdDisplay::Unlock() { - lvgl_port_unlock(); -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetupUI() { - DisplayLockGuard lock(this); - - auto screen = lv_screen_active(); - lv_obj_set_style_text_font(screen, fonts_.text_font, 0); - lv_obj_set_style_text_color(screen, lv_color_black(), 0); - - /* Container */ - container_ = lv_obj_create(screen); - lv_obj_set_size(container_, LV_HOR_RES, LV_VER_RES); - lv_obj_set_flex_flow(container_, LV_FLEX_FLOW_COLUMN); - lv_obj_set_style_pad_all(container_, 0, 0); - lv_obj_set_style_border_width(container_, 0, 0); - lv_obj_set_style_pad_row(container_, 0, 0); - - /* Status bar */ - status_bar_ = lv_obj_create(container_); - lv_obj_set_size(status_bar_, LV_HOR_RES, fonts_.text_font->line_height); - lv_obj_set_style_radius(status_bar_, 0, 0); - - /* Content */ - content_ = lv_obj_create(container_); - lv_obj_set_scrollbar_mode(content_, LV_SCROLLBAR_MODE_OFF); - lv_obj_set_style_radius(content_, 0, 0); - lv_obj_set_width(content_, LV_HOR_RES); - lv_obj_set_flex_grow(content_, 1); - - lv_obj_set_flex_flow(content_, LV_FLEX_FLOW_COLUMN); // 垂直布局(从上到下) - lv_obj_set_flex_align(content_, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_CENTER, LV_FLEX_ALIGN_SPACE_EVENLY); // 子对象居中对齐,等距分布 - - emotion_label_ = lv_label_create(content_); - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); - lv_label_set_text(emotion_label_, FONT_AWESOME_AI_CHIP); - - chat_message_label_ = lv_label_create(content_); - lv_label_set_text(chat_message_label_, ""); - lv_obj_set_width(chat_message_label_, LV_HOR_RES * 0.9); // 限制宽度为屏幕宽度的 90% - lv_label_set_long_mode(chat_message_label_, LV_LABEL_LONG_WRAP); // 设置为自动换行模式 - lv_obj_set_style_text_align(chat_message_label_, LV_TEXT_ALIGN_CENTER, 0); // 设置文本居中对齐 - - /* Status bar */ - lv_obj_set_flex_flow(status_bar_, LV_FLEX_FLOW_ROW); - lv_obj_set_style_pad_all(status_bar_, 0, 0); - lv_obj_set_style_border_width(status_bar_, 0, 0); - lv_obj_set_style_pad_column(status_bar_, 0, 0); - lv_obj_set_style_pad_left(status_bar_, 2, 0); - lv_obj_set_style_pad_right(status_bar_, 2, 0); - - network_label_ = lv_label_create(status_bar_); - lv_label_set_text(network_label_, ""); - lv_obj_set_style_text_font(network_label_, fonts_.icon_font, 0); - - notification_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(notification_label_, 1); - lv_obj_set_style_text_align(notification_label_, LV_TEXT_ALIGN_CENTER, 0); - lv_label_set_text(notification_label_, "通知"); - lv_obj_add_flag(notification_label_, LV_OBJ_FLAG_HIDDEN); - - status_label_ = lv_label_create(status_bar_); - lv_obj_set_flex_grow(status_label_, 1); - lv_label_set_long_mode(status_label_, LV_LABEL_LONG_SCROLL_CIRCULAR); - lv_label_set_text(status_label_, "正在初始化"); - lv_obj_set_style_text_align(status_label_, LV_TEXT_ALIGN_CENTER, 0); - - mute_label_ = lv_label_create(status_bar_); - lv_label_set_text(mute_label_, ""); - lv_obj_set_style_text_font(mute_label_, fonts_.icon_font, 0); - - battery_label_ = lv_label_create(status_bar_); - lv_label_set_text(battery_label_, ""); - lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); - - // 充电状态标签 - charging_label_ = lv_label_create(status_bar_); - lv_obj_set_style_text_font(charging_label_, fonts_.icon_font, 0); - lv_obj_set_style_text_align(charging_label_, LV_TEXT_ALIGN_RIGHT, 0); - lv_obj_set_flex_grow(charging_label_, 0); - lv_label_set_text(charging_label_, FONT_AWESOME_BATTERY_CHARGING); - - // 检查充电状态,如果未充电,设置充电图标为空 - int charging_level = gpio_get_level(charging_pin_); - if (charging_level == 0) { - lv_label_set_text(charging_label_, ""); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::UpdateBatteryAndChargingDisplay(uint16_t average_adc) { - DisplayLockGuard lock(this); - - // 未充电时,显示电池图标 - if (charging_label_ != nullptr) { - lv_label_set_text(charging_label_, ""); - } - - uint8_t battery_level = 0; - if (average_adc < 1970) { - battery_level = 0; - // 显示电量过低提示窗口 - ShowLowBatteryPopup(); - } else if (average_adc >= 1970 && average_adc < 2100) { - battery_level = 1; - // 如果电量回升,隐藏提示窗口 - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2100 && average_adc < 2200) { - battery_level = 2; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else if (average_adc >= 2200 && average_adc < 2300) { - battery_level = 3; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } else { - battery_level = 4; - if (low_battery_popup_ != nullptr) { - lv_obj_add_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - } - - const char* battery_icon; - switch (battery_level) { - case 0: - battery_icon = FONT_AWESOME_BATTERY_EMPTY; - break; - case 1: - battery_icon = FONT_AWESOME_BATTERY_1; - break; - case 2: - battery_icon = FONT_AWESOME_BATTERY_2; - break; - case 3: - battery_icon = FONT_AWESOME_BATTERY_3; - break; - case 4: - battery_icon = FONT_AWESOME_BATTERY_FULL; - break; - default: - battery_icon = FONT_AWESOME_BATTERY_SLASH; - break; - } - - if (battery_label_ != nullptr) { - lv_obj_set_style_text_font(battery_label_, fonts_.icon_font, 0); - lv_label_set_text(battery_label_, battery_icon); - } -} - -// lcd_display.c -void XINGZHI_1_54_TFT_LcdDisplay::ShowLowBatteryPopup() { - DisplayLockGuard lock(this); - if (low_battery_popup_ == nullptr) { - low_battery_popup_ = lv_obj_create(lv_scr_act()); - lv_obj_set_size(low_battery_popup_, LV_HOR_RES * 0.9, LV_VER_RES * 0.5); - lv_obj_center(low_battery_popup_); - lv_obj_set_style_bg_color(low_battery_popup_, lv_color_black(), 0); - lv_obj_set_style_radius(low_battery_popup_, 10, 0); - - lv_obj_t* label = lv_label_create(low_battery_popup_); - lv_label_set_text(label, "电量过低,请充电"); - lv_obj_set_style_text_color(label, lv_color_white(), 0); - lv_obj_center(label); - } - lv_obj_clear_flag(low_battery_popup_, LV_OBJ_FLAG_HIDDEN); -} - -uint16_t XINGZHI_1_54_TFT_LcdDisplay::ReadBatteryLevel() { - adc_oneshot_unit_handle_t adc_handle; - adc_oneshot_unit_init_cfg_t init_config = { - .unit_id = ADC_UNIT_2, - .ulp_mode = ADC_ULP_MODE_DISABLE, - }; - // 初始化 ADC 单元 - ESP_ERROR_CHECK(adc_oneshot_new_unit(&init_config, &adc_handle)); - adc_oneshot_chan_cfg_t chan_config = { - .atten = ADC_ATTEN_DB_12, - .bitwidth = ADC_BITWIDTH_12, - }; - // 配置 ADC 通道 - ESP_ERROR_CHECK(adc_oneshot_config_channel(adc_handle, ADC_CHANNEL_6, &chan_config)); - int adc_value; - // 读取 ADC 值 - ESP_ERROR_CHECK(adc_oneshot_read(adc_handle, ADC_CHANNEL_6, &adc_value)); - adc_oneshot_del_unit(adc_handle); - return adc_value; -} - -void XINGZHI_1_54_TFT_LcdDisplay::ChargingTimerCallback(void* arg) { - XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); - DisplayLockGuard lock(display); - - // 检查充电状态 - int charging_level = gpio_get_level(display->charging_pin_); - bool is_charging = (charging_level == 1); - display->OnStateChanged(); // 检测当前对对话状态 - // 检查电池是否充满,adc值超过2430,判定为充满 - bool is_battery_full = 0; - if (display->average_adc > 2430) - { - is_battery_full = 1; - } - if (is_charging) { - // 正在充电,更新交互时间,防止进入睡眠 - display->UpdateInteractionTime(); - if (is_battery_full) { - if (display->charging_label_ != nullptr) { - lv_label_set_text(display->charging_label_, ""); - } - if (display->battery_label_ != nullptr) { - lv_obj_set_style_text_font(display->battery_label_, display->fonts_.icon_font, 0); - lv_label_set_text(display->battery_label_, FONT_AWESOME_BATTERY_FULL); - } - } else { - if (display->charging_label_ != nullptr) { - lv_obj_set_style_text_font(display->charging_label_, display->fonts_.icon_font, 0); - lv_label_set_text(display->charging_label_, FONT_AWESOME_BATTERY_CHARGING); - } - if (display->battery_label_ != nullptr) { - lv_label_set_text(display->battery_label_, ""); - } - } - // 如果正在充电,隐藏电量过低提示窗口 - if (display->low_battery_popup_ != nullptr) { - lv_obj_add_flag(display->low_battery_popup_, LV_OBJ_FLAG_HIDDEN); - } - display->was_charging = true; // 更新上一次的充电状态为正在充电 - } else { - if (display->was_charging) { - // 充电状态从充电变为未充电,立即读取并更新电池电量 - display->average_adc = display->ReadBatteryLevel(); - } else { - // 一直处于未充电状态,正常显示电池图标 - if (display->charging_label_ != nullptr) { - if (!display->first_battery_invert_) { - display->average_adc = display->ReadBatteryLevel(); - } - display->UpdateBatteryAndChargingDisplay(display->average_adc); - display->adc_values.clear(); - display->adc_count = 0; - } - } - display->was_charging = false; // 更新上一次的充电状态为未充电 - } - // 检查睡眠状态 - display->CheckSleepState(); -} - -void XINGZHI_1_54_TFT_LcdDisplay::BatteryTimerCallback(void* arg) { - XINGZHI_1_54_TFT_LcdDisplay* display = static_cast(arg); - uint16_t adc_value = display->ReadBatteryLevel(); - if (display->first_battery_invert_) { - display->adc_samp_interval = 180000000; // adc值采样的时间间隔 - // 停止当前定时器 - esp_timer_stop(display->battery_timer_); - // 重新启动定时器,使用新的时间间隔 - ESP_ERROR_CHECK(esp_timer_start_periodic(display->battery_timer_, display->adc_samp_interval)); - } - ESP_LOGI(TAG, "adc_samp_interval: %" PRId32 "", display->adc_samp_interval); - ESP_LOGI(TAG, "Value of first_battery_invert_ before condition: %d", display->first_battery_invert_); - display->adc_values.push_back(adc_value); - display->adc_count++; - - if (display->adc_count >= 1) { - uint32_t sum = 0; - for (uint16_t value : display->adc_values) { - sum += value; - } - display->average_adc = sum / display->adc_values.size(); - display->first_battery_invert_ = true; - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::StartChargingTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(charging_timer_, adc_samp_interval)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::StartBatteryTimer() { - ESP_ERROR_CHECK(esp_timer_start_periodic(battery_timer_, adc_samp_interval)); -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetEmotion(const char* emotion) { - struct Emotion { - const char* icon; - const char* text; - }; - - static const std::vector emotions = { - {"😶", "neutral"}, - {"🙂", "happy"}, - {"😆", "laughing"}, - {"😂", "funny"}, - {"😔", "sad"}, - {"😠", "angry"}, - {"😭", "crying"}, - {"😍", "loving"}, - {"😳", "embarrassed"}, - {"😯", "surprised"}, - {"😱", "shocked"}, - {"🤔", "thinking"}, - {"😉", "winking"}, - {"😎", "cool"}, - {"😌", "relaxed"}, - {"🤤", "delicious"}, - {"😘", "kissy"}, - {"😏", "confident"}, - {"😴", "sleepy"}, - {"😜", "silly"}, - {"🙄", "confused"} - }; - - // 查找匹配的表情 - std::string_view emotion_view(emotion); - auto it = std::find_if(emotions.begin(), emotions.end(), - [&emotion_view](const Emotion& e) { return e.text == emotion_view; }); - - DisplayLockGuard lock(this); - if (emotion_label_ == nullptr) { - return; - } - - // 如果找到匹配的表情就显示对应图标,否则显示默认的neutral表情 - lv_obj_set_style_text_font(emotion_label_, fonts_.emoji_font, 0); - if (it != emotions.end()) { - lv_label_set_text(emotion_label_, it->icon); - } else { - lv_label_set_text(emotion_label_, "😶"); - } -} - -void XINGZHI_1_54_TFT_LcdDisplay::SetIcon(const char* icon) { - DisplayLockGuard lock(this); - if (emotion_label_ == nullptr) { - return; - } - lv_obj_set_style_text_font(emotion_label_, &font_awesome_30_4, 0); - lv_label_set_text(emotion_label_, icon); -} - -void XINGZHI_1_54_TFT_LcdDisplay::OnStateChanged() { - auto& app = Application::GetInstance(); - auto device_state = app.GetDeviceState(); - if (device_state != kDeviceStateIdle && !this->was_charging) { - UpdateInteractionTime(); - } -} \ No newline at end of file diff --git a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h b/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h deleted file mode 100644 index 2a7581f3..00000000 --- a/main/boards/xingzhi-cube-1.54tft-wifi/xingzhi_lcd_display.h +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef LCD_DISPLAY_H -#define LCD_DISPLAY_H - -#include "display/display.h" - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -// 新增的头文件包含语句 -#include -#include "button.h" - -class XINGZHI_1_54_TFT_LcdDisplay : public Display { -protected: - esp_lcd_panel_io_handle_t panel_io_ = nullptr; - esp_lcd_panel_handle_t panel_ = nullptr; - gpio_num_t backlight_pin_ = GPIO_NUM_NC; - bool backlight_output_invert_ = false; - - lv_draw_buf_t draw_buf_; - lv_obj_t* status_bar_ = nullptr; - lv_obj_t* content_ = nullptr; - lv_obj_t* container_ = nullptr; - lv_obj_t* side_bar_ = nullptr; - - DisplayFonts fonts_; - - esp_timer_handle_t backlight_timer_ = nullptr; - uint8_t current_brightness_ = 0; - - - lv_obj_t* charging_label_ = nullptr; // 充电状态标签 - lv_obj_t* low_battery_popup_ = nullptr; // 电量过低提示窗口对象指针 - lv_obj_t* battery_label_ = nullptr; // 已有,用于显示电量 - int32_t adc_samp_interval = 500000; // adc值采样的时间间隔,单位为微秒 - uint16_t average_adc = 0; // adc平均值 - esp_timer_handle_t charging_timer_; // 充电检测定时器句柄 - esp_timer_handle_t battery_timer_; // 电量检测定时器句柄 - gpio_num_t charging_pin_ = GPIO_NUM_38; // 充电检测引脚 - std::vector adc_values; // 用于存储读取的ADC值 - int adc_count = 0; // 记录已检测的ADC值数量 - bool was_charging = false; // 上一次的充电状态 - bool first_battery_invert_ = false; //首次上电直接检测电量 - void ShowLowBatteryPopup(); // 显示电量过低提示窗口的方法声明 - uint16_t ReadBatteryLevel(); // 读取电量的方法 - - int64_t last_interaction_time_; // 上次交互时间 - bool is_light_sleep_ = false; // 浅睡眠 - bool is_deep_sleep_ = false; // 深睡眠 - Button boot_button_; - Button volume_up_button_; - Button volume_down_button_; - - - void OnBacklightTimer(); - void InitializeBacklight(gpio_num_t backlight_pin); - virtual void SetupUI(); - virtual bool Lock(int timeout_ms = 0) override; - virtual void Unlock() override; - - -public: - XINGZHI_1_54_TFT_LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, - int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, - DisplayFonts fonts); - ~XINGZHI_1_54_TFT_LcdDisplay(); - - - static void ChargingTimerCallback(void* arg); - static void BatteryTimerCallback(void* arg); - void StartChargingTimer(); - void StartBatteryTimer(); - void UpdateBatteryAndChargingDisplay(uint16_t average_adc); - void OnStateChanged(); - - void UpdateInteractionTime(); - void CheckSleepState(); - - - virtual void SetEmotion(const char* emotion) override; - virtual void SetIcon(const char* icon) override; - virtual void SetBacklight(uint8_t brightness) override; - -}; - -#endif // LCD_DISPLAY_H diff --git a/main/boards/xmini-c3/xmini_c3_board.cc b/main/boards/xmini-c3/xmini_c3_board.cc index bc372ce5..01fdc22c 100644 --- a/main/boards/xmini-c3/xmini_c3_board.cc +++ b/main/boards/xmini-c3/xmini_c3_board.cc @@ -3,18 +3,17 @@ #include "display/ssd1306_display.h" #include "application.h" #include "button.h" -#include "config.h" -#include "iot/thing_manager.h" #include "led/single_led.h" +#include "iot/thing_manager.h" #include "settings.h" +#include "config.h" +#include "power_save_timer.h" #include "font_awesome_symbols.h" #include #include #include #include -#include -#include #define TAG "XminiC3Board" @@ -26,75 +25,30 @@ private: i2c_master_bus_handle_t codec_i2c_bus_; Button boot_button_; bool press_to_talk_enabled_ = false; - esp_timer_handle_t power_save_timer_ = nullptr; - bool sleep_mode_enabled_ = false; - int power_save_ticks_ = 0; + PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - esp_timer_create_args_t power_save_timer_args = { - .callback = [](void *arg) { - auto board = static_cast(arg); - board->PowerSaveCheck(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "power_save_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&power_save_timer_args, &power_save_timer_)); - ESP_ERROR_CHECK(esp_timer_start_periodic(power_save_timer_, 1000000)); - } - - void PowerSaveCheck() { - // 如果待机超过一定时间,则进入睡眠模式 - const int seconds_to_sleep = 120; - auto& app = Application::GetInstance(); - if (app.GetDeviceState() != kDeviceStateIdle) { - power_save_ticks_ = 0; - return; - } - - power_save_ticks_++; - if (power_save_ticks_ >= seconds_to_sleep) { - EnableSleepMode(true); - } - } - - void EnableSleepMode(bool enable) { - power_save_ticks_ = 0; - if (!sleep_mode_enabled_ && enable) { + power_save_timer_ = new PowerSaveTimer(160, 60); + power_save_timer_->OnEnterSleepMode([this]() { ESP_LOGI(TAG, "Enabling sleep mode"); auto display = GetDisplay(); display->SetChatMessage("system", ""); display->SetEmotion("sleepy"); - // 如果是LCD,还可以调节屏幕亮度 auto codec = GetAudioCodec(); codec->EnableInput(false); - - esp_pm_config_t pm_config = { - .max_freq_mhz = 160, - .min_freq_mhz = 40, - .light_sleep_enable = true, - }; - esp_pm_configure(&pm_config); - sleep_mode_enabled_ = true; - } else if (sleep_mode_enabled_ && !enable) { - esp_pm_config_t pm_config = { - .max_freq_mhz = 160, - .min_freq_mhz = 160, - .light_sleep_enable = false, - }; - esp_pm_configure(&pm_config); - ESP_LOGI(TAG, "Disabling sleep mode"); - + }); + power_save_timer_->OnExitSleepMode([this]() { auto codec = GetAudioCodec(); codec->EnableInput(true); - sleep_mode_enabled_ = false; - } + + auto display = GetDisplay(); + display->SetChatMessage("system", ""); + display->SetEmotion("neutral"); + }); + power_save_timer_->SetEnabled(true); } - void InitializeCodecI2c() { // Initialize I2C peripheral i2c_master_bus_config_t i2c_bus_cfg = { @@ -123,7 +77,7 @@ private: } }); boot_button_.OnPressDown([this]() { - EnableSleepMode(false); + power_save_timer_->WakeUp(); if (press_to_talk_enabled_) { Application::GetInstance().StartListening(); } @@ -157,8 +111,8 @@ public: } virtual Led* GetLed() override { - static SingleLed led_strip(BUILTIN_LED_GPIO); - return &led_strip; + static SingleLed led(BUILTIN_LED_GPIO); + return &led; } virtual Display* GetDisplay() override { diff --git a/main/display/display.cc b/main/display/display.cc index f04a8e82..20de5f84 100644 --- a/main/display/display.cc +++ b/main/display/display.cc @@ -13,10 +13,6 @@ #define TAG "Display" Display::Display() { - // Load brightness from settings - Settings settings("display"); - brightness_ = settings.GetInt("brightness", 100); - // Notification timer esp_timer_create_args_t notification_timer_args = { .callback = [](void *arg) { @@ -40,14 +36,14 @@ Display::Display() { }, .arg = this, .dispatch_method = ESP_TIMER_TASK, - .name = "update_display_timer", + .name = "display_update_timer", .skip_unhandled_events = true, }; ESP_ERROR_CHECK(esp_timer_create(&update_display_timer_args, &update_timer_)); ESP_ERROR_CHECK(esp_timer_start_periodic(update_timer_, 1000000)); // Create a power management lock - auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "ml307", &pm_lock_); + auto ret = esp_pm_lock_create(ESP_PM_APB_FREQ_MAX, 0, "display_update", &pm_lock_); if (ret == ESP_ERR_NOT_SUPPORTED) { ESP_LOGI(TAG, "Power management not supported"); } else { @@ -236,9 +232,3 @@ void Display::SetChatMessage(const char* role, const char* content) { } lv_label_set_text(chat_message_label_, content); } - -void Display::SetBacklight(uint8_t brightness) { - Settings settings("display", true); - settings.SetInt("brightness", brightness); - brightness_ = brightness; -} diff --git a/main/display/display.h b/main/display/display.h index a875a760..86ffe34f 100644 --- a/main/display/display.h +++ b/main/display/display.h @@ -25,17 +25,13 @@ public: virtual void SetEmotion(const char* emotion); virtual void SetChatMessage(const char* role, const char* content); virtual void SetIcon(const char* icon); - virtual void SetBacklight(uint8_t brightness); inline int width() const { return width_; } inline int height() const { return height_; } - inline uint8_t brightness() const { return brightness_; } protected: int width_ = 0; int height_ = 0; - uint8_t brightness_ = 0; - esp_pm_lock_handle_t pm_lock_ = nullptr; lv_display_t *display_ = nullptr; diff --git a/main/display/lcd_display.cc b/main/display/lcd_display.cc index 455e0ff3..4b00b6d7 100644 --- a/main/display/lcd_display.cc +++ b/main/display/lcd_display.cc @@ -1,43 +1,25 @@ #include "lcd_display.h" +#include #include #include #include -#include -#include #include -#include #include "assets/lang_config.h" #include "board.h" #define TAG "LcdDisplay" -#define LCD_LEDC_CH LEDC_CHANNEL_0 LV_FONT_DECLARE(font_awesome_30_4); SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts) - : LcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, fonts) { + : LcdDisplay(panel_io, panel, fonts) { width_ = width; height_ = height; - // 创建背光渐变定时器 - const esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - LcdDisplay* display = static_cast(arg); - display->OnBacklightTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "backlight_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &backlight_timer_)); - InitializeBacklight(backlight_pin); - // draw white std::vector buffer(width_, 0xFFFF); for (int y = 0; y < height_; y++) { @@ -53,6 +35,7 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h ESP_LOGI(TAG, "Initialize LVGL port"); lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + port_cfg.task_priority = 1; lvgl_port_init(&port_cfg); ESP_LOGI(TAG, "Adding LCD screen"); @@ -93,33 +76,16 @@ SpiLcdDisplay::SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h } SetupUI(); - - SetBacklight(brightness_); } // RGB LCD实现 RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts) - : LcdDisplay(panel_io, panel, backlight_pin, backlight_output_invert, fonts) { + : LcdDisplay(panel_io, panel, fonts) { width_ = width; height_ = height; - - // 创建背光渐变定时器 - const esp_timer_create_args_t timer_args = { - .callback = [](void* arg) { - LcdDisplay* display = static_cast(arg); - display->OnBacklightTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "backlight_timer", - .skip_unhandled_events = true, - }; - ESP_ERROR_CHECK(esp_timer_create(&timer_args, &backlight_timer_)); - InitializeBacklight(backlight_pin); // draw white std::vector buffer(width_, 0xFFFF); @@ -132,6 +98,7 @@ RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h ESP_LOGI(TAG, "Initialize LVGL port"); lvgl_port_cfg_t port_cfg = ESP_LVGL_PORT_INIT_CONFIG(); + port_cfg.task_priority = 1; lvgl_port_init(&port_cfg); ESP_LOGI(TAG, "Adding LCD screen"); @@ -173,15 +140,9 @@ RgbLcdDisplay::RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_h } SetupUI(); - - SetBacklight(brightness_); } LcdDisplay::~LcdDisplay() { - if (backlight_timer_ != nullptr) { - esp_timer_stop(backlight_timer_); - esp_timer_delete(backlight_timer_); - } // 然后再清理 LVGL 对象 if (content_ != nullptr) { lv_obj_del(content_); @@ -207,72 +168,6 @@ LcdDisplay::~LcdDisplay() { } } -void LcdDisplay::InitializeBacklight(gpio_num_t backlight_pin) { - if (backlight_pin == GPIO_NUM_NC) { - return; - } - - // Setup LEDC peripheral for PWM backlight control - const ledc_channel_config_t backlight_channel = { - .gpio_num = backlight_pin, - .speed_mode = LEDC_LOW_SPEED_MODE, - .channel = LCD_LEDC_CH, - .intr_type = LEDC_INTR_DISABLE, - .timer_sel = LEDC_TIMER_0, - .duty = 0, - .hpoint = 0, - .flags = { - .output_invert = backlight_output_invert_, - } - }; - const ledc_timer_config_t backlight_timer = { - .speed_mode = LEDC_LOW_SPEED_MODE, - .duty_resolution = LEDC_TIMER_10_BIT, - .timer_num = LEDC_TIMER_0, - .freq_hz = 20000, //背光pwm频率需要高一点,防止电感啸叫 - .clk_cfg = LEDC_AUTO_CLK, - .deconfigure = false - }; - - ESP_ERROR_CHECK(ledc_timer_config(&backlight_timer)); - ESP_ERROR_CHECK(ledc_channel_config(&backlight_channel)); -} - -void LcdDisplay::OnBacklightTimer() { - if (current_brightness_ < brightness_) { - current_brightness_++; - } else if (current_brightness_ > brightness_) { - current_brightness_--; - } - - // LEDC resolution set to 10bits, thus: 100% = 1023 - uint32_t duty_cycle = (1023 * current_brightness_) / 100; - ledc_set_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH, duty_cycle); - ledc_update_duty(LEDC_LOW_SPEED_MODE, LCD_LEDC_CH); - - if (current_brightness_ == brightness_) { - esp_timer_stop(backlight_timer_); - } -} - -void LcdDisplay::SetBacklight(uint8_t brightness) { - if (backlight_pin_ == GPIO_NUM_NC) { - return; - } - - if (brightness > 100) { - brightness = 100; - } - - ESP_LOGI(TAG, "Setting LCD backlight: %d%%", brightness); - // 停止现有的定时器(如果正在运行) - esp_timer_stop(backlight_timer_); - - Display::SetBacklight(brightness); - // 启动定时器,每 5ms 更新一次 - ESP_ERROR_CHECK(esp_timer_start_periodic(backlight_timer_, 5 * 1000)); -} - bool LcdDisplay::Lock(int timeout_ms) { return lvgl_port_lock(timeout_ms); } diff --git a/main/display/lcd_display.h b/main/display/lcd_display.h index 4256c229..e85c1a1a 100644 --- a/main/display/lcd_display.h +++ b/main/display/lcd_display.h @@ -3,13 +3,8 @@ #include "display.h" -#include -#include -#include -#include #include #include -#include #include #include @@ -18,8 +13,6 @@ class LcdDisplay : public Display { protected: esp_lcd_panel_io_handle_t panel_io_ = nullptr; esp_lcd_panel_handle_t panel_ = nullptr; - gpio_num_t backlight_pin_ = GPIO_NUM_NC; - bool backlight_output_invert_ = false; lv_draw_buf_t draw_buf_; lv_obj_t* status_bar_ = nullptr; @@ -29,36 +22,25 @@ protected: DisplayFonts fonts_; - esp_timer_handle_t backlight_timer_ = nullptr; - uint8_t current_brightness_ = 0; - void InitializeBacklight(gpio_num_t backlight_pin); - virtual void SetupUI(); virtual bool Lock(int timeout_ms = 0) override; virtual void Unlock() override; protected: // 添加protected构造函数 - LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, - DisplayFonts fonts) - : panel_io_(panel_io), panel_(panel), - backlight_pin_(backlight_pin), backlight_output_invert_(backlight_output_invert), - fonts_(fonts) {} + LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, DisplayFonts fonts) + : panel_io_(panel_io), panel_(panel), fonts_(fonts) {} public: ~LcdDisplay(); - virtual void OnBacklightTimer(); virtual void SetEmotion(const char* emotion) override; virtual void SetIcon(const char* icon) override; - virtual void SetBacklight(uint8_t brightness) override; }; // RGB LCD显示器 class RgbLcdDisplay : public LcdDisplay { public: RgbLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts); @@ -68,7 +50,6 @@ public: class MipiLcdDisplay : public LcdDisplay { public: MipiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts); @@ -78,7 +59,6 @@ public: class SpiLcdDisplay : public LcdDisplay { public: SpiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts); @@ -88,7 +68,6 @@ public: class QspiLcdDisplay : public LcdDisplay { public: QspiLcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts); @@ -98,7 +77,6 @@ public: class Mcu8080LcdDisplay : public LcdDisplay { public: Mcu8080LcdDisplay(esp_lcd_panel_io_handle_t panel_io, esp_lcd_panel_handle_t panel, - gpio_num_t backlight_pin, bool backlight_output_invert, int width, int height, int offset_x, int offset_y, bool mirror_x, bool mirror_y, bool swap_xy, DisplayFonts fonts); diff --git a/main/iot/things/battery.cc b/main/iot/things/battery.cc index b39c9300..7232a746 100644 --- a/main/iot/things/battery.cc +++ b/main/iot/things/battery.cc @@ -16,7 +16,7 @@ private: public: Battery() : Thing("Battery", "电池管理") { // 定义设备的属性 - properties_.AddNumberProperty("level", "当前电量百分比(0-100)", [this]() -> int { + properties_.AddNumberProperty("level", "当前电量百分比", [this]() -> int { auto& board = Board::GetInstance(); if (board.GetBatteryLevel(level_, charging_)) { return level_; diff --git a/main/iot/things/blaklight.cc b/main/iot/things/blaklight.cc index f0829125..b51bb582 100644 --- a/main/iot/things/blaklight.cc +++ b/main/iot/things/blaklight.cc @@ -1,6 +1,7 @@ #include "iot/thing.h" #include "board.h" #include "display/lcd_display.h" +#include "settings.h" #include @@ -11,21 +12,23 @@ namespace iot { // 这里仅定义 Backlight 的属性和方法,不包含具体的实现 class Backlight : public Thing { public: - Backlight() : Thing("Backlight", "当前 AI 机器人屏幕的亮度") { + Backlight() : Thing("Backlight", "屏幕背光") { // 定义设备的属性 - properties_.AddNumberProperty("brightness", "当前亮度值", [this]() -> int { + properties_.AddNumberProperty("brightness", "当前亮度百分比", [this]() -> int { // 这里可以添加获取当前亮度的逻辑 - auto display = Board::GetInstance().GetDisplay(); - return display->brightness(); + auto backlight = Board::GetInstance().GetBacklight(); + return backlight ? backlight->brightness() : 0; }); // 定义设备可以被远程执行的指令 methods_.AddMethod("SetBrightness", "设置亮度", ParameterList({ Parameter("brightness", "0到100之间的整数", kValueTypeNumber, true) }), [this](const ParameterList& parameters) { - auto display = Board::GetInstance().GetDisplay(); uint8_t brightness = static_cast(parameters["brightness"].number()); - display->SetBacklight(brightness); + auto backlight = Board::GetInstance().GetBacklight(); + if (backlight) { + backlight->SetBrightness(brightness, true); + } }); } }; diff --git a/main/iot/things/speaker.cc b/main/iot/things/speaker.cc index 7dc70fc3..689cd2a8 100644 --- a/main/iot/things/speaker.cc +++ b/main/iot/things/speaker.cc @@ -11,7 +11,7 @@ namespace iot { // 这里仅定义 Speaker 的属性和方法,不包含具体的实现 class Speaker : public Thing { public: - Speaker() : Thing("Speaker", "当前 AI 机器人的扬声器") { + Speaker() : Thing("Speaker", "扬声器") { // 定义设备的属性 properties_.AddNumberProperty("volume", "当前音量值", [this]() -> int { auto codec = Board::GetInstance().GetAudioCodec(); diff --git a/main/led/led.cc b/main/led/led.cc deleted file mode 100644 index 718a82c5..00000000 --- a/main/led/led.cc +++ /dev/null @@ -1,219 +0,0 @@ -#include "led.h" -#include "board.h" - -#include -#include -#include - -#define TAG "Led" - -Led::Led(gpio_num_t gpio, uint8_t max_leds) { - if (gpio == GPIO_NUM_NC) { - ESP_LOGI(TAG, "Builtin LED not connected"); - return; - } - - led_ = new Led(gpio, max_leds); - - esp_timer_create_args_t led_strip_timer_args = { - .callback = [](void *arg) { - auto light = static_cast(arg); - light->OnBlinkTimer(); - }, - .arg = this, - .dispatch_method = ESP_TIMER_TASK, - .name = "led_strip_timer", - .skip_unhandled_events = false, - }; - ESP_ERROR_CHECK(esp_timer_create(&led_strip_timer_args, &led_strip_timer_)); -} - -LedStripWrapper::~LedStripWrapper() { - if (led_strip_timer_ != nullptr) { - esp_timer_delete(led_strip_timer_); - } - if (led_ != nullptr) { - delete led_; - } -} - -void LedStripWrapper::OnBlinkTimer() { - std::lock_guard lock(mutex_); - counter_--; - timer_callback_(); -} - -void LedStripWrapper::SetLedBasicColor(LedBasicColor color, uint8_t brightness) { - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - switch (color) { - case kLedColorWhite: - led_->SetWhite(brightness); - break; - case kLedColorGrey: - led_->SetGrey(brightness); - break; - case kLedColorRed: - led_->SetRed(brightness); - break; - case kLedColorGreen: - led_->SetGreen(brightness); - break; - case kLedColorBlue: - led_->SetBlue(brightness); - break; - } -} - -void LedStripWrapper::SetLedStripBasicColor(uint8_t index, LedBasicColor color, uint8_t brightness) { - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - if (index >= led_->max_leds()) { - ESP_LOGE(TAG, "Invalid led index: %d", index); - return; - } - - switch (color) { - case kLedColorWhite: - led_strip_set_pixel(led_->led_strip(), index, brightness, brightness, brightness); - break; - case kLedColorGrey: - led_strip_set_pixel(led_->led_strip(), index, brightness, brightness, brightness); - break; - case kLedColorRed: - led_strip_set_pixel(led_->led_strip(), index, brightness, 0, 0); - break; - case kLedColorGreen: - led_strip_set_pixel(led_->led_strip(), index, 0, brightness, 0); - break; - case kLedColorBlue: - led_strip_set_pixel(led_->led_strip(), index, 0, 0, brightness); - break; - } -} - -void LedStripWrapper::StartBlinkTask(uint32_t times, uint32_t interval_ms) { - std::lock_guard lock(mutex_); - - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - esp_timer_stop(led_strip_timer_); - counter_ = times * 2; - timer_callback_ = [this]() { - if (counter_ & 1) { - led_->TurnOn(); - } else { - led_->TurnOff(); - if (counter_ == 0) { - esp_timer_stop(led_strip_timer_); - } - } - }; - esp_timer_start_periodic(led_strip_timer_, interval_ms * 1000); -} - -void LedStripWrapper::BlinkOnce(LedBasicColor color, uint8_t brightness) { - Blink(color, brightness, 1, 100); -} - -void LedStripWrapper::Blink(LedBasicColor color, uint32_t times, uint32_t interval_ms, uint8_t brightness) { - SetLedBasicColor(color, brightness); - StartBlinkTask(times, interval_ms); -} - -void LedStripWrapper::ContinuousBlink(LedBasicColor color, uint32_t interval_ms, uint8_t brightness) { - SetLedBasicColor(color, brightness); - StartBlinkTask(COUNTER_INFINITE, interval_ms); -} - -void LedStripWrapper::StaticLight(LedBasicColor color, uint8_t brightness) { - std::lock_guard lock(mutex_); - - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - SetLedBasicColor(color, brightness); - esp_timer_stop(led_strip_timer_); - led_->TurnOn(); -} - -void LedStripWrapper::ChasingLight(LedBasicColor base_color, LedBasicColor color, uint32_t interval_ms, uint8_t brightness) { - std::lock_guard lock(mutex_); - - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - esp_timer_stop(led_strip_timer_); - counter_ = COUNTER_INFINITE; - timer_callback_ = [this, base_color, color, brightness]() { - auto index = counter_ % led_->max_leds(); - for (uint8_t i = 0; i < led_->max_leds(); i++) { - if (i == index || i == (index + 1) % led_->max_leds()) { - SetLedStripBasicColor(i, color, brightness); - } else { - SetLedStripBasicColor(i, base_color, LOW_BRIGHTNESS); - } - } - led_strip_refresh(led_->led_strip()); - }; - esp_timer_start_periodic(led_strip_timer_, interval_ms * 1000); -} - -void LedStripWrapper::BreathLight(LedBasicColor color, uint32_t interval_ms) { - std::lock_guard lock(mutex_); - - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - esp_timer_stop(led_strip_timer_); - counter_ = COUNTER_INFINITE; - timer_callback_ = [this, color]() { - static bool increase = true; - static uint32_t brightness = LOW_BRIGHTNESS; - - for (uint8_t i = 0; i < led_->max_leds(); i++) { - SetLedStripBasicColor(i, color, brightness); - } - led_strip_refresh(led_->led_strip()); - - if (brightness == HIGH_BRIGHTNESS) { - increase = false; - } else if (brightness == LOW_BRIGHTNESS) { - increase = true; - } - - if (increase) { - brightness += 1; - } else { - brightness -= 1; - } - }; - esp_timer_start_periodic(led_strip_timer_, interval_ms * 1000); -} - -void LedStripWrapper::LightOff() { - std::lock_guard lock(mutex_); - - if (led_ == nullptr) { - ESP_LOGE(TAG, "Builtin LED not connected"); - return; - } - - esp_timer_stop(led_strip_timer_); - led_->TurnOff(); -}