2026-09-03 18:48:32 +09:00
---
2026-09-04 14:14:17 +09:00
type: plan
status: active
2026-10-09 16:08:28 +09:00
updated: 2026-10-09
2026-09-03 18:48:32 +09:00
---
# Initial plan
## Order
2026-10-09 20:23:00 +09:00
1. Text readout of the boundary stream — prove the contract before drawing
anything. **Starter in place: ** connects to `svc-gw` (protocol v0, contracts
`docs/BOUNDARY.md` ), shows link state, every signal with stale/null/missing
told apart, the raw message log, the mock banner, and a Mark button.
2026-09-03 18:48:32 +09:00
2. Gauges.
3. Moving map: MapLibre against the PMTiles basemap served by the Orin, with the
position from `yukart/gnss/fix` . No routing involved.
4. Guidance from `yukart/nav/guidance` , once [svc-nav ](gitea:yukart/svc-nav ) publishes it.
A moving map with position, heading and trace is most of the value in a car and
needs no routing engine at all. Routing is the expensive last fraction.
## Open
2026-10-09 20:23:00 +09:00
- **Transport.** The app talks to `svc-gw` over one WebSocket (boundary v0).
Whether high-rate topics later bypass it to the broker is decided in `svc-gw` .
2026-09-03 18:48:32 +09:00
- **CJK labels.** MapLibre cannot ship Japanese glyph sets as SDF ranges. Local
font rendering must be configured, or labels will be slow or absent.
- **Sunlight.** A typical tablet is unreadable through a windscreen in daylight.
This is a hardware problem, but the UI should not make it worse.
2026-09-03 20:28:32 +09:00
## The boundary, from this side
Two channels with different guarantees, not one socket:
| | Down (state) | Up (commands) |
|---|---|---|
| Transport | push stream | request/response |
| Loss | tolerated; next sample supersedes | **not ** tolerated; retry until acknowledged |
| On reconnect | expect current state immediately | resend anything pending |
This client always initiates and owns reconnection — the Orin never dials out.
2026-10-09 16:08:28 +09:00
Assume the link drops constantly: cable knocked, screen off, power changes.
## The USB link, as measured (2026-10-09)
Tested with the actual tablet (Redmi Pad SE 8.7 4G, Android 16) against the car
Orin. Details: [USB tablet link report ](gitea:yukart/orin-nano-env-setup ).
- **Physical:** tablet USB-C → PD hub (charging from the car) → Orin's USB-C port.
The tablet is the USB host; the Orin is a USB network gadget. The tablet picks
**NCM ** and gets `192.168.55.100` ; the Orin is * * `192.168.55.1` **.
- **Android won't route over it by default.** The link has no internet, so it never
becomes the default network while WiFi or cellular is up, and ordinary sockets go
elsewhere. Verified: the Orin was unreachable until WiFi and cellular were off.
The app must request the Ethernet network (`ConnectivityManager.requestNetwork`
with `TRANSPORT_ETHERNET` ) and bind its sockets to it.
- **About 3 s blank per power change.** Cutting the hub's PD costs ~3.5 s (full
USB detach); restoring it ~2.2 s. Ignition and cranking produce these.
- **An asleep tablet is a dead link.** Off charger and idle, Android suspends about
a minute after screen-off and answers nothing, while the Orin still sees the USB
link up. The app keeps the device awake while it is the display (screen-on flag
in the full UI; the same for any overlay widget).
- Reconnect without user action, and expect the Orin to drop a silent client by
heartbeat (project-yukart decision 24).
2026-09-03 20:28:32 +09:00
## Session marking is worth building early
A button that stamps an event (`indicator on` , `cold start` , `hard braking` ) into
the recording, on the same clock as the CAN frames, turns the Phase 1 manual event
log from a notebook-and-wristwatch exercise into structured data. It is what makes
a raw capture decodable later, and it is a Phase 1 capability that happens to live
in this repo.
2026-09-04 07:59:27 +09:00
## Mock data must look like mock data
`svc-gw` can synthesise vehicle state without any real readings, which is how this
app is developed with no Jetson present. The stream carries a flag when it does.
Rendering synthetic readings indistinguishable from real ones, on a screen in a
moving car, is a safety problem. The indication belongs on the display surface
itself, not in a settings page or a startup log.
2026-09-04 13:57:31 +09:00
## This app is the audio sink
The tablet owns the car's aux input and is the music player, so the Orin's speech
has to arrive here to be heard.
2026-10-09 16:10:47 +09:00
- TTS arrives as an **utterance: a sequence of chunks with a terminator ** over the
reliable channel, never as one opaque file body. Streaming playback (start on
the first chunk, append, finish on the terminator via `AudioTrack` streaming
mode) is the target.
- **Acceptable first version:** buffer the chunks and play on the terminator. With
a non-streaming TTS that is a single chunk anyway. Swapping to streaming then
changes only this receiver, not the protocol or the Orin.
2026-09-04 13:57:31 +09:00
- Duck other audio with `AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK` for the duration of
the utterance, then release. That is what makes Spotify drop under the voice
without either side coordinating.
- The microphone stays on the Orin. This app never captures audio; ASR input never
crosses the boundary.
2026-10-09 20:23:00 +09:00
## Development workflow
| Where | Device link | Gateway |
|---|---|---|
| Desk | tablet USB → halogen, ADB over USB | `cargo run -- --mock` in `svc-gw` on halogen; app built with `--dart-define=YUKART_GW=ws://<halogen LAN IP>:7700/` , or the address changed at runtime by tapping the link bar |
| Car | tablet USB → Orin (tablet is USB host; no ADB over USB) | `yukart-gw.service` on the Orin, `ws://192.168.55.1:7700/` , the default |
The app binds the process to the USB Ethernet network only when the gateway
address is on `192.168.55.0/24` , so a desk build pointed at halogen over WiFi is
not routed into a dead USB link.
### ADB in the car, without a hub
The tablet's only USB port is the Orin link, and in it the tablet is the host —
USB debugging is unavailable there. ADB over TCP is not, and it travels over the
same NCM link:
``` bash
adb tcpip 5555 # at the desk, over USB, once per tablet boot
# in the car, from halogen, through the Orin over the tailnet:
ssh -N -L 5555:192.168.55.100:5555 mikkeli-orin-nano &
adb connect localhost:5555
flutter run -d localhost:5555
```
The tablet keeps the RSA authorisation from the USB pairing. `adb usb` turns TCP
mode off again; a reboot does too. Android's * Wireless debugging * (pairing) is
WiFi-only and does not help here. Untested in the car so far.
### CI
`.gitea/workflows/ci.yaml` mirrors the android-stuff apps: host-mode runner on
halogen, label `flutter-android` , analyze + test, then a debug APK artifact on
push. The existing `halogen-android` runner is registered to the android-stuff
org only, so this repo needs a runner registered to the `yukart` org (a second
instance on halogen, the way `gitea-runner-audio` is set up) before jobs run.