[Feature]add MT2731_MP2_MR2_SVN388 baseline version

Change-Id: Ief04314834b31e27effab435d3ca8ba33b499059
diff --git a/src/devtools/datool/pyserial/tools/__init__.py b/src/devtools/datool/pyserial/tools/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/__init__.py
diff --git a/src/devtools/datool/pyserial/tools/hexlify_codec.py b/src/devtools/datool/pyserial/tools/hexlify_codec.py
new file mode 100644
index 0000000..3200701
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/hexlify_codec.py
@@ -0,0 +1,98 @@
+#! python
+#
+# This is a codec to create and decode hexdumps with spaces between characters. used by miniterm.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2011 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+"""\
+Python 'hex' Codec - 2-digit hex with spaces content transfer encoding.
+"""
+
+import codecs
+import pyserial as serial
+
+HEXDIGITS = '0123456789ABCDEF'
+
+### Codec APIs
+
+
+def hex_encode(input, errors='strict'):
+    return (serial.to_bytes([int(h, 16) for h in input.split()]), len(input))
+
+
+def hex_decode(input, errors='strict'):
+    return (''.join('{:02X} '.format(b) for b in input), len(input))
+
+
+class Codec(codecs.Codec):
+    def encode(self, input, errors='strict'):
+        return serial.to_bytes([int(h, 16) for h in input.split()])
+
+    def decode(self, input, errors='strict'):
+        return ''.join('{:02X} '.format(b) for b in input)
+
+
+class IncrementalEncoder(codecs.IncrementalEncoder):
+
+    def __init__(self, errors='strict'):
+        self.errors = errors
+        self.state = 0
+
+    def reset(self):
+        self.state = 0
+
+    def getstate(self):
+        return self.state
+
+    def setstate(self, state):
+        self.state = state
+
+    def encode(self, input, final=False):
+        state = self.state
+        encoded = []
+        for c in input.upper():
+            if c in HEXDIGITS:
+                z = HEXDIGITS.index(c)
+                if state:
+                    encoded.append(z + (state & 0xf0))
+                    state = 0
+                else:
+                    state = 0x100 + (z << 4)
+            elif c == ' ':      # allow spaces to separate values
+                if state and self.errors == 'strict':
+                    raise UnicodeError('odd number of hex digits')
+                state = 0
+            else:
+                if self.errors == 'strict':
+                    raise UnicodeError('non-hex digit found: %r' % c)
+        self.state = state
+        return serial.to_bytes(encoded)
+
+
+class IncrementalDecoder(codecs.IncrementalDecoder):
+    def decode(self, input, final=False):
+        return ''.join('{:02X} '.format(b) for b in input)
+
+
+class StreamWriter(Codec, codecs.StreamWriter):
+    pass
+
+
+class StreamReader(Codec, codecs.StreamReader):
+    pass
+
+
+### encodings module API
+def getregentry():
+    return codecs.CodecInfo(
+        name='hexlify',
+        encode=hex_encode,
+        decode=hex_decode,
+        incrementalencoder=IncrementalEncoder,
+        incrementaldecoder=IncrementalDecoder,
+        streamwriter=StreamWriter,
+        streamreader=StreamReader,
+        _is_text_encoding=True,
+    )
diff --git a/src/devtools/datool/pyserial/tools/list_ports.py b/src/devtools/datool/pyserial/tools/list_ports.py
new file mode 100644
index 0000000..d174f2a
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/list_ports.py
@@ -0,0 +1,103 @@
+#!/usr/bin/env python
+#
+# Serial port enumeration. Console tool and backend selection.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+"""\
+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
+import os
+import re
+
+# chose an implementation, depending on os
+#~ if sys.platform == 'cli':
+#~ else:
+if os.name == 'nt':  # sys.platform == 'win32':
+    from pyserial.tools.list_ports_windows import comports
+elif os.name == 'posix':
+    from pyserial.tools.list_ports_posix import comports
+#~ 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.
+    """
+    r = re.compile(regexp, re.I)
+    for info in comports():
+        port, desc, hwid = info
+        if r.search(port) or r.search(desc) or r.search(hwid):
+            yield info
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+def main():
+    import argparse
+
+    parser = argparse.ArgumentParser(description='Serial port enumeration')
+
+    parser.add_argument(
+            'regexp',
+            nargs='?',
+            help='only show ports that match this regex')
+
+    parser.add_argument(
+            '-v', '--verbose',
+            action='store_true',
+            help='show more messages')
+
+    parser.add_argument(
+            '-q', '--quiet',
+            action='store_true',
+            help='suppress all messages')
+
+    parser.add_argument(
+            '-n',
+            type=int,
+            help='only output the N-th entry')
+
+    args = parser.parse_args()
+
+    hits = 0
+    # get iteraror w/ or w/o filter
+    if args.regexp:
+        if not args.quiet:
+            sys.stderr.write("Filtered list with regexp: %r\n" % (args.regexp,))
+        iterator = sorted(grep(args.regexp))
+    else:
+        iterator = sorted(comports())
+    # list them
+    for n, (port, desc, hwid) in enumerate(iterator, 1):
+        if args.n is None or args.n == n:
+            sys.stdout.write("{:20}\n".format(port))
+            if args.verbose:
+                sys.stdout.write("    desc: {}\n".format(desc))
+                sys.stdout.write("    hwid: {}\n".format(hwid))
+        hits += 1
+    if not args.quiet:
+        if hits:
+            sys.stderr.write("{} ports found\n".format(hits))
+        else:
+            sys.stderr.write("no ports found\n")
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+    main()
diff --git a/src/devtools/datool/pyserial/tools/list_ports_common.py b/src/devtools/datool/pyserial/tools/list_ports_common.py
new file mode 100644
index 0000000..640b2a1
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/list_ports_common.py
@@ -0,0 +1,90 @@
+#!/usr/bin/env python
+#
+# This is a helper module for the various platform dependent list_port
+# implementations.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+import re
+
+
+def numsplit(text):
+    """\
+    Convert string into a list of texts and numbers in order to support a
+    natural sorting.
+    """
+    result = []
+    for group in re.split(r'(\d+)', text):
+        if group:
+            try:
+                group = int(group)
+            except ValueError:
+                pass
+            result.append(group)
+    return result
+
+
+class ListPortInfo(object):
+    """Info collection base class for serial ports"""
+
+    def __init__(self, device=None):
+        self.device = device
+        self.name = None
+        self.description = 'n/a'
+        self.hwid = 'n/a'
+        # USB specific data
+        self.vid = None
+        self.pid = None
+        self.serial_number = None
+        self.location = None
+        self.manufacturer = None
+        self.product = None
+        self.interface = None
+
+    def usb_description(self):
+        if self.interface is not None:
+            return '{} - {}'.format(self.product, self.interface)
+        elif self.product is not None:
+            return self.product
+        else:
+            return self.name
+
+    def usb_info(self):
+        return 'USB VID:PID={:04X}:{:04X}{}{}'.format(
+                self.vid,
+                self.pid,
+                ' SER={}'.format(self.serial_number) if self.serial_number is not None else '',
+                ' LOCATION={}'.format(self.location) if self.location is not None else '',
+                )
+
+    def apply_usb_info(self):
+        """update description and hwid from USB data"""
+        self.description = self.usb_description()
+        self.hwid = self.usb_info()
+
+    def __eq__(self, other):
+        return self.device == other.device
+
+    def __lt__(self, other):
+        return numsplit(self.device) < numsplit(other.device)
+
+    def __str__(self):
+        return '{} - {}'.format(self.device, self.description)
+
+    def __getitem__(self, index):
+        """Item access: backwards compatible -> (port, desc, hwid)"""
+        if index == 0:
+            return self.device
+        elif index == 1:
+            return self.description
+        elif index == 2:
+            return self.hwid
+        else:
+            raise IndexError('{} > 2'.format(index))
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+    print(ListPortInfo('dummy'))
diff --git a/src/devtools/datool/pyserial/tools/list_ports_linux.py b/src/devtools/datool/pyserial/tools/list_ports_linux.py
new file mode 100644
index 0000000..1969695
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/list_ports_linux.py
@@ -0,0 +1,84 @@
+#!/usr/bin/env python
+#
+# This is a module that gathers a list of serial ports including details on
+# GNU/Linux systems.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+import glob
+import os
+from pyserial.tools import list_ports_common
+
+
+class SysFS(list_ports_common.ListPortInfo):
+    """Wrapper for easy sysfs access and device info"""
+
+    def __init__(self, device):
+        super(SysFS, self).__init__(device)
+        self.name = os.path.basename(device)
+        self.usb_device_path = None
+        if os.path.exists('/sys/class/tty/%s/device' % (self.name,)):
+            self.device_path = os.path.realpath('/sys/class/tty/%s/device' % (self.name,))
+            self.subsystem = os.path.basename(os.path.realpath(os.path.join(self.device_path, 'subsystem')))
+        else:
+            self.device_path = None
+            self.subsystem = None
+        # check device type
+        if self.subsystem == 'usb-serial':
+            self.usb_device_path = os.path.dirname(os.path.dirname(self.device_path))
+        elif self.subsystem == 'usb':
+            self.usb_device_path = os.path.dirname(self.device_path)
+        else:
+            self.usb_device_path = None
+        # fill-in info for USB devices
+        if self.usb_device_path is not None:
+            self.vid = int(self.read_line(self.usb_device_path, 'idVendor'), 16)
+            self.pid = int(self.read_line(self.usb_device_path, 'idProduct'), 16)
+            self.serial_number = self.read_line(self.usb_device_path, 'serial')
+            self.location = os.path.basename(self.usb_device_path)
+            self.manufacturer = self.read_line(self.usb_device_path, 'manufacturer')
+            self.product = self.read_line(self.usb_device_path, 'product')
+            self.interface = self.read_line(self.device_path, 'interface')
+
+        if self.subsystem in ('usb', 'usb-serial'):
+            self.apply_usb_info()
+        #~ elif self.subsystem in ('pnp', 'amba'):  # PCI based devices, raspi
+        elif self.subsystem == 'pnp':  # PCI based devices
+            self.description = self.name
+            self.hwid = self.read_line(self.device_path, 'id')
+        elif self.subsystem == 'amba':  # raspi
+            self.description = self.name
+            self.hwid = os.path.basename(self.device_path)
+
+    def read_line(self, *args):
+        """\
+        Helper function to read a single line from a file.
+        One or more parameters are allowed, they are joined with os.path.join.
+        Returns None on errors..
+        """
+        try:
+            with open(os.path.join(*args)) as f:
+                line = f.readline().strip()
+            return line
+        except IOError:
+            return None
+
+
+def comports():
+    devices = glob.glob('/dev/ttyS*')           # built-in serial ports
+    devices.extend(glob.glob('/dev/ttyUSB*'))   # usb-serial with own driver
+    devices.extend(glob.glob('/dev/ttyACM*'))   # usb-serial with CDC-ACM profile
+    devices.extend(glob.glob('/dev/ttyAMA*'))   # ARM internal port (raspi)
+    devices.extend(glob.glob('/dev/rfcomm*'))   # BT serial devices
+    return [info
+            for info in [SysFS(d) for d in devices]
+            if info.subsystem != "platform"]    # hide non-present internal serial ports
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+    for port, desc, hwid in sorted(comports()):
+        print("%s: %s [%s]" % (port, desc, hwid))
diff --git a/src/devtools/datool/pyserial/tools/list_ports_osx.py b/src/devtools/datool/pyserial/tools/list_ports_osx.py
new file mode 100644
index 0000000..543b3c7
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/list_ports_osx.py
@@ -0,0 +1,259 @@
+#!/usr/bin/env python
+#
+# This is a module that gathers a list of serial ports including details on OSX
+#
+# code originally from https://github.com/makerbot/pyserial/tree/master/serial/tools
+# with contributions from cibomahto, dgs3, FarMcKon, tedbrandston
+# and modifications by cliechti, hoihu, hardkrash
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2013-2015
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+
+# List all of the callout devices in OS/X by querying IOKit.
+
+# See the following for a reference of how to do this:
+# http://developer.apple.com/library/mac/#documentation/DeviceDrivers/Conceptual/WorkingWSerial/WWSerial_SerialDevs/SerialDevices.html#//apple_ref/doc/uid/TP30000384-CIHGEAFD
+
+# More help from darwin_hid.py
+
+# Also see the 'IORegistryExplorer' for an idea of what we are actually searching
+
+import ctypes
+from ctypes import util
+
+from pyserial.tools import list_ports_common
+
+iokit = ctypes.cdll.LoadLibrary(ctypes.util.find_library('IOKit'))
+cf = ctypes.cdll.LoadLibrary(ctypes.util.find_library('CoreFoundation'))
+
+kIOMasterPortDefault = ctypes.c_void_p.in_dll(iokit, "kIOMasterPortDefault")
+kCFAllocatorDefault = ctypes.c_void_p.in_dll(cf, "kCFAllocatorDefault")
+
+kCFStringEncodingMacRoman = 0
+
+iokit.IOServiceMatching.restype = ctypes.c_void_p
+
+iokit.IOServiceGetMatchingServices.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+iokit.IOServiceGetMatchingServices.restype = ctypes.c_void_p
+
+iokit.IORegistryEntryGetParentEntry.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+
+iokit.IORegistryEntryCreateCFProperty.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p, ctypes.c_uint32]
+iokit.IORegistryEntryCreateCFProperty.restype = ctypes.c_void_p
+
+iokit.IORegistryEntryGetPath.argtypes = [ctypes.c_void_p, ctypes.c_void_p, ctypes.c_void_p]
+iokit.IORegistryEntryGetPath.restype = ctypes.c_void_p
+
+iokit.IORegistryEntryGetName.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+iokit.IORegistryEntryGetName.restype = ctypes.c_void_p
+
+iokit.IOObjectGetClass.argtypes = [ctypes.c_void_p, ctypes.c_void_p]
+iokit.IOObjectGetClass.restype = ctypes.c_void_p
+
+iokit.IOObjectRelease.argtypes = [ctypes.c_void_p]
+
+
+cf.CFStringCreateWithCString.argtypes = [ctypes.c_void_p, ctypes.c_char_p, ctypes.c_int32]
+cf.CFStringCreateWithCString.restype = ctypes.c_void_p
+
+cf.CFStringGetCStringPtr.argtypes = [ctypes.c_void_p, ctypes.c_uint32]
+cf.CFStringGetCStringPtr.restype = ctypes.c_char_p
+
+cf.CFNumberGetValue.argtypes = [ctypes.c_void_p, ctypes.c_uint32, ctypes.c_void_p]
+cf.CFNumberGetValue.restype = ctypes.c_void_p
+
+# void CFRelease ( CFTypeRef cf );
+cf.CFRelease.argtypes = [ctypes.c_void_p]
+cf.CFRelease.restype = None
+
+# CFNumber type defines
+kCFNumberSInt8Type = 1
+kCFNumberSInt16Type = 2
+kCFNumberSInt32Type = 3
+kCFNumberSInt64Type = 4
+
+
+def get_string_property(device_type, property):
+    """
+    Search the given device for the specified string property
+
+    @param device_type Type of Device
+    @param property String to search for
+    @return Python string containing the value, or None if not found.
+    """
+    key = cf.CFStringCreateWithCString(
+            kCFAllocatorDefault,
+            property.encode("mac_roman"),
+            kCFStringEncodingMacRoman)
+
+    CFContainer = iokit.IORegistryEntryCreateCFProperty(
+            device_type,
+            key,
+            kCFAllocatorDefault,
+            0)
+    output = None
+
+    if CFContainer:
+        output = cf.CFStringGetCStringPtr(CFContainer, 0)
+        if output is not None:
+            output = output.decode('mac_roman')
+        cf.CFRelease(CFContainer)
+    return output
+
+
+def get_int_property(device_type, property, cf_number_type):
+    """
+    Search the given device for the specified string property
+
+    @param device_type Device to search
+    @param property String to search for
+    @param cf_number_type CFType number
+
+    @return Python string containing the value, or None if not found.
+    """
+    key = cf.CFStringCreateWithCString(
+            kCFAllocatorDefault,
+            property.encode("mac_roman"),
+            kCFStringEncodingMacRoman)
+
+    CFContainer = iokit.IORegistryEntryCreateCFProperty(
+            device_type,
+            key,
+            kCFAllocatorDefault,
+            0)
+
+    if CFContainer:
+        if (cf_number_type == kCFNumberSInt32Type):
+            number = ctypes.c_uint32()
+        elif (cf_number_type == kCFNumberSInt16Type):
+            number = ctypes.c_uint16()
+        cf.CFNumberGetValue(CFContainer, cf_number_type, ctypes.byref(number))
+        cf.CFRelease(CFContainer)
+        return number.value
+    return None
+
+
+def IORegistryEntryGetName(device):
+    pathname = ctypes.create_string_buffer(100)  # TODO: Is this ok?
+    iokit.IOObjectGetClass(device, ctypes.byref(pathname))
+    return pathname.value
+
+
+def GetParentDeviceByType(device, parent_type):
+    """ Find the first parent of a device that implements the parent_type
+        @param IOService Service to inspect
+        @return Pointer to the parent type, or None if it was not found.
+    """
+    # First, try to walk up the IOService tree to find a parent of this device that is a IOUSBDevice.
+    parent_type = parent_type.encode('mac_roman')
+    while IORegistryEntryGetName(device) != parent_type:
+        parent = ctypes.c_void_p()
+        response = iokit.IORegistryEntryGetParentEntry(
+                device,
+                "IOService".encode("mac_roman"),
+                ctypes.byref(parent))
+        # If we weren't able to find a parent for the device, we're done.
+        if response != 0:
+            return None
+        device = parent
+    return device
+
+
+def GetIOServicesByType(service_type):
+    """
+    returns iterator over specified service_type
+    """
+    serial_port_iterator = ctypes.c_void_p()
+
+    iokit.IOServiceGetMatchingServices(
+            kIOMasterPortDefault,
+            iokit.IOServiceMatching(service_type.encode('mac_roman')),
+            ctypes.byref(serial_port_iterator))
+
+    services = []
+    while iokit.IOIteratorIsValid(serial_port_iterator):
+        service = iokit.IOIteratorNext(serial_port_iterator)
+        if not service:
+            break
+        services.append(service)
+    iokit.IOObjectRelease(serial_port_iterator)
+    return services
+
+
+def location_to_string(locationID):
+    """
+    helper to calculate port and bus number from locationID
+    """
+    loc = ['{}-'.format(locationID >> 24)]
+    while locationID & 0xf00000:
+        if len(loc) > 1:
+            loc.append('.')
+        loc.append('{}'.format((locationID >> 20) & 0xf))
+        locationID <<= 4
+    return ''.join(loc)
+
+
+class SuitableSerialInterface(object):
+    pass
+
+
+def scan_interfaces():
+    """
+    helper function to scan USB interfaces
+    returns a list of SuitableSerialInterface objects with name and id attributes
+    """
+    interfaces = []
+    for service in GetIOServicesByType('IOSerialBSDClient'):
+        device = get_string_property(service, "IOCalloutDevice")
+        if device:
+            usb_device = GetParentDeviceByType(service, "IOUSBInterface")
+            if usb_device:
+                name = get_string_property(usb_device, "USB Interface Name") or None
+                locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type) or ''
+                i = SuitableSerialInterface()
+                i.id = locationID
+                i.name = name
+                interfaces.append(i)
+    return interfaces
+
+
+def search_for_locationID_in_interfaces(serial_interfaces, locationID):
+    for interface in serial_interfaces:
+        if (interface.id == locationID):
+            return interface.name
+    return None
+
+
+def comports():
+    # Scan for all iokit serial ports
+    services = GetIOServicesByType('IOSerialBSDClient')
+    ports = []
+    serial_interfaces = scan_interfaces()
+    for service in services:
+        # First, add the callout device file.
+        device = get_string_property(service, "IOCalloutDevice")
+        if device:
+            info = list_ports_common.ListPortInfo(device)
+            # If the serial port is implemented by IOUSBDevice
+            usb_device = GetParentDeviceByType(service, "IOUSBDevice")
+            if usb_device:
+                # fetch some useful informations from properties
+                info.vid = get_int_property(usb_device, "idVendor", kCFNumberSInt16Type)
+                info.pid = get_int_property(usb_device, "idProduct", kCFNumberSInt16Type)
+                info.serial_number = get_string_property(usb_device, "USB Serial Number")
+                info.product = get_string_property(usb_device, "USB Product Name") or 'n/a'
+                info.manufacturer = get_string_property(usb_device, "USB Vendor Name")
+                locationID = get_int_property(usb_device, "locationID", kCFNumberSInt32Type)
+                info.location = location_to_string(locationID)
+                info.interface = search_for_locationID_in_interfaces(serial_interfaces, locationID)
+                info.apply_usb_info()
+            ports.append(info)
+    return ports
+
+# test
+if __name__ == '__main__':
+    for port, desc, hwid in sorted(comports()):
+        print("%s: %s [%s]" % (port, desc, hwid))
diff --git a/src/devtools/datool/pyserial/tools/list_ports_posix.py b/src/devtools/datool/pyserial/tools/list_ports_posix.py
new file mode 100644
index 0000000..ecb2334
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/list_ports_posix.py
@@ -0,0 +1,102 @@
+#!/usr/bin/env python
+#
+# This is a module that gathers a list of serial ports on POSIXy systems.
+# For some specific implementations, see also list_ports_linux, list_ports_osx
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2011-2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+"""\
+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.
+"""
+
+import glob
+import sys
+import os
+from pyserial.tools import list_ports_common
+
+# try to detect the OS so that a device can be selected...
+plat = sys.platform.lower()
+
+if plat[:5] == 'linux':    # Linux (confirmed)  # noqa
+    from pyserial.tools.list_ports_linux import comports
+
+elif plat[:6] == 'darwin':   # OS X (confirmed)
+    from pyserial.tools.list_ports_osx import comports
+
+elif plat == 'cygwin':       # cygwin/win32
+    # cygwin accepts /dev/com* in many contexts
+    # (such as 'open' call, explicit 'ls'), but 'glob.glob'
+    # and bare 'ls' do not; so use /dev/ttyS* instead
+    def comports():
+        devices = glob.glob('/dev/ttyS*')
+        return [list_ports_common.ListPortInfo(d) for d in devices]
+
+elif plat[:7] == 'openbsd':    # OpenBSD
+    def comports():
+        devices = glob.glob('/dev/cua*')
+        return [list_ports_common.ListPortInfo(d) for d in devices]
+
+elif plat[:3] == 'bsd' or plat[:7] == 'freebsd':
+
+    def comports():
+        devices = glob.glob('/dev/cua*[!.init][!.lock]')
+        return [list_ports_common.ListPortInfo(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 [list_ports_common.ListPortInfo(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 [list_ports_common.ListPortInfo(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 [list_ports_common.ListPortInfo(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 [list_ports_common.ListPortInfo(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 [list_ports_common.ListPortInfo(d) for d in devices]
+
+else:
+    # platform detection has failed...
+    import pyserial as serial
+    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))
diff --git a/src/devtools/datool/pyserial/tools/list_ports_windows.py b/src/devtools/datool/pyserial/tools/list_ports_windows.py
new file mode 100644
index 0000000..e234fc2
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/list_ports_windows.py
@@ -0,0 +1,294 @@
+#! python
+#
+# Enumerate serial ports on Windows including a human readable description
+# and hardware information.
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C) 2001-2016 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+import re
+import ctypes
+from ctypes.wintypes import BOOL
+from ctypes.wintypes import HWND
+from ctypes.wintypes import DWORD
+from ctypes.wintypes import WORD
+from ctypes.wintypes import LONG
+from ctypes.wintypes import ULONG
+from ctypes.wintypes import LPCSTR
+from ctypes.wintypes import HKEY
+from ctypes.wintypes import BYTE
+import pyserial as serial
+from pyserial.win32 import ULONG_PTR
+from pyserial.tools import list_ports_common
+
+
+def ValidHandle(value, func, arguments):
+    if value == 0:
+        raise ctypes.WinError()
+    return value
+
+NULL = 0
+HDEVINFO = ctypes.c_void_p
+PCTSTR = ctypes.c_char_p
+PTSTR = ctypes.c_void_p
+CHAR = ctypes.c_char
+LPDWORD = PDWORD = ctypes.POINTER(DWORD)
+#~ LPBYTE = PBYTE = ctypes.POINTER(BYTE)
+LPBYTE = PBYTE = ctypes.c_void_p        # XXX avoids error about types
+
+ACCESS_MASK = DWORD
+REGSAM = ACCESS_MASK
+
+
+def byte_buffer(length):
+    """Get a buffer for a string"""
+    return (BYTE*length)()
+
+
+def string(buffer):
+    s = []
+    for c in buffer:
+        if c == 0:
+            break
+        s.append(chr(c & 0xff))  # "& 0xff": hack to convert signed to unsigned
+    return ''.join(s)
+
+
+class GUID(ctypes.Structure):
+    _fields_ = [
+        ('Data1', DWORD),
+        ('Data2', WORD),
+        ('Data3', WORD),
+        ('Data4', BYTE*8),
+    ]
+
+    def __str__(self):
+        return "{%08x-%04x-%04x-%s-%s}" % (
+            self.Data1,
+            self.Data2,
+            self.Data3,
+            ''.join(["%02x" % d for d in self.Data4[:2]]),
+            ''.join(["%02x" % d for d in self.Data4[2:]]),
+        )
+
+
+class SP_DEVINFO_DATA(ctypes.Structure):
+    _fields_ = [
+        ('cbSize', DWORD),
+        ('ClassGuid', GUID),
+        ('DevInst', DWORD),
+        ('Reserved', ULONG_PTR),
+    ]
+
+    def __str__(self):
+        return "ClassGuid:%s DevInst:%s" % (self.ClassGuid, self.DevInst)
+
+PSP_DEVINFO_DATA = ctypes.POINTER(SP_DEVINFO_DATA)
+
+PSP_DEVICE_INTERFACE_DETAIL_DATA = ctypes.c_void_p
+
+setupapi = ctypes.windll.LoadLibrary("setupapi")
+SetupDiDestroyDeviceInfoList = setupapi.SetupDiDestroyDeviceInfoList
+SetupDiDestroyDeviceInfoList.argtypes = [HDEVINFO]
+SetupDiDestroyDeviceInfoList.restype = BOOL
+
+SetupDiClassGuidsFromName = setupapi.SetupDiClassGuidsFromNameA
+SetupDiClassGuidsFromName.argtypes = [PCTSTR, ctypes.POINTER(GUID), DWORD, PDWORD]
+SetupDiClassGuidsFromName.restype = BOOL
+
+SetupDiEnumDeviceInfo = setupapi.SetupDiEnumDeviceInfo
+SetupDiEnumDeviceInfo.argtypes = [HDEVINFO, DWORD, PSP_DEVINFO_DATA]
+SetupDiEnumDeviceInfo.restype = BOOL
+
+SetupDiGetClassDevs = setupapi.SetupDiGetClassDevsA
+SetupDiGetClassDevs.argtypes = [ctypes.POINTER(GUID), PCTSTR, HWND, DWORD]
+SetupDiGetClassDevs.restype = HDEVINFO
+SetupDiGetClassDevs.errcheck = ValidHandle
+
+SetupDiGetDeviceRegistryProperty = setupapi.SetupDiGetDeviceRegistryPropertyA
+SetupDiGetDeviceRegistryProperty.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, PDWORD, PBYTE, DWORD, PDWORD]
+SetupDiGetDeviceRegistryProperty.restype = BOOL
+
+SetupDiGetDeviceInstanceId = setupapi.SetupDiGetDeviceInstanceIdA
+SetupDiGetDeviceInstanceId.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, PTSTR, DWORD, PDWORD]
+SetupDiGetDeviceInstanceId.restype = BOOL
+
+SetupDiOpenDevRegKey = setupapi.SetupDiOpenDevRegKey
+SetupDiOpenDevRegKey.argtypes = [HDEVINFO, PSP_DEVINFO_DATA, DWORD, DWORD, DWORD, REGSAM]
+SetupDiOpenDevRegKey.restype = HKEY
+
+advapi32 = ctypes.windll.LoadLibrary("Advapi32")
+RegCloseKey = advapi32.RegCloseKey
+RegCloseKey.argtypes = [HKEY]
+RegCloseKey.restype = LONG
+
+RegQueryValueEx = advapi32.RegQueryValueExA
+RegQueryValueEx.argtypes = [HKEY, LPCSTR, LPDWORD, LPDWORD, LPBYTE, LPDWORD]
+RegQueryValueEx.restype = LONG
+
+
+DIGCF_PRESENT = 2
+DIGCF_DEVICEINTERFACE = 16
+INVALID_HANDLE_VALUE = 0
+ERROR_INSUFFICIENT_BUFFER = 122
+SPDRP_HARDWAREID = 1
+SPDRP_FRIENDLYNAME = 12
+SPDRP_LOCATION_PATHS = 35
+DICS_FLAG_GLOBAL = 1
+DIREG_DEV = 0x00000001
+KEY_READ = 0x20019
+
+# workaround for compatibility between Python 2.x and 3.x
+Ports = serial.to_bytes([80, 111, 114, 116, 115])  # "Ports"
+PortName = serial.to_bytes([80, 111, 114, 116, 78, 97, 109, 101])  # "PortName"
+
+
+def comports():
+    GUIDs = (GUID*8)()  # so far only seen one used, so hope 8 are enough...
+    guids_size = DWORD()
+    if not SetupDiClassGuidsFromName(
+            Ports,
+            GUIDs,
+            ctypes.sizeof(GUIDs),
+            ctypes.byref(guids_size)):
+        raise ctypes.WinError()
+
+    # repeat for all possible GUIDs
+    for index in range(guids_size.value):
+        g_hdi = SetupDiGetClassDevs(
+                ctypes.byref(GUIDs[index]),
+                None,
+                NULL,
+                DIGCF_PRESENT)  # was DIGCF_PRESENT|DIGCF_DEVICEINTERFACE which misses CDC ports
+
+        devinfo = SP_DEVINFO_DATA()
+        devinfo.cbSize = ctypes.sizeof(devinfo)
+        index = 0
+        while SetupDiEnumDeviceInfo(g_hdi, index, ctypes.byref(devinfo)):
+            index += 1
+
+            # get the real com port name
+            hkey = SetupDiOpenDevRegKey(
+                    g_hdi,
+                    ctypes.byref(devinfo),
+                    DICS_FLAG_GLOBAL,
+                    0,
+                    DIREG_DEV,  # DIREG_DRV for SW info
+                    KEY_READ)
+            port_name_buffer = byte_buffer(250)
+            port_name_length = ULONG(ctypes.sizeof(port_name_buffer))
+            RegQueryValueEx(
+                    hkey,
+                    PortName,
+                    None,
+                    None,
+                    ctypes.byref(port_name_buffer),
+                    ctypes.byref(port_name_length))
+            RegCloseKey(hkey)
+
+            # unfortunately does this method also include parallel ports.
+            # we could check for names starting with COM or just exclude LPT
+            # and hope that other "unknown" names are serial ports...
+            if string(port_name_buffer).startswith('LPT'):
+                continue
+
+            # hardware ID
+            szHardwareID = byte_buffer(250)
+            # try to get ID that includes serial number
+            if not SetupDiGetDeviceInstanceId(
+                    g_hdi,
+                    ctypes.byref(devinfo),
+                    ctypes.byref(szHardwareID),
+                    ctypes.sizeof(szHardwareID) - 1,
+                    None):
+                # fall back to more generic hardware ID if that would fail
+                if not SetupDiGetDeviceRegistryProperty(
+                        g_hdi,
+                        ctypes.byref(devinfo),
+                        SPDRP_HARDWAREID,
+                        None,
+                        ctypes.byref(szHardwareID),
+                        ctypes.sizeof(szHardwareID) - 1,
+                        None):
+                    # Ignore ERROR_INSUFFICIENT_BUFFER
+                    if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
+                        raise ctypes.WinError()
+            # stringify
+            szHardwareID_str = string(szHardwareID)
+
+            info = list_ports_common.ListPortInfo(string(port_name_buffer))
+
+            # in case of USB, make a more readable string, similar to that form
+            # that we also generate on other platforms
+            if szHardwareID_str.startswith('USB'):
+                m = re.search(r'VID_([0-9a-f]{4})&PID_([0-9a-f]{4})(\\(\w+))?', szHardwareID_str, re.I)
+                if m:
+                    info.vid = int(m.group(1), 16)
+                    info.pid = int(m.group(2), 16)
+                    if m.group(4):
+                        info.serial_number = m.group(4)
+                # calculate a location string
+                loc_path_str = byte_buffer(250)
+                if SetupDiGetDeviceRegistryProperty(
+                        g_hdi,
+                        ctypes.byref(devinfo),
+                        SPDRP_LOCATION_PATHS,
+                        None,
+                        ctypes.byref(loc_path_str),
+                        ctypes.sizeof(loc_path_str) - 1,
+                        None):
+                    #~ print (string(loc_path_str))
+                    m = re.finditer(r'USBROOT\((\w+)\)|#USB\((\w+)\)', string(loc_path_str))
+                    location = []
+                    for g in m:
+                        if g.group(1):
+                            location.append('%d' % (int(g.group(1)) + 1))
+                        else:
+                            if len(location) > 1:
+                                location.append('.')
+                            else:
+                                location.append('-')
+                            location.append(g.group(2))
+                    if location:
+                        info.location = ''.join(location)
+                info.hwid = info.usb_info()
+            elif szHardwareID_str.startswith('FTDIBUS'):
+                m = re.search(r'VID_([0-9a-f]{4})\+PID_([0-9a-f]{4})(\+(\w+))?', szHardwareID_str, re.I)
+                if m:
+                    info.vid = int(m.group(1), 16)
+                    info.pid = int(m.group(2), 16)
+                    if m.group(4):
+                        info.serial_number = m.group(4)
+                # USB location is hidden by FDTI driver :(
+                info.hwid = info.usb_info()
+            else:
+                info.hwid = szHardwareID_str
+
+            # friendly name
+            szFriendlyName = byte_buffer(250)
+            if SetupDiGetDeviceRegistryProperty(
+                    g_hdi,
+                    ctypes.byref(devinfo),
+                    SPDRP_FRIENDLYNAME,
+                    #~ SPDRP_DEVICEDESC,
+                    None,
+                    ctypes.byref(szFriendlyName),
+                    ctypes.sizeof(szFriendlyName) - 1,
+                    None):
+                info.description = string(szFriendlyName)
+            #~ else:
+                # Ignore ERROR_INSUFFICIENT_BUFFER
+                #~ if ctypes.GetLastError() != ERROR_INSUFFICIENT_BUFFER:
+                    #~ raise IOError("failed to get details for %s (%s)" % (devinfo, szHardwareID.value))
+                # ignore errors and still include the port in the list, friendly name will be same as port name
+            yield info
+        SetupDiDestroyDeviceInfoList(g_hdi)
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+    for port, desc, hwid in sorted(comports()):
+        print("%s: %s [%s]" % (port, desc, hwid))
diff --git a/src/devtools/datool/pyserial/tools/miniterm.py b/src/devtools/datool/pyserial/tools/miniterm.py
new file mode 100644
index 0000000..186ac10
--- /dev/null
+++ b/src/devtools/datool/pyserial/tools/miniterm.py
@@ -0,0 +1,878 @@
+#!/usr/bin/env python
+#
+# Very simple serial terminal
+#
+# This file is part of pySerial. https://github.com/pyserial/pyserial
+# (C)2002-2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+
+import codecs
+import os
+import sys
+import threading
+
+import pyserial as serial
+from pyserial.tools.list_ports import comports
+from pyserial.tools import hexlify_codec
+
+codecs.register(lambda c: hexlify_codec.getregentry() if c == 'hexlify' else None)
+
+try:
+    raw_input
+except NameError:
+    raw_input = input   # in python3 it's "raw"
+    unichr = chr
+
+
+def key_description(character):
+    """generate a readable description for a key"""
+    ascii_code = ord(character)
+    if ascii_code < 32:
+        return 'Ctrl+%c' % (ord('@') + ascii_code)
+    else:
+        return repr(character)
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+class ConsoleBase(object):
+    def __init__(self):
+        if sys.version_info >= (3, 0):
+            self.byte_output = sys.stdout.buffer
+        else:
+            self.byte_output = sys.stdout
+        self.output = sys.stdout
+
+    def setup(self):
+        pass
+
+    def cleanup(self):
+        pass
+
+    def getkey(self):
+        return None
+
+    def write_bytes(self, s):
+        self.byte_output.write(s)
+        self.byte_output.flush()
+
+    def write(self, s):
+        self.output.write(s)
+        self.output.flush()
+
+    #  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -  -
+    # context manager:
+    # switch terminal temporary to normal mode (e.g. to get user input)
+
+    def __enter__(self):
+        self.cleanup()
+        return self
+
+    def __exit__(self, *args, **kwargs):
+        self.setup()
+
+
+if os.name == 'nt':
+    import msvcrt
+    import ctypes
+
+    class Out(object):
+        def __init__(self, fd):
+            self.fd = fd
+
+        def flush(self):
+            pass
+
+        def write(self, s):
+            os.write(self.fd, s)
+
+    class Console(ConsoleBase):
+        def __init__(self):
+            super(Console, self).__init__()
+            self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP()
+            self._saved_icp = ctypes.windll.kernel32.GetConsoleCP()
+            ctypes.windll.kernel32.SetConsoleOutputCP(65001)
+            ctypes.windll.kernel32.SetConsoleCP(65001)
+            self.output = codecs.getwriter('UTF-8')(Out(sys.stdout.fileno()), 'replace')
+            # the change of the code page is not propagated to Python, manually fix it
+            sys.stderr = codecs.getwriter('UTF-8')(Out(sys.stderr.fileno()), 'replace')
+            sys.stdout = self.output
+            self.output.encoding = 'UTF-8'  # needed for input
+
+        def __del__(self):
+            ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp)
+            ctypes.windll.kernel32.SetConsoleCP(self._saved_icp)
+
+        def getkey(self):
+            while True:
+                z = msvcrt.getwch()
+                if z == unichr(13):
+                    return unichr(10)
+                elif z in (unichr(0), unichr(0x0e)):    # functions keys, ignore
+                    msvcrt.getwch()
+                else:
+                    return z
+
+elif os.name == 'posix':
+    import atexit
+    import termios
+
+    class Console(ConsoleBase):
+        def __init__(self):
+            super(Console, self).__init__()
+            self.fd = sys.stdin.fileno()
+            self.old = termios.tcgetattr(self.fd)
+            atexit.register(self.cleanup)
+            if sys.version_info < (3, 0):
+                self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin)
+            else:
+                self.enc_stdin = sys.stdin
+
+        def setup(self):
+            new = termios.tcgetattr(self.fd)
+            new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG
+            new[6][termios.VMIN] = 1
+            new[6][termios.VTIME] = 0
+            termios.tcsetattr(self.fd, termios.TCSANOW, new)
+
+        def getkey(self):
+            c = self.enc_stdin.read(1)
+            if c == unichr(0x7f):
+                c = unichr(8)    # map the BS key (which yields DEL) to backspace
+            return c
+
+        def cleanup(self):
+            termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old)
+
+else:
+    raise NotImplementedError("Sorry no implementation for your platform (%s) available." % sys.platform)
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+
+class Transform(object):
+    """do-nothing: forward all data unchanged"""
+    def rx(self, text):
+        """text received from serial port"""
+        return text
+
+    def tx(self, text):
+        """text to be sent to serial port"""
+        return text
+
+    def echo(self, text):
+        """text to be sent but displayed on console"""
+        return text
+
+
+class CRLF(Transform):
+    """ENTER sends CR+LF"""
+
+    def tx(self, text):
+        return text.replace('\n', '\r\n')
+
+
+class CR(Transform):
+    """ENTER sends CR"""
+
+    def rx(self, text):
+        return text.replace('\r', '\n')
+
+    def tx(self, text):
+        return text.replace('\n', '\r')
+
+
+class LF(Transform):
+    """ENTER sends LF"""
+
+
+class NoTerminal(Transform):
+    """remove typical terminal control codes from input"""
+
+    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32) if unichr(x) not in '\r\n\b\t')
+    REPLACEMENT_MAP.update({
+            0x7F: 0x2421,  # DEL
+            0x9B: 0x2425,  # CSI
+            })
+
+    def rx(self, text):
+        return text.translate(self.REPLACEMENT_MAP)
+
+    echo = rx
+
+
+class NoControls(NoTerminal):
+    """Remove all control codes, incl. CR+LF"""
+
+    REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32))
+    REPLACEMENT_MAP.update({
+            32: 0x2423,    # visual space
+            0x7F: 0x2421,  # DEL
+            0x9B: 0x2425,  # CSI
+            })
+
+
+class Printable(Transform):
+    """Show decimal code for all non-ASCII characters and replace most control codes"""
+
+    def rx(self, text):
+        r = []
+        for t in text:
+            if ' ' <= t < '\x7f' or t in '\r\n\b\t':
+                r.append(t)
+            elif t < ' ':
+                r.append(unichr(0x2400 + ord(t)))
+            else:
+                r.extend(unichr(0x2080 + ord(d) - 48) for d in '{:d}'.format(ord(t)))
+                r.append(' ')
+        return ''.join(r)
+
+    echo = rx
+
+
+class Colorize(Transform):
+    """Apply different colors for received and echo"""
+
+    def __init__(self):
+        # XXX make it configurable, use colorama?
+        self.input_color = '\x1b[37m'
+        self.echo_color = '\x1b[31m'
+
+    def rx(self, text):
+        return self.input_color + text
+
+    def echo(self, text):
+        return self.echo_color + text
+
+
+class DebugIO(Transform):
+    """Print what is sent and received"""
+
+    def rx(self, text):
+        sys.stderr.write(' [RX:{}] '.format(repr(text)))
+        sys.stderr.flush()
+        return text
+
+    def tx(self, text):
+        sys.stderr.write(' [TX:{}] '.format(repr(text)))
+        sys.stderr.flush()
+        return text
+
+
+# other ideas:
+# - add date/time for each newline
+# - insert newline after: a) timeout b) packet end character
+
+EOL_TRANSFORMATIONS = {
+        'crlf': CRLF,
+        'cr': CR,
+        'lf': LF,
+        }
+
+TRANSFORMATIONS = {
+        'direct': Transform,    # no transformation
+        'default': NoTerminal,
+        'nocontrol': NoControls,
+        'printable': Printable,
+        'colorize': Colorize,
+        'debug': DebugIO,
+        }
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+def ask_for_port():
+    """\
+    Show a list of ports and ask the user for a choice. To make selection
+    easier on systems with long device names, also allow the input of an
+    index.
+    """
+    sys.stderr.write('\n--- Available ports:\n')
+    ports = []
+    for n, (port, desc, hwid) in enumerate(sorted(comports()), 1):
+        #~ sys.stderr.write('--- %-20s %s [%s]\n' % (port, desc, hwid))
+        sys.stderr.write('--- {:2}: {:20} {}\n'.format(n, port, desc))
+        ports.append(port)
+    while True:
+        port = raw_input('--- Enter port index or full name: ')
+        try:
+            index = int(port) - 1
+            if not 0 <= index < len(ports):
+                sys.stderr.write('--- Invalid index!\n')
+                continue
+        except ValueError:
+            pass
+        else:
+            port = ports[index]
+        return port
+
+
+class Miniterm(object):
+    """\
+    Terminal application. Copy data from serial port to console and vice versa.
+    Handle special keys from the console to show menu etc.
+    """
+
+    def __init__(self, serial_instance, echo=False, eol='crlf', filters=()):
+        self.console = Console()
+        self.serial = serial_instance
+        self.echo = echo
+        self.raw = False
+        self.input_encoding = 'UTF-8'
+        self.output_encoding = 'UTF-8'
+        self.eol = eol
+        self.filters = filters
+        self.update_transformations()
+        self.exit_character = 0x1d  # GS/CTRL+]
+        self.menu_character = 0x14  # Menu: CTRL+T
+
+    def _start_reader(self):
+        """Start reader thread"""
+        self._reader_alive = True
+        # start serial->console thread
+        self.receiver_thread = threading.Thread(target=self.reader, name='rx')
+        self.receiver_thread.daemon = True
+        self.receiver_thread.start()
+
+    def _stop_reader(self):
+        """Stop reader thread only, wait for clean exit of thread"""
+        self._reader_alive = False
+        self.receiver_thread.join()
+
+    def start(self):
+        self.alive = True
+        self._start_reader()
+        # enter console->serial loop
+        self.transmitter_thread = threading.Thread(target=self.writer, name='tx')
+        self.transmitter_thread.daemon = True
+        self.transmitter_thread.start()
+        self.console.setup()
+
+    def stop(self):
+        self.alive = False
+
+    def join(self, transmit_only=False):
+        self.transmitter_thread.join()
+        if not transmit_only:
+            self.receiver_thread.join()
+
+    def update_transformations(self):
+        transformations = [EOL_TRANSFORMATIONS[self.eol]] + [TRANSFORMATIONS[f] for f in self.filters]
+        self.tx_transformations = [t() for t in transformations]
+        self.rx_transformations = list(reversed(self.tx_transformations))
+
+    def set_rx_encoding(self, encoding, errors='replace'):
+        self.input_encoding = encoding
+        self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors)
+
+    def set_tx_encoding(self, encoding, errors='replace'):
+        self.output_encoding = encoding
+        self.tx_encoder = codecs.getincrementalencoder(encoding)(errors)
+
+    def dump_port_settings(self):
+        sys.stderr.write("\n--- Settings: {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format(
+                p=self.serial))
+        sys.stderr.write('--- RTS: {:8}  DTR: {:8}  BREAK: {:8}\n'.format(
+                ('active' if self.serial.rts else 'inactive'),
+                ('active' if self.serial.dtr else 'inactive'),
+                ('active' if self.serial.break_condition else 'inactive')))
+        try:
+            sys.stderr.write('--- CTS: {:8}  DSR: {:8}  RI: {:8}  CD: {:8}\n'.format(
+                    ('active' if self.serial.cts else 'inactive'),
+                    ('active' if self.serial.dsr else 'inactive'),
+                    ('active' if self.serial.ri else 'inactive'),
+                    ('active' if self.serial.cd else 'inactive')))
+        except serial.SerialException:
+            # on RFC 2217 ports, it can happen if no modem state notification was
+            # yet received. ignore this error.
+            pass
+        sys.stderr.write('--- software flow control: {}\n'.format('active' if self.serial.xonxoff else 'inactive'))
+        sys.stderr.write('--- hardware flow control: {}\n'.format('active' if self.serial.rtscts else 'inactive'))
+        #~ sys.stderr.write('--- data escaping: %s  linefeed: %s\n' % (
+                #~ REPR_MODES[self.repr_mode],
+                #~ LF_MODES[self.convert_outgoing]))
+        sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
+        sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
+        sys.stderr.write('--- EOL: {}\n'.format(self.eol.upper()))
+        sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
+
+    def reader(self):
+        """loop and copy serial->console"""
+        try:
+            while self.alive and self._reader_alive:
+                # read all that is there or wait for one byte
+                data = self.serial.read(self.serial.in_waiting or 1)
+                if data:
+                    if self.raw:
+                        self.console.write_bytes(data)
+                    else:
+                        text = self.rx_decoder.decode(data)
+                        for transformation in self.rx_transformations:
+                            text = transformation.rx(text)
+                        self.console.write(text)
+        except serial.SerialException:
+            self.alive = False
+            # XXX would be nice if the writer could be interrupted at this
+            #     point... to exit completely
+            raise
+
+    def writer(self):
+        """\
+        Loop and copy console->serial until self.exit_character character is
+        found. When self.menu_character is found, interpret the next key
+        locally.
+        """
+        menu_active = False
+        try:
+            while self.alive:
+                try:
+                    c = self.console.getkey()
+                except KeyboardInterrupt:
+                    c = '\x03'
+                if menu_active:
+                    self.handle_menu_key(c)
+                    menu_active = False
+                elif c == self.menu_character:
+                    menu_active = True      # next char will be for menu
+                elif c == self.exit_character:
+                    self.stop()             # exit app
+                    break
+                else:
+                    #~ if self.raw:
+                    text = c
+                    for transformation in self.tx_transformations:
+                        text = transformation.tx(text)
+                    self.serial.write(self.tx_encoder.encode(text))
+                    if self.echo:
+                        echo_text = c
+                        for transformation in self.tx_transformations:
+                            echo_text = transformation.echo(echo_text)
+                        self.console.write(echo_text)
+        except:
+            self.alive = False
+            raise
+
+    def handle_menu_key(self, c):
+        """Implement a simple menu / settings"""
+        if c == self.menu_character or c == self.exit_character:
+            # Menu/exit character again -> send itself
+            self.serial.write(self.tx_encoder.encode(c))
+            if self.echo:
+                self.console.write(c)
+        elif c == '\x15':                       # CTRL+U -> upload file
+            sys.stderr.write('\n--- File to upload: ')
+            sys.stderr.flush()
+            with self.console:
+                filename = sys.stdin.readline().rstrip('\r\n')
+                if filename:
+                    try:
+                        with open(filename, 'rb') as f:
+                            sys.stderr.write('--- Sending file {} ---\n'.format(filename))
+                            while True:
+                                block = f.read(1024)
+                                if not block:
+                                    break
+                                self.serial.write(block)
+                                # Wait for output buffer to drain.
+                                self.serial.flush()
+                                sys.stderr.write('.')   # Progress indicator.
+                        sys.stderr.write('\n--- File {} sent ---\n'.format(filename))
+                    except IOError as e:
+                        sys.stderr.write('--- ERROR opening file {}: {} ---\n'.format(filename, e))
+        elif c in '\x08hH?':                    # CTRL+H, h, H, ? -> Show help
+            sys.stderr.write(self.get_help_text())
+        elif c == '\x12':                       # CTRL+R -> Toggle RTS
+            self.serial.rts = not self.serial.rts
+            sys.stderr.write('--- RTS {} ---\n'.format('active' if self.serial.rts else 'inactive'))
+        elif c == '\x04':                       # CTRL+D -> Toggle DTR
+            self.serial.dtr = not self.serial.dtr
+            sys.stderr.write('--- DTR {} ---\n'.format('active' if self.serial.dtr else 'inactive'))
+        elif c == '\x02':                       # CTRL+B -> toggle BREAK condition
+            self.serial.break_condition = not self.serial.break_condition
+            sys.stderr.write('--- BREAK {} ---\n'.format('active' if self.serial.break_condition else 'inactive'))
+        elif c == '\x05':                       # CTRL+E -> toggle local echo
+            self.echo = not self.echo
+            sys.stderr.write('--- local echo {} ---\n'.format('active' if self.echo else 'inactive'))
+        elif c == '\x06':                       # CTRL+F -> edit filters
+            sys.stderr.write('\n--- Available Filters:\n')
+            sys.stderr.write('\n'.join(
+                    '---   {:<10} = {.__doc__}'.format(k, v)
+                    for k, v in sorted(TRANSFORMATIONS.items())))
+            sys.stderr.write('\n--- Enter new filter name(s) [{}]: '.format(' '.join(self.filters)))
+            with self.console:
+                new_filters = sys.stdin.readline().lower().split()
+            if new_filters:
+                for f in new_filters:
+                    if f not in TRANSFORMATIONS:
+                        sys.stderr.write('--- unknown filter: {}'.format(repr(f)))
+                        break
+                else:
+                    self.filters = new_filters
+                    self.update_transformations()
+            sys.stderr.write('--- filters: {}\n'.format(' '.join(self.filters)))
+        elif c == '\x0c':                       # CTRL+L -> EOL mode
+            modes = list(EOL_TRANSFORMATIONS)  # keys
+            eol = modes.index(self.eol) + 1
+            if eol >= len(modes):
+                eol = 0
+            self.eol = modes[eol]
+            sys.stderr.write('--- EOL: {} ---\n'.format(self.eol.upper()))
+            self.update_transformations()
+        elif c == '\x01':                       # CTRL+A -> set encoding
+            sys.stderr.write('\n--- Enter new encoding name [{}]: '.format(self.input_encoding))
+            with self.console:
+                new_encoding = sys.stdin.readline().strip()
+            if new_encoding:
+                try:
+                    codecs.lookup(new_encoding)
+                except LookupError:
+                    sys.stderr.write('--- invalid encoding name: {}\n'.format(new_encoding))
+                else:
+                    self.set_rx_encoding(new_encoding)
+                    self.set_tx_encoding(new_encoding)
+            sys.stderr.write('--- serial input encoding: {}\n'.format(self.input_encoding))
+            sys.stderr.write('--- serial output encoding: {}\n'.format(self.output_encoding))
+        elif c == '\x09':                       # CTRL+I -> info
+            self.dump_port_settings()
+        #~ elif c == '\x01':                       # CTRL+A -> cycle escape mode
+        #~ elif c == '\x0c':                       # CTRL+L -> cycle linefeed mode
+        elif c in 'pP':                         # P -> change port
+            with self.console:
+                try:
+                    port = ask_for_port()
+                except KeyboardInterrupt:
+                    port = None
+            if port and port != self.serial.port:
+                # reader thread needs to be shut down
+                self._stop_reader()
+                # save settings
+                settings = self.serial.getSettingsDict()
+                try:
+                    new_serial = serial.serial_for_url(port, do_not_open=True)
+                    # restore settings and open
+                    new_serial.applySettingsDict(settings)
+                    new_serial.rts = self.serial.rts
+                    new_serial.dtr = self.serial.dtr
+                    new_serial.open()
+                    new_serial.break_condition = self.serial.break_condition
+                except Exception as e:
+                    sys.stderr.write('--- ERROR opening new port: {} ---\n'.format(e))
+                    new_serial.close()
+                else:
+                    self.serial.close()
+                    self.serial = new_serial
+                    sys.stderr.write('--- Port changed to: {} ---\n'.format(self.serial.port))
+                # and restart the reader thread
+                self._start_reader()
+        elif c in 'bB':                         # B -> change baudrate
+            sys.stderr.write('\n--- Baudrate: ')
+            sys.stderr.flush()
+            with self.console:
+                backup = self.serial.baudrate
+                try:
+                    self.serial.baudrate = int(sys.stdin.readline().strip())
+                except ValueError as e:
+                    sys.stderr.write('--- ERROR setting baudrate: %s ---\n'.format(e))
+                    self.serial.baudrate = backup
+                else:
+                    self.dump_port_settings()
+        elif c == '8':                          # 8 -> change to 8 bits
+            self.serial.bytesize = serial.EIGHTBITS
+            self.dump_port_settings()
+        elif c == '7':                          # 7 -> change to 8 bits
+            self.serial.bytesize = serial.SEVENBITS
+            self.dump_port_settings()
+        elif c in 'eE':                         # E -> change to even parity
+            self.serial.parity = serial.PARITY_EVEN
+            self.dump_port_settings()
+        elif c in 'oO':                         # O -> change to odd parity
+            self.serial.parity = serial.PARITY_ODD
+            self.dump_port_settings()
+        elif c in 'mM':                         # M -> change to mark parity
+            self.serial.parity = serial.PARITY_MARK
+            self.dump_port_settings()
+        elif c in 'sS':                         # S -> change to space parity
+            self.serial.parity = serial.PARITY_SPACE
+            self.dump_port_settings()
+        elif c in 'nN':                         # N -> change to no parity
+            self.serial.parity = serial.PARITY_NONE
+            self.dump_port_settings()
+        elif c == '1':                          # 1 -> change to 1 stop bits
+            self.serial.stopbits = serial.STOPBITS_ONE
+            self.dump_port_settings()
+        elif c == '2':                          # 2 -> change to 2 stop bits
+            self.serial.stopbits = serial.STOPBITS_TWO
+            self.dump_port_settings()
+        elif c == '3':                          # 3 -> change to 1.5 stop bits
+            self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE
+            self.dump_port_settings()
+        elif c in 'xX':                         # X -> change software flow control
+            self.serial.xonxoff = (c == 'X')
+            self.dump_port_settings()
+        elif c in 'rR':                         # R -> change hardware flow control
+            self.serial.rtscts = (c == 'R')
+            self.dump_port_settings()
+        else:
+            sys.stderr.write('--- unknown menu character {} --\n'.format(key_description(c)))
+
+    def get_help_text(self):
+        # help text, starts with blank line!
+        return """
+--- pySerial ({version}) - miniterm - help
+---
+--- {exit:8} Exit program
+--- {menu:8} Menu escape key, followed by:
+--- Menu keys:
+---    {menu:7} Send the menu character itself to remote
+---    {exit:7} Send the exit character itself to remote
+---    {info:7} Show info
+---    {upload:7} Upload file (prompt will be shown)
+---    {repr:7} encoding
+---    {filter:7} edit filters
+--- Toggles:
+---    {rts:7} RTS   {dtr:7} DTR   {brk:7} BREAK
+---    {echo:7} echo  {eol:7} EOL
+---
+--- Port settings ({menu} followed by the following):
+---    p          change port
+---    7 8        set data bits
+---    N E O S M  change parity (None, Even, Odd, Space, Mark)
+---    1 2 3      set stop bits (1, 2, 1.5)
+---    b          change baud rate
+---    x X        disable/enable software flow control
+---    r R        disable/enable hardware flow control
+""".format(
+                version=getattr(serial, 'VERSION', 'unknown version'),
+                exit=key_description(self.exit_character),
+                menu=key_description(self.menu_character),
+                rts=key_description('\x12'),
+                dtr=key_description('\x04'),
+                brk=key_description('\x02'),
+                echo=key_description('\x05'),
+                info=key_description('\x09'),
+                upload=key_description('\x15'),
+                repr=key_description('\x01'),
+                filter=key_description('\x06'),
+                eol=key_description('\x0c'),
+                )
+
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# default args can be used to override when calling main() from an other script
+# e.g to create a miniterm-my-device.py
+def main(default_port=None, default_baudrate=9600, default_rts=None, default_dtr=None):
+    import argparse
+
+    parser = argparse.ArgumentParser(
+            description="Miniterm - A simple terminal program for the serial port.")
+
+    parser.add_argument(
+            "port",
+            nargs='?',
+            help="serial port name ('-' to show port list)",
+            default=default_port)
+
+    parser.add_argument(
+            "baudrate",
+            nargs='?',
+            type=int,
+            help="set baud rate, default: %(default)s",
+            default=default_baudrate)
+
+    group = parser.add_argument_group("port settings")
+
+    group.add_argument(
+            "--parity",
+            choices=['N', 'E', 'O', 'S', 'M'],
+            type=lambda c: c.upper(),
+            help="set parity, one of {N E O S M}, default: N",
+            default='N')
+
+    group.add_argument(
+            "--rtscts",
+            action="store_true",
+            help="enable RTS/CTS flow control (default off)",
+            default=False)
+
+    group.add_argument(
+            "--xonxoff",
+            action="store_true",
+            help="enable software flow control (default off)",
+            default=False)
+
+    group.add_argument(
+            "--rts",
+            type=int,
+            help="set initial RTS line state (possible values: 0, 1)",
+            default=default_rts)
+
+    group.add_argument(
+            "--dtr",
+            type=int,
+            help="set initial DTR line state (possible values: 0, 1)",
+            default=default_dtr)
+
+    group.add_argument(
+            "--ask",
+            action="store_true",
+            help="ask again for port when open fails",
+            default=False)
+
+    group = parser.add_argument_group("data handling")
+
+    group.add_argument(
+            "-e", "--echo",
+            action="store_true",
+            help="enable local echo (default off)",
+            default=False)
+
+    group.add_argument(
+            "--encoding",
+            dest="serial_port_encoding",
+            metavar="CODEC",
+            help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s",
+            default='UTF-8')
+
+    group.add_argument(
+            "-f", "--filter",
+            action="append",
+            metavar="NAME",
+            help="add text transformation",
+            default=[])
+
+    group.add_argument(
+            "--eol",
+            choices=['CR', 'LF', 'CRLF'],
+            type=lambda c: c.upper(),
+            help="end of line mode",
+            default='CRLF')
+
+    group.add_argument(
+            "--raw",
+            action="store_true",
+            help="Do no apply any encodings/transformations",
+            default=False)
+
+    group = parser.add_argument_group("hotkeys")
+
+    group.add_argument(
+            "--exit-char",
+            type=int,
+            metavar='NUM',
+            help="Unicode of special character that is used to exit the application, default: %(default)s",
+            default=0x1d  # GS/CTRL+]
+            )
+
+    group.add_argument(
+            "--menu-char",
+            type=int,
+            metavar='NUM',
+            help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s",
+            default=0x14  # Menu: CTRL+T
+            )
+
+    group = parser.add_argument_group("diagnostics")
+
+    group.add_argument(
+            "-q", "--quiet",
+            action="store_true",
+            help="suppress non-error messages",
+            default=False)
+
+    group.add_argument(
+            "--develop",
+            action="store_true",
+            help="show Python traceback on error",
+            default=False)
+
+    args = parser.parse_args()
+
+    if args.menu_char == args.exit_char:
+        parser.error('--exit-char can not be the same as --menu-char')
+
+    if args.filter:
+        if 'help' in args.filter:
+            sys.stderr.write('Available filters:\n')
+            sys.stderr.write('\n'.join(
+                    '{:<10} = {.__doc__}'.format(k, v)
+                    for k, v in sorted(TRANSFORMATIONS.items())))
+            sys.stderr.write('\n')
+            sys.exit(1)
+        filters = args.filter
+    else:
+        filters = ['default']
+
+    while True:
+        # no port given on command line -> ask user now
+        if args.port is None or args.port == '-':
+            try:
+                args.port = ask_for_port()
+            except KeyboardInterrupt:
+                sys.stderr.write('\n')
+                parser.error('user aborted and port is not given')
+            else:
+                if not args.port:
+                    parser.error('port is not given')
+        try:
+            serial_instance = serial.serial_for_url(
+                    args.port,
+                    args.baudrate,
+                    parity=args.parity,
+                    rtscts=args.rtscts,
+                    xonxoff=args.xonxoff,
+                    timeout=1,
+                    do_not_open=True)
+
+            if args.dtr is not None:
+                if not args.quiet:
+                    sys.stderr.write('--- forcing DTR {}\n'.format('active' if args.dtr else 'inactive'))
+                serial_instance.dtr = args.dtr
+            if args.rts is not None:
+                if not args.quiet:
+                    sys.stderr.write('--- forcing RTS {}\n'.format('active' if args.rts else 'inactive'))
+                serial_instance.rts = args.rts
+
+            serial_instance.open()
+        except serial.SerialException as e:
+            sys.stderr.write('could not open port {}: {}\n'.format(repr(args.port), e))
+            if args.develop:
+                raise
+            if not args.ask:
+                sys.exit(1)
+            else:
+                args.port = '-'
+        else:
+            break
+
+    miniterm = Miniterm(
+            serial_instance,
+            echo=args.echo,
+            eol=args.eol.lower(),
+            filters=filters)
+    miniterm.exit_character = unichr(args.exit_char)
+    miniterm.menu_character = unichr(args.menu_char)
+    miniterm.raw = args.raw
+    miniterm.set_rx_encoding(args.serial_port_encoding)
+    miniterm.set_tx_encoding(args.serial_port_encoding)
+
+    if not args.quiet:
+        sys.stderr.write('--- Miniterm on {p.name}  {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n'.format(
+                p=miniterm.serial))
+        sys.stderr.write('--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n'.format(
+                key_description(miniterm.exit_character),
+                key_description(miniterm.menu_character),
+                key_description(miniterm.menu_character),
+                key_description('\x08'),
+                ))
+
+    miniterm.start()
+    try:
+        miniterm.join(True)
+    except KeyboardInterrupt:
+        pass
+    if not args.quiet:
+        sys.stderr.write("\n--- exit ---\n")
+    miniterm.join()
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+if __name__ == '__main__':
+    main()