def decode(self, byte_str): """ Checks if byte_str is the literal """ if byte_str != self.literal: raise ValueError('Byte array does not match literal: expected %s, got %s' % (printable(self.literal), printable(byte_str))) else: return ""
def consume_response_payload( self, payload): # type: (bytearray) -> Dict[str, Any] """ Consumes the payload bytes :param payload Payload from the Core response :returns: Dictionary containing the parsed response """ payload_length = len(payload) result = {} for field in self.response_fields: if field.length is None: continue field_length = field.length # type: Union[int, Callable[[int], int]] if callable(field_length): field_length = field_length(payload_length) if len(payload) < field_length: logger.warning( 'Payload for instruction {0} did not contain all the expected data: {1}' .format(self.instruction, printable(payload))) break data = payload[:field_length] if not isinstance(field, PaddingField): result[field.name] = field.decode(data) payload = payload[field_length:] if payload != bytearray(): logger.warning( 'Payload for instruction {0} could not be consumed completely: {1}' .format(self.instruction, printable(payload))) return result
def consume_response_payload(self, payload): """ Consumes the payload bytes :param payload Payload from the uCAN responses :type payload: list of int :returns: Dictionary containing the parsed response :rtype: dict """ payload_data = [] for response_hash in self.headers: # Headers are ordered if response_hash not in payload: logger.warning( 'Payload did not contain all the expected data: {0}'. format(printable(payload))) return None response_instruction = self._response_instruction_by_hash[ response_hash] payload_entry = payload[response_hash] crc = payload_entry[response_instruction.checksum_byte] expected_crc = UCANCommandSpec.calculate_crc( payload_entry[:response_instruction.checksum_byte]) if crc != expected_crc: logger.info('Unexpected CRC ({0} vs expected {1}): {2}'.format( crc, expected_crc, printable(payload_entry))) return None usefull_payload = payload_entry[ self.header_length:response_instruction.checksum_byte] payload_data += usefull_payload return self._parse_payload(payload_data)
def consume_response_payload(self, payload): """ Consumes the payload bytes :param payload Payload from the Core response :type payload: str :returns: Dictionary containing the parsed response :rtype: dict """ payload_length = len(payload) result = {} for field in self.response_fields: field_length = field.length if callable(field_length): field_length = field_length(payload_length) if len(payload) < field_length: logger.warning( 'Payload for instruction {0} did not contain all the expected data: {1}' .format(self.instruction, printable(payload))) break data = payload[:field_length] if not isinstance(field, PaddingField): result[field.name] = field.decode(data) payload = payload[field_length:] if payload != '': logger.warning( 'Payload for instruction {0} could not be consumed completely: {1}' .format(self.instruction, printable(payload))) return result
def write(self, data): # type: (bytes) -> int if data != self._sequence[0]: assert printable(self._sequence[0]) == printable(data) self._sequence.pop(0) if self._replies: self.fd.write(self._replies[0]) self._replies.pop(0) return len(data)
def write(self, data): """ Write data to serial port """ while self.__sequence[0][0] == 'o': time.sleep(0.01) if data != self.__sequence[0][1]: raise Exception("Got wrong data in SerialMock: expected %s, got %s", (printable(self.__sequence[0][1]), printable(data))) self.__sequence.pop(0) self.bytes_written += len(data)
def write(self, data): """ Write data to serial port """ while self.__sequence[0][0] == 'o': time.sleep(0.01) if data != self.__sequence[0][1]: raise Exception( "Got wrong data in SerialMock: expected %s, got %s", (printable(self.__sequence[0][1]), printable(data))) self.__sequence.pop(0) self.bytes_written += len(data)
def write(self, data): # type: (bytes) -> None """ Write data to serial port """ while self.__sequence[0][0] == 'o': time.sleep(0.01) if bytearray(data) != self.__sequence[0][1]: raise Exception( "Got wrong data in SerialMock:\n expected %s,\n got %s" % (printable(self.__sequence[0][1]), printable(data))) self.__sequence.pop(0) self.bytes_written += len(data)
def _write_to_serial(self, data): """ Write data to the serial port. :param data: the data to write :type data: string """ with self._serial_write_lock: if self._verbose: logger.info('Writing to Core serial: {0}'.format( printable(data))) threshold = time.time() - self._debug_buffer_duration self._debug_buffer['write'][time.time()] = printable(data) for t in self._debug_buffer['write'].keys(): if t < threshold: del self._debug_buffer['write'][t] self._serial.write(data) self._serial_bytes_written += len(data) self._communication_stats['bytes_written'] += len(data)
def __write_to_serial(self, data): # type: (bytearray) -> None """ Write data to the serial port. :param data: the data to write """ self.__debug('writing to', data) self.__serial.write(data) self.__communication_stats_bytes['bytes_written'] += len(data) threshold = time.time() - self.__debug_buffer_duration self.__debug_buffer['write'][time.time()] = printable(data) for t in self.__debug_buffer['write'].keys(): if t < threshold: del self.__debug_buffer['write'][t]
def consume_response_payload(self, payload): """ Consumes the payload bytes :param payload Payload from the uCAN responses :type payload: list of int :returns: Dictionary containing the parsed response :rtype: dict """ crc = UCANPalletCommandSpec.calculate_crc(payload) if crc != 0: logger.info('Unexpected pallet CRC ({0} != 0): {1}'.format(crc, printable(payload))) return None return self._parse_payload(payload[7:-4])
def __write_to_serial(self, data): """ Write data to the serial port. :param data: the data to write :type data: string """ if self.__verbose: PowerCommunicator.__log('writing to', data) self.__serial.write(data) self.__communication_stats['bytes_written'] += len(data) threshold = time.time() - self.__debug_buffer_duration self.__debug_buffer['write'][time.time()] = printable(data) for t in self.__debug_buffer['write'].keys(): if t < threshold: del self.__debug_buffer['write'][t]
def consume_response_payload( self, payload ): # type: (Union[bytearray, Dict[int, bytearray]]) -> Optional[Dict[str, Any]] """ Consumes the payload bytes :param payload Payload from the uCAN responses :returns: Dictionary containing the parsed response """ if isinstance(payload, bytearray): raise RuntimeError( 'An UCANCommandSpec cannot consume bytearray payloads') payload_data = bytearray() for response_hash in self.headers: # Headers are ordered if response_hash not in payload: logger.warning( 'Payload did not contain all the expected data: {0}'. format(printable(payload))) return None response_instruction = self._response_instruction_by_hash[ response_hash] payload_entry = payload[response_hash] if response_instruction.checksum_byte is None: raise RuntimeError('Unknown checksum byte') crc = payload_entry[response_instruction.checksum_byte] expected_crc = UCANCommandSpec.calculate_crc( payload_entry[:response_instruction.checksum_byte]) if crc != expected_crc: logger.info('Unexpected CRC ({0} vs expected {1}): {2}'.format( crc, expected_crc, printable(payload_entry))) return None usefull_payload = payload_entry[ self.header_length:response_instruction.checksum_byte] payload_data += usefull_payload return self._parse_payload(payload_data)
def consume_response_payload( self, payload): # type: (bytearray) -> Optional[Dict[str, Any]] """ Consumes the payload bytes """ crc = SlaveCommandSpec.decode_crc( payload[-(self._response_footer_length - 1):-len(SlaveCommandSpec.RESPONSE_SUFFIX)]) expected_crc = SlaveCommandSpec.decode_crc( SlaveCommandSpec.calculate_crc( payload[self._response_prefix_length:-self. _response_footer_length])) if crc != expected_crc: logger.info('Unexpected CRC ({0} vs expected {1}): {2}'.format( crc, expected_crc, printable(payload))) return None payload_data = payload[self.header_length + self._instruction_length:-self. _response_footer_length] return self._parse_payload(payload_data)
def consume_response_payload( self, payload ): # type: (Union[bytearray, Dict[int, bytearray]]) -> Optional[Dict[str, Any]] """ Consumes the payload bytes :param payload Payload from the uCAN responses :returns: Dictionary containing the parsed response """ if not isinstance(payload, bytearray): raise RuntimeError( 'UCANPalletCommandSpec can only consume bytearray payloads') crc = UCANPalletCommandSpec.calculate_crc(payload) if crc != 0: logger.info('Unexpected pallet CRC ({0} != 0): {1}'.format( crc, printable(payload))) return None return self._parse_payload(payload[7:-4])
def _parse_payload(self, payload_data): result = {} payload_length = len(payload_data) for field in self._response_fields: if isinstance(field, StringField): field_length = payload_data.index(0) + 1 else: field_length = field.length if callable(field_length): field_length = field_length(payload_length) if len(payload_data) < field_length: logger.warning( 'Payload did not contain all the expected data: {0}'. format(printable(payload_data))) break data = payload_data[:field_length] if not isinstance(field, PaddingField): result[field.name] = field.decode_bytes(data) payload_data = payload_data[field_length:] return result
def _parse_payload(self, payload_data): # type: (bytearray) -> Dict[str, Any] result = {} payload_length = len(payload_data) for field in self.response_fields: if field.length is None: continue field_length = field.length # type: Union[int, Callable[[int], int]] if callable(field_length): field_length = field_length(payload_length) if len(payload_data) < field_length: logger.warning( 'Payload did not contain all the expected data: {0}'. format(printable(payload_data))) break data = payload_data[:field_length] # type: bytearray if not isinstance(field, PaddingField): result[field.name] = field.decode(data) payload_data = payload_data[field_length:] return result
def do_command(self, address, command, fields, timeout=2): # type: (str, SlaveCommandSpec, Dict[str, Any], Optional[int]) -> Optional[Dict[str, Any]] """ Send an slave command over the Communicator and block until an answer is received. If the Core does not respond within the timeout period, a CommunicationTimedOutException is raised """ if not self._transparent_mode: raise RuntimeError('Transparent mode not active.') command.set_address(address) consumer = Consumer(command) self.register_consumer(consumer) master_timeout = False payload = command.create_request_payload(fields) if self._verbose: logger.info( 'Writing to slave transport: Address: {0} - Data: {1}'. format(address, printable(payload))) try: self._communicator.do_command( command=CoreAPI.slave_tx_transport_message(len(payload)), fields={'payload': payload}, timeout=timeout) except CommunicationTimedOutException as ex: logger.error( 'Internal timeout during slave transport: {0}'.format(ex)) master_timeout = True try: if master_timeout: # When there's a communication timeout with the master, catch this exception and timeout the consumer # so it uses a flow expected by the caller return consumer.get(0) if timeout is not None and not consumer.send_only(): return consumer.get(timeout) except CommunicationTimedOutException: self.unregister_consumer(consumer) raise return None
def _process_transport_message(self, package): payload = package['payload'] if self._verbose: logger.info('Reading from slave transport: Data: {0}'.format( printable(payload))) self._read_buffer += payload if SlaveCommandSpec.RESPONSE_PREFIX not in self._read_buffer: return index = self._read_buffer.index(SlaveCommandSpec.RESPONSE_PREFIX) if index > 0: self._read_buffer = self._read_buffer[index:] consumed_bytes = 0 for consumer in self._consumers[:]: consumed_bytes = consumer.suggest_payload(self._read_buffer) if consumed_bytes > 0: self.unregister_consumer(consumer) break self._read_buffer = self._read_buffer[ max(len(SlaveCommandSpec.RESPONSE_PREFIX), consumed_bytes):]
def __debug(self, action, data): # type: (str, Optional[bytearray]) -> None if self.__verbose and data is not None: logger.debug("%.3f %s power: %s" % (time.time(), action, printable(data)))
def __read_from_serial(self): """ Read a PowerCommand from the serial port. """ phase = 0 index = 0 header = "" length = 0 data = "" crc = 0 command = "" try: while phase < 8: byte = self.__serial.read_queue.get(True, 0.25) command += byte self.__communication_stats['bytes_read'] += 1 if phase == 0: # Skip non 'R' bytes if byte == 'R': phase = 1 else: phase = 0 elif phase == 1: # Expect 'T' if byte == 'T': phase = 2 else: raise Exception("Unexpected character") elif phase == 2: # Expect 'R' if byte == 'R': phase = 3 index = 0 else: raise Exception("Unexpected character") elif phase == 3: # Read the header fields header += byte index += 1 if index == 8: length = ord(byte) if length > 0: phase = 4 index = 0 else: phase = 5 elif phase == 4: # Read the data data += byte index += 1 if index == length: phase = 5 elif phase == 5: # Read the CRC code crc = ord(byte) phase = 6 elif phase == 6: # Expect '\r' if byte == '\r': phase = 7 else: raise Exception("Unexpected character") elif phase == 7: # Expect '\n' if byte == '\n': phase = 8 else: raise Exception("Unexpected character") crc_match = (crc7(header + data) == crc) if header[0] == 'E' else (crc8(data) == crc) if not crc_match: raise Exception('CRC{0} doesn\'t match'.format( '7' if header[0] == 'E' else '8')) except Empty: raise CommunicationTimedOutException('Communication timed out') finally: if self.__verbose: PowerCommunicator.__log('reading from', command) threshold = time.time() - self.__debug_buffer_duration self.__debug_buffer['read'][time.time()] = printable(command) for t in self.__debug_buffer['read'].keys(): if t < threshold: del self.__debug_buffer['read'][t] return header, data
def _read(self): """ Code for the background read thread: reads from the serial port and forward certain messages to waiting consumers Request format: 'STR' + {CID, 1 byte} + {command, 2 bytes} + {length, 2 bytes} + {payload, `length` bytes} + 'C' + {checksum, 1 byte} + '\r\n\r\n' Response format: 'RTR' + {CID, 1 byte} + {command, 2 bytes} + {length, 2 bytes} + {payload, `length` bytes} + 'C' + {checksum, 1 byte} + '\r\n' """ data = bytearray() message_length = None header_fields = None header_length = len( CoreCommunicator.START_OF_REPLY ) + 1 + 2 + 2 # RTR + CID (1 byte) + command (2 bytes) + length (2 bytes) footer_length = 1 + 1 + len( CoreCommunicator.END_OF_REPLY) # 'C' + checksum (1 byte) + \r\n need_more_data = False while not self._stop: try: # Wait for data if more data is expected if need_more_data: readers, _, _ = select.select([self._serial], [], [], 1) if not readers: continue need_more_data = False # Read what's now on the serial port num_bytes = self._serial.inWaiting() if num_bytes > 0: data += self._serial.read(num_bytes) # Update counters self._serial_bytes_read += num_bytes self._communication_stats['bytes_read'] += num_bytes # Wait for the full message, or the header length min_length = message_length or header_length if len(data) < min_length: need_more_data = True continue if message_length is None: # Check if the data contains the START_OF_REPLY if CoreCommunicator.START_OF_REPLY not in data: need_more_data = True continue # Align with START_OF_REPLY if not data.startswith(CoreCommunicator.START_OF_REPLY): data = CoreCommunicator.START_OF_REPLY + data.split( CoreCommunicator.START_OF_REPLY, 1)[-1] if len(data) < header_length: continue header_fields = CoreCommunicator._parse_header(data) message_length = header_fields[ 'length'] + header_length + footer_length # If not all data is present, wait for more data if len(data) < message_length: continue message = data[:message_length] # type: bytearray data = data[message_length:] # A possible message is received, log where appropriate if self._verbose: logger.debug('Reading from Core serial: {0}'.format( printable(message))) threshold = time.time() - self._debug_buffer_duration self._debug_buffer['read'][time.time()] = message for t in self._debug_buffer['read'].keys(): if t < threshold: del self._debug_buffer['read'][t] # Validate message boundaries correct_boundaries = message.startswith( CoreCommunicator.START_OF_REPLY) and message.endswith( CoreCommunicator.END_OF_REPLY) if not correct_boundaries: logger.info('Unexpected boundaries: {0}'.format( printable(message))) # Reset, so we'll wait for the next RTR message_length = None data = message[ 3:] + data # Strip the START_OF_REPLY, and restore full data continue # Validate message CRC crc = bytearray([message[-3]]) payload = message[8:-4] # type: bytearray checked_payload = message[3:-4] # type: bytearray expected_crc = CoreCommunicator._calculate_crc(checked_payload) if crc != expected_crc: logger.info( 'Unexpected CRC ({0} vs expected {1}): {2}'.format( crc, expected_crc, printable(checked_payload))) # Reset, so we'll wait for the next RTR message_length = None data = message[ 3:] + data # Strip the START_OF_REPLY, and restore full data continue # A valid message is received, reliver it to the correct consumer consumers = self._consumers.get(header_fields['hash'], []) for consumer in consumers[:]: if self._verbose: logger.debug( 'Delivering payload to consumer {0}.{1}: {2}'. format(header_fields['command'], header_fields['cid'], printable(payload))) consumer.consume(payload) if isinstance(consumer, Consumer): self.unregister_consumer(consumer) self.discard_cid(header_fields['cid']) # Message processed, cleaning up message_length = None except Exception: logger.exception('Unexpected exception at Core read thread') data = bytearray() message_length = None
def process(buffer): return {k: printable(v) for k, v in six.iteritems(buffer)}
def __log(action, data): if data is not None: LOGGER.info("%.3f %s power: %s" % (time.time(), action, printable(data)))
def _read(self): """ Code for the background read thread: reads from the serial port and forward certain messages to waiting consumers Request format: 'STR' + {CID, 1 byte} + {command, 2 bytes} + {length, 2 bytes} + {payload, `length` bytes} + 'C' + {checksum, 1 byte} + '\r\n\r\n' Response format: 'RTR' + {CID, 1 byte} + {command, 2 bytes} + {length, 2 bytes} + {payload, `length` bytes} + 'C' + {checksum, 1 byte} + '\r\n' """ data = '' wait_for_length = None header_length = len( CoreCommunicator.START_OF_REPLY ) + 1 + 2 + 2 # RTR + CID (1 byte) + command (2 bytes) + length (2 bytes) footer_length = 1 + 1 + len( CoreCommunicator.END_OF_REPLY) # 'C' + checksum (1 byte) + \r\n while not self._stop: # Read what's now on the buffer num_bytes = self._serial.inWaiting() if num_bytes > 0: data += self._serial.read(num_bytes) # Update counters self._serial_bytes_read += num_bytes self._communication_stats['bytes_read'] += num_bytes # Wait for a speicific number of bytes, or the header length if (wait_for_length is None and len(data) < header_length) or len(data) < wait_for_length: continue # Check if the data contains the START_OF_REPLY if CoreCommunicator.START_OF_REPLY not in data: continue if wait_for_length is None: # Flush everything before the START_OF_REPLY data = CoreCommunicator.START_OF_REPLY + data.split( CoreCommunicator.START_OF_REPLY, 1)[-1] if len(data) < header_length: continue # Not enough data header_fields = CoreCommunicator._parse_header(data) message_length = header_fields[ 'length'] + header_length + footer_length # If not all data is present, wait for more data if len(data) < message_length: wait_for_length = message_length continue message = data[:message_length] data = data[message_length:] # A possible message is received, log where appropriate if self._verbose: logger.info('Reading from Core serial: {0}'.format( printable(message))) threshold = time.time() - self._debug_buffer_duration self._debug_buffer['read'][time.time()] = printable(message) for t in self._debug_buffer['read'].keys(): if t < threshold: del self._debug_buffer['read'][t] # Validate message boundaries correct_boundaries = message.startswith( CoreCommunicator.START_OF_REPLY) and message.endswith( CoreCommunicator.END_OF_REPLY) if not correct_boundaries: logger.info('Unexpected boundaries: {0}'.format( printable(message))) # Reset, so we'll wait for the next RTR wait_for_length = None data = message[ 3:] + data # Strip the START_OF_REPLY, and restore full data continue # Validate message CRC crc = ord(message[-3]) payload = message[8:-4] checked_payload = message[3:-4] expected_crc = CoreCommunicator._calculate_crc(checked_payload) if crc != expected_crc: logger.info('Unexpected CRC ({0} vs expected {1}): {2}'.format( crc, expected_crc, printable(checked_payload))) # Reset, so we'll wait for the next RTR wait_for_length = None data = message[ 3:] + data # Strip the START_OF_REPLY, and restore full data continue # A valid message is received, reliver it to the correct consumer consumers = self._consumers.get(header_fields['header'], []) for consumer in consumers[:]: if self._verbose: logger.info( 'Delivering payload to consumer {0}.{1}: {2}'.format( header_fields['command'], header_fields['cid'], printable(payload))) consumer.consume(payload) if isinstance(consumer, Consumer): self.unregister_consumer(consumer) # Message processed, cleaning up wait_for_length = None
def __log(self, action, data): if data is not None: LOGGER.info("%.3f %s power: %s" % (time.time(), action, printable(data)))
def do_command(self, cc_address, command, identity, fields, timeout=2): """ Send a uCAN command over the Communicator and block until an answer is received. If the Core does not respond within the timeout period, a CommunicationTimedOutException is raised :param cc_address: An address of the CC connected to the uCAN :type cc_address: str :param command: specification of the command to execute :type command: master_core.ucan_command.UCANCommandSpec :param identity: The identity :type identity: str :param fields: A dictionary with the command input field values :type fields dict :param timeout: maximum allowed time before a CommunicationTimedOutException is raised :type timeout: int or None :raises: serial_utils.CommunicationTimedOutException :returns: dict containing the output fields of the command """ if self._cc_pallet_mode.get(cc_address, False) is True: raise BootloadingException('CC {0} is currently bootloading'.format(cc_address)) command.set_identity(identity) if command.sid == SID.BOOTLOADER_PALLET: consumer = PalletConsumer(cc_address, command, self._release_pallet_mode) self._cc_pallet_mode[cc_address] = True else: consumer = Consumer(cc_address, command) self.register_consumer(consumer) master_timeout = False for payload in command.create_request_payloads(identity, fields): if self._verbose: logger.info('Writing to uCAN transport: CC {0} - SID {1} - Data: {2}'.format(cc_address, command.sid, printable(payload))) try: self._communicator.do_command(command=CoreAPI.ucan_tx_transport_message(), fields={'cc_address': cc_address, 'nr_can_bytes': len(payload), 'sid': command.sid, 'payload': payload + [0] * (8 - len(payload))}, timeout=timeout) except CommunicationTimedOutException as ex: logger.error('Internal timeout during uCAN transport to CC {0}: {1}'.format(cc_address, ex)) master_timeout = True break consumer.check_send_only() if master_timeout: # When there's a communication timeout with the master, catch this exception and timeout the consumer # so it uses a flow expected by the caller return consumer.get(0) if timeout is not None: return consumer.get(timeout)
def _process_transport_message(self, package): payload_length = package['nr_can_bytes'] payload = package['payload'][:payload_length] sid = package['sid'] cc_address = package['cc_address'] if self._verbose: logger.info('Reading from uCAN transport: CC {0} - SID {1} - Data: {2}'.format(cc_address, sid, printable(payload))) consumers = self._consumers.get(cc_address, []) for consumer in consumers[:]: if consumer.suggest_payload(payload): self.unregister_consumer(consumer)
def __read_from_serial(self): # type: () -> Tuple[bytearray, bytearray] """ Read a PowerCommand from the serial port. """ phase = 0 index = 0 header = bytearray() length = 0 data = bytearray() crc = 0 command = bytearray() try: while phase < 8: byte = self.__serial.read_queue.get(True, 0.25) command += byte self.__communication_stats_bytes['bytes_read'] += 1 if phase == 0: # Skip non 'R' bytes if byte == bytearray(b'R'): phase = 1 else: phase = 0 elif phase == 1: # Expect 'T' if byte == bytearray(b'T'): phase = 2 else: raise Exception("Unexpected character") elif phase == 2: # Expect 'R' if byte == bytearray(b'R'): phase = 3 index = 0 else: raise Exception("Unexpected character") elif phase == 3: # Read the header fields header += byte index += 1 if index == 8: length = ord(byte) if length > 0: phase = 4 index = 0 else: phase = 5 elif phase == 4: # Read the data data += byte index += 1 if index == length: phase = 5 elif phase == 5: # Read the CRC code crc = ord(byte) phase = 6 elif phase == 6: # Expect '\r' if byte == bytearray(b'\r'): phase = 7 else: raise Exception("Unexpected character") elif phase == 7: # Expect '\n' if byte == bytearray(b'\n'): phase = 8 else: raise Exception("Unexpected character") if PowerCommand.get_crc(header, data) != crc: raise Exception('CRC doesn\'t match') except Empty: raise CommunicationTimedOutException('Communication timed out') except Exception: self.__debug('reading from', command) raise finally: self.__debug('reading from', command) threshold = time.time() - self.__debug_buffer_duration self.__debug_buffer['read'][time.time()] = printable(command) for t in self.__debug_buffer['read'].keys(): if t < threshold: del self.__debug_buffer['read'][t] return header, data
def __log(action, data): if data is not None: logger.info("%.3f %s power: %s" % (time.time(), action, printable(data)))