Files
project-yukart/docs/ARCHITECTURE.md
T
mikkeli 62ac7bd09d Populate design, architecture and repo layout docs
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>
2026-08-14 09:22:35 +09:00

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-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/<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