Prototyping a Software Defined Vehicle - Stage V

5 minute read Published: 2026-07-21

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

To run the entire prototype suite, four #rustlang programs are executed from the command-line:

  1. Gateway
  2. Emulator
  3. Headlamp Actuator
  4. 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:

ProcessRole
GatewaySole twin owner: install, CAN ingress, actuation, observation file tee, live publication of what is happening in the Digital Twin
DashboardObservation-only TUI (driver / engineer / ledger tail)
EmulatorFinite 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 actuatorsCMD responses on vcan0

Observation is a first-class product of the Gateway:

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                    │
└──────────────────────────────────────────────────────────────────────────────┘

Design principles we keep

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


Series: Prototyping a Software Defined Vehicle

← Previous Stage IV

All posts in this series: sdv-prototype