Skip to content

Commit 50ed36f

Browse files
committed
pyusb: Add MicroPython implementation of PyUSB library.
Signed-off-by: Damien George <damien@micropython.org>
1 parent 2c30a4e commit 50ed36f

File tree

6 files changed

+288
-0
lines changed

6 files changed

+288
-0
lines changed

unix-ffi/pyusb/examples/lsusb.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Simple example to list attached USB devices.
2+
3+
import usb.core
4+
5+
for device in usb.core.find(find_all=True):
6+
print("ID {:04x}:{:04x}".format(device.idVendor, device.idProduct))
7+
for cfg in device:
8+
print(
9+
" config numitf={} value={} attr={} power={}".format(
10+
cfg.bNumInterfaces, cfg.bConfigurationValue, cfg.bmAttributes, cfg.bMaxPower
11+
)
12+
)
13+
for itf in cfg:
14+
print(
15+
" interface class={} subclass={}".format(
16+
itf.bInterfaceClass, itf.bInterfaceSubClass
17+
)
18+
)

unix-ffi/pyusb/manifest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
metadata(version="0.1.0", pypi="pyusb")
2+
3+
package("usb")

unix-ffi/pyusb/usb/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George

unix-ffi/pyusb/usb/control.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George
3+
4+
5+
def get_descriptor(dev, desc_size, desc_type, desc_index, wIndex=0):
6+
wValue = desc_index | desc_type << 8
7+
d = dev.ctrl_transfer(0x80, 0x06, wValue, wIndex, desc_size)
8+
if len(d) < 2:
9+
raise Exception("invalid descriptor")
10+
return d

unix-ffi/pyusb/usb/core.py

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George
3+
4+
import sys
5+
import ffi
6+
import uctypes
7+
8+
if sys.maxsize >> 32:
9+
UINTPTR_SIZE = 8
10+
UINTPTR = uctypes.UINT64
11+
else:
12+
UINTPTR_SIZE = 4
13+
UINTPTR = uctypes.UINT32
14+
15+
16+
def _align_word(x):
17+
return (x + UINTPTR_SIZE - 1) & ~(UINTPTR_SIZE - 1)
18+
19+
20+
ptr_descriptor = (0 | uctypes.ARRAY, 1 | UINTPTR)
21+
22+
libusb_device_descriptor = {
23+
"bLength": 0 | uctypes.UINT8,
24+
"bDescriptorType": 1 | uctypes.UINT8,
25+
"bcdUSB": 2 | uctypes.UINT16,
26+
"bDeviceClass": 4 | uctypes.UINT8,
27+
"bDeviceSubClass": 5 | uctypes.UINT8,
28+
"bDeviceProtocol": 6 | uctypes.UINT8,
29+
"bMaxPacketSize0": 7 | uctypes.UINT8,
30+
"idVendor": 8 | uctypes.UINT16,
31+
"idProduct": 10 | uctypes.UINT16,
32+
"bcdDevice": 12 | uctypes.UINT16,
33+
"iManufacturer": 14 | uctypes.UINT8,
34+
"iProduct": 15 | uctypes.UINT8,
35+
"iSerialNumber": 16 | uctypes.UINT8,
36+
"bNumConfigurations": 17 | uctypes.UINT8,
37+
}
38+
39+
libusb_config_descriptor = {
40+
"bLength": 0 | uctypes.UINT8,
41+
"bDescriptorType": 1 | uctypes.UINT8,
42+
"wTotalLength": 2 | uctypes.UINT16,
43+
"bNumInterfaces": 4 | uctypes.UINT8,
44+
"bConfigurationValue": 5 | uctypes.UINT8,
45+
"iConfiguration": 6 | uctypes.UINT8,
46+
"bmAttributes": 7 | uctypes.UINT8,
47+
"MaxPower": 8 | uctypes.UINT8,
48+
"interface": _align_word(9) | UINTPTR, # array of libusb_interface
49+
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR,
50+
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT,
51+
}
52+
53+
libusb_interface = {
54+
"altsetting": 0 | UINTPTR, # array of libusb_interface_descriptor
55+
"num_altsetting": UINTPTR_SIZE | uctypes.INT,
56+
}
57+
58+
libusb_interface_descriptor = {
59+
"bLength": 0 | uctypes.UINT8,
60+
"bDescriptorType": 1 | uctypes.UINT8,
61+
"bInterfaceNumber": 2 | uctypes.UINT8,
62+
"bAlternateSetting": 3 | uctypes.UINT8,
63+
"bNumEndpoints": 4 | uctypes.UINT8,
64+
"bInterfaceClass": 5 | uctypes.UINT8,
65+
"bInterfaceSubClass": 6 | uctypes.UINT8,
66+
"bInterfaceProtocol": 7 | uctypes.UINT8,
67+
"iInterface": 8 | uctypes.UINT8,
68+
"endpoint": _align_word(9) | UINTPTR,
69+
"extra": (_align_word(9) + UINTPTR_SIZE) | UINTPTR,
70+
"extra_length": (_align_word(9) + 2 * UINTPTR_SIZE) | uctypes.INT,
71+
}
72+
73+
libusb = ffi.open("libusb-1.0.so")
74+
libusb_init = libusb.func("i", "libusb_init", "p")
75+
libusb_exit = libusb.func("v", "libusb_exit", "p")
76+
libusb_get_device_list = libusb.func("i", "libusb_get_device_list", "pp") # return is ssize_t
77+
libusb_free_device_list = libusb.func("v", "libusb_free_device_list", "pi")
78+
libusb_get_device_descriptor = libusb.func("i", "libusb_get_device_descriptor", "pp")
79+
libusb_get_config_descriptor = libusb.func("i", "libusb_get_config_descriptor", "pBp")
80+
libusb_free_config_descriptor = libusb.func("v", "libusb_free_config_descriptor", "p")
81+
libusb_open = libusb.func("i", "libusb_open", "pp")
82+
libusb_set_configuration = libusb.func("i", "libusb_set_configuration", "pi")
83+
libusb_claim_interface = libusb.func("i", "libusb_claim_interface", "pi")
84+
libusb_control_transfer = libusb.func("i", "libusb_control_transfer", "pBBHHpHI")
85+
86+
87+
def _new(sdesc):
88+
buf = bytearray(uctypes.sizeof(sdesc))
89+
s = uctypes.struct(uctypes.addressof(buf), sdesc)
90+
return s
91+
92+
93+
class Interface:
94+
def __init__(self, descr):
95+
# Public attributes.
96+
self.bInterfaceClass = descr.bInterfaceClass
97+
self.bInterfaceSubClass = descr.bInterfaceSubClass
98+
self.iInterface = descr.iInterface
99+
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length)
100+
101+
102+
class Configuration:
103+
def __init__(self, dev, cfg_idx):
104+
cfgs = _new(ptr_descriptor)
105+
if libusb_get_config_descriptor(dev._dev, cfg_idx, cfgs) != 0:
106+
libusb_exit(0)
107+
raise Exception
108+
descr = uctypes.struct(cfgs[0], libusb_config_descriptor)
109+
110+
# Extract all needed info because descr is going to be free'd at the end.
111+
self._itfs = []
112+
itf_array = uctypes.struct(
113+
descr.interface, (0 | uctypes.ARRAY, descr.bNumInterfaces, libusb_interface)
114+
)
115+
for i in range(descr.bNumInterfaces):
116+
itf = itf_array[i]
117+
alt_array = uctypes.struct(
118+
itf.altsetting,
119+
(0 | uctypes.ARRAY, itf.num_altsetting, libusb_interface_descriptor),
120+
)
121+
for j in range(itf.num_altsetting):
122+
alt = alt_array[j]
123+
self._itfs.append(Interface(alt))
124+
125+
# Public attributes.
126+
self.bNumInterfaces = descr.bNumInterfaces
127+
self.bConfigurationValue = descr.bConfigurationValue
128+
self.bmAttributes = descr.bmAttributes
129+
self.bMaxPower = descr.MaxPower
130+
self.extra_descriptors = uctypes.bytes_at(descr.extra, descr.extra_length)
131+
132+
# Free descr memory in the driver.
133+
libusb_free_config_descriptor(cfgs[0])
134+
135+
def __iter__(self):
136+
return iter(self._itfs)
137+
138+
139+
class Device:
140+
_TIMEOUT_DEFAULT = 1000
141+
142+
def __init__(self, dev, descr):
143+
self._dev = dev
144+
self._num_cfg = descr.bNumConfigurations
145+
self._handle = None
146+
self._claim_itf = set()
147+
148+
# Public attributes.
149+
self.idVendor = descr.idVendor
150+
self.idProduct = descr.idProduct
151+
152+
def __iter__(self):
153+
for i in range(self._num_cfg):
154+
yield Configuration(self, i)
155+
156+
def __getitem__(self, i):
157+
return Configuration(self, i)
158+
159+
def _open(self):
160+
if self._handle is None:
161+
# Open the USB device.
162+
handle = _new(ptr_descriptor)
163+
if libusb_open(self._dev, handle) != 0:
164+
libusb_exit(0)
165+
raise Exception
166+
self._handle = handle[0]
167+
168+
def _claim_interface(self, i):
169+
if libusb_claim_interface(self._handle, i) != 0:
170+
libusb_exit(0)
171+
raise Exception
172+
173+
def set_configuration(self):
174+
# Select default configuration.
175+
self._open()
176+
cfg = Configuration(self, 0).bConfigurationValue
177+
ret = libusb_set_configuration(self._handle, cfg)
178+
if ret != 0:
179+
libusb_exit(0)
180+
raise Exception
181+
182+
def ctrl_transfer(
183+
self, bmRequestType, bRequest, wValue=0, wIndex=0, data_or_wLength=None, timeout=None
184+
):
185+
if data_or_wLength is None:
186+
l = 0
187+
data = bytes()
188+
elif isinstance(data_or_wLength, int):
189+
l = data_or_wLength
190+
data = bytearray(l)
191+
else:
192+
l = len(data_or_wLength)
193+
data = data_or_wLength
194+
self._open()
195+
if wIndex & 0xFF not in self._claim_itf:
196+
self._claim_interface(wIndex & 0xFF)
197+
self._claim_itf.add(wIndex & 0xFF)
198+
if timeout is None:
199+
timeout = self._TIMEOUT_DEFAULT
200+
ret = libusb_control_transfer(
201+
self._handle, bmRequestType, bRequest, wValue, wIndex, data, l, timeout * 1000
202+
)
203+
if ret < 0:
204+
libusb_exit(0)
205+
raise Exception
206+
if isinstance(data_or_wLength, int):
207+
return data
208+
else:
209+
return ret
210+
211+
212+
def find(*, find_all=False, custom_match=None, idVendor=None, idProduct=None):
213+
if libusb_init(0) < 0:
214+
raise Exception
215+
216+
devs = _new(ptr_descriptor)
217+
count = libusb_get_device_list(0, devs)
218+
if count < 0:
219+
libusb_exit(0)
220+
raise Exception
221+
222+
dev_array = uctypes.struct(devs[0], (0 | uctypes.ARRAY, count | UINTPTR))
223+
descr = _new(libusb_device_descriptor)
224+
devices = None
225+
for i in range(count):
226+
libusb_get_device_descriptor(dev_array[i], descr)
227+
if idVendor and descr.idVendor != idVendor:
228+
continue
229+
if idProduct and descr.idProduct != idProduct:
230+
continue
231+
device = Device(dev_array[i], descr)
232+
if custom_match and not custom_match(device):
233+
continue
234+
if not find_all:
235+
return device
236+
if not devices:
237+
devices = []
238+
devices.append(device)
239+
return devices

unix-ffi/pyusb/usb/util.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# MicroPython USB host library, compatible with PyUSB.
2+
# MIT license; Copyright (c) 2021-2024 Damien P. George
3+
4+
import usb.control
5+
6+
7+
def claim_interface(device, interface):
8+
device._claim_interface(interface)
9+
10+
11+
def get_string(device, index):
12+
bs = usb.control.get_descriptor(device, 254, 3, index, 0)
13+
s = ""
14+
for i in range(2, bs[0] & 0xFE, 2):
15+
s += chr(bs[i] | bs[i + 1] << 8)
16+
return s

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy