Exemple #1
0
 def read_flash(data_length):  # type: (int) -> UCANCommandSpec
     """
     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)
        cc_address = '000.000.000.000'
        ucan_address = '000.000.000'

        command = UCANCommandSpec(sid=SID.NORMAL_COMMAND,
                                  instructions=[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,
                                  instructions=[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,
                                  instructions=[Instruction(instruction=[0, 0])],
                                  identifier=AddressField('ucan_address', 3))
        ucan_communicator.do_command(cc_address, command, ucan_address, {}, timeout=None)
Exemple #3
0
    def update(cc_address, ucan_address, ucan_communicator, hex_filename,
               version):
        # type: (str, str, UCANCommunicator, str, Optional[str]) -> bool
        """ Flashes the content from an Intel HEX file to the specified uCAN """
        try:
            logger.info('Updating uCAN {0} at CC {1} to {2}'.format(
                ucan_address, cc_address, 'v{0}'.format(version)
                if version is not None else 'unknown version'))

            try:
                response = ucan_communicator.do_command(
                    cc_address, UCANAPI.get_version(), ucan_address, {})
                if response is None:
                    raise RuntimeError()
                current_version = response['firmware_version']
                logger.info(
                    'Current uCAN version: v{0}'.format(current_version))
            except Exception:
                raise RuntimeError('Could not load uCAN version')

            if current_version == version:
                logger.info('uCAN already up-to-date. Skipping')
                return True

            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...')
            uint32_helper = UInt32Field('')
            address_blocks = list(
                range(UCANUpdater.ADDRESS_START, UCANUpdater.ADDRESS_END,
                      UCANUpdater.MAX_FLASH_BYTES))
            total_amount = float(len(address_blocks))
            crc = 0
            total_payload = bytearray()
            logged_percentage = -1
            reset_vector = bytearray([intel_hex[i] for i in range(4)])
            for index, start_address in enumerate(address_blocks):
                end_address = min(UCANUpdater.ADDRESS_END, start_address +
                                  UCANUpdater.MAX_FLASH_BYTES) - 1

                payload = intel_hex.tobinarray(start=start_address,
                                               end=end_address)
                crc = UCANPalletCommandSpec.calculate_crc(payload, crc)
                if start_address == address_blocks[-1]:
                    crc = UCANPalletCommandSpec.calculate_crc(
                        reset_vector, crc)
                    payload += reset_vector
                    payload += uint32_helper.encode(crc)

                little_start_address = struct.unpack(
                    '<I', struct.pack('>I', start_address))[0]

                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')

            try:
                response = ucan_communicator.do_command(
                    cc_address, UCANAPI.get_version(), ucan_address, {})
                if response is None:
                    raise RuntimeError()
                current_version = response['firmware_version']
                logger.info('New uCAN version: v{0}'.format(current_version))
            except Exception:
                raise RuntimeError('Could not load new uCAN version')

            logger.info('Update completed')
            return True
        except Exception as ex:
            logger.error('Error flashing: {0}'.format(ex))
            return False
Exemple #4
0
    def test_pallet_reconstructing(self):
        received_commands = []

        def send_command(_cid, _command, _fields):
            received_commands.append(_fields)

        core_communicator = CoreCommunicator(controller_serial=Mock())
        core_communicator._send_command = send_command
        ucan_communicator = UCANCommunicator(
            master_communicator=core_communicator)
        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,
                                         tx_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': bytearray([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': bytearray([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 = bytearray([0, 0, 0, 0, 0, 0, pallet_type])
            variable_payload = bytearray(list(range(
                7, 7 + length)))  # [7] or [7, 8, 9]
            crc_payload = self._uint32_helper.encode(
                UCANPalletCommandSpec.calculate_crc(fixed_payload +
                                                    variable_payload))
            ucan_communicator._process_transport_message({
                'cc_address':
                cc_address,
                'nr_can_bytes':
                8,
                'sid':
                1,
                'payload':
                bytearray([129]) + fixed_payload
            })
            ucan_communicator._process_transport_message({
                'cc_address':
                cc_address,
                'nr_can_bytes':
                length + 5,
                'sid':
                1,
                'payload':
                bytearray([0]) + variable_payload + crc_payload
            })
            self.assertDictEqual(consumer.get(1),
                                 {'other': list(variable_payload)})
Exemple #5
0
    def update(cc_address, ucan_address, ucan_communicator, hex_filename,
               version):
        # type: (str, str, UCANCommunicator, str, Optional[str]) -> bool
        """ Flashes the content from an Intel HEX file to the specified uCAN """
        try:
            logger.info('Updating uCAN {0} at CC {1} to {2}'.format(
                ucan_address, cc_address, 'v{0}'.format(version)
                if version is not None else 'unknown version'))

            if not os.path.exists(hex_filename):
                raise RuntimeError(
                    'The given path does not point to an existing file')
            intel_hex = IntelHex(hex_filename)

            try:
                in_bootloader = ucan_communicator.is_ucan_in_bootloader(
                    cc_address, ucan_address)
            except Exception:
                raise RuntimeError('uCAN did not respond')

            if in_bootloader:
                logger.info(
                    'Bootloader already active, skipping version check')
            else:
                current_version = None
                try:
                    response = ucan_communicator.do_command(
                        cc_address=cc_address,
                        command=UCANAPI.get_version(),
                        identity=ucan_address,
                        fields={})
                    if response is None:
                        raise RuntimeError()
                    current_version = response['firmware_version']
                    logger.info(
                        'Current uCAN version: v{0}'.format(current_version))
                except Exception:
                    logger.warning('Could not load uCAN version')

                if current_version == version:
                    logger.info('uCAN already up-to-date. Skipping')
                    return True

                logger.info('Bootloader not active, switching to bootloader')
                ucan_communicator.do_command(
                    cc_address=cc_address,
                    command=UCANAPI.set_bootloader_timeout(SID.NORMAL_COMMAND),
                    identity=ucan_address,
                    fields={'timeout': UCANUpdater.BOOTLOADER_TIMEOUT_UPDATE})
                response = ucan_communicator.do_command(
                    cc_address=cc_address,
                    command=UCANAPI.reset(SID.NORMAL_COMMAND),
                    identity=ucan_address,
                    fields={},
                    timeout=UCANUpdater.BOOTLOADER_APPLICATION_SWITCH_TIMEOUT)
                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=cc_address,
                                         command=UCANAPI.erase_flash(),
                                         identity=ucan_address,
                                         fields={})
            logger.info('Erasing flash... Done')

            logger.info('Flashing contents of {0}'.format(
                os.path.basename(hex_filename)))
            logger.info('Flashing...')
            uint32_helper = UInt32Field('')
            empty_payload = bytearray([255] * UCANUpdater.MAX_FLASH_BYTES)
            address_blocks = list(
                range(UCANUpdater.APPLICATION_START,
                      UCANUpdater.BOOTLOADER_START,
                      UCANUpdater.MAX_FLASH_BYTES))
            total_amount = float(len(address_blocks))
            for i in range(4):
                intel_hex[UCANUpdater.BOOTLOADER_START - 8 +
                          i] = intel_hex[i]  # Copy reset vector
                intel_hex[UCANUpdater.BOOTLOADER_START - 4 +
                          i] = 0x0  # Reserve some space for the CRC
            crc = 0
            total_payload = bytearray()
            logged_percentage = -1
            for index, start_address in enumerate(address_blocks):
                end_address = min(UCANUpdater.BOOTLOADER_START, start_address +
                                  UCANUpdater.MAX_FLASH_BYTES) - 1

                payload = bytearray(
                    intel_hex.tobinarray(start=start_address, end=end_address))
                if start_address < address_blocks[-1]:
                    crc = UCANPalletCommandSpec.calculate_crc(payload, crc)
                else:
                    payload = payload[:-4]
                    crc = UCANPalletCommandSpec.calculate_crc(payload, crc)
                    payload += uint32_helper.encode(crc)

                little_start_address = struct.unpack(
                    '<I', struct.pack('>I', start_address))[0]

                if payload != empty_payload:
                    # Since the uCAN flash area is erased, skip empty blocks
                    tries = 0
                    while True:
                        tries += 1
                        try:
                            result = ucan_communicator.do_command(
                                cc_address=cc_address,
                                command=UCANAPI.write_flash(len(payload)),
                                identity=ucan_address,
                                fields={
                                    'start_address': little_start_address,
                                    'data': payload
                                },
                                timeout=UCANUpdater.WRITE_FLASH_BLOCK_TIMEOUT)
                            if result is None or not result['success']:
                                raise RuntimeError(
                                    'Failed to flash {0} bytes to address 0x{1:04X}'
                                    .format(len(payload), start_address))
                            break
                        except CommunicationTimedOutException as ex:
                            logger.warning(
                                'Flashing... Address 0x{0:04X} failed: {1}'.
                                format(start_address, ex))
                            if tries >= 3:
                                raise

                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 (0x{0:08X})'.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=cc_address,
                command=UCANAPI.set_bootloader_timeout(SID.BOOTLOADER_COMMAND),
                identity=ucan_address,
                fields={'timeout': UCANUpdater.BOOTLOADER_TIMEOUT_RUNTIME})
            logger.info(
                'Set safety bit allowing the application to immediately start on reset'
            )
            ucan_communicator.do_command(
                cc_address=cc_address,
                command=UCANAPI.set_bootloader_safety_flag(),
                identity=ucan_address,
                fields={'safety_flag': 1})

            # Switch to application mode
            logger.info('Reset to application mode')
            response = ucan_communicator.do_command(
                cc_address=cc_address,
                command=UCANAPI.reset(SID.BOOTLOADER_COMMAND),
                identity=ucan_address,
                fields={},
                timeout=UCANUpdater.BOOTLOADER_APPLICATION_SWITCH_TIMEOUT)
            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')

            if ucan_address != '255.255.255':
                try:
                    response = ucan_communicator.do_command(
                        cc_address=cc_address,
                        command=UCANAPI.get_version(),
                        identity=ucan_address,
                        fields={})
                    if response is None:
                        raise RuntimeError()
                    current_version = response['firmware_version']
                    logger.info(
                        'New uCAN version: v{0}'.format(current_version))
                except Exception:
                    raise RuntimeError('Could not load new uCAN version')
            else:
                logger.info(
                    'Skip loading new version as address will have been changed by the application'
                )

            logger.info('Update completed')
            return True
        except Exception as ex:
            logger.error('Error flashing: {0}'.format(ex))
            return False