From 909c1828e429d0bb21cc62800e0c183efc0459cc Mon Sep 17 00:00:00 2001 From: cliechti Date: Fri, 19 Aug 2011 01:20:44 +0000 Subject: extract detailed hardware info on Linux git-svn-id: http://svn.code.sf.net/p/pyserial/code/trunk/pyserial@414 f19166aa-fa4f-0410-85c2-fa1106f25c8a --- serial/tools/list_ports_posix.py | 108 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 1 deletion(-) diff --git a/serial/tools/list_ports_posix.py b/serial/tools/list_ports_posix.py index 2fb655b..ebfa4ef 100644 --- a/serial/tools/list_ports_posix.py +++ b/serial/tools/list_ports_posix.py @@ -1,5 +1,24 @@ import glob import sys +import os +import re + +try: + import subprocess +except ImportError: + def popen(argv): + try: + si, so = os.popen2(' '.join(argv)) + return so.read().strip() + except: + raise IOError('lsusb failed') +else: + def popen(argv): + try: + return subprocess.check_output(argv).strip() + except: + raise IOError('lsusb failed') + # The comports function is expected to return an iterable that yields tuples of # 3 strings: port name, human readable description and a hardware ID. @@ -10,10 +29,97 @@ import sys # try to detect the OS so that a device can be selected... plat = sys.platform.lower() +def read_line(filename): + """help function to read a single line from a file. returns none""" + try: + f = open(filename) + line = f.readline().strip() + f.close() + return line + except IOError: + return None + +def re_group(regexp, text): + """search for regexp in text, return 1st group on match""" + m = re.search(regexp, text) + if m: return m.group(1) + + if plat[:5] == 'linux': # Linux (confirmed) + # try to extract descriptions from sysfs. this was done by experimenting, + # no guarantee that it works for all devices or in the future... + + def usb_sysfs_hw_string(sysfs_path): + """given a path to a usb device in sysfs, return a string describing it""" + bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-') + snr = read_line(sysfs_path+'/serial') + if snr: + snr_txt = ' SNR=%s' % (snr,) + else: + snr_txt = '' + return 'USB VID:PID=%s:%s%s' % ( + read_line(sysfs_path+'/idVendor'), + read_line(sysfs_path+'/idProduct'), + snr_txt + ) + + def usb_lsusb_string(sysfs_path): + bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-') + try: + desc = popen(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)]) + # descriptions from device + iManufacturer = re_group('iManufacturer\s+\w+ (.+)', desc) + iProduct = re_group('iProduct\s+\w+ (.+)', desc) + iSerial = re_group('iSerial\s+\w+ (.+)', desc) or '' + # descriptions from kernel + idVendor = re_group('idVendor\s+0x\w+ (.+)', desc) + idProduct = re_group('idProduct\s+0x\w+ (.+)', desc) + # create descriptions. prefer text from device, fall back to the others + return '%s %s %s' % (iManufacturer or idVendor, iProduct or idProduct, iSerial) + except IOError: + return base + + def describe(device): + """\ + Get a human readable description. + For USB-Serial devices try to run lsusb to get a human readable description. + For USB-CDC devices read the description from sysfs. + """ + base = os.path.basename(device) + # USB-Serial devices + sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base) + if os.path.exists(sys_dev_path): + sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path))) + return usb_lsusb_string(sys_usb) + # USB-CDC devices + sys_dev_path = '/sys/class/tty/%s/device/interface' % (base,) + if os.path.exists(sys_dev_path): + return read_line(sys_dev_path) + return base + + def hwinfo(device): + """Try to get a HW identification using sysfs""" + base = os.path.basename(device) + if os.path.exists('/sys/class/tty/%s/device' % (base,)): + # PCI based devices + sys_id_path = '/sys/class/tty/%s/device/id' % (base,) + if os.path.exists(sys_id_path): + return read_line(sys_id_path) + # USB-Serial devices + sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base) + if os.path.exists(sys_dev_path): + sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path))) + return usb_sysfs_hw_string(sys_usb) + # USB-CDC devices + if base.startswith('ttyACM'): + sys_dev_path = '/sys/class/tty/%s/device' % (base,) + if os.path.exists(sys_dev_path): + return usb_sysfs_hw_string(sys_dev_path + '/..') + return 'n/a' # XXX directly remove these from the list? + def comports(): devices = glob.glob('/dev/ttyS*') + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') - return [(d, d, d) for d in devices] + return [(d, describe(d), hwinfo(d)) for d in devices] elif plat == 'cygwin': # cygwin/win32 def comports(): -- cgit v1.2.1