Package PyMata :: Module pymata_command_handler
[hide private]
[frames] | no frames]

Source Code for Module PyMata.pymata_command_handler

  1  __author__ = 'Copyright (c) 2013 Alan Yorinks All rights reserved.' 
  2   
  3  """ 
  4  Copyright (c) 2013-14 Alan Yorinks All rights reserved. 
  5   
  6  This program is free software; you can redistribute it and/or 
  7  modify it under the terms of the GNU  General Public 
  8  License as published by the Free Software Foundation; either 
  9  version 3 of the License, or (at your option) any later version. 
 10   
 11  This library is distributed in the hope that it will be useful, 
 12  but WITHOUT ANY WARRANTY; without even the implied warranty of 
 13  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 14  Lesser General Public License for more details. 
 15   
 16  You should have received a copy of the GNU Lesser General Public 
 17  License along with this library; if not, write to the Free Software 
 18  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA 
 19  """ 
 20   
 21  import threading 
 22  import time 
 23   
 24   
25 -class PyMataCommandHandler(threading.Thread):
26 """ 27 This class handles all data interchanges with Firmata 28 The receive loop runs in its own thread. 29 30 Messages to be sent to Firmata are queued through a deque to allow for priority 31 messages to take precedence. The deque is checked within the receive loop for any 32 outgoing messages. 33 34 There is no blocking in either communications direction. 35 36 There is blocking when accessing the data tables through the _data_lock 37 """ 38 39 # the following defines are from Firmata.h 40 41 # message command bytes (128-255/ 0x80- 0xFF) 42 # from this client to firmata 43 MSG_CMD_MIN = 0x80 # minimum value for a message from firmata 44 REPORT_ANALOG = 0xC0 # enable analog input by pin # 45 REPORT_DIGITAL = 0xD0 # enable digital input by port pair 46 SET_PIN_MODE = 0xF4 # set a pin to INPUT/OUTPUT/PWM/etc 47 START_SYSEX = 0xF0 # start a MIDI Sysex message 48 END_SYSEX = 0xF7 # end a MIDI Sysex message 49 SYSTEM_RESET = 0xFF # reset from MIDI 50 51 # messages from firmata 52 DIGITAL_MESSAGE = 0x90 # send or receive data for a digital pin 53 ANALOG_MESSAGE = 0xE0 # send or receive data for a PWM configured pin 54 REPORT_VERSION = 0xF9 # report protocol version 55 56 # user defined SYSEX commands 57 # from this client 58 ENCODER_CONFIG = 0x20 # create and enable encoder object 59 TONE_PLAY = 0x5F # play a tone at a specified frequency and duration 60 SONAR_CONFIG = 0x60 # configure pins to control a Ping type sonar distance device 61 62 # messages from firmata 63 ENCODER_DATA = 0x21 # current encoder position data 64 SONAR_DATA = 0x61 # distance data returned 65 66 # standard Firmata sysex commands 67 68 SERVO_CONFIG = 0x70 # set servo pin and max and min angles 69 STRING_DATA = 0x71 # a string message with 14-bits per char 70 I2C_REQUEST = 0x76 # send an I2C read/write request 71 I2C_REPLY = 0x77 # a reply to an I2C read request 72 I2C_CONFIG = 0x78 # config I2C settings such as delay times and power pins 73 REPORT_FIRMWARE = 0x79 # report name and version of the firmware 74 SAMPLING_INTERVAL = 0x7A # modify the sampling interval 75 76 EXTENDED_ANALOG = 0x6F # analog write (PWM, Servo, etc) to any pin 77 PIN_STATE_QUERY = 0x6D # ask for a pin's current mode and value 78 PIN_STATE_RESPONSE = 0x6E # reply with pin's current mode and value 79 CAPABILITY_QUERY = 0x6B # ask for supported modes and resolution of all pins 80 CAPABILITY_RESPONSE = 0x6C # reply with supported modes and resolution 81 ANALOG_MAPPING_QUERY = 0x69 # ask for mapping of analog to pin numbers 82 ANALOG_MAPPING_RESPONSE = 0x6A # reply with analog mapping data 83 84 85 # reserved values 86 SYSEX_NON_REALTIME = 0x7E # MIDI Reserved for non-realtime messages 87 SYSEX_REALTIME = 0x7F # MIDI Reserved for realtime messages 88 89 90 # pin modes 91 INPUT = 0x00 # pin set as input 92 OUTPUT = 0x01 # pin set as output 93 ANALOG = 0x02 # analog pin in analogInput mode 94 PWM = 0x03 # digital pin in PWM output mode 95 SERVO = 0x04 # digital pin in Servo output mode 96 I2C = 0x06 # pin included in I2C setup 97 ONEWIRE = 0x07 # possible future feature 98 STEPPER = 0x08 # possible future feature 99 TONE = 0x09 # Any pin in TONE mode 100 ENCODER = 0x0a 101 SONAR = 0x0b # Any pin in SONAR mode 102 IGNORE = 0x7f 103 104 # the following pin modes are not part of or defined by Firmata 105 # but used by PyFirmata 106 107 DIGITAL = 0x20 108 109 # The response tables hold response information for all pins 110 # Each table is a table of entries for each pin, which consists of the pin mode, and its last value from firmata 111 112 113 114 # This is a table that stores analog pin modes and data 115 # each entry represents ia mode (INPUT or OUTPUT), and its last current value 116 analog_response_table = [] 117 118 # This is a table that stores digital pin modes and data 119 # each entry represents its mode (INPUT or OUTPUT, PWM, SERVO, ENCODER), and its last current value 120 digital_response_table = [] 121 122 # The analog and digital latch tables will store "latched" data for input pins. 123 # If a pin is armed, the latest value will be stored and maintained until 124 # the data is read, and the data is cleared from the latch and the latch rearmed. 125 126 # The table consists of a list of lists sized by the number of pins for the board. It is ordered by pin number 127 # and each list entry contains a latch state, a value and a date stamp when latched. 128 # An armed state = 0 and a latched state = 1 129 130 # analog_latch_table entry = [latched_state, threshold_type, threshold_value, latched_data, time_stamp] 131 # digital_latch_table_entry = [latched_state, threshold_type, latched_data, time_stamp] 132 133 analog_latch_table = [] 134 digital_latch_table = [] 135 136 # index into latch tables 137 LATCH_STATE = 0 138 LATCHED_THRESHOLD_TYPE = 1 139 140 ANALOG_LATCH_DATA_TARGET = 2 141 ANALOG_LATCHED_DATA = 3 142 ANALOG_TIME_STAMP = 4 143 144 DIGITAL_LATCHED_DATA = 2 145 DIGITAL_TIME_STAMP = 3 146 147 148 #latch states 149 LATCH_IGNORE = 0 # this pin will be ignored for latching 150 LATCH_ARMED = 1 # When the next pin value change is received for this pin, if it matches the latch criteria 151 # the data will be latched 152 LATCH_LATCHED = 2 # data has been latched. Read the data to re-arm the latch 153 154 # latch threshold types 155 DIGITAL_LATCH_LOW = 0 # for digital pins 156 DIGITAL_LATCH_HIGH = 1 # for digital pins 157 ANALOG_LATCH_GT = 2 # greater than for analog 158 ANALOG_LATCH_LT = 3 # less than for analog 159 ANALOG_LATCH_GTE = 4 # greater than or equal to for analog 160 ANALOG_LATCH_LTE = 5 # less than or equal to for analog 161 162 163 # These values are indexes into the response table entries 164 RESPONSE_TABLE_MODE = 0 165 RESPONSE_TABLE_PIN_DATA_VALUE = 1 166 167 # These values are the index into the data passed by _arduino and used to reassemble integer values 168 MSB = 2 169 LSB = 1 170 171 # This is a map that allows the look up of command handler methods using a command as the key. 172 # This is populated in the run method after the python interpreter sees all of the command handler method 173 # defines (python does not have forward referencing) 174 175 # The "key" is the command, and the value contains is a list containing the method name and the number of 176 # parameter bytes that the method will require to process the message (in some cases the value is unused) 177 command_dispatch = {} 178 179 # this deque is used by the methods that assemble messages to be sent to Firmata. The deque is filled outside of 180 # of the message processing loop and emptied within the loop. 181 command_deque = None 182 183 # firmata version information - saved as a list - [major, minor] 184 firmata_version = [] 185 186 # firmata firmware version information saved as a list [major, minor, file_name] 187 firmata_firmware = [] 188 189 # a lock to protect the data tables when they are being accessed 190 data_lock = None 191 192 # total number of pins for the discovered board 193 total_pins_discovered = 0 194 195 # total number of analog pins for the discovered board 196 number_of_analog_pins_discovered = 0 197 198 # map of i2c addresses and associated data that has been read for that address 199 i2c_map = {} 200 201 # the active_sonar_map maps the sonar trigger pin number (the key) to the current data value returned 202 active_sonar_map = {} 203
204 - def __init__(self, transport, command_deque, data_lock):
205 """ 206 constructor for CommandHandler class 207 @param transport: A reference to the communications port designator. 208 @param command_deque: A reference to a command deque. 209 @param data_lock: A reference to a thread lock. 210 """ 211 212 # this list contains the results of the last pin query 213 self.last_pin_query_results = [] 214 215 # this stores the results of a capability request 216 self.capability_query_results = [] 217 218 # this stores the results of an analog mapping query 219 self.analog_mapping_query_results = [] 220 221 self.data_lock = data_lock 222 self.transport = transport 223 self.command_deque = command_deque 224 225 self.total_pins_discovered = 0 226 227 self.number_of_analog_pins_discovered = 0 228 229 threading.Thread.__init__(self) 230 self.daemon = True 231 232 self.stop_event = threading.Event()
233
234 - def stop( self ):
235 self.stop_event.set()
236
237 - def is_stopped( self ):
238 return self.stop_event.is_set()
239
240 - def auto_discover_board(self, max_wait_time=30):
241 """ 242 This method will allow up to max_wait_time seconds for discovery (communicating with) an Arduino board 243 and then will determine a pin configuration table for the board. 244 @param max_wait_time: The maximum time to wait for discovery of the board 245 @return: True if board is successfully discovered or False upon timeout 246 """ 247 # get current time 248 start_time = time.time() 249 250 # wait for up to max_wait_time seconds for a successful capability query to occur 251 252 while len(self.analog_mapping_query_results) == 0: 253 if time.time() - start_time > max_wait_time: 254 return False 255 # keep sending out a capability query until there is a response 256 self.send_sysex(self.ANALOG_MAPPING_QUERY, None) 257 time.sleep(.1) 258 259 print "Board initialized in %d seconds" % (time.time() - start_time) 260 261 for pin in self.analog_mapping_query_results: 262 self.total_pins_discovered += 1 263 # non analog pins will be marked as IGNORE 264 if pin != self.IGNORE: 265 self.number_of_analog_pins_discovered += 1 266 267 print 'Total Number of Pins Detected = %d' % self.total_pins_discovered 268 print 'Total Number of Analog Pins Detected = %d' % self.number_of_analog_pins_discovered 269 270 # response table initialization 271 # for each pin set the mode to input and the last read data value to zero 272 for pin in range(0, self.total_pins_discovered): 273 response_entry = [self.INPUT, 0] 274 self.digital_response_table.append(response_entry) 275 276 for pin in range(0, self.number_of_analog_pins_discovered): 277 response_entry = [self.INPUT, 0] 278 self.analog_response_table.append(response_entry) 279 280 # set up latching tables 281 for pin in range(0, self.total_pins_discovered): 282 digital_latch_table_entry = [0, 0, 0, 0] 283 self.digital_latch_table.append(digital_latch_table_entry) 284 285 for pin in range(0, self.number_of_analog_pins_discovered): 286 analog_latch_table_entry = [0, 0, 0, 0, 0] 287 self.analog_latch_table.append(analog_latch_table_entry) 288 289 return True
290
291 - def report_version(self, data):
292 """ 293 This method processes the report version message, sent asynchronously by Firmata when it starts up 294 or after refresh_report_version() is called 295 296 Use the api method api_get_version to retrieve this information 297 @param data: Message data from Firmata 298 @return: No return value. 299 """ 300 self.firmata_version.append(data[0]) # add major 301 self.firmata_version.append(data[1]) # add minor
302
303 - def set_analog_latch(self, pin, threshold_type, threshold_value):
304 """ 305 This method "arms" a pin to allow data latching for the pin. 306 @param pin: Analog pin number (value following an 'A' designator, i.e. A5 = 5 307 @param threshold_type: ANALOG_LATCH_GT | ANALOG_LATCH_LT | ANALOG_LATCH_GTE | ANALOG_LATCH_LTE 308 @param threshold_value: numerical value 309 """ 310 with self.data_lock: 311 self.analog_latch_table[pin] = [self.LATCH_ARMED, threshold_type, threshold_value, 0, 0]
312
313 - def set_digital_latch(self, pin, threshold_type):
314 """ 315 This method "arms" a pin to allow data latching for the pin. 316 @param pin: digital pin number 317 @param threshold_type: DIGITAL_LATCH_HIGH | DIGITAL_LATCH_LOW 318 """ 319 with self.data_lock: 320 self.digital_latch_table[pin] = [self.LATCH_ARMED, threshold_type, 0, 0]
321
322 - def get_analog_latch_data(self, pin):
323 """ 324 This method reads the analog latch table for the specified pin and returns a list that contains: 325 [latch_state, latched_data, and time_stamp]. 326 If the latch state is latched, the entry in the table is cleared 327 @param pin: pin number 328 @return: [latch_state, latched_data, and time_stamp] 329 """ 330 with self.data_lock: 331 pin_data = self.analog_latch_table[pin] 332 current_latch_data = [pin, 333 pin_data[self.LATCH_STATE], 334 pin_data[self.ANALOG_LATCHED_DATA], 335 pin_data[self.ANALOG_TIME_STAMP]] 336 # if this is latched data, clear the latch table entry for this pin 337 if pin_data[self.LATCH_STATE] == self.LATCH_LATCHED: 338 self.analog_latch_table[pin] = [0, 0, 0, 0, 0] 339 340 return current_latch_data
341 342
343 - def get_digital_latch_data(self, pin):
344 """ 345 This method reads the digital latch table for the specified pin and returns a list that contains: 346 [latch_state, latched_data, and time_stamp]. 347 If the latch state is latched, the entry in the table is cleared 348 @param pin: pin number 349 @return: [latch_state, latched_data, and time_stamp] 350 """ 351 with self.data_lock: 352 pin_data = self.digital_latch_table[pin] 353 current_latch_data = [pin, 354 pin_data[self.LATCH_STATE], 355 pin_data[self.DIGITAL_LATCHED_DATA], 356 pin_data[self.DIGITAL_TIME_STAMP]] 357 if pin_data[self.LATCH_STATE] == self.LATCH_LATCHED: 358 self.digital_latch_table[pin] = [0, 0, 0, 0] 359 360 return current_latch_data
361 362
363 - def report_firmware(self, data):
364 """ 365 This method processes the report firmware message, sent asynchronously by Firmata when it starts up 366 or after refresh_report_firmware() is called 367 368 Use the api method api_get_firmware_version to retrieve this information 369 @param data: Message data from Firmata 370 @return: No return value. 371 """ 372 self.firmata_firmware.append(data[0]) # add major 373 self.firmata_firmware.append(data[1]) # add minor 374 375 # extract the file name string from the message 376 # file name is in bytes 2 to the end 377 name_data = data[2:] 378 379 # constructed file name 380 file_name = [] 381 382 # the file name is passed in with each character as 2 bytes, the high order byte is equal to 0 383 # so skip over these zero bytes 384 for i in name_data[::2]: 385 file_name.append(chr(i)) 386 387 # add filename to tuple 388 self.firmata_firmware.append("".join(file_name))
389
390 - def analog_message(self, data):
391 """ 392 This method handles the incoming analog data message. 393 It stores the data value for the pin in the analog response table. 394 It checks to see if the 395 @param data: Message data from Firmata 396 @return: No return value. 397 """ 398 with self.data_lock: 399 400 # convert MSB and LSB into an integer 401 self.analog_response_table[data[self.RESPONSE_TABLE_MODE]][self.RESPONSE_TABLE_PIN_DATA_VALUE] \ 402 = (data[self.MSB] << 7) + data[self.LSB] 403 404 pin = data[0] 405 pin_response_data_data = self.analog_response_table[pin] 406 value = pin_response_data_data[self.RESPONSE_TABLE_PIN_DATA_VALUE] 407 # check if data is to be latched 408 # get the analog latching table entry for this pin 409 latching_entry = self.analog_latch_table[pin] 410 if latching_entry[self.LATCH_STATE] == self.LATCH_ARMED: 411 # Has the latching criteria been met 412 if latching_entry[self.LATCHED_THRESHOLD_TYPE] == self.ANALOG_LATCH_GT: 413 if value > latching_entry[self.ANALOG_LATCH_DATA_TARGET]: 414 updated_latch_entry = latching_entry 415 updated_latch_entry[self.LATCH_STATE] = self.LATCH_LATCHED 416 updated_latch_entry[self.ANALOG_LATCHED_DATA] = value 417 # time stamp it 418 updated_latch_entry[self.ANALOG_TIME_STAMP] = time.time() 419 self.analog_latch_table[pin] = updated_latch_entry 420 else: 421 pass # haven't hit target 422 elif latching_entry[self.LATCHED_THRESHOLD_TYPE] == self.ANALOG_LATCH_GTE: 423 if value >= latching_entry[self.ANALOG_LATCH_DATA_TARGET]: 424 updated_latch_entry = latching_entry 425 updated_latch_entry[self.LATCH_STATE] = self.LATCH_LATCHED 426 updated_latch_entry[self.ANALOG_LATCHED_DATA] = value 427 # time stamp it 428 updated_latch_entry[self.ANALOG_TIME_STAMP] = time.time() 429 self.analog_latch_table[pin] = updated_latch_entry 430 else: 431 pass # haven't hit target: 432 elif latching_entry[self.LATCHED_THRESHOLD_TYPE] == self.ANALOG_LATCH_LT: 433 if value < latching_entry[self.ANALOG_LATCH_DATA_TARGET]: 434 updated_latch_entry = latching_entry 435 updated_latch_entry[self.LATCH_STATE] = self.LATCH_LATCHED 436 updated_latch_entry[self.ANALOG_LATCHED_DATA] = value 437 # time stamp it 438 updated_latch_entry[self.ANALOG_TIME_STAMP] = time.time() 439 self.analog_latch_table[pin] = updated_latch_entry 440 else: 441 pass # haven't hit target: 442 elif latching_entry[self.LATCHED_THRESHOLD_TYPE] == self.ANALOG_LATCH_LTE: 443 if value <= latching_entry[self.ANALOG_LATCH_DATA_TARGET]: 444 updated_latch_entry = latching_entry 445 updated_latch_entry[self.LATCH_STATE] = self.LATCH_LATCHED 446 updated_latch_entry[self.ANALOG_LATCHED_DATA] = value 447 # time stamp it 448 updated_latch_entry[self.ANALOG_TIME_STAMP] = time.time() 449 self.analog_latch_table[pin] = updated_latch_entry 450 else: 451 pass # haven't hit target: 452 else: 453 pass
454
455 - def digital_message(self, data):
456 """ 457 This method handles the incoming digital message. 458 It stores the data values in the digital response table. 459 Data is stored for all 8 bits of a digital port 460 @param data: Message data from Firmata 461 @return: No return value. 462 """ 463 port = data[0] 464 port_data = (data[self.MSB] << 7) + data[self.LSB] 465 466 # set all the pins for this reporting port 467 # get the first pin number for this report 468 pin = port * 8 469 for pin in range(pin, pin + 8): 470 # shift through all the bit positions and set the digital response table 471 with self.data_lock: 472 self.digital_response_table[pin][self.RESPONSE_TABLE_PIN_DATA_VALUE] = port_data & 0x01 473 # determine if the latch data table needs to be updated for each pin 474 latching_entry = self.digital_latch_table[pin] 475 if latching_entry[self.LATCH_STATE] == self.LATCH_ARMED: 476 if latching_entry[self.LATCHED_THRESHOLD_TYPE] == self.DIGITAL_LATCH_LOW: 477 if (port_data & 0x01) == 0: 478 updated_latch_entry = latching_entry 479 updated_latch_entry[self.LATCH_STATE] = self.LATCH_LATCHED 480 updated_latch_entry[self.DIGITAL_LATCHED_DATA] = self.DIGITAL_LATCH_LOW 481 # time stamp it 482 updated_latch_entry[self.DIGITAL_TIME_STAMP] = time.time() 483 else: 484 pass 485 elif latching_entry[self.LATCHED_THRESHOLD_TYPE] == self.DIGITAL_LATCH_HIGH: 486 if port_data & 0x01: 487 updated_latch_entry = latching_entry 488 updated_latch_entry[self.LATCH_STATE] = self.LATCH_LATCHED 489 updated_latch_entry[self.DIGITAL_LATCHED_DATA] = self.DIGITAL_LATCH_HIGH 490 # time stamp it 491 updated_latch_entry[self.DIGITAL_TIME_STAMP] = time.time() 492 else: 493 pass 494 else: 495 pass 496 497 # get the next data bit 498 port_data >>= 1
499
500 - def encoder_data(self, data):
501 """ 502 This method handles the incoming encoder data message and stores 503 the data in the response table. 504 @param data: Message data from Firmata 505 @return: No return value. 506 """ 507 val = int((data[self.MSB] << 7) + data[self.LSB]) 508 # set value so that it shows positive and negative values 509 if val > 8192: 510 val -= 16384 511 with self.data_lock: 512 self.digital_response_table[data[self.RESPONSE_TABLE_MODE]][self.RESPONSE_TABLE_PIN_DATA_VALUE] = val
513
514 - def sonar_data(self, data):
515 """ 516 This method handles the incoming sonar data message and stores 517 the data in the response table. 518 @param data: Message data from Firmata 519 @return: No return value. 520 """ 521 val = int((data[self.MSB] << 7) + data[self.LSB]) 522 pin_number = data[0] 523 with self.data_lock: 524 self.active_sonar_map[pin_number] = val 525 # also write it into the digital response table 526 self.digital_response_table[data[self.RESPONSE_TABLE_MODE]][self.RESPONSE_TABLE_PIN_DATA_VALUE] = val
527
529 """ 530 This method returns the entire analog response table to the caller 531 @return: The analog response table. 532 """ 533 with self.data_lock: 534 data = self.analog_response_table 535 536 return data
537
539 """ 540 This method returns the entire digital response table to the caller 541 @return: The digital response table. 542 """ 543 with self.data_lock: 544 data = self.digital_response_table 545 546 return data
547 548
549 - def send_sysex(self, sysex_command, sysex_data=None):
550 """ 551 This method will send a Sysex command to Firmata with any accompanying data 552 553 @param sysex_command: sysex command 554 @param sysex_data: data for command 555 @return : No return value. 556 """ 557 if not sysex_data: 558 sysex_data = [] 559 560 # convert the message command and data to characters 561 sysex_message = chr(self.START_SYSEX) 562 sysex_message += chr(sysex_command) 563 if len(sysex_data): 564 for d in sysex_data: 565 sysex_message += chr(d) 566 sysex_message += chr(self.END_SYSEX) 567 568 for data in sysex_message: 569 self.transport.write(data)
570
571 - def send_command(self, command):
572 """ 573 This method is used to transmit a non-sysex command. 574 @param command: Command to send to firmata includes command + data formatted by caller 575 @return : No return value. 576 """ 577 send_message = "" 578 for i in command: 579 send_message += chr(i) 580 581 for data in send_message: 582 self.transport.write(data)
583
584 - def system_reset(self, reset_sleep_time=2):
585 """ 586 Send the reset command to the Arduino. 587 It resets the response tables to their initial values 588 @return: No return value 589 """ 590 data = chr(self.SYSTEM_RESET) 591 self.transport.write(data) 592 593 # response table re-initialization 594 # for each pin set the mode to input and the last read data value to zero 595 with self.data_lock: 596 597 # remove all old entries from existing tables 598 for _ in range(len(self.digital_response_table)): 599 self.digital_response_table.pop() 600 601 for _ in range(len(self.analog_response_table)): 602 self.analog_response_table.pop() 603 604 # reinitialize tables 605 for pin in range(0, self.total_pins_discovered): 606 response_entry = [self.INPUT, 0] 607 self.digital_response_table.append(response_entry) 608 609 for pin in range(0, self.number_of_analog_pins_discovered): 610 response_entry = [self.INPUT, 0] 611 self.analog_response_table.append(response_entry) 612 613 time.sleep(reset_sleep_time)
614 615 616 #noinspection PyMethodMayBeStatic 617 # keeps pycharm happy
618 - def _string_data(self, data):
619 """ 620 This method handles the incoming string data message from Firmata. 621 The string is printed to the console 622 623 @param data: Message data from Firmata 624 @return: No return value.s 625 """ 626 print "_string_data:" 627 string_to_print = [] 628 for i in data[::2]: 629 string_to_print.append(chr(i)) 630 print string_to_print
631
632 - def i2c_reply(self, data):
633 """ 634 This method receives replies to i2c_read requests. It stores the data for each i2c device 635 address in a dictionary called i2c_map. The data is retrieved via a call to i2c_get_read_data() 636 in pymata.py 637 @param data: raw data returned from i2c device 638 """ 639 reply_data = [] 640 address = (data[0] & 0x7f) + (data[1] << 7) 641 register = data[2] & 0x7f + data[3] << 7 642 reply_data.append(register) 643 for i in xrange(4, len(data), 2): 644 data_item = (data[i] & 0x7f) + (data[i + 1] << 7) 645 reply_data.append(data_item) 646 self.i2c_map[address] = reply_data
647
648 - def capability_response(self, data):
649 """ 650 This method handles a capability response message and stores the results to be retrieved 651 via get_capability_query_results() in pymata.py 652 @param data: raw capability data 653 """ 654 self.capability_query_results = data
655
656 - def pin_state_response(self, data):
657 """ 658 This method handles a pin state response message and stores the results to be retrieved 659 via get_pin_state_query_results() in pymata.py 660 @param data: raw pin state data 661 """ 662 self.last_pin_query_results = data
663
664 - def analog_mapping_response(self, data):
665 """ 666 This method handles an analog mapping query response message and stores the results to be retrieved 667 via get_analog_mapping_request_results() in pymata.py 668 @param data: raw analog mapping data 669 """ 670 self.analog_mapping_query_results = data
671 672
673 - def run(self):
674 """ 675 This method starts the thread that continuously runs to receive and interpret 676 messages coming from Firmata. This must be the last method in this file 677 678 It also checks the deque for messages to be sent to Firmata. 679 """ 680 681 # To add a command to the command dispatch table, append here. 682 self.command_dispatch.update({self.REPORT_VERSION: [self.report_version, 2]}) 683 self.command_dispatch.update({self.REPORT_FIRMWARE: [self.report_firmware, 1]}) 684 self.command_dispatch.update({self.ANALOG_MESSAGE: [self.analog_message, 2]}) 685 self.command_dispatch.update({self.DIGITAL_MESSAGE: [self.digital_message, 2]}) 686 self.command_dispatch.update({self.ENCODER_DATA: [self.encoder_data, 3]}) 687 self.command_dispatch.update({self.SONAR_DATA: [self.sonar_data, 3]}) 688 self.command_dispatch.update({self.STRING_DATA: [self._string_data, 2]}) 689 self.command_dispatch.update({self.I2C_REPLY: [self.i2c_reply, 2]}) 690 self.command_dispatch.update({self.CAPABILITY_RESPONSE: [self.capability_response, 2]}) 691 self.command_dispatch.update({self.PIN_STATE_RESPONSE: [self.pin_state_response, 2]}) 692 self.command_dispatch.update({self.ANALOG_MAPPING_RESPONSE: [self.analog_mapping_response, 2]}) 693 694 MAX_COMMAND_WAIT_TIME = 0.5 695 696 while not self.is_stopped(): 697 698 if len(self.command_deque): 699 # get next byte from the deque and process it 700 data = self.command_deque.popleft() 701 702 # this list will be populated with the received data for the command 703 command_data = [] 704 705 command_start_time = time.time() 706 707 try: # The command handling may fail if we get invalid commands or data from 708 # Firmata so put everything in a try block 709 710 # process sysex commands 711 if data == self.START_SYSEX: 712 713 # next char is the actual sysex command 714 # wait until we can get data from the deque 715 while len(self.command_deque) == 0 and time.time() - command_start_time < MAX_COMMAND_WAIT_TIME: 716 pass 717 sysex_command = self.command_deque.popleft() 718 # retrieve the associated command_dispatch entry for this command 719 dispatch_entry = self.command_dispatch.get(sysex_command) 720 721 if dispatch_entry == None: 722 print "Error: No dispatch entry for sysex_command {0:X}".format( sysex_command ) 723 continue 724 725 # get a "pointer" to the method that will process this command 726 method = dispatch_entry[0] 727 728 # now get the rest of the data excluding the END_SYSEX byte 729 end_of_sysex = False 730 while not end_of_sysex and time.time() - command_start_time < MAX_COMMAND_WAIT_TIME: 731 # wait for more data to arrive 732 while len(self.command_deque) == 0 and time.time() - command_start_time < MAX_COMMAND_WAIT_TIME: 733 pass 734 data = self.command_deque.popleft() 735 if data != self.END_SYSEX: 736 command_data.append(data) 737 else: 738 end_of_sysex = True 739 740 # invoke the method to process the command 741 method(command_data) 742 # go to the beginning of the loop to process the next command 743 continue 744 745 #is this a command byte in the range of 0x80-0xff - these are the non-sysex messages 746 elif 0x80 <= data <= 0xff: 747 # look up the method for the command in the command dispatch table 748 # for the digital reporting the command value is modified with port number 749 # the handler needs the port to properly process, so decode that from the command and 750 # place in command_data 751 if 0x90 <= data <= 0x9f: 752 port = data & 0xf 753 command_data.append(port) 754 data = 0x90 755 # the pin number for analog data is embedded in the command so, decode it 756 elif 0xe0 <= data <= 0xef: 757 pin = data & 0xf 758 command_data.append(pin) 759 data = 0xe0 760 else: 761 pass 762 763 dispatch_entry = self.command_dispatch.get(data) 764 if dispatch_entry == None: 765 print "Error: No dispatch error for data {0:X}".format( data ) 766 continue 767 768 # this calls the method retrieved from the dispatch table 769 method = dispatch_entry[0] 770 771 # get the number of parameters that this command provides 772 num_args = dispatch_entry[1] 773 774 #look at the number of args that the selected method requires 775 # now get that number of bytes to pass to the called method 776 for i in range(num_args): 777 while len(self.command_deque) == 0 and time.time() - command_start_time < MAX_COMMAND_WAIT_TIME: 778 pass 779 data = self.command_deque.popleft() 780 command_data.append(data) 781 #go execute the command with the argument list 782 method(command_data) 783 784 # go to the beginning of the loop to process the next command 785 continue 786 787 except Exception as e: 788 789 print "Error: Caught exception in command handler -", e
790