Beispiel #1
0
 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))
Beispiel #2
0
    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})
Beispiel #3
0
    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})
Beispiel #4
0
    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