Example #1
0
 def test_parse_can_frames_exceptions(self, ascii_protobuf, mock_open):
     """Tests for an invalid CAN ID handler."""
     mock_open.side_effect = [
         mock.mock_open(read_data=ascii_protobuf).return_value
     ]
     with self.assertRaises(Exception):
         data.parse_can_frames(None)
Example #2
0
def main():
    """The main entry-point of the program"""
    # pylint: disable=R0914
    database = cantools.database.can.Database(version='')

    # Iterate through all the CAN device IDs and add them to the DBC.
    can_devices = data.parse_can_device_enum()
    for device_id, device_name in can_devices.items():
        if device_name not in ['RESERVED']:
            node = cantools.database.can.Node(
                name=device_name,
                comment='Device ID: {}'.format(hex(device_id))
            )
            database.nodes.append(node)

    # Iterate through all the CAN messages IDs and:
    #
    #   1. Convert the Message ID to the CAN Arbitration ID.
    #   2. Add the Message to the DBC.
    #   3. For each of the signals, add it to the Message.
    #   4. If the Message is critical, add an ACK message to the DBC.
    can_messages = data.parse_can_frames('can_messages.asciipb')
    device_enum = data.parse_can_device_enum()

    def get_key_by_val(dictionary, val):
        """Helper function to get key for dictionary value"""
        for key, value in dictionary.items():
            if val == value:
                return key
        return None

    for msg_id, can_frame in can_messages.items():
        source = get_key_by_val(device_enum, can_frame.source)
        # Checks for critical messages to make sure an ACK is added later
        if can_frame.is_critical is not None and can_frame.is_critical:
            ACKABLE_MESSAGES[msg_id] = ((can_frame.target).replace(" ", "")).split(",")
        # Checks for signed messages
        if can_frame.is_signed is not None and can_frame.is_signed:
            SIGNED_MESSAGES.append(str(can_frame.msg_name))

        # All these message types must be Data messages. ACK messages are
        # currently handled implicitly by the protocol layer, and will be
        # generated based on whether or not it is an ACKable message.
        frame_id = build_arbitration_id(
            msg_type=0,
            source_id=source,
            msg_id=msg_id
        )

        total_length = 0
        signals = []
        for index, field in enumerate(can_frame.fields):
            length = FIELDS_LEN[can_frame.ftype]

            if can_frame.msg_name in SIGNED_MESSAGES \
                    and not field == 'voltage':
                signal = cantools.database.can.Signal(
                    name=field,
                    start=index * length,
                    length=length,
                    is_signed=True
                )
            else:
                # battery voltage is unsigned
                signal = cantools.database.can.Signal(
                    name=field,
                    start=index * length,
                    length=length,
                    is_signed=False
                )
            signals.append(signal)
            total_length += length

        # Note: It is safe to divide by 8 since every single message under
        # the old protocol (aka. what I call CANdlelight 1.0) is
        # byte-aligned. To be precise, it uses byte-alignment padding to
        # fit 8, 4, 2, 1 bytes.
        message = cantools.database.can.Message(
            frame_id=frame_id,
            name=can_frame.msg_name,
            length=total_length // 8,
            signals=signals,
            # The sender is the Message Source
            senders=[can_frame.source]
        )
        database.messages.append(message)

        def get_ack(sender, msg_name, msg_id):
            """
            msg_id: the id of the message we are ACKing
            """
            sender_id = get_key_by_val(device_enum, sender)
            if sender_id is None:
                print("Couldn't find {}".format(sender))

            frame_id = build_arbitration_id(
                msg_type=1,
                source_id=sender_id,
                msg_id=msg_id
            )

            # All ACK responders send a message containing a ACK_STATUS in
            # CANdlelight 1.0
            signals = [
                cantools.database.can.Signal(
                    name='{}_FROM_{}_ACK_STATUS'.format(msg_name, sender),
                    start=0,
                    length=8
                )
            ]
            # This ACK message is always length 1, since it just fits the
            # ACK_STATUS
            message = cantools.database.can.Message(
                frame_id=frame_id,
                name='{}_ACK_FROM_{}'.format(msg_name, sender),
                length=1,
                signals=signals,
                # The sender is the Message Source
                senders=[sender]
            )
            return message

        # If this requires an ACK, then we go through all of the receivers.
        if msg_id in ACKABLE_MESSAGES:
            for acker in ACKABLE_MESSAGES[msg_id]:
                if acker != "":
                    message = get_ack(str(acker), can_frame.msg_name, msg_id)
                database.messages.append(message)

    # Save as a DBC file
    with open('system_can.dbc', 'w') as file_handle:
        file_handle.write(database.as_dbc_string())
Example #3
0
def main():
    """The main entry-point of the program"""
    # pylint: disable=R0914
    database = cantools.database.can.Database(version='')

    # Iterate through all the CAN device IDs and add them to the DBC.
    can_devices = data.parse_can_device_enum()
    for device_id, device_name in can_devices.items():
        if device_name not in ['RESERVED']:
            node = cantools.database.can.Node(name=device_name,
                                              comment='Device ID: {}'.format(
                                                  hex(device_id)))
            database.nodes.append(node)

    # Iterate through all the CAN messages IDs and:
    #
    #   1. Convert the Message ID to the CAN Arbitration ID.
    #   2. Add the Message to the DBC.
    #   3. For each of the signals, add it to the Message.
    #   4. If the Message is critical, add an ACK message to the DBC.
    can_messages = data.parse_can_frames('can_messages.asciipb')
    device_enum = data.parse_can_device_enum()

    def get_key_by_val(dictionary, val):
        """Helper function to get key for dictionary value"""
        for key, value in dictionary.items():
            if val == value:
                return key
        return None

    for msg_id, can_frame in can_messages.items():
        source = get_key_by_val(device_enum, can_frame.source)

        def get_muxed_voltage_signal():
            """
            Get the MUXed signals for the Voltage V/T message.
            """
            # The MUX'd message is formatted like this:
            #
            #  16-bits   16-bits     16-bits
            # +--------+---------+-------------+
            # |   id   | voltage | temperature |
            # +--------+---------+-------------+
            #
            # due to CANdlelight 1.0 alignment rules.
            results = []

            # Generate the signal used as the multiplexer. This is the `id`
            # field comprising of the first 2 bytes (even though only 7 bits
            # are necessary), since Voltage and Temperature are both 16-bit
            # values.
            multiplexer = cantools.database.can.Signal(name='BATTERY_VT_INDEX',
                                                       start=0,
                                                       length=16,
                                                       is_multiplexer=True)
            results.append(multiplexer)

            # Generate all the multiplexed signals for the Module Voltage and
            # Temperatures
            for i in range(NUM_BATTERY_MODULES):
                # The voltage is the second signal field
                voltage = cantools.database.can.Signal(
                    name='MODULE_VOLTAGE_{0:03d}'.format(i),
                    start=16,
                    length=16,
                    # The multiplexed ID is just the Cell Index
                    multiplexer_ids=[i],
                    # The multiplexer is the Module index
                    multiplexer_signal=results[0],
                    is_float=False,
                    decimal=None)

                # The Temperature is the third field
                temperature = cantools.database.can.Signal(
                    name='MODULE_TEMP_{0:03d}'.format(i),
                    start=32,
                    length=16,
                    byte_order='little_endian',
                    # The multiplexed ID is just the Cell Index
                    multiplexer_ids=[i],
                    # The multiplexer is the Module index
                    multiplexer_signal=results[0],
                    is_float=False,
                    decimal=None)

                results.append(voltage)
                results.append(temperature)

            return results

        # All these message types must be Data messages. ACK messages are
        # currently handled implicitly by the protocol layer, and will be
        # generated based on whether or not it is an ACKable message.
        frame_id = build_arbitration_id(msg_type=0,
                                        source_id=source,
                                        msg_id=msg_id)

        # We do a special case for BATTERY_VT since this is the only message
        # that we currently do that uses MUXed data.
        if can_frame.msg_name == 'BATTERY_VT':
            signals = get_muxed_voltage_signal()

            # It is safe to divide by 8 since every single message under the old
            # protocol (aka. what I call CANdlelight 1.0) is byte-aligned. To be
            # precise, it uses byte-alignment padding to fit 8, 4, 2, 1 bytes.
            message = cantools.database.can.Message(
                frame_id=frame_id,
                name=can_frame.msg_name,
                length=6,
                signals=signals,
                # The sender is the Message Source
                senders=[can_frame.source])
            database.messages.append(message)
        else:
            total_length = 0
            signals = []
            for index, field in enumerate(can_frame.fields):
                length = FIELDS_LEN[can_frame.ftype]

                # Unfortunately, our ASCIIPB doesn't denote whether a field is
                # signed/unsigned, and it is up to the caller to properly unpack
                # the CAN signal.
                #
                # The only Messages (and Signals) that are signed
                # (and currently used) are:
                #
                # - Drive Output:
                #   - throttle: int16_t
                #   - direction: int16_t
                #   - cruise_control: int16_t
                #   - mechanical_brake_state: int16_t
                # - Cruise Target:
                #   - target speed: int16_t
                # - Battery Aggregate V/C
                #   - voltage: uint16_t
                #   - current: int16_t
                # - Motor Velocity:
                #   - vehicle_velocity_left: int16_t
                #   - vehicle_velocity_right: int16_t
                if can_frame.msg_name in SIGNED_MESSAGES \
                    and not field == 'voltage':
                    signal = cantools.database.can.Signal(name=field,
                                                          start=index * length,
                                                          length=length,
                                                          is_signed=True)
                else:
                    # battery voltage is unsigned
                    signal = cantools.database.can.Signal(name=field,
                                                          start=index * length,
                                                          length=length,
                                                          is_signed=False)
                signals.append(signal)
                total_length += length

            # Note: It is safe to divide by 8 since every single message under
            # the old protocol (aka. what I call CANdlelight 1.0) is
            # byte-aligned. To be precise, it uses byte-alignment padding to
            # fit 8, 4, 2, 1 bytes.
            message = cantools.database.can.Message(
                frame_id=frame_id,
                name=can_frame.msg_name,
                length=total_length // 8,
                signals=signals,
                # The sender is the Message Source
                senders=[can_frame.source])
            database.messages.append(message)

        def get_ack(sender, msg_name, msg_id):
            """
            msg_id: the id of the message we are ACKing
            """
            sender_id = get_key_by_val(device_enum, sender)
            if sender_id is None:
                print("Couldn't find {}".format(sender))

            frame_id = build_arbitration_id(msg_type=1,
                                            source_id=sender_id,
                                            msg_id=msg_id)

            # All ACK responders send a message containing a ACK_STATUS in
            # CANdlelight 1.0
            signals = [
                cantools.database.can.Signal(
                    name='{}_FROM_{}_ACK_STATUS'.format(msg_name, sender),
                    start=0,
                    length=8)
            ]
            # This ACK message is always length 1, since it just fits the
            # ACK_STATUS
            message = cantools.database.can.Message(
                frame_id=frame_id,
                name='{}_ACK_FROM_{}'.format(msg_name, sender),
                length=1,
                signals=signals,
                # The sender is the Message Source
                senders=[sender])
            return message

        # If this requires an ACK, then we go through all of the receivers.
        # Unfortunately, our ASCIIPB file doesn't have a notion of Receivers,
        # so we hardcode this for now.
        if msg_id in ACKABLE_MESSAGES:
            for acker in ACKABLE_MESSAGES[msg_id]:
                message = get_ack(acker, can_frame.msg_name, msg_id)

                database.messages.append(message)

    # Save as a DBC file
    with open('system_can.dbc', 'w') as file_handle:
        file_handle.write(database.as_dbc_string())
    return