summaryrefslogtreecommitdiff
path: root/serial/tools/list_ports_posix.py
blob: 3e2da724520c0d69997449091c7c8756e7039023 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
import glob
import sys
import os
import re

try:
    import subprocess
except ImportError:
    def popen(argv):
        try:
            si, so =  os.popen4(' '.join(argv))
            return so.read().strip()
        except:
            raise IOError('lsusb failed')
else:
    def popen(argv):
        try:
            return subprocess.check_output(argv, stderr=subprocess.STDOUT).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.
#
# as currently no method is known to get the second two strings easily, they
# are currently just identical to the port name.

# 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"""
    if sys.version < '3':
        m = re.search(regexp, text)
    else:
        # text is bytes-like
        m = re.search(regexp, text.decode('ascii', 'replace'))
    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):
        base = os.path.basename(os.path.realpath(sysfs_path))
        bus = base.split('-')[0]
        try:
            dev = int(open(os.path.join(sysfs_path, 'devnum')).readline().strip())
            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, describe(d), hwinfo(d)) for d in devices]

elif plat == 'cygwin':       # cygwin/win32
    def comports():
        devices = glob.glob('/dev/com*')
        return [(d, d, d) for d in devices]

elif plat[:7] == 'openbsd':    # OpenBSD
    def comports():
        devices = glob.glob('/dev/cua*')
        return [(d, d, d) for d in devices]

elif plat[:3] == 'bsd' or  \
        plat[:7] == 'freebsd':

    def comports():
        devices = glob.glob('/dev/cuad*')
        return [(d, d, d) for d in devices]

elif plat[:6] == 'darwin':   # OS X (confirmed)
    def comports():
        """scan for available ports. return a list of device names."""
        devices = glob.glob('/dev/tty.*')
        return [(d, d, d) for d in devices]

elif plat[:6] == 'netbsd':   # NetBSD
    def comports():
        """scan for available ports. return a list of device names."""
        devices = glob.glob('/dev/dty*')
        return [(d, d, d) for d in devices]

elif plat[:4] == 'irix':     # IRIX
    def comports():
        """scan for available ports. return a list of device names."""
        devices = glob.glob('/dev/ttyf*')
        return [(d, d, d) for d in devices]

elif plat[:2] == 'hp':       # HP-UX (not tested)
    def comports():
        """scan for available ports. return a list of device names."""
        devices = glob.glob('/dev/tty*p0')
        return [(d, d, d) for d in devices]

elif plat[:5] == 'sunos':    # Solaris/SunOS
    def comports():
        """scan for available ports. return a list of device names."""
        devices = glob.glob('/dev/tty*c')
        return [(d, d, d) for d in devices]

elif plat[:3] == 'aix':      # AIX
    def comports():
        """scan for available ports. return a list of device names."""
        devices = glob.glob('/dev/tty*')
        return [(d, d, d) for d in devices]

else:
    # platform detection has failed...
    sys.stderr.write("""\
don't know how to enumerate ttys on this system.
! I you know how the serial ports are named send this information to
! the author of this module:

sys.platform = %r
os.name = %r
pySerial version = %s

also add the naming scheme of the serial ports and with a bit luck you can get
this module running...
""" % (sys.platform, os.name, serial.VERSION))
    raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,))

# test
if __name__ == '__main__':
    for port, desc, hwid in sorted(comports()):
        print "%s: %s [%s]" % (port, desc, hwid)