X-Git-Url: https://git.ayoreis.com/Adafruit_Blinka-hackapet.git/blobdiff_plain/2541a3b03bc62429c419beef96f038ca11b2fec2..9b912a38f45f69764a4db26309e16fdac6fe5678:/src/usb_hid.py diff --git a/src/usb_hid.py b/src/usb_hid.py index afa2a99..f7ecf97 100644 --- a/src/usb_hid.py +++ b/src/usb_hid.py @@ -12,20 +12,18 @@ from typing import Sequence from pathlib import Path import os import atexit +import sys 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 ) +this = sys.modules[__name__] -gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka" -_boot_device = 0 -devices = [] - - -def get_boot_device() -> int: - return _boot_device +this.gadget_root = "/sys/kernel/config/usb_gadget/adafruit-blinka" +this.boot_device = 0 +this.devices = [] class Device: @@ -53,6 +51,10 @@ class Device: self._last_received_report = None def send_report(self, report: bytearray, report_id: int = None): + """Send an HID report. If the device descriptor specifies zero or one report id's, + you can supply `None` (the default) as the value of ``report_id``. + Otherwise you must specify which report id to use when sending the report. + """ report_id = report_id or self.report_ids[0] device_path = self.get_device_path(report_id) with open(device_path, "rb+") as fd: @@ -91,7 +93,7 @@ class Device: device = ( Path( "%s/functions/hid.usb%s/dev" - % (gadget_root, report_id or self.report_ids[0]) + % (this.gadget_root, report_id or self.report_ids[0]) ) .read_text() .strip() @@ -256,7 +258,7 @@ Device.MOUSE = Device( ), usage_page=0x1, usage=0x02, - report_ids=[2], + report_ids=[0x02], in_report_lengths=[4], out_report_lengths=[0], ) @@ -445,93 +447,94 @@ Device.BOOT_MOUSE = Device( ), usage_page=0x1, usage=0x02, - report_ids=[1], + report_ids=[0], 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`. - # """ + """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("") + Path("%s/UDC" % this.gadget_root).write_text("") except FileNotFoundError: pass - for symlink in Path(gadget_root).glob("configs/**/hid.usb*"): + for symlink in Path(this.gadget_root).glob("configs/**/hid.usb*"): symlink.unlink() - for strings_file in Path(gadget_root).rglob("configs/*/strings/*/*"): + for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*/*"): if strings_file.is_dir(): strings_file.rmdir() - for strings_file in Path(gadget_root).rglob("configs/*/strings/*"): + for strings_file in Path(this.gadget_root).rglob("configs/*/strings/*"): if strings_file.is_dir(): strings_file.rmdir() - for config_dir in Path(gadget_root).rglob("configs/*"): + for config_dir in Path(this.gadget_root).rglob("configs/*"): if config_dir.is_dir(): config_dir.rmdir() - for function_dir in Path(gadget_root).rglob("functions/*"): + for function_dir in Path(this.gadget_root).rglob("functions/*"): if function_dir.is_dir(): function_dir.rmdir() try: - Path(gadget_root).rmdir() + Path(this.gadget_root).rmdir() except FileNotFoundError: pass + this.devices = [] 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** -# -# 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 + """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** + + 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. + """ + this.boot_device = boot_device if len(requested_devices) == 0: disable() @@ -577,20 +580,20 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: # $ echo > strings/0x409/manufacturer # $ echo > strings/0x409/product # """ - Path("%s/functions" % gadget_root).mkdir(parents=True, exist_ok=True) - Path("%s/configs" % gadget_root).mkdir(parents=True, exist_ok=True) - Path("%s/bcdDevice" % gadget_root).write_text("%s" % 1) # Version 1.0.0 - Path("%s/bcdUSB" % gadget_root).write_text("%s" % 0x0200) # USB 2.0 - Path("%s/bDeviceClass" % gadget_root).write_text( + Path("%s/functions" % this.gadget_root).mkdir(parents=True, exist_ok=True) + Path("%s/configs" % this.gadget_root).mkdir(parents=True, exist_ok=True) + Path("%s/bcdDevice" % this.gadget_root).write_text("%s" % 1) # Version 1.0.0 + Path("%s/bcdUSB" % this.gadget_root).write_text("%s" % 0x0200) # USB 2.0 + Path("%s/bDeviceClass" % this.gadget_root).write_text( "%s" % 0x00 ) # multipurpose i guess? - Path("%s/bDeviceProtocol" % gadget_root).write_text("%s" % 0x00) - Path("%s/bDeviceSubClass" % gadget_root).write_text("%s" % 0x00) - Path("%s/bMaxPacketSize0" % gadget_root).write_text("%s" % 0x08) - Path("%s/idProduct" % gadget_root).write_text( + Path("%s/bDeviceProtocol" % this.gadget_root).write_text("%s" % 0x00) + Path("%s/bDeviceSubClass" % this.gadget_root).write_text("%s" % 0x00) + Path("%s/bMaxPacketSize0" % this.gadget_root).write_text("%s" % 0x08) + Path("%s/idProduct" % this.gadget_root).write_text( "%s" % 0x0104 ) # Multifunction Composite Gadget - Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation + Path("%s/idVendor" % this.gadget_root).write_text("%s" % 0x1D6B) # Linux Foundation # """ # 2. Creating the configurations # ------------------------------ @@ -623,8 +626,8 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: # $ echo 120 > configs/c.1/MaxPower # """ - for i, device in enumerate(requested_devices): - config_root = "%s/configs/device.%s" % (gadget_root, i + 1) + for device in requested_devices: + config_root = "%s/configs/device.1" % this.gadget_root 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( @@ -632,7 +635,7 @@ def enable(requested_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) + this.devices.append(device) # """ # 3. Creating the functions # ------------------------- @@ -656,7 +659,7 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: # appropriate. # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information. """ for report_index, report_id in enumerate(device.report_ids): - function_root = "%s/functions/hid.usb%s" % (gadget_root, report_id) + function_root = "%s/functions/hid.usb%s" % (this.gadget_root, report_id) try: Path("%s/" % function_root).mkdir(parents=True) except FileExistsError: @@ -682,7 +685,12 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: # e.g.:: # # $ ln -s functions/ncm.usb0 configs/c.1 """ - Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root) + try: + Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to( + function_root + ) + except: + pass # """ 5. Enabling the gadget # ---------------------- # Such a gadget must be finally enabled so that the USB host can enumerate it. @@ -697,4 +705,4 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None: # # $ echo s3c-hsotg > UDC """ udc = next(Path("/sys/class/udc/").glob("*")) - Path("%s/UDC" % gadget_root).write_text("%s" % udc.name) + Path("%s/UDC" % this.gadget_root).write_text("%s" % udc.name)