def test_string_parsing(self): core_communicator = Mock() ucan_communicator = UCANCommunicator(master_communicator=core_communicator, verbose=True) cc_address = '000.000.000.000' ucan_address = '000.000.000' pallet_type = PalletType.MCU_ID_REQUEST # Not important for this test foo = 'XY' # 2 chars max, otherwise more segments are needed and the test might get too complex # Build response-only command command = UCANPalletCommandSpec(identifier=AddressField('ucan_address', 3), pallet_type=pallet_type, response_fields=[StringField('foo')]) ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None) consumer = ucan_communicator._consumers[cc_address][0] # Build and validate fake reply from Core payload_segment_1 = [0, 0, 0, 0, 0, 0, PalletType.MCU_ID_REPLY] payload_segment_2 = [ord(x) for x in '{0}\x00'.format(foo)] crc_payload = Int32Field.encode_bytes(UCANPalletCommandSpec.calculate_crc(payload_segment_1 + payload_segment_2)) payload_segment_2 += crc_payload ucan_communicator._process_transport_message({'cc_address': cc_address, 'nr_can_bytes': 8, 'sid': 1, 'payload': [129] + payload_segment_1}) ucan_communicator._process_transport_message({'cc_address': cc_address, 'nr_can_bytes': 8, 'sid': 1, 'payload': [0] + payload_segment_2}) self.assertDictEqual(consumer.get(1), {'foo': foo})
def test_crc(self): payload = [10, 50, 250] total_payload = payload + Int32Field.encode_bytes(UCANPalletCommandSpec.calculate_crc(payload)) self.assertEqual(0, UCANPalletCommandSpec.calculate_crc(total_payload)) crc = 0 for part in payload: crc = UCANPalletCommandSpec.calculate_crc([part], crc) total_payload = payload + Int32Field.encode_bytes(crc) self.assertEqual(0, UCANPalletCommandSpec.calculate_crc(total_payload))
def test_pallet_reconstructing(self): received_commands = [] def send_command(_cid, _command, _fields): received_commands.append(_fields) core_communicator = CoreCommunicator(controller_serial=Mock(), verbose=True) core_communicator._send_command = send_command ucan_communicator = UCANCommunicator(master_communicator=core_communicator, verbose=True) cc_address = '000.000.000.000' ucan_address = '000.000.000' pallet_type = PalletType.MCU_ID_REQUEST # Not important for this test for length in [1, 3]: # Build command command = UCANPalletCommandSpec(identifier=AddressField('ucan_address', 3), pallet_type=pallet_type, request_fields=[ByteField('foo'), ByteField('bar')], response_fields=[ByteArrayField('other', length)]) # Send command to mocked Core communicator received_commands = [] ucan_communicator.do_command(cc_address, command, ucan_address, {'foo': 1, 'bar': 2}, timeout=None) # Validate whether the correct data was send to the Core self.assertEqual(len(received_commands), 2) self.assertDictEqual(received_commands[0], {'cc_address': cc_address, 'nr_can_bytes': 8, 'payload': [129, 0, 0, 0, 0, 0, 0, pallet_type], # +--------------+ = source and destination uCAN address 'sid': SID.BOOTLOADER_PALLET}) self.assertDictEqual(received_commands[1], {'cc_address': cc_address, 'nr_can_bytes': 7, 'payload': [0, 1, 2, 219, 155, 250, 178, 0], # | | +----------------+ = checksum # | + = bar # + = foo 'sid': SID.BOOTLOADER_PALLET}) # Build fake reply from Core consumer = ucan_communicator._consumers[cc_address][0] fixed_payload = [0, 0, 0, 0, 0, 0, pallet_type] variable_payload = range(7, 7 + length) # [7] or [7, 8, 9] crc_payload = Int32Field.encode_bytes(UCANPalletCommandSpec.calculate_crc(fixed_payload + variable_payload)) ucan_communicator._process_transport_message({'cc_address': cc_address, 'nr_can_bytes': 8, 'sid': 1, 'payload': [129] + fixed_payload}) ucan_communicator._process_transport_message({'cc_address': cc_address, 'nr_can_bytes': length + 5, 'sid': 1, 'payload': [0] + variable_payload + crc_payload}) self.assertDictEqual(consumer.get(1), {'other': variable_payload})
def erase_flash(): """ Erases uCAN flash Note: uCAN needs to be in bootloader """ return UCANPalletCommandSpec( identifier=AddressField('ucan_address', 3), pallet_type=PalletType.FLASH_ERASE_REQUEST)
def get_bootloader_id(): """ Gets the uCAN bootloader ID Note: uCAN needs to be in bootloader """ return UCANPalletCommandSpec( identifier=AddressField('ucan_address', 3), pallet_type=PalletType.BOOTLOADER_ID_REQUEST, response_fields=[StringField('bootloader_id')])
def write_flash(data_length): """ Writes uCAN flash Note: uCAN needs to be in bootloader """ return UCANPalletCommandSpec( identifier=AddressField('ucan_address', 3), pallet_type=PalletType.FLASH_WRITE_REQUEST, request_fields=[ Int32Field('start_address'), ByteArrayField('data', data_length) ])
def read_flash(data_length): """ Reads uCAN flash Note: uCAN needs to be in bootloader """ return UCANPalletCommandSpec( identifier=AddressField('ucan_address', 3), pallet_type=PalletType.FLASH_READ_REQUEST, request_fields=[ UInt32Field('start_address'), ByteField('data_length') ], response_fields=[ByteArrayField('data', data_length)])
def test_bootload_lock(self): core_communicator = Mock() ucan_communicator = UCANCommunicator(master_communicator=core_communicator, verbose=True) cc_address = '000.000.000.000' ucan_address = '000.000.000' command = UCANCommandSpec(sid=SID.NORMAL_COMMAND, instruction=Instruction(instruction=[0, 0]), identifier=AddressField('ucan_address', 3)) ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None) command = UCANPalletCommandSpec(identifier=AddressField('ucan_address', 3), pallet_type=PalletType.MCU_ID_REPLY) ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None) pallet_consumer = ucan_communicator._consumers[cc_address][-1] # Load last consumer command = UCANCommandSpec(sid=SID.NORMAL_COMMAND, instruction=Instruction(instruction=[0, 0]), identifier=AddressField('ucan_address', 3)) with self.assertRaises(BootloadingException): ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None) command = UCANPalletCommandSpec(identifier=AddressField('ucan_address', 3), pallet_type=PalletType.MCU_ID_REPLY) with self.assertRaises(BootloadingException): ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None) try: pallet_consumer.get(0.1) except Exception: pass # command = UCANCommandSpec(sid=SID.NORMAL_COMMAND, instruction=Instruction(instruction=[0, 0]), identifier=AddressField('ucan_address', 3)) ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None)
def update(cc_address, ucan_address, ucan_communicator, hex_filename): """ Flashes the content from an Intel HEX file to the specified uCAN :param cc_address: CC address :param ucan_address: uCAN address :param ucan_communicator: uCAN commnicator :type ucan_communicator: master_core.ucan_communicator.UCANCommunicator :param hex_filename: The filename of the hex file to flash """ try: # TODO: Check version and skip update if the version is already active logger.info('Updating uCAN {0} at CC {1}'.format( ucan_address, cc_address)) if not os.path.exists(hex_filename): raise RuntimeError( 'The given path does not point to an existing file') intel_hex = IntelHex(hex_filename) in_bootloader = ucan_communicator.is_ucan_in_bootloader( cc_address, ucan_address) if in_bootloader: logger.info('Bootloader active') else: logger.info('Bootloader not active, switching to bootloader') ucan_communicator.do_command( cc_address, UCANAPI.set_bootloader_timeout(SID.NORMAL_COMMAND), ucan_address, {'timeout': UCANUpdater.BOOTLOADER_TIMEOUT_UPDATE}) response = ucan_communicator.do_command( cc_address, UCANAPI.reset(SID.NORMAL_COMMAND), ucan_address, {}, timeout=10) if response is None: raise RuntimeError('Error resettings uCAN before flashing') if response.get('application_mode', 1) != 0: raise RuntimeError( 'uCAN didn\'t enter bootloader after reset') in_bootloader = ucan_communicator.is_ucan_in_bootloader( cc_address, ucan_address) if not in_bootloader: raise RuntimeError('Could not enter bootloader') logger.info('Bootloader active') logger.info('Erasing flash...') ucan_communicator.do_command(cc_address, UCANAPI.erase_flash(), ucan_address, {}) logger.info('Erasing flash... Done') logger.info('Flashing contents of {0}'.format( os.path.basename(hex_filename))) logger.info('Flashing...') address_blocks = range(UCANUpdater.ADDRESS_START, UCANUpdater.ADDRESS_END, UCANUpdater.MAX_FLASH_BYTES) total_amount = float(len(address_blocks)) crc = 0 total_payload = [] logged_percentage = -1 reset_vector = [intel_hex[i] for i in xrange(4)] for index, start_address in enumerate(address_blocks): end_address = min(UCANUpdater.ADDRESS_END, start_address + UCANUpdater.MAX_FLASH_BYTES) payload = [] for i in xrange(start_address, end_address): payload.append(intel_hex[i]) crc = UCANPalletCommandSpec.calculate_crc(payload, crc) if start_address == address_blocks[-1]: crc = UCANPalletCommandSpec.calculate_crc( reset_vector, crc) payload += reset_vector payload += Int32Field.encode_bytes(crc) little_start_address = struct.unpack( '<I', struct.pack('>I', start_address) )[0] # TODO: Handle endianness in API definition using Field endianness if payload != [255] * UCANUpdater.MAX_FLASH_BYTES: # Since the uCAN flash area is erased, skip empty blocks ucan_communicator.do_command( cc_address, UCANAPI.write_flash(len(payload)), ucan_address, { 'start_address': little_start_address, 'data': payload }) total_payload += payload percentage = int(index / total_amount * 100) if percentage > logged_percentage: logger.info('Flashing... {0}%'.format(percentage)) logged_percentage = percentage logger.info('Flashing... Done') crc = UCANPalletCommandSpec.calculate_crc(total_payload) if crc != 0: raise RuntimeError( 'Unexpected error in CRC calculation ({0})'.format(crc)) # Prepare reset to application mode logger.info('Reduce bootloader timeout to {0}s'.format( UCANUpdater.BOOTLOADER_TIMEOUT_RUNTIME)) ucan_communicator.do_command( cc_address, UCANAPI.set_bootloader_timeout(SID.BOOTLOADER_COMMAND), ucan_address, {'timeout': UCANUpdater.BOOTLOADER_TIMEOUT_RUNTIME}) logger.info( 'Set safety bit allowing the application to immediately start on reset' ) ucan_communicator.do_command(cc_address, UCANAPI.set_bootloader_safety_flag(), ucan_address, {'safety_flag': 1}) # Switch to application mode logger.info('Reset to application mode') response = ucan_communicator.do_command( cc_address, UCANAPI.reset(SID.BOOTLOADER_COMMAND), ucan_address, {}, timeout=10) if response is None: raise RuntimeError('Error resettings uCAN after flashing') if response.get('application_mode', 0) != 1: raise RuntimeError( 'uCAN didn\'t enter application mode after reset') logger.info('Update completed') return True except Exception as ex: logger.error('Error flashing: {0}'.format(ex)) return False