layers and keymap test

This commit is contained in:
2022-01-06 12:06:18 +01:00
parent eb75011921
commit 38c5b6e78b
6 changed files with 194 additions and 76 deletions

0
.gitignore vendored Executable file → Normal file
View File

4
code.py Normal file → Executable file
View File

@@ -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()

43
config.py Normal file → Executable file
View File

@@ -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)
rgb_pins: tuple[int, int, int] = (board.LED_R, board.LED_G, board.LED_B)
debug_repl = True

167
keyboard.py Normal file → Executable file
View File

@@ -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)

16
keytypes.py Normal file
View File

@@ -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)

40
tests.py Executable file
View File

@@ -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")