import time
|
|
import board
|
|
import digitalio
|
|
import gc
|
|
import busio
|
|
|
|
from adafruit_hid.keyboard import Keyboard
|
|
from adafruit_hid.keycode import Keycode
|
|
from adafruit_hid.mouse import Mouse
|
|
from adafruit_neotrellis.neotrellis import NeoTrellis
|
|
|
|
OFF = (5, 0, 5)
|
|
LIT = (40, 20, 0)
|
|
UNDEF = (1, 1, 1)
|
|
PRESSED = (80, 40, 0)
|
|
|
|
BOUNCE_SECS = 0.250
|
|
NEOTRELLIS_COUNT = 16
|
|
|
|
class SnakeSwitch:
|
|
def __init__(self, switch_pins, layouts):
|
|
i2c_bus = busio.I2C(board.SCL, board.SDA)
|
|
self.trellis = NeoTrellis(i2c_bus)
|
|
self.kbd = Keyboard()
|
|
|
|
self.switch_inputs = tuple(digitalio.DigitalInOut(pin) for pin in switch_pins)
|
|
for switch_input in self.switch_inputs:
|
|
switch_input.switch_to_input(pull=digitalio.Pull.UP)
|
|
|
|
self.layouts = layouts
|
|
self.active_layouts = {}
|
|
self.active_switches = {}
|
|
|
|
for i in range(NEOTRELLIS_COUNT):
|
|
self.active_layouts[i] = False
|
|
# activate rising / falling edge events on all keys, set up callback
|
|
self.trellis.activate_key(i, NeoTrellis.EDGE_RISING)
|
|
self.trellis.activate_key(i, NeoTrellis.EDGE_FALLING)
|
|
self.trellis.callbacks[i] = self.handle_neotrellis_event
|
|
|
|
self.toggle_layout(sorted(self.layouts.keys())[0])
|
|
self.paint_trellis()
|
|
|
|
def handle_neotrellis_event(self, event):
|
|
if event.edge == NeoTrellis.EDGE_RISING:
|
|
# Highlight the currently pressed key:
|
|
self.trellis.pixels[event.number] = PRESSED
|
|
elif event.edge == NeoTrellis.EDGE_FALLING:
|
|
# On release, activate a button if it has a defined layout:
|
|
if event.number in self.layouts:
|
|
self.toggle_layout(event.number)
|
|
self.paint_trellis()
|
|
|
|
def toggle_layout(self, layout_id):
|
|
print(time.monotonic(), 'switching layout: ', layout_id)
|
|
self.active_layouts[layout_id] = not self.active_layouts[layout_id]
|
|
|
|
def paint_trellis(self):
|
|
for layout_button, active in self.active_layouts.items():
|
|
if active:
|
|
# Layout is currently activated:
|
|
self.trellis.pixels[layout_button] = LIT
|
|
elif layout_button in self.layouts:
|
|
# Layout is defined, but deactivate:
|
|
self.trellis.pixels[layout_button] = OFF
|
|
else:
|
|
# Layout isn't defined in the config file:
|
|
self.trellis.pixels[layout_button] = UNDEF
|
|
|
|
def advance_frame(self):
|
|
# XXX: trellis sync may need to happen less frequently
|
|
self.trellis.sync()
|
|
|
|
current_layouts = [self.layouts[layout] for layout, active in self.active_layouts.items() if active]
|
|
|
|
for switch, switch_input in enumerate(self.switch_inputs):
|
|
# If switch is un-pressed, it's pulled high. Make sure to release
|
|
# any keys attached to it and mark it non-active.
|
|
if switch_input.value:
|
|
if switch in self.active_switches and self.active_switches[switch]:
|
|
print(time.monotonic(), 'marking switch', switch, ' inactive')
|
|
keys = self.get_keys_for_switch(switch, current_layouts)
|
|
for key in keys:
|
|
print(time.monotonic(), 'release key:', key)
|
|
self.kbd.release(key)
|
|
self.active_switches[switch] = False
|
|
continue
|
|
|
|
if switch in self.active_switches and self.active_switches[switch]:
|
|
# If we're already marked active, do nothing.
|
|
continue
|
|
|
|
# If switch is pressed, it's pulled low. Debounce by waiting for bounce time:
|
|
time.sleep(BOUNCE_SECS)
|
|
|
|
print(time.monotonic(), 'marking switch', switch, ' active')
|
|
self.active_switches[switch] = True
|
|
|
|
for key in self.get_keys_for_switch(switch, current_layouts):
|
|
print(time.monotonic(), 'press key:', key)
|
|
self.kbd.press(key)
|
|
|
|
def get_keys_for_switch(self, switch, current_layouts):
|
|
keys = []
|
|
for layout in current_layouts:
|
|
if switch in layout:
|
|
if isinstance(layout[switch], tuple):
|
|
keys.extend(layout[switch])
|
|
else:
|
|
keys.append(layout[switch])
|
|
return keys
|