asyncio: experimental module serial.aio added

non-bloking I/O using asyncio experiment (Python 3.4+ only and currently
also only Posix)
diff --git a/serial/aio.py b/serial/aio.py
new file mode 100644
index 0000000..476959a
--- /dev/null
+++ b/serial/aio.py
@@ -0,0 +1,112 @@
+#!/usr/bin/env python3
+#
+# Python Serial Port Extension for Win32, Linux, BSD, Jython
+# module for serial IO for POSIX compatible systems, like Linux
+# see __init__.py
+#
+# (C) 2015 Chris Liechti <cliechti@gmx.net>
+#
+# SPDX-License-Identifier:    BSD-3-Clause
+"""\
+Support asyncio with serial ports. EXPERIMENTAL
+
+Posix platforms only, Python 3.4+ only.
+
+Windows event loops can not wait for serial ports with the current
+implementation. It should be possible to get that working though.
+"""
+import asyncio
+import serial
+
+class SerialTransport(asyncio.Transport):
+    def __init__(self, loop, protocol, serial_instance):
+        self._loop = loop
+        self._protocol = protocol
+        self.serial = serial_instance
+        self._closing = False
+        self._paused = False
+        # XXX how to support url handlers too
+        self.serial.timeout = 0
+        self.serial.nonblocking()
+        loop.call_soon(protocol.connection_made, self)
+        # only start reading when connection_made() has been called
+        loop.call_soon(loop.add_reader, self.serial.fd, self._read_ready)
+
+    def __repr__(self):
+        return '{self.__class__.__name__}({self._loop}, {self._protocol}, {self.serial})'.format(self=self)
+
+    def close(self):
+        if self._closing:
+            return
+        self._closing = True
+        self._loop.remove_reader(self.serial.fd)
+        self.serial.close()
+        self._loop.call_soon(self._protocol.connection_lost, None)
+
+    def _read_ready(self):
+        data = self.serial.read(1024)
+        if data:
+            self._protocol.data_received(data)
+
+    def write(self, data):
+        self.serial.write(data)
+
+    def can_write_eof(self):
+        return False
+
+    def pause_reading(self):
+        if self._closing:
+            raise RuntimeError('Cannot pause_reading() when closing')
+        if self._paused:
+            raise RuntimeError('Already paused')
+        self._paused = True
+        self._loop.remove_reader(self._sock_fd)
+        if self._loop.get_debug():
+            logger.debug("%r pauses reading", self)
+
+    def resume_reading(self):
+        if not self._paused:
+            raise RuntimeError('Not paused')
+        self._paused = False
+        if self._closing:
+            return
+        self._loop.add_reader(self._sock_fd, self._read_ready)
+        if self._loop.get_debug():
+            logger.debug("%r resumes reading", self)
+
+    #~ def set_write_buffer_limits(self, high=None, low=None):
+    #~ def get_write_buffer_size(self):
+    #~ def writelines(self, list_of_data):
+    #~ def write_eof(self):
+    #~ def abort(self):
+
+@asyncio.coroutine
+def create_serial_connection(loop, protocol_factory, *args, **kwargs):
+    ser = serial.Serial(*args, **kwargs)
+    protocol = protocol_factory()
+    transport = SerialTransport(loop, protocol, ser)
+    return (transport, protocol)
+
+# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
+# test
+if __name__ == '__main__':
+    class Output(asyncio.Protocol):
+        def connection_made(self, transport):
+            self.transport = transport
+            print('port opened', transport)
+            transport.serial.setRTS(0)
+            transport.write(b'hello world\n')
+
+        def data_received(self, data):
+            print('data received', repr(data))
+            self.transport.close()
+
+        def connection_lost(self, exc):
+            print('port closed')
+            asyncio.get_event_loop().stop()
+
+    loop = asyncio.get_event_loop()
+    coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
+    loop.run_until_complete(coro)
+    loop.run_forever()
+    loop.close()