What this example does
This guide sends a read-only OBD-II request for engine RPM and decodes the response.
The request uses:
- functional request CAN ID
0x7DF - OBD service
0x01, current powertrain data - PID
0x0C, engine speed
This is a small diagnostics example. It is not a complete scan tool and it is not UDS implementation code.
Work safely
Use a bench ECU, simulator, or approved test vehicle setup. Do not experiment while a vehicle is moving. Start with documented read-only services.
Never assume that every vehicle follows the same IDs, gateway behavior, or access policy. The target may not expose OBD traffic on the connector or network you selected.
The raw OBD-II request
The eight CAN data bytes are:
02 01 0C 00 00 00 00 00
They mean:
| Byte | Meaning |
|---|---|
02 | Two application bytes follow in this single-frame request |
01 | Request current powertrain data |
0C | Request engine RPM |
| Remaining bytes | Padding |
Send it with can-utils:
cansend can0 7DF#02010C0000000000
Watch for responses:
candump can0,7E8:7F8
Physical responses commonly use IDs from 0x7E8 through 0x7EF.
Example response
can0 7E8 [8] 04 41 0C 1A F8 00 00 00
The important bytes are:
04: four application bytes follow41: positive response to service0x010C: engine RPM PID1A F8: RPM data bytes A and B
The formula is:
RPM = ((A x 256) + B) / 4
For A = 0x1A and B = 0xF8:
RPM = ((26 x 256) + 248) / 4
RPM = 1726
Complete Python example
Install python-can:
python -m pip install python-can
Save this as read_rpm.py:
import can
REQUEST_ID = 0x7DF
FIRST_RESPONSE_ID = 0x7E8
request = can.Message(
arbitration_id=REQUEST_ID,
data=[0x02, 0x01, 0x0C, 0x00, 0x00, 0x00, 0x00, 0x00],
is_extended_id=False,
)
response_filter = [
{
"can_id": FIRST_RESPONSE_ID,
"can_mask": 0x7F8, # Accept 0x7E8 through 0x7EF
"extended": False,
}
]
with can.Bus(
interface="socketcan",
channel="can0",
can_filters=response_filter,
) as bus:
bus.send(request, timeout=1.0)
print("Engine RPM request sent")
response = bus.recv(timeout=2.0)
if response is None:
raise SystemExit("No OBD-II response")
data = response.data
if len(data) < 5:
raise SystemExit(f"Response is too short: {data.hex(' ')}")
if data[1] != 0x41 or data[2] != 0x0C:
raise SystemExit(f"Unexpected response: {data.hex(' ')}")
rpm = ((data[3] << 8) | data[4]) / 4.0
print(f"Response ID: 0x{response.arbitration_id:X}")
print(f"Engine speed: {rpm:.0f} rpm")
Run it:
python read_rpm.py
Why the first byte looks like ISO-TP
OBD-II messages longer than the basic CAN payload use ISO-TP. Even this short request uses a single-frame length byte.
For a single frame, the low part of the first byte tells the receiver how many application bytes follow. Longer diagnostic messages use first frames, consecutive frames, and flow-control frames.
Do not manually implement multi-frame handling by adding sleeps between raw CAN frames. Use a tested ISO-TP stack.
Functional vs physical addressing
The request ID 0x7DF is commonly used as a functional request: any relevant ECU may respond.
A physical request targets one ECU, often using a paired ID such as:
request: 0x7E0
response: 0x7E8
Actual IDs vary. Use the diagnostic specification for the target.
Common reasons for no response
- The engine or ECU is not powered in the required state.
can0has the wrong bitrate.- A gateway blocks the request.
- The selected network is not the diagnostic CAN network.
- The vehicle uses different addressing.
- The PID is not supported.
- The interface is bus-off.
- Another diagnostic session affects responses.
First confirm that raw traffic is present and that the request frame is actually transmitted.
OBD-II vs UDS
OBD-II defines emissions-related diagnostic services and standardized parameters. UDS is a broader diagnostic protocol used for ECU sessions, data identifiers, trouble codes, routines, security access, and programming.
Both can use ISO-TP over CAN, but their service bytes and data models are different.
The simple summary
To read engine RPM, send an OBD-II service 0x01 request for PID 0x0C, wait for a response, check the service and PID bytes, and apply the defined formula. Keep the first diagnostics tests read-only and use ISO-TP libraries for longer requests.