⚠️ Early-stage project — Embrig is actively evolving. APIs and configuration formats may change, and production use should be evaluated carefully.
Rust · CAN · UDP · Deterministic

Embrig

One test suite. From simulation to real hardware.

Deterministic embedded testing for CAN & Ethernet — define any network with a DBC file or a netmap, write suites in plain YAML, and run them against host-compiled firmware (SIL), a deterministic virtual simulation, a real ECU over SocketCAN, or a UDP network.

Deterministic · No flaky tests · AGPL-3.0

For embedded teams building ECUs, robots, vehicles and networked devices who want the same integration tests to run before and after hardware exists. A network simulator, a YAML test runner and a SIL/HIL harness — unified by one idea: write the test once.

Built for reproducibility

⚡

Deterministic by design

An integer-microsecond clock, ECUs stepped in config order — a PASS→FAIL flip is always caused by your change, never by the runner.

🔁

One suite, many targets

The same YAML tests drive host-compiled firmware (SIL), the virtual simulation on your laptop, a live CAN bus over SocketCAN — bare, or with a restbus of simulated peers running beside your real ECU — or an Ethernet (UDP) network. Without rewriting anything.

📐

DBC and netmap native

Parse BO_/SG_/VAL_ for CAN, or a netmap of named fields at byte offsets for UDP — Intel/Motorola byte order, signed values, scaling and symbolic value tables.

🧪

YAML test DSL

send, set_signal, wait, expect, fault — plus send_udp, set_field, expect_udp, fault_udp for Ethernet. Assert equals, greater_than, less_than, present, absent within us/ms/s deadlines.

📊

Reports that explain

HTML/JSON results with the reason each test failed, and exit codes: 0 all pass · 1 test failures · 2 usage errors.

🛠

One-command CLI

init scaffolds vehicle.yaml, DBC and tests; simulate prints the deterministic bus trace; test and report close the loop.

How it works

1

Write tests in YAML

Plain YAML describes stimulus and assertions against real DBC signals — no C harness, no bespoke framework.

2

Run them against your real firmware (SIL)

Host-compiled firmware implementing the Ecu trait runs on the virtual bus. Each simulated step has a wall-clock budget (default 100 ms) — an overrun fails the test instead of hanging it.

3

Or on a virtual network

A deterministic simulator of your network — nodes defined in YAML or Rust — runs on any laptop. The bundle includes an EV powertrain demo: inject overvoltage, brake presses, dropped frames.

4

Re-run against real hardware

The identical suite drives a physical ECU over SocketCAN, or talks to Ethernet nodes over a real UDP socket. On a bare socket, set_signal, set_field and fault are rejected with a clear error — not silently ignored. Switch the interface to restbus and your simulated peers (config nodes, reactive vECUs) run beside the real ECU as a hardware-in-the-loop rest bus.

See it in action

embrig test examples/ev-powertrain/vehicle.yaml
$ embrig test examples/ev-powertrain/vehicle.yaml
PASS  brake_press_disables_motor           (360 ms)
PASS  bus_carries_periodic_frames           (230 ms)
PASS  charger_faults_on_stale_battery       (910 ms)
PASS  nominal_conditions_enable_motor       (320 ms)
PASS  overvoltage_disables_motor            (410 ms)

5 passed, 0 failed
embrig simulate vehicle.yaml --duration 600ms --verbose
$ embrig simulate examples/ev-powertrain/vehicle.yaml --duration 600ms --verbose

           0 0x100  [8] A0 0F 00 00 5A 02 01 00
           0 0x110  [8] 00 00 00 00 00 00 00 00
           0 0x120  [8] 01 00 00 00 00 00 00 00
           0 0x210  [8] 00 00 00 00 00 00 00 00
           0 0x220  [8] 00 00 00 00 00 00 00 00
           0 0x230  [8] 03 00 00 00 00 00 00 00
       50000 0x220  [8] 01 00 00 00 00 00 00 00
       50000 0x230  [8] 03 00 00 00 00 00 00 00
      100000 0x100  [8] A0 0F 00 00 5A 02 01 00
         … deterministic integer-µs clock, ECUs stepped in config order
a change flips exactly the right test
# bump the VCU overvoltage threshold from 450 V to 460 V in vehicle.yaml
$ embrig test examples/ev-powertrain/vehicle.yaml

PASS  brake_press_disables_motor           (360 ms)
PASS  bus_carries_periodic_frames           (230 ms)
PASS  charger_faults_on_stale_battery       (910 ms)
PASS  nominal_conditions_enable_motor       (320 ms)
FAIL  overvoltage_disables_motor            (410 ms)
       0x220.motor_enable expected false, got 1

4 passed, 1 failed
# the flip is caused by your change, never by the runner

From laptop to hardware

Simulationvirtual bus on your laptop
→
SIL firmwareyour real control code
→
vCAN / SocketCANloopback, then an adapter
→
HILyour ECU on the wire
→
HIL + restbussimulated peers beside it

Write the test once. Increase the realism of the environment without rewriting the test.

Get started in two minutes

No hardware. No CAN adapter. No setup.

A Rust workspace — clone it, run one command.

cargo run --bin embrig -- test examples/ev-powertrain/vehicle.yaml

# → 5 passed, 0 failed

Then go further — same suite, more realism:

# Software-in-the-loop: your firmware on the virtual bus
cargo run --example sil_firmware --package embrig-sil
# → 2 passed, 0 failed

# … or a diff-drive rover: drive, e-stop, over-speed fail-safe
cargo run --example robot_sil --package embrig-sil
# → 4 passed, 0 failed

# … or the Ethernet rover: telemetry, overrides, drop/corrupt faults
cargo run --example udp_rover --package embrig-test
# → 4 passed, 0 failed

# Scaffold your own project
cargo run --bin embrig -- init my-project
cargo run --bin embrig -- test my-project/vehicle.yaml

# Drive a real CAN bus (build with the socketcan feature)
cargo build --features socketcan
cargo run --features socketcan --bin embrig -- \
  test my-project/vehicle.yaml --interface vcan0

# … or add a simulated rest bus beside your real ECU
# (interface "hil" is declared in vehicle.yaml with type: restbus)
cargo run --features socketcan --bin embrig -- \
  test my-project/vehicle.yaml --interface hil

A test in plain YAML

name: overvoltage_disables_motor
timeout: 5s
steps:
  - wait: { time: 300ms }
  - set_signal: { ecu: battery, id: 0x100, signal: voltage, value: 460.0 }
  - expect: { id: 0x220, signal: motor_enable, equals: false, within: 1s }
  - expect: { id: 0x230, signal: state, equals: "SAFE", within: 1s }

The same file runs in the simulator, against SIL firmware, and on the bus. Over 450 V the VCU refuses to enable the motor — a PASS→FAIL flip proves the ECU behaves, not the runner.

Development

cargo fmt --all --check
cargo clippy --workspace --all-targets -- -D warnings
cargo test --workspace --features socketcan   # 178 tests, incl. 11 CLI black-box
cargo run --example sil_firmware --package embrig-sil
cargo run --example robot_sil --package embrig-sil
cargo run --example udp_rover --package embrig-test
scripts/vcan-smoke.sh           # optional: exercise the real SocketCAN path on vcan0

Guides: how to SIL-test your firmware · how to HIL-test your firmware · how to test Ethernet (UDP) nodes