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>
This commit is contained in:
2026-08-14 09:21:10 +09:00
parent e16b460d56
commit 62ac7bd09d
4 changed files with 304 additions and 5 deletions
+119
View File
@@ -0,0 +1,119 @@
# 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 |