Skip to content

10 · Project — Automated CAN Signal Validation Suite

This project pulls together every module in Level 2 into one working deliverable: an automated CAPL test suite that validates a small network's signals for correctness, timing, DTC behavior, and restbus integrity — the kind of suite a real team would run on every build.

About this module

All CAPL, DBC, and CANoe test-module structure below is genuine, documented syntax assembled from the patterns in Modules 1–9. Nothing here was run against a live CANoe instance — treat it as a reviewed reference implementation to adapt and verify on your own setup, not a captured passing run.

Scope

A small network of three messages, matching the DBC style from Module 6:

BO_ 256 EngineStatus: 8 ECU_ENGINE
 SG_ EngineRPM : 0|16@1+ (0.25,0) [0|16383.75] "rpm" Vector__XXX
 SG_ CoolantTemp : 16|8@1+ (1,-40) [-40|215] "degC" Vector__XXX
 SG_ EngineRunning : 24|1@1+ (1,0) [0|1] "" Vector__XXX

BO_ 512 BrakeStatus: 4 ECU_ABS
 SG_ BrakePressure : 0|12@1+ (0.1,0) [0|409.5] "bar" Vector__XXX

BO_ 1024 SensorFusionFrame: 32 ADAS_ECU
 SG_ ObjectCount : 0|8@1+ (1,0) [0|255] "count" Vector__XXX

BA_DEF_ BO_ "GenMsgCycleTime" INT 0 60000;
BA_ "GenMsgCycleTime" BO_ 256 10;
BA_ "GenMsgCycleTime" BO_ 512 20;
BA_ "GenMsgCycleTime" BO_ 1024 20;
BA_ "VFrameFormat" BO_ 1024 1;

The suite validates: signal range plausibility (Module 6), CAN-FD frame tagging (Module 3), message cycle-time adherence (Modules 7/8), and a coolant-overheat DTC's maturation behavior (Module 5) — restbus-backed throughout (Module 8).

Restbus: the supporting cast

variables
{
  message EngineStatus engineMsg;
  message BrakeStatus brakeMsg;
}

on start
{
  engineMsg.EngineRunning = 1;
  engineMsg.EngineRPM = 800;
  engineMsg.CoolantTemp = 90;
  setTimer(txEngine, 10);
  setTimer(txBrake, 20);
}

on timer txEngine { output(engineMsg); setTimer(txEngine, 10); }
on timer txBrake  { output(brakeMsg);  setTimer(txBrake, 20); }

export void SetCoolantTemp(float degC) { engineMsg.CoolantTemp = degC; }
export void SetEngineRPM(float rpm)    { engineMsg.EngineRPM = rpm; }

Test module: range and CAN-FD validation

testcase tc_SignalRangeValidation()
{
  testCaseTitle("All signals stay within DBC-declared physical range");
  testWaitForTimeout(50);

  testStepCheck("EngineRPM within [0,16383.75]",
    engineMsg.EngineRPM >= 0 && engineMsg.EngineRPM <= 16383.75);
  testStepCheck("CoolantTemp within [-40,215]",
    engineMsg.CoolantTemp >= -40 && engineMsg.CoolantTemp <= 215);
  testStepCheck("BrakePressure within [0,409.5]",
    brakeMsg.BrakePressure >= 0 && brakeMsg.BrakePressure <= 409.5);
}

testcase tc_CanFdFrameTagging()
{
  testCaseTitle("SensorFusionFrame is correctly tagged and sized as CAN-FD");
  message SensorFusionFrame fdCheck;
  testWaitForTimeout(50);
  testStepCheck("fdf bit set", fdCheck.fdf == 1);
  testStepCheck("payload length is 32 bytes per DLC mapping", fdCheck.dlc == 12);
}

fdCheck.dlc == 12 follows directly from Module 3's DLC table: DLC 12 maps to 24 bytes, so a genuinely 32-byte payload needs DLC 13 — this line is deliberately written to demonstrate the kind of assertion bug that table exists to prevent; a reviewer should catch that the DBC's declared 32-byte length requires DLC 13, not 12, before this test ships.

Test module: timing adherence

testcase tc_EngineStatusCycleTime()
{
  testCaseTitle("EngineStatus arrives within tolerance of its 10ms cycle time");
  dword lastTime, thisTime, delta;
  int i;
  lastTime = timeNowNS() / 1000000;
  for (i = 0; i < 20; i++)
  {
    testWaitForMessage(EngineStatus, 50);
    thisTime = timeNowNS() / 1000000;
    delta = thisTime - lastTime;
    testStepCheck("cycle time within 10ms +-2ms", delta >= 8 && delta <= 12);
    lastTime = thisTime;
  }
}

Allowing a small tolerance band (±2ms here) rather than asserting an exact 10ms is deliberate — Module 8 covered why sloppy restbus timing can produce false failures on an exact-match assertion; the tolerance should be as tight as the ECU's actual documented timing spec allows, not arbitrarily loose.

Test module: DTC maturation

testcase tc_CoolantOverheatDtcMaturation()
{
  testCaseTitle("Overheat DTC matures to confirmed only after 2nd cycle failure");
  byte statusReq[] = {0x19, 0x02, 0xFF};

  SetCoolantTemp(135.0);
  testWaitForTimeout(200);
  DiagRequestSend(diagRequest(statusReq));
  testWaitForTimeout(100);
  // Expect pending (bit2) set, confirmed (bit3) not yet set
  testStepCheck("pending set after first cycle", (gLastDtcStatus & 0x04) != 0);
  testStepCheck("not confirmed after first cycle", (gLastDtcStatus & 0x08) == 0);

  // Simulate a new operation cycle
  SetCoolantTemp(90.0);
  testWaitForTimeout(50);
  SetCoolantTemp(135.0);
  testWaitForTimeout(200);
  DiagRequestSend(diagRequest(statusReq));
  testWaitForTimeout(100);
  testStepCheck("confirmed after second cycle failure", (gLastDtcStatus & 0x08) != 0);
}

on diagResponse *
{
  gLastDtcStatus = this.byte(4);  // offset depends on actual response layout; verify against ECU spec
}

Test module skeleton, assembled

variables
{
  byte gLastDtcStatus;
}

testpreparation
{
  // Confirm restbus is transmitting before any testcase runs
  testWaitForTimeout(30);
}

testcasefinalization
{
  SetCoolantTemp(90.0);
  SetEngineRPM(800);
}

Result reporting

Per Module 7, this suite should export an XML report with per-testcase verdicts, and per Module 9's HIL framing, be explicit that this project is bus-simulation-only: it validates protocol/logic correctness, not real-ECU embedded-software behavior — a HIL or vehicle-tier suite would be a distinct, later phase against real hardware.

Cheat sheet: what this project exercises from each module

Module Concept exercised here
3 — CAN-FD DLC/length assertion on SensorFusionFrame
5 — DTCs Two-cycle maturation test via 0x19 status mask
6 — DBC Range assertions derived directly from [min\|max] fields
7 — Test sequences testpreparation/testcasefinalization structure, independent testcases
8 — Restbus exported SetCoolantTemp/SetEngineRPM scenario control

How It Actually Works: the DTC maturation test has a hidden assumption worth checking

tc_CoolantOverheatDtcMaturation sets CoolantTemp = 90.0 for only 50 ms to "simulate a new operation cycle" before triggering the fault again. Module 5 established that the DTC status byte's per-cycle bits (testFailedThisOperationCycle, testNotCompletedThisOperationCycle) reset on an actual operation-cycle boundary — commonly a real power-state transition like key-off/key-on — not merely on the monitored signal returning briefly to a healthy value. This test only works at all if the specific ECU under test happens to define its "operation cycle" as something this restbus can actually simulate by value alone; if the real ECU's operation-cycle boundary is tied to ignition state (a separate signal this suite never touches, like EngineRunning toggling to 0) or to a genuine power interruption, then briefly returning CoolantTemp to 90.0 changes nothing about the ECU's internal cycle counter, and the second SetCoolantTemp(135.0) fires within the same operation cycle as the first — which, per Module 5's debounce-counter mechanism, may or may not reproduce the "2 of 3 cycles" maturation behavior depending on whether the debounce counter's threshold logic is itself cycle-gated or purely fail/pass-count-gated.

This is exactly the kind of assumption a suite like this needs to make explicit rather than silently bake into a testWaitForTimeout(50): the test's real precondition is "the DUT's operation-cycle boundary has been crossed," and depending on the target ECU's diagnostic spec, satisfying that might require toggling EngineRunning to 0 and back (simulating key-off/key-on) in addition to the value dip, not the value dip alone. A reviewer signing off on this test case should ask the ECU's diagnostic spec author what actually constitutes an operation-cycle boundary for this DTC before trusting that a pass here proves the "2 of 3 cycles" rule — otherwise a false pass (or false fail) is entirely plausible depending on how that specific ECU's software actually defines a cycle.

Exercise

  1. Fix the tc_CanFdFrameTagging DLC bug identified above, and explain in one sentence why the original assertion would have let a genuinely mis-tagged frame pass undetected.
  2. Add a fourth testcase, tc_RestbusStartupOrder, that verifies EngineStatus and BrakeStatus are both transmitting before any diagnostic request is sent in tc_CoolantOverheatDtcMaturation — describe (pseudocode is fine) how you'd detect "restbus not yet active" versus "restbus active but signal at default value."
  3. This suite currently has no test for BrakePressure's own boundary behavior. Using Level 1's boundary-value technique and this project's DBC, write one full test case (table format, as in Level 1 Module 8) for a brake-pressure-triggered warning threshold of your own choosing.