I finally got back to this project and rewrote the code for it, so that it now acts the same as commercial stenotypes, one called "Gemini PR" to be specific. It turned out to be much easier than I anticipated, mostly because CircuitPython has grown a lot of the features needed for it in the mean time. The code looks basically like this:
import keypad import usb_cdc class Steno: def __init__(self, matrix, cols, rows): self.matrix = matrix self.keypad = keypad.KeyMatrix(rows, cols) self.width = len(cols) def run(self): report = bytearray(6) report[:] = b"\x80\x00\x00\x00\x00\x00" pressed_count = 0 event = keypad.Event() while True: while self.keypad.events.get_into(event): y, x = divmod(event.key_number, self.width) key = self.matrix[y][x] if event.pressed: byte, bit = divmod(key, 7) report[byte] |= 1 << (6 - bit) pressed_count += 1 else: pressed_count -= 1 if pressed_count == 0: usb_cdc.data.write(report)) report[:] = b"\x80\x00\x00\x00\x00\x00"
That's it. There is also a boot.py to enable the second serial for data (and disable the REPL and disk unless a key is pressed when connecting it):
import usb_cdc import usb_hid import storage import board import digitalio row = digitalio.DigitalInOut(board.SCL) col = digitalio.DigitalInOut(board.MOSI) col.switch_to_output(value=1) row.switch_to_input(pull=digitalio.Pull.DOWN) if not row.value: storage.disable_usb_drive() usb_cdc.enable(console=False, data=True) else: usb_cdc.enable(console=True, data=True) usb_hid.disable() row.deinit() col.deinit()
And of course the key matrix definition:
import board import usteno from micropython import const ROWS = (board.SCL, board.AREF, board.D6, board.A4) COLS = (board.MOSI, board.SCK, board.A0, board.MISO, board.A3, board.A5, board.TX, board.A2, board.A6, board.A1) _FN = const(0) _N1 = const(1) _N2 = const(2) _N3 = const(3) _N4 = const(4) _N5 = const(5) _N6 = const(6) _S1 = const(7) _S2 = const(8) _TX = const(9) _KX = const(10) _PX = const(11) _WX = const(12) _HX = const(13) _RX = const(14) _AX = const(15) _OX = const(16) _X1 = const(17) _X2 = const(18) _R1 = const(19) _R2 = const(20) _PW = const(21) _X3 = const(22) _X4 = const(23) _XE = const(24) _XU = const(25) _XF = const(26) _XR = const(27) _XP = const(28) _XB = const(29) _XL = const(30) _XG = const(31) _XT = const(32) _XS = const(33) _XD = const(34) _N7 = const(35) _N8 = const(36) _N9 = const(37) _NA = const(38) _NB = const(39) _NC = const(40) _XZ = const(41) MATRIX = ( (_N1, _N2, 0, _N3, 0, _N4, 0, _N8, _N9, _NA), (_S1, _TX, _PX, _HX, _X1, _XF, _XP, _XL, _XT, _XD), (_S2, _KX, _WX, _RX, _X2, _XR, _XB, _XG, _XS, _XZ), ( 0, 0, _AX, _OX, 0, _XE, _XU, 0, 0, 0), ) usteno.Steno(MATRIX, COLS, ROWS).run()
Then you just need to start Plover, configure the right serial port in settings/machine (it was /dev/ttyACM0 for me), select Gemini GP as the machine type, and you are ready to steno!