Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
| 3 | # Python Serial Port Extension for Win32, Linux, BSD, Jython |
| 4 | # module for serial IO for POSIX compatible systems, like Linux |
| 5 | # see __init__.py |
| 6 | # |
| 7 | # (C) 2015 Chris Liechti <cliechti@gmx.net> |
| 8 | # |
| 9 | # SPDX-License-Identifier: BSD-3-Clause |
| 10 | """\ |
| 11 | Support asyncio with serial ports. EXPERIMENTAL |
| 12 | |
| 13 | Posix platforms only, Python 3.4+ only. |
| 14 | |
| 15 | Windows event loops can not wait for serial ports with the current |
| 16 | implementation. It should be possible to get that working though. |
| 17 | """ |
| 18 | import asyncio |
| 19 | import serial |
Chris Liechti | fc01110 | 2015-11-18 00:38:19 +0100 | [diff] [blame] | 20 | import logging |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 21 | |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 22 | |
| 23 | class SerialTransport(asyncio.Transport): |
| 24 | def __init__(self, loop, protocol, serial_instance): |
| 25 | self._loop = loop |
| 26 | self._protocol = protocol |
| 27 | self.serial = serial_instance |
| 28 | self._closing = False |
| 29 | self._paused = False |
| 30 | # XXX how to support url handlers too |
| 31 | self.serial.timeout = 0 |
| 32 | self.serial.nonblocking() |
| 33 | loop.call_soon(protocol.connection_made, self) |
| 34 | # only start reading when connection_made() has been called |
| 35 | loop.call_soon(loop.add_reader, self.serial.fd, self._read_ready) |
| 36 | |
| 37 | def __repr__(self): |
| 38 | return '{self.__class__.__name__}({self._loop}, {self._protocol}, {self.serial})'.format(self=self) |
| 39 | |
| 40 | def close(self): |
| 41 | if self._closing: |
| 42 | return |
| 43 | self._closing = True |
| 44 | self._loop.remove_reader(self.serial.fd) |
| 45 | self.serial.close() |
| 46 | self._loop.call_soon(self._protocol.connection_lost, None) |
| 47 | |
| 48 | def _read_ready(self): |
| 49 | data = self.serial.read(1024) |
| 50 | if data: |
| 51 | self._protocol.data_received(data) |
| 52 | |
| 53 | def write(self, data): |
| 54 | self.serial.write(data) |
| 55 | |
| 56 | def can_write_eof(self): |
| 57 | return False |
| 58 | |
| 59 | def pause_reading(self): |
| 60 | if self._closing: |
| 61 | raise RuntimeError('Cannot pause_reading() when closing') |
| 62 | if self._paused: |
| 63 | raise RuntimeError('Already paused') |
| 64 | self._paused = True |
| 65 | self._loop.remove_reader(self._sock_fd) |
| 66 | if self._loop.get_debug(): |
Chris Liechti | fc01110 | 2015-11-18 00:38:19 +0100 | [diff] [blame] | 67 | logging.debug("%r pauses reading", self) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 68 | |
| 69 | def resume_reading(self): |
| 70 | if not self._paused: |
| 71 | raise RuntimeError('Not paused') |
| 72 | self._paused = False |
| 73 | if self._closing: |
| 74 | return |
| 75 | self._loop.add_reader(self._sock_fd, self._read_ready) |
| 76 | if self._loop.get_debug(): |
Chris Liechti | fc01110 | 2015-11-18 00:38:19 +0100 | [diff] [blame] | 77 | logging.debug("%r resumes reading", self) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 78 | |
| 79 | #~ def set_write_buffer_limits(self, high=None, low=None): |
| 80 | #~ def get_write_buffer_size(self): |
| 81 | #~ def writelines(self, list_of_data): |
| 82 | #~ def write_eof(self): |
| 83 | #~ def abort(self): |
| 84 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 85 | |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 86 | @asyncio.coroutine |
| 87 | def create_serial_connection(loop, protocol_factory, *args, **kwargs): |
| 88 | ser = serial.Serial(*args, **kwargs) |
| 89 | protocol = protocol_factory() |
| 90 | transport = SerialTransport(loop, protocol, ser) |
| 91 | return (transport, protocol) |
| 92 | |
| 93 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 94 | # test |
| 95 | if __name__ == '__main__': |
| 96 | class Output(asyncio.Protocol): |
| 97 | def connection_made(self, transport): |
| 98 | self.transport = transport |
| 99 | print('port opened', transport) |
Chris Liechti | 0cd7d07 | 2015-09-04 23:04:53 +0200 | [diff] [blame] | 100 | transport.serial.rts = False |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 101 | transport.write(b'hello world\n') |
| 102 | |
| 103 | def data_received(self, data): |
| 104 | print('data received', repr(data)) |
| 105 | self.transport.close() |
| 106 | |
| 107 | def connection_lost(self, exc): |
| 108 | print('port closed') |
| 109 | asyncio.get_event_loop().stop() |
| 110 | |
| 111 | loop = asyncio.get_event_loop() |
| 112 | coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200) |
| 113 | loop.run_until_complete(coro) |
| 114 | loop.run_forever() |
| 115 | loop.close() |