]> Repositories - Adafruit_Blinka-hackapet.git/blobdiff - src/usb_hid.py
odroidm1.py
[Adafruit_Blinka-hackapet.git] / src / usb_hid.py
index a893d837cee0f3ab93330404deff509ad0700f21..38e19b190528c8092248f07287f561c3b677c9f5 100644 (file)
@@ -1,3 +1,6 @@
+# SPDX-FileCopyrightText: 2021 Melissa LeBlanc-Williams for Adafruit Industries
+#
+# SPDX-License-Identifier: MIT
 """
 `usb_hid` - support for usb hid devices via usb_gadget driver
 ===========================================================
 """
 `usb_hid` - support for usb hid devices via usb_gadget driver
 ===========================================================
@@ -12,25 +15,24 @@ from typing import Sequence
 from pathlib import Path
 import os
 import atexit
 from pathlib import Path
 import os
 import atexit
+import sys
 
 for module in ["dwc2", "libcomposite"]:
 
 for module in ["dwc2", "libcomposite"]:
-    if Path("/proc/modules").read_text().find(module) == -1:
+    if Path("/proc/modules").read_text(encoding="utf-8").find(module) == -1:
         raise Exception(
             "%s module not present in your kernel. did you insmod it?" % module
         )
         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:
     """
 
 
 class Device:
     """
-    HID Device specification: see https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
+    HID Device specification: see
+    https://github.com/adafruit/circuitpython/blob/main/shared-bindings/usb_hid/Device.c
     """
 
     def __init__(
     """
 
     def __init__(
@@ -52,6 +54,10 @@ class Device:
         self._last_received_report = None
 
     def send_report(self, report: bytearray, report_id: int = None):
         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:
         report_id = report_id or self.report_ids[0]
         device_path = self.get_device_path(report_id)
         with open(device_path, "rb+") as fd:
@@ -90,9 +96,9 @@ class Device:
         device = (
             Path(
                 "%s/functions/hid.usb%s/dev"
         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()
+            .read_text(encoding="utf-8")
             .strip()
             .split(":")[1]
         )
             .strip()
             .split(":")[1]
         )
@@ -255,7 +261,7 @@ Device.MOUSE = Device(
     ),
     usage_page=0x1,
     usage=0x02,
     ),
     usage_page=0x1,
     usage=0x02,
-    report_ids=[2],
+    report_ids=[0x02],
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
@@ -339,13 +345,15 @@ Device.BOOT_KEYBOARD = Device(
             0x29,
             0x05,  # usage maximum (kana)
             0x91,
             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,
             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,
             0x95,
             0x06,  # report count (6)
             0x75,
@@ -442,7 +450,7 @@ Device.BOOT_MOUSE = Device(
     ),
     usage_page=0x1,
     usage=0x02,
     ),
     usage_page=0x1,
     usage=0x02,
-    report_ids=[1],
+    report_ids=[0],
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
     in_report_lengths=[4],
     out_report_lengths=[0],
 )
@@ -457,29 +465,30 @@ def disable() -> None:
     as `usb_cdc` or `storage` to free up endpoints for use by `usb_hid`.
     """
     try:
     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("", encoding="utf-8")
     except FileNotFoundError:
         pass
     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()
 
         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()
 
         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()
         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()
         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:
         if function_dir.is_dir():
             function_dir.rmdir()
     try:
-        Path(gadget_root).rmdir()
-    except:
+        Path(this.gadget_root).rmdir()
+    except FileNotFoundError:
         pass
         pass
+    this.devices = []
 
 
 atexit.register(disable)
 
 
 atexit.register(disable)
@@ -523,17 +532,13 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
 
     The HID boot device must usually be the first or only device presented by CircuitPython.
     The HID device will be USB interface number 0.
 
     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).
+    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.
     """
     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
+    this.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 len(requested_devices) == 0:
         disable()
         return
@@ -543,168 +548,178 @@ def enable(requested_devices: Sequence[Device], boot_device: int = 0) -> None:
     if boot_device == 2:
         requested_devices = [Device.BOOT_MOUSE]
 
     if boot_device == 2:
         requested_devices = [Device.BOOT_MOUSE]
 
-    """
-    1. Creating the gadgets
-    -----------------------
-
-    For each gadget to be created its corresponding directory must be created::
-
-        $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
-
-    e.g.::
-
-        $ mkdir $CONFIGFS_HOME/usb_gadget/g1
-
-        ...
-        ...
-        ...
-
-        $ cd $CONFIGFS_HOME/usb_gadget/g1
-
-    Each gadget needs to have its vendor id <VID> and product id <PID> specified::
-
-        $ echo <VID> > idVendor
-        $ echo <PID> > idProduct
-
-    A gadget also needs its serial number, manufacturer and product strings.
-    In order to have a place to store them, a strings subdirectory must be created
-    for each language, e.g.::
-
-        $ mkdir strings/0x409
-
-    Then the strings can be specified::
-
-        $ echo <serial number> > strings/0x409/serialnumber
-        $ echo <manufacturer> > strings/0x409/manufacturer
-        $ echo <product> > 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(
-        "%s" % 0x00
+    # """
+    # 1. Creating the gadgets
+    # -----------------------
+    #
+    # For each gadget to be created its corresponding directory must be created::
+    #
+    #     $ mkdir $CONFIGFS_HOME/usb_gadget/<gadget name>
+    #
+    # e.g.::
+    #
+    #     $ mkdir $CONFIGFS_HOME/usb_gadget/g1
+    #
+    #     ...
+    #     ...
+    #     ...
+    #
+    #     $ cd $CONFIGFS_HOME/usb_gadget/g1
+    #
+    # Each gadget needs to have its vendor id <VID> and product id <PID> specified::
+    #
+    #     $ echo <VID> > idVendor
+    #     $ echo <PID> > idProduct
+    #
+    # A gadget also needs its serial number, manufacturer and product strings.
+    # In order to have a place to store them, a strings subdirectory must be created
+    # for each language, e.g.::
+    #
+    #     $ mkdir strings/0x409
+    #
+    # Then the strings can be specified::
+    #
+    #     $ echo <serial number> > strings/0x409/serialnumber
+    #     $ echo <manufacturer> > strings/0x409/manufacturer
+    #     $ echo <product> > strings/0x409/product
+    # """
+    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, encoding="utf-8"
+    )  # Version 1.0.0
+    Path("%s/bcdUSB" % this.gadget_root).write_text(
+        "%s" % 0x0200, encoding="utf-8"
+    )  # USB 2.0
+    Path("%s/bDeviceClass" % this.gadget_root).write_text(
+        "%s" % 0x00, encoding="utf-8"
     )  # multipurpose i guess?
     )  # 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(
-        "%s" % 0x0104
+    Path("%s/bDeviceProtocol" % this.gadget_root).write_text(
+        "%s" % 0x00, encoding="utf-8"
+    )
+    Path("%s/bDeviceSubClass" % this.gadget_root).write_text(
+        "%s" % 0x00, encoding="utf-8"
+    )
+    Path("%s/bMaxPacketSize0" % this.gadget_root).write_text(
+        "%s" % 0x08, encoding="utf-8"
+    )
+    Path("%s/idProduct" % this.gadget_root).write_text(
+        "%s" % 0x0104, encoding="utf-8"
     )  # Multifunction Composite Gadget
     )  # Multifunction Composite Gadget
-    Path("%s/idVendor" % gadget_root).write_text("%s" % 0x1D6B)  # Linux Foundation
-    """
-    2. Creating the configurations
-    ------------------------------
-
-    Each gadget will consist of a number of configurations, their corresponding
-    directories must be created:
-
-    $ mkdir configs/<name>.<number>
-
-    where <name> can be any string which is legal in a filesystem and the
-    <number> is the configuration's number, e.g.::
-
-        $ mkdir configs/c.1
-
-        ...
-        ...
-        ...
-
-    Each configuration also needs its strings, so a subdirectory must be created
-    for each language, e.g.::
-
-        $ mkdir configs/c.1/strings/0x409
-
-    Then the configuration string can be specified::
-
-        $ echo <configuration> > configs/c.1/strings/0x409/configuration
-
-    Some attributes can also be set for a configuration, e.g.::
-
-        $ echo 120 > configs/c.1/MaxPower
-        """
-
-    for i, device in enumerate(requested_devices):
-        config_root = "%s/configs/device.%s" % (gadget_root, i + 1)
+    Path("%s/idVendor" % this.gadget_root).write_text(
+        "%s" % 0x1D6B, encoding="utf-8"
+    )  # Linux Foundation
+    # """
+    # 2. Creating the configurations
+    # ------------------------------
+    #
+    # Each gadget will consist of a number of configurations, their corresponding
+    # directories must be created:
+    #
+    # $ mkdir configs/<name>.<number>
+    #
+    # where <name> can be any string which is legal in a filesystem and the
+    # <number> is the configuration's number, e.g.::
+    #
+    #     $ mkdir configs/c.1
+    #
+    #     ...
+    #     ...
+    #     ...
+    #
+    # Each configuration also needs its strings, so a subdirectory must be created
+    # for each language, e.g.::
+    #
+    #     $ mkdir configs/c.1/strings/0x409
+    #
+    # Then the configuration string can be specified::
+    #
+    #     $ echo <configuration> > configs/c.1/strings/0x409/configuration
+    #
+    # Some attributes can also be set for a configuration, e.g.::
+    #
+    #     $ echo 120 > configs/c.1/MaxPower
+    #     """
+
+    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(
         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(
-            "my configuration"
+            "my configuration", encoding="utf-8"
         )
         )
-        Path("%s/MaxPower" % config_root).write_text("150")
-        Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080)
-        devices.append(device)
-        """
-        3. Creating the functions
-        -------------------------
-
-        The gadget will provide some functions, for each function its corresponding
-        directory must be created::
-
-            $ mkdir functions/<name>.<instance name>
-
-        where <name> corresponds to one of allowed function names and instance name
-        is an arbitrary string allowed in a filesystem, e.g.::
-
-          $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
-
-          ...
-          ...
-          ...
-
-        Each function provides its specific set of attributes, with either read-only
-        or read-write access. Where applicable they need to be written to as
-        appropriate.
-        Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.
-
-        """
-
-        # create functions
+        Path("%s/MaxPower" % config_root).write_text("150", encoding="utf-8")
+        Path("%s/bmAttributes" % config_root).write_text("%s" % 0x080, encoding="utf-8")
+        this.devices.append(device)
+        # """
+        # 3. Creating the functions
+        # -------------------------
+        #
+        # The gadget will provide some functions, for each function its corresponding
+        # directory must be created::
+        #
+        #     $ mkdir functions/<name>.<instance name>
+        #
+        # where <name> corresponds to one of allowed function names and instance name
+        # is an arbitrary string allowed in a filesystem, e.g.::
+        #
+        #   $ mkdir functions/ncm.usb0 # usb_f_ncm.ko gets loaded with request_module()
+        #
+        #   ...
+        #   ...
+        #   ...
+        #
+        # Each function provides its specific set of attributes, with either read-only
+        # or read-write access. Where applicable they need to be written to as
+        # appropriate.
+        # Please refer to Documentation/ABI/*/configfs-usb-gadget* for more information.  """
         for report_index, report_id in enumerate(device.report_ids):
         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:
                 continue
             try:
                 Path("%s/" % function_root).mkdir(parents=True)
             except FileExistsError:
                 continue
-            Path("%s/protocol" % function_root).write_text("%s" % report_id)
+            Path("%s/protocol" % function_root).write_text(
+                "%s" % report_id, encoding="utf-8"
+            )
             Path("%s/report_length" % function_root).write_text(
             Path("%s/report_length" % function_root).write_text(
-                "%s" % device.in_report_lengths[report_index]
+                "%s" % device.in_report_lengths[report_index], encoding="utf-8"
             )
             )
-            Path("%s/subclass" % function_root).write_text("%s" % 1)
+            Path("%s/subclass" % function_root).write_text("%s" % 1, encoding="utf-8")
             Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
             Path("%s/report_desc" % function_root).write_bytes(device.descriptor)
-            """
-            4. Associating the functions with their configurations
-            ------------------------------------------------------
-
-            At this moment a number of gadgets is created, each of which has a number of
-            configurations specified and a number of functions available. What remains
-            is specifying which function is available in which configuration (the same
-            function can be used in multiple configurations). This is achieved with
-            creating symbolic links::
-
-                $ ln -s functions/<name>.<instance name> configs/<name>.<number>
-
-            e.g.::
-
-                $ ln -s functions/ncm.usb0 configs/c.1
-            """
-
-            Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(function_root)
-    """
-    5. Enabling the gadget
-    ----------------------
-    Such a gadget must be finally enabled so that the USB host can enumerate it.
-
-    In order to enable the gadget it must be bound to a UDC (USB Device
-    Controller)::
-
-        $ echo <udc name> > UDC
-
-    where <udc name> is one of those found in /sys/class/udc/*
-    e.g.::
-
-    $ echo s3c-hsotg > UDC
-
-    """
+            """
+            4. Associating the functions with their configurations
+            ------------------------------------------------------
+            #
+            At this moment a number of gadgets is created, each of which has a number of
+            configurations specified and a number of functions available. What remains
+            is specifying which function is available in which configuration (the same
+            function can be used in multiple configurations). This is achieved with
+            creating symbolic links::
+            #
+                $ ln -s functions/<name>.<instance name> configs/<name>.<number>
+            #
+            e.g.::
+            #
+            #     $ ln -s functions/ncm.usb0 configs/c.1  """
+            try:
+                Path("%s/hid.usb%s" % (config_root, report_id)).symlink_to(
+                    function_root
+                )
+            except FileNotFoundError:
+                pass
+    # """ 5. Enabling the gadget
+    # ----------------------
+    # Such a gadget must be finally enabled so that the USB host can enumerate it.
+    #
+    # In order to enable the gadget it must be bound to a UDC (USB Device
+    # Controller)::
+    #
+    #     $ echo <udc name> > UDC
+    #
+    # where <udc name> is one of those found in /sys/class/udc/*
+    # e.g.::
+    #
+    # $ echo s3c-hsotg > UDC  """
     udc = next(Path("/sys/class/udc/").glob("*"))
     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, encoding="utf-8")