Skip to content

Commit 503185c

Browse files
committed
feat: add ScreenSleepController and ScreenSleepObserver for display sleep management
Extracts the display sleep/wake state machine from LGFXDriver::task_handler into a dedicated ScreenSleepController class, and adds a ScreenSleepObserver hook for peripheral backlight synchronisation (e.g. keyboard LED). Key design points: - Sinusoidal backlight fade (cos ease-out to sleep, sin ease-in to wake) replaces the previous linear step-per-tick approach. - Animation frames use setHardwareBrightness() so they never corrupt the wakeBrightness mirror; only the user-facing setBrightness() updates it. - A fresh dim clears wakeStartTime so a stale post-powersave wake animation cannot cause a brightness snap when activity later interrupts the dim. - The no-backlight activity-wake condition is guarded by !sleepRequested, matching the backlight path, so a manual sleep() request does not immediately re-wake on no-backlight devices. - Static requestWake/requestSleep/isScreenSleeping forwarders on DisplayDriver allow any driver context (e.g. I2C keyboard) to signal the controller without a direct pointer to LGFXDriver. - New virtual methods on DisplayDriver (panelSleep, panelWake, powerSaveOn, powerSaveOff, hasBacklight, getTouchIntPin, setHardwareBrightness) follow the established no-op default pattern so non-LGFX drivers are unaffected. - ScreenSleepObserver is an optional singleton; all call-sites null-guard so devices that don't need peripheral sync can leave it unregistered. docs/class-diagram.png needs updating (ScreenSleepController, ScreenSleepObserver). Signed-off-by: Andrew Yong <me@ndoo.sg>
1 parent f36d2a9 commit 503185c

7 files changed

Lines changed: 371 additions & 93 deletions

File tree

include/graphics/driver/DisplayDriver.h

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
#include "graphics/DeviceGUI.h"
44
#include "graphics/LVGL/LVGLGraphics.h"
5+
#include "graphics/driver/ScreenSleepController.h"
56
#include <cstdint>
67

78
#define H_NORM_PX(h_scr_percent) ((int16_t)((screenWidth / 100.0) * (h_scr_percent)))
@@ -25,7 +26,17 @@ class DisplayDriver
2526
virtual ~DisplayDriver() {}
2627

2728
virtual uint8_t getBrightness() { return 255; }
28-
virtual void setBrightness(uint8_t timeout) {}
29+
// setBrightness is the user-facing path: updates the wakeBrightness mirror in ScreenSleepController.
30+
// setHardwareBrightness is the animation-internal path: drives hardware only, leaves the mirror untouched.
31+
virtual void setBrightness(uint8_t brightness) {}
32+
virtual void setHardwareBrightness(uint8_t brightness) {}
33+
34+
virtual void panelSleep(void) {}
35+
virtual void panelWake(void) {}
36+
virtual void powerSaveOn(void) {}
37+
virtual void powerSaveOff(void) {}
38+
virtual bool hasBacklight(void) { return false; }
39+
virtual int getTouchIntPin(void) { return -1; }
2940

3041
virtual uint16_t getScreenTimeout() { return 0; }
3142
virtual void setScreenTimeout(uint16_t timeout) {}
@@ -35,11 +46,19 @@ class DisplayDriver
3546

3647
lv_display_t *getDisplay(void) { return display; }
3748

49+
ScreenSleepController *sleepController(void) { return _sleepController; }
50+
51+
// signal wake/sleep from any context (e.g. I2C keyboard driver without GPIO interrupt)
52+
static void requestWake(void) { if (_sleepController) _sleepController->wake(); }
53+
static void requestSleep(void) { if (_sleepController) _sleepController->sleep(); }
54+
static bool isScreenSleeping(void) { return _sleepController && _sleepController->isSleeping(); }
55+
3856
protected:
3957
LVGLGraphics lvgl;
4058
LVGLDisplay *display;
4159
LVGLTouch *touch;
4260
DeviceGUI *view;
4361
uint16_t screenWidth;
4462
uint16_t screenHeight;
63+
static ScreenSleepController *_sleepController;
4564
};

include/graphics/driver/LGFXDriver.h

Lines changed: 33 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
#include "LovyanGFX.h"
44
#include "graphics/driver/DisplayDriverConfig.h"
5+
#include "graphics/driver/ScreenSleepController.h"
56
#include "graphics/driver/TFTDriver.h"
67
#include "input/InputDriver.h"
8+
#include "input/ScreenSleepObserver.h"
79
#include "lvgl_private.h"
810
#include "util/ILog.h"
911
#include <functional>
@@ -12,7 +14,7 @@ constexpr uint32_t defaultLongPressTime = 600; // ms until long press is detecte
1214
constexpr uint32_t defaultGestureLimit = 10; // x/y diff pixel until a swipe gesture is detected (lvgl default is 50)
1315

1416
constexpr uint32_t defaultScreenTimeout = 30 * 1000;
15-
constexpr uint32_t defaultBrightness = 153;
17+
constexpr uint8_t defaultBrightness = 153;
1618

1719
template <class LGFX> class LGFXDriver : public TFTDriver<LGFX>
1820
{
@@ -24,15 +26,23 @@ template <class LGFX> class LGFXDriver : public TFTDriver<LGFX>
2426
bool hasTouch(void) override;
2527
bool hasButton(void) override { return lgfx->hasButton(); }
2628
bool hasLight(void) override { return lgfx->light(); }
27-
bool isPowersaving(void) override { return powerSaving; }
29+
bool isPowersaving(void) override { return _sleepController.isSleeping(); }
2830
void printConfig(void) override;
2931
void task_handler(void) override;
3032

31-
uint8_t getBrightness(void) override { return lgfx->getBrightness(); }
32-
void setBrightness(uint8_t brightness) override;
33+
uint8_t getBrightness(void) override { return lgfx->getBrightness(); }
34+
void setHardwareBrightness(uint8_t brightness) override { lgfx->setBrightness(brightness); }
35+
void setBrightness(uint8_t brightness) override;
3336

3437
uint16_t getScreenTimeout() override { return screenTimeout / 1000; }
35-
void setScreenTimeout(uint16_t timeout) override { screenTimeout = timeout * 1000; };
38+
void setScreenTimeout(uint16_t timeout) override { screenTimeout = (uint32_t)timeout * 1000; };
39+
40+
void panelSleep(void) override { lgfx->sleep(); }
41+
void panelWake(void) override { lgfx->wakeup(); }
42+
void powerSaveOn(void) override { lgfx->powerSaveOn(); }
43+
void powerSaveOff(void) override { lgfx->powerSaveOff(); }
44+
bool hasBacklight(void) override { return lgfx->light() != nullptr; }
45+
int getTouchIntPin(void) override;
3646

3747
protected:
3848
// lvgl callbacks have to be static cause it's a C library, not C++
@@ -41,13 +51,12 @@ template <class LGFX> class LGFXDriver : public TFTDriver<LGFX>
4151
static void touchpad_read(lv_indev_t *indev_driver, lv_indev_data_t *data);
4252

4353
uint32_t screenTimeout;
44-
uint32_t lastBrightness;
45-
bool powerSaving;
4654

4755
private:
4856
void init_lgfx(void);
4957

5058
static LGFX *lgfx;
59+
ScreenSleepController _sleepController;
5160
size_t bufsize;
5261
lv_color_t *buf1;
5362
lv_color_t *buf2;
@@ -59,15 +68,17 @@ template <class LGFX> LGFX *LGFXDriver<LGFX>::lgfx = nullptr;
5968
template <class LGFX>
6069
LGFXDriver<LGFX>::LGFXDriver(uint16_t width, uint16_t height)
6170
: TFTDriver<LGFX>(lgfx ? lgfx : new LGFX, width, height), screenTimeout(defaultScreenTimeout),
62-
lastBrightness(defaultBrightness), powerSaving(false), bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
71+
_sleepController(this, defaultBrightness),
72+
bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
6373
{
6474
lgfx = this->tft;
6575
}
6676

6777
template <class LGFX>
6878
LGFXDriver<LGFX>::LGFXDriver(const DisplayDriverConfig &cfg)
6979
: TFTDriver<LGFX>(lgfx ? lgfx : new LGFX(cfg), cfg.width(), cfg.height()), screenTimeout(defaultScreenTimeout),
70-
lastBrightness(defaultBrightness), powerSaving(false), bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
80+
_sleepController(this, defaultBrightness),
81+
bufsize(0), buf1(nullptr), buf2(nullptr), calibrating(false)
7182
{
7283
lgfx = this->tft;
7384
}
@@ -81,91 +92,18 @@ template <class LGFX> bool LGFXDriver<LGFX>::hasTouch(void)
8192
#endif
8293
}
8394

84-
template <class LGFX> void LGFXDriver<LGFX>::task_handler(void)
95+
template <class LGFX> int LGFXDriver<LGFX>::getTouchIntPin(void)
8596
{
86-
// handle display timeout
87-
if ((screenTimeout > 0 && lv_display_get_inactive_time(NULL) > screenTimeout) || powerSaving ||
88-
(DisplayDriver::view->isScreenLocked())) {
89-
// sleep screen only if there are means for wakeup
90-
if (DisplayDriver::view->getInputDriver()->hasPointerDevice() || hasTouch() ||
91-
DisplayDriver::view->getInputDriver()->hasKeyboardDevice() || hasButton()) {
92-
if (hasLight()) {
93-
if (!powerSaving) {
94-
// dim display brightness slowly down
95-
uint32_t brightness = lgfx->getBrightness();
96-
if (brightness > 0) {
97-
lgfx->setBrightness(brightness - 1);
98-
} else {
99-
ILOG_INFO("enter powersave");
100-
DisplayDriver::view->screenSaving(true);
101-
if (hasTouch() && hasButton()) {
102-
ILOG_DEBUG("disable touch, enable button input");
103-
lv_indev_enable(DisplayDriver::touch, false);
104-
lv_indev_enable(InputDriver::instance()->getButton(), true);
105-
}
106-
lgfx->sleep();
107-
lgfx->powerSaveOn();
108-
powerSaving = true;
109-
}
110-
}
111-
if (powerSaving) {
112-
int pin_int = -1;
113-
if (hasTouch()) {
11497
#ifndef CUSTOM_TOUCH_DRIVER
115-
pin_int = lgfx->touch()->config().pin_int;
98+
return lgfx->touch() ? lgfx->touch()->config().pin_int : -1;
11699
#else
117-
pin_int = lgfx->getTouchInt();
118-
#endif
119-
}
120-
if (hasButton()) {
121-
#ifdef BUTTON_PIN // only relevant for CYD scenario
122-
pin_int = BUTTON_PIN;
100+
return lgfx->getTouchInt();
123101
#endif
124-
}
125-
if ((pin_int >= 0 && DisplayDriver::view->sleep(pin_int)) ||
126-
(screenTimeout + 50 > lv_display_get_inactive_time(NULL) && !DisplayDriver::view->isScreenLocked())) {
127-
delay(2); // let the CPU finish to restore all register in case of light sleep
128-
// woke up by touch or button
129-
ILOG_INFO("leaving powersave");
130-
powerSaving = false;
131-
DisplayDriver::view->triggerHeartbeat();
132-
lgfx->powerSaveOff();
133-
lgfx->wakeup();
134-
lgfx->setBrightness(lastBrightness);
135-
DisplayDriver::view->screenSaving(false);
136-
if (hasTouch() && hasButton()) {
137-
ILOG_DEBUG("enable touch, disable button input");
138-
lv_indev_enable(DisplayDriver::touch, true);
139-
lv_indev_enable(InputDriver::instance()->getButton(), false);
140-
}
141-
lv_display_trigger_activity(NULL);
142-
} else {
143-
// we woke up due to e.g. serial traffic (or sleep() simply not implemented)
144-
// continue with processing loop and enter sleep() again next round
145-
}
146-
}
147-
}
148-
// no BL pin defined to control brightness, so show blank screen instead
149-
else {
150-
if (!powerSaving) {
151-
DisplayDriver::view->blankScreen(true);
152-
lgfx->sleep();
153-
lgfx->powerSaveOn();
154-
powerSaving = true;
155-
}
156-
if (screenTimeout > lv_display_get_inactive_time(NULL)) {
157-
DisplayDriver::view->blankScreen(false);
158-
lgfx->powerSaveOff();
159-
lgfx->wakeup();
160-
powerSaving = false;
161-
lv_disp_trig_activity(NULL);
162-
}
163-
}
164-
}
165-
} else if (lgfx->getBrightness() < lastBrightness) {
166-
lgfx->setBrightness(lastBrightness);
167-
lastBrightness = lgfx->getBrightness();
168-
}
102+
}
103+
104+
template <class LGFX> void LGFXDriver<LGFX>::task_handler(void)
105+
{
106+
_sleepController.tick(screenTimeout, hasTouch(), hasButton());
169107

170108
if (!calibrating) {
171109
DisplayDriver::task_handler();
@@ -337,6 +275,9 @@ template <class LGFX> void LGFXDriver<LGFX>::init(DeviceGUI *gui)
337275
lv_timer_set_period(timer, 10); // 100Hz as I2C touch controllers support
338276
#endif
339277
}
278+
279+
_sleepController.setContext(DisplayDriver::view, DisplayDriver::touch);
280+
DisplayDriver::_sleepController = &_sleepController;
340281
}
341282

342283
template <class LGFX> void LGFXDriver<LGFX>::init_lgfx(void)
@@ -413,8 +354,8 @@ template <class LGFX> bool LGFXDriver<LGFX>::calibrate(uint16_t parameters[8])
413354

414355
template <class LGFX> void LGFXDriver<LGFX>::setBrightness(uint8_t brightness)
415356
{
416-
lgfx->setBrightness(brightness);
417-
lastBrightness = brightness;
357+
setHardwareBrightness(brightness);
358+
_sleepController.setWakeBrightness(brightness);
418359
}
419360

420361
template <class LGFX> void LGFXDriver<LGFX>::printConfig(void)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
#pragma once
2+
3+
#include "lvgl.h"
4+
#include <cstdint>
5+
6+
class DisplayDriver;
7+
class DeviceGUI;
8+
9+
/**
10+
* @brief Controls the display sleep/wake state machine and sinusoidal backlight
11+
* fade animations. Owned by LGFXDriver; accessible externally via
12+
* DisplayDriver::sleepController().
13+
*
14+
* Unlike a traditional screensaver (which keeps the screen on to prevent
15+
* burn-in), this class powers the display off entirely and coordinates
16+
* peripheral backlight synchronisation via ScreenSleepObserver.
17+
*/
18+
class ScreenSleepController
19+
{
20+
public:
21+
explicit ScreenSleepController(DisplayDriver *panel, uint8_t wakeBrightness);
22+
void setContext(DeviceGUI *view, lv_indev_t *touch);
23+
24+
void wake(void); // request wake — safe to call from any context
25+
void sleep(void); // request sleep — safe to call from any context
26+
bool isSleeping(void) const;
27+
28+
void tick(uint32_t screenTimeout, bool hasTouch, bool hasButton);
29+
30+
void setWakeBrightness(uint8_t brightness);
31+
uint8_t getWakeBrightness(void) const;
32+
33+
private:
34+
void dimStep(bool hasTouch, bool hasButton);
35+
void wakeStep(void);
36+
void enterPowerSave(bool hasTouch, bool hasButton);
37+
void exitPowerSave(bool hasTouch, bool hasButton);
38+
void checkWakeConditions(uint32_t screenTimeout, bool hasTouch, bool hasButton);
39+
void startWakeFromCurrent(uint8_t targetBrightness);
40+
41+
// ms for the brightness fade triggered by a manual sleep request or screen-lock
42+
static constexpr uint16_t defaultDimDuration = 1000;
43+
// ms for the brightness fade triggered by the inactivity timeout
44+
static constexpr uint16_t timeoutDimDuration = 10000;
45+
46+
DisplayDriver *panel;
47+
DeviceGUI *view = nullptr;
48+
lv_indev_t *touch = nullptr;
49+
50+
volatile bool wakeRequested = false;
51+
volatile bool sleepRequested = false;
52+
bool powerSaving = false;
53+
54+
// Target brightness to restore after waking. Mirrored here on every user-facing
55+
// setBrightness() call; animation frames use setHardwareBrightness() so they never
56+
// corrupt this value.
57+
uint8_t wakeBrightness = 0;
58+
uint32_t dimStartTime = 0;
59+
uint8_t dimStartBrightness = 0;
60+
uint16_t dimDuration = 0;
61+
uint32_t wakeStartTime = 0;
62+
};
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
#pragma once
2+
3+
/**
4+
* @brief Optional observer that synchronizes peripheral backlights (e.g. a keyboard
5+
* LED) with the display fade animation. Register via setInstance(); all
6+
* call-sites null-guard, so it is safe to leave unset on devices that don't need it.
7+
*/
8+
class ScreenSleepObserver
9+
{
10+
public:
11+
static ScreenSleepObserver *instance(void) { return observer; }
12+
static void setInstance(ScreenSleepObserver *obs) { observer = obs; }
13+
14+
virtual void onScreenSleep(void) {}
15+
// progress: animation progress 0.0–1.0 (0 = animation start, 1 = animation complete);
16+
// fadingIn: true = waking (0→full brightness), false = dimming (full brightness→0)
17+
virtual void applyBrightnessProgress(float progress, bool fadingIn) {}
18+
19+
virtual ~ScreenSleepObserver() = default;
20+
21+
protected:
22+
static ScreenSleepObserver *observer;
23+
};

source/graphics/driver/DisplayDriver.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
#include "graphics/driver/DisplayDriver.h"
22
#include "util/ILog.h"
33

4+
ScreenSleepController *DisplayDriver::_sleepController = nullptr;
5+
46
#if LV_USE_PROFILER
57
#if defined(ARCH_PORTDUINO)
68
#include <sys/syscall.h>

0 commit comments

Comments
 (0)