fix EOL issues
diff --git a/examples/port_publisher.py b/examples/port_publisher.py
index d426da0..074a529 100755
--- a/examples/port_publisher.py
+++ b/examples/port_publisher.py
@@ -1,570 +1,570 @@
-#! /usr/bin/env python

-#

-# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>

-#

-# SPDX-License-Identifier:    BSD-3-Clause

-"""\

-Multi-port serial<->TCP/IP forwarder.

-- RFC 2217

-- check existence of serial port periodically

-- start/stop forwarders

-- each forwarder creates a server socket and opens the serial port

-- serial ports are opened only once. network connect/disconnect

-  does not influence serial port

-- only one client per connection

-"""

-import os

-import select

-import socket

-import sys

-import time

-import traceback

-

-import serial

-import serial.rfc2217

-import serial.tools.list_ports

-

-import dbus

-

-# Try to import the avahi service definitions properly. If the avahi module is

-# not available, fall back to a hard-coded solution that hopefully still works.

-try:

-    import avahi

-except ImportError:

-    class avahi:

-        DBUS_NAME = "org.freedesktop.Avahi"

-        DBUS_PATH_SERVER = "/"

-        DBUS_INTERFACE_SERVER = "org.freedesktop.Avahi.Server"

-        DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"

-        IF_UNSPEC = -1

-        PROTO_UNSPEC, PROTO_INET, PROTO_INET6 = -1, 0, 1

-

-

-class ZeroconfService:

-    """\

-    A simple class to publish a network service with zeroconf using avahi.

-    """

-

-    def __init__(self, name, port, stype="_http._tcp",

-                 domain="", host="", text=""):

-        self.name = name

-        self.stype = stype

-        self.domain = domain

-        self.host = host

-        self.port = port

-        self.text = text

-        self.group = None

-

-    def publish(self):

-        bus = dbus.SystemBus()

-        server = dbus.Interface(

-            bus.get_object(

-                avahi.DBUS_NAME,

-                avahi.DBUS_PATH_SERVER

-            ),

-            avahi.DBUS_INTERFACE_SERVER

-        )

-

-        g = dbus.Interface(

-            bus.get_object(

-                avahi.DBUS_NAME,

-                server.EntryGroupNew()

-            ),

-            avahi.DBUS_INTERFACE_ENTRY_GROUP

-        )

-

-        g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),

-                     self.name, self.stype, self.domain, self.host,

-                     dbus.UInt16(self.port), self.text)

-

-        g.Commit()

-        self.group = g

-

-    def unpublish(self):

-        if self.group is not None:

-            self.group.Reset()

-            self.group = None

-

-    def __str__(self):

-        return "%r @ %s:%s (%s)" % (self.name, self.host, self.port, self.stype)

-

-

-class Forwarder(ZeroconfService):

-    """\

-    Single port serial<->TCP/IP forarder that depends on an external select

-    loop.

-    - Buffers for serial -> network and network -> serial

-    - RFC 2217 state

-    - Zeroconf publish/unpublish on open/close.

-    """

-

-    def __init__(self, device, name, network_port, on_close=None, log=None):

-        ZeroconfService.__init__(self, name, network_port, stype='_serial_port._tcp')

-        self.alive = False

-        self.network_port = network_port

-        self.on_close = on_close

-        self.log = log

-        self.device = device

-        self.serial = serial.Serial()

-        self.serial.port = device

-        self.serial.baudrate = 115200

-        self.serial.timeout = 0

-        self.socket = None

-        self.server_socket = None

-        self.rfc2217 = None  # instantiate later, when connecting

-

-    def __del__(self):

-        try:

-            if self.alive:

-                self.close()

-        except:

-            pass  # XXX errors on shutdown

-

-    def open(self):

-        """open serial port, start network server and publish service"""

-        self.buffer_net2ser = bytearray()

-        self.buffer_ser2net = bytearray()

-

-        # open serial port

-        try:

-            self.serial.rts = False

-            self.serial.open()

-        except Exception as msg:

-            self.handle_serial_error(msg)

-

-        self.serial_settings_backup = self.serial.get_settings()

-

-        # start the socket server

-        # XXX add IPv6 support: use getaddrinfo for socket options, bind to multiple sockets?

-        #       info_list = socket.getaddrinfo(None, port, 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)

-        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

-        self.server_socket.setsockopt(

-            socket.SOL_SOCKET,

-            socket.SO_REUSEADDR,

-            self.server_socket.getsockopt(

-                socket.SOL_SOCKET,

-                socket.SO_REUSEADDR

-            ) | 1

-        )

-        self.server_socket.setblocking(0)

-        try:

-            self.server_socket.bind(('', self.network_port))

-            self.server_socket.listen(1)

-        except socket.error as msg:

-            self.handle_server_error()

-            #~ raise

-        if self.log is not None:

-            self.log.info("%s: Waiting for connection on %s..." % (self.device, self.network_port))

-

-        # zeroconfig

-        self.publish()

-

-        # now we are ready

-        self.alive = True

-

-    def close(self):

-        """Close all resources and unpublish service"""

-        if self.log is not None:

-            self.log.info("%s: closing..." % (self.device, ))

-        self.alive = False

-        self.unpublish()

-        if self.server_socket:

-            self.server_socket.close()

-        if self.socket:

-            self.handle_disconnect()

-        self.serial.close()

-        if self.on_close is not None:

-            # ensure it is only called once

-            callback = self.on_close

-            self.on_close = None

-            callback(self)

-

-    def write(self, data):

-        """the write method is used by serial.rfc2217.PortManager. it has to

-        write to the network."""

-        self.buffer_ser2net += data

-

-    def update_select_maps(self, read_map, write_map, error_map):

-        """Update dictionaries for select call. insert fd->callback mapping"""

-        if self.alive:

-            # always handle serial port reads

-            read_map[self.serial] = self.handle_serial_read

-            error_map[self.serial] = self.handle_serial_error

-            # handle serial port writes if buffer is not empty

-            if self.buffer_net2ser:

-                write_map[self.serial] = self.handle_serial_write

-            # handle network

-            if self.socket is not None:

-                # handle socket if connected

-                # only read from network if the internal buffer is not

-                # already filled. the TCP flow control will hold back data

-                if len(self.buffer_net2ser) < 2048:

-                    read_map[self.socket] = self.handle_socket_read

-                # only check for write readiness when there is data

-                if self.buffer_ser2net:

-                    write_map[self.socket] = self.handle_socket_write

-                error_map[self.socket] = self.handle_socket_error

-            else:

-                # no connection, ensure clear buffer

-                self.buffer_ser2net = bytearray()

-            # check the server socket

-            read_map[self.server_socket] = self.handle_connect

-            error_map[self.server_socket] = self.handle_server_error

-

-    def handle_serial_read(self):

-        """Reading from serial port"""

-        try:

-            data = os.read(self.serial.fileno(), 1024)

-            if data:

-                # store data in buffer if there is a client connected

-                if self.socket is not None:

-                    # escape outgoing data when needed (Telnet IAC (0xff) character)

-                    if self.rfc2217:

-                        data = serial.to_bytes(self.rfc2217.escape(data))

-                    self.buffer_ser2net += data

-            else:

-                self.handle_serial_error()

-        except Exception as msg:

-            self.handle_serial_error(msg)

-

-    def handle_serial_write(self):

-        """Writing to serial port"""

-        try:

-            # write a chunk

-            n = os.write(self.serial.fileno(), bytes(self.buffer_net2ser))

-            # and see how large that chunk was, remove that from buffer

-            self.buffer_net2ser = self.buffer_net2ser[n:]

-        except Exception as msg:

-            self.handle_serial_error(msg)

-

-    def handle_serial_error(self, error=None):

-        """Serial port error"""

-        # terminate connection

-        self.close()

-

-    def handle_socket_read(self):

-        """Read from socket"""

-        try:

-            # read a chunk from the serial port

-            data = self.socket.recv(1024)

-            if data:

-                # Process RFC 2217 stuff when enabled

-                if self.rfc2217:

-                    data = serial.to_bytes(self.rfc2217.filter(data))

-                # add data to buffer

-                self.buffer_net2ser += data

-            else:

-                # empty read indicates disconnection

-                self.handle_disconnect()

-        except socket.error:

-            self.handle_socket_error()

-

-    def handle_socket_write(self):

-        """Write to socket"""

-        try:

-            # write a chunk

-            count = self.socket.send(bytes(self.buffer_ser2net))

-            # and remove the sent data from the buffer

-            self.buffer_ser2net = self.buffer_ser2net[count:]

-        except socket.error:

-            self.handle_socket_error()

-

-    def handle_socket_error(self):

-        """Socket connection fails"""

-        self.handle_disconnect()

-

-    def handle_connect(self):

-        """Server socket gets a connection"""

-        # accept a connection in any case, close connection

-        # below if already busy

-        connection, addr = self.server_socket.accept()

-        if self.socket is None:

-            self.socket = connection

-            # More quickly detect bad clients who quit without closing the

-            # connection: After 1 second of idle, start sending TCP keep-alive

-            # packets every 1 second. If 3 consecutive keep-alive packets

-            # fail, assume the client is gone and close the connection.

-            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)

-            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)

-            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)

-            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)

-            self.socket.setblocking(0)

-            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

-            if self.log is not None:

-                self.log.warning('%s: Connected by %s:%s' % (self.device, addr[0], addr[1]))

-            self.serial.rts = True

-            self.serial.dtr = True

-            if self.log is not None:

-                self.rfc2217 = serial.rfc2217.PortManager(self.serial, self, logger=log.getChild(self.device))

-            else:

-                self.rfc2217 = serial.rfc2217.PortManager(self.serial, self)

-        else:

-            # reject connection if there is already one

-            connection.close()

-            if self.log is not None:

-                self.log.warning('%s: Rejecting connect from %s:%s' % (self.device, addr[0], addr[1]))

-

-    def handle_server_error(self):

-        """Socket server fails"""

-        self.close()

-

-    def handle_disconnect(self):

-        """Socket gets disconnected"""

-        # signal disconnected terminal with control lines

-        try:

-            self.serial.rts = False

-            self.serial.dtr = False

-        finally:

-            # restore original port configuration in case it was changed

-            self.serial.apply_settings(self.serial_settings_backup)

-            # stop RFC 2217 state machine

-            self.rfc2217 = None

-            # clear send buffer

-            self.buffer_ser2net = bytearray()

-            # close network connection

-            if self.socket is not None:

-                self.socket.close()

-                self.socket = None

-                if self.log is not None:

-                    self.log.warning('%s: Disconnected' % self.device)

-

-

-def test():

-    service = ZeroconfService(name="TestService", port=3000)

-    service.publish()

-    raw_input("Press any key to unpublish the service ")

-    service.unpublish()

-

-

-if __name__ == '__main__':

-    import logging

-    import argparse

-

-    VERBOSTIY = [

-            logging.ERROR,      # 0

-            logging.WARNING,    # 1 (default)

-            logging.INFO,       # 2

-            logging.DEBUG,      # 3

-            ]

-

-    parser = argparse.ArgumentParser(usage="""\

-%(prog)s [options]

-

-Announce the existence of devices using zeroconf and provide

-a TCP/IP <-> serial port gateway (implements RFC 2217).

-

-If running as daemon, write to syslog. Otherwise write to stdout.

-""",

-        epilog="""\

-NOTE: no security measures are implemented. Anyone can remotely connect

-to this service over the network.

-

-Only one connection at once, per port, is supported. When the connection is

-terminated, it waits for the next connect.

-""")

-

-    group = parser.add_argument_group("serial port settings")

-

-    group.add_argument(

-            "--ports-regex",

-            help="specify a regex to search against the serial devices and their descriptions (default: %(default)s)",

-            default='/dev/ttyUSB[0-9]+',

-            metavar="REGEX")

-

-    group = parser.add_argument_group("network settings")

-

-    group.add_argument(

-            "--tcp-port",

-            dest="base_port",

-            help="specify lowest TCP port number (default: %(default)s)",

-            default=7000,

-            type=int,

-            metavar="PORT")

-

-    group = parser.add_argument_group("daemon")

-

-    group.add_argument(

-            "-d", "--daemon",

-            dest="daemonize",

-            action="store_true",

-            help="start as daemon",

-            default=False)

-

-    group.add_argument(

-            "--pidfile",

-            help="specify a name for the PID file",

-            default=None,

-            metavar="FILE")

-

-    group = parser.add_argument_group("diagnostics")

-

-    group.add_argument(

-            "-o", "--logfile",

-            help="write messages file instead of stdout",

-            default=None,

-            metavar="FILE")

-

-    group.add_argument(

-            "-q", "--quiet",

-            dest="verbosity",

-            action="store_const",

-            const=0,

-            help="suppress most diagnostic messages",

-            default=1)

-

-    group.add_argument(

-            "-v", "--verbose",

-            dest="verbosity",

-            action="count",

-            help="increase diagnostic messages")

-

-

-    args = parser.parse_args()

-

-    # set up logging

-    logging.basicConfig(level=VERBOSTIY[min(args.verbosity, len(VERBOSTIY) - 1)])

-    log = logging.getLogger('port_publisher')

-

-    # redirect output if specified

-    if args.logfile is not None:

-        class WriteFlushed:

-            def __init__(self, fileobj):

-                self.fileobj = fileobj

-            def write(self, s):

-                self.fileobj.write(s)

-                self.fileobj.flush()

-            def close(self):

-                self.fileobj.close()

-        sys.stdout = sys.stderr = WriteFlushed(open(args.logfile, 'a'))

-        # atexit.register(lambda: sys.stdout.close())

-

-    if args.daemonize:

-        # if running as daemon is requested, do the fork magic

-        # args.quiet = True

-        # do the UNIX double-fork magic, see Stevens' "Advanced

-        # Programming in the UNIX Environment" for details (ISBN 0201563177)

-        try:

-            pid = os.fork()

-            if pid > 0:

-                # exit first parent

-                sys.exit(0)

-        except OSError as e:

-            log.critical("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))

-            sys.exit(1)

-

-        # decouple from parent environment

-        os.chdir("/")   # don't prevent unmounting....

-        os.setsid()

-        os.umask(0)

-

-        # do second fork

-        try:

-            pid = os.fork()

-            if pid > 0:

-                # exit from second parent, save eventual PID before

-                if args.pidfile is not None:

-                    open(args.pidfile, 'w').write("%d" % pid)

-                sys.exit(0)

-        except OSError as e:

-            log.critical("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))

-            sys.exit(1)

-

-        if args.logfile is None:

-            import syslog

-            syslog.openlog("serial port publisher")

-            # redirect output to syslog

-            class WriteToSysLog:

-                def __init__(self):

-                    self.buffer = ''

-                def write(self, s):

-                    self.buffer += s

-                    if '\n' in self.buffer:

-                        output, self.buffer = self.buffer.split('\n', 1)

-                        syslog.syslog(output)

-                def flush(self):

-                    syslog.syslog(self.buffer)

-                    self.buffer = ''

-                def close(self):

-                    self.flush()

-            sys.stdout = sys.stderr = WriteToSysLog()

-

-            # ensure the that the daemon runs a normal user, if run as root

-        #if os.getuid() == 0:

-            #    name, passwd, uid, gid, desc, home, shell = pwd.getpwnam('someuser')

-            #    os.setgid(gid)     # set group first

-            #    os.setuid(uid)     # set user

-

-    # keep the published stuff in a dictionary

-    published = {}

-    # get a nice hostname

-    hostname = socket.gethostname()

-

-    def unpublish(forwarder):

-        """when forwarders die, we need to unregister them"""

-        try:

-            del published[forwarder.device]

-        except KeyError:

-            pass

-        else:

-            log.info("unpublish: %s" % (forwarder))

-

-    alive = True

-    next_check = 0

-    # main loop

-    while alive:

-        try:

-            # if it is time, check for serial port devices

-            now = time.time()

-            if now > next_check:

-                next_check = now + 5

-                connected = [d for d, p, i in serial.tools.list_ports.grep(args.ports_regex)]

-                # Handle devices that are published, but no longer connected

-                for device in set(published).difference(connected):

-                    log.info("unpublish: %s" % (published[device]))

-                    unpublish(published[device])

-                # Handle devices that are connected but not yet published

-                for device in set(connected).difference(published):

-                    # Find the first available port, starting from 7000

-                    port = args.base_port

-                    ports_in_use = [f.network_port for f in published.values()]

-                    while port in ports_in_use:

-                        port += 1

-                    published[device] = Forwarder(

-                            device,

-                            "%s on %s" % (device, hostname),

-                            port,

-                            on_close=unpublish,

-                            log=log

-                    )

-                    log.warning("publish: %s" % (published[device]))

-                    published[device].open()

-

-            # select_start = time.time()

-            read_map = {}

-            write_map = {}

-            error_map = {}

-            for publisher in published.values():

-                publisher.update_select_maps(read_map, write_map, error_map)

-            readers, writers, errors = select.select(

-                    read_map.keys(),

-                    write_map.keys(),

-                    error_map.keys(),

-                    5

-            )

-            # select_end = time.time()

-            # print "select used %.3f s" % (select_end - select_start)

-            for reader in readers:

-                read_map[reader]()

-            for writer in writers:

-                write_map[writer]()

-            for error in errors:

-                error_map[error]()

-            # print "operation used %.3f s" % (time.time() - select_end)

-        except KeyboardInterrupt:

-            alive = False

-            sys.stdout.write('\n')

-        except SystemExit:

-            raise

-        except:

-            #~ raise

-            traceback.print_exc()

+#! /usr/bin/env python
+#
+# (C) 2001-2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+"""\
+Multi-port serial<->TCP/IP forwarder.
+- RFC 2217
+- check existence of serial port periodically
+- start/stop forwarders
+- each forwarder creates a server socket and opens the serial port
+- serial ports are opened only once. network connect/disconnect
+  does not influence serial port
+- only one client per connection
+"""
+import os
+import select
+import socket
+import sys
+import time
+import traceback
+
+import serial
+import serial.rfc2217
+import serial.tools.list_ports
+
+import dbus
+
+# Try to import the avahi service definitions properly. If the avahi module is
+# not available, fall back to a hard-coded solution that hopefully still works.
+try:
+    import avahi
+except ImportError:
+    class avahi:
+        DBUS_NAME = "org.freedesktop.Avahi"
+        DBUS_PATH_SERVER = "/"
+        DBUS_INTERFACE_SERVER = "org.freedesktop.Avahi.Server"
+        DBUS_INTERFACE_ENTRY_GROUP = DBUS_NAME + ".EntryGroup"
+        IF_UNSPEC = -1
+        PROTO_UNSPEC, PROTO_INET, PROTO_INET6 = -1, 0, 1
+
+
+class ZeroconfService:
+    """\
+    A simple class to publish a network service with zeroconf using avahi.
+    """
+
+    def __init__(self, name, port, stype="_http._tcp",
+                 domain="", host="", text=""):
+        self.name = name
+        self.stype = stype
+        self.domain = domain
+        self.host = host
+        self.port = port
+        self.text = text
+        self.group = None
+
+    def publish(self):
+        bus = dbus.SystemBus()
+        server = dbus.Interface(
+            bus.get_object(
+                avahi.DBUS_NAME,
+                avahi.DBUS_PATH_SERVER
+            ),
+            avahi.DBUS_INTERFACE_SERVER
+        )
+
+        g = dbus.Interface(
+            bus.get_object(
+                avahi.DBUS_NAME,
+                server.EntryGroupNew()
+            ),
+            avahi.DBUS_INTERFACE_ENTRY_GROUP
+        )
+
+        g.AddService(avahi.IF_UNSPEC, avahi.PROTO_UNSPEC, dbus.UInt32(0),
+                     self.name, self.stype, self.domain, self.host,
+                     dbus.UInt16(self.port), self.text)
+
+        g.Commit()
+        self.group = g
+
+    def unpublish(self):
+        if self.group is not None:
+            self.group.Reset()
+            self.group = None
+
+    def __str__(self):
+        return "%r @ %s:%s (%s)" % (self.name, self.host, self.port, self.stype)
+
+
+class Forwarder(ZeroconfService):
+    """\
+    Single port serial<->TCP/IP forarder that depends on an external select
+    loop.
+    - Buffers for serial -> network and network -> serial
+    - RFC 2217 state
+    - Zeroconf publish/unpublish on open/close.
+    """
+
+    def __init__(self, device, name, network_port, on_close=None, log=None):
+        ZeroconfService.__init__(self, name, network_port, stype='_serial_port._tcp')
+        self.alive = False
+        self.network_port = network_port
+        self.on_close = on_close
+        self.log = log
+        self.device = device
+        self.serial = serial.Serial()
+        self.serial.port = device
+        self.serial.baudrate = 115200
+        self.serial.timeout = 0
+        self.socket = None
+        self.server_socket = None
+        self.rfc2217 = None  # instantiate later, when connecting
+
+    def __del__(self):
+        try:
+            if self.alive:
+                self.close()
+        except:
+            pass  # XXX errors on shutdown
+
+    def open(self):
+        """open serial port, start network server and publish service"""
+        self.buffer_net2ser = bytearray()
+        self.buffer_ser2net = bytearray()
+
+        # open serial port
+        try:
+            self.serial.rts = False
+            self.serial.open()
+        except Exception as msg:
+            self.handle_serial_error(msg)
+
+        self.serial_settings_backup = self.serial.get_settings()
+
+        # start the socket server
+        # XXX add IPv6 support: use getaddrinfo for socket options, bind to multiple sockets?
+        #       info_list = socket.getaddrinfo(None, port, 0, socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
+        self.server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.server_socket.setsockopt(
+            socket.SOL_SOCKET,
+            socket.SO_REUSEADDR,
+            self.server_socket.getsockopt(
+                socket.SOL_SOCKET,
+                socket.SO_REUSEADDR
+            ) | 1
+        )
+        self.server_socket.setblocking(0)
+        try:
+            self.server_socket.bind(('', self.network_port))
+            self.server_socket.listen(1)
+        except socket.error as msg:
+            self.handle_server_error()
+            #~ raise
+        if self.log is not None:
+            self.log.info("%s: Waiting for connection on %s..." % (self.device, self.network_port))
+
+        # zeroconfig
+        self.publish()
+
+        # now we are ready
+        self.alive = True
+
+    def close(self):
+        """Close all resources and unpublish service"""
+        if self.log is not None:
+            self.log.info("%s: closing..." % (self.device, ))
+        self.alive = False
+        self.unpublish()
+        if self.server_socket:
+            self.server_socket.close()
+        if self.socket:
+            self.handle_disconnect()
+        self.serial.close()
+        if self.on_close is not None:
+            # ensure it is only called once
+            callback = self.on_close
+            self.on_close = None
+            callback(self)
+
+    def write(self, data):
+        """the write method is used by serial.rfc2217.PortManager. it has to
+        write to the network."""
+        self.buffer_ser2net += data
+
+    def update_select_maps(self, read_map, write_map, error_map):
+        """Update dictionaries for select call. insert fd->callback mapping"""
+        if self.alive:
+            # always handle serial port reads
+            read_map[self.serial] = self.handle_serial_read
+            error_map[self.serial] = self.handle_serial_error
+            # handle serial port writes if buffer is not empty
+            if self.buffer_net2ser:
+                write_map[self.serial] = self.handle_serial_write
+            # handle network
+            if self.socket is not None:
+                # handle socket if connected
+                # only read from network if the internal buffer is not
+                # already filled. the TCP flow control will hold back data
+                if len(self.buffer_net2ser) < 2048:
+                    read_map[self.socket] = self.handle_socket_read
+                # only check for write readiness when there is data
+                if self.buffer_ser2net:
+                    write_map[self.socket] = self.handle_socket_write
+                error_map[self.socket] = self.handle_socket_error
+            else:
+                # no connection, ensure clear buffer
+                self.buffer_ser2net = bytearray()
+            # check the server socket
+            read_map[self.server_socket] = self.handle_connect
+            error_map[self.server_socket] = self.handle_server_error
+
+    def handle_serial_read(self):
+        """Reading from serial port"""
+        try:
+            data = os.read(self.serial.fileno(), 1024)
+            if data:
+                # store data in buffer if there is a client connected
+                if self.socket is not None:
+                    # escape outgoing data when needed (Telnet IAC (0xff) character)
+                    if self.rfc2217:
+                        data = serial.to_bytes(self.rfc2217.escape(data))
+                    self.buffer_ser2net += data
+            else:
+                self.handle_serial_error()
+        except Exception as msg:
+            self.handle_serial_error(msg)
+
+    def handle_serial_write(self):
+        """Writing to serial port"""
+        try:
+            # write a chunk
+            n = os.write(self.serial.fileno(), bytes(self.buffer_net2ser))
+            # and see how large that chunk was, remove that from buffer
+            self.buffer_net2ser = self.buffer_net2ser[n:]
+        except Exception as msg:
+            self.handle_serial_error(msg)
+
+    def handle_serial_error(self, error=None):
+        """Serial port error"""
+        # terminate connection
+        self.close()
+
+    def handle_socket_read(self):
+        """Read from socket"""
+        try:
+            # read a chunk from the serial port
+            data = self.socket.recv(1024)
+            if data:
+                # Process RFC 2217 stuff when enabled
+                if self.rfc2217:
+                    data = serial.to_bytes(self.rfc2217.filter(data))
+                # add data to buffer
+                self.buffer_net2ser += data
+            else:
+                # empty read indicates disconnection
+                self.handle_disconnect()
+        except socket.error:
+            self.handle_socket_error()
+
+    def handle_socket_write(self):
+        """Write to socket"""
+        try:
+            # write a chunk
+            count = self.socket.send(bytes(self.buffer_ser2net))
+            # and remove the sent data from the buffer
+            self.buffer_ser2net = self.buffer_ser2net[count:]
+        except socket.error:
+            self.handle_socket_error()
+
+    def handle_socket_error(self):
+        """Socket connection fails"""
+        self.handle_disconnect()
+
+    def handle_connect(self):
+        """Server socket gets a connection"""
+        # accept a connection in any case, close connection
+        # below if already busy
+        connection, addr = self.server_socket.accept()
+        if self.socket is None:
+            self.socket = connection
+            # More quickly detect bad clients who quit without closing the
+            # connection: After 1 second of idle, start sending TCP keep-alive
+            # packets every 1 second. If 3 consecutive keep-alive packets
+            # fail, assume the client is gone and close the connection.
+            self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)
+            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 1)
+            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 1)
+            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 3)
+            self.socket.setblocking(0)
+            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
+            if self.log is not None:
+                self.log.warning('%s: Connected by %s:%s' % (self.device, addr[0], addr[1]))
+            self.serial.rts = True
+            self.serial.dtr = True
+            if self.log is not None:
+                self.rfc2217 = serial.rfc2217.PortManager(self.serial, self, logger=log.getChild(self.device))
+            else:
+                self.rfc2217 = serial.rfc2217.PortManager(self.serial, self)
+        else:
+            # reject connection if there is already one
+            connection.close()
+            if self.log is not None:
+                self.log.warning('%s: Rejecting connect from %s:%s' % (self.device, addr[0], addr[1]))
+
+    def handle_server_error(self):
+        """Socket server fails"""
+        self.close()
+
+    def handle_disconnect(self):
+        """Socket gets disconnected"""
+        # signal disconnected terminal with control lines
+        try:
+            self.serial.rts = False
+            self.serial.dtr = False
+        finally:
+            # restore original port configuration in case it was changed
+            self.serial.apply_settings(self.serial_settings_backup)
+            # stop RFC 2217 state machine
+            self.rfc2217 = None
+            # clear send buffer
+            self.buffer_ser2net = bytearray()
+            # close network connection
+            if self.socket is not None:
+                self.socket.close()
+                self.socket = None
+                if self.log is not None:
+                    self.log.warning('%s: Disconnected' % self.device)
+
+
+def test():
+    service = ZeroconfService(name="TestService", port=3000)
+    service.publish()
+    raw_input("Press any key to unpublish the service ")
+    service.unpublish()
+
+
+if __name__ == '__main__':
+    import logging
+    import argparse
+
+    VERBOSTIY = [
+            logging.ERROR,      # 0
+            logging.WARNING,    # 1 (default)
+            logging.INFO,       # 2
+            logging.DEBUG,      # 3
+            ]
+
+    parser = argparse.ArgumentParser(usage="""\
+%(prog)s [options]
+
+Announce the existence of devices using zeroconf and provide
+a TCP/IP <-> serial port gateway (implements RFC 2217).
+
+If running as daemon, write to syslog. Otherwise write to stdout.
+""",
+        epilog="""\
+NOTE: no security measures are implemented. Anyone can remotely connect
+to this service over the network.
+
+Only one connection at once, per port, is supported. When the connection is
+terminated, it waits for the next connect.
+""")
+
+    group = parser.add_argument_group("serial port settings")
+
+    group.add_argument(
+            "--ports-regex",
+            help="specify a regex to search against the serial devices and their descriptions (default: %(default)s)",
+            default='/dev/ttyUSB[0-9]+',
+            metavar="REGEX")
+
+    group = parser.add_argument_group("network settings")
+
+    group.add_argument(
+            "--tcp-port",
+            dest="base_port",
+            help="specify lowest TCP port number (default: %(default)s)",
+            default=7000,
+            type=int,
+            metavar="PORT")
+
+    group = parser.add_argument_group("daemon")
+
+    group.add_argument(
+            "-d", "--daemon",
+            dest="daemonize",
+            action="store_true",
+            help="start as daemon",
+            default=False)
+
+    group.add_argument(
+            "--pidfile",
+            help="specify a name for the PID file",
+            default=None,
+            metavar="FILE")
+
+    group = parser.add_argument_group("diagnostics")
+
+    group.add_argument(
+            "-o", "--logfile",
+            help="write messages file instead of stdout",
+            default=None,
+            metavar="FILE")
+
+    group.add_argument(
+            "-q", "--quiet",
+            dest="verbosity",
+            action="store_const",
+            const=0,
+            help="suppress most diagnostic messages",
+            default=1)
+
+    group.add_argument(
+            "-v", "--verbose",
+            dest="verbosity",
+            action="count",
+            help="increase diagnostic messages")
+
+
+    args = parser.parse_args()
+
+    # set up logging
+    logging.basicConfig(level=VERBOSTIY[min(args.verbosity, len(VERBOSTIY) - 1)])
+    log = logging.getLogger('port_publisher')
+
+    # redirect output if specified
+    if args.logfile is not None:
+        class WriteFlushed:
+            def __init__(self, fileobj):
+                self.fileobj = fileobj
+            def write(self, s):
+                self.fileobj.write(s)
+                self.fileobj.flush()
+            def close(self):
+                self.fileobj.close()
+        sys.stdout = sys.stderr = WriteFlushed(open(args.logfile, 'a'))
+        # atexit.register(lambda: sys.stdout.close())
+
+    if args.daemonize:
+        # if running as daemon is requested, do the fork magic
+        # args.quiet = True
+        # do the UNIX double-fork magic, see Stevens' "Advanced
+        # Programming in the UNIX Environment" for details (ISBN 0201563177)
+        try:
+            pid = os.fork()
+            if pid > 0:
+                # exit first parent
+                sys.exit(0)
+        except OSError as e:
+            log.critical("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror))
+            sys.exit(1)
+
+        # decouple from parent environment
+        os.chdir("/")   # don't prevent unmounting....
+        os.setsid()
+        os.umask(0)
+
+        # do second fork
+        try:
+            pid = os.fork()
+            if pid > 0:
+                # exit from second parent, save eventual PID before
+                if args.pidfile is not None:
+                    open(args.pidfile, 'w').write("%d" % pid)
+                sys.exit(0)
+        except OSError as e:
+            log.critical("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror))
+            sys.exit(1)
+
+        if args.logfile is None:
+            import syslog
+            syslog.openlog("serial port publisher")
+            # redirect output to syslog
+            class WriteToSysLog:
+                def __init__(self):
+                    self.buffer = ''
+                def write(self, s):
+                    self.buffer += s
+                    if '\n' in self.buffer:
+                        output, self.buffer = self.buffer.split('\n', 1)
+                        syslog.syslog(output)
+                def flush(self):
+                    syslog.syslog(self.buffer)
+                    self.buffer = ''
+                def close(self):
+                    self.flush()
+            sys.stdout = sys.stderr = WriteToSysLog()
+
+            # ensure the that the daemon runs a normal user, if run as root
+        #if os.getuid() == 0:
+            #    name, passwd, uid, gid, desc, home, shell = pwd.getpwnam('someuser')
+            #    os.setgid(gid)     # set group first
+            #    os.setuid(uid)     # set user
+
+    # keep the published stuff in a dictionary
+    published = {}
+    # get a nice hostname
+    hostname = socket.gethostname()
+
+    def unpublish(forwarder):
+        """when forwarders die, we need to unregister them"""
+        try:
+            del published[forwarder.device]
+        except KeyError:
+            pass
+        else:
+            log.info("unpublish: %s" % (forwarder))
+
+    alive = True
+    next_check = 0
+    # main loop
+    while alive:
+        try:
+            # if it is time, check for serial port devices
+            now = time.time()
+            if now > next_check:
+                next_check = now + 5
+                connected = [d for d, p, i in serial.tools.list_ports.grep(args.ports_regex)]
+                # Handle devices that are published, but no longer connected
+                for device in set(published).difference(connected):
+                    log.info("unpublish: %s" % (published[device]))
+                    unpublish(published[device])
+                # Handle devices that are connected but not yet published
+                for device in set(connected).difference(published):
+                    # Find the first available port, starting from 7000
+                    port = args.base_port
+                    ports_in_use = [f.network_port for f in published.values()]
+                    while port in ports_in_use:
+                        port += 1
+                    published[device] = Forwarder(
+                            device,
+                            "%s on %s" % (device, hostname),
+                            port,
+                            on_close=unpublish,
+                            log=log
+                    )
+                    log.warning("publish: %s" % (published[device]))
+                    published[device].open()
+
+            # select_start = time.time()
+            read_map = {}
+            write_map = {}
+            error_map = {}
+            for publisher in published.values():
+                publisher.update_select_maps(read_map, write_map, error_map)
+            readers, writers, errors = select.select(
+                    read_map.keys(),
+                    write_map.keys(),
+                    error_map.keys(),
+                    5
+            )
+            # select_end = time.time()
+            # print "select used %.3f s" % (select_end - select_start)
+            for reader in readers:
+                read_map[reader]()
+            for writer in writers:
+                write_map[writer]()
+            for error in errors:
+                error_map[error]()
+            # print "operation used %.3f s" % (time.time() - select_end)
+        except KeyboardInterrupt:
+            alive = False
+            sys.stdout.write('\n')
+        except SystemExit:
+            raise
+        except:
+            #~ raise
+            traceback.print_exc()