Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # |
Chris Liechti | 3e02f70 | 2015-12-16 23:06:04 +0100 | [diff] [blame] | 3 | # Experimental implementation of asyncio support. |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 4 | # |
Chris Liechti | 3e02f70 | 2015-12-16 23:06:04 +0100 | [diff] [blame] | 5 | # This file is part of pySerial. https://github.com/pyserial/pyserial |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 6 | # (C) 2015 Chris Liechti <cliechti@gmx.net> |
| 7 | # |
| 8 | # SPDX-License-Identifier: BSD-3-Clause |
| 9 | """\ |
| 10 | Support asyncio with serial ports. EXPERIMENTAL |
| 11 | |
| 12 | Posix platforms only, Python 3.4+ only. |
| 13 | |
| 14 | Windows event loops can not wait for serial ports with the current |
| 15 | implementation. It should be possible to get that working though. |
| 16 | """ |
| 17 | import asyncio |
| 18 | import serial |
Chris Liechti | fc01110 | 2015-11-18 00:38:19 +0100 | [diff] [blame] | 19 | import logging |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 20 | |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 21 | |
| 22 | class SerialTransport(asyncio.Transport): |
| 23 | def __init__(self, loop, protocol, serial_instance): |
Robert Smallshire | 2918726 | 2016-03-22 22:15:39 +0100 | [diff] [blame^] | 24 | super().__init__() |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 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 | |
Chris Liechti | 88e45ee | 2016-01-19 22:18:34 +0100 | [diff] [blame] | 40 | def close(self, exc=None): |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 41 | if self._closing: |
| 42 | return |
| 43 | self._closing = True |
| 44 | self._loop.remove_reader(self.serial.fd) |
| 45 | self.serial.close() |
Chris Liechti | 88e45ee | 2016-01-19 22:18:34 +0100 | [diff] [blame] | 46 | self._loop.call_soon(self._protocol.connection_lost, exc) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 47 | |
| 48 | def _read_ready(self): |
Chris Liechti | 88e45ee | 2016-01-19 22:18:34 +0100 | [diff] [blame] | 49 | try: |
| 50 | data = self.serial.read(1024) |
| 51 | except serial.SerialException as e: |
| 52 | self.close(exc=e) |
| 53 | else: |
| 54 | if data: |
| 55 | self._protocol.data_received(data) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 56 | |
| 57 | def write(self, data): |
Chris Liechti | 88e45ee | 2016-01-19 22:18:34 +0100 | [diff] [blame] | 58 | try: |
| 59 | self.serial.write(data) |
| 60 | except serial.SerialException as e: |
| 61 | self.close(exc=e) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 62 | |
| 63 | def can_write_eof(self): |
| 64 | return False |
| 65 | |
| 66 | def pause_reading(self): |
| 67 | if self._closing: |
| 68 | raise RuntimeError('Cannot pause_reading() when closing') |
| 69 | if self._paused: |
| 70 | raise RuntimeError('Already paused') |
| 71 | self._paused = True |
| 72 | self._loop.remove_reader(self._sock_fd) |
| 73 | if self._loop.get_debug(): |
Chris Liechti | fc01110 | 2015-11-18 00:38:19 +0100 | [diff] [blame] | 74 | logging.debug("%r pauses reading", self) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 75 | |
| 76 | def resume_reading(self): |
| 77 | if not self._paused: |
| 78 | raise RuntimeError('Not paused') |
| 79 | self._paused = False |
| 80 | if self._closing: |
| 81 | return |
| 82 | self._loop.add_reader(self._sock_fd, self._read_ready) |
| 83 | if self._loop.get_debug(): |
Chris Liechti | fc01110 | 2015-11-18 00:38:19 +0100 | [diff] [blame] | 84 | logging.debug("%r resumes reading", self) |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 85 | |
| 86 | #~ def set_write_buffer_limits(self, high=None, low=None): |
| 87 | #~ def get_write_buffer_size(self): |
| 88 | #~ def writelines(self, list_of_data): |
| 89 | #~ def write_eof(self): |
| 90 | #~ def abort(self): |
| 91 | |
Chris Liechti | 033f17c | 2015-08-30 21:28:04 +0200 | [diff] [blame] | 92 | |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 93 | @asyncio.coroutine |
| 94 | def create_serial_connection(loop, protocol_factory, *args, **kwargs): |
| 95 | ser = serial.Serial(*args, **kwargs) |
| 96 | protocol = protocol_factory() |
| 97 | transport = SerialTransport(loop, protocol, ser) |
| 98 | return (transport, protocol) |
| 99 | |
| 100 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - |
| 101 | # test |
| 102 | if __name__ == '__main__': |
| 103 | class Output(asyncio.Protocol): |
| 104 | def connection_made(self, transport): |
| 105 | self.transport = transport |
| 106 | print('port opened', transport) |
Chris Liechti | 0cd7d07 | 2015-09-04 23:04:53 +0200 | [diff] [blame] | 107 | transport.serial.rts = False |
Chris Liechti | d07a9e7 | 2015-08-22 02:58:47 +0200 | [diff] [blame] | 108 | transport.write(b'hello world\n') |
| 109 | |
| 110 | def data_received(self, data): |
| 111 | print('data received', repr(data)) |
| 112 | self.transport.close() |
| 113 | |
| 114 | def connection_lost(self, exc): |
| 115 | print('port closed') |
| 116 | asyncio.get_event_loop().stop() |
| 117 | |
| 118 | loop = asyncio.get_event_loop() |
| 119 | coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200) |
| 120 | loop.run_until_complete(coro) |
| 121 | loop.run_forever() |
| 122 | loop.close() |