|
| 1 | +# MicroPython USB keypad module |
| 2 | +# MIT license; Copyright (c) 2023 Dave Wickham |
| 3 | + |
| 4 | +from .hid import HIDInterface |
| 5 | +from .keycodes import KEYPAD_KEYS_TO_KEYCODES |
| 6 | +from .utils import STAGE_SETUP, split_bmRequestType |
| 7 | +from micropython import const |
| 8 | +import micropython |
| 9 | + |
| 10 | +_INTERFACE_PROTOCOL_KEYBOARD = const(0x01) |
| 11 | +_REQ_CONTROL_SET_REPORT = const(0x09) |
| 12 | +_REQ_CONTROL_SET_IDLE = const(0x0A) |
| 13 | + |
| 14 | +# fmt: off |
| 15 | +_KEYPAD_REPORT_DESC = bytes( |
| 16 | + [ |
| 17 | + 0x05, 0x01, # Usage Page (Generic Desktop) |
| 18 | + 0x09, 0x07, # Usage (Keypad) |
| 19 | + 0xA1, 0x01, # Collection (Application) |
| 20 | + 0x05, 0x07, # Usage Page (Keypad) |
| 21 | + 0x19, 0x00, # Usage Minimum (00), |
| 22 | + 0x29, 0xFF, # Usage Maximum (ff), |
| 23 | + 0x15, 0x00, # Logical Minimum (0), |
| 24 | + 0x25, 0xFF, # Logical Maximum (ff), |
| 25 | + 0x95, 0x01, # Report Count (1), |
| 26 | + 0x75, 0x08, # Report Size (8), |
| 27 | + 0x81, 0x00, # Input (Data, Array, Absolute) |
| 28 | + 0x05, 0x08, # Usage page (LEDs) |
| 29 | + 0x19, 0x01, # Usage minimum (1) |
| 30 | + 0x29, 0x05, # Usage Maximum (5), |
| 31 | + 0x95, 0x05, # Report Count (5), |
| 32 | + 0x75, 0x01, # Report Size (1), |
| 33 | + 0x91, 0x02, # Output (Data, Variable, Absolute) |
| 34 | + 0x95, 0x01, # Report Count (1), |
| 35 | + 0x75, 0x03, # Report Size (3), |
| 36 | + 0x91, 0x01, # Output (Constant) |
| 37 | + 0xC0, # End Collection |
| 38 | + ] |
| 39 | +) |
| 40 | +# fmt: on |
| 41 | + |
| 42 | + |
| 43 | +class KeypadInterface(HIDInterface): |
| 44 | + # Very basic synchronous USB keypad HID interface |
| 45 | + |
| 46 | + def __init__(self): |
| 47 | + self.numlock = None |
| 48 | + self.capslock = None |
| 49 | + self.scrolllock = None |
| 50 | + self.compose = None |
| 51 | + self.kana = None |
| 52 | + self.set_report_initialised = False |
| 53 | + super().__init__( |
| 54 | + _KEYPAD_REPORT_DESC, |
| 55 | + protocol=_INTERFACE_PROTOCOL_KEYBOARD, |
| 56 | + interface_str="MicroPython Keypad!", |
| 57 | + use_out_ep=True, |
| 58 | + ) |
| 59 | + |
| 60 | + def handle_interface_control_xfer(self, stage, request): |
| 61 | + if request[1] == _REQ_CONTROL_SET_IDLE and not self.set_report_initialised: |
| 62 | + # Hacky initialisation goes here |
| 63 | + self.set_report() |
| 64 | + self.set_report_initialised = True |
| 65 | + |
| 66 | + if stage == STAGE_SETUP: |
| 67 | + return super().handle_interface_control_xfer(stage, request) |
| 68 | + |
| 69 | + bmRequestType, bRequest, wValue, _, _ = request |
| 70 | + recipient, req_type, _ = split_bmRequestType(bmRequestType) |
| 71 | + |
| 72 | + return True |
| 73 | + |
| 74 | + def set_report(self, args=None): |
| 75 | + self.out_buffer = bytearray(1) |
| 76 | + self.submit_xfer(self._out_ep, self.out_buffer, self.set_report_cb) |
| 77 | + return True |
| 78 | + |
| 79 | + def set_report_cb(self, ep_addr, result, xferred_bytes): |
| 80 | + buf_result = int(self.out_buffer[0]) |
| 81 | + self.numlock = buf_result & 1 |
| 82 | + self.capslock = (buf_result >> 1) & 1 |
| 83 | + self.scrolllock = (buf_result >> 2) & 1 |
| 84 | + self.compose = (buf_result >> 3) & 1 |
| 85 | + self.kana = (buf_result >> 4) & 1 |
| 86 | + |
| 87 | + micropython.schedule(self.set_report, None) |
| 88 | + |
| 89 | + def send_report(self, key=None): |
| 90 | + if key is None: |
| 91 | + super().send_report(bytes(1)) |
| 92 | + else: |
| 93 | + super().send_report(KEYPAD_KEYS_TO_KEYCODES[key].to_bytes(1, "big")) |
0 commit comments