• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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