3
|
1 """
|
|
2 Copyright (c) 2020-2021 Alan Yorinks All rights reserved.
|
|
3
|
|
4 This program is free software; you can redistribute it and/or
|
|
5 modify it under the terms of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
6 Version 3 as published by the Free Software Foundation; either
|
|
7 or (at your option) any later version.
|
|
8 This library is distributed in the hope that it will be useful,
|
|
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
|
11 General Public License for more details.
|
|
12
|
|
13 You should have received a copy of the GNU AFFERO GENERAL PUBLIC LICENSE
|
|
14 along with this library; if not, write to the Free Software
|
|
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
16 """
|
|
17
|
|
18 import asyncio
|
|
19 # import socket
|
|
20 # import struct
|
|
21 import sys
|
|
22 import time
|
5
|
23 import logging
|
|
24 log = logging.getLogger('tele')
|
3
|
25
|
|
26 # noinspection PyPackageRequirementscd
|
|
27 from serial.serialutil import SerialException
|
|
28 # noinspection PyPackageRequirements
|
|
29 from serial.tools import list_ports
|
|
30
|
|
31 # noinspection PyUnresolvedReferences
|
|
32 from telemetrix_aio.private_constants import PrivateConstants
|
|
33 # noinspection PyUnresolvedReferences
|
|
34 from telemetrix_aio.telemetrix_aio_socket import TelemetrixAioSocket
|
|
35 # noinspection PyUnresolvedReferences
|
5
|
36 from telemetrix_local_serial import TelemetrixAioSerial
|
|
37
|
|
38 # for debug logging. Should move to private_constants.py
|
|
39 CommandName = {
|
|
40 0: 'LOOP_COMMAND',
|
|
41 1: 'SET_PIN_MODE',
|
|
42 2: 'DIGITAL_WRITE',
|
|
43 3: 'ANALOG_WRITE',
|
|
44 4: 'MODIFY_REPORTING',
|
|
45 5: 'GET_FIRMWARE_VERSION',
|
|
46 6: 'ARE_U_THERE',
|
|
47 7: 'SERVO_ATTACH',
|
|
48 8: 'SERVO_WRITE',
|
|
49 9: 'SERVO_DETACH',
|
|
50 10: 'I2C_BEGIN',
|
|
51 11: 'I2C_READ',
|
|
52 12: 'I2C_WRITE',
|
|
53 13: 'SONAR_NEW',
|
|
54 14: 'DHT_NEW',
|
|
55 15: 'STOP_ALL_REPORTS',
|
|
56 16: 'SET_ANALOG_SCANNING_INTERVAL',
|
|
57 17: 'ENABLE_ALL_REPORTS',
|
|
58 18: 'RESET',
|
|
59 19: 'SPI_INIT',
|
|
60 20: 'SPI_WRITE_BLOCKING',
|
|
61 21: 'SPI_READ_BLOCKING',
|
|
62 22: 'SPI_SET_FORMAT',
|
|
63 23: 'SPI_CS_CONTROL',
|
|
64 24: 'ONE_WIRE_INIT',
|
|
65 25: 'ONE_WIRE_RESET',
|
|
66 26: 'ONE_WIRE_SELECT',
|
|
67 27: 'ONE_WIRE_SKIP',
|
|
68 28: 'ONE_WIRE_WRITE',
|
|
69 29: 'ONE_WIRE_READ',
|
|
70 30: 'ONE_WIRE_RESET_SEARCH',
|
|
71 31: 'ONE_WIRE_SEARCH',
|
|
72 32: 'ONE_WIRE_CRC8',
|
|
73 33: 'SET_PIN_MODE_STEPPER',
|
|
74 34: 'STEPPER_MOVE_TO',
|
|
75 35: 'STEPPER_MOVE',
|
|
76 36: 'STEPPER_RUN',
|
|
77 37: 'STEPPER_RUN_SPEED',
|
|
78 38: 'STEPPER_SET_MAX_SPEED',
|
|
79 39: 'STEPPER_SET_ACCELERATION',
|
|
80 40: 'STEPPER_SET_SPEED',
|
|
81 41: 'STEPPER_SET_CURRENT_POSITION',
|
|
82 42: 'STEPPER_RUN_SPEED_TO_POSITION',
|
|
83 43: 'STEPPER_STOP',
|
|
84 44: 'STEPPER_DISABLE_OUTPUTS',
|
|
85 45: 'STEPPER_ENABLE_OUTPUTS',
|
|
86 46: 'STEPPER_SET_MINIMUM_PULSE_WIDTH',
|
|
87 47: 'STEPPER_SET_ENABLE_PIN',
|
|
88 48: 'STEPPER_SET_3_PINS_INVERTED',
|
|
89 49: 'STEPPER_SET_4_PINS_INVERTED',
|
|
90 50: 'STEPPER_IS_RUNNING',
|
|
91 51: 'STEPPER_GET_CURRENT_POSITION',
|
|
92 52: 'STEPPER_GET_DISTANCE_TO_GO',
|
|
93 53: 'STEPPER_GET_TARGET_POSITION',
|
|
94 54: 'GET_FEATURES',
|
|
95 }
|
3
|
96
|
|
97 # noinspection GrazieInspection,PyArgumentList,PyMethodMayBeStatic,PyRedundantParentheses
|
|
98 class TelemetrixAIO:
|
|
99 """
|
|
100 This class exposes and implements the TelemetrixAIO API.
|
|
101 It includes the public API methods as well as
|
|
102 a set of private methods. This is an asyncio API.
|
|
103
|
|
104 """
|
|
105
|
|
106 # noinspection PyPep8,PyPep8
|
|
107 def __init__(self, com_port=None,
|
|
108 arduino_instance_id=1, arduino_wait=4,
|
|
109 sleep_tune=0.0001, autostart=True,
|
|
110 loop=None, shutdown_on_exception=True,
|
|
111 close_loop_on_shutdown=True,
|
|
112 ip_address=None, ip_port=31335):
|
|
113
|
|
114 """
|
|
115 If you have a single Arduino connected to your computer,
|
|
116 then you may accept all the default values.
|
|
117
|
|
118 Otherwise, specify a unique arduino_instance id for each board in use.
|
|
119
|
|
120 :param com_port: e.g. COM3 or /dev/ttyACM0.
|
|
121
|
|
122 :param arduino_instance_id: Must match value in the Telemetrix4Arduino sketch
|
|
123
|
|
124 :param arduino_wait: Amount of time to wait for an Arduino to
|
|
125 fully reset itself.
|
|
126
|
|
127 :param sleep_tune: A tuning parameter (typically not changed by user)
|
|
128
|
|
129 :param autostart: If you wish to call the start method within
|
|
130 your application, then set this to False.
|
|
131
|
|
132 :param loop: optional user provided event loop
|
|
133
|
|
134 :param shutdown_on_exception: call shutdown before raising
|
|
135 a RunTimeError exception, or
|
|
136 receiving a KeyboardInterrupt exception
|
|
137
|
|
138 :param close_loop_on_shutdown: stop and close the event loop loop
|
|
139 when a shutdown is called or a serial
|
|
140 error occurs
|
|
141
|
|
142 """
|
|
143 # check to make sure that Python interpreter is version 3.8.3 or greater
|
|
144 python_version = sys.version_info
|
5
|
145 if python_version[:3] < (3, 8, 3):
|
|
146 raise RuntimeError("ERROR: Python 3.8.3 or greater is "
|
|
147 "required for use of this program.")
|
3
|
148
|
|
149 # save input parameters
|
|
150 self.com_port = com_port
|
|
151 self.arduino_instance_id = arduino_instance_id
|
|
152 self.arduino_wait = arduino_wait
|
|
153 self.sleep_tune = sleep_tune
|
|
154 self.autostart = autostart
|
|
155 self.ip_address = ip_address
|
|
156 self.ip_port = ip_port
|
|
157
|
|
158 # if tcp, this variable is set to the connected socket
|
|
159 self.sock = None
|
|
160
|
|
161 # set the event loop
|
|
162 if loop is None:
|
|
163 self.loop = asyncio.get_event_loop()
|
|
164 else:
|
|
165 self.loop = loop
|
|
166
|
|
167 self.shutdown_on_exception = shutdown_on_exception
|
|
168 self.close_loop_on_shutdown = close_loop_on_shutdown
|
|
169
|
|
170 # dictionaries to store the callbacks for each pin
|
|
171 self.analog_callbacks = {}
|
|
172
|
|
173 self.digital_callbacks = {}
|
|
174
|
|
175 self.i2c_callback = None
|
|
176 self.i2c_callback2 = None
|
|
177
|
|
178 self.i2c_1_active = False
|
|
179 self.i2c_2_active = False
|
|
180
|
|
181 self.spi_callback = None
|
|
182
|
|
183 self.onewire_callback = None
|
|
184
|
|
185 # debug loopback callback method
|
|
186 self.loop_back_callback = None
|
|
187
|
|
188 # the trigger pin will be the key to retrieve
|
|
189 # the callback for a specific HC-SR04
|
|
190 self.sonar_callbacks = {}
|
|
191
|
|
192 self.sonar_count = 0
|
|
193
|
|
194 self.dht_callbacks = {}
|
|
195
|
|
196 self.dht_count = 0
|
|
197
|
|
198 # serial port in use
|
5
|
199 self.serial_port: Optional[TelemetrixAioSerial] = None
|
|
200
|
3
|
201 self.the_task = None
|
|
202
|
|
203 # flag to indicate we are in shutdown mode
|
|
204 self.shutdown_flag = False
|
|
205
|
|
206 # reported features
|
|
207 self.reported_features = 0
|
|
208
|
|
209 # To add a command to the command dispatch table, append here.
|
5
|
210 self.report_dispatch = {
|
|
211 PrivateConstants.LOOP_COMMAND: self._report_loop_data,
|
|
212 PrivateConstants.DEBUG_PRINT: self._report_debug_data,
|
|
213 PrivateConstants.DIGITAL_REPORT: self._digital_message,
|
|
214 PrivateConstants.ANALOG_REPORT: self._analog_message,
|
|
215 PrivateConstants.SERVO_UNAVAILABLE: self._servo_unavailable,
|
|
216 PrivateConstants.I2C_READ_REPORT: self._i2c_read_report,
|
|
217 PrivateConstants.I2C_TOO_FEW_BYTES_RCVD: self._i2c_too_few,
|
|
218 PrivateConstants.I2C_TOO_MANY_BYTES_RCVD: self._i2c_too_many,
|
|
219 PrivateConstants.SONAR_DISTANCE: self._sonar_distance_report,
|
|
220 PrivateConstants.DHT_REPORT: self._dht_report,
|
|
221 PrivateConstants.SPI_REPORT: self._spi_report,
|
|
222 PrivateConstants.ONE_WIRE_REPORT: self._onewire_report,
|
|
223 PrivateConstants.STEPPER_DISTANCE_TO_GO: self._stepper_distance_to_go_report,
|
|
224 PrivateConstants.STEPPER_TARGET_POSITION: self._stepper_target_position_report,
|
|
225 PrivateConstants.STEPPER_CURRENT_POSITION: self._stepper_current_position_report,
|
|
226 PrivateConstants.STEPPER_RUNNING_REPORT: self._stepper_is_running_report,
|
|
227 PrivateConstants.STEPPER_RUN_COMPLETE_REPORT: self._stepper_run_complete_report,
|
|
228 PrivateConstants.STEPPER_DISTANCE_TO_GO: self._stepper_distance_to_go_report,
|
|
229 PrivateConstants.STEPPER_TARGET_POSITION: self._stepper_target_position_report,
|
|
230 PrivateConstants.FEATURES: self._features_report,
|
|
231 }
|
3
|
232
|
|
233 # dictionaries to store the callbacks for each pin
|
|
234 self.analog_callbacks = {}
|
|
235
|
|
236 self.digital_callbacks = {}
|
|
237
|
|
238 self.i2c_callback = None
|
|
239 self.i2c_callback2 = None
|
|
240
|
|
241 self.i2c_1_active = False
|
|
242 self.i2c_2_active = False
|
|
243
|
|
244 self.spi_callback = None
|
|
245
|
|
246 self.onewire_callback = None
|
|
247
|
|
248 self.cs_pins_enabled = []
|
|
249
|
|
250 # flag to indicate if spi is initialized
|
|
251 self.spi_enabled = False
|
|
252
|
|
253 # flag to indicate if onewire is initialized
|
|
254 self.onewire_enabled = False
|
|
255
|
|
256 # the trigger pin will be the key to retrieve
|
|
257 # the callback for a specific HC-SR04
|
|
258 self.sonar_callbacks = {}
|
|
259
|
|
260 self.sonar_count = 0
|
|
261
|
|
262 self.dht_callbacks = {}
|
|
263
|
|
264 # stepper motor variables
|
|
265
|
|
266 # updated when a new motor is added
|
|
267 self.next_stepper_assigned = 0
|
|
268
|
|
269 # valid list of stepper motor interface types
|
|
270 self.valid_stepper_interfaces = [1, 2, 3, 4, 6, 8]
|
|
271
|
|
272 # maximum number of steppers supported
|
|
273 self.max_number_of_steppers = 4
|
|
274
|
|
275 # number of steppers created - not to exceed the maximum
|
|
276 self.number_of_steppers = 0
|
|
277
|
|
278 # dictionary to hold stepper motor information
|
|
279 self.stepper_info = {'instance': False, 'is_running': None,
|
|
280 'maximum_speed': 1, 'speed': 0, 'acceleration': 0,
|
|
281 'distance_to_go_callback': None,
|
|
282 'target_position_callback': None,
|
|
283 'current_position_callback': None,
|
|
284 'is_running_callback': None,
|
|
285 'motion_complete_callback': None,
|
|
286 'acceleration_callback': None}
|
|
287
|
|
288 # build a list of stepper motor info items
|
|
289 self.stepper_info_list = []
|
|
290 # a list of dictionaries to hold stepper information
|
|
291 for motor in range(self.max_number_of_steppers):
|
|
292 self.stepper_info_list.append(self.stepper_info)
|
|
293
|
|
294 print(f'TelemetrixAIO Version: {PrivateConstants.TELEMETRIX_AIO_VERSION}')
|
|
295 print(f'Copyright (c) 2018-2021 Alan Yorinks All rights reserved.\n')
|
|
296
|
|
297 if autostart:
|
|
298 self.loop.run_until_complete(self.start_aio())
|
|
299
|
|
300 async def start_aio(self):
|
|
301 """
|
|
302 This method may be called directly, if the autostart
|
|
303 parameter in __init__ is set to false.
|
|
304
|
|
305 This method instantiates the serial interface and then performs auto pin
|
|
306 discovery if using a serial interface, or creates and connects to
|
|
307 a TCP/IP enabled device running StandardFirmataWiFi.
|
|
308
|
|
309 Use this method if you wish to start TelemetrixAIO manually from
|
|
310 an asyncio function.
|
|
311 """
|
5
|
312 log.debug('start_aio')
|
3
|
313 if not self.ip_address:
|
|
314 if not self.com_port:
|
|
315 # user did not specify a com_port
|
|
316 try:
|
|
317 await self._find_arduino()
|
|
318 except KeyboardInterrupt:
|
|
319 if self.shutdown_on_exception:
|
|
320 await self.shutdown()
|
|
321 else:
|
|
322 # com_port specified - set com_port and baud rate
|
|
323 try:
|
|
324 await self._manual_open()
|
|
325 except KeyboardInterrupt:
|
|
326 if self.shutdown_on_exception:
|
|
327 await self.shutdown()
|
|
328
|
|
329 if self.com_port:
|
|
330 print(f'Telemetrix4AIO found and connected to {self.com_port}')
|
|
331
|
|
332 # no com_port found - raise a runtime exception
|
|
333 else:
|
|
334 if self.shutdown_on_exception:
|
|
335 await self.shutdown()
|
|
336 raise RuntimeError('No Arduino Found or User Aborted Program')
|
|
337 # using tcp/ip
|
|
338 else:
|
|
339 self.sock = TelemetrixAioSocket(self.ip_address, self.ip_port, self.loop)
|
|
340 await self.sock.start()
|
|
341 # self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
342 # self.sock.connect((self.ip_address, self.ip_port))
|
|
343 # print(f'Successfully connected to: {self.ip_address}:{self.ip_port}')
|
|
344
|
|
345 # get arduino firmware version and print it
|
|
346 firmware_version = await self._get_firmware_version()
|
5
|
347 log.debug(f'start_aio p2 {firmware_version=}')
|
3
|
348 if not firmware_version:
|
5
|
349 log.info('*** Firmware Version retrieval timed out. ***')
|
|
350 log.info('\nDo you have Arduino connectivity and do you have the ')
|
|
351 log.info('Telemetrix4Arduino sketch uploaded to the board and are connected')
|
|
352 log.info('to the correct serial port.\n')
|
|
353 log.info('To see a list of serial ports, type: '
|
3
|
354 '"list_serial_ports" in your console.')
|
|
355 if self.shutdown_on_exception:
|
|
356 await self.shutdown()
|
|
357 raise RuntimeError
|
|
358 else:
|
5
|
359 log.debug(f'p3')
|
6
|
360 if firmware_version[1] < 5:
|
3
|
361 raise RuntimeError('Please upgrade the server firmware to version 5.0.0 or greater')
|
5
|
362 log.info(f'Telemetrix4Arduino Version Number: {firmware_version[1]}.'
|
|
363 f'{firmware_version[2]}.{firmware_version[3]}')
|
3
|
364 # start the command dispatcher loop
|
|
365 command = [PrivateConstants.ENABLE_ALL_REPORTS]
|
5
|
366 log.debug('start_aio send ENABLE_ALL_REPORTS')
|
3
|
367 await self._send_command(command)
|
|
368 if not self.loop:
|
5
|
369 log.debug('start_aio new loop')
|
3
|
370 self.loop = asyncio.get_event_loop()
|
|
371 self.the_task = self.loop.create_task(self._arduino_report_dispatcher())
|
5
|
372 log.debug('create task for _arduino_report_dispatcher')
|
|
373 log.debug(f'create task for _arduino_report_dispatcher done: {self.the_task}')
|
3
|
374
|
|
375 # get the features list
|
|
376 command = [PrivateConstants.GET_FEATURES]
|
5
|
377 log.debug('send get_features')
|
3
|
378 await self._send_command(command)
|
|
379 time.sleep(.5)
|
|
380
|
|
381 # Have the server reset its data structures
|
|
382 command = [PrivateConstants.RESET]
|
5
|
383 log.debug('send reset')
|
3
|
384 await self._send_command(command)
|
5
|
385 log.debug(f'start_aio bye\n')
|
3
|
386
|
|
387 async def get_event_loop(self):
|
|
388 """
|
|
389 Return the currently active asyncio event loop
|
|
390
|
|
391 :return: Active event loop
|
|
392
|
|
393 """
|
|
394 return self.loop
|
|
395
|
|
396 async def _find_arduino(self):
|
|
397 """
|
|
398 This method will search all potential serial ports for an Arduino
|
|
399 containing a sketch that has a matching arduino_instance_id as
|
|
400 specified in the input parameters of this class.
|
|
401
|
|
402 This is used explicitly with the FirmataExpress sketch.
|
|
403 """
|
|
404
|
|
405 # a list of serial ports to be checked
|
|
406 serial_ports = []
|
|
407
|
|
408 print('Opening all potential serial ports...')
|
|
409 the_ports_list = list_ports.comports()
|
|
410 for port in the_ports_list:
|
|
411 if port.pid is None:
|
|
412 continue
|
|
413 print('\nChecking {}'.format(port.device))
|
|
414 try:
|
6
|
415 self.serial_port = TelemetrixAioSerial(port.device, 115200)
|
|
416 await self.serial_port.open()
|
3
|
417 except SerialException:
|
|
418 continue
|
|
419 # create a list of serial ports that we opened
|
|
420 serial_ports.append(self.serial_port)
|
|
421
|
|
422 # display to the user
|
|
423 print('\t' + port.device)
|
|
424
|
|
425 # wait for arduino to reset
|
|
426 print('\nWaiting {} seconds(arduino_wait) for Arduino devices to '
|
|
427 'reset...'.format(self.arduino_wait))
|
|
428 await asyncio.sleep(self.arduino_wait)
|
|
429
|
|
430 print('\nSearching for an Arduino configured with an arduino_instance = ',
|
|
431 self.arduino_instance_id)
|
|
432
|
|
433 for serial_port in serial_ports:
|
|
434 self.serial_port = serial_port
|
|
435
|
|
436 command = [PrivateConstants.ARE_U_THERE]
|
6
|
437 try:
|
|
438 i_am_here = await asyncio.wait_for(self._send_command(command, has_response=True), timeout=0.5)
|
|
439 except asyncio.TimeoutError:
|
|
440 log.info(f'no answer- retrying')
|
3
|
441 continue
|
6
|
442
|
3
|
443 # got an I am here message - is it the correct ID?
|
|
444 if i_am_here[2] == self.arduino_instance_id:
|
|
445 self.com_port = serial_port.com_port
|
|
446 return
|
6
|
447 raise ValueError("not found")
|
3
|
448
|
|
449 async def _manual_open(self):
|
|
450 """
|
|
451 Com port was specified by the user - try to open up that port
|
|
452
|
|
453 """
|
6
|
454 log.info(f'Searching for correct arduino_instance_id: {self.arduino_instance_id}')
|
|
455 while True:
|
|
456 # if port is not found, a serial exception will be thrown
|
|
457 log.info('Opening {} ...'.format(self.com_port))
|
|
458 self.serial_port = TelemetrixAioSerial(self.com_port, 115200)
|
|
459 # log.debug('await open')
|
|
460 await self.serial_port.open()
|
|
461
|
|
462 # print('Waiting {} seconds for the Arduino To Reset.'
|
|
463 # .format(self.arduino_wait))
|
|
464 # await asyncio.sleep(self.arduino_wait)
|
|
465 command = [PrivateConstants.ARE_U_THERE]
|
|
466 try:
|
|
467 i_am_here = await asyncio.wait_for(self._send_command(command, has_response=True), timeout=0.5)
|
|
468 except asyncio.TimeoutError:
|
|
469 log.info(f'no answer- retrying')
|
|
470 continue
|
|
471 break
|
3
|
472
|
|
473 if not i_am_here:
|
6
|
474 log.warning(f'ERROR: correct arduino_instance_id not found')
|
5
|
475
|
|
476 log.info('Correct arduino_instance_id found\n')
|
3
|
477
|
|
478 async def _get_firmware_version(self):
|
|
479 """
|
|
480 This method retrieves the Arduino4Telemetrix firmware version
|
|
481
|
|
482 :returns: Firmata firmware version
|
|
483 """
|
|
484 command = [PrivateConstants.GET_FIRMWARE_VERSION]
|
6
|
485 log.debug('GET_FIRMWARE_VERSION')
|
|
486 firmware_version = await self._send_command(command, has_response=True)
|
|
487 log.debug(f'{firmware_version=}\n')
|
|
488
|
3
|
489 return firmware_version
|
|
490
|
|
491 async def analog_write(self, pin, value):
|
|
492 """
|
|
493 Set the specified pin to the specified value.
|
|
494
|
|
495 :param pin: arduino pin number
|
|
496
|
|
497 :param value: pin value (maximum 16 bits)
|
|
498
|
|
499 """
|
|
500 value_msb = value >> 8
|
|
501 value_lsb = value & 0xff
|
|
502 command = [PrivateConstants.ANALOG_WRITE, pin, value_msb, value_lsb]
|
|
503 await self._send_command(command)
|
|
504
|
|
505 async def digital_write(self, pin, value):
|
|
506 """
|
|
507 Set the specified pin to the specified value.
|
|
508
|
|
509 :param pin: arduino pin number
|
|
510
|
|
511 :param value: pin value (1 or 0)
|
|
512
|
|
513 """
|
|
514 command = [PrivateConstants.DIGITAL_WRITE, pin, value]
|
|
515 await self._send_command(command)
|
|
516
|
|
517 async def i2c_read(self, address, register, number_of_bytes,
|
|
518 callback, i2c_port=0,
|
|
519 write_register=True):
|
|
520 """
|
|
521 Read the specified number of bytes from the specified register for
|
|
522 the i2c device.
|
|
523
|
|
524
|
|
525 :param address: i2c device address
|
|
526
|
|
527 :param register: i2c register (or None if no register selection is needed)
|
|
528
|
|
529 :param number_of_bytes: number of bytes to be read
|
|
530
|
|
531 :param callback: Required callback function to report i2c data as a
|
|
532 result of read command
|
|
533
|
|
534 :param i2c_port: select the default port (0) or secondary port (1)
|
|
535
|
|
536 :param write_register: If True, the register is written
|
|
537 before read
|
|
538 Else, the write is suppressed
|
|
539
|
|
540
|
|
541 callback returns a data list:
|
|
542
|
|
543 [I2C_READ_REPORT, address, register, count of data bytes,
|
|
544 data bytes, time-stamp]
|
|
545
|
|
546 """
|
|
547 if not callback:
|
|
548 if self.shutdown_on_exception:
|
|
549 await self.shutdown()
|
|
550 raise RuntimeError('i2c_read: A Callback must be specified')
|
|
551
|
|
552 await self._i2c_read_request(address, register, number_of_bytes,
|
|
553 callback=callback, i2c_port=i2c_port,
|
|
554 write_register=write_register)
|
|
555
|
|
556 async def i2c_read_restart_transmission(self, address, register,
|
|
557 number_of_bytes,
|
|
558 callback, i2c_port=0,
|
|
559 write_register=True):
|
|
560 """
|
|
561 Read the specified number of bytes from the specified register for
|
|
562 the i2c device. This restarts the transmission after the read. It is
|
|
563 required for some i2c devices such as the MMA8452Q accelerometer.
|
|
564
|
|
565
|
|
566 :param address: i2c device address
|
|
567
|
|
568 :param register: i2c register (or None if no register
|
|
569 selection is needed)
|
|
570
|
|
571 :param number_of_bytes: number of bytes to be read
|
|
572
|
|
573 :param callback: Required callback function to report i2c data as a
|
|
574 result of read command
|
|
575
|
|
576 :param i2c_port: select the default port (0) or secondary port (1)
|
|
577
|
|
578 :param write_register: If True, the register is written
|
|
579 before read
|
|
580 Else, the write is suppressed
|
|
581
|
|
582 callback returns a data list:
|
|
583
|
|
584 [I2C_READ_REPORT, address, register, count of data bytes,
|
|
585 data bytes, time-stamp]
|
|
586
|
|
587 """
|
|
588 if not callback:
|
|
589 if self.shutdown_on_exception:
|
|
590 await self.shutdown()
|
|
591 raise RuntimeError(
|
|
592 'i2c_read_restart_transmission: A Callback must be specified')
|
|
593
|
|
594 await self._i2c_read_request(address, register, number_of_bytes,
|
|
595 stop_transmission=False,
|
|
596 callback=callback, i2c_port=i2c_port,
|
|
597 write_register=write_register)
|
|
598
|
|
599 async def _i2c_read_request(self, address, register, number_of_bytes,
|
|
600 stop_transmission=True, callback=None,
|
|
601 i2c_port=0, write_register=True):
|
|
602 """
|
|
603 This method requests the read of an i2c device. Results are retrieved
|
|
604 via callback.
|
|
605
|
|
606 :param address: i2c device address
|
|
607
|
|
608 :param register: register number (or None if no register selection is needed)
|
|
609
|
|
610 :param number_of_bytes: number of bytes expected to be returned
|
|
611
|
|
612 :param stop_transmission: stop transmission after read
|
|
613
|
|
614 :param callback: Required callback function to report i2c data as a
|
|
615 result of read command.
|
|
616
|
|
617 :param i2c_port: select the default port (0) or secondary port (1)
|
|
618
|
|
619 :param write_register: If True, the register is written
|
|
620 before read
|
|
621 Else, the write is suppressed
|
|
622
|
|
623 """
|
|
624 if not i2c_port:
|
|
625 if not self.i2c_1_active:
|
|
626 if self.shutdown_on_exception:
|
|
627 await self.shutdown()
|
|
628 raise RuntimeError(
|
|
629 'I2C Read: set_pin_mode i2c never called for i2c port 1.')
|
|
630
|
|
631 if i2c_port:
|
|
632 if not self.i2c_2_active:
|
|
633 if self.shutdown_on_exception:
|
|
634 await self.shutdown()
|
|
635 raise RuntimeError(
|
|
636 'I2C Read: set_pin_mode i2c never called for i2c port 2.')
|
|
637
|
|
638 if not callback:
|
|
639 if self.shutdown_on_exception:
|
|
640 await self.shutdown()
|
|
641 raise RuntimeError('I2C Read: A callback function must be specified.')
|
|
642
|
|
643 if not i2c_port:
|
|
644 self.i2c_callback = callback
|
|
645 else:
|
|
646 self.i2c_callback2 = callback
|
|
647
|
|
648 if not register:
|
|
649 register = 0
|
|
650
|
|
651 if write_register:
|
|
652 write_register = 1
|
|
653 else:
|
|
654 write_register = 0
|
|
655
|
|
656 # message contains:
|
|
657 # 1. address
|
|
658 # 2. register
|
|
659 # 3. number of bytes
|
|
660 # 4. restart_transmission - True or False
|
|
661 # 5. i2c port
|
|
662 # 6. suppress write flag
|
|
663
|
|
664 command = [PrivateConstants.I2C_READ, address, register, number_of_bytes,
|
|
665 stop_transmission, i2c_port, write_register]
|
|
666 await self._send_command(command)
|
|
667
|
|
668 async def i2c_write(self, address, args, i2c_port=0):
|
|
669 """
|
|
670 Write data to an i2c device.
|
|
671
|
|
672 :param address: i2c device address
|
|
673
|
|
674 :param i2c_port: 0= port 1, 1 = port 2
|
|
675
|
|
676 :param args: A variable number of bytes to be sent to the device
|
|
677 passed in as a list
|
|
678
|
|
679 """
|
|
680 if not i2c_port:
|
|
681 if not self.i2c_1_active:
|
|
682 if self.shutdown_on_exception:
|
|
683 await self.shutdown()
|
|
684 raise RuntimeError(
|
|
685 'I2C Write: set_pin_mode i2c never called for i2c port 1.')
|
|
686
|
|
687 if i2c_port:
|
|
688 if not self.i2c_2_active:
|
|
689 if self.shutdown_on_exception:
|
|
690 await self.shutdown()
|
|
691 raise RuntimeError(
|
|
692 'I2C Write: set_pin_mode i2c never called for i2c port 2.')
|
|
693
|
|
694 command = [PrivateConstants.I2C_WRITE, len(args), address, i2c_port]
|
|
695
|
|
696 for item in args:
|
|
697 command.append(item)
|
|
698
|
|
699 await self._send_command(command)
|
|
700
|
|
701 async def loop_back(self, start_character, callback):
|
|
702 """
|
|
703 This is a debugging method to send a character to the
|
|
704 Arduino device, and have the device loop it back.
|
|
705
|
|
706 :param start_character: The character to loop back. It should be
|
|
707 an integer.
|
|
708
|
|
709 :param callback: Looped back character will appear in the callback method
|
|
710
|
|
711 """
|
|
712
|
|
713 if not callback:
|
|
714 if self.shutdown_on_exception:
|
|
715 await self.shutdown()
|
|
716 raise RuntimeError('loop_back: A callback function must be specified.')
|
|
717 command = [PrivateConstants.LOOP_COMMAND, ord(start_character)]
|
|
718 self.loop_back_callback = callback
|
|
719 await self._send_command(command)
|
|
720
|
|
721 async def set_analog_scan_interval(self, interval):
|
|
722 """
|
|
723 Set the analog scanning interval.
|
|
724
|
|
725 :param interval: value of 0 - 255 - milliseconds
|
|
726 """
|
|
727
|
|
728 if 0 <= interval <= 255:
|
|
729 command = [PrivateConstants.SET_ANALOG_SCANNING_INTERVAL, interval]
|
|
730 await self._send_command(command)
|
|
731 else:
|
|
732 if self.shutdown_on_exception:
|
|
733 await self.shutdown()
|
|
734 raise RuntimeError('Analog interval must be between 0 and 255')
|
|
735
|
|
736 async def set_pin_mode_analog_input(self, pin_number, differential=0, callback=None):
|
|
737 """
|
|
738 Set a pin as an analog input.
|
|
739
|
|
740 :param pin_number: arduino pin number
|
|
741
|
|
742 :param callback: async callback function
|
|
743
|
|
744 :param differential: difference in previous to current value before
|
|
745 report will be generated
|
|
746
|
|
747 callback returns a data list:
|
|
748
|
|
749 [pin_type, pin_number, pin_value, raw_time_stamp]
|
|
750
|
|
751 The pin_type for analog input pins = 2
|
|
752
|
|
753 """
|
|
754
|
|
755 if not callback:
|
|
756 if self.shutdown_on_exception:
|
|
757 await self.shutdown()
|
|
758 raise RuntimeError(
|
|
759 'set_pin_mode_analog_input: A callback function must be specified.')
|
|
760
|
|
761 await self._set_pin_mode(pin_number, PrivateConstants.AT_ANALOG,
|
|
762 differential, callback=callback)
|
|
763
|
|
764 async def set_pin_mode_analog_output(self, pin_number):
|
|
765 """
|
|
766
|
|
767 Set a pin as a pwm (analog output) pin.
|
|
768
|
|
769 :param pin_number:arduino pin number
|
|
770
|
|
771 """
|
|
772
|
|
773 await self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT, differential=0,
|
|
774 callback=None)
|
|
775
|
|
776 async def set_pin_mode_digital_input(self, pin_number, callback):
|
|
777 """
|
|
778 Set a pin as a digital input.
|
|
779
|
|
780 :param pin_number: arduino pin number
|
|
781
|
|
782 :param callback: async callback function
|
|
783
|
|
784 callback returns a data list:
|
|
785
|
|
786 [pin_type, pin_number, pin_value, raw_time_stamp]
|
|
787
|
|
788 The pin_type for digital input pins = 0
|
|
789
|
|
790 """
|
|
791 await self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT, differential=0,
|
|
792 callback=callback)
|
|
793
|
|
794 async def set_pin_mode_digital_input_pullup(self, pin_number, callback):
|
|
795 """
|
|
796 Set a pin as a digital input with pullup enabled.
|
|
797
|
|
798 :param pin_number: arduino pin number
|
|
799
|
|
800 :param callback: async callback function
|
|
801
|
|
802 callback returns a data list:
|
|
803
|
|
804 [pin_type, pin_number, pin_value, raw_time_stamp]
|
|
805
|
|
806 The pin_type for digital input pins with pullups enabled = 11
|
|
807
|
|
808 """
|
|
809 if not callback:
|
|
810 if self.shutdown_on_exception:
|
|
811 await self.shutdown()
|
|
812 raise RuntimeError(
|
|
813 'set_pin_mode_digital_input_pullup: A callback function must be specified.')
|
|
814
|
|
815 await self._set_pin_mode(pin_number, PrivateConstants.AT_INPUT_PULLUP,
|
|
816 differential=0, callback=callback)
|
|
817
|
|
818 async def set_pin_mode_digital_output(self, pin_number):
|
|
819 """
|
|
820 Set a pin as a digital output pin.
|
|
821
|
|
822 :param pin_number: arduino pin number
|
|
823 """
|
|
824
|
|
825 await self._set_pin_mode(pin_number, PrivateConstants.AT_OUTPUT, differential=0,
|
|
826 callback=None)
|
|
827
|
|
828 # noinspection PyIncorrectDocstring
|
|
829 async def set_pin_mode_i2c(self, i2c_port=0):
|
|
830 """
|
|
831 Establish the standard Arduino i2c pins for i2c utilization.
|
|
832
|
|
833 :param i2c_port: 0 = i2c1, 1 = i2c2
|
|
834
|
|
835 NOTES: 1. THIS METHOD MUST BE CALLED BEFORE ANY I2C REQUEST IS MADE
|
|
836 2. Callbacks are set within the individual i2c read methods of this
|
|
837 API.
|
|
838
|
|
839 See i2c_read, or i2c_read_restart_transmission.
|
|
840
|
|
841 """
|
|
842 # test for i2c port 2
|
|
843 if i2c_port:
|
|
844 # if not previously activated set it to activated
|
|
845 # and the send a begin message for this port
|
|
846 if not self.i2c_2_active:
|
|
847 self.i2c_2_active = True
|
|
848 else:
|
|
849 return
|
|
850 # port 1
|
|
851 else:
|
|
852 if not self.i2c_1_active:
|
|
853 self.i2c_1_active = True
|
|
854 else:
|
|
855 return
|
|
856
|
|
857 command = [PrivateConstants.I2C_BEGIN, i2c_port]
|
|
858 await self._send_command(command)
|
|
859
|
|
860 async def set_pin_mode_dht(self, pin, callback=None, dht_type=22):
|
|
861 """
|
|
862
|
|
863 :param pin: connection pin
|
|
864
|
|
865 :param callback: callback function
|
|
866
|
|
867 :param dht_type: either 22 for DHT22 or 11 for DHT11
|
|
868
|
|
869 Error Callback: [DHT REPORT Type, DHT_ERROR_NUMBER, PIN, DHT_TYPE, Time]
|
|
870
|
|
871 Valid Data Callback: DHT REPORT Type, DHT_DATA=, PIN, DHT_TYPE, Humidity,
|
|
872 Temperature,
|
|
873 Time]
|
|
874
|
|
875 """
|
|
876 if self.reported_features & PrivateConstants.DHT_FEATURE:
|
|
877
|
|
878 if not callback:
|
|
879 if self.shutdown_on_exception:
|
|
880 await self.shutdown()
|
|
881 raise RuntimeError('set_pin_mode_dht: A Callback must be specified')
|
|
882
|
|
883 if self.dht_count < PrivateConstants.MAX_DHTS - 1:
|
|
884 self.dht_callbacks[pin] = callback
|
|
885 self.dht_count += 1
|
|
886
|
|
887 if dht_type != 22 and dht_type != 11:
|
|
888 dht_type = 22
|
|
889
|
|
890 command = [PrivateConstants.DHT_NEW, pin, dht_type]
|
|
891 await self._send_command(command)
|
|
892 else:
|
|
893 if self.shutdown_on_exception:
|
|
894 await self.shutdown()
|
|
895 raise RuntimeError(
|
|
896 f'Maximum Number Of DHTs Exceeded - set_pin_mode_dht fails for pin {pin}')
|
|
897
|
|
898 else:
|
|
899 if self.shutdown_on_exception:
|
|
900 await self.shutdown()
|
|
901 raise RuntimeError(f'The DHT feature is disabled in the server.')
|
|
902
|
|
903 async def set_pin_mode_servo(self, pin_number, min_pulse=544, max_pulse=2400):
|
|
904 """
|
|
905
|
|
906 Attach a pin to a servo motor
|
|
907
|
|
908 :param pin_number: pin
|
|
909
|
|
910 :param min_pulse: minimum pulse width
|
|
911
|
|
912 :param max_pulse: maximum pulse width
|
|
913
|
|
914 """
|
|
915 if self.reported_features & PrivateConstants.SERVO_FEATURE:
|
|
916
|
|
917 minv = (min_pulse).to_bytes(2, byteorder="big")
|
|
918 maxv = (max_pulse).to_bytes(2, byteorder="big")
|
|
919
|
|
920 command = [PrivateConstants.SERVO_ATTACH, pin_number,
|
|
921 minv[0], minv[1], maxv[0], maxv[1]]
|
|
922 await self._send_command(command)
|
|
923 else:
|
|
924 if self.shutdown_on_exception:
|
|
925 await self.shutdown()
|
|
926 raise RuntimeError(f'The SERVO feature is disabled in the server.')
|
|
927
|
|
928 async def set_pin_mode_sonar(self, trigger_pin, echo_pin,
|
|
929 callback):
|
|
930 """
|
|
931
|
|
932 :param trigger_pin:
|
|
933
|
|
934 :param echo_pin:
|
|
935
|
|
936 :param callback: callback
|
|
937
|
|
938 """
|
|
939 if self.reported_features & PrivateConstants.SONAR_FEATURE:
|
|
940
|
|
941 if not callback:
|
|
942 if self.shutdown_on_exception:
|
|
943 await self.shutdown()
|
|
944 raise RuntimeError('set_pin_mode_sonar: A Callback must be specified')
|
|
945
|
|
946 if self.sonar_count < PrivateConstants.MAX_SONARS - 1:
|
|
947 self.sonar_callbacks[trigger_pin] = callback
|
|
948 self.sonar_count += 1
|
|
949
|
|
950 command = [PrivateConstants.SONAR_NEW, trigger_pin, echo_pin]
|
|
951 await self._send_command(command)
|
|
952 else:
|
|
953 if self.shutdown_on_exception:
|
|
954 await self.shutdown()
|
|
955 raise RuntimeError(
|
|
956 f'Maximum Number Of Sonars Exceeded - set_pin_mode_sonar fails for pin {trigger_pin}')
|
|
957 else:
|
|
958 if self.shutdown_on_exception:
|
|
959 await self.shutdown()
|
|
960 raise RuntimeError(f'The SONAR feature is disabled in the server.')
|
|
961
|
|
962 async def set_pin_mode_spi(self, chip_select_list=None):
|
|
963 """
|
|
964 Specify the list of chip select pins.
|
|
965
|
|
966 Standard Arduino MISO, MOSI and CLK pins are used for the board in use.
|
|
967
|
|
968 Chip Select is any digital output capable pin.
|
|
969
|
|
970 :param chip_select_list: this is a list of pins to be used for chip select.
|
|
971 The pins will be configured as output, and set to high
|
|
972 ready to be used for chip select.
|
|
973 NOTE: You must specify the chips select pins here!
|
|
974
|
|
975
|
|
976 command message: [command, number of cs pins, [cs pins...]]
|
|
977 """
|
|
978 if self.reported_features & PrivateConstants.SPI_FEATURE:
|
|
979
|
|
980 if type(chip_select_list) != list:
|
|
981 if self.shutdown_on_exception:
|
|
982 await self.shutdown()
|
|
983 raise RuntimeError('chip_select_list must be in the form of a list')
|
|
984 if not chip_select_list:
|
|
985 if self.shutdown_on_exception:
|
|
986 await self.shutdown()
|
|
987 raise RuntimeError('Chip select pins were not specified')
|
|
988
|
|
989 self.spi_enabled = True
|
|
990
|
|
991 command = [PrivateConstants.SPI_INIT, len(chip_select_list)]
|
|
992
|
|
993 for pin in chip_select_list:
|
|
994 command.append(pin)
|
|
995 self.cs_pins_enabled.append(pin)
|
|
996 await self._send_command(command)
|
|
997 else:
|
|
998 if self.shutdown_on_exception:
|
|
999 await self.shutdown()
|
|
1000 raise RuntimeError(f'The SPI feature is disabled in the server.')
|
|
1001
|
|
1002 async def set_pin_mode_stepper(self, interface=1, pin1=2, pin2=3, pin3=4,
|
|
1003 pin4=5, enable=True):
|
|
1004 """
|
|
1005 Stepper motor support is implemented as a proxy for the
|
|
1006 the AccelStepper library for the Arduino.
|
|
1007
|
|
1008 https://github.com/waspinator/AccelStepper
|
|
1009
|
|
1010 Instantiate a stepper motor.
|
|
1011
|
|
1012 Initialize the interface and pins for a stepper motor.
|
|
1013
|
|
1014 :param interface: Motor Interface Type:
|
|
1015
|
|
1016 1 = Stepper Driver, 2 driver pins required
|
|
1017
|
|
1018 2 = FULL2WIRE 2 wire stepper, 2 motor pins required
|
|
1019
|
|
1020 3 = FULL3WIRE 3 wire stepper, such as HDD spindle,
|
|
1021 3 motor pins required
|
|
1022
|
|
1023 4 = FULL4WIRE, 4 wire full stepper, 4 motor pins
|
|
1024 required
|
|
1025
|
|
1026 6 = HALF3WIRE, 3 wire half stepper, such as HDD spindle,
|
|
1027 3 motor pins required
|
|
1028
|
|
1029 8 = HALF4WIRE, 4 wire half stepper, 4 motor pins required
|
|
1030
|
|
1031 :param pin1: Arduino digital pin number for motor pin 1
|
|
1032
|
|
1033 :param pin2: Arduino digital pin number for motor pin 2
|
|
1034
|
|
1035 :param pin3: Arduino digital pin number for motor pin 3
|
|
1036
|
|
1037 :param pin4: Arduino digital pin number for motor pin 4
|
|
1038
|
|
1039 :param enable: If this is true, the output pins at construction time.
|
|
1040
|
|
1041 :return: Motor Reference number
|
|
1042 """
|
|
1043 if self.reported_features & PrivateConstants.STEPPERS_FEATURE:
|
|
1044
|
|
1045 if self.number_of_steppers == self.max_number_of_steppers:
|
|
1046 if self.shutdown_on_exception:
|
|
1047 await self.shutdown()
|
|
1048 raise RuntimeError('Maximum number of steppers has already been assigned')
|
|
1049
|
|
1050 if interface not in self.valid_stepper_interfaces:
|
|
1051 if self.shutdown_on_exception:
|
|
1052 await self.shutdown()
|
|
1053 raise RuntimeError('Invalid stepper interface')
|
|
1054
|
|
1055 self.number_of_steppers += 1
|
|
1056
|
|
1057 motor_id = self.next_stepper_assigned
|
|
1058 self.next_stepper_assigned += 1
|
|
1059 self.stepper_info_list[motor_id]['instance'] = True
|
|
1060
|
|
1061 # build message and send message to server
|
|
1062 command = [PrivateConstants.SET_PIN_MODE_STEPPER, motor_id, interface, pin1,
|
|
1063 pin2, pin3, pin4, enable]
|
|
1064 await self._send_command(command)
|
|
1065
|
|
1066 # return motor id
|
|
1067 return motor_id
|
|
1068 else:
|
|
1069 if self.shutdown_on_exception:
|
|
1070 await self.shutdown()
|
|
1071 raise RuntimeError(f'The Stepper feature is disabled in the server.')
|
|
1072
|
|
1073 async def spi_cs_control(self, chip_select_pin, select):
|
|
1074 """
|
|
1075 Control an SPI chip select line
|
|
1076 :param chip_select_pin: pin connected to CS
|
|
1077
|
|
1078 :param select: 0=select, 1=deselect
|
|
1079 """
|
|
1080 if not self.spi_enabled:
|
|
1081 if self.shutdown_on_exception:
|
|
1082 await self.shutdown()
|
|
1083 raise RuntimeError(f'spi_cs_control: SPI interface is not enabled.')
|
|
1084
|
|
1085 if chip_select_pin not in self.cs_pins_enabled:
|
|
1086 if self.shutdown_on_exception:
|
|
1087 await self.shutdown()
|
|
1088 raise RuntimeError(f'spi_cs_control: chip select pin never enabled.')
|
|
1089 command = [PrivateConstants.SPI_CS_CONTROL, chip_select_pin, select]
|
|
1090 await self._send_command(command)
|
|
1091
|
|
1092 async def spi_read_blocking(self, register_selection, number_of_bytes_to_read,
|
|
1093 call_back=None):
|
|
1094 """
|
|
1095 Read the specified number of bytes from the specified SPI port and
|
|
1096 call the callback function with the reported data.
|
|
1097
|
|
1098 :param register_selection: Register to be selected for read.
|
|
1099
|
|
1100 :param number_of_bytes_to_read: Number of bytes to read
|
|
1101
|
|
1102 :param call_back: Required callback function to report spi data as a
|
|
1103 result of read command
|
|
1104
|
|
1105
|
|
1106 callback returns a data list:
|
|
1107 [SPI_READ_REPORT, count of data bytes read, data bytes, time-stamp]
|
|
1108
|
|
1109 SPI_READ_REPORT = 13
|
|
1110
|
|
1111 """
|
|
1112
|
|
1113 if not self.spi_enabled:
|
|
1114 if self.shutdown_on_exception:
|
|
1115 await self.shutdown()
|
|
1116 raise RuntimeError(f'spi_read_blocking: SPI interface is not enabled.')
|
|
1117
|
|
1118 if not call_back:
|
|
1119 if self.shutdown_on_exception:
|
|
1120 await self.shutdown()
|
|
1121 raise RuntimeError('spi_read_blocking: A Callback must be specified')
|
|
1122
|
|
1123 self.spi_callback = call_back
|
|
1124
|
|
1125 command = [PrivateConstants.SPI_READ_BLOCKING, number_of_bytes_to_read,
|
|
1126 register_selection]
|
|
1127
|
|
1128 await self._send_command(command)
|
|
1129
|
|
1130 async def spi_set_format(self, clock_divisor, bit_order, data_mode):
|
|
1131 """
|
|
1132 Configure how the SPI serializes and de-serializes data on the wire.
|
|
1133
|
|
1134 See Arduino SPI reference materials for details.
|
|
1135
|
|
1136 :param clock_divisor:
|
|
1137
|
|
1138 :param bit_order:
|
|
1139
|
|
1140 LSBFIRST = 0
|
|
1141
|
|
1142 MSBFIRST = 1 (default)
|
|
1143
|
|
1144 :param data_mode:
|
|
1145
|
|
1146 SPI_MODE0 = 0x00 (default)
|
|
1147
|
|
1148 SPI_MODE1 = 0x04
|
|
1149
|
|
1150 SPI_MODE2 = 0x08
|
|
1151
|
|
1152 SPI_MODE3 = 0x0C
|
|
1153
|
|
1154 """
|
|
1155
|
|
1156 if not self.spi_enabled:
|
|
1157 if self.shutdown_on_exception:
|
|
1158 await self.shutdown()
|
|
1159 raise RuntimeError(f'spi_set_format: SPI interface is not enabled.')
|
|
1160
|
|
1161 command = [PrivateConstants.SPI_SET_FORMAT, clock_divisor, bit_order,
|
|
1162 data_mode]
|
|
1163 await self._send_command(command)
|
|
1164
|
|
1165 async def spi_write_blocking(self, bytes_to_write):
|
|
1166 """
|
|
1167 Write a list of bytes to the SPI device.
|
|
1168
|
|
1169 :param bytes_to_write: A list of bytes to write. This must
|
|
1170 be in the form of a list.
|
|
1171
|
|
1172 """
|
|
1173
|
|
1174 if not self.spi_enabled:
|
|
1175 if self.shutdown_on_exception:
|
|
1176 await self.shutdown()
|
|
1177 raise RuntimeError(f'spi_write_blocking: SPI interface is not enabled.')
|
|
1178
|
|
1179 if type(bytes_to_write) is not list:
|
|
1180 if self.shutdown_on_exception:
|
|
1181 await self.shutdown()
|
|
1182 raise RuntimeError('spi_write_blocking: bytes_to_write must be a list.')
|
|
1183
|
|
1184 command = [PrivateConstants.SPI_WRITE_BLOCKING, len(bytes_to_write)]
|
|
1185
|
|
1186 for data in bytes_to_write:
|
|
1187 command.append(data)
|
|
1188
|
|
1189 await self._send_command(command)
|
|
1190
|
|
1191 async def set_pin_mode_one_wire(self, pin):
|
|
1192 """
|
|
1193 Initialize the one wire serial bus.
|
|
1194
|
|
1195 :param pin: Data pin connected to the OneWire device
|
|
1196 """
|
|
1197 self.onewire_enabled = True
|
|
1198 command = [PrivateConstants.ONE_WIRE_INIT, pin]
|
|
1199 await self._send_command(command)
|
|
1200
|
|
1201 async def onewire_reset(self, callback=None):
|
|
1202 """
|
|
1203 Reset the onewire device
|
|
1204
|
|
1205 :param callback: required function to report reset result
|
|
1206
|
|
1207 callback returns a list:
|
|
1208 [ReportType = 14, Report Subtype = 25, reset result byte,
|
|
1209 timestamp]
|
|
1210 """
|
|
1211 if not self.onewire_enabled:
|
|
1212 if self.shutdown_on_exception:
|
|
1213 await self.shutdown()
|
|
1214 raise RuntimeError(f'onewire_reset: OneWire interface is not enabled.')
|
|
1215 if not callback:
|
|
1216 if self.shutdown_on_exception:
|
|
1217 await self.shutdown()
|
|
1218 raise RuntimeError('onewire_reset: A Callback must be specified')
|
|
1219
|
|
1220 self.onewire_callback = callback
|
|
1221
|
|
1222 command = [PrivateConstants.ONE_WIRE_RESET]
|
|
1223 await self._send_command(command)
|
|
1224
|
|
1225 async def onewire_select(self, device_address):
|
|
1226 """
|
|
1227 Select a device based on its address
|
|
1228 :param device_address: A bytearray of 8 bytes
|
|
1229 """
|
|
1230 if not self.onewire_enabled:
|
|
1231 if self.shutdown_on_exception:
|
|
1232 await self.shutdown()
|
|
1233 raise RuntimeError(f'onewire_select: OneWire interface is not enabled.')
|
|
1234
|
|
1235 if type(device_address) is not list:
|
|
1236 if self.shutdown_on_exception:
|
|
1237 await self.shutdown()
|
|
1238 raise RuntimeError('onewire_select: device address must be an array of 8 '
|
|
1239 'bytes.')
|
|
1240
|
|
1241 if len(device_address) != 8:
|
|
1242 if self.shutdown_on_exception:
|
|
1243 await self.shutdown()
|
|
1244 raise RuntimeError('onewire_select: device address must be an array of 8 '
|
|
1245 'bytes.')
|
|
1246 command = [PrivateConstants.ONE_WIRE_SELECT]
|
|
1247 for data in device_address:
|
|
1248 command.append(data)
|
|
1249 await self._send_command(command)
|
|
1250
|
|
1251 async def onewire_skip(self):
|
|
1252 """
|
|
1253 Skip the device selection. This only works if you have a
|
|
1254 single device, but you can avoid searching and use this to
|
|
1255 immediately access your device.
|
|
1256 """
|
|
1257 if not self.onewire_enabled:
|
|
1258 if self.shutdown_on_exception:
|
|
1259 await self.shutdown()
|
|
1260 raise RuntimeError(f'onewire_skip: OneWire interface is not enabled.')
|
|
1261
|
|
1262 command = [PrivateConstants.ONE_WIRE_SKIP]
|
|
1263 await self._send_command(command)
|
|
1264
|
|
1265 async def onewire_write(self, data, power=0):
|
|
1266 """
|
|
1267 Write a byte to the onewire device. If 'power' is one
|
|
1268 then the wire is held high at the end for
|
|
1269 parasitically powered devices. You
|
|
1270 are responsible for eventually de-powering it by calling
|
|
1271 another read or write.
|
|
1272
|
|
1273 :param data: byte to write.
|
|
1274 :param power: power control (see above)
|
|
1275 """
|
|
1276 if not self.onewire_enabled:
|
|
1277 if self.shutdown_on_exception:
|
|
1278 await self.shutdown()
|
|
1279 raise RuntimeError(f'onewire_write: OneWire interface is not enabled.')
|
|
1280 if 0 < data < 255:
|
|
1281 command = [PrivateConstants.ONE_WIRE_WRITE, data, power]
|
|
1282 await self._send_command(command)
|
|
1283 else:
|
|
1284 if self.shutdown_on_exception:
|
|
1285 await self.shutdown()
|
|
1286 raise RuntimeError('onewire_write: Data must be no larger than 255')
|
|
1287
|
|
1288 async def onewire_read(self, callback=None):
|
|
1289 """
|
|
1290 Read a byte from the onewire device
|
|
1291 :param callback: required function to report onewire data as a
|
|
1292 result of read command
|
|
1293
|
|
1294
|
|
1295 callback returns a data list:
|
|
1296 [ONEWIRE_REPORT, ONEWIRE_READ=29, data byte, time-stamp]
|
|
1297
|
|
1298 ONEWIRE_REPORT = 14
|
|
1299 """
|
|
1300 if not self.onewire_enabled:
|
|
1301 if self.shutdown_on_exception:
|
|
1302 await self.shutdown()
|
|
1303 raise RuntimeError(f'onewire_read: OneWire interface is not enabled.')
|
|
1304
|
|
1305 if not callback:
|
|
1306 if self.shutdown_on_exception:
|
|
1307 await self.shutdown()
|
|
1308 raise RuntimeError('onewire_read A Callback must be specified')
|
|
1309
|
|
1310 self.onewire_callback = callback
|
|
1311
|
|
1312 command = [PrivateConstants.ONE_WIRE_READ]
|
|
1313 await self._send_command(command)
|
|
1314
|
|
1315 async def onewire_reset_search(self):
|
|
1316 """
|
|
1317 Begin a new search. The next use of search will begin at the first device
|
|
1318 """
|
|
1319
|
|
1320 if not self.onewire_enabled:
|
|
1321 if self.shutdown_on_exception:
|
|
1322 await self.shutdown()
|
|
1323 raise RuntimeError(f'onewire_reset_search: OneWire interface is not '
|
|
1324 f'enabled.')
|
|
1325 else:
|
|
1326 command = [PrivateConstants.ONE_WIRE_RESET_SEARCH]
|
|
1327 await self._send_command(command)
|
|
1328
|
|
1329 async def onewire_search(self, callback=None):
|
|
1330 """
|
|
1331 Search for the next device. The device address will returned in the callback.
|
|
1332 If a device is found, the 8 byte address is contained in the callback.
|
|
1333 If no more devices are found, the address returned contains all elements set
|
|
1334 to 0xff.
|
|
1335
|
|
1336 :param callback: required function to report a onewire device address
|
|
1337
|
|
1338 callback returns a data list:
|
|
1339 [ONEWIRE_REPORT, ONEWIRE_SEARCH=31, 8 byte address, time-stamp]
|
|
1340
|
|
1341 ONEWIRE_REPORT = 14
|
|
1342 """
|
|
1343 if not self.onewire_enabled:
|
|
1344 if self.shutdown_on_exception:
|
|
1345 await self.shutdown()
|
|
1346 raise RuntimeError(f'onewire_search: OneWire interface is not enabled.')
|
|
1347
|
|
1348 if not callback:
|
|
1349 if self.shutdown_on_exception:
|
|
1350 await self.shutdown()
|
|
1351 raise RuntimeError('onewire_read A Callback must be specified')
|
|
1352
|
|
1353 self.onewire_callback = callback
|
|
1354
|
|
1355 command = [PrivateConstants.ONE_WIRE_SEARCH]
|
|
1356 await self._send_command(command)
|
|
1357
|
|
1358 async def onewire_crc8(self, address_list, callback=None):
|
|
1359 """
|
|
1360 Compute a CRC check on an array of data.
|
|
1361 :param address_list:
|
|
1362
|
|
1363 :param callback: required function to report a onewire device address
|
|
1364
|
|
1365 callback returns a data list:
|
|
1366 [ONEWIRE_REPORT, ONEWIRE_CRC8=32, CRC, time-stamp]
|
|
1367
|
|
1368 ONEWIRE_REPORT = 14
|
|
1369
|
|
1370 """
|
|
1371
|
|
1372 if not self.onewire_enabled:
|
|
1373 if self.shutdown_on_exception:
|
|
1374 await self.shutdown()
|
|
1375 raise RuntimeError(f'onewire_crc8: OneWire interface is not enabled.')
|
|
1376
|
|
1377 if not callback:
|
|
1378 if self.shutdown_on_exception:
|
|
1379 await self.shutdown()
|
|
1380 raise RuntimeError('onewire_crc8 A Callback must be specified')
|
|
1381
|
|
1382 if type(address_list) is not list:
|
|
1383 if self.shutdown_on_exception:
|
|
1384 await self.shutdown()
|
|
1385 raise RuntimeError('onewire_crc8: address list must be a list.')
|
|
1386
|
|
1387 self.onewire_callback = callback
|
|
1388
|
|
1389 address_length = len(address_list)
|
|
1390
|
|
1391 command = [PrivateConstants.ONE_WIRE_CRC8, address_length - 1]
|
|
1392
|
|
1393 for data in address_list:
|
|
1394 command.append(data)
|
|
1395
|
|
1396 await self._send_command(command)
|
|
1397
|
|
1398 async def _set_pin_mode(self, pin_number, pin_state, differential, callback):
|
|
1399 """
|
|
1400 A private method to set the various pin modes.
|
|
1401
|
|
1402 :param pin_number: arduino pin number
|
|
1403
|
|
1404 :param pin_state: INPUT/OUTPUT/ANALOG/PWM/PULLUP - for SERVO use
|
|
1405 servo_config()
|
|
1406 For DHT use: set_pin_mode_dht
|
|
1407
|
|
1408 :param differential: for analog inputs - threshold
|
|
1409 value to be achieved for report to
|
|
1410 be generated
|
|
1411
|
|
1412 :param callback: A reference to an async call back function to be
|
|
1413 called when pin data value changes
|
|
1414
|
|
1415 """
|
|
1416 if not callback and pin_state != PrivateConstants.AT_OUTPUT:
|
|
1417 if self.shutdown_on_exception:
|
|
1418 await self.shutdown()
|
|
1419 raise RuntimeError('_set_pin_mode: A Callback must be specified')
|
|
1420 else:
|
|
1421 if pin_state == PrivateConstants.AT_INPUT:
|
5
|
1422 command = [PrivateConstants.SET_PIN_MODE, pin_number, pin_state, 1]
|
3
|
1423 self.digital_callbacks[pin_number] = callback
|
|
1424 elif pin_state == PrivateConstants.AT_INPUT_PULLUP:
|
5
|
1425 command = [PrivateConstants.SET_PIN_MODE, pin_number, pin_state, 1]
|
|
1426 log.debug(f'saving yuor cb for {pin_number=} to {callback}')
|
3
|
1427 self.digital_callbacks[pin_number] = callback
|
|
1428 elif pin_state == PrivateConstants.AT_ANALOG:
|
5
|
1429 command = [PrivateConstants.SET_PIN_MODE, pin_number, pin_state, differential >> 8, differential & 0xff, 1]
|
3
|
1430 self.analog_callbacks[pin_number] = callback
|
|
1431 elif pin_state == PrivateConstants.AT_OUTPUT:
|
5
|
1432 command = [PrivateConstants.SET_PIN_MODE, pin_number, pin_state, 1]
|
3
|
1433 else:
|
|
1434 if self.shutdown_on_exception:
|
|
1435 await self.shutdown()
|
|
1436 raise RuntimeError('Unknown pin state')
|
|
1437
|
|
1438
|
|
1439 await asyncio.sleep(.05)
|
6
|
1440 await self._send_command(command)
|
|
1441
|
3
|
1442
|
|
1443 async def servo_detach(self, pin_number):
|
|
1444 """
|
|
1445 Detach a servo for reuse
|
|
1446 :param pin_number: attached pin
|
|
1447 """
|
|
1448 command = [PrivateConstants.SERVO_DETACH, pin_number]
|
|
1449 await self._send_command(command)
|
|
1450
|
|
1451 async def servo_write(self, pin_number, angle):
|
|
1452 """
|
|
1453
|
|
1454 Set a servo attached to a pin to a given angle.
|
|
1455
|
|
1456 :param pin_number: pin
|
|
1457
|
|
1458 :param angle: angle (0-180)
|
|
1459
|
|
1460 """
|
|
1461 command = [PrivateConstants.SERVO_WRITE, pin_number, angle]
|
|
1462 await self._send_command(command)
|
|
1463
|
|
1464 async def stepper_move_to(self, motor_id, position):
|
|
1465 """
|
|
1466 Set an absolution target position. If position is positive, the movement is
|
|
1467 clockwise, else it is counter-clockwise.
|
|
1468
|
|
1469 The run() function (below) will try to move the motor (at most one step per call)
|
|
1470 from the current position to the target position set by the most
|
|
1471 recent call to this function. Caution: moveTo() also recalculates the
|
|
1472 speed for the next step.
|
|
1473 If you are trying to use constant speed movements, you should call setSpeed()
|
|
1474 after calling moveTo().
|
|
1475
|
|
1476 :param motor_id: motor id: 0 - 3
|
|
1477
|
|
1478 :param position: target position. Maximum value is 32 bits.
|
|
1479 """
|
|
1480
|
|
1481 if not self.stepper_info_list[motor_id]['instance']:
|
|
1482 if self.shutdown_on_exception:
|
|
1483 await self.shutdown()
|
|
1484 raise RuntimeError('stepper_move_to: Invalid motor_id.')
|
|
1485
|
|
1486 if position < 0:
|
|
1487 polarity = 1
|
|
1488 else:
|
|
1489 polarity = 0
|
|
1490 position = abs(position)
|
|
1491
|
|
1492 position_bytes = list(position.to_bytes(4, 'big', signed=True))
|
|
1493
|
|
1494 command = [PrivateConstants.STEPPER_MOVE_TO, motor_id]
|
|
1495 for value in position_bytes:
|
|
1496 command.append(value)
|
|
1497 command.append(polarity)
|
|
1498
|
|
1499 await self._send_command(command)
|
|
1500
|
|
1501 async def stepper_move(self, motor_id, relative_position):
|
|
1502 """
|
|
1503 Set the target position relative to the current position.
|
|
1504
|
|
1505 :param motor_id: motor id: 0 - 3
|
|
1506
|
|
1507 :param relative_position: The desired position relative to the current
|
|
1508 position. Negative is anticlockwise from
|
|
1509 the current position. Maximum value is 32 bits.
|
|
1510 """
|
|
1511 if not self.stepper_info_list[motor_id]['instance']:
|
|
1512 if self.shutdown_on_exception:
|
|
1513 await self.shutdown()
|
|
1514 raise RuntimeError('stepper_move: Invalid motor_id.')
|
|
1515
|
|
1516 if relative_position < 0:
|
|
1517 polarity = 1
|
|
1518 else:
|
|
1519 polarity = 0
|
|
1520 position = abs(relative_position)
|
|
1521
|
|
1522 position_bytes = list(position.to_bytes(4, 'big', signed=True))
|
|
1523
|
|
1524 command = [PrivateConstants.STEPPER_MOVE, motor_id]
|
|
1525 for value in position_bytes:
|
|
1526 command.append(value)
|
|
1527 command.append(polarity)
|
|
1528 await self._send_command(command)
|
|
1529
|
|
1530 async def stepper_run(self, motor_id, completion_callback=None):
|
|
1531 """
|
|
1532 This method steps the selected motor based on the current speed.
|
|
1533
|
|
1534 Once called, the server will continuously attempt to step the motor.
|
|
1535
|
|
1536 :param motor_id: 0 - 3
|
|
1537
|
|
1538 :param completion_callback: call back function to receive motion complete
|
|
1539 notification
|
|
1540
|
|
1541 callback returns a data list:
|
|
1542
|
|
1543 [report_type, motor_id, raw_time_stamp]
|
|
1544
|
|
1545 The report_type = 19
|
|
1546 """
|
|
1547 if not completion_callback:
|
|
1548 if self.shutdown_on_exception:
|
|
1549 await self.shutdown()
|
|
1550 raise RuntimeError('stepper_run: A motion complete callback must be '
|
|
1551 'specified.')
|
|
1552
|
|
1553 if not self.stepper_info_list[motor_id]['instance']:
|
|
1554 if self.shutdown_on_exception:
|
|
1555 await self.shutdown()
|
|
1556 raise RuntimeError('stepper_run: Invalid motor_id.')
|
|
1557
|
|
1558 self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback
|
|
1559 command = [PrivateConstants.STEPPER_RUN, motor_id]
|
|
1560 await self._send_command(command)
|
|
1561
|
|
1562 async def stepper_run_speed(self, motor_id):
|
|
1563 """
|
|
1564 This method steps the selected motor based at a constant speed as set by the most
|
|
1565 recent call to stepper_set_max_speed(). The motor will run continuously.
|
|
1566
|
|
1567 Once called, the server will continuously attempt to step the motor.
|
|
1568
|
|
1569 :param motor_id: 0 - 3
|
|
1570
|
|
1571 """
|
|
1572 if not self.stepper_info_list[motor_id]['instance']:
|
|
1573 if self.shutdown_on_exception:
|
|
1574 await self.shutdown()
|
|
1575 raise RuntimeError('stepper_run_speed: Invalid motor_id.')
|
|
1576
|
|
1577 command = [PrivateConstants.STEPPER_RUN_SPEED, motor_id]
|
|
1578 await self._send_command(command)
|
|
1579
|
|
1580 async def stepper_set_max_speed(self, motor_id, max_speed):
|
|
1581 """
|
|
1582 Sets the maximum permitted speed. The stepper_run() function will accelerate
|
|
1583 up to the speed set by this function.
|
|
1584
|
|
1585 Caution: the maximum speed achievable depends on your processor and clock speed.
|
|
1586 The default maxSpeed is 1 step per second.
|
|
1587
|
|
1588 Caution: Speeds that exceed the maximum speed supported by the processor may
|
|
1589 result in non-linear accelerations and decelerations.
|
|
1590
|
|
1591 :param motor_id: 0 - 3
|
|
1592
|
|
1593 :param max_speed: 1 - 1000
|
|
1594 """
|
|
1595
|
|
1596 if not self.stepper_info_list[motor_id]['instance']:
|
|
1597 if self.shutdown_on_exception:
|
|
1598 await self.shutdown()
|
|
1599 raise RuntimeError('stepper_set_max_speed: Invalid motor_id.')
|
|
1600
|
|
1601 if not 1 < max_speed <= 1000:
|
|
1602 if self.shutdown_on_exception:
|
|
1603 await self.shutdown()
|
|
1604 raise RuntimeError('stepper_set_max_speed: Speed range is 1 - 1000.')
|
|
1605
|
|
1606 self.stepper_info_list[motor_id]['max_speed'] = max_speed
|
|
1607 max_speed_msb = (max_speed & 0xff00) >> 8
|
|
1608 max_speed_lsb = max_speed & 0xff
|
|
1609
|
|
1610 command = [PrivateConstants.STEPPER_SET_MAX_SPEED, motor_id, max_speed_msb,
|
|
1611 max_speed_lsb]
|
|
1612 await self._send_command(command)
|
|
1613
|
|
1614 async def stepper_get_max_speed(self, motor_id):
|
|
1615 """
|
|
1616 Returns the maximum speed configured for this stepper
|
|
1617 that was previously set by stepper_set_max_speed()
|
|
1618
|
|
1619 Value is stored in the client, so no callback is required.
|
|
1620
|
|
1621 :param motor_id: 0 - 3
|
|
1622
|
|
1623 :return: The currently configured maximum speed.
|
|
1624 """
|
|
1625 if not self.stepper_info_list[motor_id]['instance']:
|
|
1626 if self.shutdown_on_exception:
|
|
1627 await self.shutdown()
|
|
1628 raise RuntimeError('stepper_max_speed: Invalid motor_id.')
|
|
1629
|
|
1630 return self.stepper_info_list[motor_id]['max_speed']
|
|
1631
|
|
1632 async def stepper_set_acceleration(self, motor_id, acceleration):
|
|
1633 """
|
|
1634 Sets the acceleration/deceleration rate.
|
|
1635
|
|
1636 :param motor_id: 0 - 3
|
|
1637
|
|
1638 :param acceleration: The desired acceleration in steps per second
|
|
1639 per second. Must be > 0.0. This is an
|
|
1640 expensive call since it requires a square
|
|
1641 root to be calculated on the server.
|
|
1642 Dont call more often than needed.
|
|
1643
|
|
1644 """
|
|
1645 if not self.stepper_info_list[motor_id]['instance']:
|
|
1646 if self.shutdown_on_exception:
|
|
1647 await self.shutdown()
|
|
1648 raise RuntimeError('stepper_set_acceleration: Invalid motor_id.')
|
|
1649
|
|
1650 if not 1 < acceleration <= 1000:
|
|
1651 if self.shutdown_on_exception:
|
|
1652 await self.shutdown()
|
|
1653 raise RuntimeError('stepper_set_acceleration: Acceleration range is 1 - '
|
|
1654 '1000.')
|
|
1655
|
|
1656 self.stepper_info_list[motor_id]['acceleration'] = acceleration
|
|
1657
|
|
1658 max_accel_msb = acceleration >> 8
|
|
1659 max_accel_lsb = acceleration & 0xff
|
|
1660
|
|
1661 command = [PrivateConstants.STEPPER_SET_ACCELERATION, motor_id, max_accel_msb,
|
|
1662 max_accel_lsb]
|
|
1663 await self._send_command(command)
|
|
1664
|
|
1665 async def stepper_set_speed(self, motor_id, speed):
|
|
1666 """
|
|
1667 Sets the desired constant speed for use with stepper_run_speed().
|
|
1668
|
|
1669 :param motor_id: 0 - 3
|
|
1670
|
|
1671 :param speed: 0 - 1000 The desired constant speed in steps per
|
|
1672 second. Positive is clockwise. Speeds of more than 1000 steps per
|
|
1673 second are unreliable. Speed accuracy depends on the Arduino
|
|
1674 crystal. Jitter depends on how frequently you call the
|
|
1675 stepper_run_speed() method.
|
|
1676 The speed will be limited by the current value of
|
|
1677 stepper_set_max_speed().
|
|
1678 """
|
|
1679 if not self.stepper_info_list[motor_id]['instance']:
|
|
1680 if self.shutdown_on_exception:
|
|
1681 await self.shutdown()
|
|
1682 raise RuntimeError('stepper_set_speed: Invalid motor_id.')
|
|
1683
|
|
1684 if not 0 < speed <= 1000:
|
|
1685 if self.shutdown_on_exception:
|
|
1686 await self.shutdown()
|
|
1687 raise RuntimeError('stepper_set_speed: Speed range is 0 - '
|
|
1688 '1000.')
|
|
1689
|
|
1690 self.stepper_info_list[motor_id]['speed'] = speed
|
|
1691
|
|
1692 speed_msb = speed >> 8
|
|
1693 speed_lsb = speed & 0xff
|
|
1694
|
|
1695 command = [PrivateConstants.STEPPER_SET_SPEED, motor_id, speed_msb, speed_lsb]
|
|
1696 await self._send_command(command)
|
|
1697
|
|
1698 async def stepper_get_speed(self, motor_id):
|
|
1699 """
|
|
1700 Returns the most recently set speed.
|
|
1701 that was previously set by stepper_set_speed();
|
|
1702
|
|
1703 Value is stored in the client, so no callback is required.
|
|
1704
|
|
1705 :param motor_id: 0 - 3
|
|
1706
|
|
1707 """
|
|
1708 if not self.stepper_info_list[motor_id]['instance']:
|
|
1709 if self.shutdown_on_exception:
|
|
1710 await self.shutdown()
|
|
1711 raise RuntimeError('stepper_get_speed: Invalid motor_id.')
|
|
1712
|
|
1713 return self.stepper_info_list[motor_id]['speed']
|
|
1714
|
|
1715 async def stepper_get_distance_to_go(self, motor_id, distance_to_go_callback):
|
|
1716 """
|
|
1717 Request the distance from the current position to the target position
|
|
1718 from the server.
|
|
1719
|
|
1720 :param motor_id: 0 - 3
|
|
1721
|
|
1722 :param distance_to_go_callback: required callback function to receive report
|
|
1723
|
|
1724 :return: The distance to go is returned via the callback as a list:
|
|
1725
|
|
1726 [REPORT_TYPE=15, motor_id, distance in steps, time_stamp]
|
|
1727
|
|
1728 A positive distance is clockwise from the current position.
|
|
1729
|
|
1730 """
|
|
1731 if not distance_to_go_callback:
|
|
1732 if self.shutdown_on_exception:
|
|
1733 await self.shutdown()
|
|
1734 raise RuntimeError('stepper_get_distance_to_go Read: A callback function must be specified.')
|
|
1735
|
|
1736 if not self.stepper_info_list[motor_id]['instance']:
|
|
1737 if self.shutdown_on_exception:
|
|
1738 await self.shutdown()
|
|
1739 raise RuntimeError('stepper_get_distance_to_go: Invalid motor_id.')
|
|
1740 self.stepper_info_list[motor_id][
|
|
1741 'distance_to_go_callback'] = distance_to_go_callback
|
|
1742 command = [PrivateConstants.STEPPER_GET_DISTANCE_TO_GO, motor_id]
|
|
1743 await self._send_command(command)
|
|
1744
|
|
1745 async def stepper_get_target_position(self, motor_id, target_callback):
|
|
1746 """
|
|
1747 Request the most recently set target position from the server.
|
|
1748
|
|
1749 :param motor_id: 0 - 3
|
|
1750
|
|
1751 :param target_callback: required callback function to receive report
|
|
1752
|
|
1753 :return: The distance to go is returned via the callback as a list:
|
|
1754
|
|
1755 [REPORT_TYPE=16, motor_id, target position in steps, time_stamp]
|
|
1756
|
|
1757 Positive is clockwise from the 0 position.
|
|
1758
|
|
1759 """
|
|
1760 if not target_callback:
|
|
1761 if self.shutdown_on_exception:
|
|
1762 await self.shutdown()
|
|
1763 raise RuntimeError(
|
|
1764 'stepper_get_target_position Read: A callback function must be specified.')
|
|
1765
|
|
1766 if not self.stepper_info_list[motor_id]['instance']:
|
|
1767 if self.shutdown_on_exception:
|
|
1768 await self.shutdown()
|
|
1769 raise RuntimeError('stepper_get_target_position: Invalid motor_id.')
|
|
1770
|
|
1771 self.stepper_info_list[motor_id][
|
|
1772 'target_position_callback'] = target_callback
|
|
1773
|
|
1774 command = [PrivateConstants.STEPPER_GET_TARGET_POSITION, motor_id]
|
|
1775 await self._send_command(command)
|
|
1776
|
|
1777 async def stepper_get_current_position(self, motor_id, current_position_callback):
|
|
1778 """
|
|
1779 Request the current motor position from the server.
|
|
1780
|
|
1781 :param motor_id: 0 - 3
|
|
1782
|
|
1783 :param current_position_callback: required callback function to receive report
|
|
1784
|
|
1785 :return: The current motor position returned via the callback as a list:
|
|
1786
|
|
1787 [REPORT_TYPE=17, motor_id, current position in steps, time_stamp]
|
|
1788
|
|
1789 Positive is clockwise from the 0 position.
|
|
1790 """
|
|
1791 if not current_position_callback:
|
|
1792 if self.shutdown_on_exception:
|
|
1793 await self.shutdown()
|
|
1794 raise RuntimeError(
|
|
1795 'stepper_get_current_position Read: A callback function must be specified.')
|
|
1796
|
|
1797 if not self.stepper_info_list[motor_id]['instance']:
|
|
1798 if self.shutdown_on_exception:
|
|
1799 await self.shutdown()
|
|
1800 raise RuntimeError('stepper_get_current_position: Invalid motor_id.')
|
|
1801
|
|
1802 self.stepper_info_list[motor_id]['current_position_callback'] = current_position_callback
|
|
1803
|
|
1804 command = [PrivateConstants.STEPPER_GET_CURRENT_POSITION, motor_id]
|
|
1805 await self._send_command(command)
|
|
1806
|
|
1807 async def stepper_set_current_position(self, motor_id, position):
|
|
1808 """
|
|
1809 Resets the current position of the motor, so that wherever the motor
|
|
1810 happens to be right now is considered to be the new 0 position. Useful
|
|
1811 for setting a zero position on a stepper after an initial hardware
|
|
1812 positioning move.
|
|
1813
|
|
1814 Has the side effect of setting the current motor speed to 0.
|
|
1815
|
|
1816 :param motor_id: 0 - 3
|
|
1817
|
|
1818 :param position: Position in steps. This is a 32 bit value
|
|
1819 """
|
|
1820
|
|
1821 if not self.stepper_info_list[motor_id]['instance']:
|
|
1822 if self.shutdown_on_exception:
|
|
1823 await self.shutdown()
|
|
1824 raise RuntimeError('stepper_set_current_position: Invalid motor_id.')
|
|
1825 position_bytes = list(position.to_bytes(4, 'big', signed=True))
|
|
1826
|
|
1827 command = [PrivateConstants.STEPPER_SET_CURRENT_POSITION, motor_id]
|
|
1828 for value in position_bytes:
|
|
1829 command.append(value)
|
|
1830 await self._send_command(command)
|
|
1831
|
|
1832 async def stepper_run_speed_to_position(self, motor_id, completion_callback=None):
|
|
1833 """
|
|
1834 Runs the motor at the currently selected speed until the target position is
|
|
1835 reached.
|
|
1836
|
|
1837 Does not implement accelerations.
|
|
1838
|
|
1839 :param motor_id: 0 - 3
|
|
1840
|
|
1841 :param completion_callback: call back function to receive motion complete
|
|
1842 notification
|
|
1843
|
|
1844 callback returns a data list:
|
|
1845
|
|
1846 [report_type, motor_id, raw_time_stamp]
|
|
1847
|
|
1848 The report_type = 19
|
|
1849 """
|
|
1850 if not completion_callback:
|
|
1851 if self.shutdown_on_exception:
|
|
1852 await self.shutdown()
|
|
1853 raise RuntimeError('stepper_run_speed_to_position: A motion complete '
|
|
1854 'callback must be '
|
|
1855 'specified.')
|
|
1856 if not self.stepper_info_list[motor_id]['instance']:
|
|
1857 if self.shutdown_on_exception:
|
|
1858 await self.shutdown()
|
|
1859 raise RuntimeError('stepper_run_speed_to_position: Invalid motor_id.')
|
|
1860
|
|
1861 self.stepper_info_list[motor_id]['motion_complete_callback'] = completion_callback
|
|
1862 command = [PrivateConstants.STEPPER_RUN_SPEED_TO_POSITION, motor_id]
|
|
1863 await self._send_command(command)
|
|
1864
|
|
1865 async def stepper_stop(self, motor_id):
|
|
1866 """
|
|
1867 Sets a new target position that causes the stepper
|
|
1868 to stop as quickly as possible, using the current speed and
|
|
1869 acceleration parameters.
|
|
1870
|
|
1871 :param motor_id: 0 - 3
|
|
1872 """
|
|
1873 if not self.stepper_info_list[motor_id]['instance']:
|
|
1874 if self.shutdown_on_exception:
|
|
1875 await self.shutdown()
|
|
1876 raise RuntimeError('stepper_stop: Invalid motor_id.')
|
|
1877
|
|
1878 command = [PrivateConstants.STEPPER_STOP, motor_id]
|
|
1879 await self._send_command(command)
|
|
1880
|
|
1881 async def stepper_disable_outputs(self, motor_id):
|
|
1882 """
|
|
1883 Disable motor pin outputs by setting them all LOW.
|
|
1884
|
|
1885 Depending on the design of your electronics this may turn off
|
|
1886 the power to the motor coils, saving power.
|
|
1887
|
|
1888 This is useful to support Arduino low power modes: disable the outputs
|
|
1889 during sleep and then re-enable with enableOutputs() before stepping
|
|
1890 again.
|
|
1891
|
|
1892 If the enable Pin is defined, sets it to OUTPUT mode and clears
|
|
1893 the pin to disabled.
|
|
1894
|
|
1895 :param motor_id: 0 - 3
|
|
1896 """
|
|
1897 if not self.stepper_info_list[motor_id]['instance']:
|
|
1898 if self.shutdown_on_exception:
|
|
1899 await self.shutdown()
|
|
1900 raise RuntimeError('stepper_disable_outputs: Invalid motor_id.')
|
|
1901
|
|
1902 command = [PrivateConstants.STEPPER_DISABLE_OUTPUTS, motor_id]
|
|
1903 await self._send_command(command)
|
|
1904
|
|
1905 async def stepper_enable_outputs(self, motor_id):
|
|
1906 """
|
|
1907 Enable motor pin outputs by setting the motor pins to OUTPUT
|
|
1908 mode.
|
|
1909
|
|
1910 If the enable Pin is defined, sets it to OUTPUT mode and sets
|
|
1911 the pin to enabled.
|
|
1912
|
|
1913 :param motor_id: 0 - 3
|
|
1914 """
|
|
1915 if not self.stepper_info_list[motor_id]['instance']:
|
|
1916 if self.shutdown_on_exception:
|
|
1917 await self.shutdown()
|
|
1918 raise RuntimeError('stepper_enable_outputs: Invalid motor_id.')
|
|
1919
|
|
1920 command = [PrivateConstants.STEPPER_ENABLE_OUTPUTS, motor_id]
|
|
1921 await self._send_command(command)
|
|
1922
|
|
1923 async def stepper_set_min_pulse_width(self, motor_id, minimum_width):
|
|
1924 """
|
|
1925 Sets the minimum pulse width allowed by the stepper driver.
|
|
1926
|
|
1927 The minimum practical pulse width is approximately 20 microseconds.
|
|
1928
|
|
1929 Times less than 20 microseconds will usually result in 20 microseconds or so.
|
|
1930
|
|
1931 :param motor_id: 0 -3
|
|
1932
|
|
1933 :param minimum_width: A 16 bit unsigned value expressed in microseconds.
|
|
1934 """
|
|
1935 if not self.stepper_info_list[motor_id]['instance']:
|
|
1936 if self.shutdown_on_exception:
|
|
1937 await self.shutdown()
|
|
1938 raise RuntimeError('stepper_set_min_pulse_width: Invalid motor_id.')
|
|
1939
|
|
1940 if not 0 < minimum_width <= 0xff:
|
|
1941 if self.shutdown_on_exception:
|
|
1942 await self.shutdown()
|
|
1943 raise RuntimeError('stepper_set_min_pulse_width: Pulse width range = '
|
|
1944 '0-0xffff.')
|
|
1945
|
|
1946 width_msb = minimum_width >> 8
|
|
1947 width_lsb = minimum_width & 0xff
|
|
1948
|
|
1949 command = [PrivateConstants.STEPPER_SET_MINIMUM_PULSE_WIDTH, motor_id, width_msb,
|
|
1950 width_lsb]
|
|
1951 await self._send_command(command)
|
|
1952
|
|
1953 async def stepper_set_enable_pin(self, motor_id, pin=0xff):
|
|
1954 """
|
|
1955 Sets the enable pin number for stepper drivers.
|
|
1956 0xFF indicates unused (default).
|
|
1957
|
|
1958 Otherwise, if a pin is set, the pin will be turned on when
|
|
1959 enableOutputs() is called and switched off when disableOutputs()
|
|
1960 is called.
|
|
1961
|
|
1962 :param motor_id: 0 - 4
|
|
1963 :param pin: 0-0xff
|
|
1964 """
|
|
1965 if not self.stepper_info_list[motor_id]['instance']:
|
|
1966 if self.shutdown_on_exception:
|
|
1967 await self.shutdown()
|
|
1968 raise RuntimeError('stepper_set_enable_pin: Invalid motor_id.')
|
|
1969
|
|
1970 if not 0 < pin <= 0xff:
|
|
1971 if self.shutdown_on_exception:
|
|
1972 await self.shutdown()
|
|
1973 raise RuntimeError('stepper_set_enable_pin: Pulse width range = '
|
|
1974 '0-0xff.')
|
|
1975 command = [PrivateConstants.STEPPER_SET_ENABLE_PIN, motor_id, pin]
|
|
1976
|
|
1977 await self._send_command(command)
|
|
1978
|
|
1979 async def stepper_set_3_pins_inverted(self, motor_id, direction=False, step=False,
|
|
1980 enable=False):
|
|
1981 """
|
|
1982 Sets the inversion for stepper driver pins.
|
|
1983
|
|
1984 :param motor_id: 0 - 3
|
|
1985
|
|
1986 :param direction: True=inverted or False
|
|
1987
|
|
1988 :param step: True=inverted or False
|
|
1989
|
|
1990 :param enable: True=inverted or False
|
|
1991 """
|
|
1992 if not self.stepper_info_list[motor_id]['instance']:
|
|
1993 if self.shutdown_on_exception:
|
|
1994 await self.shutdown()
|
|
1995 raise RuntimeError('stepper_set_3_pins_inverted: Invalid motor_id.')
|
|
1996
|
|
1997 command = [PrivateConstants.STEPPER_SET_3_PINS_INVERTED, motor_id, direction,
|
|
1998 step, enable]
|
|
1999
|
|
2000 await self._send_command(command)
|
|
2001
|
|
2002 async def stepper_set_4_pins_inverted(self, motor_id, pin1_invert=False,
|
|
2003 pin2_invert=False,
|
|
2004 pin3_invert=False, pin4_invert=False, enable=False):
|
|
2005 """
|
|
2006 Sets the inversion for 2, 3 and 4 wire stepper pins
|
|
2007
|
|
2008 :param motor_id: 0 - 3
|
|
2009
|
|
2010 :param pin1_invert: True=inverted or False
|
|
2011
|
|
2012 :param pin2_invert: True=inverted or False
|
|
2013
|
|
2014 :param pin3_invert: True=inverted or False
|
|
2015
|
|
2016 :param pin4_invert: True=inverted or False
|
|
2017
|
|
2018 :param enable: True=inverted or False
|
|
2019 """
|
|
2020 if not self.stepper_info_list[motor_id]['instance']:
|
|
2021 if self.shutdown_on_exception:
|
|
2022 await self.shutdown()
|
|
2023 raise RuntimeError('stepper_set_4_pins_inverted: Invalid motor_id.')
|
|
2024
|
|
2025 command = [PrivateConstants.STEPPER_SET_4_PINS_INVERTED, motor_id, pin1_invert,
|
|
2026 pin2_invert, pin3_invert, pin4_invert, enable]
|
|
2027
|
|
2028 await self._send_command(command)
|
|
2029
|
|
2030 async def stepper_is_running(self, motor_id, callback):
|
|
2031 """
|
|
2032 Checks to see if the motor is currently running to a target.
|
|
2033
|
|
2034 Callback return True if the speed is not zero or not at the target position.
|
|
2035
|
|
2036 :param motor_id: 0-4
|
|
2037
|
|
2038 :param callback: required callback function to receive report
|
|
2039
|
|
2040 :return: The current running state returned via the callback as a list:
|
|
2041
|
|
2042 [REPORT_TYPE=18, motor_id, True or False for running state, time_stamp]
|
|
2043 """
|
|
2044 if not callback:
|
|
2045 if self.shutdown_on_exception:
|
|
2046 await self.shutdown()
|
|
2047 raise RuntimeError(
|
|
2048 'stepper_is_running: A callback function must be specified.')
|
|
2049
|
|
2050 if not self.stepper_info_list[motor_id]['instance']:
|
|
2051 if self.shutdown_on_exception:
|
|
2052 await self.shutdown()
|
|
2053 raise RuntimeError('stepper_is_running: Invalid motor_id.')
|
|
2054
|
|
2055 self.stepper_info_list[motor_id]['is_running_callback'] = callback
|
|
2056
|
|
2057 command = [PrivateConstants.STEPPER_IS_RUNNING, motor_id]
|
|
2058 await self._send_command(command)
|
|
2059
|
|
2060 async def shutdown(self):
|
|
2061 """
|
|
2062 This method attempts an orderly shutdown
|
|
2063 If any exceptions are thrown, they are ignored.
|
|
2064
|
|
2065 """
|
|
2066 self.shutdown_flag = True
|
|
2067 # stop all reporting - both analog and digital
|
|
2068 try:
|
|
2069 if self.serial_port:
|
|
2070 command = [PrivateConstants.STOP_ALL_REPORTS]
|
|
2071 await self._send_command(command)
|
|
2072
|
|
2073 time.sleep(.5)
|
|
2074
|
|
2075 await self.serial_port.reset_input_buffer()
|
|
2076 await self.serial_port.close()
|
|
2077 if self.close_loop_on_shutdown:
|
|
2078 self.loop.stop()
|
|
2079 elif self.sock:
|
|
2080 command = [PrivateConstants.STOP_ALL_REPORTS]
|
|
2081 await self._send_command(command)
|
|
2082 self.the_task.cancel()
|
|
2083 time.sleep(.5)
|
|
2084 if self.close_loop_on_shutdown:
|
|
2085 self.loop.stop()
|
|
2086 except (RuntimeError, SerialException):
|
|
2087 pass
|
|
2088
|
|
2089 async def disable_all_reporting(self):
|
|
2090 """
|
|
2091 Disable reporting for all digital and analog input pins
|
|
2092 """
|
|
2093 command = [PrivateConstants.MODIFY_REPORTING,
|
|
2094 PrivateConstants.REPORTING_DISABLE_ALL, 0]
|
|
2095 await self._send_command(command)
|
|
2096
|
|
2097 async def disable_analog_reporting(self, pin):
|
|
2098 """
|
|
2099 Disables analog reporting for a single analog pin.
|
|
2100
|
|
2101 :param pin: Analog pin number. For example for A0, the number is 0.
|
|
2102
|
|
2103 """
|
|
2104 command = [PrivateConstants.MODIFY_REPORTING,
|
|
2105 PrivateConstants.REPORTING_ANALOG_DISABLE, pin]
|
|
2106 await self._send_command(command)
|
|
2107
|
|
2108 async def disable_digital_reporting(self, pin):
|
|
2109 """
|
|
2110 Disables digital reporting for a single digital pin
|
|
2111
|
|
2112
|
|
2113 :param pin: pin number
|
|
2114
|
|
2115 """
|
|
2116 command = [PrivateConstants.MODIFY_REPORTING,
|
|
2117 PrivateConstants.REPORTING_DIGITAL_DISABLE, pin]
|
|
2118 await self._send_command(command)
|
|
2119
|
|
2120 async def enable_analog_reporting(self, pin):
|
|
2121 """
|
|
2122 Enables analog reporting for the specified pin.
|
|
2123
|
|
2124 :param pin: Analog pin number. For example for A0, the number is 0.
|
|
2125
|
|
2126
|
|
2127 """
|
|
2128 command = [PrivateConstants.MODIFY_REPORTING,
|
|
2129 PrivateConstants.REPORTING_ANALOG_ENABLE, pin]
|
|
2130 await self._send_command(command)
|
|
2131
|
|
2132 async def enable_digital_reporting(self, pin):
|
|
2133 """
|
|
2134 Enable reporting on the specified digital pin.
|
|
2135
|
|
2136 :param pin: Pin number.
|
|
2137 """
|
|
2138
|
5
|
2139 log.debug(f'enable_digital_reporting {pin=}')
|
|
2140 command = [PrivateConstants.MODIFY_REPORTING, PrivateConstants.REPORTING_DIGITAL_ENABLE, pin]
|
3
|
2141 await self._send_command(command)
|
|
2142
|
|
2143 async def _arduino_report_dispatcher(self):
|
|
2144 """
|
|
2145 This is a private method.
|
|
2146 It continually accepts and interprets data coming from Telemetrix4Arduino,and then
|
|
2147 dispatches the correct handler to process the data.
|
|
2148
|
|
2149 It first receives the length of the packet, and then reads in the rest of the
|
|
2150 packet. A packet consists of a length, report identifier and then the report data.
|
|
2151 Using the report identifier, the report handler is fetched from report_dispatch.
|
|
2152
|
|
2153 :returns: This method never returns
|
|
2154 """
|
|
2155 while True:
|
|
2156 if self.shutdown_flag:
|
|
2157 break
|
|
2158 try:
|
|
2159 if not self.ip_address:
|
|
2160 packet_length = await self.serial_port.read()
|
|
2161 else:
|
|
2162
|
|
2163 packet_length = ord(await self.sock.read())
|
|
2164
|
|
2165 except TypeError:
|
|
2166 continue
|
|
2167
|
|
2168 # get the rest of the packet
|
|
2169 if not self.ip_address:
|
|
2170 packet = await self.serial_port.read(packet_length)
|
|
2171 else:
|
|
2172 packet = list(await self.sock.read(packet_length))
|
|
2173
|
|
2174 report = packet[0]
|
|
2175 await self.report_dispatch[report](packet[1:])
|
|
2176 await asyncio.sleep(self.sleep_tune)
|
|
2177
|
|
2178 '''
|
|
2179 Report message handlers
|
|
2180 '''
|
|
2181
|
|
2182 async def _report_loop_data(self, data):
|
|
2183 """
|
|
2184 Print data that was looped back
|
|
2185
|
|
2186 :param data: byte of loop back data
|
|
2187 """
|
|
2188 if self.loop_back_callback:
|
|
2189 await self.loop_back_callback(data)
|
|
2190
|
|
2191 async def _spi_report(self, report):
|
|
2192
|
|
2193 cb_list = [PrivateConstants.SPI_REPORT, report[0]] + report[1:]
|
|
2194
|
|
2195 cb_list.append(time.time())
|
|
2196
|
|
2197 await self.spi_callback(cb_list)
|
|
2198
|
|
2199 async def _onewire_report(self, report):
|
|
2200 cb_list = [PrivateConstants.ONE_WIRE_REPORT, report[0]] + report[1:]
|
|
2201 cb_list.append(time.time())
|
|
2202 await self.onewire_callback(cb_list)
|
|
2203
|
|
2204 async def _report_debug_data(self, data):
|
|
2205 """
|
|
2206 Print debug data sent from Arduino
|
|
2207
|
|
2208 :param data: data[0] is a byte followed by 2
|
|
2209 bytes that comprise an integer
|
|
2210 """
|
|
2211 value = (data[1] << 8) + data[2]
|
|
2212 print(f'DEBUG ID: {data[0]} Value: {value}')
|
|
2213
|
|
2214 async def _analog_message(self, data):
|
|
2215 """
|
|
2216 This is a private message handler method.
|
|
2217 It is a message handler for analog messages.
|
|
2218
|
|
2219 :param data: message data
|
|
2220
|
|
2221 """
|
|
2222 pin = data[0]
|
|
2223 value = (data[1] << 8) + data[2]
|
|
2224
|
|
2225 time_stamp = time.time()
|
|
2226
|
|
2227 # append pin number, pin value, and pin type to return value and return as a list
|
|
2228 message = [PrivateConstants.AT_ANALOG, pin, value, time_stamp]
|
|
2229
|
|
2230 await self.analog_callbacks[pin](message)
|
|
2231
|
|
2232 async def _dht_report(self, data):
|
|
2233 """
|
|
2234 This is a private message handler for dht reports
|
|
2235
|
|
2236 :param data: data[0] = report error return
|
|
2237 No Errors = 0
|
|
2238
|
|
2239 Checksum Error = 1
|
|
2240
|
|
2241 Timeout Error = 2
|
|
2242
|
|
2243 Invalid Value = 999
|
|
2244
|
|
2245 data[1] = pin number
|
|
2246
|
|
2247 data[2] = dht type 11 or 22
|
|
2248
|
|
2249 data[3] = humidity positivity flag
|
|
2250
|
|
2251 data[4] = temperature positivity value
|
|
2252
|
|
2253 data[5] = humidity integer
|
|
2254
|
|
2255 data[6] = humidity fractional value
|
|
2256
|
|
2257 data[7] = temperature integer
|
|
2258
|
|
2259 data[8] = temperature fractional value
|
|
2260 """
|
|
2261 if data[0]: # DHT_ERROR
|
|
2262 # error report
|
|
2263 # data[0] = report sub type, data[1] = pin, data[2] = error message
|
|
2264 if self.dht_callbacks[data[1]]:
|
|
2265 # Callback 0=DHT REPORT, DHT_ERROR, PIN, Time
|
|
2266 message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2],
|
|
2267 time.time()]
|
|
2268 await self.dht_callbacks[data[1]](message)
|
|
2269 else:
|
|
2270 # got valid data DHT_DATA
|
|
2271 f_humidity = float(data[5] + data[6] / 100)
|
|
2272 if data[3]:
|
|
2273 f_humidity *= -1.0
|
|
2274 f_temperature = float(data[7] + data[8] / 100)
|
|
2275 if data[4]:
|
|
2276 f_temperature *= -1.0
|
|
2277 message = [PrivateConstants.DHT_REPORT, data[0], data[1], data[2],
|
|
2278 f_humidity, f_temperature, time.time()]
|
|
2279
|
|
2280 await self.dht_callbacks[data[1]](message)
|
|
2281
|
|
2282 async def _digital_message(self, data):
|
|
2283 """
|
|
2284 This is a private message handler method.
|
|
2285 It is a message handler for Digital Messages.
|
|
2286
|
|
2287 :param data: digital message
|
|
2288
|
|
2289 """
|
|
2290 pin = data[0]
|
|
2291 value = data[1]
|
|
2292
|
|
2293 time_stamp = time.time()
|
5
|
2294 log.debug(f'digital_callbacks[{pin}] is {self.digital_callbacks.get(pin)}')
|
3
|
2295 if self.digital_callbacks[pin]:
|
|
2296 message = [PrivateConstants.DIGITAL_REPORT, pin, value, time_stamp]
|
|
2297 await self.digital_callbacks[pin](message)
|
|
2298
|
|
2299 async def _servo_unavailable(self, report):
|
|
2300 """
|
|
2301 Message if no servos are available for use.
|
|
2302
|
|
2303 :param report: pin number
|
|
2304 """
|
|
2305 if self.shutdown_on_exception:
|
|
2306 await self.shutdown()
|
|
2307 raise RuntimeError(
|
|
2308 f'Servo Attach For Pin {report[0]} Failed: No Available Servos')
|
|
2309
|
|
2310 async def _i2c_read_report(self, data):
|
|
2311 """
|
|
2312 Execute callback for i2c reads.
|
|
2313
|
|
2314 :param data: [I2C_READ_REPORT, i2c_port, number of bytes read, address, register, bytes read..., time-stamp]
|
|
2315 """
|
|
2316
|
|
2317 # we receive [# data bytes, address, register, data bytes]
|
|
2318 # number of bytes of data returned
|
|
2319
|
|
2320 # data[0] = number of bytes
|
|
2321 # data[1] = i2c_port
|
|
2322 # data[2] = number of bytes returned
|
|
2323 # data[3] = address
|
|
2324 # data[4] = register
|
|
2325 # data[5] ... all the data bytes
|
|
2326
|
|
2327 cb_list = [PrivateConstants.I2C_READ_REPORT, data[0], data[1]] + data[2:]
|
|
2328 cb_list.append(time.time())
|
|
2329
|
|
2330 if cb_list[1]:
|
|
2331 await self.i2c_callback2(cb_list)
|
|
2332 else:
|
|
2333 await self.i2c_callback(cb_list)
|
|
2334
|
|
2335 async def _i2c_too_few(self, data):
|
|
2336 """
|
|
2337 I2c reports too few bytes received
|
|
2338
|
|
2339 :param data: data[0] = device address
|
|
2340 """
|
|
2341 if self.shutdown_on_exception:
|
|
2342 await self.shutdown()
|
|
2343 raise RuntimeError(
|
|
2344 f'i2c too few bytes received from i2c port {data[0]} i2c address {data[1]}')
|
|
2345
|
|
2346 async def _i2c_too_many(self, data):
|
|
2347 """
|
|
2348 I2c reports too few bytes received
|
|
2349
|
|
2350 :param data: data[0] = device address
|
|
2351 """
|
|
2352 if self.shutdown_on_exception:
|
|
2353 await self.shutdown()
|
|
2354 raise RuntimeError(
|
|
2355 f'i2c too many bytes received from i2c port {data[0]} i2c address {data[1]}')
|
|
2356
|
|
2357 async def _sonar_distance_report(self, report):
|
|
2358 """
|
|
2359
|
|
2360 :param report: data[0] = trigger pin, data[1] and data[2] = distance
|
|
2361
|
|
2362 callback report format: [PrivateConstants.SONAR_DISTANCE, trigger_pin, distance_value, time_stamp]
|
|
2363 """
|
|
2364
|
|
2365 # get callback from pin number
|
|
2366 cb = self.sonar_callbacks[report[0]]
|
|
2367
|
|
2368 # build report data
|
|
2369 cb_list = [PrivateConstants.SONAR_DISTANCE, report[0],
|
|
2370 ((report[1] << 8) + report[2]), time.time()]
|
|
2371
|
|
2372 await cb(cb_list)
|
|
2373
|
|
2374 async def _stepper_distance_to_go_report(self, report):
|
|
2375 """
|
|
2376 Report stepper distance to go.
|
|
2377
|
|
2378 :param report: data[0] = motor_id, data[1] = steps MSB, data[2] = steps byte 1,
|
|
2379 data[3] = steps bytes 2, data[4] = steps LSB
|
|
2380
|
|
2381 callback report format: [PrivateConstants.STEPPER_DISTANCE_TO_GO, motor_id
|
|
2382 steps, time_stamp]
|
|
2383 """
|
|
2384
|
|
2385 # get callback
|
|
2386 cb = self.stepper_info_list[report[0]]['distance_to_go_callback']
|
|
2387
|
|
2388 # isolate the steps bytes and covert list to bytes
|
|
2389 steps = bytes(report[1:])
|
|
2390
|
|
2391 # get value from steps
|
|
2392 num_steps = int.from_bytes(steps, byteorder='big', signed=True)
|
|
2393
|
|
2394 cb_list = [PrivateConstants.STEPPER_DISTANCE_TO_GO, report[0], num_steps,
|
|
2395 time.time()]
|
|
2396
|
|
2397 await cb(cb_list)
|
|
2398
|
|
2399 async def _stepper_target_position_report(self, report):
|
|
2400 """
|
|
2401 Report stepper target position to go.
|
|
2402
|
|
2403 :param report: data[0] = motor_id, data[1] = target position MSB,
|
|
2404 data[2] = target position byte MSB+1
|
|
2405 data[3] = target position byte MSB+2
|
|
2406 data[4] = target position LSB
|
|
2407
|
|
2408 callback report format: [PrivateConstants.STEPPER_TARGET_POSITION, motor_id
|
|
2409 target_position, time_stamp]
|
|
2410 """
|
|
2411
|
|
2412 # get callback
|
|
2413 cb = self.stepper_info_list[report[0]]['target_position_callback']
|
|
2414
|
|
2415 # isolate the steps bytes and covert list to bytes
|
|
2416 target = bytes(report[1:])
|
|
2417
|
|
2418 # get value from steps
|
|
2419 target_position = int.from_bytes(target, byteorder='big', signed=True)
|
|
2420
|
|
2421 cb_list = [PrivateConstants.STEPPER_TARGET_POSITION, report[0], target_position,
|
|
2422 time.time()]
|
|
2423
|
|
2424 await cb(cb_list)
|
|
2425
|
|
2426 async def _stepper_current_position_report(self, report):
|
|
2427 """
|
|
2428 Report stepper current position.
|
|
2429
|
|
2430 :param report: data[0] = motor_id, data[1] = current position MSB,
|
|
2431 data[2] = current position byte MSB+1
|
|
2432 data[3] = current position byte MSB+2
|
|
2433 data[4] = current position LSB
|
|
2434
|
|
2435 callback report format: [PrivateConstants.STEPPER_CURRENT_POSITION, motor_id
|
|
2436 current_position, time_stamp]
|
|
2437 """
|
|
2438
|
|
2439 # get callback
|
|
2440 cb = self.stepper_info_list[report[0]]['current_position_callback']
|
|
2441
|
|
2442 # isolate the steps bytes and covert list to bytes
|
|
2443 position = bytes(report[1:])
|
|
2444
|
|
2445 # get value from steps
|
|
2446 current_position = int.from_bytes(position, byteorder='big', signed=True)
|
|
2447
|
|
2448 cb_list = [PrivateConstants.STEPPER_CURRENT_POSITION, report[0], current_position,
|
|
2449 time.time()]
|
|
2450
|
|
2451 await cb(cb_list)
|
|
2452
|
|
2453 async def _stepper_is_running_report(self, report):
|
|
2454 """
|
|
2455 Report if the motor is currently running
|
|
2456
|
|
2457 :param report: data[0] = motor_id, True if motor is running or False if it is not.
|
|
2458
|
|
2459 callback report format: [18, motor_id,
|
|
2460 running_state, time_stamp]
|
|
2461 """
|
|
2462
|
|
2463 # get callback
|
|
2464 cb = self.stepper_info_list[report[0]]['is_running_callback']
|
|
2465
|
|
2466 cb_list = [PrivateConstants.STEPPER_RUNNING_REPORT, report[0], time.time()]
|
|
2467
|
|
2468 await cb(cb_list)
|
|
2469
|
|
2470 async def _stepper_run_complete_report(self, report):
|
|
2471 """
|
|
2472 The motor completed it motion
|
|
2473
|
|
2474 :param report: data[0] = motor_id
|
|
2475
|
|
2476 callback report format: [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, motor_id,
|
|
2477 time_stamp]
|
|
2478 """
|
|
2479
|
|
2480 # get callback
|
|
2481 cb = self.stepper_info_list[report[0]]['motion_complete_callback']
|
|
2482
|
|
2483 cb_list = [PrivateConstants.STEPPER_RUN_COMPLETE_REPORT, report[0],
|
|
2484 time.time()]
|
|
2485
|
|
2486 await cb(cb_list)
|
|
2487
|
|
2488 async def _features_report(self, report):
|
|
2489 self.reported_features = report[0]
|
|
2490
|
|
2491 async def _send_command(self, command):
|
|
2492 """
|
|
2493 This is a private utility method.
|
|
2494
|
|
2495
|
|
2496 :param command: command data in the form of a list
|
|
2497
|
|
2498 :returns: number of bytes sent
|
|
2499 """
|
6
|
2500 if self.serial_port is None:
|
|
2501 raise TypeError
|
|
2502 if command:
|
|
2503 # the length of the list is added at the head
|
|
2504 command.insert(0, len(command))
|
|
2505 # print(command)
|
|
2506 send_message = bytes(command)
|
|
2507 log.debug(f'sending {CommandName[command[1]]} {command[2:]}')
|
|
2508 if not self.ip_address:
|
|
2509 await self.serial_port.write(send_message)
|
|
2510 else:
|
|
2511 await self.sock.write(send_message)
|
|
2512 # await asyncio.sleep(.1)
|
3
|
2513 else:
|
|
2514 await self.sock.write(send_message)
|
|
2515 # await asyncio.sleep(.1)
|