Skip to content

Commit

Permalink
serial port enumeration code added
Browse files Browse the repository at this point in the history
  • Loading branch information
stefanix committed Mar 28, 2012
1 parent 72fb044 commit 9c3045c
Show file tree
Hide file tree
Showing 5 changed files with 521 additions and 8 deletions.
16 changes: 9 additions & 7 deletions backend/app.py
Expand Up @@ -15,6 +15,7 @@
VERSION = "v12.02-beta2"
SERIAL_PORT = None
BITSPERSECOND = 9600
NETWORK_PORT = 4444
CONFIG_FILE = "lasaurapp.conf"
GUESS_PPREFIX = "tty.usbmodem"
COOKIE_KEY = 'secret_key_jkn23489hsdf'
Expand All @@ -36,27 +37,27 @@ def data_root():



def run_with_callback(host, port=4444, timeout=0.01):
def run_with_callback(host):
""" Start a wsgiref server instance with control over the main loop.
This is a function that I derived from the bottle.py run()
"""
handler = default_app()
server = wsgiref.simple_server.make_server(host, port, handler)
server.timeout = timeout
server = wsgiref.simple_server.make_server(host, NETWORK_PORT, handler)
server.timeout = 0.01
print "-----------------------------------------------------------------------------"
print "Bottle server starting up ..."
print "Serial is set to %d bps" % BITSPERSECOND
print "Point your browser to: "
print "http://%s:%d/ (local)" % ('127.0.0.1', port)
print "http://%s:%d/ (local)" % ('127.0.0.1', NETWORK_PORT)
if host == '':
print "http://%s:%d/ (public)" % (socket.gethostbyname(socket.gethostname()), port)
print "http://%s:%d/ (public)" % (socket.gethostbyname(socket.gethostname()), NETWORK_PORT)
print "Use Ctrl-C to quit."
print "-----------------------------------------------------------------------------"
print
try:
webbrowser.open_new_tab('http://127.0.0.1:'+str(port))
webbrowser.open_new_tab('http://127.0.0.1:'+str(NETWORK_PORT))
except webbrowser.Error:
print "Cannot open Webbrowser, please to so manually."
print "Cannot open Webbrowser, please do so manually."
while 1:
try:
SerialManager.send_queue_as_ready()
Expand Down Expand Up @@ -246,6 +247,7 @@ def queue_pct_done_handler():
else:
run_with_callback('127.0.0.1')
else:
SerialManager.list_devices()
print "-----------------------------------------------------------------------------"
print "ERROR: LasaurApp doesn't know what serial device to connect to!"
print "On Linux or OSX this is something like '/dev/tty.usbmodemfd121' and on"
Expand Down
99 changes: 99 additions & 0 deletions backend/serial_list_ports.py
@@ -0,0 +1,99 @@
#!/usr/bin/env python

# portable serial port access with python
# this is a wrapper module for different platform implementations of the
# port enumeration feature
#
# (C) 2011 Chris Liechti <cliechti@gmx.net>
# this is distributed under a free software license, see license.txt

"""\
This module will provide a function called comports that returns an
iterable (generator or list) that will enumerate available com ports. Note that
on some systems non-existent ports may be listed.
Additionally a grep function is supplied that can be used to search for ports
based on their descriptions or hardware ID.
"""

import sys, os, re

# chose an implementation, depending on os
#~ if sys.platform == 'cli':
#~ else:
import os
# chose an implementation, depending on os
if os.name == 'nt': #sys.platform == 'win32':
from serial_list_ports_windows import *
elif os.name == 'posix':
from serial_list_ports_posix import *
#~ elif os.name == 'java':
else:
raise ImportError("Sorry: no implementation for your platform ('%s') available" % (os.name,))


def grep(regexp):
"""\
Search for ports using a regular expression. Port name, description and
hardware ID are searched. The function returns an iterable that returns the
same tuples as comport() would do.
"""
for port, desc, hwid in comports():
if re.search(regexp, port, re.I) or re.search(regexp, desc) or re.search(regexp, hwid):
yield port, desc, hwid


def main():
import optparse

parser = optparse.OptionParser(
usage = "%prog [options] [<regexp>]",
description = "Miniterm - A simple terminal program for the serial port."
)

parser.add_option("--debug",
help="print debug messages and tracebacks (development mode)",
dest="debug",
default=False,
action='store_true')

parser.add_option("-v", "--verbose",
help="show more messages (can be given multiple times)",
dest="verbose",
default=1,
action='count')

parser.add_option("-q", "--quiet",
help="suppress all messages",
dest="verbose",
action='store_const',
const=0)

(options, args) = parser.parse_args()


hits = 0
# get iteraror w/ or w/o filter
if args:
if len(args) > 1:
parser.error('more than one regexp not supported')
print "Filtered list with regexp: %r" % (args[0],)
iterator = sorted(grep(args[0]))
else:
iterator = sorted(comports())
# list them
for port, desc, hwid in iterator:
print "%-20s" % (port,)
if options.verbose > 1:
print " desc: %s" % (desc,)
print " hwid: %s" % (hwid,)
hits += 1
if options.verbose:
if hits:
print "%d ports found" % (hits,)
else:
print "no ports found"

# test
if __name__ == '__main__':
main()
197 changes: 197 additions & 0 deletions backend/serial_list_ports_posix.py
@@ -0,0 +1,197 @@
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"""
m = re.search(regexp, text)
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):
bus, dev = os.path.basename(os.path.realpath(sysfs_path)).split('-')
try:
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 == 'openbsd3': # BSD
def comports():
devices = glob.glob('/dev/ttyp*')
return [(d, d, d) for d in devices]

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

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)

0 comments on commit 9c3045c

Please sign in to comment.