|
7 | 7 | import os |
8 | 8 | import struct |
9 | 9 | from typing import Awaitable, Callable, List, Optional, TypeVar |
10 | | -from uuid import UUID |
11 | 10 |
|
12 | 11 | import reactivex.operators as op |
13 | 12 | import semver |
|
18 | 17 | from reactivex.subject import BehaviorSubject, Subject |
19 | 18 | from tqdm.auto import tqdm |
20 | 19 | from tqdm.contrib.logging import logging_redirect_tqdm |
21 | | -from usb.control import get_descriptor |
22 | 20 | from usb.core import Device as USBDevice |
23 | | -from usb.core import Endpoint, USBTimeoutError |
24 | | -from usb.util import ENDPOINT_IN, ENDPOINT_OUT, endpoint_direction, find_descriptor |
| 21 | +from usb.core import Endpoint, Interface, USBTimeoutError |
| 22 | +from usb.util import ( |
| 23 | + CTRL_IN, |
| 24 | + CTRL_RECIPIENT_INTERFACE, |
| 25 | + CTRL_TYPE_CLASS, |
| 26 | + ENDPOINT_IN, |
| 27 | + ENDPOINT_OUT, |
| 28 | + build_request_type, |
| 29 | + endpoint_direction, |
| 30 | + find_descriptor, |
| 31 | +) |
25 | 32 |
|
26 | 33 | from pybricksdev.ble.lwp3.bytecodes import HubKind |
27 | 34 | from pybricksdev.ble.nus import NUS_RX_UUID, NUS_TX_UUID |
28 | 35 | from pybricksdev.ble.pybricks import ( |
| 36 | + DEVICE_NAME_UUID, |
29 | 37 | FW_REV_UUID, |
30 | 38 | PNP_ID_UUID, |
31 | 39 | PYBRICKS_COMMAND_EVENT_UUID, |
|
37 | 45 | HubCapabilityFlag, |
38 | 46 | StatusFlag, |
39 | 47 | UserProgramId, |
| 48 | + short_uuid, |
40 | 49 | unpack_hub_capabilities, |
41 | 50 | unpack_pnp_id, |
42 | 51 | ) |
|
45 | 54 | from pybricksdev.tools import chunk |
46 | 55 | from pybricksdev.tools.checksum import xor_bytes |
47 | 56 | from pybricksdev.usb.pybricks import ( |
| 57 | + PYBRICKS_USB_INTERFACE_CLASS_REQUEST_MAX_SIZE, |
48 | 58 | PybricksUsbInEpMessageType, |
| 59 | + PybricksUsbInterfaceClassRequest, |
49 | 60 | PybricksUsbOutEpMessageType, |
50 | 61 | ) |
51 | 62 |
|
@@ -786,6 +797,40 @@ async def start_notify(self, uuid: str, callback: Callable) -> None: |
786 | 797 | return await self._client.start_notify(uuid, callback) |
787 | 798 |
|
788 | 799 |
|
| 800 | +def get_interface_class_data( |
| 801 | + dev: USBDevice, |
| 802 | + interface: Interface, |
| 803 | + desc_size: int, |
| 804 | + request: int = 0, |
| 805 | + value: int = 0, |
| 806 | +) -> bytes: |
| 807 | + """ |
| 808 | + Get a vendor-specific descriptor. |
| 809 | +
|
| 810 | + usb.util doesn't have a method like this, so we have to do it ourselves. |
| 811 | +
|
| 812 | + Args: |
| 813 | + dev: The USB device. |
| 814 | + vendor_code: The vendor code from the BOS descriptor. |
| 815 | + value: The wValue field of the request. |
| 816 | + index: The wIndex field of the request. |
| 817 | + """ |
| 818 | + |
| 819 | + bmRequestType = build_request_type( |
| 820 | + CTRL_IN, CTRL_TYPE_CLASS, CTRL_RECIPIENT_INTERFACE |
| 821 | + ) |
| 822 | + |
| 823 | + ret = dev.ctrl_transfer( |
| 824 | + bmRequestType=bmRequestType, |
| 825 | + bRequest=request, |
| 826 | + wValue=value, |
| 827 | + wIndex=interface.index, |
| 828 | + data_or_wLength=desc_size, |
| 829 | + ) |
| 830 | + |
| 831 | + return bytes(ret) |
| 832 | + |
| 833 | + |
789 | 834 | class PybricksHubUSB(PybricksHub): |
790 | 835 | _device: USBDevice |
791 | 836 | _ep_in: Endpoint |
@@ -820,46 +865,49 @@ async def _client_connect(self) -> bool: |
820 | 865 | # There is 1 byte overhead for PybricksUsbMessageType |
821 | 866 | self._max_write_size = self._ep_out.wMaxPacketSize - 1 |
822 | 867 |
|
823 | | - # Get length of BOS descriptor |
824 | | - bos_descriptor = get_descriptor(self._device, 5, 0x0F, 0) |
825 | | - (ofst, _, bos_len, _) = struct.unpack("<BBHB", bos_descriptor) |
826 | | - |
827 | | - # Get full BOS descriptor |
828 | | - bos_descriptor = get_descriptor(self._device, bos_len, 0x0F, 0) |
| 868 | + # This is the equivalent of reading GATT characteristics for BLE connections. |
829 | 869 |
|
830 | | - while ofst < bos_len: |
831 | | - (size, desc_type, cap_type) = struct.unpack_from( |
832 | | - "<BBB", bos_descriptor, offset=ofst |
| 870 | + def read_data(req: PybricksUsbInterfaceClassRequest, value: int) -> bytes: |
| 871 | + return get_interface_class_data( |
| 872 | + self._device, |
| 873 | + intf, |
| 874 | + PYBRICKS_USB_INTERFACE_CLASS_REQUEST_MAX_SIZE, |
| 875 | + req, |
| 876 | + value, |
833 | 877 | ) |
834 | 878 |
|
835 | | - if desc_type != 0x10: |
836 | | - logger.error("Expected Device Capability descriptor") |
837 | | - exit(1) |
| 879 | + hub_name_desc = read_data( |
| 880 | + PybricksUsbInterfaceClassRequest.GATT_CHARACTERISTIC, |
| 881 | + short_uuid(DEVICE_NAME_UUID), |
| 882 | + ) |
838 | 883 |
|
839 | | - # Look for platform descriptors |
840 | | - if cap_type == 0x05: |
841 | | - uuid_bytes = bos_descriptor[ofst + 4 : ofst + 4 + 16] |
842 | | - uuid_str = str(UUID(bytes_le=bytes(uuid_bytes))) |
| 884 | + self._hub_name = str(hub_name_desc, "utf-8") |
843 | 885 |
|
844 | | - if uuid_str == FW_REV_UUID: |
845 | | - fw_version = bytearray(bos_descriptor[ofst + 20 : ofst + size]) |
846 | | - self.fw_version = Version(fw_version.decode()) |
| 886 | + fw_version_desc = read_data( |
| 887 | + PybricksUsbInterfaceClassRequest.GATT_CHARACTERISTIC, |
| 888 | + short_uuid(FW_REV_UUID), |
| 889 | + ) |
847 | 890 |
|
848 | | - elif uuid_str == SW_REV_UUID: |
849 | | - self._protocol_version = bytearray( |
850 | | - bos_descriptor[ofst + 20 : ofst + size] |
851 | | - ) |
| 891 | + self.fw_version = Version(str(fw_version_desc, "utf-8")) |
852 | 892 |
|
853 | | - elif uuid_str == PYBRICKS_HUB_CAPABILITIES_UUID: |
854 | | - caps = bytearray(bos_descriptor[ofst + 20 : ofst + size]) |
855 | | - ( |
856 | | - _, |
857 | | - self._capability_flags, |
858 | | - self._max_user_program_size, |
859 | | - self._num_of_slots, |
860 | | - ) = unpack_hub_capabilities(caps) |
| 893 | + sw_version_desc = read_data( |
| 894 | + PybricksUsbInterfaceClassRequest.GATT_CHARACTERISTIC, |
| 895 | + short_uuid(SW_REV_UUID), |
| 896 | + ) |
| 897 | + |
| 898 | + self._protocol_version = str(sw_version_desc, "utf-8") |
| 899 | + |
| 900 | + hub_caps_desc = read_data( |
| 901 | + PybricksUsbInterfaceClassRequest.PYBRICKS_CHARACTERISTIC, |
| 902 | + short_uuid(PYBRICKS_HUB_CAPABILITIES_UUID), |
| 903 | + ) |
861 | 904 |
|
862 | | - ofst += size |
| 905 | + ( |
| 906 | + _, |
| 907 | + self._capability_flags, |
| 908 | + self._max_user_program_size, |
| 909 | + self._num_of_slots, |
| 910 | + ) = unpack_hub_capabilities(hub_caps_desc) |
863 | 911 |
|
864 | 912 | self._monitor_task = asyncio.create_task(self._monitor_usb()) |
865 | 913 |
|
|
0 commit comments