CanLab/docs

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.

The service scan is read-only by default

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.

Attempt limits are real

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.

ModuleWhat it does
core/xcp.pyXCP 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.pyDoIP (ISO 13400) over stdlib sockets: vehicle discovery, routing activation and UDS over IP.