forked from micropython/micropython-lib
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
pyusb: Add MicroPython implementation of PyUSB library.
Signed-off-by: Damien George <[email protected]>
- Loading branch information
Showing
6 changed files
with
288 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
# Simple example to list attached USB devices. | ||
|
||
import usb.core | ||
|
||
for device in usb.core.find(find_all=True): | ||
print("ID {:04x}:{:04x}".format(device.idVendor, device.idProduct)) | ||
for cfg in device: | ||
print( | ||
" config numitf={} value={} attr={} power={}".format( | ||
cfg.bNumInterfaces, cfg.bConfigurationValue, cfg.bmAttributes, cfg.bMaxPower | ||
) | ||
) | ||
for itf in cfg: | ||
print( | ||
" interface class={} subclass={}".format( | ||
itf.bInterfaceClass, itf.bInterfaceSubClass | ||
) | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
metadata(version="0.1.0", pypi="pyusb") | ||
|
||
package("usb") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
# MicroPython USB host library, compatible with PyUSB. | ||
# MIT license; Copyright (c) 2021-2024 Damien P. George |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
# MicroPython USB host library, compatible with PyUSB. | ||
# MIT license; Copyright (c) 2021-2024 Damien P. George | ||
|
||
|
||
def get_descriptor(dev, desc_size, desc_type, desc_index, wIndex=0): | ||
wValue = desc_index | desc_type << 8 | ||
d = dev.ctrl_transfer(0x80, 0x06, wValue, wIndex, desc_size) | ||
if len(d) < 2: | ||
raise Exception("invalid descriptor") | ||
return d |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,239 @@ | ||
# MicroPython USB host library, compatible with PyUSB. | ||
# MIT license; Copyright (c) 2021-2024 Damien P. George | ||
|
||
import sys | ||
import ffi | ||
import uctypes | ||
|
||
if sys.maxsize >> 32: | ||
UINTPTR_SIZE = 8 | ||
UINTPTR = uctypes.UINT64 | ||
else: | ||
UINTPTR_SIZE = 4 | ||
UINTPTR = uctypes.UINT32 | ||
|
||
|
||
def _align_word(x): | ||
return (x + UINTPTR_SIZE - 1) & ~(UINTPTR_SIZE - 1) | ||
|
||
|
||
ptr_descriptor = (0 | uctypes.ARRAY, 1 | UINTPTR) | ||
|
||
libusb_device_descriptor = { | ||
"bLength": 0 | uctypes.UINT8, | ||
"bDescriptorType": 1 | uctypes.UINT8, | ||
"bcdUSB": 2 | uctypes.UINT16, | ||
"bDeviceClass": 4 | uctypes.UINT8, | ||
"bDeviceSubClass": 5 | uctypes.UINT8, | ||
"bDeviceProtocol": 6 | uctypes.UINT8, | ||
"bMaxPacketSize0": 7 | uctypes.UINT8, | ||
"idVendor": 8 | uctypes.UINT16, | ||
"idProduct": 10 | uctypes.UINT16, | ||
"bcdDevice": 12 | uctypes.UINT16, | ||
"iManufacturer": 14 | uctypes.UINT8, | ||
"iProduct": 15 | uctypes.UINT8, | ||
"iSerialNumber": 16 | uctypes.UINT8, | ||
"bNumConfigurations": 17 | uctypes.UINT8, | ||
} | ||
|
||
libusb_config_descriptor = { | ||
"bLength": 0 | uctypes.UINT8, | ||
"bDescriptorType": 1 | uctypes.UINT8, | ||
"wTotalLength": 2 | uctypes.UINT16, | ||
"bNumInterfaces": 4 | uctypes.UINT8, | ||
"bConfigurationValue": 5 | uctypes.UINT8, | ||
"iConfiguration": 6 | uctypes.UINT8, | ||
"bmAttributes": 7 | uctypes.UINT8, | ||
"MaxPower": 8 | uctypes.UINT8, | ||
"interface": _align_word(9) | UINTPTR, # array of libusb_interface | ||
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR, | ||
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT, | ||
} | ||
|
||
libusb_interface = { | ||
"altsetting": 0 | UINTPTR, # array of libusb_interface_descriptor | ||
"num_altsetting": UINTPTR_SIZE | uctypes.INT, | ||
} | ||
|
||
libusb_interface_descriptor = { | ||
"bLength": 0 | uctypes.UINT8, | ||
"bDescriptorType": 1 | uctypes.UINT8, | ||
"bInterfaceNumber": 2 | uctypes.UINT8, | ||
"bAlternateSetting": 3 | uctypes.UINT8, | ||
"bNumEndpoints": 4 | uctypes.UINT8, | ||
"bInterfaceClass": 5 | uctypes.UINT8, | ||
"bInterfaceSubClass": 6 | uctypes.UINT8, | ||
"bInterfaceProtocol": 7 | uctypes.UINT8, | ||
"iInterface": 8 | uctypes.UINT8, | ||
"endpoint": _align_word(9) | UINTPTR, | ||
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR, | ||
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT, | ||
} | ||
|
||
libusb = ffi.open("libusb-1.0.so") | ||
libusb_init = libusb.func("i", "libusb_init", "p") | ||
libusb_exit = libusb.func("v", "libusb_exit", "p") | ||
libusb_get_device_list = libusb.func("i", "libusb_get_device_list", "pp") # return is ssize_t | ||
libusb_free_device_list = libusb.func("v", "libusb_free_device_list", "pi") | ||
libusb_get_device_descriptor = libusb.func("i", "libusb_get_device_descriptor", "pp") | ||
libusb_get_config_descriptor = libusb.func("i", "libusb_get_config_descriptor", "pBp") | ||
libusb_free_config_descriptor = libusb.func("v", "libusb_free_config_descriptor", "p") | ||
libusb_open = libusb.func("i", "libusb_open", "pp") | ||
libusb_set_configuration = libusb.func("i", "libusb_set_configuration", "pi") | ||
libusb_claim_interface = libusb.func("i", "libusb_claim_interface", "pi") | ||
libusb_control_transfer = libusb.func("i", "libusb_control_transfer", "pBBHHpHI") | ||
|
||
|
||
def _new(sdesc): | ||
buf = bytearray(uctypes.sizeof(sdesc)) | ||
s = uctypes.struct(uctypes.addressof(buf), sdesc) | ||
return s | ||
|
||
|
||
class Interface: | ||
def __init__(self, descr): | ||
# Public attributes. | ||
self.bInterfaceClass = descr.bInterfaceClass | ||
self.bInterfaceSubClass = descr.bInterfaceSubClass | ||
self.iInterface = descr.iInterface | ||
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length) | ||
|
||
|
||
class Configuration: | ||
def __init__(self, dev, cfg_idx): | ||
cfgs = _new(ptr_descriptor) | ||
if libusb_get_config_descriptor(dev._dev, cfg_idx, cfgs) != 0: | ||
libusb_exit(0) | ||
raise Exception | ||
descr = uctypes.struct(cfgs[0], libusb_config_descriptor) | ||
|
||
# Extract all needed info because descr is going to be free'd at the end. | ||
self._itfs = [] | ||
itf_array = uctypes.struct( | ||
descr.interface, (0 | uctypes.ARRAY, descr.bNumInterfaces, libusb_interface) | ||
) | ||
for i in range(descr.bNumInterfaces): | ||
itf = itf_array[i] | ||
alt_array = uctypes.struct( | ||
itf.altsetting, | ||
(0 | uctypes.ARRAY, itf.num_altsetting, libusb_interface_descriptor), | ||
) | ||
for j in range(itf.num_altsetting): | ||
alt = alt_array[j] | ||
self._itfs.append(Interface(alt)) | ||
|
||
# Public attributes. | ||
self.bNumInterfaces = descr.bNumInterfaces | ||
self.bConfigurationValue = descr.bConfigurationValue | ||
self.bmAttributes = descr.bmAttributes | ||
self.bMaxPower = descr.MaxPower | ||
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length) | ||
|
||
# Free descr memory in the driver. | ||
libusb_free_config_descriptor(cfgs[0]) | ||
|
||
def __iter__(self): | ||
return iter(self._itfs) | ||
|
||
|
||
class Device: | ||
_TIMEOUT_DEFAULT = 1000 | ||
|
||
def __init__(self, dev, descr): | ||
self._dev = dev | ||
self._num_cfg = descr.bNumConfigurations | ||
self._handle = None | ||
self._claim_itf = set() | ||
|
||
# Public attributes. | ||
self.idVendor = descr.idVendor | ||
self.idProduct = descr.idProduct | ||
|
||
def __iter__(self): | ||
for i in range(self._num_cfg): | ||
yield Configuration(self, i) | ||
|
||
def __getitem__(self, i): | ||
return Configuration(self, i) | ||
|
||
def _open(self): | ||
if self._handle is None: | ||
# Open the USB device. | ||
handle = _new(ptr_descriptor) | ||
if libusb_open(self._dev, handle) != 0: | ||
libusb_exit(0) | ||
raise Exception | ||
self._handle = handle[0] | ||
|
||
def _claim_interface(self, i): | ||
if libusb_claim_interface(self._handle, i) != 0: | ||
libusb_exit(0) | ||
raise Exception | ||
|
||
def set_configuration(self): | ||
# Select default configuration. | ||
self._open() | ||
cfg = Configuration(self, 0).bConfigurationValue | ||
ret = libusb_set_configuration(self._handle, cfg) | ||
if ret != 0: | ||
libusb_exit(0) | ||
raise Exception | ||
|
||
def ctrl_transfer( | ||
self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None | ||
): | ||
if data_or_wLength is None: | ||
l = 0 | ||
data = bytes() | ||
elif isinstance(data_or_wLength, int): | ||
l = data_or_wLength | ||
data = bytearray(l) | ||
else: | ||
l = len(data_or_wLength) | ||
data = data_or_wLength | ||
self._open() | ||
if wIndex & 0xFF not in self._claim_itf: | ||
self._claim_interface(wIndex & 0xFF) | ||
self._claim_itf.add(wIndex & 0xFF) | ||
if timeout is None: | ||
timeout = self._TIMEOUT_DEFAULT | ||
ret = libusb_control_transfer( | ||
self._handle, bmRequestType, bRequest, wValue, wIndex, data, l, timeout * 1000 | ||
) | ||
if ret < 0: | ||
libusb_exit(0) | ||
raise Exception | ||
if isinstance(data_or_wLength, int): | ||
return data | ||
else: | ||
return ret | ||
|
||
|
||
def find(*, find_all=False, custom_match=None, idVendor=None, idProduct=None): | ||
if libusb_init(0) < 0: | ||
raise Exception | ||
|
||
devs = _new(ptr_descriptor) | ||
count = libusb_get_device_list(0, devs) | ||
if count < 0: | ||
libusb_exit(0) | ||
raise Exception | ||
|
||
dev_array = uctypes.struct(devs[0], (0 | uctypes.ARRAY, count | UINTPTR)) | ||
descr = _new(libusb_device_descriptor) | ||
devices = None | ||
for i in range(count): | ||
libusb_get_device_descriptor(dev_array[i], descr) | ||
if idVendor and descr.idVendor != idVendor: | ||
continue | ||
if idProduct and descr.idProduct != idProduct: | ||
continue | ||
device = Device(dev_array[i], descr) | ||
if custom_match and not custom_match(device): | ||
continue | ||
if not find_all: | ||
return device | ||
if not devices: | ||
devices = [] | ||
devices.append(device) | ||
return devices |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
# MicroPython USB host library, compatible with PyUSB. | ||
# MIT license; Copyright (c) 2021-2024 Damien P. George | ||
|
||
import usb.control | ||
|
||
|
||
def claim_interface(device, interface): | ||
device._claim_interface(interface) | ||
|
||
|
||
def get_string(device, index): | ||
bs = usb.control.get_descriptor(device, 254, 3, index, 0) | ||
s = "" | ||
for i in range(2, bs[0] & 0xFE, 2): | ||
s += chr(bs[i] | bs[i + 1] << 8) | ||
return s |