project-yukart becomes the code-free hub: overview, roadmap and cross-cutting design only. Each app or app cluster gets its own repo, indexed from here. - ARCHITECTURE.md: separate services over a message bus, with displays as bus subscribers so the directly-driven screen, Android client and ESP32 panel share one contract. Records CAN scope (listen-only + OBD-II as sole writer) and a decisions table for choices still in flux. - REPOS.md: repo splitting rule, planned repos, naming, and Gitea template-repo policy (scaffolding only — no upstream link after instantiation). - ROADMAP.md: phases 0-5, capture and decode before displays. Bus choice (MQTT) is marked proposed pending a check against real CAN rates; implementation language is still open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
4.8 KiB
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-bridgeruns listen-only and must never transmit.obd-polleris 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_subwhile 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/<signal> decoded signals (rpm, speed, coolant, …), retained
yukart/gnss/fix position/speed/time, retained
yukart/system/<service> 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 |