Skip to content

feat: add ScreenSleepController and ScreenSleepObserver for display sleep management#293

Open
ndoo wants to merge 1 commit intomeshtastic:masterfrom
Meshtastic-Malaysia:feat/sinusoidal-display-fade
Open

feat: add ScreenSleepController and ScreenSleepObserver for display sleep management#293
ndoo wants to merge 1 commit intomeshtastic:masterfrom
Meshtastic-Malaysia:feat/sinusoidal-display-fade

Conversation

@ndoo
Copy link

@ndoo ndoo commented Mar 16, 2026

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).

DisplayDriver  (non-template base)
└── TFTDriver<TFT>
    └── LGFXDriver<LGFX>                     (primary hardware driver)
        └── owns ScreenSleepController *      exposed via sleepController()

ScreenSleepObserver *                         optional singleton; peripheral backlight sync
    └── e.g. keyboard backlight driver

* new

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.

Changes

  • ScreenSleepController (new, 2 files): owns the full sleep/wake state machine. Fade is 10 s for idle timeout, 1 s for user-initiated sleep or screen lock. Mid-fade activity reverses the animation smoothly from the current brightness level via a backdated sinusoid phase (asinf). InputDriver::instance() call-sites are null-guarded throughout.
  • ScreenSleepObserver (new, 2 files): narrow optional singleton interface for peripherals that need to mirror the display fade (e.g. a keyboard backlight). All call-sites null-guard so it is safe to leave unregistered.
  • DisplayDriver: new virtual methods and static requestWake / requestSleep / isScreenSleeping forwarders as described above.
  • LGFXDriver: task_handler reduced to _sleepController.tick(). setBrightness updates the wake-brightness mirror; setHardwareBrightness drives hardware only.

docs/class-diagram.png needs updating to reflect ScreenSleepController and ScreenSleepObserver.

Test plan

  • T-Deck (t-deck-tft): display dims sinusoidally on idle timeout and on screen lock; wakes smoothly on touch/encoder input; no crash with no registered InputDriver singleton
  • T-Lora Pager: display fade + keyboard backlight sync via ScreenSleepObserver (unmerged downstream work)
  • Device without PWM backlight: blanks/unblanks directly, no regression

@ndoo
Copy link
Author

ndoo commented Mar 16, 2026

Here is what it looks like in practice.

IMG_9818.mov

@ndoo ndoo marked this pull request as ready for review March 16, 2026 05:39
@mverch67
Copy link
Collaborator

The AI-generated code appears completely unwieldy and unmaintainable. It should be moved to a separate class for hiding the screen and not unnecessarily bloat the DisplayDriver interface.

@ndoo
Copy link
Author

ndoo commented Mar 17, 2026

I did consider that, but at the same time it would still have to integrate into display driver in a way that replaces or overloads it.

@ndoo
Copy link
Author

ndoo commented Mar 17, 2026

I have been thinking about how to hook a better wake/sleep mechanism that allows me to also sync and fade the keyboard backlight but did not arrive at any design that doesn't involve hacking at the display driver and input driver invasively.

@mverch67
Copy link
Collaborator

mverch67 commented Mar 17, 2026

I have been thinking about how to hook a better wake/sleep mechanism that allows me to also sync and fade the keyboard backlight but did not arrive at any design that doesn't involve hacking at the display driver and input driver invasively.

You could use an instance of a new ScreenSaver class instantiated in DisplayDriver and allow access to that instance, e.g.

displayDriver->screensaver()->wake()

where screensaver() returns the screenSaver instance.

@ndoo
Copy link
Author

ndoo commented Mar 17, 2026

Sure. Let me hand-refactor it.

@ndoo ndoo marked this pull request as draft March 20, 2026 03:09
…leep 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>
@ndoo ndoo force-pushed the feat/sinusoidal-display-fade branch from 00139bc to 503185c Compare March 20, 2026 06:22
@ndoo ndoo changed the title feat: sinusoidal display dim/wake for devices with PWM backlight feat: add ScreenSleepController and ScreenSleepObserver for display sleep management Mar 20, 2026
@ndoo
Copy link
Author

ndoo commented Mar 20, 2026

I have been thinking about how to hook a better wake/sleep mechanism that allows me to also sync and fade the keyboard backlight but did not arrive at any design that doesn't involve hacking at the display driver and input driver invasively.

You could use an instance of a new ScreenSaver class instantiated in DisplayDriver and allow access to that instance, e.g.

displayDriver->screensaver()->wake()

where screensaver() returns the screenSaver instance.

Done, but screensaver -> ScreenSleepController instead as it feels more descriptive (since screensaver makes it sound like it's to reduce burn in rather than turn off the screen)

Updated the PR text too.

ScreenSleepObserver will be used for keyboard backlight sync, other lighting sync in future.

@ndoo ndoo marked this pull request as ready for review March 20, 2026 06:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants