rnd-ash / ecu_diagnostics

A Rust crate for ECU diagnostic protocols (UDS / KWP)
GNU General Public License v3.0
170 stars 28 forks source link

OBD2 CAN IDs not matching spec #12

Open camercu opened 2 years ago

camercu commented 2 years ago

Background

According to Wikipedia (sorry I don't have access to the actual OBD-II specification):

The PID query and response occurs on the vehicle's CAN bus. Standard OBD requests and responses use functional addresses. The diagnostic reader initiates a query using CAN ID 7DFh, which acts as a broadcast address, and accepts responses from any ID in the range 7E8h to 7EFh. ECUs that can respond to OBD queries listen both to the functional broadcast ID of 7DFh and one assigned ID in the range 7E0h to 7E7h. Their response has an ID of their assigned ID plus 8 e.g. 7E8h through 7EFh.

This approach allows up to eight ECUs, each independently responding to OBD queries. The diagnostic reader can use the ID in the ECU response frame to continue communication with a specific ECU. In particular, multi-frame communication requires a response to the specific ECU ID rather than to ID 7DFh.

This library's current code has you specify a sid/did (tx_id, rx_id) for OBD messages, and uses a single socket (socketCAN ISO-TP kernel interface) to handle sending/receiving messages on just those addresses. However, this causes problems when running against vehicles that follow the spec.

Observed Behavior

On a 2020 Toyota Camry, I tested Service_09::get_vin(), but it causes a timeout error. The reason is that if I set the tx_id to 0x7df, I get the first frame back from the ECU (with CAN ID 0x7e8), but the ECU pauses and waits for a FlowControl frame to be sent with CAN ID 0x7e0 (8 less than the ECU's transmit ID). The ISO-TP kernel sends flow control using the original broadcast address, 0x7df, which the ECU ignores. If I set the tx_id to 0x7e0 and request the VIN, the ECU completely ignores the request.

Other Implementations

I have a scan tool that sends the OBD messages properly, and I looked at the example code for Comma.ai's Panda, and they also send the request to 0x7df and listen on 0x7e8 (which sends the FC frame using ID 0x7e0). Their handling of UDS protocol does similar filtering to ensure the tx addr is changed from 0x7df to something in the range 0x7e0-0x7e7 (based on the first response received).

Proposed Design

The OBD code should have a single transmit socket with tx_id = 0x7df, and eight listening sockets with rx_id in the range 0x7e8 - 0x7ef (and tx_id for FC frames set to a value that is 8 less than the rx_id). This would follow the specification, allowing 8 ECUs to respond to OBD queries simultaneously. If it is a simple request, like to get the VIN, then that function can just take the first response and return it. In order for this to be possible, you will first have to implement the suggestion in Issue #11 to have a software-based ISO-TP channel. The socketcan-isotp crate definitely supports this, as demonstrated in their examples.

camercu commented 2 years ago

Here is a capture (using candump) of what "good" OBD2 traffic should look like when requesting a VIN (Service 09, PID 02):

  vcan0  7DF   [8]  02 09 02 00 00 00 00 00  <== OBD2 request sent to functional (broadcast) address 0x7df
  vcan0  7E8   [8]  10 14 49 02 01 57 41 55  <== ECU responds with first frame, here using ArbID 0x7e8
  vcan0  7E0   [8]  30 00 00 00 00 00 00 00  <== FlowControl frame sent to ECU's ArbID minus 8 (here 0x7e0)
  vcan0  7E8   [8]  21 5A 5A 5A 38 56 39 46  <== ECU responds with rest of data
  vcan0  7E8   [8]  22 41 31 34 39 38 35 30
  vcan0  7E8   [2]  23 FE

Here is what the current implementation actually sends, when using 0x7df as the tx_id:

  can0  7DF   [8]  02 09 02 00 00 00 00 00
  can0  7E8   [8]  10 14 49 02 01 34 54 31
  can0  7DF   [8]  30 00 00 00 00 00 00 00   <== FlowControl frame incorrectly sent to original ArbID, 0x7df
=== ECU hangs, awaiting FC response on 7E0 ====

And here is what happens when using 0x7e0 as the tx_id on a 2020 Toyota Camry or 2022 Carolla:

  can0  7E0   [8]  02 09 02 00 00 00 00 00
=== ECU does not respond ===
camercu commented 2 years ago

I talked to the author of the can-isotp Linux kernel module, and he confirmed that the OBD2 request message should be sent as a broadcast message in a single CAN frame, with 8 ISO-TP sockets established to listen for responses.

Here is our discussion

rnd-ash commented 2 years ago

OK thanks for this!

I will work on a new OBD2 diagnostic server when I get time to fix this issue :)

camercu commented 1 year ago

Adding a note about extended addressing for whenever we can get around to fixing this issue.

If using 29-bit (extended) addresses instead of the typical 11-bit addressing, the broadcast ID is 0x18DB33F1 instead of 0x7DF.

If the vehicle responds to the requests, you'll typically see responses with CAN IDs 18DAF100 to 18DAF1FF (in practice, typically 18DAF110 and 18DAF11E). The response identifier is also sometimes shown in the 'J1939 PGN' form, specifically the PGN 0xDA00 (55808), which in the J1939-71 standard is marked as 'Reserved for ISO 15765-2'.

ref: https://www.csselectronics.com/pages/obd2-explained-simple-intro