1 """
2 Copyright (c) 2015 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 General Public
6 License as published by the Free Software Foundation; either
7 version 3 of the License, or (at your option) any later version.
8
9 This library is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 General Public License for more details.
13
14 You should have received a copy of the GNU General Public
15 License along with this library; if not, write to the Free Software
16 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
17 """
18
19 import asyncio
20 import sys
21 import time
22 import signal
23
24
25 import serial
26
27 from .constants import Constants
28 from .private_constants import PrivateConstants
29 from .pin_data import PinData
30 from .pymata_serial import PymataSerial
34 """
35 This class exposes and implements the pymata_core asyncio API, It includes the public API methods as well as
36 a set of private methods. If your application is using asyncio, this is the API that you should use.
37
38 After instantiating this class, its "start" method MUST be called to perform Arduino pin auto-detection.
39 """
40
41 - def __init__(self, arduino_wait=2, sleep_tune=.001, com_port=None):
42 """
43 This is the "constructor" method for the PymataCore class.
44 @param arduino_wait: Amount of time to wait for Arduino to reset. UNO takes 2 seconds, Leonardo can be zero
45 @param sleep_tune: This parameter sets the amount of time PyMata core uses to set asyncio.sleep
46 @param com_port: Manually selected com port - normally it is auto-detected
47 @return: This method never returns
48 """
49 self.sleep_tune = sleep_tune
50 self.arduino_wait = arduino_wait
51 self.com_port = com_port
52
53
54 self.command_dictionary = {PrivateConstants.REPORT_VERSION: self._report_version,
55 PrivateConstants.REPORT_FIRMWARE: self._report_firmware,
56 PrivateConstants.CAPABILITY_RESPONSE: self._capability_response,
57 PrivateConstants.ANALOG_MAPPING_RESPONSE: self._analog_mapping_response,
58 PrivateConstants.PIN_STATE_RESPONSE: self._pin_state_response,
59 PrivateConstants.STRING_DATA: self._string_data,
60 PrivateConstants.ANALOG_MESSAGE: self._analog_message,
61 PrivateConstants.DIGITAL_MESSAGE: self._digital_message,
62 PrivateConstants.I2C_REPLY: self._i2c_reply,
63 PrivateConstants.SONAR_DATA: self._sonar_data,
64 PrivateConstants.ENCODER_DATA: self._encoder_data}
65
66
67 self.query_reply_data = {PrivateConstants.REPORT_VERSION: '', PrivateConstants.STRING_DATA: '',
68 PrivateConstants.REPORT_FIRMWARE: '',
69 PrivateConstants.CAPABILITY_RESPONSE: None,
70 PrivateConstants.ANALOG_MAPPING_RESPONSE: None,
71 PrivateConstants.PIN_STATE_RESPONSE: None}
72
73
74
75
76
77
78 self.i2c_map = {}
79
80
81
82
83
84 self.active_sonar_map = {}
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120 self.latch_map = {}
121
122 print('{}{}{}'.format('\n', "pymata_aio Version " + PrivateConstants.PYMATA_VERSION,
123 '\tCopyright (c) 2015 Alan Yorinks All rights reserved.\n'))
124 sys.stdout.flush()
125
126 if com_port is None:
127 self.com_port = self._discover_port()
128
129 self.sleep_tune = sleep_tune
130
131
132 self.analog_pins = []
133 self.digital_pins = []
134 self.loop = None
135 self.the_task = None
136 self.serial_port = None
137
139 """
140 This method must be called immediately after the class is instantiated. It instantiates the serial
141 interface and then performs auto pin discovery.
142 @return:No return value.
143 """
144 self.loop = asyncio.get_event_loop()
145 try:
146 self.serial_port = PymataSerial(self.com_port, 57600, self.sleep_tune)
147 except serial.SerialException:
148 print('Cannot instantiate serial interface: ' + self.com_port)
149 sys.exit(0)
150
151
152 time.sleep(self.arduino_wait)
153
154
155 self.loop = asyncio.get_event_loop()
156 self.the_task = self.loop.create_task(self._command_dispatcher())
157
158
159 asyncio.async(self.get_analog_map())
160
161
162 report = self.loop.run_until_complete(self.get_analog_map())
163 if not report:
164 print('\n\n{} {} ***'.format('*** Analog map query timed out waiting for port:',
165 self.serial_port.com_port))
166 print('\nIs your Arduino plugged in?')
167 try:
168 loop = asyncio.get_event_loop()
169 for t in asyncio.Task.all_tasks(loop):
170 t.cancel()
171 loop.run_until_complete(asyncio.sleep(.1))
172 loop.close()
173 loop.stop()
174 sys.exit(0)
175 except RuntimeError:
176
177 sys.exit(0)
178 except TypeError:
179 sys.exit(0)
180
181
182 for pin in report:
183 digital_data = PinData()
184 self.digital_pins.append(digital_data)
185 if pin != Constants.IGNORE:
186 analog_data = PinData()
187 self.analog_pins.append(analog_data)
188
189 print('{} {} {} {} {}'.format('Auto-discovery complete. Found', len(self.digital_pins), 'Digital Pins and',
190 len(self.analog_pins), 'Analog Pins\n\n'))
191
192 @asyncio.coroutine
194 """
195 Retrieve the last data update for the specified analog pin.
196 @param pin: Analog pin number (ex. A2 is specified as 2)
197 @return: Last value reported for the analog pin
198 """
199 return self.analog_pins[pin].current_value
200
201 @asyncio.coroutine
214
215 @asyncio.coroutine
217 """
218 Retrieve the last data update for the specified digital pin.
219 @param pin: Digital pin number
220 @return: Last value reported for the digital pin
221 """
222 return self.digital_pins[pin].current_value
223
224 @asyncio.coroutine
249
250 @asyncio.coroutine
259
260 @asyncio.coroutine
271
272 @asyncio.coroutine
274 """
275 This command enables the rotary encoder (2 pin + ground) and will
276 enable encoder reporting.
277
278 NOTE: This command is not currently part of standard arduino firmata, but is provided for legacy
279 support of CodeShield on an Arduino UNO.
280
281 Encoder data is retrieved by performing a digital_read from pin a (encoder pin_a)
282
283 @param pin_a: Encoder pin 1.
284 @param pin_b: Encoder pin 2.
285 @param cb: callback function to report encoder changes
286 @return: No return value
287 """
288 data = [pin_a, pin_b]
289 if cb:
290 self.digital_pins[pin_a].cb = cb
291 yield from self._send_sysex(PrivateConstants.ENCODER_CONFIG, data)
292
293 @asyncio.coroutine
295 """
296 This method retrieves the latest encoder data value
297 @param pin: Encoder Pin
298 @return: encoder data value
299 """
300 return self.digital_pins[pin].current_value
301
302 @asyncio.coroutine
311
312 @asyncio.coroutine
314 """
315 Enables digital reporting. By turning reporting on for all 8 bits in the "port" -
316 this is part of Firmata's protocol specification.
317 @param pin: Pin and all pins for this port
318 @return: No return value
319 """
320 port = pin // 8
321 command = [PrivateConstants.REPORT_DIGITAL + port, PrivateConstants.REPORTING_ENABLE]
322 yield from self._send_command(command)
323
324 @asyncio.coroutine
326 """
327 This method will send an extended-data analog write command to the selected pin.
328 @param pin: 0 - 127
329 @param data: 0 - 0xfffff
330 @return: No return value
331 """
332 analog_data = [pin, data & 0x7f, (data >> 7) & 0x7f, data >> 14]
333 yield from self._send_sysex(PrivateConstants.EXTENDED_ANALOG, analog_data)
334
335 @asyncio.coroutine
337 """
338 A list is returned containing the latch state for the pin, the latched value, and the time stamp
339 [latched_state, threshold_type, threshold_value, latched_data, time_stamp]
340 @param pin: Pin number.
341 @return: [latched_state, threshold_type, threshold_value, latched_data, time_stamp]
342 """
343 key = 'A' + str(pin)
344 if key in self.latch_map:
345 entry = self.latch_map.get(key)
346 return entry
347 else:
348 return None
349
350 @asyncio.coroutine
370
371 @asyncio.coroutine
382
383 @asyncio.coroutine
385 """
386 A list is returned containing the latch state for the pin, the latched value, and the time stamp
387 [pin_num, latch_state, latched_value, time_stamp]
388 @param pin: Pin number.
389 @return: [latched_state, threshold_type, threshold_value, latched_data, time_stamp]
390 """
391 key = 'D' + str(pin)
392 if key in self.latch_map:
393 entry = self.latch_map.get(key)
394 return entry
395 else:
396 return None
397
398 @asyncio.coroutine
415
416 @asyncio.coroutine
427
428 @asyncio.coroutine
442
443 @asyncio.coroutine
450
451 @asyncio.coroutine
453 """
454 NOTE: THIS METHOD MUST BE CALLED BEFORE ANY I2C REQUEST IS MADE
455 This method initializes Firmata for I2c operations.
456
457 @param read_delay_time (in microseconds): an optional parameter, default is 0
458 @return: No Return Value
459 """
460 data = [read_delay_time & 0x7f, read_delay_time >> 7]
461 yield from self._send_sysex(PrivateConstants.I2C_CONFIG, data)
462
463 @asyncio.coroutine
465 """
466 This method retrieves cached i2c data to support a polling mode.
467 @param address: I2C device address
468 @return:Last cached value read
469 """
470 if address in self.i2c_map:
471 map_entry = self.i2c_map.get(address)
472 data = map_entry.get('value')
473 return data
474 else:
475 return None
476
477 @asyncio.coroutine
478 - def i2c_read_request(self, address, register, number_of_bytes, read_type, cb=None):
479 """
480 This method requests the read of an i2c device. Results are retrieved by a call to
481 i2c_get_read_data(). or by callback.
482 If a callback method is provided, when data is received from the device it will be sent to the callback method.
483 Some devices require that transmission be restarted (e.g. MMA8452Q acceleromater).
484 Use I2C_READ | I2C_RESTART_TX for those cases.
485 @param address: i2c device address
486 @param register: register number (can be set to zero)
487 @param number_of_bytes: number of bytes expected to be returned
488 @param read_type: I2C_READ or I2C_READ_CONTINUOUSLY. I2C_RESTART_TX may be OR'ed when required
489 @param cb: Optional callback function to report i2c data as result of read command
490 @return: No return value.
491 """
492
493 if address not in self.i2c_map:
494
495 self.i2c_map[address] = {'value': None, 'callback': cb}
496 data = [address, read_type, register & 0x7f, register >> 7,
497 number_of_bytes & 0x7f, number_of_bytes >> 7]
498 yield from self._send_sysex(PrivateConstants.I2C_REQUEST, data)
499
500 @asyncio.coroutine
502 """
503 Write data to an i2c device.
504 @param address: i2c device address
505 @param args: A variable number of bytes to be sent to the device passed in as a list
506 @return: No return value.
507 """
508 data = [address, Constants.I2C_WRITE]
509 for item in args:
510 item_lsb = item & 0x7f
511 data.append(item_lsb)
512 item_msb = item >> 7
513 data.append(item_msb)
514 yield from self._send_sysex(PrivateConstants.I2C_REQUEST, data)
515
516 @asyncio.coroutine
517 - def play_tone(self, pin, tone_command, frequency, duration):
518 """
519 This method will call the Tone library for the selected pin.
520 It requires FirmataPlus to be loaded onto the arduino
521 If the tone command is set to TONE_TONE, then the specified tone will be played.
522 Else, if the tone command is TONE_NO_TONE, then any currently playing tone will be disabled.
523 @param pin: Pin number
524 @param tone_command: Either TONE_TONE, or TONE_NO_TONE
525 @param frequency: Frequency of tone
526 @param duration: Duration of tone in milliseconds
527 @return: No return value
528 """
529
530 if tone_command == Constants.TONE_TONE:
531
532 if duration:
533 data = [tone_command, pin, frequency & 0x7f, frequency >> 7, duration & 0x7f, duration >> 7]
534
535 else:
536 data = [tone_command, pin, frequency & 0x7f, frequency >> 7, 0, 0]
537
538
539
540
541 else:
542 data = [tone_command, pin]
543 yield from self._send_sysex(PrivateConstants.TONE_DATA, data)
544
545 @asyncio.coroutine
555
556 @asyncio.coroutine
557 - def servo_config(self, pin, min_pulse=544, max_pulse=2400):
558 """
559 Configure a pin as a servo pin. Set pulse min, max in ms.
560 Use this method (not set_pin_mode) to configure a pin for servo operation.
561 @param pin: Servo Pin.
562 @param min_pulse: Min pulse width in ms.
563 @param max_pulse: Max pulse width in ms.
564 @return: No return value
565 """
566
567 command = [pin, min_pulse & 0x7f, min_pulse >> 7, max_pulse & 0x7f,
568 max_pulse >> 7]
569
570 yield from self._send_sysex(PrivateConstants.SERVO_CONFIG, command)
571
572 @asyncio.coroutine
574 """
575 This method "arms" an analog pin for its data to be latched and saved in the latching table
576 If a callback method is provided, when latching criteria is achieved, the callback function is called
577 with latching data notification.
578 Data returned in the callback list has the pin number as the first element,
579 @param pin: Analog pin number (value following an 'A' designator, i.e. A5 = 5
580 @param threshold_type: ANALOG_LATCH_GT | ANALOG_LATCH_LT | ANALOG_LATCH_GTE | ANALOG_LATCH_LTE
581 @param threshold_value: numerical value - between 0 and 1023
582 @param cb: callback method
583 @return: True if successful, False if parameter data is invalid
584 """
585 if Constants.LATCH_GT <= threshold_type <= Constants.LATCH_LTE:
586 key = 'A' + str(pin)
587 if 0 <= threshold_value <= 1023:
588 self.latch_map[key] = [Constants.LATCH_ARMED, threshold_type, threshold_value, 0, 0, cb]
589 return True
590 else:
591 return False
592
593 @asyncio.coroutine
595 """
596 This method "arms" a digital pin for its data to be latched and saved in the latching table
597 If a callback method is provided, when latching criteria is achieved, the callback function is called
598 with latching data notification.
599 Data returned in the callback list has the pin number as the first element,
600 @param pin: Digital pin number
601 @param threshold_value: 0 or 1
602 @param cb: callback function
603 @return: True if successful, False if parameter data is invalid
604 """
605 if 0 <= threshold_value <= 1:
606 key = 'D' + str(pin)
607 self.latch_map[key] = [Constants.LATCH_ARMED, Constants.LATCH_EQ, threshold_value, 0, 0, cb]
608 return True
609 else:
610 return False
611
612 @asyncio.coroutine
613 - def set_pin_mode(self, pin_number, pin_state, callback=None):
614 """
615 This method sets the pin mode for the specified pin. For Servo, use servo_config() instead.
616 @param pin_number: Arduino Pin Number
617 @param pin_state:INPUT/OUTPUT/ANALOG/PWM/
618 @param callback: Optional: A reference to a call back function to be called when pin data value changes
619 @return: No return value.
620 """
621 if callback:
622 if pin_state == Constants.INPUT:
623 self.digital_pins[pin_number].cb = callback
624 elif pin_state == Constants.ANALOG:
625 self.analog_pins[pin_number].cb = callback
626 else:
627 print('{} {}'.format('set_pin_mode: callback ignored for pin state:', pin_state))
628
629 if pin_state == Constants.INPUT or pin_state == Constants.ANALOG:
630 pin_mode = Constants.INPUT
631 else:
632 pin_mode = pin_state
633 command = [PrivateConstants.SET_PIN_MODE, pin_number, pin_mode]
634 yield from self._send_command(command)
635 if pin_state == Constants.ANALOG:
636 yield from self.enable_analog_reporting(pin_number)
637 elif pin_state == Constants.INPUT:
638 yield from self.enable_digital_reporting(pin_number)
639 else:
640 pass
641
642 @asyncio.coroutine
644 """
645 This method sends the desired sampling interval to Firmata.
646 Note: Standard Firmata will ignore any interval less than 10 milliseconds
647 @param interval: Integer value for desired sampling interval in milliseconds
648 @return: No return value.
649 """
650 data = [interval & 0x7f, interval >> 7]
651 self._send_sysex(PrivateConstants.SAMPLING_INTERVAL, data)
652
653 @asyncio.coroutine
655 """
656 This method attempts an orderly shutdown
657 @return: No return value
658 """
659 print('Shutting down ...')
660 yield from self.send_reset()
661 yield from asyncio.sleep(1)
662 signal.alarm(1)
663
664 @asyncio.coroutine
665 - def sleep(self, sleep_time):
666 """
667 This method is a proxy method for asyncio.sleep
668 @param sleep_time: Sleep interval in seconds
669 @return:No return value.
670 """
671 try:
672 yield from asyncio.sleep(sleep_time)
673 except RuntimeError:
674 print('sleep exception')
675 self.shutdown()
676
677 @asyncio.coroutine
678 - def sonar_config(self, trigger_pin, echo_pin, cb=None, ping_interval=50, max_distance=200):
679 """
680 Configure the pins,ping interval and maximum distance for an HC-SR04 type device.
681 Single pin configuration may be used. To do so, set both the trigger and echo pins to the same value.
682 Up to a maximum of 6 SONAR devices is supported
683 If the maximum is exceeded a message is sent to the console and the request is ignored.
684 NOTE: data is measured in centimeters
685 @param trigger_pin: The pin number of for the trigger (transmitter).
686 @param echo_pin: The pin number for the received echo.
687 @param cb: optional callback function to report sonar data changes
688 @param ping_interval: Minimum interval between pings. Lowest number to use is 33 ms.Max is 127
689 @param max_distance: Maximum distance in cm. Max is 200.
690 @return:No return value.
691
692 """
693
694
695 if trigger_pin in self.active_sonar_map:
696 return
697
698 if max_distance > 200:
699 max_distance = 200
700 max_distance_lsb = max_distance & 0x7f
701 max_distance_msb = max_distance >> 7
702 data = [trigger_pin, echo_pin, ping_interval, max_distance_lsb, max_distance_msb]
703 self.set_pin_mode(trigger_pin, Constants.SONAR, Constants.INPUT)
704 self.set_pin_mode(echo_pin, Constants.SONAR, Constants.INPUT)
705
706 if len(self.active_sonar_map) > 6:
707
708
709 print("sonar_config: maximum number of devices assigned - ignoring request")
710
711 else:
712 self.active_sonar_map[trigger_pin] = [cb, 0]
713
714 yield from self._send_sysex(PrivateConstants.SONAR_CONFIG, data)
715
716 @asyncio.coroutine
718 """
719 Retrieve Ping (HC-SR04 type) data. The data is presented as a dictionary.
720 The 'key' is the trigger pin specified in sonar_config() and the 'data' is the
721 current measured distance (in centimeters)
722 for that pin. If there is no data, the value is set to None.
723 @param trigger_pin: key into sonar data map
724 @return: active_sonar_map
725 """
726
727 sonar_pin_entry = self.active_sonar_map.get(trigger_pin)
728 value = sonar_pin_entry[1]
729 return value
730
731 @asyncio.coroutine
733 """
734 Configure stepper motor prior to operation.
735 This is a FirmataPlus feature.
736 @param steps_per_revolution: number of steps per motor revolution
737 @param stepper_pins: a list of control pin numbers - either 4 or 2
738 @return:No return value.
739
740 """
741 data = [PrivateConstants.STEPPER_CONFIGURE, steps_per_revolution & 0x7f, steps_per_revolution >> 7]
742 for pin in range(len(stepper_pins)):
743 data.append(stepper_pins[pin])
744 yield from self._send_sysex(PrivateConstants.STEPPER_DATA, data)
745
746 @asyncio.coroutine
748 """
749 Move a stepper motor for the number of steps at the specified speed
750 This is a FirmataPlus feature.
751 @param motor_speed: 21 bits of data to set motor speed
752 @param number_of_steps: 14 bits for number of steps & direction
753 positive is forward, negative is reverse
754 @return:No return value.
755
756 """
757 if number_of_steps > 0:
758 direction = 1
759 else:
760 direction = 0
761 abs_number_of_steps = abs(number_of_steps)
762 data = [PrivateConstants.STEPPER_STEP, motor_speed & 0x7f, (motor_speed >> 7) & 0x7f, motor_speed >> 14,
763 abs_number_of_steps & 0x7f, abs_number_of_steps >> 7, direction]
764 yield from self._send_sysex(PrivateConstants.STEPPER_DATA, data)
765
766 @asyncio.coroutine
768 """
769 This is a private method.
770 It continually accepts and interprets data coming from Firmata,and then
771 dispatches the correct handler to process the data.
772 @return: This method never returns
773 """
774
775 sysex = []
776
777 while True:
778 try:
779 next_command_byte = yield from self.serial_port.read()
780
781 if next_command_byte == PrivateConstants.START_SYSEX:
782 while next_command_byte != PrivateConstants.END_SYSEX:
783 yield from asyncio.sleep(self.sleep_tune)
784 next_command_byte = yield from self.serial_port.read()
785 sysex.append(next_command_byte)
786 yield from self.command_dictionary[sysex[0]](sysex)
787 sysex = []
788 yield from asyncio.sleep(self.sleep_tune)
789
790 elif 0xE0 <= next_command_byte < 0xEF:
791
792
793 command = []
794
795 pin = next_command_byte & 0x0f
796 command.append(pin)
797
798 command = yield from self._wait_for_data(command, 2)
799
800 yield from self._analog_message(command)
801
802 elif 0x90 <= next_command_byte <= 0x9F:
803 command = []
804 pin = next_command_byte & 0x0f
805 command.append(pin)
806 command = yield from self._wait_for_data(command, 2)
807 yield from self._digital_message(command)
808
809 elif next_command_byte in self.command_dictionary:
810 yield from self.command_dictionary[next_command_byte]()
811 yield from asyncio.sleep(self.sleep_tune)
812 else:
813
814 yield from asyncio.sleep(self.sleep_tune)
815 continue
816
817 except Exception as ex:
818
819 print(ex)
820 raise
821
822 '''
823 Firmata message handlers
824 '''
825
826 @asyncio.coroutine
828 """
829 This is a private message handler method.
830 It is a message handler for the analog mapping response message
831 @param data: response data
832 @return: none - but saves the response
833 """
834 self.query_reply_data[PrivateConstants.ANALOG_MAPPING_RESPONSE] = data[1:-1]
835
836 @asyncio.coroutine
838 """
839 This is a private message handler method.
840 It is a message handler for analog messages.
841 @param data: message data
842 @return: None - but saves the data in the pins structure
843 """
844 pin = data[0]
845 value = (data[PrivateConstants.MSB] << 7) + data[PrivateConstants.LSB]
846
847 self.analog_pins[pin].current_value = value
848 if self.analog_pins[pin].cb:
849
850
851 value = [pin, value]
852 loop = asyncio.get_event_loop()
853 loop.call_soon(self.analog_pins[pin].cb, value)
854
855
856 key = 'A' + str(pin)
857 if key in self.latch_map:
858 yield from self._check_latch_data(key, value[1])
859
860 @asyncio.coroutine
862 """
863 This is a private message handler method.
864 It is a message handler for capability report responses.
865 @param data: capability report
866 @return: None - but report is saved
867 """
868 self.query_reply_data[PrivateConstants.CAPABILITY_RESPONSE] = data[1:-1]
869
870 @asyncio.coroutine
872 """
873 This is a private message handler method.
874 It is a message handler for Digital Messages.
875 @param data: digital message
876 @return: None - but update is saved in pins structure
877 """
878 port = data[0]
879 port_data = (data[PrivateConstants.MSB] << 7) + data[PrivateConstants.LSB]
880 pin = port * 8
881 for pin in range(pin, min(pin + 8, len(self.digital_pins))):
882 self.digital_pins[pin].current_value = port_data & 0x01
883 data = [pin, self.digital_pins[pin].current_value]
884 if self.digital_pins[pin].cb:
885 self.digital_pins[pin].cb(data)
886
887 key = 'D' + str(pin)
888 if key in self.latch_map:
889 yield from self._check_latch_data(key, port_data & 0x01)
890 port_data >>= 1
891
892 @asyncio.coroutine
894 """
895 This is a private message handler method.
896 It handles encoder data messages.
897 @param data: encoder data
898 @return: None - but update is saved in the digital pins structure
899 """
900
901 data = data[1:-1]
902 pin = data[0]
903 val = int((data[PrivateConstants.MSB] << 7) + data[PrivateConstants.LSB])
904
905 if val > 8192:
906 val -= 16384
907
908 if val != self.digital_pins[pin].current_value:
909 self.digital_pins[pin].current_value = val
910 if self.digital_pins[pin].cb:
911 self.digital_pins[pin].cb([pin, val])
912
913 @asyncio.coroutine
915 """
916 This is a private message handler method.
917 It handles replies to i2c_read requests. It stores the data for each i2c device
918 address in a dictionary called i2c_map. The data may be retrieved via a polling call to i2c_get_read_data().
919 It a callback was specified in pymata.i2c_read, the raw data is sent through the callback
920 @param data: raw data returned from i2c device
921 """
922
923 data = data[1:-1]
924 reply_data = []
925 address = (data[0] & 0x7f) + (data[1] << 7)
926 if address in self.i2c_map:
927 for i in range(0, len(data)):
928 reply_data.append(data[i])
929 map_entry = self.i2c_map.get(address)
930 map_entry['value'] = reply_data
931 self.i2c_map[address] = map_entry
932 cb = map_entry.get('callback')
933 if cb:
934 cb(reply_data)
935 yield from asyncio.sleep(self.sleep_tune)
936
937 @asyncio.coroutine
939 """
940 This is a private message handler method.
941 It handles pin state query response messages.
942 @param data: Pin state message
943 @return: None - but response is saved
944 """
945 self.query_reply_data[PrivateConstants.PIN_STATE_RESPONSE] = data[1:-1]
946
947 @asyncio.coroutine
949 """
950 This is a private message handler method.
951 This method handles the sysex 'report firmware' command sent by Firmata (0x79).
952 It assembles the firmware version by concatenating the major and minor version number components and
953 the firmware identifier into a string.
954 e.g. "2.3 StandardFirmata.ino"
955 @param sysex_data: Sysex data sent from Firmata
956 @return: None
957 """
958
959 major = sysex_data[1]
960 version_string = str(major)
961
962
963 minor = sysex_data[2]
964
965
966 version_string += '.'
967
968
969 version_string += str(minor)
970
971 version_string += ' '
972
973
974
975 name = sysex_data[3:-1]
976
977
978 for e in name:
979 version_string += chr(e)
980
981
982 self.query_reply_data[PrivateConstants.REPORT_FIRMWARE] = version_string
983
984 @asyncio.coroutine
986 """
987 This is a private message handler method.
988 This method reads the following 2 bytes after the report version command (0xF9 - non sysex).
989 The first byte is the major number and the second byte is the minor number.
990 @return: None
991 """
992
993 major = yield from self.serial_port.read()
994 version_string = str(major)
995 minor = yield from self.serial_port.read()
996 version_string += '.'
997 version_string += str(minor)
998 self.query_reply_data[PrivateConstants.REPORT_VERSION] = version_string
999
1000 @asyncio.coroutine
1002 """
1003 This method handles the incoming sonar data message and stores
1004 the data in the response table.
1005 @param data: Message data from Firmata
1006 @return: No return value.
1007 """
1008
1009
1010 data = data[1:-1]
1011 pin_number = data[0]
1012 val = int((data[PrivateConstants.MSB] << 7) + data[PrivateConstants.LSB])
1013
1014 sonar_pin_entry = self.active_sonar_map[pin_number]
1015
1016
1017
1018 if sonar_pin_entry[0] is not None:
1019
1020 if sonar_pin_entry[1] != val:
1021 sonar_pin_entry[1] = val
1022 self.active_sonar_map[pin_number] = sonar_pin_entry
1023
1024 if sonar_pin_entry[0]:
1025 sonar_pin_entry[0]([pin_number, val])
1026
1027
1028 self.active_sonar_map[pin_number] = sonar_pin_entry
1029
1030 yield from asyncio.sleep(self.sleep_tune)
1031
1032 @asyncio.coroutine
1034 """
1035 This is a private message handler method.
1036 It is the message handler for String data messages that will be printed to the console.
1037 @param data: message
1038 @return: None - message is sent to console
1039 """
1040 reply = ''
1041 data = data[1:-1]
1042 for x in data:
1043 reply_data = x
1044 if reply_data:
1045 reply += chr(reply_data)
1046 print(reply)
1047
1048 '''
1049 utilities
1050 '''
1051
1052 @asyncio.coroutine
1083
1084
1086 """
1087 This is a private utility method.
1088 This method attempts to discover the com port that the arduino is connected to.
1089 @return: Detected Comport
1090 """
1091 locations = ['dev/ttyACM0', '/dev/ttyACM0', '/dev/ttyACM1', '/dev/ttyACM2', '/dev/ttyACM3', '/dev/ttyACM4',
1092 '/dev/ttyACM5', '/dev/ttyUSB0', '/dev/ttyUSB1', '/dev/ttyUSB2', '/dev/ttyUSB3', '/dev/ttyUSB4',
1093 '/dev/ttyUSB5', '/dev/ttyUSB6', '/dev/ttyUSB7', '/dev/ttyUSB8', '/dev/ttyUSB9', '/dev/ttyUSB10',
1094 '/dev/ttyS0', '/dev/ttyS1', '/dev/ttyS2', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8',
1095 'com9', 'com10', 'com11', 'com12', 'com13', 'com14', 'com15', 'com16', 'com17', 'com18', 'com19',
1096 'com20', 'com21', 'com1', 'end'
1097 ]
1098 detected = None
1099 for device in locations:
1100 try:
1101 serialport = serial.Serial(device, 57600, timeout=0)
1102 detected = device
1103 serialport.close()
1104 break
1105 except serial.SerialException:
1106 if device == 'end':
1107 print('Unable to find Serial Port, Please plug in cable or check cable connections.')
1108 detected = None
1109 exit()
1110 print('{}{}\n'.format("Using COM Port:", detected))
1111 return detected
1112
1113
1141
1142 @asyncio.coroutine
1165
1166 @asyncio.coroutine
1168 """
1169 This is a private utility method.
1170 The method sends a non-sysex command to Firmata.
1171 @param command: command data
1172 @return: length of data sent
1173 """
1174 send_message = ""
1175 for i in command:
1176 send_message += chr(i)
1177 result = None
1178 for data in send_message:
1179 try:
1180 result = yield from self.serial_port.write(data)
1181 except():
1182 print('cannot send command')
1183 return result
1184
1185 @asyncio.coroutine
1186 - def _send_sysex(self, sysex_command, sysex_data=None):
1187 """
1188 This is a private utility method.
1189 This method sends a sysex command to Firmata.
1190
1191 @param sysex_command: sysex command
1192 @param sysex_data: data for command
1193 @return : No return value.
1194 """
1195 if not sysex_data:
1196 sysex_data = []
1197
1198
1199 sysex_message = chr(PrivateConstants.START_SYSEX)
1200 sysex_message += chr(sysex_command)
1201 if len(sysex_data):
1202 for d in sysex_data:
1203 sysex_message += chr(d)
1204 sysex_message += chr(PrivateConstants.END_SYSEX)
1205
1206 for data in sysex_message:
1207 yield from self.serial_port.write(data)
1208
1209 @asyncio.coroutine
1211 """
1212 This is a private utility method.
1213 This method accumulates the requested number of bytes and then returns the full command
1214 @param current_command: command id
1215 @param number_of_bytes: how many bytes to wait for
1216 @return: command
1217 """
1218 while number_of_bytes:
1219 next_command_byte = yield from self.serial_port.read()
1220 current_command.append(next_command_byte)
1221 number_of_bytes -= 1
1222 yield from asyncio.sleep(self.sleep_tune)
1223 return current_command
1224
1228 """
1229 The 'Control-C' handler
1230 @param the_signal: signal
1231 @param frame: not used
1232 @return: never returns.
1233 """
1234 print('You pressed Ctrl+C!')
1235
1236
1237 try:
1238 loop = asyncio.get_event_loop()
1239 for t in asyncio.Task.all_tasks(loop):
1240 t.cancel()
1241 loop.run_until_complete(asyncio.sleep(.01))
1242 loop.close()
1243 loop.stop()
1244
1245 sys.exit(0)
1246 except RuntimeError:
1247
1248
1249 sys.exit(1)
1250
1251
1252 signal.signal(signal.SIGINT, _signal_handler)
1253 signal.signal(signal.SIGTERM, _signal_handler)
1254 signal.signal(signal.SIGALRM, _signal_handler)
1255