Merge ~jocave/checkbox-support:add-lsusb-py-script into checkbox-support: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)
Reviewer Review Type Date Requested Status
Sylvain Pineau (community) Approve
Review via email: mp+369382@code.launchpad.net

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.
Revision history for this message
Sylvain Pineau (sylvain-pineau) wrote :

+1

review: Approve

Preview Diff

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

Subscribers

People subscribed via source and target branches