3
|
1 # -*- coding: utf-8 -*-
|
|
2 """
|
|
3 Copyright (c) 2015-2020 Alan Yorinks All rights reserved.
|
|
4
|
|
5 This program is free software; you can redistribute it and/or
|
|
6 modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
7 Version 3 as published by the Free Software Foundation; either
|
|
8 or (at your option) any later version.
|
|
9 This library is distributed in the hope that it will be useful,
|
|
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
12 General Public License for more details.
|
|
13
|
|
14 You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
15 along with this library; if not, write to the Free Software
|
|
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
17 """
|
|
18
|
|
19 import asyncio
|
|
20 import sys
|
|
21 import serial
|
|
22 import time
|
|
23
|
|
24 LF = 0x0a
|
|
25
|
|
26
|
|
27 # noinspection PyStatementEffect,PyUnresolvedReferences,PyUnresolvedReferences
|
|
28 class TelemetrixAioSerial:
|
|
29 """
|
|
30 This class encapsulates management of the serial port that communicates
|
|
31 with the Arduino Firmata
|
|
32 It provides a 'futures' interface to make Pyserial compatible with asyncio
|
|
33 """
|
|
34
|
|
35 def __init__(self, com_port='/dev/ttyACM0', baud_rate=115200, sleep_tune=.0001,
|
|
36 telemetrix_aio_instance=None, close_loop_on_error=True):
|
|
37
|
|
38 """
|
|
39 This is the constructor for the aio serial handler
|
|
40
|
|
41 :param com_port: Com port designator
|
|
42
|
|
43 :param baud_rate: UART baud rate
|
|
44
|
|
45 :param telemetrix_aio_instance: reference to caller
|
|
46
|
|
47 :return: None
|
|
48 """
|
|
49 # print('Initializing Arduino - Please wait...', end=" ")
|
|
50 sys.stdout.flush()
|
|
51 self.my_serial = serial.Serial(com_port, baud_rate, timeout=1,
|
|
52 writeTimeout=1)
|
|
53
|
|
54 self.com_port = com_port
|
|
55 self.sleep_tune = sleep_tune
|
|
56 self.telemetrix_aio_instance = telemetrix_aio_instance
|
|
57 self.close_loop_on_error = close_loop_on_error
|
|
58
|
|
59 # used by read_until
|
|
60 self.start_time = None
|
|
61
|
|
62 async def get_serial(self):
|
|
63 """
|
|
64 This method returns a reference to the serial port in case the
|
|
65 user wants to call pyserial methods directly
|
|
66
|
|
67 :return: pyserial instance
|
|
68 """
|
|
69 return self.my_serial
|
|
70
|
|
71 async def write(self, data):
|
|
72 """
|
|
73 This is an asyncio adapted version of pyserial write. It provides a
|
|
74 non-blocking write and returns the number of bytes written upon
|
|
75 completion
|
|
76
|
|
77 :param data: Data to be written
|
|
78 :return: Number of bytes written
|
|
79 """
|
|
80 # the secret sauce - it is in your future
|
|
81 future = asyncio.Future()
|
|
82 result = None
|
|
83 try:
|
|
84 # result = self.my_serial.write(bytes([ord(data)]))
|
|
85 result = self.my_serial.write(bytes(data))
|
|
86
|
|
87 except serial.SerialException:
|
|
88 # noinspection PyBroadException
|
|
89 loop = None
|
|
90 await self.close()
|
|
91 future.cancel()
|
|
92 if self.close_loop_on_error:
|
|
93 loop = asyncio.get_event_loop()
|
|
94 loop.stop()
|
|
95
|
|
96 if self.telemetrix_aio_instance.the_task:
|
|
97 self.telemetrix_aio_instance.the_task.cancel()
|
|
98 await asyncio.sleep(1)
|
|
99 if self.close_loop_on_error:
|
|
100 loop.close()
|
|
101
|
|
102 if result:
|
|
103 future.set_result(result)
|
|
104 while True:
|
|
105 if not future.done():
|
|
106 # spin our asyncio wheels until future completes
|
|
107 await asyncio.sleep(self.sleep_tune)
|
|
108
|
|
109 else:
|
|
110 return future.result()
|
|
111
|
|
112 async def read(self, size=1):
|
|
113 """
|
|
114 This is an asyncio adapted version of pyserial read
|
|
115 that provides non-blocking read.
|
|
116
|
|
117 :return: One character
|
|
118 """
|
|
119
|
|
120 # create an asyncio Future
|
|
121 future = asyncio.Future()
|
|
122
|
|
123 # create a flag to indicate when data becomes available
|
|
124 data_available = False
|
|
125
|
|
126 # wait for a character to become available and read from
|
|
127 # the serial port
|
|
128 while True:
|
|
129 if not data_available:
|
|
130 # test to see if a character is waiting to be read.
|
|
131 # if not, relinquish control back to the event loop through the
|
|
132 # short sleep
|
|
133 if not self.my_serial.in_waiting:
|
|
134 await asyncio.sleep(self.sleep_tune*2)
|
|
135
|
|
136 # data is available.
|
|
137 # set the flag to true so that the future can "wait" until the
|
|
138 # read is completed.
|
|
139 else:
|
|
140 data_available = True
|
|
141 data = self.my_serial.read(size)
|
|
142 # set future result to make the character available
|
|
143 if size == 1:
|
|
144 future.set_result(ord(data))
|
|
145 else:
|
|
146 future.set_result(list(data))
|
|
147 else:
|
|
148 # wait for the future to complete
|
|
149 if not future.done():
|
|
150 await asyncio.sleep(self.sleep_tune)
|
|
151 else:
|
|
152 # future is done, so return the character
|
|
153 return future.result()
|
|
154
|
|
155 async def read_until(self, expected=LF, size=None, timeout=1):
|
|
156 """
|
|
157 This is an asyncio adapted version of pyserial read
|
|
158 that provides non-blocking read.
|
|
159
|
|
160 :return: Data delimited by expected
|
|
161 """
|
|
162
|
|
163 expected = str(expected).encode()
|
|
164 # create an asyncio Future
|
|
165 future = asyncio.Future()
|
|
166
|
|
167 # create a flag to indicate when data becomes available
|
|
168 data_available = False
|
|
169
|
|
170 if timeout:
|
|
171 self.start_time = time.time()
|
|
172
|
|
173 # wait for a character to become available and read from
|
|
174 # the serial port
|
|
175 while True:
|
|
176 if not data_available:
|
|
177 # test to see if a character is waiting to be read.
|
|
178 # if not, relinquish control back to the event loop through the
|
|
179 # short sleep
|
|
180 if not self.my_serial.in_waiting:
|
|
181 if timeout:
|
|
182 elapsed_time = time.time() - self.start_time
|
|
183 if elapsed_time > timeout:
|
|
184 return None
|
|
185 await asyncio.sleep(self.sleep_tune)
|
|
186 # data is available.
|
|
187 # set the flag to true so that the future can "wait" until the
|
|
188 # read is completed.
|
|
189 else:
|
|
190 data_available = True
|
|
191 data = self.my_serial.read_until(expected, size)
|
|
192 # set future result to make the character available
|
|
193 return_value = list(data)
|
|
194 future.set_result(return_value)
|
|
195 else:
|
|
196 # wait for the future to complete
|
|
197 if not future.done():
|
|
198 await asyncio.sleep(self.sleep_tune)
|
|
199 else:
|
|
200 # future is done, so return the character
|
|
201 return future.result()
|
|
202
|
|
203 async def reset_input_buffer(self):
|
|
204 """
|
|
205 Reset the input buffer
|
|
206 """
|
|
207 self.my_serial.reset_input_buffer()
|
|
208
|
|
209 async def close(self):
|
|
210 """
|
|
211 Close the serial port
|
|
212 """
|
|
213 if self.my_serial:
|
|
214 self.my_serial.close()
|