Last active
January 18, 2022 03:14
-
-
Save MMcM/44691c27d8dfa10b3937bdfbda1f06bd to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
USB_VID = 0x239A | |
USB_PID = 0x8058 | |
USB_PRODUCT = "Serpente" | |
USB_MANUFACTURER = "arturo182" | |
CHIP_VARIANT = SAMD21E18A | |
CHIP_FAMILY = samd21 | |
SPI_FLASH_FILESYSTEM = 1 | |
EXTERNAL_FLASH_DEVICE_COUNT = 1 | |
EXTERNAL_FLASH_DEVICES = GD25Q32C | |
LONGINT_IMPL = NONE | |
CIRCUITPY_AUDIOBUSIO = 0 | |
CIRCUITPY_FREQUENCYIO = 0 | |
CIRCUITPY_GAMEPAD = 0 | |
# Save some more flash space | |
CIRCUITPY_ANALOGIO = 0 | |
CIRCUITPY_PULSEIO = 0 | |
CIRCUITPY_ROTARYIO = 0 | |
CIRCUITPY_RTC = 0 | |
CIRCUITPY_USB_MIDI = 0 | |
CIRCUITPY_TOUCHIO = 0 | |
SUPEROPT_GC = 0 | |
CFLAGS_BOARD = --param max-inline-insns-auto=15 | |
ifeq ($(TRANSLATION), zh_Latn_pinyin) | |
RELEASE_NEEDS_CLEAN_BUILD = 1 | |
CFLAGS_INLINE_LIMIT = 35 | |
endif | |
ifeq ($(TRANSLATION), ja) | |
RELEASE_NEEDS_CLEAN_BUILD = 1 | |
CFLAGS_INLINE_LIMIT = 35 | |
endif | |
ifeq ($(TRANSLATION), de_DE) | |
RELEASE_NEEDS_CLEAN_BUILD = 1 | |
CFLAGS_INLINE_LIMIT = 35 | |
SUPEROPT_VM = 0 | |
endif | |
FROZEN_MPY_DIRS += $(TOP)/frozen/Adafruit_CircuitPython_HID |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# Four reed switches from an old Soviet calculator connected to a Serpente. | |
import board | |
from tiny_kbd import Keyboard, ScanDirection, KC | |
keyboard = Keyboard() | |
keyboard.rows = ( board.D0, ) | |
keyboard.cols = ( board.D1, board.D2, board.D3, board.D4 ) | |
keyboard.scan_direction = ScanDirection.ROWS | |
# Proof of concept of various tiny-kbd.KC possibilities. | |
keyboard.keymap = [ | |
[ | |
[ KC.VOLUME_INCREMENT, KC.VOLUME_DECREMENT, KC.ESCAPE, KC.MO(1) ], | |
], | |
[ | |
[ KC.SEND_STRING('hello\n'), KC.SEND_STRING('goodbye\n'), KC.ALT(KC.TAB), KC.TRANSPARENT ], | |
], | |
] | |
if __name__ == '__main__': | |
keyboard.run() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# CircuitPython keyboard matrix for smaller memory devices such as Cortex M0. | |
from micropython import const | |
from adafruit_hid.keyboard import Keyboard as Keyboard_HID | |
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS | |
from adafruit_hid.keycode import Keycode | |
from adafruit_hid.consumer_control import ConsumerControl | |
from adafruit_hid.consumer_control_code import ConsumerControlCode | |
import digitalio | |
import usb_hid | |
class Action: | |
TRANSPARENT = const(-1) | |
KEYBOARD_CODE = const(0) | |
CONSUMER_CODE = const(1) | |
STRING = const(2) | |
LAYER_MOMENTARY = const(8) | |
LAYER_MOMENTARY_MODIFIER = const(9) | |
LAYER_TOGGLE = const(10) | |
LAYER_SET = const(11) | |
TRANSPARENT_ACTION = (Action.TRANSPARENT,) | |
class ScanDirection: | |
ROWS = const(0) | |
COLUMNS = const(1) | |
def _as_io(x): | |
# Allow MCP230xx as well as board GPIO. | |
return x if x.__class__.__name__ is 'DigitalInOut' else digitalio.DigitalInOut(x) | |
class MatrixScanner: | |
def __init__(self, rows, cols, direction): | |
if direction == ScanDirection.ROWS: | |
self.outputs = [_as_io(x) for x in rows] | |
self.inputs = [_as_io(x) for x in cols] | |
self.event = lambda o,i,s: (o, i, s) | |
elif direction == ScanDirection.COLUMNS: | |
self.outputs = [_as_io(x) for x in cols] | |
self.inputs = [_as_io(x) for x in rows] | |
self.event = lambda o,i,s: (i, o, s) | |
else: | |
raise ValueError('invalid scan direction: %s' % direction) | |
self.num_out = len(self.outputs) | |
if self.num_out == 0: | |
self.num_out = 1 # For direct case, all inputs. | |
self.num_in = len(self.inputs) | |
num_bytes = (self.num_in + 7) // 8 | |
self.key_states = [bytearray(num_bytes) for i in range(self.num_out)] | |
for pin in self.outputs: | |
pin.switch_to_input() | |
for pin in self.inputs: | |
pin.switch_to_input(pull=digitalio.Pull.UP) | |
def __iter__(self): | |
self.out_index = 0 | |
self.out_selected = False | |
return self | |
def __next__(self): | |
while True: | |
if not self.out_selected: | |
if self.out_index >= self.num_out: | |
raise StopIteration | |
if self.out_index < len(self.outputs): | |
self.outputs[self.out_index].switch_to_output(value=False) # Ground | |
self.in_index = 0 | |
self.selected_key_states = self.key_states[self.out_index] | |
self.out_selected = True | |
pressed = not self.inputs[self.in_index].value | |
byte_index = self.in_index // 8 | |
byte_mask = 1 << (self.in_index % 8) | |
was_pressed = (self.selected_key_states[byte_index] & byte_mask) != 0 | |
if pressed == was_pressed: | |
event = None | |
else: | |
self.selected_key_states[byte_index] ^= byte_mask | |
event = self.event(self.out_index, self.in_index, pressed) | |
self.in_index += 1 | |
if self.in_index >= self.num_in: | |
if self.out_index < len(self.outputs): | |
self.outputs[self.out_index].switch_to_input() # Hi-Z | |
self.out_index += 1 | |
self.out_selected = False | |
if event is not None: | |
return event | |
class _Keymap: | |
def __init__(self, layers): | |
self.layers = layers | |
self.layer_mask = 1 | |
def __getitem__(self, rc): | |
row, col = rc | |
lidx = len(self.layers) - 1 | |
while lidx >= 0: | |
if self.layer_mask & (1 << lidx): | |
layer = self.layers[lidx] | |
cols = layer[row] | |
key = cols[col] | |
if key != TRANSPARENT_ACTION: | |
return key | |
lidx -= 1 | |
return None | |
def add(self, l): | |
self.layer_mask |= (1 << l) | |
def remove(self, l): | |
self.layer_mask &= ~(1 << l) | |
def toggle(self, l): | |
self.layer_mask ^= (1 << l) | |
def set(self, l): | |
self.layer_mask = (1 << l) | 1 | |
class Keyboard: | |
direct = None | |
rows = None | |
cols = None | |
scan_direction = None | |
keymap = None | |
matrix_scanner = MatrixScanner | |
layout = None | |
def run(self): | |
if self.direct is not None: | |
if self.rows is not None or self.cols is not None or self.scan_direction is not None: | |
raise ValueError('cannot set both direct and rows/cols') | |
matrix = self.matrix_scanner((), self.direct, ScanDirection.ROWS) | |
elif self.rows is not None and self.cols is not None and self.scan_direction is not None: | |
matrix = self.matrix_scanner(self.rows, self.cols, self.scan_direction) | |
else: | |
raise ValueError('must set all of rows and cols and scan_direction') | |
if self.keymap is None: | |
raise ValueError('must specify keymap') | |
self._keymap = _Keymap(self.keymap) | |
self.keyboard_hid = Keyboard_HID(usb_hid.devices) | |
self.consumer_control = None | |
while True: | |
for row, col, pressed in matrix: | |
key = self._keymap[row, col] | |
if key is not None: | |
self.action(key, pressed) | |
def action(self, key, pressed): | |
if isinstance(key, int): | |
if pressed: | |
self.keyboard_hid.press(key) | |
else: | |
self.keyboard_hid.release(key) | |
elif isinstance(key, str): | |
if pressed: | |
if self.layout is None: | |
self.layout = KeyboardLayoutUS(self.keyboard_hid) | |
self.layout.write(key) | |
elif isinstance(key, tuple): | |
self.extended_action(key, pressed) | |
else: | |
raise ValueError('invalid key binding: %s of type %s' % (key, type(key))) | |
def extended_action(self, key, pressed): | |
action = key[0] | |
if action == Action.KEYBOARD_CODE: | |
keys = key[1:] | |
if pressed: | |
self.keyboard_hid.press(*keys) | |
else: | |
self.keyboard_hid.release(*keys) | |
elif action == Action.CONSUMER_CODE: | |
if self.consumer_control is None: | |
self.consumer_control = ConsumerControl(usb_hid.devices) | |
if pressed: | |
self.consumer_control.send(key[1]) | |
elif action == Action.STRING: | |
self.action(key[1], pressed) | |
elif action == Action.LAYER_MOMENTARY: | |
if pressed: | |
self._keymap.add(key[1]) | |
else: | |
self._keymap.remove(key[1]) | |
elif action == Action.LAYER_MOMENTARY_MODIFIER: | |
if pressed: | |
self._keymap.add(key[1]) | |
self.keyboard_hid.press(keys[2:]) | |
else: | |
self.keyboard_hid.release(keys[2:]) | |
self._keymap.remove(key[1]) | |
elif action == Action.LAYER_TOGGLE: | |
self._keymap.toggle(key[1]) | |
elif action == Action.LAYER_SET: | |
self._keymap.set(key[1]) | |
else: | |
raise ValueError('invalid extended key binding: %s of type %s' % (key, action)) | |
class _Layers: | |
def MO(self, l): | |
return (Action.LAYER_MOMENTARY, l) | |
def LM(self, l, *mod): | |
return (Action.LAYER_MOMENTARY, l) + mod | |
def TG(self, l): | |
return (Action.LAYER_TOGGLE, l) | |
def TO(self, l): | |
return (Action.LAYER_SET, l) | |
class _Modifiers: | |
def _mod(self, mod, kc): | |
if isinstance(kc, tuple): | |
if kc[0] == Action.KEYBOARD_CODE: | |
# https://github.com/micropython/micropython/issues/1329 | |
# return (kc[0], mod, *kc[1:]) | |
return (kc[0], mod) + kc[1:] | |
if isinstance(kc, int): | |
return (Action.KEYBOARD_CODE, mod, kc) | |
raise ValueError('base key code not recognized: %s' % kc) | |
def LCTL(self, kc): | |
return self._mod(Keycode.LEFT_CONTROL, kc) | |
CTL = LCTL | |
def LSFT(self, kc): | |
return self._mod(Keycode.LEFT_SHIFT, kc) | |
SFT = LSFT | |
def LALT(self, kc): | |
return self._mod(Keycode.LEFT_ALT, kc) | |
ALT = LALT | |
LOPT = LALT | |
OPT = LOPT | |
def LGUI(self, kc): | |
return self._mod(Keycode.LEFT_GUI, kc) | |
GUI = LGUI | |
LCMD = LGUI | |
CMD = LCMD | |
LWIN = LGUI | |
WIN = LWIN | |
def RCTL(self, kc): | |
return self._mod(Keycode.RIGHT_CONTROL, kc) | |
def RSFT(self, kc): | |
return self._mod(Keycode.RIGHT_SHIFT, kc) | |
def RALT(self, kc): | |
return self._mod(Keycode.RIGHT_ALT, kc) | |
ROPT = RALT | |
def RGUI(self, kc): | |
return self._mod(Keycode.RIGHT_GUI, kc) | |
RCMD = RGUI | |
RWIN = RGUI | |
class _KeysNamespace(_Modifiers, _Layers): | |
def __init__(self): | |
self.NO = None | |
self.XXXXXXX = None | |
self.TRANSPARENT = TRANSPARENT_ACTION | |
self._______ = TRANSPARENT_ACTION | |
def SEND_STRING(self, s): | |
return (Action.STRING, str(s)) | |
def __getattr__(self, a): | |
# Lookup in HID maps, which are frozen in flash. | |
kc = getattr(Keycode, a, None) | |
if kc is not None: | |
return kc # Could be KEYBOARD_CODE, but save a little space for ordinary case. | |
cc = getattr(ConsumerControlCode, a, None) | |
if cc is not None: | |
return (CONSUMER_CODE, cc) | |
raise AttributeError("KC has no attribute '%s'" % a) | |
KC = _KeysNamespace() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment