diff --git a/boards/crkbd/kb.py b/boards/crkbd/kb.py index 297fd2b..5619c1c 100644 --- a/boards/crkbd/kb.py +++ b/boards/crkbd/kb.py @@ -23,6 +23,7 @@ class KMKKeyboard(_KMKKeyboard): uart_pin = board.P0_08 rgb_pixel_pin = board.P0_06 i2c = board.I2C + powersave_pin = board.P0_13 coord_mapping = [] coord_mapping.extend(ic(0, x) for x in range(12)) diff --git a/kmk/extensions/__init__.py b/kmk/extensions/__init__.py index d3cbde1..822d945 100644 --- a/kmk/extensions/__init__.py +++ b/kmk/extensions/__init__.py @@ -43,3 +43,9 @@ class Extension: def after_hid_send(self, keyboard): raise NotImplementedError + + def on_powersave_enable(self, keyboard): + raise NotImplementedError + + def on_powersave_disable(self, keyboard): + raise NotImplementedError diff --git a/kmk/extensions/ble_split.py b/kmk/extensions/ble_split.py index a925db2..c01e217 100644 --- a/kmk/extensions/ble_split.py +++ b/kmk/extensions/ble_split.py @@ -12,7 +12,9 @@ from storage import getmount class BLE_Split(Extension): '''Enables splitting keyboards wirelessly''' - def __init__(self, split_flip=True, split_side=None, hid_type=HIDModes.BLE): + def __init__( + self, split_flip=True, split_side=None, psave_ms=30, hid_type=HIDModes.BLE + ): self._is_target = True self._uart_buffer = [] self.hid_type = hid_type @@ -27,6 +29,8 @@ class BLE_Split(Extension): self._uart_connection = None self._advertisment = None self._advertising = False + self._psave_ms = psave_ms + self._psave_enable = False def __repr__(self): return f'BLE_SPLIT({self._to_dict()})' @@ -90,6 +94,16 @@ class BLE_Split(Extension): def after_hid_send(self, keyboard): return + def on_powersave_enable(self, keyboard): + if self._uart_connection and not self._psave_enable: + self._uart_connection.connection_interval = self._psave_ms + self._psave_enable = True + + def on_powersave_disable(self, keyboard): + if self._uart_connection and self._psave_enable: + self._uart_connection.connection_interval = 11.25 + self._psave_enable = False + def _check_all_connections(self): '''Validates the correct number of BLE connections''' self._connection_count = len(self._ble.connections) diff --git a/kmk/extensions/international.py b/kmk/extensions/international.py index 8b41831..d698819 100644 --- a/kmk/extensions/international.py +++ b/kmk/extensions/international.py @@ -51,3 +51,9 @@ class International(Extension): def after_hid_send(self, keyboard): return + + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return diff --git a/kmk/extensions/layers.py b/kmk/extensions/layers.py index 9dd7456..9f2470c 100644 --- a/kmk/extensions/layers.py +++ b/kmk/extensions/layers.py @@ -82,7 +82,13 @@ class Layers(Extension): def before_hid_send(self, keyboard): return - def _after_hid_send(self, keyboard): + def after_hid_send(self, keyboard): + return + + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): return @staticmethod diff --git a/kmk/extensions/leader.py b/kmk/extensions/leader.py index 36efe97..d9a8c6e 100644 --- a/kmk/extensions/leader.py +++ b/kmk/extensions/leader.py @@ -76,6 +76,12 @@ class Leader(Extension): def after_hid_send(self, keyboard): return + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return + @staticmethod def _compile_sequences(sequences): diff --git a/kmk/extensions/led.py b/kmk/extensions/led.py index d805120..5b5cd11 100644 --- a/kmk/extensions/led.py +++ b/kmk/extensions/led.py @@ -90,6 +90,12 @@ class LED(Extension): return keyboard + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return + def _init_effect(self): self._pos = 0 self._effect_init = False @@ -154,13 +160,10 @@ class LED(Extension): self._pos = (self._pos + self.animation_speed) % 256 self.set_brightness(self._brightness) - return self - def effect_static(self): self.set_brightness(self._brightness) # Set animation mode to none to prevent cycles from being wasted self.animation_mode = None - return self def animate(self): ''' @@ -184,30 +187,23 @@ class LED(Extension): self.animation_mode = AnimationModes.STATIC self._enabled = not self._enabled - return state def _key_led_inc(self, key, state, *args, **kwargs): self.increase_brightness() - return state def _key_led_dec(self, key, state, *args, **kwargs): self.decrease_brightness() - return state def _key_led_ani(self, key, state, *args, **kwargs): self.increase_ani() - return state def _key_led_and(self, key, state, *args, **kwargs): self.decrease_ani() - return state def _key_led_mode_static(self, key, state, *args, **kwargs): self._effect_init = True self.animation_mode = AnimationModes.STATIC - return state def _key_led_mode_breathe(self, key, state, *args, **kwargs): self._effect_init = True self.animation_mode = AnimationModes.BREATHING - return state diff --git a/kmk/extensions/media_keys.py b/kmk/extensions/media_keys.py index 600fadd..916e1fd 100644 --- a/kmk/extensions/media_keys.py +++ b/kmk/extensions/media_keys.py @@ -47,3 +47,9 @@ class MediaKeys(Extension): def after_hid_send(self, keyboard): return + + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return diff --git a/kmk/extensions/modtap.py b/kmk/extensions/modtap.py index e035c9c..a4ddd8f 100644 --- a/kmk/extensions/modtap.py +++ b/kmk/extensions/modtap.py @@ -35,6 +35,12 @@ class ModTap(Extension): def after_hid_send(self, keyboard): return + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return + def mt_pressed(self, key, state, *args, **kwargs): '''Sets the timer start and acts like a modifier otherwise''' state.keys_pressed.add(key.meta.mods) diff --git a/kmk/extensions/power.py b/kmk/extensions/power.py index 01bf148..45f57f0 100644 --- a/kmk/extensions/power.py +++ b/kmk/extensions/power.py @@ -8,23 +8,23 @@ from kmk.kmktime import sleep_ms, ticks_diff, ticks_ms class Power(Extension): - def __init__(self, powersave_pin=None, enable=False, is_target=True): - self.enable = enable + def __init__(self, powersave_pin=None): + self.enable = False self.powersave_pin = powersave_pin # Powersave pin board object - self.is_target = is_target self._powersave_start = ticks_ms() self._usb_last_scan = ticks_ms() - 5000 self._psp = None # Powersave pin object self._i2c = None + self._loopcounter = 0 make_key( names=('PS_TOG',), on_press=self._ps_tog, on_release=handler_passthrough ) make_key( - names=('PS_ENB',), on_press=self._ps_enable, on_release=handler_passthrough + names=('PS_ON',), on_press=self._ps_enable, on_release=handler_passthrough ) make_key( - names=('PS_DIS',), on_press=self._ps_disable, on_release=handler_passthrough + names=('PS_OFF',), on_press=self._ps_disable, on_release=handler_passthrough ) def __repr__(self): @@ -34,7 +34,6 @@ class Power(Extension): return f'''Power( enable={self.enable} powersave_pin={self.powersave_pin} - is_target={self.is_target} _powersave_start={self._powersave_start} _usb_last_scan={self._usb_last_scan} _psp={self._psp} ) @@ -44,32 +43,47 @@ class Power(Extension): return def on_runtime_disable(self, keyboard): - self.disable_powersave + self.disable_powersave() def during_bootup(self, keyboard): - self._detect_i2c() - self.enable = not bool(self.usb_scan) + self._i2c_scan() + return def before_matrix_scan(self, keyboard): - if self.usb_rescan_timer(): - self.enable = not bool(self.usb_scan) + return def after_matrix_scan(self, keyboard, matrix_update): if matrix_update: self.psave_time_reset() - else: - self.psleep() + return def before_hid_send(self, keyboard): return def after_hid_send(self, keyboard): + if self.enable: + self.psleep() + + def on_powersave_enable(self, keyboard): + '''Gives 10 cycles to allow other extentions to clean up before powersave''' + if keyboard._trigger_powersave_enable: + if self._loopcounter > 10: + self._loopcounter += 1 + return + self._loopcounter = 0 + keyboard._trigger_powersave_enable = False + self.enable_powersave(keyboard) return - def enable_powersave(self): + def on_powersave_disable(self, keyboard): + keyboard._trigger_powersave_disable = False + self.disable_powersave() + return + + def enable_powersave(self, keyboard): '''Enables power saving features''' print('Psave True') - if self.powersave_pin: + if keyboard.i2c_deinit_count >= self._i2c and self.powersave_pin: # Allows power save to prevent RGB drain. # Example here https://docs.nicekeyboards.com/#/nice!nano/pinout_schematic @@ -98,18 +112,23 @@ class Power(Extension): ''' Sleeps longer and longer to save power the more time in between updates. ''' - if ticks_diff(ticks_ms(), self._powersave_start) <= 20000 and self.is_target: - sleep_ms(1) - elif ticks_diff(ticks_ms(), self._powersave_start) <= 40000 and self.is_target: - sleep_ms(4) - elif ticks_diff(ticks_ms(), self._powersave_start) <= 60000 and self.is_target: + if ticks_diff(ticks_ms(), self._powersave_start) <= 60000: sleep_ms(8) elif ticks_diff(ticks_ms(), self._powersave_start) >= 240000: - sleep_ms(250) + sleep_ms(180) def psave_time_reset(self): self._powersave_start = ticks_ms() + def _i2c_scan(self): + i2c = board.I2C() + while not i2c.try_lock(): + pass + try: + self._i2c = len(i2c.scan()) + finally: + i2c.unlock() + def usb_rescan_timer(self): return bool(ticks_diff(ticks_ms(), self._usb_last_scan) > 5000) @@ -121,24 +140,16 @@ class Power(Extension): # https://github.com/adafruit/circuitpython/pull/3513 return True - def _detect_i2c(self): - '''Detects i2c devices and disables cutting power to them''' - # TODO Figure out how this could deinit/reinit instead. - self._i2c = board.I2C() - devices = self._i2c.scan() - if devices != []: - self.powersave_pin = None - - def _ps_tog(self): + def _ps_tog(self, key, keyboard, *args, **kwargs): if self.enable: - self.enable_powersave() + keyboard._trigger_powersave_disable = True else: - self.disable_powersave() + keyboard._trigger_powersave_enable = True - def _ps_enable(self): + def _ps_enable(self, key, keyboard, *args, **kwargs): if not self.enable: - self.enable_powersave() + keyboard._trigger_powersave_enable = True - def _ps_disable(self): + def _ps_disable(self, key, keyboard, *args, **kwargs): if self.enable: - self.disable_powersave() + keyboard._trigger_powersave_disable = True diff --git a/kmk/extensions/rgb.py b/kmk/extensions/rgb.py index 24e711d..4b65a85 100644 --- a/kmk/extensions/rgb.py +++ b/kmk/extensions/rgb.py @@ -168,6 +168,12 @@ class RGB(Extension): return keyboard + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + self._do_update() + @staticmethod def time_ms(): return int(time.monotonic() * 1000) @@ -590,4 +596,4 @@ class RGB(Extension): self.val = self.val_default if self.animation_mode == AnimationModes.STATIC_STANDBY: self.animation_mode = AnimationModes.STATIC - self._do_update() + self._do_update() diff --git a/kmk/extensions/split.py b/kmk/extensions/split.py index aa9404b..16bc985 100644 --- a/kmk/extensions/split.py +++ b/kmk/extensions/split.py @@ -96,6 +96,12 @@ class Split(Extension): def after_hid_send(self, keyboard): return + def on_powersave_enable(self, keyboard): + return + + def on_powersave_disable(self, keyboard): + return + def _send(self, update): if self.split_target_left: update[1] += self.split_offset diff --git a/kmk/kmk_keyboard.py b/kmk/kmk_keyboard.py index 2561222..d1f3206 100644 --- a/kmk/kmk_keyboard.py +++ b/kmk/kmk_keyboard.py @@ -1,3 +1,5 @@ +import gc + from kmk.consts import KMK_RELEASE, UnicodeMode from kmk.hid import BLEHID, USBHID, AbstractHID, HIDModes from kmk.keys import KC @@ -39,6 +41,9 @@ class KMKKeyboard: state_changed = False _old_timeouts_len = None _new_timeouts_len = None + _trigger_powersave_enable = False + _trigger_powersave_disable = False + i2c_deinit_count = 0 # this should almost always be PREpended to, replaces # former use of reversed_active_layers which had pointless @@ -345,6 +350,10 @@ class KMKKeyboard: return self + # Only one GC to allow for extentions to have room. + # There are random memory allocations without this + gc.collect() + def go(self, hid_type=HIDModes.USB, **kwargs): self.hid_type = hid_type @@ -372,7 +381,7 @@ class KMKKeyboard: try: self._handle_matrix_report(ext.before_matrix_scan(self)) except Exception as err: - print('Failed to run pre matrix function: ', err) + print('Failed to run pre matrix function: ', err, ext) self.matrix_update = self.matrix.scan_for_changes() @@ -384,7 +393,7 @@ class KMKKeyboard: if self._matrix_modify is not None: self.matrix_update = self._matrix_modify except Exception as err: - print('Failed to run post matrix function: ', err) + print('Failed to run post matrix function: ', err, ext) self._handle_matrix_report(self.matrix_update) self.matrix_update = None @@ -393,7 +402,7 @@ class KMKKeyboard: try: ext.before_hid_send(self) except Exception as err: - print('Failed to run pre hid function: ', err) + print('Failed to run pre hid function: ', err, ext) if self.hid_pending: self._send_hid() @@ -411,7 +420,21 @@ class KMKKeyboard: try: ext.after_hid_send(self) except Exception as err: - print('Failed to run post hid function: ', err) + print('Failed to run post hid function: ', err, ext) + + if self._trigger_powersave_enable: + for ext in self.extensions: + try: + ext.on_powersave_enable(self) + except Exception as err: + print('Failed to run post hid function: ', err, ext) + + if self._trigger_powersave_disable: + for ext in self.extensions: + try: + ext.on_powersave_disable(self) + except Exception as err: + print('Failed to run post hid function: ', err, ext) if self.state_changed: self._print_debug_cycle() diff --git a/user_keymaps/kdb424/corne.py b/user_keymaps/kdb424/corne.py index e8f8f13..d0df84a 100644 --- a/user_keymaps/kdb424/corne.py +++ b/user_keymaps/kdb424/corne.py @@ -7,6 +7,7 @@ import terminalio from adafruit_display_text import label from kb import KMKKeyboard from kmk.extensions.ble_split import BLE_Split +from kmk.extensions.power import Power from kmk.extensions.rgb import RGB from kmk.handlers.sequences import send_string, simple_key_sequence from kmk.hid import HIDModes @@ -26,19 +27,25 @@ SUPER_L = KC.LM(4, KC.LGUI) keyboard.tap_time = 320 keyboard.debug_enabled = False -# TODO Get this out of here -rgb_pixel_pin = board.P0_06 -rgb_ext = RGB(pixel_pin=rgb_pixel_pin, num_pixels=27, val_limit=100, hue_default=190, sat_default=100, val_default=5) +rgb_ext = RGB(pixel_pin=keyboard.rgb_pixel_pin, num_pixels=27, val_limit=100, hue_default=190, sat_default=100, val_default=5) split = BLE_Split(split_side=split_side) -keyboard.extensions = [split, rgb_ext] +power = Power(powersave_pin=keyboard.powersave_pin) -displayio.release_displays() -i2c = board.I2C() -display_bus = displayio.I2CDisplay(i2c, device_address=0x3c) -display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32) -splash = displayio.Group(max_size=10) -display.show(splash) +keyboard.extensions = [split, rgb_ext, power] + +enable_oled = False + +if enable_oled: + displayio.release_displays() + i2c = board.I2C() + display_bus = displayio.I2CDisplay(i2c, device_address=0x3c) + display = adafruit_displayio_ssd1306.SSD1306(display_bus, width=128, height=32) + splash = displayio.Group(max_size=10) + display.show(splash) +else: + displayio.release_displays() + keyboard.i2c_deinit_count += 1 keyboard.keymap = [ # DVORAK @@ -94,7 +101,7 @@ keyboard.keymap = [ # [ # RAISE1 - _______, _______, _______, _______, _______, _______, XXXXXXX, XXXXXXX, KC.N7, KC.N8, KC.N9, KC.DEL, \ + _______, _______, _______, _______, _______, _______, KC.PS_TOG, XXXXXXX, KC.N7, KC.N8, KC.N9, KC.DEL, \ _______, _______, _______, _______, _______, _______, XXXXXXX, XXXXXXX, KC.N4, KC.N5, KC.N6, KC.BSLS, \ _______, _______, _______, _______, _______, _______, XXXXXXX, XXXXXXX, KC.N1, KC.N2, KC.N3, KC.MINS, \ _______, _______, _______, _______, KC.EQL, KC.N0,