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
+32 -5
View File
@@ -1,10 +1,37 @@
# Project Yukart
Mostly passive probing and info extraction and GUI on a Z27AG Colt Ralliart.
Overview available at [OVERVIEW.md](OVERVIEW.md).
Making a 2008 Mitsubishi Colt Ralliart (Z27AG) a bit smarter with modern technologies.
What the system is meant to do is described in [OVERVIEW.md](OVERVIEW.md).
## Other repositories
This repository is the **overview and roadmap only — it holds no code.**
Every application or application cluster lives in its own repository and is
indexed here.
- [Environment setup](gitea:yukart/orin-nano-env-setup)
## Documents
## Roadmap
- [Overview](OVERVIEW.md) — what the system is meant to do
- [Architecture](docs/ARCHITECTURE.md) — system shape, services, message bus, display contract
- [Repositories](docs/REPOS.md) — repo layout, naming, template-repo policy
- [Roadmap](docs/ROADMAP.md) — phases and current status
## Repository index
| Repository | Role | Status |
|---|---|---|
| [project-yukart](gitea:yukart/project-yukart) | This hub — overview, roadmap, cross-cutting design | Active |
| [orin-nano-env-setup](gitea:yukart/orin-nano-env-setup) | Jetson host bring-up: drivers, kernel modules, OS-level setup | Active |
Planned repositories and the reasoning behind the split are described in
[docs/REPOS.md](docs/REPOS.md).
## Hardware at a glance
| Part | Role |
|---|---|
| NVIDIA Jetson Orin Nano Super (`mikkeli-orin-nano-2`) | Main compute, JetPack r36.4 / Ubuntu 22.04 |
| PEAK PCAN-USB | Car CAN bus access (`can1`, 500 kbps) |
| Onboard MTTCan | Second CAN interface (`can0`) |
| Waveshare SIM7600G-H-M.2 | LTE Cat 4 + GNSS |
Details and rationale live in the environment repo's
[bill of materials](gitea:yukart/orin-nano-env-setup).
+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 |
+86
View File
@@ -0,0 +1,86 @@
# Repositories
## Splitting rule
- **`project-yukart`** — overview, roadmap, cross-cutting design. No code, ever.
- **`orin-nano-env-setup`** — the host: kernel modules, drivers, OS packages,
device permissions, systemd plumbing. Anything that must be true *before* an
application starts.
- **One repo per application or application cluster.** A cluster is a set of
services that are developed, versioned and deployed together.
- **Helper/deployment repos** stand alone when they are reusable outside Yukart.
Anything cross-repo — topic names, payload schemas, the DBC — belongs in the
contracts repo, not copied into each consumer.
## Current
| Repository | Role |
|---|---|
| [project-yukart](gitea:yukart/project-yukart) | Hub: overview, roadmap, architecture |
| [orin-nano-env-setup](gitea:yukart/orin-nano-env-setup) | Jetson host bring-up |
## Planned
| Repository | Role | Why separate |
|---|---|---|
| `yukart-contracts` | Topic namespace, payload schemas, Z27AG DBC | Consumed by every other repo, including non-Jetson clients; must not be duplicated |
| `yukart-core` | Acquisition + processing cluster: `can-bridge`, `obd-poller`, `gnss`, `decoder`, `recorder` | Developed and deployed as one unit on the Orin |
| `yukart-gw` | `telemetry-gw` — serves external display clients | Independent release cycle from acquisition; the network-facing surface |
| `yukart-hud` | Local renderer for the directly-driven screen (Class A) | Its own toolchain and graphics stack |
| `yukart-android` | Android display client (Class B) | Entirely separate toolchain and release process |
| `yukart-esp32-display` | ESP32 RLCD/e-ink display firmware (Class B) | Embedded toolchain, flashed not deployed |
| `yukart-analysis` | Offline session analysis, reverse-engineering notebooks | Runs off-car, no runtime coupling |
Create these lazily — when there is something to put in them, not before.
## Naming
- `yukart-*` for project-specific repositories.
- **No prefix** for reusable helpers, since the prefix would be a lie the first
time one is used in another project.
## Helper and deployment repos
Arduino and ESP32 work implies flashing, build and deployment scaffolding that
is likely to be reused. Keep those unprefixed and generic — e.g. an ESP32
project skeleton with the toolchain, flashing scripts and CI already wired up.
### Gitea template repositories
Gitea supports these: repo **Settings → Repository → Template**, after which the
repo offers **"Use this template"** to create new repos from it.
Use a template repo for **scaffolding** — a new ESP32 or Arduino project that
should start with the toolchain, directory layout and flashing scripts in place.
Do **not** use one for code you intend to keep updating in one place. Template
instantiation is a fork-at-create: the copy has no link back, so improvements to
the template never reach repos already created from it. For genuinely shared
code, use a git submodule or publish a package instead.
Rule of thumb: *template for the starting shape, submodule/package for living
code.*
## Linking convention
Cross-repo links use the `gitea:owner/repo` scheme, as established in the hub
README — e.g. `[Environment setup](gitea:yukart/orin-nano-env-setup)`.
## Per-repo document layout
Following the convention already set by `orin-nano-env-setup`:
```
README.md minimal portal, points everywhere else
docs/
GUIDELINES.md implementation practice for this repo
BOM.md hardware, where the repo owns hardware
impl/
plans/ what is intended
reports/ what actually happened, and why
```
The `impl/plans` ÷ `impl/reports` split is worth keeping: plans go stale,
reports do not. A report like `PCAN-SETUP.md` stays valuable precisely because
it records the investigation and the dead ends, not just the final commands.
+67
View File
@@ -0,0 +1,67 @@
# Roadmap
Phases are ordered by dependency, not by date. Each phase should end with
something demonstrable in the car.
## Phase 0 — Host bring-up · *in progress*
Getting the Jetson to see the hardware at all.
- [x] PCAN-USB working as SocketCAN `can1` (out-of-tree `pcan.ko`, netdev build)
- [x] SIM7600G-H-M.2 specified and documented
- [ ] SIM7600 brought up: LTE data path and GNSS via gpsd
- [ ] Kernel-update procedure for the out-of-tree PCAN module — it does not
survive kernel upgrades and will break silently
- [ ] Power and mounting in the car: clean shutdown on ignition off
Tracked in [orin-nano-env-setup](gitea:yukart/orin-nano-env-setup).
## Phase 1 — Capture
Get real Z27AG traffic onto disk before designing anything around it.
- [ ] `can-bridge` — listen-only, timestamped frames onto the bus
- [ ] `recorder` — persist raw sessions
- [ ] Capture drive sessions across varied conditions (idle, cold start, moving,
lights/indicators/windows actuated) with a written log of what was done when
The manual event log matters more than it sounds: it is what makes the raw
capture decodable later.
## Phase 2 — Decode
- [ ] Correlate captures against the event log; identify recurring frame IDs
- [ ] Build the Z27AG DBC incrementally in `yukart-contracts`
- [ ] `decoder` — raw frames to named signals
- [ ] `obd-poller` — active OBD-II PIDs, single writer, rate-limited
- [ ] `gnss` — position/speed, useful as ground truth for validating decoded speed
OBD-II gives standardised signals quickly and is worth doing early; it also
provides a cross-check for reverse-engineered ones.
## Phase 3 — Bus and contracts
- [ ] Stand up the broker; confirm MQTT holds up at real CAN rates (decision 4
in [ARCHITECTURE.md](ARCHITECTURE.md) is still *proposed*)
- [ ] Freeze v1 of topic names and payload schemas
- [ ] `telemetry-gw` — external client access, including the serial bridge
## Phase 4 — Displays
Order chosen by feedback speed, not ambition.
- [ ] One display end-to-end first, proving the contract — Android client is the
quickest to iterate on
- [ ] `yukart-hud` — directly-driven screen (Class A)
- [ ] `yukart-esp32-display` — RLCD/e-ink glanceable subset (Class B)
## Phase 5 — Camera and inference · *separate task*
Deliberately deferred. It is the reason the compute is a Jetson rather than a
Pi, but it shares nothing with the CAN work beyond the bus.
## Deferred
- Live off-board telemetry over LTE — hardware is present, but local logging
covers the current need
- Any CAN transmission beyond OBD-II diagnostics