layers and keymap test
This commit is contained in:
0
.gitignore
vendored
Executable file → Normal file
0
.gitignore
vendored
Executable file → Normal file
4
code.py
Normal file → Executable file
4
code.py
Normal file → Executable 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
43
config.py
Normal file → Executable 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
167
keyboard.py
Normal file → Executable 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
16
keytypes.py
Normal 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
40
tests.py
Executable 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")
|
||||
Reference in New Issue
Block a user