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