From 6716fbc7a1ec5d14887d54b7d0286a149d15d46a Mon Sep 17 00:00:00 2001 From: Sébastien Dailly Date: Sun, 13 Aug 2023 10:45:56 +0200 Subject: Added a sleep function in the keyboard --- src/code.py | 39 +++++++++++++++++++++++++++---------- src/skeleton.py | 15 ++++++++++++--- src/sleep_mode.py | 57 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/ticks.py | 4 ++-- 4 files changed, 100 insertions(+), 15 deletions(-) create mode 100644 src/sleep_mode.py (limited to 'src') diff --git a/src/code.py b/src/code.py index 813574a..ad634ad 100644 --- a/src/code.py +++ b/src/code.py @@ -28,6 +28,7 @@ from supervisor import runtime from actions import Action import skeleton import menu +from sleep_mode import Power # CONFIGURABLES ------------------------ @@ -43,6 +44,8 @@ macropad = MacroPad() macropad.display.auto_refresh = False macropad.pixels.auto_write = False +power = Power(macropad) + Action().set_macropad(macropad) # Load all the macro key setups from .py files in MACRO_FOLDER @@ -76,6 +79,7 @@ if not apps: app_index = 0 + def run_application(app): # Read encoder position. If it's changed, declare it as a button. position = app.macropad.encoder @@ -91,32 +95,41 @@ def run_application(app): event = app.macropad.keys.events.get() if not event: app.tick() + power.tick() return # No key events key_number = event.key_number pressed = event.pressed + power.execute_action(key_number, pressed or False) return app.execute_action(key_number, pressed or False) def switch_layout(configuration): global app app = skeleton.Handler(macropad, configuration) + power.set_configuration(configuration) app.start() def search_and_switch(layout): - found_application = False for local_app in apps: if local_app.title == layout and app is not local_app: - found_application = True switch_layout(local_app) break +def stack(layout): + for configuration in apps: + if configuration.title == layout and app is not configuration: + app.stack(configuration) + app.start() + break + def send_layout(_any): if usb_cdc.data.connected: usb_cdc.data.write(bytes("%s\n" % app.name, "utf-8")) actions = { "layout" : search_and_switch, + "stack" : stack, "get_layout" : send_layout, } @@ -128,13 +141,18 @@ while True: # First read from the serial # If we have an application name from the command line, switch to this app. - if usb_cdc.data.connected and usb_cdc.data.in_waiting != 0: - line = usb_cdc.data.read(usb_cdc.data.in_waiting).strip() - commands = json.loads(line) - for command in commands: - for key, value in command.items(): - actions[key](value) - continue + if usb_cdc.data.connected: + while usb_cdc.data.in_waiting != 0: + line = usb_cdc.data.readline(usb_cdc.data.in_waiting).strip() + try: + commands = json.loads(line) + for command in commands: + for key, value in command.items(): + actions[key](value) + continue + except ValueError: + print(line) + pass # Handle encoder button. If state has changed, and if there's a # corresponding macro, set up variables to act on this just like @@ -152,7 +170,8 @@ while True: if configuration is None: continue switch_layout(configuration) break - send_layout(None) + if configuration.is_layout: + send_layout(None) run_application(app) continue diff --git a/src/skeleton.py b/src/skeleton.py index 8497d34..1e4a03a 100644 --- a/src/skeleton.py +++ b/src/skeleton.py @@ -42,6 +42,9 @@ class Configuration(object): # Set the application available in the menu by default self.visible = True + + self.is_layout = True + self.Action = namedtuple("Action", ("name", "callback", "color")) self.title = title self.keys = { @@ -88,10 +91,16 @@ class Handler(object): 9 : None, 10 : None, 11 : None, } - self.options = configuration.group + self.stack(configuration) + def stack(self, configuration): + """ Add a new configuration on top of an existing one + """ + self.group = displayio.Group() self.name = configuration.title + self.options = configuration.group + for index, value in configuration.keys.items(): if value is None: continue @@ -112,6 +121,7 @@ class Handler(object): self._update_tick() self.macropad.pixels.show() + def _update_tick(self): self.next_tick = ticks.add( ticks_ms(), @@ -162,8 +172,7 @@ class Handler(object): self.macropad.consumer_control.release() self.macropad.mouse.release_all() self.macropad.stop_tone() - for key in range(12): - self._set_color(key, False) + self.macropad.pixels.fill((0, 0, 0)) self.macropad.pixels.show() self.macropad.display.refresh() diff --git a/src/sleep_mode.py b/src/sleep_mode.py new file mode 100644 index 0000000..9c56c24 --- /dev/null +++ b/src/sleep_mode.py @@ -0,0 +1,57 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# The firmware I’m using does not have the function display_sleep available. So +# I’m sending the raw code directly. +# +# https://docs.circuitpython.org/projects/macropad/en/latest/api.html#adafruit_macropad.MacroPad.display_sleep +# + +timeout = 15 * 60 * 1000 +from supervisor import ticks_ms +import ticks + +class Power(object): + + def __init__(self, macropad): + self.macropad = macropad + self.last_update = ticks_ms() + self.configuration = None + self.sleep = False + self.macropad.display.brightness = 0 + self.macropad.display.bus.send(0xAF, b"") + + def start(self): + self.last_update = ticks_ms() + self._wake() + + def set_configuration(self, configuration): + self.configuration = configuration + self.start() + + def _sleep(self): + if self.sleep: + return + self.sleep = True + self.macropad.pixels.fill((0, 0, 0)) + self.macropad.pixels.show() + self.macropad.display.bus.send(0xAE, b"") + + def _wake(self): + if not self.sleep: + return + self.sleep = False + #self.macropad.display_sleep = False + for index, value in self.configuration.keys.items(): + if value is None or index >= 12: + continue + self.macropad.pixels[index] = value.color + self.macropad.pixels.show() + self.macropad.display.bus.send(0xAF, b"") + + def tick(self): + if ticks.diff(ticks_ms(), self.last_update) > timeout: + self._sleep() + + def execute_action(self, key, pressed): + self.start() diff --git a/src/ticks.py b/src/ticks.py index ba6831a..1f7e943 100644 --- a/src/ticks.py +++ b/src/ticks.py @@ -10,7 +10,7 @@ def add(ticks, delta): "Add a delta to a base number of ticks, performing wraparound at 2**29ms." return (ticks + delta) % _TICKS_PERIOD -def ticks_diff(ticks1, ticks2): +def diff(ticks1, ticks2): "Compute the signed difference between two ticks values, assuming that they are within 2**28 ticks" diff = (ticks1 - ticks2) & _TICKS_MAX diff = ((diff + _TICKS_HALFPERIOD) & _TICKS_MAX) - _TICKS_HALFPERIOD @@ -18,4 +18,4 @@ def ticks_diff(ticks1, ticks2): def less(ticks): "Return true iff ticks1 is less than ticks2, assuming that they are within 2**28 ticks" - return ticks_diff(ticks_ms(), ticks) < 0 + return diff(ticks_ms(), ticks) < 0 -- cgit v1.2.3