# Architecture ## The one rule **Everything is a bus participant.** Acquisition services publish; displays, loggers and analysis tools subscribe. No service knows which display exists, and no display talks to hardware. This is what lets the three display options coexist without forking the system, and what lets a service be restarted or replaced mid-drive without taking the rest down. ## Shape ``` CAN (can1) ──▶ can-bridge ──┐ OBD-II ──▶ obd-poller ─┤ GNSS ──▶ gnss ├──▶ message bus ──┬──▶ recorder (disk) camera ──▶ vision* ─┘ ├──▶ hud (local video out) └──▶ telemetry-gw ──▶ remote displays * later phase, separate task ``` ## Services | Service | Responsibility | Transmits on CAN | |---|---|---| | `can-bridge` | Read SocketCAN frames, timestamp, publish raw | No | | `obd-poller` | Active OBD-II PID request/response | **Yes** | | `gnss` | gpsd client → position, speed, time fix | No | | `decoder` | Raw frames → named signals via DBC | No | | `recorder` | Persist raw + decoded sessions to disk | No | | `telemetry-gw` | Serve external display clients | No | | `vision` | Camera capture and on-device inference (later) | No | Each ships as a systemd unit. The host-level prerequisites they depend on (kernel modules, device permissions, gpsd) are the environment repo's job, not theirs. ## CAN bus scope Passive sniffing **plus** active OBD-II queries. OBD-II is a request/response protocol, so it necessarily transmits onto a 2008 car's bus. Constraints that follow: - `can-bridge` runs listen-only and must never transmit. - `obd-poller` is the **single writer**. No other service opens a TX-capable socket. - Transmission is rate-limited and restricted to standard OBD-II diagnostic IDs. - TX is a distinct, explicitly enabled mode — not the default state on boot. Reverse-engineering of Z27AG-specific frame IDs proceeds purely from observed traffic; discovered signals are recorded as a DBC rather than as code. ## Message bus **Proposed: MQTT (mosquitto) on the Jetson.** Rationale: - First-class client libraries on every display target — ESP32 (Arduino `PubSubClient` / ESP-IDF) and Android (Paho) — which is the deciding factor. - Broker handles fan-out and retained "last known value", so a display that connects mid-drive immediately has state to render. - Trivially inspectable with `mosquitto_sub` while debugging in a car. Rejected: NNG/ZeroMQ (lower latency, no broker, but a poor ESP32 story and no retained values); DDS (capable, disproportionate). ### Topics ``` yukart/can/raw high-rate raw frames, not retained yukart/vehicle/ decoded signals (rpm, speed, coolant, …), retained yukart/gnss/fix position/speed/time, retained yukart/system/ health and status, retained ``` Payloads: JSON for low-rate signals (readable, debuggable); compact binary or CBOR for `can/raw`, since a 500 kbps bus produces far more traffic than JSON should carry. Topic names and payload schemas are a **contract shared across repos** — Android and ESP32 clients depend on them — so they are versioned in their own repo rather than duplicated per client. ## Display contract Three display paths, in two classes: **Class A — video out.** A screen driven directly by the Orin over SPI/DP/HDMI. The renderer is a local process subscribing to the bus over loopback. **Class B — data only.** The Orin sends no video, only data; the device renders itself. | Path | Transport | Notes | |---|---|---| | Android app | WiFi, or USB (RNDIS) | Full-featured client, richest UI | | ESP32 + RLCD/e-ink | WiFi, or USB/BT serial | Low-rate, glanceable subset of signals | Class B devices reaching the bus over IP connect to the broker directly. For transports without IP (BT SPP, plain USB serial), `telemetry-gw` bridges serial ↔ bus rather than each device inventing its own protocol. Because both classes are bus subscribers, adding, removing or swapping a display touches no acquisition code. ## Decisions Recorded here so fast-moving choices stay visible. Flip a row rather than rewriting the document. | # | Decision | Status | Date | |---|---|---|---| | 1 | Separate services over a message bus, not one process | Accepted | 2026-08-14 | | 2 | Displays are bus subscribers; no display-specific logic upstream | Accepted | 2026-08-14 | | 3 | Listen-only sniffing + active OBD-II, single writer | Accepted | 2026-08-14 | | 4 | MQTT as the bus | **Proposed** | 2026-08-14 | | 5 | Implementation language(s) per service | **Open** | — | | 6 | Local logging first; off-board LTE telemetry deferred | Accepted | 2026-08-14 | | 7 | Camera/AI inference is a later, separate task | Accepted | 2026-08-14 |