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