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