Real Steno

A project log for Steno Keyboard

Type at 300 words per minute!

deʃhipudeʃhipu 11/22/2021 at 20:150 Comments

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:
                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
                    pressed_count -= 1
                    if pressed_count == 0:
                        report[:] = b"\x80\x00\x00\x00\x00\x00"

 That's it.  There is also a 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)

if not row.value:
    usb_cdc.enable(console=False, data=True)
    usb_cdc.enable(console=True, data=True)


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)

    (_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!