Files
contracts/docs/BOUNDARY.md
T
2026-10-13 18:35:06 +09:00

4.0 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/+ and yukart/host/+. 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-poweroff (the Orin's unit only), and when boot_id is not the current boot's

A repeated id returns the original ack (last 256 kept, shared across connections) and never re-runs the command.

poweroff is the exception to retry-until-acked: the ack cache does not survive the reboot it causes, so a resend could shut the next boot down too. The client sends it once and does not resend it; 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: its fix is an object, which the envelope's value does not allow yet.
  • Any version beyond proto: 0. A breaking change bumps it; the client checks it.