- """
- `snakeswitch`
- =============
-
- CircuitPython implementation of a foot-switch and NeoTrellis device for
- triggering keyboard events.
-
- See code.py for a demo of the usage.
-
- * Author(s): Brennen Bearnes
- """
-
- # pylint: disable=import-error
- import time
- import board
- import digitalio
- import busio
-
- import neopixel
-
- from adafruit_hid.keyboard import Keyboard
- from adafruit_neotrellis.neotrellis import NeoTrellis
- # pylint: enable=import-error
-
- COLOR_ACTIVE_LAYOUT = (40, 20, 0)
- COLOR_INACTIVE_LAYOUT = (5, 0, 5)
- COLOR_PRESSED = (80, 40, 0)
- COLOR_TRIGGER = (0, 5, 0)
- COLOR_UNDEFINED = (0, 0, 0)
-
- BOUNCE_SECS = 0.250
- NEOTRELLIS_COUNT = 16
-
- def get_keys_for_switch(switch, current_layouts):
- """
- Return a list of keycodes that are triggered by a switch in the
- given set of 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
-
- def diag(*message):
- """Print a diagnostic message to the console."""
- print(time.monotonic(), message)
-
- class SnakeSwitch:
- """
- Class modeling a SnakeSwitch.
-
- Expects a tuple listing the pins that switches are connected to,
- and a dictionary of layouts - see switch_config.py for a sample
- configuration.
- """
-
- def __init__(self, switch_pins, layouts):
- i2c_bus = busio.I2C(board.SCL, board.SDA)
- self.trellis = NeoTrellis(i2c_bus)
- self.kbd = Keyboard()
-
- diag('initializing pins', switch_pins)
- 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):
- # Mark any mapped to each button as currently inactive:
- 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
-
- # Activate first defined layout that isn't just a key trigger:
- for layout_id, layout in self.layouts.items():
- if isinstance(layout, dict):
- self.toggle_layout(layout_id)
- break
- self.paint_trellis()
-
- # Turn off NeoPixel on the Feather M4:
- onboard_pixel = neopixel.NeoPixel(board.NEOPIXEL, 1)
- onboard_pixel[0] = (0, 0, 0)
-
- def handle_neotrellis_event(self, event):
- """A callback for handling NeoTrelllis rising/falling edges."""
- if event.edge == NeoTrellis.EDGE_RISING:
- # Highlight the currently pressed button:
- self.trellis.pixels[event.number] = COLOR_PRESSED
-
- elif event.edge == NeoTrellis.EDGE_FALLING:
- # On release, activate a button if it has a defined layout (or fire
- # any defined key events):
- if event.number in self.layouts:
- action_for_button = self.layouts[event.number]
- if isinstance(action_for_button, int):
- # It's a key code, press it:
- diag("pressing and releasing keycode: ", action_for_button)
- self.kbd.press(self.layouts[event.number])
- self.kbd.release_all()
- elif isinstance(action_for_button, tuple):
- # It's a tuple with multiple key codes - fire them all:
- diag("pressing and releasing keycode combo: ", action_for_button)
- for k in action_for_button:
- self.kbd.press(k)
- self.kbd.release_all()
- else:
- # Toggle a layout:
- self.toggle_layout(event.number)
- self.paint_trellis()
-
- def toggle_layout(self, layout_id):
- """Toggle the state of a given layout."""
- diag('switching layout: ', layout_id)
- self.active_layouts[layout_id] = not self.active_layouts[layout_id]
-
- def paint_trellis(self):
- """Set state of trellis LEDs based on defined and active layouts."""
- for layout_button, active in self.active_layouts.items():
- if active:
- # Layout is currently activated:
- self.trellis.pixels[layout_button] = COLOR_ACTIVE_LAYOUT
- elif layout_button in self.layouts:
- if isinstance(self.layouts[layout_button], (tuple, int)):
- # Button is actually a keycode or set of keycodes to fire on press:
- self.trellis.pixels[layout_button] = COLOR_TRIGGER
- else:
- # Layout is defined, but deactivated:
- self.trellis.pixels[layout_button] = COLOR_INACTIVE_LAYOUT
- else:
- # Layout isn't defined in the config file:
- self.trellis.pixels[layout_button] = COLOR_UNDEFINED
-
- def advance_frame(self):
- """
- Advance the frame, synchronizing the trellis (which will call
- handle_neotrellis_event() as necessary) and pressing / releasing
- keys where appropriate for the currently pressed switch and
- currently active layout.
- """
- # Trellis sync may need to happen less frequently than this?
- self.trellis.sync()
-
- # Accumulate currently-active switch layouts:
- 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]:
- diag('marking switch', switch, ' inactive')
- keys = get_keys_for_switch(switch, current_layouts)
- for key in keys:
- diag('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)
-
- diag('marking switch', switch, ' active')
- self.active_switches[switch] = True
-
- for key in get_keys_for_switch(switch, current_layouts):
- diag('press key:', key)
- self.kbd.press(key)
|