1#!/usr/bin/env python 2 3# portable serial port access with python 4# 5# This is a module that gathers a list of serial ports including details on 6# GNU/Linux systems 7# 8# (C) 2011-2013 Chris Liechti <cliechti@gmx.net> 9# this is distributed under a free software license, see license.txt 10 11import glob 12import sys 13import os 14import re 15 16try: 17 import subprocess 18except ImportError: 19 def popen(argv): 20 try: 21 si, so = os.popen4(' '.join(argv)) 22 return so.read().strip() 23 except: 24 raise IOError('lsusb failed') 25else: 26 def popen(argv): 27 try: 28 return subprocess.check_output(argv, stderr=subprocess.STDOUT).strip() 29 except: 30 raise IOError('lsusb failed') 31 32 33# The comports function is expected to return an iterable that yields tuples of 34# 3 strings: port name, human readable description and a hardware ID. 35# 36# as currently no method is known to get the second two strings easily, they 37# are currently just identical to the port name. 38 39# try to detect the OS so that a device can be selected... 40plat = sys.platform.lower() 41 42def read_line(filename): 43 """help function to read a single line from a file. returns none""" 44 try: 45 f = open(filename) 46 line = f.readline().strip() 47 f.close() 48 return line 49 except IOError: 50 return None 51 52def re_group(regexp, text): 53 """search for regexp in text, return 1st group on match""" 54 if sys.version < '3': 55 m = re.search(regexp, text) 56 else: 57 # text is bytes-like 58 m = re.search(regexp, text.decode('ascii', 'replace')) 59 if m: return m.group(1) 60 61 62# try to extract descriptions from sysfs. this was done by experimenting, 63# no guarantee that it works for all devices or in the future... 64 65def usb_sysfs_hw_string(sysfs_path): 66 """given a path to a usb device in sysfs, return a string describing it""" 67 bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-') 68 snr = read_line(sysfs_path+'/serial') 69 if snr: 70 snr_txt = ' SNR=%s' % (snr,) 71 else: 72 snr_txt = '' 73 return 'USB VID:PID=%s:%s%s' % ( 74 read_line(sysfs_path+'/idVendor'), 75 read_line(sysfs_path+'/idProduct'), 76 snr_txt 77 ) 78 79def usb_lsusb_string(sysfs_path): 80 base = os.path.basename(os.path.realpath(sysfs_path)) 81 bus = base.split('-')[0] 82 try: 83 dev = int(read_line(os.path.join(sysfs_path, 'devnum'))) 84 desc = popen(['lsusb', '-v', '-s', '%s:%s' % (bus, dev)]) 85 # descriptions from device 86 iManufacturer = re_group('iManufacturer\s+\w+ (.+)', desc) 87 iProduct = re_group('iProduct\s+\w+ (.+)', desc) 88 iSerial = re_group('iSerial\s+\w+ (.+)', desc) or '' 89 # descriptions from kernel 90 idVendor = re_group('idVendor\s+0x\w+ (.+)', desc) 91 idProduct = re_group('idProduct\s+0x\w+ (.+)', desc) 92 # create descriptions. prefer text from device, fall back to the others 93 return '%s %s %s' % (iManufacturer or idVendor, iProduct or idProduct, iSerial) 94 except IOError: 95 return base 96 97def describe(device): 98 """\ 99 Get a human readable description. 100 For USB-Serial devices try to run lsusb to get a human readable description. 101 For USB-CDC devices read the description from sysfs. 102 """ 103 base = os.path.basename(device) 104 # USB-Serial devices 105 sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base) 106 if os.path.exists(sys_dev_path): 107 sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path))) 108 return usb_lsusb_string(sys_usb) 109 # USB-CDC devices 110 sys_dev_path = '/sys/class/tty/%s/device/interface' % (base,) 111 if os.path.exists(sys_dev_path): 112 return read_line(sys_dev_path) 113 114 # USB Product Information 115 sys_dev_path = '/sys/class/tty/%s/device' % (base,) 116 if os.path.exists(sys_dev_path): 117 product_name_file = os.path.dirname(os.path.realpath(sys_dev_path)) + "/product" 118 if os.path.exists(product_name_file): 119 return read_line(product_name_file) 120 121 return base 122 123def hwinfo(device): 124 """Try to get a HW identification using sysfs""" 125 base = os.path.basename(device) 126 if os.path.exists('/sys/class/tty/%s/device' % (base,)): 127 # PCI based devices 128 sys_id_path = '/sys/class/tty/%s/device/id' % (base,) 129 if os.path.exists(sys_id_path): 130 return read_line(sys_id_path) 131 # USB-Serial devices 132 sys_dev_path = '/sys/class/tty/%s/device/driver/%s' % (base, base) 133 if os.path.exists(sys_dev_path): 134 sys_usb = os.path.dirname(os.path.dirname(os.path.realpath(sys_dev_path))) 135 return usb_sysfs_hw_string(sys_usb) 136 # USB-CDC devices 137 if base.startswith('ttyACM'): 138 sys_dev_path = '/sys/class/tty/%s/device' % (base,) 139 if os.path.exists(sys_dev_path): 140 return usb_sysfs_hw_string(sys_dev_path + '/..') 141 return 'n/a' # XXX directly remove these from the list? 142 143def comports(): 144 devices = glob.glob('/dev/ttyS*') + glob.glob('/dev/ttyUSB*') + glob.glob('/dev/ttyACM*') 145 return [(d, describe(d), hwinfo(d)) for d in devices] 146 147# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 148# test 149if __name__ == '__main__': 150 for port, desc, hwid in sorted(comports()): 151 print "%s: %s [%s]" % (port, desc, hwid) 152