Decision 30: CAN feeds displays, OBD-II (K-line via the ELM) calibrates, so every obd2 signal carries the obd_ prefix and the plain names are left for decoded ones. yukart/gnss/<signal> gives displays the fix as single values; gnss/fix stays for navigation. Boundary: reboot beside poweroff, same guards. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
4.2 KiB
type
| type |
|---|
| reference |
Display boundary protocol — v0
Between a display client (app-flutter) and svc-gw. The
bus topics in schema/ stay inside the Orin; this is what crosses to the tablet.
Transport
One WebSocket, ws://<orin>:7700/, JSON text messages, each with a "t" type.
The tablet reaches the car Orin at 192.168.55.1 over USB NCM; the app binds to
that network itself (see app-flutter's initial plan).
svc-gw caps its socket's send rate (SO_MAX_PACING_RATE, ~5 MB/s; project-yukart
decision 28, proposed). Unpaced bursts toward the tablet stall the USB link for
seconds; paced traffic does not (app-flutter docs/impl/reports/LINK-TEST-2026-10-11.md).
State messages are far below the cap; it matters for utterances and anything bulky.
The client always connects and owns reconnection. svc-gw refuses peers outside loopback, RFC 1918 and the tailnet's 100.64.0.0/10.
The two channels from decision 24 share the socket but keep different guarantees:
| Down (state) | Up (commands) | |
|---|---|---|
| Loss | tolerated; svc-gw drops the oldest when a client lags (64-message queue) | not tolerated; retry until acked |
| On reconnect | hello, then current state on the next tick |
resend anything unacked, same id |
Down: svc-gw → client
t |
When | Fields |
|---|---|---|
hello |
first message | proto (0), server, host, boot_id (the host's, changes every boot), source (bus | mock | none), mock, hb_ms, timeout_ms |
hb |
every hb_ms |
ts (Orin epoch ms), clients |
state |
5 Hz, when there is a source | seq, mock, signals: name → envelope (schema/envelope.json) |
ack |
reply to a cmd |
id, ok, detail, dup (true when answered from the cache) |
With source: bus, signals is the newest envelope of every signal svc-gw has
seen on yukart/vehicle/+, yukart/host/+ and yukart/gnss/+. svc-gw marks one
quality: "stale" when it has not been refreshed for 5 s, or is known only from
the broker's retained copy (which may predate a reboot). A signal once seen is
never removed.
signals holds only what is currently known. A missing signal is unknown, a
null value is known-without-value, quality: "stale" is an old value. All three
are normal and a display must render each distinctly.
⚠ mock: true must be visible on the display surface whenever it appears in
hello or in any envelope. Not in a settings page, not in a log.
Up: client → svc-gw
t |
When | Fields |
|---|---|---|
hb |
every hb_ms |
— |
cmd |
on user action | id (unique, the idempotency key), cmd, args |
svc-gw drops a client silent for timeout_ms (5 s). The client should treat
timeout_ms without any downstream message the same way and reconnect: an asleep
tablet, or an Orin behind a replugged hub, looks connected at the link layer.
Commands
cmd |
args |
Effect |
|---|---|---|
ping |
— | ack with detail: "pong" |
mark |
text, delay_ms (optional: how long the command waited before this send) |
stamped into the open recording; fails with no session open |
poweroff |
confirm: "poweroff", boot_id (from this connection's hello) |
acks, then shuts the host down cleanly (systemctl poweroff) about 2 s later. Refused unless svc-gw runs with --allow-power (the Orin's unit only), and when boot_id is not the current boot's |
reboot |
confirm: "reboot", boot_id |
the same, with systemctl reboot; the same flag and refusals |
A repeated id returns the original ack (last 256 kept, shared across
connections) and never re-runs the command.
poweroff and reboot are the exception to retry-until-acked: the ack cache does
not survive the restart they cause, so a resend could shut the next boot down (or
reboot it again). The client sends them once and does not resend; boot_id makes
svc-gw refuse one from a previous boot regardless.
Not yet
- TTS utterances (id, chunk sequence, terminator, expiry) — when there is a TTS.
gnss/fixas one object (navigation's input): the envelope'svaluedoes not allow one yet. Displays get the fix as single values onyukart/gnss/<signal>instead.- Any version beyond
proto: 0. A breaking change bumps it; the client checks it.