Running a full ECU diagnostic check with no hardware
EcuReset is deliberately last because the ECU reboots after it.
There's a friction in automotive software work. You want to test your ECU's diagnostics: the UDS services a workshop tool or a flashing rig talks to; and to do that you need an ECU on a bench, a CAN interface (PCAN, a Vector box, something), wiring, power, and a tester driving the other end.
None of that fits in a CI pipeline. Diagnostics get tested by hand, on hardware, and the regression coverage is thin.
We wanted to see how far we could push the other direction: run the whole thing - a real ECU, a real tester, a real CAN bus. Entirely in software, headless, on a free CI runner.
What we built
Two virtual STM32H5 microcontrollers inside LabWired, wired together by a virtual FDCAN bus. One runs a UDS tester (the client side); the other runs a UDS ECU (the server) that registers all 27 ISO-14229 services. Both run real udslib firmware - the same open-source ISO-14229 stack you'd compile for a part. Nothing is mocked.
The tester sends a genuine request for every service, the request crosses the virtual bus, the ECU's real handler runs, the response comes back, and the tester checks the bytes.
firmware.elf builds running on modelled H563 silicon —
the ECU's is stock udslib. The only assertion the gate makes is the tester's own
results word: memory_value @ 0x20010000 == 0x07FFFFFF.
At the end it sets one bit per service in a results word. A CI job reads that word and asserts it equals 0x07FFFFFF - all 27 services answered correctly, across the bus. 27/27, exit 0, no hardware in the loop.
Here's an abridged trace from a real run. Every line is one UDS exchange crossing the virtual CAN bus and getting checked:
$ labwired test --script examples/h5_uds_tester/allservices-gate.yaml
H5-UDS-TESTER H5-UDS-ECU-FULL # two virtual STM32H5s boot
TESTER_REQ_10 ECU_READY RESP_50_OK # DiagnosticSessionControl
TESTER_REQ_27_SEED RESP_67_OK # SecurityAccess (seed)
TESTER_REQ_27_KEY RESP_67_OK # SecurityAccess (key)
PHASE1 4/4 PASS
TESTER_REQ_22 RESP_62_OK # ReadDataByIdentifier (VIN)
TESTER_REQ_2E RESP_6E_OK # WriteDataByIdentifier
PHASE2 8/8 PASS
TESTER_REQ_85 RESP_C5_OK # ControlDTCSetting
TESTER_REQ_86 RESP_C6_OK # ResponseOnEvent
PHASE3 4/4 PASS
TESTER_REQ_34 RESP_74_OK # RequestDownload
TESTER_REQ_38 RESP_78_OK # RequestFileTransfer
PHASE4 6/6 PASS
TESTER_REQ_84 RESP_C4_OK # SecuredDataTransmission
TESTER_REQ_11 ECU_RESET RESP_51_OK # EcuReset (tested last)
PHASE5 5/5 PASS
SERVICES 27/27 PASS
exit=0
10DiagnosticSessionControl
29Authentication
27SecurityAccess
3ETesterPresent
22ReadDataByIdentifier
2EWriteDataByIdentifier
3DWriteMemoryByAddress
23ReadMemoryByAddress
24ReadScalingDataByIdentifier
2CDynamicallyDefineDataIdentifier
2AReadDataByPeriodicIdentifier
2FInputOutputControlByIdentifier
19ReadDTCInformation
85ControlDTCSetting
14ClearDiagnosticInformation
86ResponseOnEvent
31RoutineControl
34RequestDownload
36TransferData
37RequestTransferExit
35RequestUpload
38RequestFileTransfer
28CommunicationControl
83AccessTimingParameter
87LinkControl
84SecuredDataTransmission
11EcuReset — last
EcuReset is held to the
very end so the reboot can't wipe the other 26 results.
And it's not just on my machine: the same thing runs on a clean GitHub Actions runner. Install the toolchain, build both firmwares, build the emulator, run the gate, green end to end. Here's the CI run.
Here's the udslib ECU running on a virtual STM32H5:
ReadDataByIdentifier request over CAN-FD — real udslib firmware, no hardware. Open full-screen ↗It works, it runs nightly on free GitHub runners, and it catches real bugs. The interesting part is which bugs it caught. Every one turned out to be in the emulator, not udslib, and they're all fixed and released.
What you get
A full 27-service UDS diagnostic check - session control, security access, read/write by identifier, DTCs, routine control, the whole programming-and-transfer sequence, right down to ECU reset. Running across two virtual MCUs on a simulated CAN network, headless, in CI, for the cost of a GitHub runner minute. The ECU firmware uses udslib completely unmodified.
That's the version of “digital twin”, your actual firmware running against a CPU model honest enough to surface real bugs - before anything touches a bench.
Try it / the receipts
- The gate & nightly CI merged and live in udslib at examples/h5_uds_tester; it closed issue #58.
- The green CI run full build + 27/27 on a clean runner: actions/runs/29379477389.
- udslib the open ISO-14229 stack under test, used 100% unmodified: w1ne/udslib.
- Run the ECU yourself - the interactive lab above: STM32H5 UDS ECU in the LabWired playground.