Merge ~jocave/checkbox-support:add-lsusb-py-script into checkbox-support:master
- Git
- lp:~jocave/checkbox-support
- add-lsusb-py-script
- Merge into master
Proposed by
Jonathan Cave
Status: | Merged |
---|---|
Approved by: | Jonathan Cave |
Approved revision: | 477e4845272bc5df9b084c980281ca9725806a40 |
Merged at revision: | c8e97c2cfed156d2e2171a9f54dac9125ef44630 |
Proposed branch: | ~jocave/checkbox-support:add-lsusb-py-script |
Merge into: | checkbox-support:master |
Diff against target: |
566 lines (+549/-0) 2 files modified
checkbox_support/scripts/lsusb.py (+547/-0) setup.py (+2/-0) |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Sylvain Pineau (community) | Approve | ||
Review via email: mp+369382@code.launchpad.net |
Commit message
Description of the change
Add lsusb.oy script and expose in setup.py. This is used by a variety of jobs and launchers so keeping just one copy here now seems sensible.
To post a comment you must log in.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | diff --git a/checkbox_support/scripts/lsusb.py b/checkbox_support/scripts/lsusb.py |
2 | new file mode 100755 |
3 | index 0000000..c243835 |
4 | --- /dev/null |
5 | +++ b/checkbox_support/scripts/lsusb.py |
6 | @@ -0,0 +1,547 @@ |
7 | +#!/usr/bin/env python3 |
8 | +# lsusb.py |
9 | +# Displays your USB devices in reasonable form. |
10 | +# (c) Kurt Garloff <garloff@suse.de>, 2/2009, GPL v2 or v3. |
11 | +# |
12 | +# Copyright 2016 Canonical Ltd. |
13 | +# Sylvain Pineau <sylvain.pineau@canonical.com> |
14 | +# |
15 | +# Usage: See usage() |
16 | + |
17 | +from functools import total_ordering |
18 | +import getopt |
19 | +import os |
20 | +import re |
21 | +import sys |
22 | + |
23 | +# Global options |
24 | +showint = False |
25 | +showhubint = False |
26 | +noemptyhub = False |
27 | +nohub = False |
28 | +warnsort = False |
29 | +shortmode = False |
30 | + |
31 | +prefix = "/sys/bus/usb/devices/" |
32 | +usbids = "/usr/share/usb.ids" |
33 | + |
34 | +esc = chr(27) |
35 | +norm = esc + "[0;0m" |
36 | +bold = esc + "[0;1m" |
37 | +red = esc + "[0;31m" |
38 | +green = esc + "[0;32m" |
39 | +amber = esc + "[0;33m" |
40 | + |
41 | +cols = ("", "", "", "", "") |
42 | + |
43 | +usbvendors = [] |
44 | +usbproducts = [] |
45 | +usbclasses = [] |
46 | + |
47 | +devlst = ( |
48 | + 'host', # usb-storage |
49 | + 'video4linux/video', # uvcvideo et al. |
50 | + 'sound/card', # snd-usb-audio |
51 | + 'net/', # cdc_ether, ... |
52 | + 'input/input', # usbhid |
53 | + 'usb:hiddev', # usb hid |
54 | + 'bluetooth/hci', # btusb |
55 | + 'ttyUSB', # btusb |
56 | + 'tty/', # cdc_acm |
57 | + 'usb:lp', # usblp |
58 | + 'usb/', # hiddev, usblp |
59 | + ) |
60 | + |
61 | + |
62 | +def readattr(path, name): |
63 | + "Read attribute from sysfs and return as string" |
64 | + f = open(prefix + path + "/" + name) |
65 | + return f.readline().rstrip("\n") |
66 | + |
67 | + |
68 | +def readlink(path, name): |
69 | + "Read symlink and return basename" |
70 | + return os.path.basename(os.readlink(prefix + path + "/" + name)) |
71 | + |
72 | + |
73 | +@total_ordering |
74 | +class UsbClass: |
75 | + "Container for USB Class/Subclass/Protocol" |
76 | + |
77 | + def __init__(self, cl, sc, pr, str=""): |
78 | + self.pclass = cl |
79 | + self.subclass = sc |
80 | + self.proto = pr |
81 | + self.desc = str |
82 | + |
83 | + def __repr__(self): |
84 | + return self.desc |
85 | + |
86 | + def __lt__(self, oth): |
87 | + if self.pclass != oth.pclass: |
88 | + return self.pclass - oth.pclass |
89 | + if self.subclass != oth.subclass: |
90 | + return self.subclass - oth.subclass |
91 | + return self.proto - oth.proto |
92 | + |
93 | + def __eq__(self, oth): |
94 | + if self.pclass != oth.pclass: |
95 | + return False |
96 | + if self.subclass != oth.subclass: |
97 | + return False |
98 | + return self.proto == oth.proto |
99 | + |
100 | + |
101 | +@total_ordering |
102 | +class UsbVendor: |
103 | + "Container for USB Vendors" |
104 | + |
105 | + def __init__(self, vid, vname=""): |
106 | + self.vid = vid |
107 | + self.vname = vname |
108 | + |
109 | + def __repr__(self): |
110 | + return self.vname |
111 | + |
112 | + def __lt__(self, oth): |
113 | + return self.vid - oth.vid |
114 | + |
115 | + def __eq__(self, oth): |
116 | + return self.vid == oth.vid |
117 | + |
118 | + |
119 | +@total_ordering |
120 | +class UsbProduct: |
121 | + "Container for USB VID:PID devices" |
122 | + |
123 | + def __init__(self, vid, pid, pname=""): |
124 | + self.vid = vid |
125 | + self.pid = pid |
126 | + self.pname = pname |
127 | + |
128 | + def __repr__(self): |
129 | + return self.pname |
130 | + |
131 | + def __lt__(self, oth): |
132 | + if self.vid != oth.vid: |
133 | + return self.vid - oth.vid |
134 | + return self.pid - oth.pid |
135 | + |
136 | + def __eq__(self, oth): |
137 | + if self.vid != oth.vid: |
138 | + return False |
139 | + return self.pid == oth.pid |
140 | + |
141 | + |
142 | +def ishexdigit(str): |
143 | + "return True if all digits are valid hex digits" |
144 | + for dg in str: |
145 | + if not dg.isdigit() and dg not in 'abcdef': |
146 | + return False |
147 | + return True |
148 | + |
149 | + |
150 | +def parse_usb_ids(): |
151 | + "Parse /usr/share/usb.ids and fill usbvendors, usbproducts, usbclasses" |
152 | + id = 0 |
153 | + sid = 0 |
154 | + mode = 0 |
155 | + strg = "" |
156 | + cstrg = "" |
157 | + with open(usbids, encoding="utf-8", errors='ignore') as f: |
158 | + for ln in f: |
159 | + if ln[0] == '#': |
160 | + continue |
161 | + ln = ln.rstrip('\n') |
162 | + if len(ln) == 0: |
163 | + continue |
164 | + if ishexdigit(ln[0:4]): |
165 | + mode = 0 |
166 | + id = int(ln[:4], 16) |
167 | + usbvendors.append(UsbVendor(id, ln[6:])) |
168 | + continue |
169 | + if ln[0] == '\t' and ishexdigit(ln[1:3]): |
170 | + sid = int(ln[1:5], 16) |
171 | + # USB devices |
172 | + if mode == 0: |
173 | + usbproducts.append(UsbProduct(id, sid, ln[7:])) |
174 | + continue |
175 | + elif mode == 1: |
176 | + nm = ln[5:] |
177 | + if nm != "Unused": |
178 | + strg = cstrg + ":" + nm |
179 | + else: |
180 | + strg = cstrg + ":" |
181 | + usbclasses.append(UsbClass(id, sid, -1, strg)) |
182 | + continue |
183 | + if ln[0] == 'C': |
184 | + mode = 1 |
185 | + id = int(ln[2:4], 16) |
186 | + cstrg = ln[6:] |
187 | + usbclasses.append(UsbClass(id, -1, -1, cstrg)) |
188 | + continue |
189 | + if ( |
190 | + mode == 1 and ln[0] == '\t' and ln[1] == '\t' and |
191 | + ishexdigit(ln[2:4]) |
192 | + ): |
193 | + prid = int(ln[2:4], 16) |
194 | + usbclasses.append(UsbClass(id, sid, prid, strg + ":" + ln[6:])) |
195 | + continue |
196 | + mode = 2 |
197 | + |
198 | + |
199 | +def find_usb_prod(vid, pid): |
200 | + "Return device name from USB Vendor:Product list" |
201 | + strg = "" |
202 | + dev = UsbVendor(vid, "") |
203 | + try: |
204 | + strg = [v for v in usbvendors if v == dev][0].__repr__() |
205 | + except IndexError: |
206 | + return "" |
207 | + dev = UsbProduct(vid, pid, "") |
208 | + try: |
209 | + strg += " " + [p for p in usbproducts if p == dev][0].__repr__() |
210 | + except IndexError: |
211 | + return strg |
212 | + return strg |
213 | + |
214 | + |
215 | +def find_usb_class(cid, sid, pid): |
216 | + "Return USB protocol from usbclasses list" |
217 | + if cid == 0xff and sid == 0xff and pid == 0xff: |
218 | + return "Vendor Specific" |
219 | + dev = UsbClass(cid, sid, pid, "") |
220 | + try: |
221 | + return [c for c in usbclasses if c == dev][0].__repr__() |
222 | + except IndexError: |
223 | + pass |
224 | + dev = UsbClass(cid, sid, -1, "") |
225 | + try: |
226 | + return [c for c in usbclasses if c == dev][0].__repr__() |
227 | + except IndexError: |
228 | + pass |
229 | + dev = UsbClass(cid, -1, -1, "") |
230 | + try: |
231 | + return [c for c in usbclasses if c == dev][0].__repr__() |
232 | + except IndexError: |
233 | + return "" |
234 | + |
235 | + |
236 | +def find_storage(hostno): |
237 | + "Return SCSI block dev names for host" |
238 | + res = "" |
239 | + for ent in os.listdir("/sys/class/scsi_device/"): |
240 | + (host, bus, tgt, lun) = ent.split(":") |
241 | + if host == hostno: |
242 | + try: |
243 | + path = "/sys/class/scsi_device/%s/device/block" % ent |
244 | + for ent2 in os.listdir(path): |
245 | + res += ent2 + " " |
246 | + except: |
247 | + pass |
248 | + return res |
249 | + |
250 | + |
251 | +def find_dev(driver, usbname): |
252 | + "Return pseudo devname that's driven by driver" |
253 | + res = "" |
254 | + for nm in devlst: |
255 | + dir = prefix + usbname |
256 | + prep = "" |
257 | + idx = nm.find('/') |
258 | + if idx != -1: |
259 | + prep = nm[:idx+1] |
260 | + dir += "/" + nm[:idx] |
261 | + nm = nm[idx+1:] |
262 | + ln = len(nm) |
263 | + try: |
264 | + for ent in os.listdir(dir): |
265 | + if ent[:ln] == nm: |
266 | + res += prep+ent+" " |
267 | + if nm == "host": |
268 | + res += "(" + find_storage(ent[ln:])[:-1] + ")" |
269 | + except: |
270 | + pass |
271 | + return res |
272 | + |
273 | + |
274 | +class UsbInterface: |
275 | + "Container for USB interface info" |
276 | + |
277 | + def __init__(self, parent=None, level=1): |
278 | + self.parent = parent |
279 | + self.level = level |
280 | + self.fname = "" |
281 | + self.iclass = 0 |
282 | + self.isclass = 0 |
283 | + self.iproto = 0 |
284 | + self.noep = 0 |
285 | + self.driver = "" |
286 | + self.devname = "" |
287 | + self.protoname = "" |
288 | + |
289 | + def read(self, fname): |
290 | + fullpath = "" |
291 | + if self.parent: |
292 | + fullpath += self.parent.fname + "/" |
293 | + fullpath += fname |
294 | + self.fname = fname |
295 | + self.iclass = int(readattr(fullpath, "bInterfaceClass"), 16) |
296 | + self.isclass = int(readattr(fullpath, "bInterfaceSubClass"), 16) |
297 | + self.iproto = int(readattr(fullpath, "bInterfaceProtocol"), 16) |
298 | + self.noep = int(readattr(fullpath, "bNumEndpoints")) |
299 | + try: |
300 | + self.driver = readlink(fname, "driver") |
301 | + self.devname = find_dev(self.driver, fname) |
302 | + except: |
303 | + pass |
304 | + self.protoname = find_usb_class(self.iclass, self.isclass, self.iproto) |
305 | + |
306 | + def __str__(self): |
307 | + return "%-16s(IF) %02x:%02x:%02x %iEPs (%s) %s%s %s%s%s\n" % \ |
308 | + (" " * self.level+self.fname, self.iclass, |
309 | + self.isclass, self.iproto, self.noep, |
310 | + self.protoname, |
311 | + cols[3], self.driver, |
312 | + cols[4], self.devname, cols[0]) |
313 | + |
314 | + |
315 | +class UsbDevice: |
316 | + "Container for USB device info" |
317 | + |
318 | + def __init__(self, parent=None, level=0): |
319 | + self.parent = parent |
320 | + self.level = level |
321 | + self.display_name = "" |
322 | + self.fname = "" |
323 | + self.busnum = 0 |
324 | + self.devnum = 0 |
325 | + self.iclass = 0 |
326 | + self.isclass = 0 |
327 | + self.iproto = 0 |
328 | + self.vid = 0 |
329 | + self.pid = 0 |
330 | + self.name = "" |
331 | + self.usbver = "" |
332 | + self.speed = "" |
333 | + self.maxpower = "" |
334 | + self.noports = 0 |
335 | + self.nointerfaces = 0 |
336 | + self.driver = "" |
337 | + self.devname = "" |
338 | + self.interfaces = [] |
339 | + self.children = [] |
340 | + |
341 | + def read(self, fname): |
342 | + self.fname = fname |
343 | + self.iclass = int(readattr(fname, "bDeviceClass"), 16) |
344 | + self.isclass = int(readattr(fname, "bDeviceSubClass"), 16) |
345 | + self.iproto = int(readattr(fname, "bDeviceProtocol"), 16) |
346 | + self.vid = int(readattr(fname, "idVendor"), 16) |
347 | + self.pid = int(readattr(fname, "idProduct"), 16) |
348 | + self.busnum = int(readattr(fname, "busnum")) |
349 | + self.devnum = int(readattr(fname, "devnum")) |
350 | + self.usbver = readattr(fname, "version") |
351 | + try: |
352 | + self.name = readattr(fname, "manufacturer") + " " \ |
353 | + + readattr(fname, "product") |
354 | + if self.name[:5] == "Linux": |
355 | + rx = re.compile(r"Linux [^ ]* .hci[-_]hcd") |
356 | + mch = rx.match(self.name) |
357 | + if mch: |
358 | + self.name = "Linux Foundation %.2f root hub" % float( |
359 | + self.usbver) |
360 | + except: |
361 | + pass |
362 | + if not self.name: |
363 | + self.name = find_usb_prod(self.vid, self.pid) |
364 | + # Some USB Card readers have a better name than Generic ... |
365 | + if self.name[:7] == "Generic": |
366 | + oldnm = self.name |
367 | + self.name = find_usb_prod(self.vid, self.pid) |
368 | + if not self.name: |
369 | + self.name = oldnm |
370 | + self.speed = readattr(fname, "speed") |
371 | + self.maxpower = readattr(fname, "bMaxPower") |
372 | + self.noports = int(readattr(fname, "maxchild")) |
373 | + try: |
374 | + self.nointerfaces = int(readattr(fname, "bNumInterfaces")) |
375 | + except: |
376 | + self.nointerfaces = 0 |
377 | + try: |
378 | + self.driver = readlink(fname, "driver") |
379 | + self.devname = find_dev(self.driver, fname) |
380 | + except: |
381 | + pass |
382 | + |
383 | + def readchildren(self): |
384 | + if self.fname[0:3] == "usb": |
385 | + fname = self.fname[3:] |
386 | + else: |
387 | + fname = self.fname |
388 | + for dirent in os.listdir(prefix + self.fname): |
389 | + if not dirent[0:1].isdigit(): |
390 | + continue |
391 | + if os.access(prefix + dirent + "/bInterfaceClass", os.R_OK): |
392 | + iface = UsbInterface(self, self.level+1) |
393 | + iface.read(dirent) |
394 | + self.interfaces.append(iface) |
395 | + else: |
396 | + usbdev = UsbDevice(self, self.level+1) |
397 | + usbdev.read(dirent) |
398 | + usbdev.readchildren() |
399 | + self.children.append(usbdev) |
400 | + |
401 | + def __str__(self): |
402 | + if self.iclass == 9: |
403 | + col = cols[2] |
404 | + if noemptyhub and len(self.children) == 0: |
405 | + return "" |
406 | + if nohub: |
407 | + str = "" |
408 | + else: |
409 | + col = cols[1] |
410 | + if not nohub or self.iclass != 9: |
411 | + if shortmode: |
412 | + str = "ID %04x:%04x %s" % (self.vid, self.pid, self.name) |
413 | + else: |
414 | + str = "Bus %03d Device %03d: ID %04x:%04x %s" % \ |
415 | + (self.busnum, self.devnum, self.vid, self.pid, self.name) |
416 | + str += "\n" |
417 | + if showint: |
418 | + for iface in self.interfaces: |
419 | + str += iface.__str__() |
420 | + for child in self.children: |
421 | + str += child.__str__() |
422 | + return str |
423 | + |
424 | + |
425 | +def deepcopy(lst): |
426 | + "Returns a deep copy from the list lst" |
427 | + copy = [] |
428 | + for item in lst: |
429 | + copy.append(item) |
430 | + return copy |
431 | + |
432 | + |
433 | +def display_diff(lst1, lst2, fmtstr, args): |
434 | + "Compare lists (same length!) and display differences" |
435 | + for idx in range(0, len(lst1)): |
436 | + if lst1[idx] != lst2[idx]: |
437 | + print("Warning: " + fmtstr % args(lst2[idx])) |
438 | + |
439 | + |
440 | +def fix_usbvend(): |
441 | + "Sort USB vendor list and (optionally) display diffs" |
442 | + if warnsort: |
443 | + oldusbvend = deepcopy(usbvendors) |
444 | + usbvendors.sort() |
445 | + if warnsort: |
446 | + display_diff(usbvendors, oldusbvend, "Unsorted Vendor ID %04x", |
447 | + lambda x: (x.vid,)) |
448 | + |
449 | + |
450 | +def fix_usbprod(): |
451 | + "Sort USB products list" |
452 | + if warnsort: |
453 | + oldusbprod = deepcopy(usbproducts) |
454 | + usbproducts.sort() |
455 | + if warnsort: |
456 | + display_diff(usbproducts, oldusbprod, |
457 | + "Unsorted Vendor:Product ID %04x:%04x", |
458 | + lambda x: (x.vid, x.pid)) |
459 | + |
460 | + |
461 | +def fix_usbclass(): |
462 | + "Sort USB class list" |
463 | + if warnsort: |
464 | + oldusbcls = deepcopy(usbclasses) |
465 | + usbclasses.sort() |
466 | + if warnsort: |
467 | + display_diff(usbclasses, oldusbcls, |
468 | + "Unsorted USB class %02x:%02x:%02x", |
469 | + lambda x: (x.pclass, x.subclass, x.proto)) |
470 | + |
471 | + |
472 | +def usage(): |
473 | + "Displays usage information" |
474 | + print("Usage: lsusb.py [options]") |
475 | + print("Options:") |
476 | + print(" -h display this help") |
477 | + print(" -i display interface information") |
478 | + print(" -I display interface information, even for hubs") |
479 | + print(" -u suppress empty hubs") |
480 | + print(" -U suppress all hubs") |
481 | + print(" -c use colors") |
482 | + print(" -s short mode") |
483 | + print(" -w display warning if usb.ids is not sorted correctly") |
484 | + print(" -f FILE override filename for /usr/share/usb.ids") |
485 | + return 2 |
486 | + |
487 | + |
488 | +def read_usb(): |
489 | + "Read toplevel USB entries and print" |
490 | + for dirent in os.listdir(prefix): |
491 | + if not dirent[0:3] == "usb": |
492 | + continue |
493 | + usbdev = UsbDevice(None, 0) |
494 | + usbdev.read(dirent) |
495 | + usbdev.readchildren() |
496 | + print(usbdev.__str__(), end="") |
497 | + |
498 | + |
499 | +def main(): |
500 | + "main entry point" |
501 | + global showint, showhubint, noemptyhub, nohub, warnsort, cols, usbids, \ |
502 | + shortmode |
503 | + try: |
504 | + (optlist, args) = getopt.gnu_getopt(sys.argv[1:], "hiIuUwcsf:", ("help",)) |
505 | + except getopt.GetoptError as exc: |
506 | + print("Error:", exc) |
507 | + sys.exit(usage()) |
508 | + for opt in optlist: |
509 | + if opt[0] == "-h" or opt[0] == "--help": |
510 | + usage() |
511 | + sys.exit(0) |
512 | + if opt[0] == "-i": |
513 | + showint = True |
514 | + continue |
515 | + if opt[0] == "-I": |
516 | + showint = True |
517 | + showhubint = True |
518 | + continue |
519 | + if opt[0] == "-u": |
520 | + noemptyhub = True |
521 | + continue |
522 | + if opt[0] == "-U": |
523 | + noemptyhub = True |
524 | + nohub = True |
525 | + continue |
526 | + if opt[0] == "-c": |
527 | + cols = (norm, bold, red, green, amber) |
528 | + continue |
529 | + if opt[0] == "-w": |
530 | + warnsort = True |
531 | + continue |
532 | + if opt[0] == "-f": |
533 | + usbids = opt[1] |
534 | + continue |
535 | + if opt[0] == "-s": |
536 | + shortmode = True |
537 | + continue |
538 | + if len(args) > 0: |
539 | + print("Error: excess args %s ..." % args[0]) |
540 | + sys.exit(usage()) |
541 | + try: |
542 | + parse_usb_ids() |
543 | + fix_usbvend() |
544 | + fix_usbprod() |
545 | + fix_usbclass() |
546 | + except: |
547 | + print(" WARNING: Failure to read usb.ids", file=sys.stderr) |
548 | + print(sys.exc_info(), file=sys.stderr) |
549 | + read_usb() |
550 | + |
551 | +# Entry point |
552 | +if __name__ == "__main__": |
553 | + main() |
554 | diff --git a/setup.py b/setup.py |
555 | index 4f4c992..7f115f8 100755 |
556 | --- a/setup.py |
557 | +++ b/setup.py |
558 | @@ -88,6 +88,8 @@ setup( |
559 | "checkbox_support.scripts.snap_connect:main"), |
560 | ("checkbox-support-eddystone_scanner=" |
561 | "checkbox_support.scripts.eddystone_scanner:main"), |
562 | + ("checkbox-support-lsusb=" |
563 | + "checkbox_support.scripts.lsusb:main"), |
564 | ], |
565 | }, |
566 | ) |
+1