from micropython import const from pin import Pin, PinEvent _VALID_KEYCODES = range(0, 255) _VALID_MODIFIERS = [0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000] class KeyEvent: """Interpretations of KeyStates as single events""" NO_EVENT: int = const(0) """No new event has occurred""" PRESSED_: int = const(1) """The key was pressed""" RELEASED: int = const(2) """The key was released""" SOFT_RELEASED: int = const(3) """The key was released programatically, and not by an actual physical release of a button.""" PRESSED_TOGGLED_ON = const(4) """Key is pressed and is emulating a toggle key in ON state that is not released until the physical key is released, then pressed again""" RELEASED_TOGGLED_ON = const(5) """Key is unpressed and is emulating a toggle key in ON state that is not released unit the physical key is pressed again""" PRESSED_TOGGLED_OFF = const(6) """Key is pressed and is emulating a toggle key in OFF state""" class KeyEvents: current: int previous: int def __init__(self): self.current = KeyEvent.RELEASED self.previous = KeyEvent.RELEASED def shift(self, next): print( f"next: {next}, current: {self.current}, previous: {self.previous}") self.previous = self.current self.current = next class KeyBase: events: KeyEvents allowed_events: list[KeyEvent] key_index: int layer_index: int def __init__(self, allowed_events: list[int]): self.allowed_events = allowed_events self.events = KeyEvents() self.key_index = None self.layer_index = None def enrich(self, key_index: int, layer_index: int): self.key_index = key_index self.layer_index = layer_index def _validate_next_event(self, next_event: int): if next_event is None or next_event not in self.allowed_events: raise Exception( f"KeyEvent with value of {next_event} is not a valid event for this type of key") def _set_event(self, next_event: int) -> bool: """Sets previous state if new state is valid Returns: A boolean that determines if the state was updated or not""" self._validate_next_event(next_event) if next_event is KeyEvent.NO_EVENT: return False self.events.shift(next_event) return True def handle(self, keyboard): raise NotImplementedError() def self_test(self, keymap: list[list[object]]): raise NotImplementedError() def soft_release(self, keyboard, pin): """Release key programatically and wait until it's released physically before registrating additional key presses""" raise NotImplementedError() class KeycodeBase(KeyBase): """Basic press and release functionality that appends a keycode to the report when active""" keycode: int def __init__(self, keycode: int): allowed_events = [KeyEvent.NO_EVENT, KeyEvent.PRESSED_, KeyEvent.RELEASED, KeyEvent.SOFT_RELEASED] super().__init__(allowed_events) self.keycode = keycode def self_test(self, keymap): raise NotImplementedError def soft_release(self, keyboard, pin): if self.events.current != KeyEvent.PRESSED_: return self._set_event(KeyEvent.SOFT_RELEASED) def _consume_next_event(self, pin) -> bool: """Returns: a boolean representing if the event property was updated with a new event or not""" next_pin_event = pin.read_event() current_key_event, previous_key_event = self.events.current, self.events.previous next_key_event = None if current_key_event == KeyEvent.NO_EVENT: if next_pin_event == PinEvent.NO_EVENT: next_key_event = KeyEvent.NO_EVENT if next_pin_event == PinEvent.RELEASED: next_key_event = KeyEvent.RELEASED if next_pin_event == PinEvent.PRESSED: next_key_event = KeyEvent.PRESSED_ if current_key_event == KeyEvent.RELEASED: if next_pin_event == PinEvent.NO_EVENT: next_key_event = KeyEvent.NO_EVENT if next_pin_event == PinEvent.RELEASED: next_key_event = KeyEvent.NO_EVENT if next_pin_event == PinEvent.PRESSED: next_key_event = KeyEvent.PRESSED_ if current_key_event == KeyEvent.PRESSED_: if next_pin_event == PinEvent.NO_EVENT: next_key_event = KeyEvent.NO_EVENT if next_pin_event == PinEvent.RELEASED: next_key_event = KeyEvent.RELEASED if next_pin_event == PinEvent.PRESSED: next_key_event = KeyEvent.NO_EVENT if current_key_event == KeyEvent.SOFT_RELEASED: if next_pin_event == PinEvent.NO_EVENT: next_key_event = KeyEvent.NO_EVENT if next_pin_event == PinEvent.RELEASED: next_key_event = KeyEvent.RELEASED if next_pin_event == PinEvent.PRESSED: next_key_event = KeyEvent.NO_EVENT return self._set_event(next_key_event) def _handle_event(self, keyboard) -> None: current = self.events.current if current == KeyEvent.NO_EVENT: return if current == KeyEvent.PRESSED_: keyboard.add_keycode_to_report(self.keycode) if current == KeyEvent.RELEASED: keyboard.remove_keycode_from_report(self.keycode) if current == KeyEvent.SOFT_RELEASED: keyboard.remove_keycode_from_report(self.keycode) def handle(self, keyboard, pin): if not self._consume_next_event(pin): return self._handle_event(keyboard) class Keycode(KeycodeBase): def __init__(self, keycode): super().__init__(keycode) def self_test(self, keymap): if not self.keycode in _VALID_KEYCODES: raise Exception("Keycode is not valid") class Modifier(KeycodeBase): def __init__(self, keycode): super().__init__(keycode) def self_test(self, keymap): if not self.keycode in _VALID_MODIFIERS: raise Exception("Keycode is not valid") class LayerKeyBase(KeyBase): layer_to_switch_to: int def __init__(self, layer: int, allowed_events): super().__init__(allowed_events) self.layer_to_switch_to = layer class Toggle(LayerKeyBase): def __init__(self, layer: int): allowed_events = [ KeyEvent.NO_EVENT, KeyEvent.PRESSED_TOGGLED_ON, KeyEvent.RELEASED_TOGGLED_ON, KeyEvent.PRESSED_TOGGLED_OFF, KeyEvent.RELEASED, KeyEvent.SOFT_RELEASED] has_received_state_from = None super().__init__(layer, allowed_events) def _consume_next_event(self, pin: Pin) -> bool: """Returns: a boolean representing if the event property was updated with a new event or not""" next_pin_event = pin.read_event() current_key_event = self.events.current if next_pin_event == PinEvent.NO_EVENT: return self._set_event(KeyEvent.NO_EVENT) if current_key_event == KeyEvent.PRESSED_TOGGLED_ON: if next_pin_event == PinEvent.PRESSED: return self._set_event(KeyEvent.NO_EVENT) if next_pin_event == PinEvent.RELEASED: return self._set_event(KeyEvent.RELEASED_TOGGLED_ON) if current_key_event == KeyEvent.RELEASED_TOGGLED_ON: if next_pin_event == PinEvent.PRESSED: return self._set_event(KeyEvent.PRESSED_TOGGLED_OFF) if next_pin_event == PinEvent.RELEASED: return self._set_event(KeyEvent.NO_EVENT) if current_key_event == KeyEvent.PRESSED_TOGGLED_OFF: if next_pin_event == PinEvent.PRESSED: return self._set_event(KeyEvent.NO_EVENT) if next_pin_event == PinEvent.RELEASED: return self._set_event(KeyEvent.RELEASED) if current_key_event == KeyEvent.RELEASED: if next_pin_event == PinEvent.PRESSED: return self._set_event(KeyEvent.PRESSED_TOGGLED_ON) if next_pin_event == PinEvent.RELEASED: return self._set_event(KeyEvent.NO_EVENT) if current_key_event == KeyEvent.SOFT_RELEASED: if next_pin_event == PinEvent.RELEASED: return self._set_event(KeyEvent.RELEASED) else: return self._set_event(KeyEvent.NO_EVENT) raise NotImplementedError() def _release_all_other_keys(self, keyboard, pin: Pin): for layer in keyboard.keymap: for key in layer: if key is self: continue pin_of_key = keyboard.pins[layer.index(key)] key.soft_release(keyboard, pin_of_key) def _handle_event(self, keyboard, pin): current = self.events.current if current == KeyEvent.PRESSED_TOGGLED_ON: keyboard.layer.toggle(self.layer_to_switch_to) self._release_all_other_keys(keyboard, pin) self._swap_state(keyboard) if current == KeyEvent.RELEASED_TOGGLED_ON: pass if current == KeyEvent.PRESSED_TOGGLED_OFF: keyboard.layer.release_toggled() self._release_all_other_keys(keyboard, pin) self._swap_state_back(keyboard) if current == KeyEvent.RELEASED: pass def handle(self, keyboard, pin: Pin): if not self._consume_next_event(pin): return self._handle_event(keyboard, pin) def self_test(self, keymap: list[list[object]]): raise NotImplementedError() def soft_release(self, keyboard, pin): pressed = self.events.current not in [ KeyEvent.RELEASED, KeyEvent.PRESSED_TOGGLED_OFF, KeyEvent.SOFT_RELEASED] if not pressed: return print(f"releasing {keyboard}, {self.events.current}") self.has_received_state_from = None self._set_event(KeyEvent.SOFT_RELEASED) def _swap_state(self, keyboard) -> None: key_to_swap_with = keyboard.get_key( self.layer_to_switch_to, self.key_index) other_keys_state = key_to_swap_with._handle_on_swap_state( self.events, (self.layer_index, self.key_index)) self.events = other_keys_state def _swap_state_back(self, keyboard) -> None: if not self.has_received_state_from: return key_to_swap_with = keyboard.get_key( self.has_received_state_from[0], self.has_received_state_from[1]) other_keys_state = key_to_swap_with._handle_on_swap_state( self.events, None) self.events = other_keys_state def _handle_on_swap_state(self, received_state: KeyEvents, indexes: tuple[int, int]) -> KeyEvents: """Invoked on the Toggle key that is present on the layer another Toggle key is switching to Makes sure that the state is carried over so that pressing the button once more returns to the old layer.""" self.has_received_state_from = indexes state_to_send_back = self.events self.events = received_state return state_to_send_back # todo: Release all keys not the same as the old layer # todo: Debounce old toggle when pressing new toggle # if type(key) is Toggle: # if currentlyPressed and not previouslyToggled and not previouslyToggledReleased and not previouslyUntoggledPressed: # self.toggled_layer = key.layer # self.held_layer = None # self.pin_states_last_cycle[pin_index] = ( # __TOGGLED_PRESSED, key) # for i in range(len(self.pin_states_last_cycle)): # if i == pin_index or len(self.pin_states_last_cycle[i]) == 1: # continue # value, last_keycode = self.pin_states_last_cycle[i] # if type(last_keycode) is Toggle: # self.pin_states_last_cycle[i] = ( # __UNPRESSED,) # if type(last_keycode) is Hold: # self.held_layer = None # self.pin_states_last_cycle[i] = ( # __DEBOUNCE,) # continue # if not currentlyPressed and previouslyToggled: # self.pin_states_last_cycle[pin_index] = ( # __TOGGLED_RELEASED, key) # continue # if currentlyPressed and previouslyToggledReleased: # self.toggled_layer = 0 # self.pin_states_last_cycle[pin_index] = ( # __UNTOGGLED_PRESSED, key) # continue # if not currentlyPressed and previouslyUntoggledPressed: # self.pin_states_last_cycle[pin_index] = ( # __UNPRESSED,) # continue # class Hold(LayerKeyBase): def __init__(self, layer: int): allowed_events = [KeyEvent.SOFT_RELEASED] super().__init__(layer, allowed_events) def soft_release(self, keyboard, pin): pass # todo: Release all keys not the same as the old layer # if type(key) is Hold: # if not currentlyPressed and previouslyDebounced: # self.pin_states_last_cycle[pin_index] = ( # __UNPRESSED,) # # if currentlyPressed and not previouslyPressed and not previouslyDebounced: # self.held_layer = key.layer # self.pin_states_last_cycle[pin_index] = ( # __PRESSED, key) # continue # if previouslyPressed and not currentlyPressed: # self.held_layer = None # self.pin_states_last_cycle[pin_index] = ( # __UNPRESSED,) # continue #