blob: 3b230c56b00984ac47f1e6450d11df3a9b6a1656 [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):
24 self._loop = loop
25 self._protocol = protocol
26 self.serial = serial_instance
27 self._closing = False
28 self._paused = False
29 # XXX how to support url handlers too
30 self.serial.timeout = 0
31 self.serial.nonblocking()
32 loop.call_soon(protocol.connection_made, self)
33 # only start reading when connection_made() has been called
34 loop.call_soon(loop.add_reader, self.serial.fd, self._read_ready)
35
36 def __repr__(self):
37 return '{self.__class__.__name__}({self._loop}, {self._protocol}, {self.serial})'.format(self=self)
38
Chris Liechti88e45ee2016-01-19 22:18:34 +010039 def close(self, exc=None):
Chris Liechtid07a9e72015-08-22 02:58:47 +020040 if self._closing:
41 return
42 self._closing = True
43 self._loop.remove_reader(self.serial.fd)
44 self.serial.close()
Chris Liechti88e45ee2016-01-19 22:18:34 +010045 self._loop.call_soon(self._protocol.connection_lost, exc)
Chris Liechtid07a9e72015-08-22 02:58:47 +020046
47 def _read_ready(self):
Chris Liechti88e45ee2016-01-19 22:18:34 +010048 try:
49 data = self.serial.read(1024)
50 except serial.SerialException as e:
51 self.close(exc=e)
52 else:
53 if data:
54 self._protocol.data_received(data)
Chris Liechtid07a9e72015-08-22 02:58:47 +020055
56 def write(self, data):
Chris Liechti88e45ee2016-01-19 22:18:34 +010057 try:
58 self.serial.write(data)
59 except serial.SerialException as e:
60 self.close(exc=e)
Chris Liechtid07a9e72015-08-22 02:58:47 +020061
62 def can_write_eof(self):
63 return False
64
65 def pause_reading(self):
66 if self._closing:
67 raise RuntimeError('Cannot pause_reading() when closing')
68 if self._paused:
69 raise RuntimeError('Already paused')
70 self._paused = True
71 self._loop.remove_reader(self._sock_fd)
72 if self._loop.get_debug():
Chris Liechtifc011102015-11-18 00:38:19 +010073 logging.debug("%r pauses reading", self)
Chris Liechtid07a9e72015-08-22 02:58:47 +020074
75 def resume_reading(self):
76 if not self._paused:
77 raise RuntimeError('Not paused')
78 self._paused = False
79 if self._closing:
80 return
81 self._loop.add_reader(self._sock_fd, self._read_ready)
82 if self._loop.get_debug():
Chris Liechtifc011102015-11-18 00:38:19 +010083 logging.debug("%r resumes reading", self)
Chris Liechtid07a9e72015-08-22 02:58:47 +020084
85 #~ def set_write_buffer_limits(self, high=None, low=None):
86 #~ def get_write_buffer_size(self):
87 #~ def writelines(self, list_of_data):
88 #~ def write_eof(self):
89 #~ def abort(self):
90
Chris Liechti033f17c2015-08-30 21:28:04 +020091
Chris Liechtid07a9e72015-08-22 02:58:47 +020092@asyncio.coroutine
93def create_serial_connection(loop, protocol_factory, *args, **kwargs):
94 ser = serial.Serial(*args, **kwargs)
95 protocol = protocol_factory()
96 transport = SerialTransport(loop, protocol, ser)
97 return (transport, protocol)
98
Robert Smallshire981a3212016-03-22 21:57:02 +010099
100@asyncio.coroutine
101def open_serial_connection(*,
102 loop=None,
103 limit=asyncio.streams._DEFAULT_LIMIT,
104 **kwargs):
105 """A wrapper for create_serial_connection() returning a (reader,
106 writer) pair.
107
108 The reader returned is a StreamReader instance; the writer is a
109 StreamWriter instance.
110
111 The arguments are all the usual arguments to Serial(). Additional
112 optional keyword arguments are loop (to set the event loop instance
113 to use) and limit (to set the buffer limit passed to the
114 StreamReader.
115
116 This function is a coroutine.
117 """
118 if loop is None:
119 loop = asyncio.get_event_loop()
120 reader = asyncio.StreamReader(limit=limit, loop=loop)
121 protocol = asyncio.StreamReaderProtocol(reader, loop=loop)
122 transport, _ = yield from create_serial_connection(
123 loop=loop,
124 protocol_factory=lambda: protocol,
125 **kwargs)
126 writer = asyncio.StreamWriter(transport, protocol, reader, loop)
127 return reader, writer
128
129
Chris Liechtid07a9e72015-08-22 02:58:47 +0200130# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
131# test
132if __name__ == '__main__':
133 class Output(asyncio.Protocol):
134 def connection_made(self, transport):
135 self.transport = transport
136 print('port opened', transport)
Chris Liechti0cd7d072015-09-04 23:04:53 +0200137 transport.serial.rts = False
Chris Liechtid07a9e72015-08-22 02:58:47 +0200138 transport.write(b'hello world\n')
139
140 def data_received(self, data):
141 print('data received', repr(data))
142 self.transport.close()
143
144 def connection_lost(self, exc):
145 print('port closed')
146 asyncio.get_event_loop().stop()
147
148 loop = asyncio.get_event_loop()
149 coro = create_serial_connection(loop, Output, '/dev/ttyUSB0', baudrate=115200)
150 loop.run_until_complete(coro)
151 loop.run_forever()
152 loop.close()