Bladeren bron

composable input layers bound to buttons; press/release of keys

This adds a config file format which allows input layers to be defined for
each NeoTrellis button.  Buttons with a layer attached will glow an
indicator color.  Layers can be toggled independently, and all the keys
defined for a given switch will be sent.

Keys are now held until the switch is released, which allows for chording
and combining them with actual keyboard input.

TODO:

- Order matters within key definitions, and there's probably some implicit
ordering of composed layers that could get weird.  Maybe higher-number
layers should just automatically come first?

- The code still feels pretty confusing and could definitely be optimized
in a couple of places.

- I occasionally get weird behavior from the NeoTrellis, suspect that
might be because I'm reading it too often.
master
Brennen Bearnes 2 jaren geleden
bovenliggende
commit
e948cb28b2
2 gewijzigde bestanden met toevoegingen van 90 en 34 verwijderingen
  1. +6
    -2
      code.py
  2. +84
    -32
      snakeswitch.py

+ 6
- 2
code.py Bestand weergeven

@ -5,8 +5,12 @@ https://github.com/adafruit/snakeswitch
* Author(s): Brennen Bearnes
"""
import board
from snakeswitch import SnakeSwitch
ss = SnakeSwitch()
from switch_config import LAYOUTS
SWITCH_PINS = (board.D5, board.D6, board.D9)
ss = SnakeSwitch(SWITCH_PINS, LAYOUTS)
while True:
ss.advance_frame()

+ 84
- 32
snakeswitch.py Bestand weergeven

@ -9,51 +9,103 @@ from adafruit_hid.keycode import Keycode
from adafruit_hid.mouse import Mouse
from adafruit_neotrellis.neotrellis import NeoTrellis
OFF = (1, 0, 0)
OFF = (5, 0, 5)
LIT = (40, 20, 0)
SWITCH_PINS = (board.D5, board.D6, board.D9, board.D10, board.D11, board.D12, board.A1)
BOUNCE_SECS = 0.100
UNDEF = (1, 1, 1)
PRESSED = (80, 40, 0)
BOUNCE_SECS = 0.250
NEOTRELLIS_COUNT = 16
class SnakeSwitch:
def __init__(self):
def __init__(self, switch_pins, layouts):
i2c_bus = busio.I2C(board.SCL, board.SDA)
self.trellis = NeoTrellis(i2c_bus)
self.kbd = Keyboard()
self.switch_ins = tuple(digitalio.DigitalInOut(pin) for pin in SWITCH_PINS)
for switch_in in self.switch_ins:
switch_in.switch_to_input(pull=digitalio.Pull.UP)
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.kbd = Keyboard()
self.states = {}
self.layouts = layouts
self.active_layouts = {}
self.active_switches = {}
for i in range(16):
self.states[i] = False
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.toggle
self.trellis.callbacks[i] = self.handle_neotrellis_event
self.toggle_layout(sorted(self.layouts.keys())[0])
self.paint_trellis()
def toggle(self, event):
# turn the LED on when a rising edge is detected
def handle_neotrellis_event(self, event):
if event.edge == NeoTrellis.EDGE_RISING:
self.states[event.number] = not self.states[event.number]
# 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()
for state, value in self.states.items():
if value:
self.trellis.pixels[state] = LIT
else:
self.trellis.pixels[state] = OFF
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):
for switch, switch_in in enumerate(self.switch_ins):
self.trellis.sync()
if not switch_in.value:
# If switch is pressed, it's pulled low. Debounce by waiting for bounce time.
time.sleep(BOUNCE_SECS)
print('switch: ', switch)
print(gc.mem_free())
self.kbd.send(Keycode.LEFT_GUI, Keycode.TAB)
# Wait for switch to be released.
while not switch_in.value:
pass
# 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

Laden…
Annuleren
Opslaan