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)
.
mil()
, rsl()
, awl()
, pwl() -> int
Lamp Codes - 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 Lamprsl()
: Red Stop Lampawl()
: Amber Warning Lamppl()
: 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
, andoc
(andcm()
).
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.