J1939 Diagnostic Messages - dfieschko/RP1210 GitHub Wiki

This package includes some tools for parsing and generating J1939 diagnostic messages (e.g. DM1, DM2, DM12).

J1939.DiagnosticMessage Class

DiagnosticMessage stores lamp codes (2 bytes) and Diagnostic Trouble Codes (n*4 bytes), and can be used to either parse diagnostic messages you've received or to generate diagnostic messages to transmit.

Properties

Diagnostic Message properties are accessed directly. Modifying one property will automatically modify the others.

data

  • J1939 message data (type: bytes)
  • Doesn't include PGN, etc - only message data
  • First 2 bytes hold lamp codes
  • Subsequent n*4 bytes hold DTCs

lamps

  • Contains lamp codes (2 bytes) (type: bytes)
  • Byte 0 = MIL, RSL, AWL, and PL lamp codes (3 bits each)
  • Bytes 1 = proprietary

codes

  • Contains list of DTC objects (type: list[DTC])
  • Iterating over DiagnosticMessage will iterate over codes

Methods

to_dtcs(data : bytes) -> list[DTC]

Parses given J1939 message data (type:bytes) into a list of DTCs (diagnostic trouble codes) (type: list[DTC]). This is a static method, so you don't need to initialize a DiagnosticMessage class to use it.

  • param data (bytes): J1939 message data
  • returns list of DTCs parsed from message data (list[DTC])

DiagnosticMessage uses this method to generate its codes property. Therefore, the following two lines will result in equivalent values for dtc_list:

dtc_list = DiagnosticMessage.to_dtcs(j1939_msg_data)
dtc_list = DiagnosticMessage(j1939_msg_data).codes

num_dtcs() -> int

num_dtcs() returns the number of DTCs contained in the DiagnosticMessage. This is equivalent to len(DiagnosticMessage.codes).

Lamp Codes - mil(), rsl(), awl(), pwl() -> int

The lamp code functions return 3-bit lamp codes corresponding with the lamp they represent (as ints). These are stored in byte 0 of the class's lamps property. There are four Lamp Code functions, each of which has four possible states: Lamp OFF, Lamp ON, Lamp Flashing 1 Hz, and Lamp Flashing 2 Hz.

  • mil(): Malfunction Indicator Lamp
  • rsl(): Red Stop Lamp
  • awl(): Amber Warning Lamp
  • pl(): Protection Lamp

Magic Methods

__getitem__ and __setitem__

These allow a DiagnosticMessage object to be treated like a list of DTCs, which makes it convenient to process the meat of a diagnostic message. Accessing DiagnosticMessage as a list has the same effect as accessing the DiagnosticMessage.code property.

dm1_msg = DiagnosticMessage(j1939_data)
# the following two lines are equivalent to each other:
first_dtc = dm1_msg[0]
first_dtc = dm1_msg.codes[0]
# iterating through DTCs is as simple as:
for dtc in dm1_msg:
   process_dtc(dtc)

__iadd__

This allows you to use the += operator to directly add DTCs to a DiagnosticMessage's codes list.

dm1_msg += DTC(dtc_data)

__bytes__

Allows a DiagnosticMessage object to be converted to bytes (returns data property). This allows a DiagnosticMessage object to be used as the data parameter for J1939Message objects.

msg_data = bytes(dm1_msg) # is equivalent to:
msg_data = dm1_msg.data

j1939_msg = J1939Message(pgn=0xFECA, pri=6, sa=0xF9, data=dm1_msg)

__int__

int(DiagnosticMessage) will return an integer representation of the data property.

__str__

str(DiagnosticMessage) will return a str representation of the data property (mostly useful for print statements, etc).

__len__

len(DiagnosticMessage) will return the number of DTCs contained in the DiagnosticMessage object. It's like this to preserve consistency with __getitem__ and __setitem__.

__bool__

DiagnosticMessage objects will evaluate to True if they contain DTCs, or False if they don't.

__eq__

Directly compares data property with another DiagnosticMessage or bytes.

DiagnosticMessage Examples

Initializing DiagnosticMessage

msg_data = rp1210_client.rx()
# initialize from J1939Message
dm1_msg = DiagnosticMessage(J1939Message(msg_data))
# initialize directly from message data
dm1_msg = DiagnosticMessage(msg_data)
# create your own
dm1_msg = DiagnosticMessage()
... # add DTCs, modify lamp codes, etc

Reading Diagnostic Trouble Codes

# access dtcs through codes property:
dtc_list = dm1_msg.codes # where dm1_msg is instance of DiagnosticMessage
# iterate through DTCs directly:
for dtc in dm1_msg:
   process_dtc(dtc)

Reading Lamp Codes

# where dm1_msg is instance of DiagnosticMessage:
malfunction_indicator_code = dm1_msg.mil()
red_stop_lamp_code = dm1_msg.rsl()
amber_warning_lamp_code = dm1_msg.awl()
protection_lamp_code = dm1_msg.pl()

Generating Diagnostic Messages

dtc1 = DTC(spn=423, fmi=12, oc=80)
dtc2 = DTC(spn=100, fmi=1, oc=2)

dm1_msg = DiagnosticMessage() # init
dm1_msg += dtc1 # add DTCs
dm1_msg += dtc2

j1939_msg = J1939Message(pgn=0xFECA, pri=6, sa=0xF9, data=dm1_msg)
rp1210_client.tx(j1939_msg)

Minimal Working Example

# Setup for minimal working example
from RP1210 import RP1210Client
from RP1210.J1939 import J1939Message, DiagnosticMessage, DTC

VENDOR = "NULN2R32" # Nexiq USB-Link 2
DEVICE = 1          # this vendor/device combo will only work with USB-Link 2

# Set up client
client = RP1210Client()
client.setVendor(VENDOR)
client.setDevice(DEVICE)
client.connect()

while True:
   # Receive data
   data_bytes = client.rx()
   j1939_msg = J1939Message(data_bytes)

   # Process DM1s
   if j1939_msg.pgn == 0xFECA:
      # init DiagnosticMessage
      dm1_msg = DiagnosticMessage(j1939_msg)  # init with J1939Message
      dm1_msg = DiagnosticMessage(data_bytes) # OR init with data directly from ReadMessage
   
      # access properties
      dtc_list   = dm1_msg.codes # type: list[DTC] (n*4 bytes)
      lamp_codes = dm1_msg.lamps # type: bytes (2 bytes)
      dm1_data   = dm1_msg.data  # type: bytes (2 bytes + n*4 bytes)

      # iterating over DiagnosticMessage will iterate over its DTCs (see J1939.DTC)
      for dtc in dm1_msg:
         print(f"SPN: {dtc.spn} FMI: {dtc.fmi} OC: {dtc.oc}")

      # functions for parsing lamp codes
      if dm1_msg.mil(): # 3-bit code
         print("Malfunction Indicator Lamp on!")
      if dm1_msg.rsl(): # 3-bit code
         print("Red Stop Lamp on!")
      if dm1_msg.awl(): # 3-bit code
         print("Amber Warning Lamp on!")
      if dm1_msg.pl(): # 3-bit code
         print("Protection Lamp on!")

J1939.DTC Class

Diagnostic Trouble Codes (DTCs) reported in a DiagnosticMessage are each composed of four bytes and contain integer values for a fault's SPN (Suspect Parameter Number), FMI (Failure Mode Identifier), and Occurrence Count (OC). The class J9139.DTC can be used to parse or generate these codes.

Properties

DTC properties are accessed directly. Modifying one property will automatically modify the others.

data

  • DTC data, composed of 4 bytes (type: bytes)
  • Contains data for spn, fmi, and oc (and cm()).

spn

  • Suspect Parameter Number (type: int)
  • 19 bits, making up bytes 0, 1, and part of byte 2.

fmi

  • Failure Mode Identifier (type: int)
  • 5 bits, making up part of byte 2.

oc

  • Occurrence Count (type: int)
  • 8 bits, making up byte 3.

Methods

cm() -> int

  • Returns Conversion Method bit as int.
  • Will be 0 in most cases.

Magic Methods

__getitem__ and __setitem__

These allow a DTC object's data property to be accessed and modified directly by treating DTC as a list of bytes.

__iadd__

Allows oc (Occurrence Count) to be incremented by incrementing DTC object directly.

dtc = DTC(spn=10, fmi=10, oc=20)
dtc += 1 # OC will now be 21

__bytes__

Allows a DTC object to be converted to bytes (returns data property).

__int__

int(DTC) will return an integer representation of the data property.

__str__

str(DTC) will return a str representation of the data property (mostly useful for print statements, etc).

__len__

Returns len(DTC.data).

__eq__

Directly compares data property with another DTC or bytes.

__bool__

DTC will evaluate to True if it contains information, or False if it doesn't.