--- type: reference --- # Display boundary protocol — v0 Between a display client (`app-flutter`) and [svc-gw](gitea:yukart/svc-gw). The bus topics in `schema/` stay inside the Orin; this is what crosses to the tablet. ## Transport One WebSocket, `ws://: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/fix` as one object (navigation's input): the envelope's `value` does not allow one yet. Displays get the fix as single values on `yukart/gnss/` instead. - Any version beyond `proto: 0`. A breaking change bumps it; the client checks it.