From 38c5b6e78bb7840a2f749000632e1b39a8dd9f0b Mon Sep 17 00:00:00 2001 From: Zackarias Montell Date: Thu, 6 Jan 2022 12:06:18 +0100 Subject: [PATCH] layers and keymap test --- .gitignore | 0 code.py | 4 +- config.py | 43 ++++++++++---- keyboard.py | 167 ++++++++++++++++++++++++++++++++-------------------- keytypes.py | 16 +++++ tests.py | 40 +++++++++++++ 6 files changed, 194 insertions(+), 76 deletions(-) mode change 100755 => 100644 .gitignore mode change 100644 => 100755 code.py mode change 100644 => 100755 config.py mode change 100644 => 100755 keyboard.py create mode 100644 keytypes.py create mode 100755 tests.py diff --git a/.gitignore b/.gitignore old mode 100755 new mode 100644 diff --git a/code.py b/code.py old mode 100644 new mode 100755 index 7c5ee06..b18e934 --- a/code.py +++ b/code.py @@ -1,6 +1,6 @@ from keyboard import Keyboard -from config import io_extenders_pinout, pinout, keymap, rgb_pins, layer_colors +from config import io_extenders_pinout, pinout, keymap, rgb_pins, layer_colors, debug_repl -keyboard_state_manager = Keyboard(io_extenders_pinout, pinout, keymap, rgb_pins, layer_colors) +keyboard_state_manager = Keyboard(io_extenders_pinout, pinout, keymap, rgb_pins, layer_colors, debug_repl) keyboard_state_manager.start() \ No newline at end of file diff --git a/config.py b/config.py old mode 100644 new mode 100755 index 9bb0207..05ade9d --- a/config.py +++ b/config.py @@ -6,20 +6,43 @@ from keycodes import SE io_extenders_pinout = [(0x20, board.GP1, board.GP0)] -pinout: tuple[int,int] = [ - (0,7), (0,11), (0,15), (0,0), - (0,8), (0,4), (0,14), (0,1), - (0,6), (0,10), (0,13), (0,2), - (0,9), (0,5), (0,12), (0,3) +pinout: tuple[int, int] = [ + (0, 7), (0, 11), (0, 15), (0, 0), + (0, 8), (0, 4), (0, 14), (0, 1), + (0, 6), (0, 10), (0, 13), (0, 2), + (0, 9), (0, 5), (0, 12), (0, 3) ] -keymap: list[int] = [ +keymap = [[ Hold(1), Toggle(2), Toggle(3), Toggle(4), - SE.A, SE.B, SE.C, SE.D, + Toggle(0), SE.B, SE.C, SE.D, SE.E, SE.F, SE.G, SE.H, SE.I, SE.J, SE.K, SE.L -] +], [ + Hold(1), Toggle(2), Toggle(3), Toggle(4), + SE.ONE, Hold(1), SE.C, SE.D, + SE.E, SE.F, SE.G, SE.H, + SE.BACKSPACE, SE.J, SE.K, SE.L +], [ + Hold(1), Toggle(2), Toggle(3), Toggle(4), + SE.A, Hold(1), Hold(2), SE.D, + SE.E, SE.F, SE.G, SE.H, + SE.I, SE.J, SE.K, SE.L +], [ + Hold(1), Toggle(2), Toggle(3), Toggle(4), + SE.A, SE.B, Hold(2), Hold(3), + SE.E, SE.F, SE.G, SE.H, + SE.I, SE.J, SE.K, SE.L +], [ + Hold(1), Toggle(2), Toggle(3), Toggle(4), + SE.A, SE.B, SE.C, Hold(3), + SE.E, SE.F, SE.G, SE.H, + SE.I, SE.J, SE.K, SE.LEFT_ALT +]] -layer_colors = [(255,255,255),(0,255,0),(0,0,255), (255,0,255), (255, 255, 0)] +layer_colors = [(255, 255, 255), (0, 255, 0), (0, 0, 255), + (255, 0, 255), (255, 255, 0)] -rgb_pins: tuple[int,int,int] = (board.LED_R, board.LED_G, board.LED_B) \ No newline at end of file +rgb_pins: tuple[int, int, int] = (board.LED_R, board.LED_G, board.LED_B) + +debug_repl = True diff --git a/keyboard.py b/keyboard.py old mode 100644 new mode 100755 index aef9077..42d7a21 --- a/keyboard.py +++ b/keyboard.py @@ -2,30 +2,24 @@ from adafruit_mcp230xx.digital_inout import DigitalInOut from adafruit_mcp230xx.mcp23017 import MCP23017 import digitalio import busio +from tests import test_keymap import usb_hid import pwmio import time from micropython import const +from keytypes import LayerKey, Toggle, Hold -class LayerKey: - layer: int - def __init__(self, layer:int): - self.layer = layer -class Toggle(LayerKey): - def __init__(self, layer:int): - super().__init__(layer) -class Hold(LayerKey): - def __init__(self, layer:int): - super().__init__(layer) -__RED = (255,0,0) -__BLUE = (0,0,255) -__GREEN = (0,255,0) -__WHITE = (255,255,255) -__OFF = (0,0,0) -__BLINK = (0.05,0,0) +__RED = (255, 0, 0) +__BLUE = (0, 0, 255) +__GREEN = (0, 255, 0) +__WHITE = (255, 255, 255) +__OFF = (0, 0, 0) +__BLINK = (0.05, 0, 0) __INVERT_8_BIT_INTEGER_BITMASK = const(0xffff) __DUTY_CYCLE_OFF = __INVERT_8_BIT_INTEGER_BITMASK + + class RGBLED: leds: list[pwmio.PWMOut, pwmio.PWMOut, pwmio.PWMOut] @@ -34,13 +28,13 @@ class RGBLED: for i in range(len(rgb_pins)): pin = rgb_pins[i] led = pwmio.PWMOut(pin) - led.duty_cycle = __DUTY_CYCLE_OFF + led.duty_cycle = __DUTY_CYCLE_OFF self.leds.append(led) def set(self, rgb_values: tuple[int, int, int]): for i in range(3): input = rgb_values[i] - assert 0 <= input <=255 + assert 0 <= input <= 255 value = self.__to_inverse_8_bit_value(rgb_values[i]) self.leds[i].duty_cycle = value @@ -53,21 +47,21 @@ class RGBLED: def indicate_exception(self) -> None: self.animate( __RED, - __BLINK, - __OFF, __BLINK, - __RED, + __OFF, __BLINK, - __OFF, - __BLINK, - __RED, + __RED, + __BLINK, + __OFF, + __BLINK, + __RED, __BLINK, __OFF) def indicate_boot(self) -> None: self.animate(__WHITE, __BLINK, __OFF) - - def animate(self,*color_sleep_cycles: tuple[int, int, int]) -> None: + + def animate(self, *color_sleep_cycles: tuple[int, int, int]) -> None: """Takes arguments of tuple with three int values. Argument tuples are in the structure of 'color to display' and 'time to wait' after each other example: animate((255,255,255),(0.05,0,0),(0,0,0)) - will blink the led white for 0.05 seconds @@ -78,15 +72,22 @@ class RGBLED: else: time.sleep(color_sleep_cycles[i][0]) + __PRESSED = const(0) __UNPRESSED = const(1) __TOGGLED_PRESSED = const(2) __TOGGLED_RELEASED = const(3) __UNTOGGLED_PRESSED = const(4) +__DEBOUNCE = const(5) + + class Keyboard: - active_layer: int + debug_repl: bool + + toggled_layer: int + held_layer: int previous_layers: list[int] - layer_colors: list[tuple[int,int,int]] + layer_colors: list[tuple[int, int, int]] pressed_keys_last_cycle: set[int] pressed_keys: set[int] @@ -94,28 +95,42 @@ class Keyboard: pins: list[digitalio.DigitalInOut] pin_states_last_cycle: list[int] - keymap: list[int] + keymap: list[list[int]] keyboard_device: usb_hid.Device led: RGBLED - def __init__(self, io_extenders_pinout: list[tuple[int, int, int]], pinout: list[tuple[int,int]], keymap: list[int], rgb_pins: tuple[int, int, int], layer_colors: list[tuple[int, int, int]]): + def __init__( + self, + io_extenders_pinout: list[tuple[int, int, int]], + pinout: list[tuple[int, int]], + keymap: list[list[int]], + rgb_pins: tuple[int, int, int], + layer_colors: list[tuple[int, int, int]], + debug_repl: bool = False): """Initialize new keyboard - + 'io_extenders_pinout': list of tuple containing the i2c address, clock pin and data pin of the device. The order of the extenders are decided by the order in the list. 'pinout': list of tuple containing the index of the io_extender provided in 'io_extenders_pinout' and the pin of that extender 'keymap': list of keycodes that will be mapped against the pinout provided 'rgb_pins': tuple of three values in the inclusive range of 0-255 deciding the brightness of the red, green and blue leds""" + self.debug_repl = debug_repl + if debug_repl: + print("Initializing keyboard firmware") + self.keyboard_device, self.media_device = self.initialize_hid() io_extenders = self.initialize_io_extenders(io_extenders_pinout) self.pins = self.initialize_pins(io_extenders, pinout) + test_keymap(keymap, debug_repl) self.keymap = keymap self.layer_colors = layer_colors self.led = RGBLED(rgb_pins) self.led.indicate_boot() + if debug_repl: + print("Done initializing keyboard") def initialize_hid(self) -> tuple[usb_hid.Device, usb_hid.Device]: """Initializes keyboard and media device if availabe""" @@ -155,24 +170,31 @@ class Keyboard: return pins def start(self): + if self.debug_repl: + print("Starting keyboard") try: - self.active_layer = 0 - self.previous_layers = [] + self.toggled_layer = 0 + self.held_layer = None self.pressed_keys = set() self.pressed_keys_last_cycle = set() self.pin_states_last_cycle = [] - for pin in self.pins: self.pin_states_last_cycle.append(__UNPRESSED) + for pin in self.pins: + self.pin_states_last_cycle.append((__UNPRESSED,)) + if self.debug_repl: + print("Keyboard started") while True: for pin in self.pins: pin_index = self.pins.index(pin) - keycode = self.keymap[pin_index] - previously = self.pin_states_last_cycle[pin_index] + keycode = self.keymap[self.held_layer if self.held_layer != + None else self.toggled_layer][pin_index] + previousValue = self.pin_states_last_cycle[pin_index][0] value = pin.value currentlyPressed = value == __PRESSED - previouslyPressed = previously == __PRESSED - previouslyToggled = previously == __TOGGLED_PRESSED - previouslyToggledReleased = previously == __TOGGLED_RELEASED - previouslyUntoggledPressed = previously == __UNTOGGLED_PRESSED + previouslyPressed = previousValue == __PRESSED + previouslyToggled = previousValue == __TOGGLED_PRESSED + previouslyToggledReleased = previousValue == __TOGGLED_RELEASED + previouslyUntoggledPressed = previousValue == __UNTOGGLED_PRESSED + previouslyDebounced = previousValue == __DEBOUNCE if not isinstance(keycode, LayerKey): if currentlyPressed: if not previouslyPressed: @@ -184,49 +206,66 @@ class Keyboard: # Catch silenly if same keycode is pressed twice then released except KeyError: pass - self.pin_states_last_cycle[pin_index] = value + self.pin_states_last_cycle[pin_index] = (value,) continue # TODO: Always release old key when entering new layer if type(keycode) is Hold: - if currentlyPressed and not previouslyPressed: - self.previous_layers.append(self.active_layer) - self.active_layer = keycode.layer - print(f"hold: {self.previous_layers}") - self.pin_states_last_cycle[pin_index] = value + if not currentlyPressed and previouslyDebounced: + self.pin_states_last_cycle[pin_index] = ( + __UNPRESSED,) + + if currentlyPressed and not previouslyPressed and not previouslyDebounced: + self.held_layer = keycode.layer + self.pin_states_last_cycle[pin_index] = ( + __PRESSED, keycode) continue - if previouslyPressed and not currentlyPressed: - self.active_layer = self.previous_layers.pop() - self.pin_states_last_cycle[pin_index] = value - print(f"release: {self.previous_layers}") + if previouslyPressed and not currentlyPressed: + self.held_layer = None + self.pin_states_last_cycle[pin_index] = ( + __UNPRESSED,) continue if type(keycode) is Toggle: if currentlyPressed and not previouslyToggled and not previouslyToggledReleased and not previouslyUntoggledPressed: - self.previous_layers.append(self.active_layer) - self.active_layer = keycode.layer - print(f"Toggled: {self.previous_layers}") - self.pin_states_last_cycle[pin_index] = __TOGGLED_PRESSED + self.toggled_layer = keycode.layer + self.held_layer = None + self.pin_states_last_cycle[pin_index] = ( + __TOGGLED_PRESSED, keycode) + 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: - print(f"Toggled Released: {self.previous_layers}") - self.pin_states_last_cycle[pin_index] = __TOGGLED_RELEASED + if not currentlyPressed and previouslyToggled: + self.pin_states_last_cycle[pin_index] = ( + __TOGGLED_RELEASED, keycode) continue if currentlyPressed and previouslyToggledReleased: - print(f"Untoggled: {self.previous_layers}") - self.active_layer = self.previous_layers.pop() - self.pin_states_last_cycle[pin_index] = __UNTOGGLED_PRESSED + self.toggled_layer = 0 + self.pin_states_last_cycle[pin_index] = ( + __UNTOGGLED_PRESSED, keycode) continue if not currentlyPressed and previouslyUntoggledPressed: - print(f"Untoggled unpressed: {self.previous_layers}") - self.pin_states_last_cycle[pin_index] = __UNPRESSED + self.pin_states_last_cycle[pin_index] = ( + __UNPRESSED,) continue - if len(self.layer_colors) >= (self.active_layer + 1): - self.led.set(self.layer_colors[self.active_layer]) + active_layer = self.held_layer if self.held_layer != None else self.toggled_layer + if len(self.layer_colors) >= (active_layer + 1): + self.led.set(self.layer_colors[active_layer]) else: self.led.off() if self.pressed_keys != self.pressed_keys_last_cycle: self.send_nkro_report() self.pressed_keys_last_cycle = set(self.pressed_keys) except Exception as e: + if self.debug_repl: + raise e print(f"Exception thrown: {e}, restarting..") self.led.indicate_exception() time.sleep(1) diff --git a/keytypes.py b/keytypes.py new file mode 100644 index 0000000..a2eab54 --- /dev/null +++ b/keytypes.py @@ -0,0 +1,16 @@ + +class LayerKey: + layer: int + + def __init__(self, layer: int): + self.layer = layer + + +class Toggle(LayerKey): + def __init__(self, layer: int): + super().__init__(layer) + + +class Hold(LayerKey): + def __init__(self, layer: int): + super().__init__(layer) diff --git a/tests.py b/tests.py new file mode 100755 index 0000000..bccf734 --- /dev/null +++ b/tests.py @@ -0,0 +1,40 @@ +from keytypes import LayerKey + + +def test_keymap(keymap: list[list], output: bool = False) -> None: + valid_keycodes = range(4, 120) + valid_modifiers = [0x0100, 0x0200, 0x0400, 0x0800, 0x1000, 0x2000, 0x4000] + if output: + print("Testing keymap for errors") + for layer in keymap: + for key in layer: + if not isinstance(key, LayerKey): + if key in valid_keycodes: + if output: + print( + f"key is Keycode [{keymap.index(layer)}][{layer.index(key)}]", end=".. ") + if key in valid_modifiers: + if output: + print( + f"key is Modifier [{keymap.index(layer)}][{layer.index(key)}]", end=".. ") + if output: + print("and is valid") + continue + if isinstance(key, LayerKey): + layer_key_name = key.__class__.__name__ + if output: + print( + f"key is {layer_key_name} [{keymap.index(layer)}][{layer.index(key)}]", end=".. ") + key_on_layer_to_switch_to = keymap[key.layer][layer.index(key)] + key_is_not_hold = type( + key_on_layer_to_switch_to) is not type(key) + hold_key_is_not_same_layer = type( + key_on_layer_to_switch_to) is type(key) and key_on_layer_to_switch_to.layer != key.layer + if key_is_not_hold or hold_key_is_not_same_layer: + raise Exception( + f"{layer_key_name} layer key on layer[{keymap.index(layer)}] key[{layer.index(key)}] must have an identical {layer_key_name} layer key on layer[{key.layer}] key[{layer.index(key)}]") + else: + if output: + print("and is valid") + if output: + print("Done testing keymap")