X-Git-Url: https://git.ayoreis.com/hackapet/Adafruit_Blinka.git/blobdiff_plain/96247c0ce7d885ba5d3bf9fe8725d91df2c12f2a..6e2fc2d7771cc089fb32fd3184c694e03ac21810:/src/usb_hid.py diff --git a/src/usb_hid.py b/src/usb_hid.py index dc057df..a893d83 100644 --- a/src/usb_hid.py +++ b/src/usb_hid.py @@ -1,14 +1,38 @@ +""" +`usb_hid` - support for usb hid devices via usb_gadget driver +=========================================================== +See `CircuitPython:usb_hid` in CircuitPython for more details. +For now using report ids in the descriptor + +# regarding usb_gadget see https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt +* Author(s): Björn Bösel +""" + from typing import Sequence from pathlib import Path import os import atexit -# https://www.kernel.org/doc/Documentation/usb/gadget_configfs.txt +for module in ["dwc2", "libcomposite"]: + if Path("/proc/modules").read_text().find(module) == -1: + raise Exception( + "%s module not present in your kernel. did you insmod it?" % module + ) gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka" +_boot_device = 0 +devices = [] + + +def get_boot_device() -> int: + return _boot_device class Device: + """ + HID Device specification: see https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c + """ + def __init__( self, *, @@ -28,15 +52,30 @@ class Device: self._last_received_report = None def send_report(self, report: bytearray, report_id: int = None): - device_path = self.gets_device_path(report_id) + report_id = report_id or self.report_ids[0] + device_path = self.get_device_path(report_id) with open(device_path, "rb+") as fd: + if report_id > 0: + report = bytearray(report_id.to_bytes(1, "big")) + report fd.write(report) @property def last_received_report( self, - ): - device_path = self.gets_device_path(self.report_ids[0]) + ) -> bytes: + """The HID OUT report as a `bytes` (read-only). `None` if nothing received. + Same as `get_last_received_report()` with no argument. + + Deprecated: will be removed in CircutPython 8.0.0. Use `get_last_received_report()` instead. + """ + return self.get_last_received_report() + + def get_last_received_report(self, report_id=None) -> bytes: + """Get the last received HID OUT or feature report for the given report ID. + The report ID may be omitted if there is no report ID, or only one report ID. + Return `None` if nothing received. + """ + device_path = self.get_device_path(report_id or self.report_ids[0]) with open(device_path, "rb+") as fd: os.set_blocking(fd.fileno(), False) report = fd.read(self.out_report_lengths[0]) @@ -44,7 +83,10 @@ class Device: self._last_received_report = report return self._last_received_report - def gets_device_path(self, report_id): + def get_device_path(self, report_id): + """ + translates the /dev/hidg device from the report id + """ device = ( Path( "%s/functions/hid.usb%s/dev" @@ -57,6 +99,10 @@ class Device: device_path = "/dev/hidg%s" % device return device_path + KEYBOARD = None + MOUSE = None + CONSUMER_CONTROL = None + Device.KEYBOARD = Device( descriptor=bytes( @@ -67,6 +113,8 @@ Device.KEYBOARD = Device( 0x06, # usage (keyboard) 0xA1, 0x01, # collection (application) + 0x85, + 0x01, # Report ID (1) 0x05, 0x07, # usage page (kbrd/keypad) 0x19, @@ -100,13 +148,15 @@ Device.KEYBOARD = Device( 0x29, 0x05, # usage maximum (kana) 0x91, - 0x02, # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile) + 0x02, # output + # (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile) 0x95, 0x01, # report count (1) 0x75, 0x05, # report size (5) 0x91, - 0x01, # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile) + 0x01, # output + # (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile) 0x95, 0x06, # report count (6) 0x75, @@ -143,6 +193,8 @@ Device.MOUSE = Device( 0x02, # Usage (Mouse) 0xA1, 0x01, # Collection (Application) + 0x85, + 0x02, # Report ID (2) 0x09, 0x01, # Usage (Pointer) 0xA1, @@ -201,7 +253,6 @@ Device.MOUSE = Device( 0xC0, # End Collection ) ), - # Omitted for brevity. usage_page=0x1, usage=0x02, report_ids=[2], @@ -218,6 +269,8 @@ Device.CONSUMER_CONTROL = Device( 0x01, # Usage (Consumer Control) 0xA1, 0x01, # Collection (Application) + 0x85, + 0x03, # Report ID (3) 0x75, 0x10, # Report Size (16) 0x95, @@ -237,17 +290,172 @@ Device.CONSUMER_CONTROL = Device( 0xC0, # End Collection ) ), - # Omitted for brevity. usage_page=0x0C, usage=0x01, report_ids=[3], in_report_lengths=[2], out_report_lengths=[0], ) -devices = [Device.KEYBOARD, Device.MOUSE, Device.CONSUMER_CONTROL] + +Device.BOOT_KEYBOARD = Device( + descriptor=bytes( + ( + 0x05, + 0x01, # usage page (generic desktop ctrls) + 0x09, + 0x06, # usage (keyboard) + 0xA1, + 0x01, # collection (application) + 0x05, + 0x07, # usage page (kbrd/keypad) + 0x19, + 0xE0, # usage minimum (0xe0) + 0x29, + 0xE7, # usage maximum (0xe7) + 0x15, + 0x00, # logical minimum (0) + 0x25, + 0x01, # logical maximum (1) + 0x75, + 0x01, # report size (1) + 0x95, + 0x08, # report count (8) + 0x81, + 0x02, # input (data,var,abs,no wrap,linear,preferred state,no null position) + 0x95, + 0x01, # report count (1) + 0x75, + 0x08, # report size (8) + 0x81, + 0x01, # input (const,array,abs,no wrap,linear,preferred state,no null position) + 0x95, + 0x03, # report count (3) + 0x75, + 0x01, # report size (1) + 0x05, + 0x08, # usage page (leds) + 0x19, + 0x01, # usage minimum (num lock) + 0x29, + 0x05, # usage maximum (kana) + 0x91, + 0x02, # output (data,var,abs,no wrap,linear,preferred state,no null position,non-volatile) + 0x95, + 0x01, # report count (1) + 0x75, + 0x05, # report size (5) + 0x91, + 0x01, # output (const,array,abs,no wrap,linear,preferred state,no null position,non-volatile) + 0x95, + 0x06, # report count (6) + 0x75, + 0x08, # report size (8) + 0x15, + 0x00, # logical minimum (0) + 0x26, + 0xFF, + 0x00, # logical maximum (255) + 0x05, + 0x07, # usage page (kbrd/keypad) + 0x19, + 0x00, # usage minimum (0x00) + 0x2A, + 0xFF, + 0x00, # usage maximum (0xff) + 0x81, + 0x00, # input (data,array,abs,no wrap,linear,preferred state,no null position) + 0xC0, # end collection + ) + ), + usage_page=0x1, + usage=0x6, + report_ids=[0x0], + in_report_lengths=[8], + out_report_lengths=[1], +) +Device.BOOT_MOUSE = Device( + descriptor=bytes( + ( + 0x05, + 0x01, # Usage Page (Generic Desktop Ctrls) + 0x09, + 0x02, # Usage (Mouse) + 0xA1, + 0x01, # Collection (Application) + 0x09, + 0x01, # Usage (Pointer) + 0xA1, + 0x00, # Collection (Physical) + 0x05, + 0x09, # Usage Page (Button) + 0x19, + 0x01, # Usage Minimum (0x01) + 0x29, + 0x05, # Usage Maximum (0x05) + 0x15, + 0x00, # Logical Minimum (0) + 0x25, + 0x01, # Logical Maximum (1) + 0x95, + 0x05, # Report Count (5) + 0x75, + 0x01, # Report Size (1) + 0x81, + 0x02, # Input (Data,Var,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x95, + 0x01, # Report Count (1) + 0x75, + 0x03, # Report Size (3) + 0x81, + 0x01, # Input (Const,Array,Abs,No Wrap,Linear,Preferred State,No Null Position) + 0x05, + 0x01, # Usage Page (Generic Desktop Ctrls) + 0x09, + 0x30, # Usage (X) + 0x09, + 0x31, # Usage (Y) + 0x15, + 0x81, # Logical Minimum (-127) + 0x25, + 0x7F, # Logical Maximum (127) + 0x75, + 0x08, # Report Size (8) + 0x95, + 0x02, # Report Count (2) + 0x81, + 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0x09, + 0x38, # Usage (Wheel) + 0x15, + 0x81, # Logical Minimum (-127) + 0x25, + 0x7F, # Logical Maximum (127) + 0x75, + 0x08, # Report Size (8) + 0x95, + 0x01, # Report Count (1) + 0x81, + 0x06, # Input (Data,Var,Rel,No Wrap,Linear,Preferred State,No Null Position) + 0xC0, # End Collection + 0xC0, # End Collection + ) + ), + usage_page=0x1, + usage=0x02, + report_ids=[1], + in_report_lengths=[4], + out_report_lengths=[0], +) def disable() -> None: + """Do not present any USB HID devices to the host computer. + Can be called in ``boot.py``, before USB is connected. + The HID composite device is normally enabled by default, + but on some boards with limited endpoints, including STM32F4, + it is disabled by default. You must turn off another USB device such + as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`. + """ try: Path("%s/UDC" % gadget_root).write_text("") except FileNotFoundError: @@ -274,13 +482,67 @@ def disable() -> None: pass -# atexit.register(disable) +atexit.register(disable) + + +def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: + """Specify which USB HID devices that will be available. + Can be called in ``boot.py``, before USB is connected. + + :param Sequence devices: `Device` objects. + If `devices` is empty, HID is disabled. The order of the ``Devices`` + may matter to the host. For instance, for MacOS, put the mouse device + before any Gamepad or Digitizer HID device or else it will not work. + :param int boot_device: If non-zero, inform the host that support for a + a boot HID device is available. + If ``boot_device=1``, a boot keyboard is available. + If ``boot_device=2``, a boot mouse is available. No other values are allowed. + See below. + + If you enable too many devices at once, you will run out of USB endpoints. + The number of available endpoints varies by microcontroller. + CircuitPython will go into safe mode after running ``boot.py`` to inform you if + not enough endpoints are available. + **Boot Devices** -def enable(devices: Sequence[Device], boot_device: int = 0) -> None: - if len(devices) == 0: + Boot devices implement a fixed, predefined report descriptor, defined in + https://www.usb.org/sites/default/files/hid1_12.pdf, Appendix B. A USB host + can request to use the boot device if the USB device says it is available. + Usually only a BIOS or other kind of limited-functionality + host needs boot keyboard support. + + For example, to make a boot keyboard available, you can use this code:: + + usb_hid.enable((Device.KEYBOARD), boot_device=1) # 1 for a keyboard + + If the host requests the boot keyboard, the report descriptor provided by `Device.KEYBOARD` + will be ignored, and the predefined report descriptor will be used. + But if the host does not request the boot keyboard, + the descriptor provided by `Device.KEYBOARD` will be used. + + The HID boot device must usually be the first or only device presented by CircuitPython. + The HID device will be USB interface number 0. + To make sure it is the first device, disable other USB devices, including CDC and MSC (CIRCUITPY). + If you specify a non-zero ``boot_device``, and it is not the first device, CircuitPython + will enter safe mode to report this error. + """ + global _boot_device, devices + _boot_device = boot_device + + """ Enable usb_hid + shim for https://docs.circuitpython.org/en/latest/shared-bindings/usb_hid/index.html#usb_hid.enable + + """ + if len(requested_devices) == 0: disable() return + + if boot_device == 1: + requested_devices = [Device.BOOT_KEYBOARD] + if boot_device == 2: + requested_devices = [Device.BOOT_MOUSE] + """ 1. Creating the gadgets ----------------------- @@ -362,9 +624,8 @@ def enable(devices: Sequence[Device], boot_device: int = 0) -> None: $ echo 120 > configs/c.1/MaxPower """ - for i, device in enumerate(devices): - - config_root = "%s/configs/device.%s" % (gadget_root, 1) + for i, device in enumerate(requested_devices): + config_root = "%s/configs/device.%s" % (gadget_root, i + 1) Path("%s/" % config_root).mkdir(parents=True, exist_ok=True) Path("%s/strings/0x409" % config_root).mkdir(parents=True, exist_ok=True) Path("%s/strings/0x409/configuration" % config_root).write_text( @@ -372,6 +633,7 @@ def enable(devices: Sequence[Device], boot_device: int = 0) -> None: ) Path("%s/MaxPower" % config_root).write_text("150") Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080) + devices.append(device) """ 3. Creating the functions -------------------------