Search code examples
pythonpython-3.xcan-busj1939

How do I assign the J1939 standard to a message in a Cantools DBC file?


So i made a python script which can convert a DBC in an Excel and back using the library cantools. Turning the DBC-file into an Excel is no problem, but if i want to create a DBC out of the Excel-file and i assign the J1939 standard to a message, it won't show up in the DBC-file. Instead it only shows CAN extended.

I gave the message with the J1939 standard the attribute protocol="j1939" and save the DBC-file with dumpfile(db, "__.dbc"). Does anyone know if there is a special setting how i have to save the DBC-file, or if it is possible at all with cantools?

Here a short code example:

signal1 = cantools.db.can.Signal(name="signal1", start=1, length=8,
                                 is_signed=True, scale=1, offset=0, unit=None, spn=1586)

frame1 = cantools.db.can.Message(name="Msg1", frame_id=0x16fd3b09, protocol="j1939",
                                 is_extended_frame=True, length=8, signals=[signal1],
                                 cycle_time=50, senders=["A"])

db_cantools = cantools.db.can.Database(messages=[frame1])

cantools.db.dump_file(db_cantools, "test_db.dbc")

Picture of the DBC file in Vector CANdb (ID-Format is CAN Extended and not J1939).

For a different DBC file (which contains J1939 messages) i found with

db.dbc.attributes

a dict of attributes in my database:

OrderedDict([('ProtocolType', attribute('ProtocolType', J1939)),...)

I don't know if that would be the key, to create this attribute, but I also don't know how i do that. Also in the attributes of the messages i find j1939 but same problem (don't know how to create them).

Edit: I tried to create a J1939 template DBC file and added the messages there. It works, but messages which use the ID-Format CAN Standard or CAN Extended also turn into the ID-Format J1939.


Solution

  • The quality of the documentation is unfortunately quite poor.

    But looking into it, you will find that Database can take dbc_specifics as kwarg, which in turn should be of type DBCSpecifics.

    In order to set the Database Protocol to J1939, you should create a key ProtocolType in _attribute_definitions with value of type AttributeDefinition.

    I don't know if there is an easier way in the API to reach the same result (this one is extremely verbose), but the following will work:

    import cantools.database as candb
    
    dbc_spec = candb.can.formats.dbc_specifics.DbcSpecifics(
        attribute_definitions = {
            "ProtocolType": candb.can.attribute_definition.AttributeDefinition(
                                name = "ProtocolType",
                                default_value = 'J1939',
                                type_name = 'STRING'
                            )
        }
    )
    
    signal1 = candb.can.Signal(name="signal1", start=0, length=8,
                                     is_signed=True, unit=None, spn=1586)
    
    frame1 = candb.can.Message(name="Msg1", frame_id=0x16fd3b09, protocol="j1939",
                                     is_extended_frame=True, length=8, signals=[signal1],
                                     cycle_time=50, senders=["A"])
    
    db_cantools = candb.can.Database(messages=[frame1], dbc_specifics=dbc_spec)
    
    candb.dump_file(db_cantools, "test_db.dbc")
    

    Note that the current version of Signal doesn't seem to take scale and offset as kwarg but rather a conversion object.

    Edit: you can set the protocol of messages individually with the attribute VFrameFormat. This should be first added to the attribute definitions of your dbc file. Then as attribute for the desired messages:

    import cantools.database as candb
    
    vframe_format = candb.can.attribute_definition.AttributeDefinition(
                                name = "VFrameFormat",
                                default_value = 3, # 0: CAN Standard; 1: CAN Extended; 3: J1939
                                type_name = 'INT',
                                kind='BO_'
                            )
    
    dbc_spec = candb.can.formats.dbc_specifics.DbcSpecifics(
        attribute_definitions = {
            "ProtocolType": candb.can.attribute_definition.AttributeDefinition(
                                name = "ProtocolType",
                                default_value = 'J1939',
                                type_name = 'STRING'
                            ),
            'VFrameFormat': vframe_format
        }
    )
    
    signal1 = candb.can.Signal(name="signal1", start=0, length=8,
                                     is_signed=True, unit=None, spn=1586)
    
    
    msg_spec_ext = candb.can.formats.dbc_specifics.DbcSpecifics(
        attributes = {'VFrameFormat': candb.can.attribute.Attribute(
                                value = 1,  # 0: CAN Standard; 1: CAN Extended; 3: J1939
                                definition = vframe_format
                            )}
    )
    
    frame1 = candb.can.Message(name="Msg1", frame_id=0x16fd3b09, protocol="j1939",
                                     is_extended_frame=True, length=8, signals=[signal1],
                                     cycle_time=50, senders=["A"], dbc_specifics=msg_spec_ext)
    
    db_cantools = candb.can.Database(messages=[frame1], dbc_specifics=dbc_spec)
    
    candb.dump_file(db_cantools, "test_db.dbc")
    

    If you edit a DBC File with Vector CANdb++, you will see that VFrameFormat is an ENUM with the following values:

    • 0: StandardCAN
    • 1: ExtendedCAN
    • 3: J1939PG
    • 14: StandardCAN_FD
    • 15: ExtendedCAN_FD