At a glance
Stage V separates control from observation: the Emulator drives the vehicle bus; the Gateway alone owns a Digital Twin (Brain, assembly actors, ROB, FSM, Observation Tee); a Dashboard watches live over UDS or Zenoh; every decision is also teed to disk as JSONL for later replay.
[ Emulator ]
│ (vcan0)
v
┌─ Gateway ──────────────────────┐
│ ┌─ Digital Twin ─────────────┐ │
│ │ Brain Actor │ │
│ │ Assembly actors │ │ ──(UDS / Zenoh)──> [ Dashboard (TUI) ]
│ │ ROB · FSM │ │
│ │ Observation Tee │ │
│ └──────────────┬─────────────┘ │
└────────────────┼───────────────┘
│
v
[ Disk: JSONL Logs ]
Preface
Note: A capstone version 1.0 is blogged here, as a summary of the phases through Stage V.
The preceding phase gave us an arrangement where, we had
- Two Assemblies (Headlamp and Wiper), and actuators for each, picking ('switch on')and pushing CAN messages on the Bus ('ack' or 'nack')
- Quiescence over multiple FSM hops
- Ledger row produced / emitted for every FSM transition
- The Digital Twin (with its Brain actor) was owned by the Gateway application (run from the command line)
- An emulator which could generate messages representing real Vehicle's environment, ad infinitum , but without any start / stop indicators
To run the entire prototype suite, four #rustlang programs are executed from the command-line:
- Gateway
- Emulator
- Headlamp Actuator
- Wiper Actuator
Of course, the CAN Bus has to run on the Linux OS.
What are we adding in this phase (Phase 5)
We are adding a Dashboard as a process separate from the Gateway. The Gateway owns the Digital twin; the Dashboard is an observation consumer. The Gateway emits diagnostics and state-transition ledger rows. The Dashboard collects those, interprets those as appropriate and then displays appropriately.
The emitted records are transported in JSONL form and use either a Unix Domain Socket or Zenoh in peer-mode.
The Dashboard is built using #ratatui.
Final arrangement
Five independently runnable binaries share the bus and observation path:
| Process | Role |
|---|---|
| Gateway | Sole twin owner: install, CAN ingress, actuation, observation file tee, live publication of what is happening in the Digital Twin |
| Dashboard | Observation-only TUI (driver / engineer / ledger tail) |
| Emulator | Finite or Ctrl+C CAN lifecycle; emits RPM / lux / rain; ensures that the Digital Twin receives PowerOn before any message it produces and PowerOff before it exits. |
| Headlamp / Wiper actuators | CMD responses on vcan0 |
Observation is a first-class product of the Gateway:
- Files always:
ObservationTeeconverts each twin record once and writesmanifest.json,diagnostic.jsonl, andledger.jsonlunder./observations/<run-id>/. This is a step towards re-playability of events that come in to the Digital Twin through the Gateway. - Transport is chosen at the start: : Because the Gateway and Dashboard have to agree on how do they communicate between themselves, while running the executable, we provide exactly one mode —
--uds PATH, or--zenoh --keyexpr EXPR(or Gateway--no-livefor headless capture) for sharing the 'live' payloads. These payloads share the same envelope shape as the 'observations' files meant for replay (schema currently v3). - Install gate: Gateway waits for a Dashboard (UDS accept or first Zenoh matching subscriber) before installing the twin, so the live consumer is attached when the session starts. In other words, a Gateway is launched and then, it waits for the Dashboard to connect; only after that a Digital Twin is created and 'installed'.
The library pyramid is honoured by both the Gateway and Dashboard. That way, the common struct s remain the source of truth in both of them; JSON mirrors them field-for-field. Presentation components (glyphs, colours, labels) stay on receiver-side , i.e., on the Dashboard.
Dashboard view
Layout sketch of the ratatui Dashboard (five panes):
┌─ Session ────────────────────────────────────────────────────────────────────┐
│ Car: virtual-car-01 │ Twin T+: 0:00:42 │ FSM: Driving │ seq=104 │
└──────────────────────────────────────────────────────────────────────────────┘
┌─ Diagnostic (Driver) ────────────────┐┌─ State Transitions (Engineer) ───────┐
│ ││ Current state: Driving │
│ Notice: rain raining=true ││ Last event: UpdateAmbientLux(40) │
│ ││ Sub-assemblies: │
│ Speed: ########.... 72 km/h ││ Headlamp: On │
│ Visibility: # low (tunnel/dark) ││ Wiper: Running │
│ Weather: ~ Raining ││ │
│ Wipers: ~~ moving ││ │
└──────────────────────────────────────┘└──────────────────────────────────────┘
┌─ Transition ledger ──────────────────────────────────────────────────────────┐
│ [101] | UpdateRpm(2100) | Driving -> Driving │
│ [102] | UpdateAmbientLux(40) | Driving -> Driving │
│ [103] | RainsStarted | Driving -> Driving │
│ > [104] | UpdateRpm(2200) | Driving -> Driving │
│ (up to ~20 rows; '>' marks newest) │
└──────────────────────────────────────────────────────────────────────────────┘
┌──────────────────────────────────────────────────────────────────────────────┐
│ Connected via uds:./tmp/observation.sock | Keys: q quit │
└──────────────────────────────────────────────────────────────────────────────┘
- There are 5 panes. Diagnostic is meant for a regular driver of the car; State Transitions is meant for an observer from the operations team or an engineer.
- Topmost pane shows, amongst other things, in which state the Digital Twin's FSM is currently in.
- Bottommost pane is a running commentary of the Transition Ledger (similar behaviour as
tail -fof Linux/Unix, but no up-scrolling button yet)
Design principles we keep
-
Emit facts, format at the edge. payloads emitted by the Twin carries data only; glyphs are Dashboard's preferences.
-
Wide streams, selective collectors. The twin emits rich diagnostics and ledger context as continuous streams; the dashboard's panes filter what needs to be shown, where and when.
-
CAN first for the vehicle bus. Zenoh in this phase is an observation carrier, not a bus replacement.
Details, run commands, and file paths live in the Iteration 5 README.
Summary
Stage V is an observation-and-presentation milestone: same twin, ROB, and assemblies as Stage IV, but the Gateway now treats diagnostics and ledger rows as first-class products. Files are always teed under ./observations/<run-id>/; live delivery is optional and exclusive — UDS, Zenoh, or --no-live. A fifth process, the Dashboard, is observation-only: it never owns the twin, only consumes schema-v3 envelopes and formats them for driver and engineer panes.
From the operator’s perspective: start actuators, Gateway, Dashboard, then emulator — watch FSM, weather, and ledger hop in a ratatui TUI while the run lands on disk. From the developer’s perspective: emit facts at the twin boundary; present at the edge; keep common as the shared source of truth.
Where the code is
- Repository:
sdv_simulation_5on GitHub — builds onsdv_simulation_4. - Operational reference: project README (crate layout, how to run, UDS/Zenoh flags, Dashboard surfaces, tests).
- Design decisions:
docs/DESIGN.md; roadmap and TBDs indocs/PLAN.md. - Historical notes:
docs/archive/(Iteration 4 twin/ROB design retained for reference). - This post is the narrative; the README on
mainis the current truth for running and extending the prototype.
Series: Prototyping a Software Defined Vehicle
← Previous Stage IV
All posts in this series: sdv-prototype