Macropad hotkeys: Add screensaver mode to prevent OLED burn-in

This commit introduces a screensaver mode to the Adafruit Macropad
hotkeys script, addressing issue #2882.

Key Changes:
- Screensaver Activation: Added a new configurable parameter
  SCREENSAVER_START_TIME, which specifies the duration of inactivity
  (in seconds) before the screensaver activates.
- Class-Level Variables: Introduced class-level variables in the App
  class to track the last activity time, screensaver mode status, and
  breathing effect brightness.
- Screensaver Functions: Added helper functions:
  * enter_screensaver_mode(): Turns off the OLED display and LEDs,
    entering screensaver mode.
  * wake_from_screensaver(): Exits screensaver mode and restores the
    previous state of the display and LEDs.
  * screensaver_breathing_effect(): Implements a breathing LED effect
    during screensaver mode.
- Main Loop Integration: Modified the main loop to monitor inactivity
  and trigger the screensaver mode based on the specified timeout.

This update ensures the Macropad's OLED display is protected from
burn-in during extended periods of inactivity.

Fixes issue #2882.
Signed-off-by: Flavio Fernandes <flavio@flaviof.com>
This commit is contained in:
Flavio Fernandes 2024-09-14 09:26:09 -04:00
parent 24406bfb99
commit fd92dab98c

View file

@ -23,7 +23,7 @@ from adafruit_macropad import MacroPad
# CONFIGURABLES ------------------------ # CONFIGURABLES ------------------------
MACRO_FOLDER = '/macros' MACRO_FOLDER = '/macros'
SCREENSAVER_START_TIME = 10 * 60 # seconds of inactivity will clear oled to avoid burn in
# CLASSES AND FUNCTIONS ---------------- # CLASSES AND FUNCTIONS ----------------
@ -31,6 +31,13 @@ class App:
""" Class representing a host-side application, for which we have a set """ Class representing a host-side application, for which we have a set
of macro sequences. Project code was originally more complex and of macro sequences. Project code was originally more complex and
this was helpful, but maybe it's excessive now?""" this was helpful, but maybe it's excessive now?"""
# Class-level variables (shared among all instances)
last_activity_time = time.monotonic()
in_screensaver_mode = False
breathing_brightness = 0.1 # Initial brightness
breathing_direction = 1 # 1 for increasing brightness, -1 for decreasing
def __init__(self, appdata): def __init__(self, appdata):
self.name = appdata['name'] self.name = appdata['name']
self.macros = appdata['macros'] self.macros = appdata['macros']
@ -38,6 +45,7 @@ class App:
def switch(self): def switch(self):
""" Activate application settings; update OLED labels and LED """ Activate application settings; update OLED labels and LED
colors. """ colors. """
App.last_activity_time = time.monotonic()
group[13].text = self.name # Application name group[13].text = self.name # Application name
if self.name: if self.name:
rect.fill = 0xFFFFFF rect.fill = 0xFFFFFF
@ -57,6 +65,29 @@ class App:
macropad.pixels.show() macropad.pixels.show()
macropad.display.refresh() macropad.display.refresh()
# SCREENSAVER MODE HELPERS -------------
def enter_screensaver_mode():
macropad.display.auto_refresh = False
macropad.display_sleep = True
for i in range(12):
macropad.pixels[i] = 0 # Turn off all key LEDs
macropad.pixels.show()
App.in_screensaver_mode = True
def wake_from_screensaver():
App.in_screensaver_mode = False
macropad.display_sleep = False
macropad.display.auto_refresh = True
apps[app_index].switch() # Redraw the OLED and LEDs
def screensaver_breathing_effect():
App.breathing_brightness += 0.001 * App.breathing_direction
if App.breathing_brightness >= 1.0 or App.breathing_brightness <= 0.1:
App.breathing_direction *= -1 # Reverse direction
pixel_brightness = int(255 * App.breathing_brightness)
macropad.pixels[0] = (0, pixel_brightness, 0) # Green key for breathing effect
macropad.pixels.show()
# INITIALIZATION ----------------------- # INITIALIZATION -----------------------
@ -111,9 +142,13 @@ apps[app_index].switch()
# MAIN LOOP ---------------------------- # MAIN LOOP ----------------------------
while True: while True:
current_time = time.monotonic()
# Read encoder position. If it's changed, switch apps. # Read encoder position. If it's changed, switch apps.
position = macropad.encoder position = macropad.encoder
if position != last_position: if position != last_position:
if App.in_screensaver_mode:
wake_from_screensaver()
app_index = position % len(apps) app_index = position % len(apps)
apps[app_index].switch() apps[app_index].switch()
last_position = position last_position = position
@ -132,6 +167,12 @@ while True:
else: else:
event = macropad.keys.events.get() event = macropad.keys.events.get()
if not event or event.key_number >= len(apps[app_index].macros): if not event or event.key_number >= len(apps[app_index].macros):
if App.in_screensaver_mode:
screensaver_breathing_effect() # Continue breathing effect in screensaver mode
else:
time_since_last_activity = current_time - App.last_activity_time
if time_since_last_activity > SCREENSAVER_START_TIME:
enter_screensaver_mode()
continue # No key events, or no corresponding macro, resume loop continue # No key events, or no corresponding macro, resume loop
key_number = event.key_number key_number = event.key_number
pressed = event.pressed pressed = event.pressed
@ -140,8 +181,13 @@ while True:
# and there IS a corresponding macro available for it...other situations # and there IS a corresponding macro available for it...other situations
# are avoided by 'continue' statements above which resume the loop. # are avoided by 'continue' statements above which resume the loop.
App.last_activity_time = current_time # Reset inactivity timer
sequence = apps[app_index].macros[key_number][2] sequence = apps[app_index].macros[key_number][2]
if pressed: if pressed:
if App.in_screensaver_mode:
wake_from_screensaver()
continue # Skip this event, as it was used for screen wake up
# 'sequence' is an arbitrary-length list, each item is one of: # 'sequence' is an arbitrary-length list, each item is one of:
# Positive integer (e.g. Keycode.KEYPAD_MINUS): key pressed # Positive integer (e.g. Keycode.KEYPAD_MINUS): key pressed
# Negative integer: (absolute value) key released # Negative integer: (absolute value) key released