blob: 76d4f18da3440f00f2f6f12d5234864157ef55de [file] [log] [blame]
Chris Liechtid07a9e72015-08-22 02:58:47 +02001#!/usr/bin/env python3
2#
Chris Liechti3e02f702015-12-16 23:06:04 +01003# Experimental implementation of asyncio support.
Chris Liechtid07a9e72015-08-22 02:58:47 +02004#
Chris Liechti3e02f702015-12-16 23:06:04 +01005# This file is part of pySerial. https://github.com/pyserial/pyserial
Chris Liechtid07a9e72015-08-22 02:58:47 +02006# (C) 2015 Chris Liechti <cliechti@gmx.net>
7#
8# SPDX-License-Identifier: BSD-3-Clause
9"""\
10Support asyncio with serial ports. EXPERIMENTAL
11
12Posix platforms only, Python 3.4+ only.
13
14Windows event loops can not wait for serial ports with the current
15implementation. It should be possible to get that working though.
16"""
17import asyncio
18import serial
Chris Liechtifc011102015-11-18 00:38:19 +010019import logging
Chris Liechti033f17c2015-08-30 21:28:04 +020020
Chris Liechtid07a9e72015-08-22 02:58:47 +020021
22class SerialTransport(asyncio.Transport):
23 def __init__(self, loop, protocol, serial_instance):
Robert Smallshire29187262016-03-22 22:15:39 +010024 super().__init__()
Chris Liechtid07a9e72015-08-22 02:58:47 +020025 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
Robert Smallshire48141682016-03-22 21:07:36 +010040 def is_closing(self):
41 """Return True if the transport is closing or closed."""
42 return self._closing
43
Chris Liechti88e45ee2016-01-19 22:18:34 +010044 def close(self, exc=None):
Chris Liechtid07a9e72015-08-22 02:58:47 +020045 if self._closing:
46 return
47 self._closing = True
48 self._loop.remove_reader(self.serial.fd)
49 self.serial.close()
Chris Liechti88e45ee2016-01-19 22:18:34 +010050 self._loop.call_soon(self._protocol.connection_lost, exc)
Chris Liechtid07a9e72015-08-22 02:58:47 +020051
52 def _read_ready(self):
Chris Liechti88e45ee2016-01-19 22:18:34 +010053 try:
54 data = self.serial.read(1024)
55 except serial.SerialException as e:
56 self.close(exc=e)
57 else:
58 if data:
59 self._protocol.data_received(data)
Chris Liechtid07a9e72015-08-22 02:58:47 +020060
61 def write(self, data):
Chris Liechti88e45ee2016-01-19 22:18:34 +010062 try:
63 self.serial.write(data)
64 except serial.SerialException as e:
65 self.close(exc=e)
Chris Liechtid07a9e72015-08-22 02:58:47 +020066
67 def can_write_eof(self):
68 return False
69
70 def pause_reading(self):
71 if self._closing:
72 raise RuntimeError('Cannot pause_reading() when closing')
73 if self._paused:
74 raise RuntimeError('Already paused')
75 self._paused = True
Robert Smallshireba9b17f2016-03-22 22:38:08 +010076 self._loop.remove_reader(self.serial.fd)
Chris Liechtid07a9e72015-08-22 02:58:47 +020077 if self._loop.get_debug():
Chris Liechtifc011102015-11-18 00:38:19 +010078 logging.debug("%r pauses reading", self)
Chris Liechtid07a9e72015-08-22 02:58:47 +020079
80 def resume_reading(self):
81 if not self._paused:
82 raise RuntimeError('Not paused')
83 self._paused = False
84 if self._closing:
85 return
Robert Smallshireba9b17f2016-03-22 22:38:08 +010086 self._loop.add_reader(self.serial.fd, self._read_ready)
Chris Liechtid07a9e72015-08-22 02:58:47 +020087 if self._loop.get_debug():
Chris Liechtifc011102015-11-18 00:38:19 +010088 logging.debug("%r resumes reading", self)
Chris Liechtid07a9e72015-08-22 02:58:47 +020089
90 #~ def set_write_buffer_limits(self, high=None, low=None):
91 #~ def get_write_buffer_size(self):
92 #~ def writelines(self, list_of_data):
93 #~ def write_eof(self):
94 #~ def abort(self):
95
Chris Liechti033f17c2015-08-30 21:28:04 +020096
Chris Liechtid07a9e72015-08-22 02:58:47 +020097@asyncio.coroutine
98def create_serial_connection(loop, protocol_factory, *args, **kwargs):
99 ser = serial.Serial(*args, **kwargs)
100 protocol = protocol_factory()
101 transport = SerialTransport(loop, protocol, ser)
102 return (transport, protocol)
103
Robert Smallshire981a3212016-03-22 21:57:02 +0100104
105@asyncio.coroutine
106def open_serial_connection(*,
107 loop=None,
108 limit=asyncio.streams._DEFAULT_LIMIT,
109 **kwargs):
110 """A wrapper for create_serial_connection() returning a (reader,
111 writer) pair.
112
113 The reader returned is a StreamReader instance; the writer is a
114 StreamWriter instance.
115
116 The arguments are all the usual arguments to Serial(). Additional
117 optional keyword arguments are loop (to set the event loop instance
118 to use) and limit (to set the buffer limit passed to the
119 StreamReader.
120
121 This function is a coroutine.
122 """
123 if loop is None:
124 loop = asyncio.get_event_loop()
125 reader = asyncio.StreamReader(limit=limit, loop=loop)
126 protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
127 transport, _ = yield from create_serial_connection(
128 loop=loop,
129 protocol_factory=lambda: protocol,
130 **kwargs)
131 writer = asyncio.StreamWriter(transport, protocol, reader, loop)
132 return reader, writer
133
134
Chris Liechtid07a9e72015-08-22 02:58:47 +0200135# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
136# test
137if __name__ == '__main__':
138 class Output(asyncio.Protocol):
139 def connection_made(self, transport):
140 self.transport = transport
141 print('port opened', transport)
Chris Liechti0cd7d072015-09-04 23:04:53 +0200142 transport.serial.rts = False
Chris Liechtid07a9e72015-08-22 02:58:47 +0200143 transport.write(b'hello world\n')
144
145 def data_received(self, data):
146 print('data received', repr(data))
147 self.transport.close()
148
149 def connection_lost(self, exc):
150 print('port closed')
151 asyncio.get_event_loop().stop()
152
153 loop = asyncio.get_event_loop()
154 coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
155 loop.run_until_complete(coro)
156 loop.run_forever()
157 loop.close()