|
| 1 | +import array |
| 2 | +import time |
| 3 | + |
| 4 | +import board |
| 5 | +import rp2pio |
| 6 | +import usb_hid |
| 7 | +from adafruit_hid.consumer_control import ConsumerControl |
| 8 | +from adafruit_hid.consumer_control_code import ConsumerControlCode |
| 9 | +from adafruit_hid.keyboard import Keyboard |
| 10 | +from adafruit_pioasm import Program |
| 11 | +from adafruit_ticks import ticks_add, ticks_less, ticks_ms |
| 12 | + |
| 13 | +from next_keycode import cc_value, is_cc, next_modifiers, next_scancodes |
| 14 | + |
| 15 | +NEXT_SERIAL_BUS_FREQUENCY = ( |
| 16 | + 18958 # 455kHz/24 https://journal.spencerwnelson.com/entries/nextkb.html |
| 17 | +) |
| 18 | + |
| 19 | +pio_program = Program( |
| 20 | + """ |
| 21 | +top: |
| 22 | + pull block ; wait for send request |
| 23 | + out x, 1 ; trigger receive? |
| 24 | + out y, 7 ; get count of bits to transmit (minus 1) |
| 25 | +
|
| 26 | + set pins, 1 |
| 27 | +bitloop: |
| 28 | + out pins, 1 [7] ; send next bit |
| 29 | + jmp y--, bitloop [7] ; loop if bits left to send |
| 30 | +
|
| 31 | + set pins, 1 ; idle the bus after last bit |
| 32 | + jmp !x, top ; to top if no scancode expected |
| 33 | +
|
| 34 | + set y, 19 ; 20 bits to receive |
| 35 | +
|
| 36 | + wait 0, pin 0 [7] ; wait for falling edge plus half bit time |
| 37 | +recvloop: |
| 38 | + in pins, 1 [7] ; sample in the middle of the bit |
| 39 | + jmp y--, recvloop [7] ; loop until all bits read |
| 40 | +
|
| 41 | + push ; send report to CircuitPython |
| 42 | +""" |
| 43 | +) |
| 44 | + |
| 45 | +def pack_message(bitcount, data, trigger_receive=False): |
| 46 | + if bitcount > 24: |
| 47 | + raise ValueError("too many bits in message") |
| 48 | + trigger_receive = bool(trigger_receive) |
| 49 | + message = ( |
| 50 | + (trigger_receive << 31) | ((bitcount - 1) << 24) | (data << (24 - bitcount)) |
| 51 | + ) |
| 52 | + return array.array("I", [message]) |
| 53 | + |
| 54 | + |
| 55 | +def pack_message_str(bitstring, trigger_receive=False): |
| 56 | + bitcount = len(bitstring) |
| 57 | + data = int(bitstring, 2) |
| 58 | + return pack_message(bitcount, data, trigger_receive=trigger_receive) |
| 59 | + |
| 60 | + |
| 61 | +def set_leds(i): |
| 62 | + return pack_message_str(f"0000000001110{i:02b}0000000") |
| 63 | + |
| 64 | + |
| 65 | +TEST1 = pack_message_str("010100") |
| 66 | +TEST2 = pack_message_str("001010") |
| 67 | +QUERY = pack_message_str("000001000", 1) |
| 68 | +RESET = pack_message_str("0111101111110000000000") |
| 69 | + |
| 70 | +BIT_BREAK = 1 << 11 |
| 71 | +BIT_MOD = 1 |
| 72 | + |
| 73 | + |
| 74 | +def is_make(report): |
| 75 | + return not bool(report & BIT_BREAK) |
| 76 | + |
| 77 | + |
| 78 | +def is_mod_report(report): |
| 79 | + return not (report & 1) |
| 80 | + |
| 81 | + |
| 82 | +# keycode bits are backwards compared to other information sources |
| 83 | +# (bit 0 is first) |
| 84 | +def keycode(report): |
| 85 | + b = f"{report >> 12:07b}" |
| 86 | + b = "".join(reversed(b)) |
| 87 | + return int(b, 2) |
| 88 | + |
| 89 | + |
| 90 | +def modifiers(report): |
| 91 | + return (report >> 1) & 0x7F |
| 92 | + |
| 93 | + |
| 94 | +sm = rp2pio.StateMachine( |
| 95 | + pio_program.assembled, |
| 96 | + first_sideset_pin=board.D11, |
| 97 | + first_in_pin=board.D12, |
| 98 | + pull_in_pin_up=1, |
| 99 | + first_set_pin=board.D13, |
| 100 | + set_pin_count=1, |
| 101 | + first_out_pin=board.D13, |
| 102 | + out_pin_count=1, |
| 103 | + frequency=16 * NEXT_SERIAL_BUS_FREQUENCY, |
| 104 | + in_shift_right=False, |
| 105 | + wait_for_txstall=False, |
| 106 | + out_shift_right=False, |
| 107 | + **pio_program.pio_kwargs, |
| 108 | +) |
| 109 | + |
| 110 | +class KeyboardHandler: |
| 111 | + def __init__(self): |
| 112 | + self.old_modifiers = 0 |
| 113 | + self.cc = ConsumerControl(usb_hid.devices) |
| 114 | + self.kbd = Keyboard(usb_hid.devices) |
| 115 | + |
| 116 | + def set_key_state(self, key, state): |
| 117 | + print("set_key_state", key, state) |
| 118 | + if state: |
| 119 | + self.kbd.press(key) |
| 120 | + else: |
| 121 | + self.kbd.release(key) |
| 122 | + |
| 123 | + def handle_report(self, value): |
| 124 | + if value == 1536: |
| 125 | + return |
| 126 | + |
| 127 | + if is_mod_report(value): |
| 128 | + mods = modifiers(value) |
| 129 | + changes = self.old_modifiers ^ mods |
| 130 | + self.old_modifiers = mods |
| 131 | + for i in range(7): |
| 132 | + bit = 1 << i |
| 133 | + if changes & bit: # Modifier key pressed or released |
| 134 | + self.set_key_state(next_modifiers[i], mods & bit) |
| 135 | + else: |
| 136 | + code = next_scancodes.get(keycode(value)) |
| 137 | + make = is_make(value) |
| 138 | + if code: |
| 139 | + if is_cc(code): |
| 140 | + if make: |
| 141 | + self.cc.send(cc_value(code)) |
| 142 | + else: |
| 143 | + self.set_key_state(code, make) |
| 144 | + |
| 145 | + |
| 146 | +handler = KeyboardHandler() |
| 147 | + |
| 148 | +recv_buf = array.array("I", [0]) |
| 149 | + |
| 150 | +sm.write(RESET) |
| 151 | +time.sleep(0.1) |
| 152 | +sm.write(set_leds(0)) |
| 153 | +time.sleep(0.1) |
| 154 | + |
| 155 | +print("Keyboard ready!") |
| 156 | + |
| 157 | +while True: |
| 158 | + sm.write(QUERY) |
| 159 | + deadline = ticks_add(ticks_ms(), 100) |
| 160 | + while ticks_less(ticks_ms(), deadline): |
| 161 | + if sm.in_waiting: |
| 162 | + sm.readinto(recv_buf) |
| 163 | + value = recv_buf[0] |
| 164 | + handler.handle_report(value) |
| 165 | + break |
| 166 | + else: |
| 167 | + print("keyboard did not respond - resetting") |
| 168 | + sm.restart() |
| 169 | + sm.write(RESET) |
| 170 | + time.sleep(0.1) |
0 commit comments