Files
macropad/keytypes.py

353 lines
14 KiB
Python

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
#