Skip to content

pyusb: Add MicroPython implementation of PyUSB library. #864

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
May 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions unix-ffi/pyusb/examples/lsusb.py
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
)
)
3 changes: 3 additions & 0 deletions unix-ffi/pyusb/manifest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
metadata(version="0.1.0", pypi="pyusb")

package("usb")
2 changes: 2 additions & 0 deletions unix-ffi/pyusb/usb/__init__.py
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
10 changes: 10 additions & 0 deletions unix-ffi/pyusb/usb/control.py
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
239 changes: 239 additions & 0 deletions unix-ffi/pyusb/usb/core.py
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
16 changes: 16 additions & 0 deletions unix-ffi/pyusb/usb/util.py
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
Loading