Diagnostics
Talking to ECUs rather than listening to the bus. Everything here sends request frames, so read Safety first, and note that ordinary reads are not behind the ARM TX gate.
What the tab holds
Six sub-tabs: OBD-II and UDS, UDS deep scan, UDS services, security access, bus load and bus health.
ISO-TP
core/isotp.py implements ISO 15765-2, the transport that
carries diagnostic messages larger than eight bytes: single frames,
multi-frame transmission with the flow-control handshake and separation
time, and reassembly of responses.
You rarely touch this directly, but it is the layer everything else rides on, so when a scan returns nothing this is often where the problem is.
It is tested against can-isotp, an independent implementation, in both directions. That test found that the first consecutive frame after each flow control went out without the separation time the receiver had asked for; every consecutive frame is now timed against the one before it.
UDS
core/uds.py implements ISO 14229 requests: read diagnostic
trouble codes, read ECU identification by data identifier, and scan which
services an ECU supports.
Probing a service like ECU Reset or Clear Diagnostic Information does exactly what the name says. Destructive services are skipped unless you tick Include destructive services and confirm.
DTCs come back as four-byte records: three bytes of code plus a status byte. The printable form follows ISO 15031-6, so the high two bits select the letter (P, C, B or U) and the rest give the digits.
Security access
core/security_access.py handles service 0x27, the seed and key
exchange that unlocks a protected session. It requests a seed and tries
common algorithms, or your own key function from a Python script; there is
an example at canlab/sample_data/example_seedkey.py.
Brute force is rate limited and stops the moment the ECU reports that its attempt limit is reached. Tripping that counter can lock a module until it is power cycled, and on some modules permanently. This is not a feature to point at a part you cannot replace.
OBD-II
core/obd2_pids.py decodes 78 mode 01 PIDs with the SAE J1979
formulas. A scan first asks the vehicle which PIDs it supports, walking the
continuation windows rather than assuming the first 32, and then reads only
those. Trouble codes are read with modes 03 (stored) and 07 (pending), which
every OBD-II vehicle answers, and UDS service 0x19 only if they get no reply.
A vehicle that answers nothing is reported as silent, not as clean.
This is the one protocol where you can expect an answer from any compliant vehicle without knowing anything about it, which makes it a good first test that your interface and wiring work at all.
J1939 and NMEA 2000
core/j1939.py decodes parameter group numbers for heavy
vehicles by the SAE J1939-71 bit layouts in core/j1939_db.py:
26 PGNs and 152 parameters, 30 more PGNs named, the preferred source-address
table, two-bit switch states, and the error and not-available ranges, which
are never shown as readings. DM1 active trouble codes decode into SPN, FMI,
CM and OC fields.
An audit of the earlier table found values read from the wrong bytes and the wrong messages, among them coolant temperature from half of the crankcase pressure. On the real truck log the corrected layouts agree with each other: the brakes' and the engine's road speeds differ by 0.33 km/h, absolute inlet pressure minus boost is the barometer, and lifetime distance over fuel matches the ECU's own economy.
Marine NMEA 2000 uses the same 29-bit frame, so the data page decides which table applies. Hand-written decoders, checked against frames from a real recording, cover heading, rate of turn, position, course and speed, wind, temperature and the GNSS fix; every other standard PGN, 216 in all, is decoded from a table distilled from canboat (Apache 2.0). Where both exist they agree on every value on the real recording.
Messages that span several frames are reassembled by
core/multiframe.py before decoding: J1939 transport protocol,
both BAM broadcasts and RTS/CTS sessions between two other nodes (observed
only; CanLab never sends a CTS), and NMEA 2000 fast packets with their
sequence and counter byte. Timeouts run on the frame clock. On the marine
recording this rebuilds 60 GNSS position fixes of 43 bytes, decoded to a
position, date, time, altitude and satellite count, and 60 satellite lists
of 135 bytes; on the truck log, 85 BAM broadcasts of engine and retarder
configuration with nothing dropped. The PGN scan in INTELLIGENCE shows the
reassembled messages; list_pgns and
list_transport_messages serve them over MCP. No recording in
the corpus has an RTS/CTS session, so that path is tested against synthetic
frames and against two can-j1939 nodes holding a real one on a virtual
bus.
Bus load and health
Two monitor sub-tabs. Load shows utilisation over time. Health tracks error frames, bus-off events and arbitration IDs that go silent, which is the quickest way to notice that something you did upset a module.
XCP and DoIP
Both have panels in DIAGNOSTICS.
| Module | What it does |
|---|---|
core/xcp.py | XCP over CAN, read-only: CONNECT, UPLOAD and SHORT_UPLOAD plus a measurement poller. It deliberately implements no memory-write or programming commands, so it cannot put a value into an ECU. |
core/doip.py | DoIP (ISO 13400) over stdlib sockets: vehicle discovery, routing activation and UDS over IP. |