From 04d9e775f089d998dd84113dbb95cd1396221153 Mon Sep 17 00:00:00 2001 From: Brandon Date: Thu, 15 Jan 2026 23:00:51 +0800 Subject: [PATCH 1/2] feat(oled): Implement brightness control and auto-power save - **OledDisplay**: - Add hardware power control in `SetPowerSaveMode`. - Add `SetContrast` using SSD1306/SH1106 command 0x81. - Implement `OledBacklight` adapter class to map brightness to contrast. - Default brightness set to 50% to match hardware reset state. - Logic: Brightness <= 5% turns off display power; > 5% turns on and sets contrast. - **CompactWifiBoard**: - Integrate `PowerSaveTimer` for 60-second idle auto-sleep. - Override `GetBacklight` to expose `OledBacklight` to MCP tools. - Add wake-up triggers to Boot, Touch, and Volume buttons. - Sync system power save level with display timer. --- .../bread-compact-wifi/compact_wifi_board.cc | 33 +++++++++++++++++++ main/display/oled_display.cc | 30 +++++++++++++++++ main/display/oled_display.h | 12 +++++++ 3 files changed, 75 insertions(+) diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index 04d71910ad..05dbe72810 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -9,6 +9,7 @@ #include "lamp_controller.h" #include "led/single_led.h" #include "assets/lang_config.h" +#include "power_save_timer.h" #include #include @@ -31,6 +32,18 @@ class CompactWifiBoard : public WifiBoard { Button touch_button_; Button volume_up_button_; Button volume_down_button_; + PowerSaveTimer* power_save_timer_; + + void InitializePowerSaveTimer() { + power_save_timer_ = new PowerSaveTimer(-1, 60, -1); + power_save_timer_->OnEnterSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(true); + }); + power_save_timer_->OnExitSleepMode([this]() { + GetDisplay()->SetPowerSaveMode(false); + }); + power_save_timer_->SetEnabled(true); + } void InitializeDisplayI2c() { i2c_master_bus_config_t bus_config = { @@ -102,6 +115,7 @@ class CompactWifiBoard : public WifiBoard { void InitializeButtons() { boot_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting) { EnterWifiConfigMode(); @@ -110,13 +124,16 @@ class CompactWifiBoard : public WifiBoard { app.ToggleChatState(); }); touch_button_.OnPressDown([this]() { + power_save_timer_->WakeUp(); Application::GetInstance().StartListening(); }); touch_button_.OnPressUp([this]() { + power_save_timer_->WakeUp(); Application::GetInstance().StopListening(); }); volume_up_button_.OnClick([this]() { + power_save_timer_->WakeUp(); auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -127,11 +144,13 @@ class CompactWifiBoard : public WifiBoard { }); 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) { @@ -142,6 +161,7 @@ class CompactWifiBoard : public WifiBoard { }); volume_down_button_.OnLongPress([this]() { + power_save_timer_->WakeUp(); GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); @@ -160,6 +180,7 @@ class CompactWifiBoard : public WifiBoard { volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); InitializeSsd1306Display(); + InitializePowerSaveTimer(); InitializeButtons(); InitializeTools(); } @@ -183,6 +204,18 @@ class CompactWifiBoard : public WifiBoard { virtual Display* GetDisplay() override { return display_; } + + virtual Backlight* GetBacklight() override { + static OledBacklight backlight(static_cast(display_)); + return &backlight; + } + + virtual void SetPowerSaveLevel(PowerSaveLevel level) override { + if (level != PowerSaveLevel::LOW_POWER) { + power_save_timer_->WakeUp(); + } + WifiBoard::SetPowerSaveLevel(level); + } }; DECLARE_BOARD(CompactWifiBoard); diff --git a/main/display/oled_display.cc b/main/display/oled_display.cc index 74469148a1..5f19ab3b04 100644 --- a/main/display/oled_display.cc +++ b/main/display/oled_display.cc @@ -394,3 +394,33 @@ void OledDisplay::SetTheme(Theme* theme) { auto screen = lv_screen_active(); lv_obj_set_style_text_font(screen, text_font, 0); } + +void OledDisplay::SetPowerSaveMode(bool on) { + LvglDisplay::SetPowerSaveMode(on); + if (panel_ != nullptr) { + esp_lcd_panel_disp_on_off(panel_, !on); + } +} + +void OledDisplay::SetContrast(uint8_t contrast) { + if (panel_io_ != nullptr) { + ESP_LOGI(TAG, "SetContrast: %d", contrast); + esp_lcd_panel_io_tx_param(panel_io_, 0x81, &contrast, 1); + } +} + +OledBacklight::OledBacklight(OledDisplay* display) : display_(display) { + brightness_ = 50; +} + +void OledBacklight::SetBrightnessImpl(uint8_t brightness) { + if (brightness <= 5) { + display_->SetPowerSaveMode(true); + } else { + display_->SetPowerSaveMode(false); + // Map 6-100 to 0-255 + // 5 is min brightness, so we map 5-100 to 0-255 roughly + uint8_t contrast = static_cast((brightness) * 255 / 100); + display_->SetContrast(contrast); + } +} diff --git a/main/display/oled_display.h b/main/display/oled_display.h index c7f4077958..09e4ab8f11 100644 --- a/main/display/oled_display.h +++ b/main/display/oled_display.h @@ -2,6 +2,7 @@ #define OLED_DISPLAY_H #include "lvgl_display.h" +#include "backlight.h" #include #include @@ -35,6 +36,17 @@ class OledDisplay : public LvglDisplay { virtual void SetChatMessage(const char* role, const char* content) override; virtual void SetEmotion(const char* emotion) override; virtual void SetTheme(Theme* theme) override; + virtual void SetPowerSaveMode(bool on) override; + void SetContrast(uint8_t contrast); +}; + +class OledBacklight : public Backlight { +public: + OledBacklight(OledDisplay* display); + virtual void SetBrightnessImpl(uint8_t brightness) override; + +private: + OledDisplay* display_; }; #endif // OLED_DISPLAY_H From 2c032823e306a909af28f4c734a6860591f15aca Mon Sep 17 00:00:00 2001 From: Brandon Date: Sat, 7 Feb 2026 02:58:50 +0800 Subject: [PATCH 2/2] feat(oled): make auto-power save optional and fix brightness control - Add CONFIG_OLED_AUTO_POWER_SAVE Kconfig option (default off) for bread-compact-wifi to make screen auto-off optional - Make Backlight::SetBrightness virtual to allow OledBacklight to skip the PWM gradual transition, applying brightness changes immediately --- main/Kconfig.projbuild | 7 +++++ .../bread-compact-wifi/compact_wifi_board.cc | 26 ++++++++++++++++++- main/boards/common/backlight.h | 2 +- main/display/oled_display.cc | 20 ++++++++++++++ main/display/oled_display.h | 3 ++- 5 files changed, 55 insertions(+), 3 deletions(-) diff --git a/main/Kconfig.projbuild b/main/Kconfig.projbuild index ed86440fb2..1f48ca27b5 100644 --- a/main/Kconfig.projbuild +++ b/main/Kconfig.projbuild @@ -510,6 +510,13 @@ choice DISPLAY_OLED_TYPE bool "SH1106 128*64" endchoice +config OLED_AUTO_POWER_SAVE + bool "Enable OLED Auto Power Save" + depends on BOARD_TYPE_BREAD_COMPACT_WIFI + help + Automatically turn off the OLED display after a period of inactivity. + The screen wakes up on button press or voice activity. + choice DISPLAY_LCD_TYPE depends on BOARD_TYPE_BREAD_COMPACT_WIFI_LCD || BOARD_TYPE_BREAD_COMPACT_ESP32_LCD || BOARD_TYPE_CGC || BOARD_TYPE_WAVESHARE_P4_NANO || BOARD_TYPE_WAVESHARE_P4_WIFI6_TOUCH_LCD_XC || BOARD_TYPE_BREAD_COMPACT_WIFI_CAM prompt "LCD Type" diff --git a/main/boards/bread-compact-wifi/compact_wifi_board.cc b/main/boards/bread-compact-wifi/compact_wifi_board.cc index 05dbe72810..5a38c54932 100644 --- a/main/boards/bread-compact-wifi/compact_wifi_board.cc +++ b/main/boards/bread-compact-wifi/compact_wifi_board.cc @@ -9,7 +9,9 @@ #include "lamp_controller.h" #include "led/single_led.h" #include "assets/lang_config.h" +#ifdef CONFIG_OLED_AUTO_POWER_SAVE #include "power_save_timer.h" +#endif #include #include @@ -22,6 +24,8 @@ #define TAG "CompactWifiBoard" +static constexpr int kPowerSaveTimeoutSeconds = 60; + class CompactWifiBoard : public WifiBoard { private: i2c_master_bus_handle_t display_i2c_bus_; @@ -32,10 +36,11 @@ class CompactWifiBoard : public WifiBoard { Button touch_button_; Button volume_up_button_; Button volume_down_button_; +#ifdef CONFIG_OLED_AUTO_POWER_SAVE PowerSaveTimer* power_save_timer_; void InitializePowerSaveTimer() { - power_save_timer_ = new PowerSaveTimer(-1, 60, -1); + power_save_timer_ = new PowerSaveTimer(-1, kPowerSaveTimeoutSeconds, -1); power_save_timer_->OnEnterSleepMode([this]() { GetDisplay()->SetPowerSaveMode(true); }); @@ -44,6 +49,7 @@ class CompactWifiBoard : public WifiBoard { }); power_save_timer_->SetEnabled(true); } +#endif void InitializeDisplayI2c() { i2c_master_bus_config_t bus_config = { @@ -115,7 +121,9 @@ class CompactWifiBoard : public WifiBoard { void InitializeButtons() { boot_button_.OnClick([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif auto& app = Application::GetInstance(); if (app.GetDeviceState() == kDeviceStateStarting) { EnterWifiConfigMode(); @@ -124,16 +132,22 @@ class CompactWifiBoard : public WifiBoard { app.ToggleChatState(); }); touch_button_.OnPressDown([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif Application::GetInstance().StartListening(); }); touch_button_.OnPressUp([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif Application::GetInstance().StopListening(); }); volume_up_button_.OnClick([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif auto codec = GetAudioCodec(); auto volume = codec->output_volume() + 10; if (volume > 100) { @@ -144,13 +158,17 @@ class CompactWifiBoard : public WifiBoard { }); volume_up_button_.OnLongPress([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif GetAudioCodec()->SetOutputVolume(100); GetDisplay()->ShowNotification(Lang::Strings::MAX_VOLUME); }); volume_down_button_.OnClick([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif auto codec = GetAudioCodec(); auto volume = codec->output_volume() - 10; if (volume < 0) { @@ -161,7 +179,9 @@ class CompactWifiBoard : public WifiBoard { }); volume_down_button_.OnLongPress([this]() { +#ifdef CONFIG_OLED_AUTO_POWER_SAVE power_save_timer_->WakeUp(); +#endif GetAudioCodec()->SetOutputVolume(0); GetDisplay()->ShowNotification(Lang::Strings::MUTED); }); @@ -180,7 +200,9 @@ class CompactWifiBoard : public WifiBoard { volume_down_button_(VOLUME_DOWN_BUTTON_GPIO) { InitializeDisplayI2c(); InitializeSsd1306Display(); +#ifdef CONFIG_OLED_AUTO_POWER_SAVE InitializePowerSaveTimer(); +#endif InitializeButtons(); InitializeTools(); } @@ -210,12 +232,14 @@ class CompactWifiBoard : public WifiBoard { return &backlight; } +#ifdef CONFIG_OLED_AUTO_POWER_SAVE virtual void SetPowerSaveLevel(PowerSaveLevel level) override { if (level != PowerSaveLevel::LOW_POWER) { power_save_timer_->WakeUp(); } WifiBoard::SetPowerSaveLevel(level); } +#endif }; DECLARE_BOARD(CompactWifiBoard); diff --git a/main/boards/common/backlight.h b/main/boards/common/backlight.h index 5c09b3d22e..3f015ca015 100644 --- a/main/boards/common/backlight.h +++ b/main/boards/common/backlight.h @@ -13,7 +13,7 @@ class Backlight { ~Backlight(); void RestoreBrightness(); - void SetBrightness(uint8_t brightness, bool permanent = false); + virtual void SetBrightness(uint8_t brightness, bool permanent = false); inline uint8_t brightness() const { return brightness_; } protected: diff --git a/main/display/oled_display.cc b/main/display/oled_display.cc index 5f19ab3b04..a2ae753974 100644 --- a/main/display/oled_display.cc +++ b/main/display/oled_display.cc @@ -1,4 +1,5 @@ #include "oled_display.h" +#include "settings.h" #include "assets/lang_config.h" #include "lvgl_theme.h" #include "lvgl_font.h" @@ -413,6 +414,25 @@ OledBacklight::OledBacklight(OledDisplay* display) : display_(display) { brightness_ = 50; } +void OledBacklight::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); + } + + brightness_ = brightness; + target_brightness_ = brightness; + SetBrightnessImpl(brightness); + ESP_LOGI(TAG, "Set OLED brightness to %d", brightness); +} + void OledBacklight::SetBrightnessImpl(uint8_t brightness) { if (brightness <= 5) { display_->SetPowerSaveMode(true); diff --git a/main/display/oled_display.h b/main/display/oled_display.h index 09e4ab8f11..d2a6b3c508 100644 --- a/main/display/oled_display.h +++ b/main/display/oled_display.h @@ -43,7 +43,8 @@ class OledDisplay : public LvglDisplay { class OledBacklight : public Backlight { public: OledBacklight(OledDisplay* display); - virtual void SetBrightnessImpl(uint8_t brightness) override; + void SetBrightness(uint8_t brightness, bool permanent = false) override; + void SetBrightnessImpl(uint8_t brightness) override; private: OledDisplay* display_;