From 573d988a3c40db538b74a1653dab7520968fd56b Mon Sep 17 00:00:00 2001 From: Bob Abeles Date: Tue, 12 Sep 2023 13:51:55 -0700 Subject: [PATCH 1/4] Increase host timeout from 1 second to 20. --- adafruit_hid/consumer_control.py | 14 ++++++++------ adafruit_hid/keyboard.py | 16 +++++++++------- adafruit_hid/mouse.py | 14 ++++++++------ 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/adafruit_hid/consumer_control.py b/adafruit_hid/consumer_control.py index d26f116..88985c9 100644 --- a/adafruit_hid/consumer_control.py +++ b/adafruit_hid/consumer_control.py @@ -44,12 +44,14 @@ def __init__(self, devices: Sequence[usb_hid.Device]) -> None: self._report = bytearray(2) # Do a no-op to test if HID device is ready. - # If not, wait a bit and try once more. - try: - self.send(0x0) - except OSError: - time.sleep(1) - self.send(0x0) + # Some hosts take awhile to enumerate after POR, so retry a few times. + for _ in range(20): + try: + self.send(0x0) + return + except OSError: + time.sleep(1.0) + raise OSError("HID device init timeout.") def send(self, consumer_code: int) -> None: """Send a report to do the specified consumer control action, diff --git a/adafruit_hid/keyboard.py b/adafruit_hid/keyboard.py index c8bbbab..7fc0454 100644 --- a/adafruit_hid/keyboard.py +++ b/adafruit_hid/keyboard.py @@ -66,13 +66,15 @@ def __init__(self, devices: Sequence[usb_hid.Device]) -> None: self._led_status = b"\x00" # Do a no-op to test if HID device is ready. - # If not, wait a bit and try once more. - try: - self.release_all() - except OSError: - time.sleep(1) - self.release_all() - + # Some hosts take awhile to enumerate after POR, so retry a few times. + for _ in range(20): + try: + self.release_all() + return + except OSError: + time.sleep(1.0) + raise OSError("HID device init timeout.") + def press(self, *keycodes: int) -> None: """Send a report indicating that the given keys have been pressed. diff --git a/adafruit_hid/mouse.py b/adafruit_hid/mouse.py index 3b324db..29b88c1 100644 --- a/adafruit_hid/mouse.py +++ b/adafruit_hid/mouse.py @@ -46,12 +46,14 @@ def __init__(self, devices: Sequence[usb_hid.Device]): self.report = bytearray(4) # Do a no-op to test if HID device is ready. - # If not, wait a bit and try once more. - try: - self._send_no_move() - except OSError: - time.sleep(1) - self._send_no_move() + # Some hosts take awhile to enumerate after POR, so retry a few times. + for _ in range(20): + try: + self._send_no_move() + return + except OSError: + time.sleep(1.0) + raise OSError("HID device init timeout.") def press(self, buttons: int) -> None: """Press the given mouse buttons. From 31e52ee80dd97a412f18d383349ef77e8be72aa5 Mon Sep 17 00:00:00 2001 From: Bob Abeles Date: Tue, 12 Sep 2023 14:17:28 -0700 Subject: [PATCH 2/4] Fix black issue --- adafruit_hid/keyboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/adafruit_hid/keyboard.py b/adafruit_hid/keyboard.py index 7fc0454..1b67a03 100644 --- a/adafruit_hid/keyboard.py +++ b/adafruit_hid/keyboard.py @@ -74,7 +74,7 @@ def __init__(self, devices: Sequence[usb_hid.Device]) -> None: except OSError: time.sleep(1.0) raise OSError("HID device init timeout.") - + def press(self, *keycodes: int) -> None: """Send a report indicating that the given keys have been pressed. From 5bf6505d9ef815686d4a5f1a73af3feb2f784243 Mon Sep 17 00:00:00 2001 From: Bob Abeles Date: Wed, 13 Sep 2023 06:27:42 -0700 Subject: [PATCH 3/4] Review feedback --- adafruit_hid/__init__.py | 25 ++++++++++++++++++------- adafruit_hid/consumer_control.py | 19 ++++++------------- adafruit_hid/keyboard.py | 19 ++++++------------- adafruit_hid/mouse.py | 20 ++++++-------------- 4 files changed, 36 insertions(+), 47 deletions(-) diff --git a/adafruit_hid/__init__.py b/adafruit_hid/__init__.py index 532d31a..6d0e964 100644 --- a/adafruit_hid/__init__.py +++ b/adafruit_hid/__init__.py @@ -19,6 +19,8 @@ # imports from __future__ import annotations +import time +import supervisor try: from typing import Sequence @@ -31,17 +33,26 @@ def find_device( - devices: Sequence[usb_hid.Device], *, usage_page: int, usage: int + devices: Sequence[usb_hid.Device], *, usage_page: int, usage: int, timeout: int ) -> usb_hid.Device: """Search through the provided sequence of devices to find the one with the matching - usage_page and usage.""" + usage_page and usage. Wait up to timeout seconds for USB to become ready.""" if hasattr(devices, "send_report"): devices = [devices] # type: ignore - for device in devices: + device = None + for dev in devices: if ( - device.usage_page == usage_page - and device.usage == usage - and hasattr(device, "send_report") + dev.usage_page == usage_page + and dev.usage == usage + and hasattr(dev, "send_report") ): + device = dev + break + if device is None: + raise ValueError("Could not find matching HID device.") + + for _ in range(timeout): + if supervisor.runtime.usb_connected: return device - raise ValueError("Could not find matching HID device.") + time.sleep(1.0) + raise OSError("Failed to initialize HID device. Is USB connected?") diff --git a/adafruit_hid/consumer_control.py b/adafruit_hid/consumer_control.py index 88985c9..f86cbab 100644 --- a/adafruit_hid/consumer_control.py +++ b/adafruit_hid/consumer_control.py @@ -18,7 +18,6 @@ # pylint: disable=wrong-import-position import struct -import time from . import find_device try: @@ -31,28 +30,22 @@ class ConsumerControl: """Send ConsumerControl code reports, used by multimedia keyboards, remote controls, etc.""" - def __init__(self, devices: Sequence[usb_hid.Device]) -> None: + def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = 45) -> None: """Create a ConsumerControl object that will send Consumer Control Device HID reports. + :param timeout: Time in seconds to wait for USB to become ready before timing out. + Devices can be a sequence of devices that includes a Consumer Control device or a CC device itself. A device is any object that implements ``send_report()``, ``usage_page`` and ``usage``. """ - self._consumer_device = find_device(devices, usage_page=0x0C, usage=0x01) + self._consumer_device = find_device( + devices, usage_page=0x0C, usage=0x01, timeout=timeout + ) # Reuse this bytearray to send consumer reports. self._report = bytearray(2) - # Do a no-op to test if HID device is ready. - # Some hosts take awhile to enumerate after POR, so retry a few times. - for _ in range(20): - try: - self.send(0x0) - return - except OSError: - time.sleep(1.0) - raise OSError("HID device init timeout.") - def send(self, consumer_code: int) -> None: """Send a report to do the specified consumer control action, and then stop the action (so it will not repeat). diff --git a/adafruit_hid/keyboard.py b/adafruit_hid/keyboard.py index 1b67a03..9b38a71 100644 --- a/adafruit_hid/keyboard.py +++ b/adafruit_hid/keyboard.py @@ -9,7 +9,6 @@ * Author(s): Scott Shawcroft, Dan Halbert """ -import time from micropython import const import usb_hid @@ -39,14 +38,18 @@ class Keyboard: # No more than _MAX_KEYPRESSES regular keys may be pressed at once. - def __init__(self, devices: Sequence[usb_hid.Device]) -> None: + def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = 45) -> None: """Create a Keyboard object that will send keyboard HID reports. + :param timeout: Time in seconds to wait for USB to become ready before timing out. + Devices can be a sequence of devices that includes a keyboard device or a keyboard device itself. A device is any object that implements ``send_report()``, ``usage_page`` and ``usage``. """ - self._keyboard_device = find_device(devices, usage_page=0x1, usage=0x06) + self._keyboard_device = find_device( + devices, usage_page=0x1, usage=0x06, timeout=timeout + ) # Reuse this bytearray to send keyboard reports. self.report = bytearray(8) @@ -65,16 +68,6 @@ def __init__(self, devices: Sequence[usb_hid.Device]) -> None: # No keyboard LEDs on. self._led_status = b"\x00" - # Do a no-op to test if HID device is ready. - # Some hosts take awhile to enumerate after POR, so retry a few times. - for _ in range(20): - try: - self.release_all() - return - except OSError: - time.sleep(1.0) - raise OSError("HID device init timeout.") - def press(self, *keycodes: int) -> None: """Send a report indicating that the given keys have been pressed. diff --git a/adafruit_hid/mouse.py b/adafruit_hid/mouse.py index 29b88c1..fbb193b 100644 --- a/adafruit_hid/mouse.py +++ b/adafruit_hid/mouse.py @@ -8,8 +8,6 @@ * Author(s): Dan Halbert """ -import time - from . import find_device try: @@ -29,14 +27,18 @@ class Mouse: MIDDLE_BUTTON = 4 """Middle mouse button.""" - def __init__(self, devices: Sequence[usb_hid.Device]): + def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = 45) -> None: """Create a Mouse object that will send USB mouse HID reports. + :param timeout: Time in seconds to wait for USB to become ready before timing out. + Devices can be a sequence of devices that includes a keyboard device or a keyboard device itself. A device is any object that implements ``send_report()``, ``usage_page`` and ``usage``. """ - self._mouse_device = find_device(devices, usage_page=0x1, usage=0x02) + self._mouse_device = find_device( + devices, usage_page=0x1, usage=0x02, timeout=timeout + ) # Reuse this bytearray to send mouse reports. # report[0] buttons pressed (LEFT, MIDDLE, RIGHT) @@ -45,16 +47,6 @@ def __init__(self, devices: Sequence[usb_hid.Device]): # report[3] wheel movement self.report = bytearray(4) - # Do a no-op to test if HID device is ready. - # Some hosts take awhile to enumerate after POR, so retry a few times. - for _ in range(20): - try: - self._send_no_move() - return - except OSError: - time.sleep(1.0) - raise OSError("HID device init timeout.") - def press(self, buttons: int) -> None: """Press the given mouse buttons. From 9b398e095770705e56d00b6721d002fc19dcee29 Mon Sep 17 00:00:00 2001 From: Bob Abeles Date: Wed, 13 Sep 2023 15:51:08 -0700 Subject: [PATCH 4/4] second round code review changes --- adafruit_hid/__init__.py | 38 ++++++++++++++++++++++++++------ adafruit_hid/consumer_control.py | 3 ++- adafruit_hid/keyboard.py | 3 ++- adafruit_hid/mouse.py | 3 ++- 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/adafruit_hid/__init__.py b/adafruit_hid/__init__.py index 6d0e964..f4d8f76 100644 --- a/adafruit_hid/__init__.py +++ b/adafruit_hid/__init__.py @@ -20,7 +20,11 @@ # imports from __future__ import annotations import time -import supervisor + +try: + import supervisor +except ImportError: + supervisor = None try: from typing import Sequence @@ -33,10 +37,18 @@ def find_device( - devices: Sequence[usb_hid.Device], *, usage_page: int, usage: int, timeout: int + devices: Sequence[usb_hid.Device], + *, + usage_page: int, + usage: int, + timeout: int = None, ) -> usb_hid.Device: """Search through the provided sequence of devices to find the one with the matching - usage_page and usage. Wait up to timeout seconds for USB to become ready.""" + usage_page and usage. + + :param timeout: Time in seconds to wait for USB to become ready before timing out. + Defaults to None to wait indefinitely.""" + if hasattr(devices, "send_report"): devices = [devices] # type: ignore device = None @@ -51,8 +63,20 @@ def find_device( if device is None: raise ValueError("Could not find matching HID device.") - for _ in range(timeout): - if supervisor.runtime.usb_connected: - return device + if supervisor is None: + # Blinka doesn't have supervisor (see issue Adafruit_Blinka#711), so wait + # one second for USB to become ready time.sleep(1.0) - raise OSError("Failed to initialize HID device. Is USB connected?") + elif timeout is None: + # default behavior: wait indefinitely for USB to become ready + while not supervisor.runtime.usb_connected: + time.sleep(1.0) + else: + # wait up to timeout seconds for USB to become ready + for _ in range(timeout): + if supervisor.runtime.usb_connected: + return device + time.sleep(1.0) + raise OSError("Failed to initialize HID device. Is USB connected?") + + return device diff --git a/adafruit_hid/consumer_control.py b/adafruit_hid/consumer_control.py index f86cbab..e501040 100644 --- a/adafruit_hid/consumer_control.py +++ b/adafruit_hid/consumer_control.py @@ -30,10 +30,11 @@ class ConsumerControl: """Send ConsumerControl code reports, used by multimedia keyboards, remote controls, etc.""" - def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = 45) -> None: + def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None: """Create a ConsumerControl object that will send Consumer Control Device HID reports. :param timeout: Time in seconds to wait for USB to become ready before timing out. + Defaults to None to wait indefinitely. Devices can be a sequence of devices that includes a Consumer Control device or a CC device itself. A device is any object that implements ``send_report()``, ``usage_page`` and diff --git a/adafruit_hid/keyboard.py b/adafruit_hid/keyboard.py index 9b38a71..282d545 100644 --- a/adafruit_hid/keyboard.py +++ b/adafruit_hid/keyboard.py @@ -38,10 +38,11 @@ class Keyboard: # No more than _MAX_KEYPRESSES regular keys may be pressed at once. - def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = 45) -> None: + def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None: """Create a Keyboard object that will send keyboard HID reports. :param timeout: Time in seconds to wait for USB to become ready before timing out. + Defaults to None to wait indefinitely. Devices can be a sequence of devices that includes a keyboard device or a keyboard device itself. A device is any object that implements ``send_report()``, ``usage_page`` and diff --git a/adafruit_hid/mouse.py b/adafruit_hid/mouse.py index fbb193b..014b778 100644 --- a/adafruit_hid/mouse.py +++ b/adafruit_hid/mouse.py @@ -27,10 +27,11 @@ class Mouse: MIDDLE_BUTTON = 4 """Middle mouse button.""" - def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = 45) -> None: + def __init__(self, devices: Sequence[usb_hid.Device], timeout: int = None) -> None: """Create a Mouse object that will send USB mouse HID reports. :param timeout: Time in seconds to wait for USB to become ready before timing out. + Defaults to None to wait indefinitely. Devices can be a sequence of devices that includes a keyboard device or a keyboard device itself. A device is any object that implements ``send_report()``, ``usage_page`` and