J1939 Fundamentals: Addressing, PGN/SPN Structure, and Network Management
Document ID: J19-01 Series: Telematics Tutorial Series Target audience: Intermediate — you should understand Controller Area Network (CAN) extended (29-bit) frame format (CAN-02) and be familiar with heavy-duty vehicle systems.
Learning Objectives
By the end of this document, you will be able to:
- Decompose a 29-bit CAN identifier into its SAE J1939 fields: Priority, Extended Data Page, Data Page, Protocol Data Unit (PDU) Format, PDU Specific, and Source Address
- Distinguish between PDU1 (destination-specific) and PDU2 (broadcast) message formats
- Look up Parameter Group Numbers (PGN) and Suspect Parameter Numbers (SPN) to identify message content
- Describe the J1939 address claiming process and the 64-bit device NAME structure
- Parse a J1939 frame and extract SPN values using the documented resolution and offset
1. What Is J1939?
SAE International (SAE) J1939 is a set of standards defining how Electronic Control Units (ECU) in heavy-duty vehicles communicate over a CAN bus. It is the dominant protocol for trucks, buses, construction equipment, agricultural machinery, and marine engines. J1939 also serves as the foundation for several industry-specific adaptations: ISOBUS (ISO 11783) for agricultural equipment, National Marine Electronics Association (NMEA) 2000 for marine networks, and the Fleet Management System (FMS) standard for telematics gateways on commercial vehicles.
J1939 is built on top of the CAN physical and data-link layers defined in International Organization for Standardization (ISO) 11898. It uses extended (29-bit) CAN identifiers exclusively and operates at 250 kbit/s (classical) or 500 kbit/s (CAN FD). Understanding CAN fundamentals (CAN-01 through CAN-04) is a prerequisite for working with J1939.
J1939 builds on the CAN physical and data link layers (ISO 11898) and adds: - A standardized 29-bit CAN ID structure with priority, addressing, and parameter group identification - A comprehensive library of defined parameter groups (PGNs) and parameters (SPNs) - Transport protocols for messages exceeding 8 bytes - Network management including address claiming and NAME-based identification - Diagnostic messages (Diagnostic Message 1 (DM1) — active diagnostic trouble codes — through DM13 and beyond) - Extended Transport Protocol (ETP) for messages exceeding 1785 bytes (see J19-02)
1.1 J1939 Standard Family
| Standard | Scope |
|---|---|
| J1939-11 | Physical layer — 250 kbit/s, twisted shielded pair, 9-pin Deutsch connector |
| J1939-13 | Off-board diagnostic connector (Type 1 and Type 2) |
| J1939-14 | Physical layer — 500 kbit/s |
| J1939-21 | Data link layer — transport protocols, network management |
| J1939-22 | Data link layer for CAN FD |
| J1939-71 | Vehicle application layer — SPN and PGN definitions for on-highway vehicles |
| J1939-73 | Application layer — diagnostics (DM messages) |
| J1939-81 | Network management — address claiming, NAME |
J1939 is defined across multiple SAE documents: J1939-11 (physical layer, 250 kbit/s twisted pair), J1939-21 (data link layer, transport protocols), J1939-71 (vehicle application layer, PGN/SPN definitions), J1939-73 (diagnostics), J1939-81 (network management, address claiming).
📝 Note: J1939 uses the term "network management" (defined in J1939-81) rather than the abbreviation **NMT**, which belongs to the **CANopen** protocol's Network Management service. In J1939, network management is accomplished through the address claiming process described in Section 7.
1.2 Cross-References to the CAN Document Series
For CAN physical layer details (wiring, termination, transceivers), see CAN-01. For CAN frame format and arbitration, see CAN-02. For CAN bit timing and error handling, see CAN-03. For J1939 transport protocols (BAM, CMDT, ETP) and DM messages (DM1 through DM13), see J19-02. For J1939 CAN FD extensions and tooling, see J19-03.
2. The 29-Bit CAN Identifier in J1939
J1939 exclusively uses 29-bit extended CAN identifiers. The 29 bits are divided into six fields:
packet-beta 0-2: "Priority (3 bits)" 3: "EDP" 4: "DP" 5-12: "PF — PDU Format (8 bits)" 13-20: "PS — PDU Specific (8 bits)" 21-28: "SA — Source Address (8 bits)"
Figure: J1901 01 29bit id decomposition
Bit numbering in J1939 follows big-endian convention (bit 1 is the MSB of the first byte). Mermaid diagrams in this series use 0-indexed bit positions for consistency with programming languages.
| Field | Bits | Size | Description |
|---|---|---|---|
| Priority | 28–26 | 3 bits | Message priority (0 = highest, 7 = lowest). Default: 3 for control, 6 for information. Lower numeric values indicate higher priority. Priority 3 (0b011) is the default for most PGNs. Priority 1–2 is reserved for safety-critical messages (e.g., torque control). Priority 6–7 is used for low-priority status messages and file transfers. |
| EDP | 25 | 1 bit | Extended Data Page. Usually 0. When 1, indicates non-J1939 messages (ISO 15765 — the diagnostics transport protocol used by On-Board Diagnostics (OBD2) and Unified Diagnostic Services (UDS)) |
| DP | 24 | 1 bit | Data Page. Selects between two pages of PGN definitions (page 0 or page 1) |
| PF | 23–16 | 8 bits | PDU Format. Determines the PDU type (PDU1 vs PDU2) and contributes to the PGN |
| PS | 15–8 | 8 bits | PDU Specific. Meaning depends on PF value — either destination address or group extension |
| SA | 7–0 | 8 bits | Source Address (SA). The address of the transmitting node (0–253 available; 254 = Null; 255 = Global) |
2.1 Worked Example: Decoding a J1939 CAN ID
Given CAN ID: 0x18FEE900 (decimal: 419,358,976)
can_id = 0x18FEE900
priority = (can_id >> 26) & 0x07 # bits 28-26
edp = (can_id >> 25) & 0x01 # bit 25
dp = (can_id >> 24) & 0x01 # bit 24
pf = (can_id >> 16) & 0xFF # bits 23-16
ps = (can_id >> 8) & 0xFF # bits 15-8
sa = (can_id >> 0) & 0xFF # bits 7-0
print(f"CAN ID: 0x{can_id:08X}")
print(f"Priority: {priority}")
print(f"EDP: {edp}")
print(f"DP: {dp}")
print(f"PF: 0x{pf:02X} ({pf})")
print(f"PS: 0x{ps:02X} ({ps})")
print(f"SA: 0x{sa:02X} ({sa})")
# Calculate PGN
if pf < 240: # PDU1
pgn = (edp << 17) | (dp << 16) | (pf << 8)
print(f"PDU Format: PDU1 (destination-specific)")
print(f"Destination Address: 0x{ps:02X} ({ps})")
else: # PDU2
pgn = (edp << 17) | (dp << 16) | (pf << 8) | ps
print(f"PDU Format: PDU2 (broadcast)")
print(f"PGN: 0x{pgn:04X} ({pgn})")
# Output
CAN ID: 0x18FEE900
Priority: 6
EDP: 0
DP: 0
PF: 0xFE (254)
PS: 0xE9 (233)
SA: 0x00 (0)
PDU Format: PDU2 (broadcast)
PGN: 0xFEE9 (65257)
PGN 65257 (0xFEE9) is Fuel Consumption (Liquid) — a broadcast message from the engine ECU (SA = 0x00).
2.2 Worked Example: PDU1 CAN ID (Destination-Specific)
Given CAN ID: 0x18EAFF00 (decimal: 418,053,888)
can_id = 0x18EAFF00
priority = (can_id >> 26) & 0x07 # bits 28-26
edp = (can_id >> 25) & 0x01 # bit 25
dp = (can_id >> 24) & 0x01 # bit 24
pf = (can_id >> 16) & 0xFF # bits 23-16
ps = (can_id >> 8) & 0xFF # bits 15-8
sa = (can_id >> 0) & 0xFF # bits 7-0
print(f"CAN ID: 0x{can_id:08X}")
print(f"Priority: {priority}")
print(f"EDP: {edp}")
print(f"DP: {dp}")
print(f"PF: 0x{pf:02X} ({pf})")
print(f"PS: 0x{ps:02X} ({ps})")
print(f"SA: 0x{sa:02X} ({sa})")
# PF = 0xEA (234) < 240 → PDU1
pgn = (edp << 17) | (dp << 16) | (pf << 8)
print(f"PDU Format: PDU1 (destination-specific)")
print(f"Destination Address: 0x{ps:02X} ({ps})")
print(f"PGN: 0x{pgn:04X} ({pgn})")
# Output
CAN ID: 0x18EAFF00
Priority: 6
EDP: 0
DP: 0
PF: 0xEA (234)
PS: 0xFF (255)
SA: 0x00 (0)
PDU Format: PDU1 (destination-specific)
Destination Address: 0xFF (255)
PGN: 0xEA00 (59904)
PGN 59904 (0xEA00) is the Request PGN — the engine ECU (SA = 0x00) is broadcasting a request (DA = 0xFF, global destination) asking all nodes to transmit a specific PGN.
3. PDU1 vs PDU2
The value of the PF (PDU Format) field determines how the PS (PDU Specific) field is interpreted:
3.1 PDU1: Destination-Specific (PF = 0–239)
When PF < 240 (0xF0): - The PS field contains the Destination Address (DA) — the SA of the intended recipient - The PGN is calculated from EDP, DP, and PF only (PS is NOT part of the PGN) - The message is intended for a specific node (point-to-point)
PGN = (EDP << 17) | (DP << 16) | (PF << 8)
Example: PF = 0xEA (234), PS = 0xFF (broadcast destination) - PGN = 0xEA00 (59904) = Request PGN — a node requests another node to transmit a specific PGN - PS = 0xFF means the request is broadcast to all nodes
3.2 PDU2: Broadcast (PF = 240–255)
When PF ≥ 240 (0xF0): - The PS field is a Group Extension (GE) — it extends the PGN address space - PS is included in the PGN calculation - The message is broadcast to all nodes on the network
PGN = (EDP << 17) | (DP << 16) | (PF << 8) | PS
Example: PF = 0xFE (254), PS = 0xE9 (233) - PGN = 0xFEE9 (65257) = Fuel Consumption (Liquid) - Broadcast by the engine ECU to all nodes
3.3 PDU1 vs PDU2 Comparison
| Feature | PDU1 (PF < 240) | PDU2 (PF ≥ 240) |
|---|---|---|
| PS field meaning | Destination address | Group extension (part of PGN) |
| PGN includes PS? | No | Yes |
| Communication type | Point-to-point or broadcast (PS=0xFF) | Always broadcast |
| PGN range | 0x0000–0xEF00 | 0xF000–0x1FFFF |
| Typical use | Requests, acknowledgments, targeted control | Periodic broadcast data |
block-beta
columns 6
block:header:6
columns 6
h1["Priority\n3 bits"] h2["EDP\n1 bit"] h3["DP\n1 bit"] h4["PF\n8 bits"] h5["PS\n8 bits"] h6["SA\n8 bits"]
end
space:6
block:pdu1:6
columns 1
p1title["PDU1: PF < 240 (0xF0)"]
p1ps["PS = Destination Address (DA)"]
p1pgn["PGN = (EDP<<17) | (DP<<16) | (PF<<8)"]
p1note["PS is NOT part of PGN"]
p1ex["Example: PF=0xEA, PS=0xFF → PGN 59904 (Request)"]
end
space:6
block:pdu2:6
columns 1
p2title["PDU2: PF ≥ 240 (0xF0)"]
p2ps["PS = Group Extension (GE)"]
p2pgn["PGN = (EDP<<17) | (DP<<16) | (PF<<8) | PS"]
p2note["PS IS part of PGN"]
p2ex["Example: PF=0xFE, PS=0xE9 → PGN 65257 (Fuel Consumption)"]
end
Figure: J1901 02 pdu1 vs pdu2
📝 Note: Even PDU1 messages can be broadcast by setting PS = 0xFF (global destination address). The distinction between PDU1 and PDU2 is about how the PS field is interpreted, not strictly about unicast vs broadcast.
4. PGN and SPN Definitions
4.1 Parameter Group Numbers (PGN)
A PGN identifies a group of related parameters transmitted in a single J1939 message. Each PGN has a defined data length (usually 8 bytes), transmission rate, and set of SPNs.
Common PGNs:
| PGN | Hex | Name | Abbreviation | Data length | Default rate |
|---|---|---|---|---|---|
| 61444 | 0xF004 | Electronic Engine Controller 1 | EEC1 | 8 bytes | 10 ms |
| 65262 | 0xFEEE | Engine Temperature 1 | ET1 | 8 bytes | 1000 ms |
| 65263 | 0xFEEF | Engine Fluid Level/Pressure 1 | EFL/P1 | 8 bytes | 500 ms |
| 65257 | 0xFEE9 | Fuel Consumption (Liquid) | LFC | 8 bytes | 1000 ms |
| 65265 | 0xFEF1 | Cruise Control/Vehicle Speed | CCVS | 8 bytes | 100 ms |
| 65266 | 0xFEF2 | Fuel Economy (Liquid) | LFE | 8 bytes | 100 ms |
| 65269 | 0xFEF5 | Ambient Conditions | AMB | 8 bytes | 1000 ms |
| 65270 | 0xFEF6 | Inlet/Exhaust Conditions 1 | IC1 | 8 bytes | 500 ms |
| 61443 | 0xF003 | Electronic Engine Controller 2 | EEC2 | 8 bytes | 50 ms |
| 65279 | 0xFEFF | Water in Fuel Indicator | WFI | 8 bytes | 1000 ms |
This table covers the most commonly encountered PGNs. The complete J1939 SPN/PGN database contains over 8,000 defined parameters. The full database is available from SAE as a digital annex to J1939-71 (vehicle application layer).
4.2 Suspect Parameter Numbers (SPN)
An SPN identifies a single data parameter within a PGN. Each SPN has a defined position (start byte and bit), length, resolution (scale factor), offset, and unit.
📝 Note: J1939 uses **1-based byte numbering** — the first byte of the data field is byte 1, not byte 0. The notation "byte.bit" (e.g., "1.0") means byte 1, bit 0 (the least-significant bit of that byte). When you write code in Python or C, remember that array indices are 0-based, so J1939 byte 1 maps to `data[0]`, byte 4 maps to `data[3]`, and so on.
Common SPNs in PGN 61444 (EEC1):
| SPN | Name | Start byte.bit | Length | Resolution | Offset | Unit | Range |
|---|---|---|---|---|---|---|---|
| 899 | Engine Torque Mode | 1.0 | 4 bits | 1 | 0 | — | 0–15 (enumerated) |
| 512 | Driver’s Demand Engine Torque | 2 | 8 bits | 1 | −125 | % | −125 to 125 |
| 513 | Actual Engine Torque | 3 | 8 bits | 1 | −125 | % | −125 to 125 |
| 190 | Engine Speed | 4–5 | 16 bits | 0.125 | 0 | RPM | 0–8031.875 |
| 1483 | Source Address of Controlling Device | 6 | 8 bits | 1 | 0 | — | 0–255 |
| 1675 | Engine Starter Mode | 7.0 | 4 bits | 1 | 0 | — | 0–15 (enumerated) |
4.3 Decoding SPN Values
def decode_j1939_eec1(data):
"""Decode PGN 61444 (EEC1) — Electronic Engine Controller 1."""
if len(data) < 8:
raise ValueError("EEC1 requires 8 bytes")
# SPN 899: Engine Torque Mode (byte 1, bits 3-0)
torque_mode = data[0] & 0x0F
# SPN 512: Driver's Demand Engine Torque (byte 2)
driver_torque = data[1] - 125 # offset -125
# SPN 513: Actual Engine Torque (byte 3)
actual_torque = data[2] - 125 # offset -125
# SPN 190: Engine Speed (bytes 4-5, little-endian)
engine_speed_raw = data[3] | (data[4] << 8)
engine_speed = engine_speed_raw * 0.125 # resolution 0.125 RPM
# SPN 1483: Source Address of Controlling Device (byte 6)
controlling_sa = data[5]
return {
'EngTorqueMode': torque_mode,
'DriverDemandTorque': driver_torque,
'ActualTorque': actual_torque,
'EngineSpeed': engine_speed,
'ControllingSA': controlling_sa
}
# Example: EEC1 data from a diesel truck
data = bytes([0x03, 0xC8, 0xC3, 0x20, 0x1C, 0x00, 0xFF, 0xFF])
result = decode_j1939_eec1(data)
for name, value in result.items():
print(f"{name}: {value}")
# Output
EngTorqueMode: 3
DriverDemandTorque: 75
ActualTorque: 70
EngineSpeed: 900.0
ControllingSA: 0
4.4 Additional PGN Decode Examples
CCVS — Cruise Control/Vehicle Speed (PGN 65265, 0x00FEF1):
def decode_ccvs(data: bytes) -> dict:
"""Decode CCVS (PGN 65265) — Vehicle Speed and Cruise Control.
Key SPNs:
SPN 84 — Wheel-Based Vehicle Speed: bytes 2-3, 1/256 km/h per bit
SPN 595 — Cruise Control Active: byte 1, bits 1-0
SPN 596 — Cruise Control Enable Switch: byte 1, bits 3-2
"""
result = {}
# SPN 84: Wheel-Based Vehicle Speed (bytes 2-3, little-endian)
raw_speed = data[1] | (data[2] << 8)
if raw_speed < 0xFAFF:
result['vehicle_speed_kmh'] = raw_speed / 256.0
else:
result['vehicle_speed_kmh'] = None # Not available
# SPN 595: Cruise Control Active (byte 1, bits 1:0)
cc_active = data[0] & 0x03
result['cruise_active'] = {0: 'Off', 1: 'On', 2: 'Error', 3: 'N/A'}.get(cc_active)
# SPN 596: Cruise Enable Switch (byte 1, bits 3:2)
cc_enable = (data[0] >> 2) & 0x03
result['cruise_enabled'] = {0: 'Off', 1: 'On', 2: 'Error', 3: 'N/A'}.get(cc_enable)
return result
# Example: Vehicle at 85.5 km/h, cruise control on
ccvs_data = bytes([0x05, 0x80, 0x55, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF])
print("CCVS:", decode_ccvs(ccvs_data))
Expected output:
CCVS: {'vehicle_speed_kmh': 85.5, 'cruise_active': 'On', 'cruise_enabled': 'On'}
ET1 — Engine Temperature 1 (PGN 65262, 0x00FEEE):
def decode_et1(data: bytes) -> dict:
"""Decode ET1 (PGN 65262) — Engine Temperatures.
Key SPNs:
SPN 110 — Engine Coolant Temperature: byte 1, 1°C/bit, offset -40
SPN 175 — Engine Oil Temperature 1: bytes 3-4, 0.03125°C/bit, offset -273
SPN 174 — Fuel Temperature 1: byte 2, 1°C/bit, offset -40
"""
result = {}
# SPN 110: Coolant Temperature (byte 1)
if data[0] != 0xFF:
result['coolant_temp_c'] = data[0] - 40
else:
result['coolant_temp_c'] = None
# SPN 174: Fuel Temperature (byte 2)
if data[1] != 0xFF:
result['fuel_temp_c'] = data[1] - 40
else:
result['fuel_temp_c'] = None
# SPN 175: Oil Temperature (bytes 3-4, little-endian)
raw_oil = data[2] | (data[3] << 8)
if raw_oil < 0xFAFF:
result['oil_temp_c'] = raw_oil * 0.03125 - 273.0
else:
result['oil_temp_c'] = None
return result
# Example: Coolant 85°C, fuel 45°C, oil 95°C
et1_data = bytes([0x7D, 0x55, 0x00, 0x2E, 0xFF, 0xFF, 0xFF, 0xFF])
print("ET1:", decode_et1(et1_data))
Expected output:
ET1: {'coolant_temp_c': 85, 'fuel_temp_c': 45, 'oil_temp_c': 95.0}
LFE — Fuel Economy (PGN 65266, 0x00FEF2):
def decode_lfe(data: bytes) -> dict:
"""Decode LFE (PGN 65266) — Fuel Economy.
Key SPNs:
SPN 183 — Fuel Rate: bytes 1-2, 0.05 L/h per bit
SPN 184 — Instantaneous Fuel Economy: bytes 3-4, 1/512 km/L per bit
"""
result = {}
# SPN 183: Fuel Rate (bytes 1-2, little-endian)
raw_rate = data[0] | (data[1] << 8)
if raw_rate < 0xFAFF:
result['fuel_rate_lph'] = raw_rate * 0.05
else:
result['fuel_rate_lph'] = None
# SPN 184: Instantaneous Fuel Economy (bytes 3-4)
raw_econ = data[2] | (data[3] << 8)
if raw_econ < 0xFAFF:
result['fuel_economy_kml'] = raw_econ / 512.0
else:
result['fuel_economy_kml'] = None
return result
# Example: 12.5 L/h fuel rate, 8.5 km/L economy
lfe_data = bytes([0xFA, 0x00, 0x00, 0x11, 0xFF, 0xFF, 0xFF, 0xFF])
print("LFE:", decode_lfe(lfe_data))
Expected output:
LFE: {'fuel_rate_lph': 12.5, 'fuel_economy_kml': 8.5}
AMB — Ambient Conditions (PGN 65269, 0x00FEF5):
def decode_amb(data: bytes) -> dict:
"""Decode AMB (PGN 65269) — Ambient Conditions.
Key SPNs:
SPN 171 — Ambient Air Temperature: bytes 4-5, 0.03125°C/bit, offset -273
SPN 108 — Barometric Pressure: byte 1, 0.5 kPa per bit
"""
result = {}
# SPN 108: Barometric Pressure (byte 1)
if data[0] != 0xFF:
result['baro_pressure_kpa'] = data[0] * 0.5
else:
result['baro_pressure_kpa'] = None
# SPN 171: Ambient Air Temperature (bytes 4-5, little-endian)
raw_temp = data[3] | (data[4] << 8)
if raw_temp < 0xFAFF:
result['ambient_temp_c'] = raw_temp * 0.03125 - 273.0
else:
result['ambient_temp_c'] = None
return result
# Example: Barometric pressure 101.5 kPa, ambient temp 25°C
# 101.5 / 0.5 = 203 = 0xCB; (25 + 273) / 0.03125 = 9536 = 0x2540
amb_data = bytes([0xCB, 0xFF, 0xFF, 0x40, 0x25, 0xFF, 0xFF, 0xFF])
print("AMB:", decode_amb(amb_data))
Expected output:
AMB: {'baro_pressure_kpa': 101.5, 'ambient_temp_c': 25.0}
4.5 Sentinel Values — ‘Not Available’ and ‘Error’ Indicators
J1939 reserves specific bit patterns to indicate that a parameter is not available, in error, or not yet initialized. Failing to check for these values is a common bug that produces wildly incorrect readings.
| Data Width | Not Available | Error | Not Yet Available |
|---|---|---|---|
| 1 bit | N/A | N/A | N/A |
| 2 bits | 0b11 |
0b10 |
N/A |
| 4 bits (nibble) | 0xF |
0xE |
N/A |
| 8 bits (1 byte) | 0xFF |
0xFE |
N/A |
| 16 bits (2 bytes) | 0xFFFF |
0xFEFF |
0xFB00–0xFEFF |
| 32 bits (4 bytes) | 0xFFFFFFFF |
0xFEFFFFFF |
N/A |
def is_available(raw_value: int, bit_width: int) -> bool:
"""Check if a J1939 parameter value is valid (not a sentinel).
Args:
raw_value: The raw integer value from the CAN frame
bit_width: Number of bits for this SPN (8, 16, or 32)
Returns:
True if the value represents real data, False if it's a sentinel
"""
sentinels = {
8: range(0xFE, 0x100), # 0xFE (error) and 0xFF (not available)
16: range(0xFB00, 0x10000), # 0xFB00+ are reserved
32: range(0xFAFFFFFF, 0x100000000),
}
check_range = sentinels.get(bit_width)
if check_range is None:
return True # No sentinel defined for this width
return raw_value not in check_range
# Example usage in a decode function
def safe_decode_spn(raw: int, width: int, factor: float, offset: float) -> float | None:
"""Decode an SPN value with sentinel checking."""
if not is_available(raw, width):
return None
return raw * factor + offset
# Demonstrate — 8-bit, 16-bit, and 32-bit sentinel checks
print(safe_decode_spn(0x7D, 8, 1.0, -40)) # 85°C — valid
print(safe_decode_spn(0xFF, 8, 1.0, -40)) # None — not available (8-bit)
print(safe_decode_spn(0xFE, 8, 1.0, -40)) # None — error indicator (8-bit)
print(safe_decode_spn(21845, 16, 1/256, 0)) # 85.33 km/h — valid
print(safe_decode_spn(0xFFFF, 16, 1/256, 0)) # None — not available (16-bit)
print(safe_decode_spn(123456789, 32, 0.001, 0)) # 123456.789 — valid
print(safe_decode_spn(0xFFFFFFFF, 32, 0.001, 0)) # None — not available (32-bit)
Expected output:
85.0
None
None
85.33203125
None
123456.789
None
5. J1939 Source Addresses
Every J1939 device has a unique Source Address (SA). Source addresses 0–253 are available for assignment. Address 254 (0xFE) is the Null address (used during address claiming before a node has claimed), and address 255 (0xFF) is the Global address (broadcast destination for all nodes).
Summary of reserved addresses: - 254 (0xFE) = Null address (device has not yet claimed an address) - 255 (0xFF) = Global address (broadcast destination for PDU1 messages)
5.1 Preferred Addresses
SAE J1939-81 defines preferred addresses for common device types:
| SA | Device |
|---|---|
| 0 | Engine #1 |
| 1 | Engine #2 |
| 3 | Transmission #1 |
| 5 | Shift Console — Primary |
| 11 | Brakes — System Controller |
| 15 | Retarder — Engine |
| 17 | Cruise Control |
| 21 | Cab Climate Control |
| 25 | Alternator/Charging System |
| 33 | Vehicle Navigation |
| 37 | Instrument Cluster #1 |
| 41 | Vehicle Security |
| 49 | Body Controller |
| 249 | Off-board Diagnostic Tool #1 |
| 250 | Off-board Diagnostic Tool #2 |
6. The 64-Bit Device NAME
Every J1939 device has a unique 64-bit NAME that identifies it on the network. The NAME is used during address claiming to resolve address conflicts.
6.1 NAME Fields
packet-beta 0-0: "Arb Addr Cap (1)" 1-3: "Industry Group (3)" 4-7: "Veh Sys Instance (4)" 8-14: "Vehicle System (7)" 15: "Rsvd" 16-23: "Function (8)" 24-28: "Function Instance (5)" 29-31: "ECU Instance (3)" 32-42: "Manufacturer Code (11)" 43-63: "Identity Number (21)"
Figure: J1901 03 name field structure
| Field | Bits | Size | Description |
|---|---|---|---|
| Arbitrary Address Capable | 63 | 1 bit | 1 = device can use any address; 0 = must use preferred address |
| Industry Group | 62–60 | 3 bits | 0=Global, 1=On-Highway, 2=Agricultural, 3=Construction, 4=Marine, 5=Industrial |
| Vehicle System Instance | 59–56 | 4 bits | Instance of the vehicle system (usually 0) |
| Vehicle System | 55–49 | 7 bits | Type of vehicle system |
| Reserved | 48 | 1 bit | Always 0 |
| Function | 47–40 | 8 bits | The device function (e.g., 0=Engine, 3=Transmission) |
| Function Instance | 39–35 | 5 bits | Instance of the function (e.g., Engine #1 = 0, Engine #2 = 1) |
| ECU Instance | 34–32 | 3 bits | Instance of the ECU implementing the function |
| Manufacturer Code | 31–21 | 11 bits | SAE-assigned manufacturer code (e.g., Cummins, Caterpillar) |
| Identity Number | 20–0 | 21 bits | Unique serial number assigned by the manufacturer |
6.2 Worked Example: Constructing a 64-Bit NAME
def build_j1939_name(
arbitrary_address_capable: int, # 1 bit (63)
industry_group: int, # 3 bits (62-60)
vehicle_system_instance: int, # 4 bits (59-56)
vehicle_system: int, # 7 bits (55-49)
reserved: int, # 1 bit (48) — always 0
function: int, # 8 bits (47-40)
function_instance: int, # 5 bits (39-35)
ecu_instance: int, # 3 bits (34-32)
manufacturer_code: int, # 11 bits (31-21)
identity_number: int # 21 bits (20-0)
) -> int:
"""Build a 64-bit J1939 NAME from its component fields."""
name = (arbitrary_address_capable & 0x01) << 63
name |= (industry_group & 0x07) << 60
name |= (vehicle_system_instance & 0x0F) << 56
name |= (vehicle_system & 0x7F) << 49
name |= (reserved & 0x01) << 48
name |= (function & 0xFF) << 40
name |= (function_instance & 0x1F) << 35
name |= (ecu_instance & 0x07) << 32
name |= (manufacturer_code & 0x7FF) << 21
name |= (identity_number & 0x1FFFFF)
return name
# Example: Cummins engine ECU (Engine #1)
engine_name = build_j1939_name(
arbitrary_address_capable=0, # Must use preferred address (Engine #1 = SA 0x00)
industry_group=1, # On-Highway
vehicle_system_instance=0,
vehicle_system=0,
reserved=0,
function=0, # Engine function
function_instance=0, # Engine #1
ecu_instance=0,
manufacturer_code=141, # Cummins (SAE-assigned)
identity_number=12345 # Unique serial
)
print(f"NAME: 0x{engine_name:016X}")
print(f" Bits 63: Arbitrary address capable = 0")
print(f" Bits 62-60: Industry group = 1 (On-Highway)")
print(f" Bits 47-40: Function = 0 (Engine)")
print(f" Bits 31-21: Manufacturer code = 141 (Cummins)")
print(f" Bits 20-0: Identity number = 12345")
Expected output:
NAME: 0x1000000011A03039
Bits 63: Arbitrary address capable = 0
Bits 62-60: Industry group = 1 (On-Highway)
Bits 47-40: Function = 0 (Engine)
Bits 31-21: Manufacturer code = 141 (Cummins)
Bits 20-0: Identity number = 12345
6.3 NAME Uniqueness
The NAME must be globally unique. The combination of Manufacturer Code (SAE-assigned) and Identity Number (manufacturer-assigned serial) guarantees uniqueness across all J1939 devices worldwide — similar to how a Media Access Control (MAC) address uniquely identifies an Ethernet device.
7. Address Claiming
Address claiming is the process by which J1939 devices negotiate their source addresses at startup. It prevents address conflicts when multiple devices claim the same address.
7.1 The Address Claim Process
-
At power-on, each device sends an Address Claimed message (PGN 60928, 0xEE00) containing its 64-bit NAME and its claimed address.
-
If two devices claim the same address, the device with the numerically lower NAME wins. The losing device must either:
- Claim a different address (if Arbitrary Address Capable = 1)
-
Give up and go to the null address 0xFE (if it can only use its preferred address)
-
A device can also send a Request for Address Claimed (PGN 59904 requesting PGN 60928) to discover what addresses are in use before claiming.
7.2 Address Claim Sequence
1. Device A powers on, claims address 0x00 (Engine #1)
→ Sends PGN 60928 with SA=0x00, data=NAME_A
2. Device B powers on, also claims address 0x00
→ Sends PGN 60928 with SA=0x00, data=NAME_B
3. Conflict detected! Compare NAMEs:
If NAME_A < NAME_B → Device A keeps address 0x00
Device B must reclaim (different address or null)
If NAME_B < NAME_A → Device B keeps address 0x00
Device A must reclaim
4. Losing device (with higher NAME):
If Arbitrary Address Capable = 1:
→ Picks a different address and sends new Address Claimed
If Arbitrary Address Capable = 0:
→ Sends a **Cannot Claim Address** message (PGN 60928 with SA=0xFE)
sequenceDiagram
participant A as Device A
(Engine ECU)
participant Bus as CAN Bus
participant B as Device B
(Aftermarket)
Note over A: Preferred SA=0x00
NAME_A (lower value)
Note over B: Preferred SA=0x00
NAME_B (higher value)
Arb Addr Capable=1
A->>Bus: Address Claimed (PGN 60928)
SA=0x00, data=NAME_A
Note over Bus: Device A claims 0x00
B->>Bus: Address Claimed (PGN 60928)
SA=0x00, data=NAME_B
Note over Bus: Conflict! Two devices at SA 0x00
rect rgb(255, 255, 200)
Note over A,B: NAME comparison: NAME_A < NAME_B
Device A wins (lower NAME = higher priority)
end
A->>Bus: Address Claimed (PGN 60928)
SA=0x00, data=NAME_A
Note over A: Device A keeps 0x00
alt Arbitrary Address Capable = 1
B->>Bus: Address Claimed (PGN 60928)
SA=0x81, data=NAME_B
Note over B: Reclaims at new address 0x81
else Arbitrary Address Capable = 0
B-->>Bus: Cannot Claim Address (PGN 60928)
SA=0xFE
Note over B: Null address — cannot participate
end
Figure: J1901 04 address claiming
💡 Tip: Lower NAME values have higher priority. Manufacturers can control priority by choosing lower Identity Numbers for critical devices. The Arbitrary Address Capable bit should be set to 1 for all non-critical devices to allow graceful address conflict resolution.
7.3 Address Claiming — Complete Python Example
# pip install python-j1939 python-can
import j1939
import time
class AddressClaimHandler(j1939.ControllerApplication):
"""J1939 address claiming example.
Demonstrates:
- Constructing a J1939 NAME
- Claiming an address
- Handling contention (another device has the same address)
- Detecting conflicts
"""
def __init__(self, name, desired_address=0x80):
super().__init__(name, desired_address)
self._claimed = False
def start(self):
"""Begin address claim procedure."""
self.claim_address()
print(f"Requesting address 0x{self._desired_address:02X}...")
def on_address_claimed(self, address):
"""Called when we successfully claimed an address."""
self._claimed = True
print(f"Successfully claimed address 0x{address:02X}")
def on_address_lost(self, old_address):
"""Called when another node with higher priority NAME claimed our address."""
self._claimed = False
print(f"Lost address 0x{old_address:02X} to higher-priority node!")
print("Attempting to claim a new address...")
# Construct a J1939 NAME (64-bit identifier)
# Each field has specific meaning per J1939-81
name = j1939.Name(
arbitrary_address_capable=1, # Bit 63: Can use arbitrary address
industry_group=0, # Bits 62-60: Global (0)
vehicle_system_instance=0, # Bits 59-56
vehicle_system=0, # Bits 55-49
function=0xFF, # Bits 48-40: Non-specific function
function_instance=0, # Bits 39-35
ecu_instance=0, # Bits 34-32
manufacturer_code=0x0000, # Bits 31-21: Not assigned
identity_number=0x000001 # Bits 20-0: Unique serial number
)
print(f"NAME: 0x{name.value:016X}")
print(f" Arbitrary address capable: {name.arbitrary_address_capable}")
print(f" Industry group: {name.industry_group}")
print(f" Manufacturer code: {name.manufacturer_code}")
print(f" Identity number: {name.identity_number}")
# Create the J1939 bus
ecu = j1939.ElectronicControlUnit()
ecu.connect(bustype='socketcan', channel='can0')
# Create and start the application
app = AddressClaimHandler(name, desired_address=0x80)
ecu.add_ca(controller_application=app)
app.start()
# Wait for claim to complete (typically < 250 ms)
time.sleep(1.0)
if app._claimed:
print(f"\nAddress claim successful. Listening for conflicts...")
try:
while True:
time.sleep(1)
except KeyboardInterrupt:
pass
else:
print("Address claim failed — another node has higher priority.")
ecu.disconnect()
Expected output:
NAME: 0x80FF000000000001
Arbitrary address capable: 1
Industry group: 0
Manufacturer code: 0
Identity number: 1
Requesting address 0x80...
Successfully claimed address 0x80
Address claim successful. Listening for conflicts...
Troubleshooting
| # | Symptom | Likely cause | Diagnostic step | Resolution |
|---|---|---|---|---|
| 1 | No J1939 messages seen on the bus | Wrong bit rate (J1939 uses 250 kbit/s by default) or wrong CAN ID filter | Configure interface for 250 kbit/s, 29-bit extended IDs; use candump -e can0 |
Set bit rate to 250000; ensure extended frame support is enabled |
| 2 | PGN decode produces wrong values | Byte order error (J1939 uses little-endian for multi-byte SPNs) | Compare raw bytes against the SPN definition in J1939-71 | Verify byte order; J1939 SPNs are little-endian unless specified otherwise |
| 3 | Two devices claiming the same address | Address conflict — both devices have the same preferred address | Capture Address Claimed messages (PGN 60928); compare NAMEs | Configure one device with a different preferred address or enable Arbitrary Address Capable |
| 4 | Device disappears from the bus after startup | Lost address claim — another device with a lower NAME won the conflict | Check for Cannot Claim Address messages (SA=0xFE) | Assign a different preferred address to the losing device |
| 5 | Transport protocol messages incomplete | Broadcast Announce Message (BAM) or Connection Mode Data Transfer (CMDT) transfer timed out or was interrupted | Monitor TP.CM (PGN 60416) and TP.DT (PGN 60160) frames | See J19-02 for transport protocol troubleshooting |
| 6 | DM1 message shows active Diagnostic Trouble Codes (DTCs) that don’t clear | Active faults — the underlying condition is still present | Read DM1 SPN / Failure Mode Identifier (FMI) codes and check the corresponding sensor/system | Repair the fault; DM1 DTCs clear automatically when the fault condition is no longer detected |
| 7 | Unknown PGN in captured traffic | Manufacturer-proprietary PGN not defined in J1939-71 | Check if the PGN falls in the proprietary range (0xFF00–0xFFFF) | Consult the manufacturer’s documentation for proprietary PGN definitions |
Common Failure Mode Identifier (FMI) Codes
When reading DM1 Diagnostic Trouble Codes (DTCs), each DTC includes an SPN and an FMI that describes the type of failure. The five most frequently encountered FMI values are:
| FMI | Failure Mode | Typical Meaning |
|---|---|---|
| 0 | Data valid but above normal operational range — most severe | Sensor reading too high (e.g., coolant temperature above critical threshold) |
| 1 | Data valid but below normal operational range — most severe | Sensor reading too low (e.g., oil pressure below critical threshold) |
| 2 | Data erratic, intermittent, or incorrect | Signal fluctuating or inconsistent (e.g., noisy wiring, poor connector) |
| 3 | Voltage above normal, or shorted to high source | Open circuit or short to battery on a sensor input |
| 4 | Voltage below normal, or shorted to low source | Short to ground on a sensor input |
For complete DTC decode procedures and the full FMI table (FMI 0–31), see J19-02.
References
-
SAE J1939-21:2018 — Data Link Layer. Defines the 29-bit CAN ID structure, PDU format, and transport protocols.
-
SAE J1939-71:2020 — Vehicle Application Layer. Defines PGNs and SPNs for on-highway heavy-duty vehicles.
-
SAE J1939-81:2017 — Network Management. Defines address claiming, the 64-bit NAME, and preferred addresses.
-
SAE J1939-11:2022 — Physical Layer — 250 kbit/s, Twisted Shielded Pair. Physical layer specification.
-
SAE J1939-73:2019 — Application Layer — Diagnostics. Defines DM messages (DM1–DM13+).
-
isobus.net — ISOBUS (ISO 11783) community reference. ISOBUS extends J1939 for agricultural equipment, adding implement control and task management layers.
-
Kvaser J1939 Introduction — Practical guide to J1939 protocol fundamentals.
-
Open-SAE-J1939 — Open-source J1939 stack for embedded systems. Repository: github.com/DanielMartensson/Open-SAE-J1939.
-
NMEA 2000 — Marine networking standard built on J1939. Uses the same PGN/SPN structure with marine-specific parameter definitions.
-
FMS Standard — Fleet Management System interface standard (maintained by the FMS working group of European truck manufacturers). Defines a read-only telematics gateway exposing a subset of J1939 PGNs.
Changelog
| Version | Date | Author | Summary of changes |
|---|---|---|---|
| 1.0 | 2026-03-16 | Telematics Tutorial Series | Initial publication |
| 1.1 | 2026-03-16 | Telematics Tutorial Series | Polish pass: expanded all 25 acronyms on first use (ISO, SAE, OBD, ISOBUS, NMEA, FMS, NMT, ETP); added PDU1 CAN ID worked example (Section 2.2); added AMB decode example (5th PGN); added expected output blocks for CCVS, ET1, LFE, AMB; added 32-bit sentinel check examples; added NAME construction worked example (Section 6.2); added FMI quick-reference table (FMI 0–4); added all five cross-references (CAN-01, CAN-02, CAN-03, J19-02, J19-03); fixed CCVS/ET1 example data to match stated output values; fixed PDU1 decimal CAN ID value; added NMEA 2000 and FMS Standard to references |
```