Repository navigation
Expand file tree
/
Copy pathcontroller.py
More file actions
291 lines (243 loc) · 9.5 KB
/
Copy pathcontroller.py
File metadata and controls
291 lines (243 loc) · 9.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
"""This module contains the Controller class, which is the base class for all devices."""
import sys
import serial
from serial.serialutil import PARITY_NONE, STOPBITS_ONE
from .tools import is_null_or_empty
class BaseSerialController:
"""Base interface for serial communication with a motor controller."""
def __init__(self,port,baudrate=9600,bytesize=8,parity=serial.PARITY_NONE,
stopbits=serial.STOPBITS_ONE,timeout=10,write_timeout=0.5,debug=True):
"""Open a serial connection and initialize response state.
Parameters
----------
port : str
Serial port name.
baudrate : int, default=9600
Serial communication speed.
bytesize : int, default=8
Number of data bits per byte.
parity : int, default=serial.PARITY_NONE
Serial parity configuration.
stopbits : int, default=serial.STOPBITS_ONE
Number of stop bits.
timeout : float, default=10
Read timeout in seconds.
write_timeout : float, default=0.5
Write timeout in seconds.
debug : bool, default=True
Whether to print connection and communication diagnostics.
"""
self.debug = debug
self.port = port
self.last_response = None
self.last_status = None
try:
self.s = serial.Serial(
port,
baudrate=baudrate,
bytesize=bytesize,
parity=parity,
stopbits=stopbits,
timeout=timeout,
write_timeout=write_timeout,
)
except serial.SerialException:
print("Could not open port {port}.")
self.s = None
if self.s.is_open:
if self.debug:
print(f"Controller on port {port}: Connection established!")
def __enter__(self):
"""Return this controller for use as a context manager.
Returns
-------
BaseSerialController
This controller instance.
"""
return self
def __exit__(self, exc_type, exc_val, exc_tb):
"""Close the serial connection when leaving a context manager."""
self.s.close()
def read_response(self):
"""Read a response from the serial port.
Raises
------
NotImplementedError
Always raised by the base implementation.
"""
raise NotImplementedError
def parse(self):
"""Parse a raw serial response into controller values.
Raises
------
NotImplementedError
Always raised by the base implementation.
"""
raise NotImplementedError
def send_to_serial(self,fullmessage):
"""Send a complete message and optionally read its response.
Parameters
----------
fullmessage : str or bytes
Message to send to the serial device.
Raises
------
NotImplementedError
Always raised by the base implementation.
"""
raise NotImplementedError
def send_instruction(self, instruction, message = None):
"""Build, send, and parse a device instruction.
Parameters
----------
instruction : str
Device command code.
message : str or list, optional
Command arguments.
Raises
------
NotImplementedError
Always raised by the base implementation.
"""
raise NotImplementedError
def close_connection(self):
"""Close the serial connection if it is open."""
if self.s.is_open:
self.s.close()
print("Connection is closed!")
class Controller(BaseSerialController):
"""Serial controller implementation for request/response devices."""
def __init__(self, port, baudrate=9600, bytesize=8, parity=serial.PARITY_NONE, stopbits=serial.STOPBITS_ONE, timeout=10, write_timeout=0.5, debug=True,
end_char="\r\n", response_separator=" ", request_separator=" ", data_separator=" "):
"""Initialize a controller and configure message separators.
Parameters
----------
port : str
Serial port name.
baudrate, bytesize, parity, stopbits, timeout, write_timeout, debug
Serial settings passed to :class:`BaseSerialController`.
end_char : str, default="\\r\\n"
Terminator expected at the end of a response.
response_separator : str, default=" "
Separator used to split response fields.
request_separator : str, default=" "
Separator placed after a command code.
data_separator : str, default=" "
Separator used between command arguments.
"""
super().__init__(port, baudrate=baudrate, bytesize=bytesize, parity=parity, stopbits=stopbits, timeout=timeout, write_timeout=write_timeout, debug=debug)
self.end_char = end_char
self.response_separator = response_separator
self.request_separator = request_separator
self.data_separator = data_separator
def read_response(self):
"""Read one response terminated by ``end_char``.
Returns
-------
bytes
Raw response bytes received from the controller.
"""
response = self.s.read_until(self.end_char.encode()) # Waiting until response read
if self.debug:
print("RX:", response.strip())
return response
def parse(self, response, debug=True):
"""Parses the received answer/message.
The basic implementation expects a code string to arrive with the message from the serial device. Reimplement according to your on device communication protocols.
Parameters
----------
response : bytes
Answer/message from the serial device to be parsed.
debug : bool, optional
Turns on/off debug messages
"""
if is_null_or_empty(response):
if debug:
print("Parse: Status/Response may be incomplete!")
print("Parse: Message:", response)
return None
response = response.decode().strip().split(sep=self.response_separator) # this is the full message from the serial port
code = response[0] # code- usually string defines the data meaning
if self.debug:
print(f'Code: {code}')
print(f'Data: {response[1:]}')
return (code, response[1:])
def create_message_from_list(self, data_list):
"""Join command arguments into a serialized message.
Parameters
----------
data_list : list
List containing the data pieces to be sent after the command character.
Returns
-------
str or None
Serialized arguments, or ``None`` when no data is supplied.
"""
if data_list is not None:
msg = str()
for d in data_list:
try:
for d in data_list:
msg+=(self.data_separator)
msg+=(str(d))
return msg
except:
return None
else:
return None
def send_to_serial(self, code=None, message=None):
"""Send a command to the controller and read its response.
Parameters
----------
code : str, optional
Command code.
message : str or list, optional
Command arguments.
Returns
-------
bytes or None
Raw response bytes, or ``None`` if no command was assembled.
"""
# self.s.reset_input_buffer() # NOT SURE if we need this
# Compose the message if it is a list
if type(message) is list:
message = self.create_message_from_list(message)
# Start with the command is necessary
if code is not None:
command = code + self.request_separator
# Append command if necessary
if message is not None:
mesg = str(message)
command += mesg + '\n'
command = command.encode()
if command:
self.s.reset_input_buffer()
if self.debug:
print("TX:", command)
# Execute the command and wait for a response
self.s.write(command)
# Read the response of the serial port
response = self.read_response()
return response
else:
return None
def send_instruction(self, instruction: str, message = None):
"""Wrapper function that sends the instruction with the data message. Reads the last response and parses it into the specified format.
Parameters
----------
instruction : str
Instruction code for the serial device to command a specific behaviour.
message : list, str, optinal
Describes the data message sent following the instruction code
Returns
-------
status : tuple
(code, response data) code is the response code of the device for a specific request which puts the following response data into context.
"""
response = self.send_to_serial(instruction, message)
status = self.parse(response=response, debug=self.debug)
self.last_response = response
self.last_status = status
if self.debug:
print('STATUS:', status)
return status