Add navigation, Orin runtime and data flow design; rework repo naming
Navigation: self-hosted MapLibre + Valhalla + PMTiles, offline-first, with Japan specifics (left-hand traffic via valhalla_build_admins, data volume, CJK labels). Map matching pulled forward as Phase 2 ground truth. Orin runtime: new document covering the 8 GB shared-memory budget, capture-first startup tiers, failure isolation, storage and the audio graph. Data flow: direction belongs to topics inside the Orin; the display boundary is two simplex channels rather than one duplex socket; cloud links are outbound-initiated only; services mock themselves rather than having separate stub implementations. Naming: repositories are <group>-<thing> with no project prefix, so lexical order matches semantic grouping. Indexes the five new repos. Decisions 8-19 recorded; 14 (recording retention) left open. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013kj58Jugkic783b8ZrtTVZ
This commit is contained in:
@@ -37,6 +37,9 @@ 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.
|
||||
|
||||
How these coexist on one 8 GB board — memory budget, startup ordering, failure
|
||||
isolation, audio — is in [ORIN-RUNTIME.md](ORIN-RUNTIME.md).
|
||||
|
||||
## CAN bus scope
|
||||
|
||||
Passive sniffing **plus** active OBD-II queries. OBD-II is a request/response
|
||||
@@ -71,6 +74,8 @@ 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
|
||||
yukart/nav/guidance next manoeuvre, distance, street name, retained
|
||||
yukart/nav/route active route geometry, retained
|
||||
```
|
||||
|
||||
Payloads: JSON for low-rate signals (readable, debuggable); compact binary or
|
||||
@@ -81,6 +86,75 @@ Topic names and payload schemas are a **contract shared across repos** — Andro
|
||||
and ESP32 clients depend on them — so they are versioned in their own repo
|
||||
rather than duplicated per client.
|
||||
|
||||
## Data flow and direction
|
||||
|
||||
### Inside the Orin, direction belongs to topics, not links
|
||||
|
||||
Every on-Orin component is a bus participant, so there are no directed links
|
||||
between services to design. `decoder` subscribing to raw frames and publishing
|
||||
decoded ones is not a duplex link with anything — it is two independent one-way
|
||||
relationships that happen to share a process.
|
||||
|
||||
The only place direction is real is the Orin/display boundary.
|
||||
|
||||
### The boundary is two simplex channels, not one duplex one
|
||||
|
||||
Both directions carry traffic, but they want opposite guarantees:
|
||||
|
||||
| | Orin → app | app → Orin |
|
||||
|---|---|---|
|
||||
| Content | signals, guidance, position | route request, PTT, session mark, prefs |
|
||||
| Rate | continuous, 1–50 Hz | bursty, human-paced, under 1 Hz |
|
||||
| Loss tolerance | **high** — the next sample supersedes | **none** — a dropped mark is data lost silently |
|
||||
| Semantics | latest-wins, drop under backpressure | at-least-once, acknowledged, retried |
|
||||
| On reconnect | push current state at once | retry whatever is pending |
|
||||
|
||||
One duplex socket with uniform semantics forces a choice between stalling the
|
||||
stream and losing commands. So: **downstream a push stream that drops oldest under
|
||||
pressure, upstream plain request/response.** That is decision 9 generalised from
|
||||
route requests to everything the app sends.
|
||||
|
||||
The app always initiates. The Orin listens and never dials out to a display, so
|
||||
reconnection is entirely the client's problem.
|
||||
|
||||
### Cloud links are outbound-initiated
|
||||
|
||||
The Orin calls out; nothing calls in. This is forced anyway — an IoT SIM behind
|
||||
CGNAT has no inbound path — but stating it as a rule buys the CAN safety property
|
||||
for free. A cloud client that cannot initiate can never reach `obd-poller`, so the
|
||||
single-writer rule survives contact with the internet with no special casing.
|
||||
|
||||
### Fixtures publish the real schema
|
||||
|
||||
Hardware arrives incrementally, so some sources are stubs for now. A stub
|
||||
publishes the **real topic with the real payload schema**: `yukart/gnss/fix` fed
|
||||
from a replayed trace must be indistinguishable, at the bus, from the same topic
|
||||
fed by a real fix. Swapping the real source in is then a no-op for every
|
||||
subscriber.
|
||||
|
||||
A stub that invents its own topic or shape is worse than no stub — it defers the
|
||||
integration work rather than removing it.
|
||||
|
||||
### `svc-gw` holds two roles, and one must never block the other
|
||||
|
||||
`svc-gw` is both the local aggregation hub and the app gateway. Combining them is
|
||||
acceptable because the hub role holds **no durable state**: everything it knows is
|
||||
derived from retained topics, so a restart repopulates within one round of
|
||||
retained messages and costs nothing.
|
||||
|
||||
Keeping the roles separable requires four rules:
|
||||
|
||||
- The hub reaches steady state with **zero clients connected**, always. Nothing in
|
||||
the aggregation path may wait on a display.
|
||||
- The boundary is **drop-only** — bounded per-client queues, drop oldest, never
|
||||
block. A tablet with its screen off must not stall aggregation.
|
||||
- **Backpressure never propagates inward.** A slow client is dropped at the
|
||||
boundary, never absorbed by slowing the bus subscription.
|
||||
- Per-client state lives per-connection and never touches the aggregate.
|
||||
|
||||
Follow these and splitting the gateway out later is a refactor rather than a
|
||||
rewrite: the seam stays visible even while the process is one.
|
||||
|
||||
## Display contract
|
||||
|
||||
Three display paths, in two classes:
|
||||
@@ -103,6 +177,83 @@ 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.
|
||||
|
||||
## Navigation
|
||||
|
||||
Self-hosted and offline-first, rather than a cloud maps SDK. Japan has good LTE
|
||||
coverage, so this is not the argument it would be in a sparse country — it rests
|
||||
on tunnels, of which Japan has a great many, plus mountain roads and urban
|
||||
canyon. Independently: a map that round-trips to a cloud API in order to pan is
|
||||
the wrong shape for a car whatever the coverage, and holding the data locally is
|
||||
what makes map matching against recorded sessions possible at all.
|
||||
|
||||
| Piece | Choice | Role |
|
||||
|---|---|---|
|
||||
| Basemap tiles | Protomaps **PMTiles**, Japan extract | Single file, static-served; no tile server process |
|
||||
| Renderer | **MapLibre GL** | Basemap, own position, trace, route line |
|
||||
| Routing | **Valhalla** | Route and turn-by-turn, from OSM tiles built offline |
|
||||
|
||||
Valhalla over GraphHopper for three reasons: it is C++ with memory-mapped tiles
|
||||
rather than a JVM holding a resident graph, which suits an 8 GB Jetson; it
|
||||
targets embedded and automotive use explicitly; and it ships **Meili** map
|
||||
matching.
|
||||
|
||||
That last point is why this is not a detour from the roadmap. Meili's
|
||||
`trace_attributes` snaps a recorded GNSS trace onto OSM and returns road
|
||||
attribution along it — speed limit, road class, geometry. That is *ground truth
|
||||
for validating reverse-engineered CAN signals* in Phase 2, and it pays off
|
||||
before any navigation UI exists.
|
||||
|
||||
A moving map — position, heading and trace, no routing — is most of the value in
|
||||
a car and needs only the renderer and a GNSS fix. Routing is the expensive 20%
|
||||
and comes last.
|
||||
|
||||
### Guidance on the bus, route requests over HTTP
|
||||
|
||||
Guidance is state, so it belongs on the bus: `yukart/nav/guidance` carries the
|
||||
next manoeuvre, retained, and a display connecting mid-drive renders immediately.
|
||||
|
||||
Route *requests* are request/response and fit MQTT badly. Displays call the
|
||||
Valhalla HTTP API through `telemetry-gw` instead of inventing a request/reply
|
||||
topic pair. Recorded as decision 9.
|
||||
|
||||
### Map data is not source
|
||||
|
||||
Basemap and routing tiles are large binary artifacts rebuilt from upstream OSM.
|
||||
They are never committed. `tool-mapmint` holds the scripts that fetch and build
|
||||
them, plus a report of each build's provenance; `svc-nav` consumes the output. The
|
||||
artifacts themselves live outside git and never touch the workstation-side repo.
|
||||
|
||||
They reach the car by `rsync` **over the LAN only, never over LTE** — the IoT SIM
|
||||
is not a sensible transport for a multi-gigabyte tile set. Gate the sync on a
|
||||
reachable LAN target rather than on an SSID, so the check validates the thing it
|
||||
actually needs, and treat NetworkManager's metered flag as a second guard.
|
||||
Cadence is quarterly or on annoyance; road geometry does not move fast enough to
|
||||
justify anything more.
|
||||
|
||||
Swap atomically. Build beside the live copy, symlink-swap, restart Valhalla —
|
||||
never overwrite tiles in place while the server is reading them. The basemap is
|
||||
one file, so write-then-`mv` covers it with no restart.
|
||||
|
||||
This also settles the tile *form*: the **directory** wins over the `.tar`, twice
|
||||
over — once for RAM on an 8 GB board, and once because rsync deltas a tree of
|
||||
changed files efficiently where a single multi-gigabyte tar forces a full-file
|
||||
checksum at both ends.
|
||||
|
||||
### Japan specifics
|
||||
|
||||
| Concern | Consequence |
|
||||
|---|---|
|
||||
| **Left-hand traffic** | `valhalla_build_admins` builds the admin database that determines driving side. It is a separate step *before* the tile build, and the database is used only at build time, not by the running server. Skip it and Valhalla routes and narrates as though Japan drove on the right. |
|
||||
| **Data volume** | The whole-Japan PBF is **2.3 GB** — Japan is among the more densely mapped countries in OSM. Routing tiles and basemap scale with it. Geofabrik publishes per-region extracts (Kantō, Chūbu, Hokkaidō, …); start with the region actually driven, not the whole country. |
|
||||
| **CJK labels** | MapLibre cannot ship Japanese glyph sets as SDF ranges — far too large. Set `localIdeographFontFamily` so the browser renders CJK from on-device fonts. Protomaps tiles also carry `name:en`, so romanised labels are an option. |
|
||||
| **Addressing** | Japanese addresses are block-based, not street-based. Valhalla does not geocode at all, so destination search is a separate problem — do not assume a western geocoder transfers. |
|
||||
|
||||
### Known risk
|
||||
|
||||
Valhalla's arm64 container story is unsettled upstream. Confirming that Valhalla
|
||||
builds and runs on the Jetson is the **first** task in this workstream, before
|
||||
any data pipeline work is done against it.
|
||||
|
||||
## Decisions
|
||||
|
||||
Recorded here so fast-moving choices stay visible. Flip a row rather than
|
||||
@@ -117,3 +268,15 @@ rewriting the document.
|
||||
| 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 |
|
||||
| 8 | Self-hosted offline navigation (MapLibre GL + Valhalla + PMTiles) over a cloud maps SDK | Accepted | 2026-09-03 |
|
||||
| 9 | Nav guidance published on the bus; route requests over HTTP via `telemetry-gw` | **Proposed** | 2026-09-03 |
|
||||
| 10 | Map artifacts delivered by `rsync` over LAN only; Valhalla tiles kept as a directory, not a tar | Accepted | 2026-09-03 |
|
||||
| 11 | Boot ordering is capture-first: acquisition reaches ready before serving or voice | Accepted | 2026-09-03 |
|
||||
| 12 | The Orin owns the car's aux input; the display unit carries no permanent cable | Accepted | 2026-09-03 |
|
||||
| 13 | Reasoning stays in the cloud — a local LLM does not fit beside the voice stack | Accepted | 2026-09-03 |
|
||||
| 14 | Recording retention policy — unbounded growth silently stops capture | **Open** | — |
|
||||
| 15 | Boundary is two simplex channels — lossy stream down, acknowledged request/response up | Accepted | 2026-09-03 |
|
||||
| 16 | Cloud links are outbound-initiated only; nothing calls in | Accepted | 2026-09-03 |
|
||||
| 17 | `svc-gw` combines local hub and app gateway; no client may block the hub | Accepted | 2026-09-03 |
|
||||
| 18 | Stubs publish the real topic and schema, never a fixture-specific one | Accepted | 2026-09-03 |
|
||||
| 19 | Services mock themselves behind their own input interface; no separate stub implementations exist | Accepted | 2026-09-04 |
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
---
|
||||
type: default
|
||||
---
|
||||
# Orin runtime
|
||||
|
||||
[ARCHITECTURE.md](ARCHITECTURE.md) describes services and the bus without saying
|
||||
how they coexist on one 8 GB board. This document covers that: what runs, in what
|
||||
order, under what budget, and what happens when it goes wrong.
|
||||
|
||||
Cross-cutting by nature — it spans `svc-core`, `svc-nav`, `svc-gw` and the host
|
||||
repo, so it lives in the hub rather than in any one of them.
|
||||
|
||||
## Layers
|
||||
|
||||
```
|
||||
clients app-flutter, app-esp32 off the Orin
|
||||
─────────────────────────────────────────────────────────
|
||||
serving svc-gw, svc-nav
|
||||
processing decoder, recorder
|
||||
acquisition can-bridge, obd-poller, gnss
|
||||
bus mosquitto
|
||||
─────────────────────────────────────────────────────────
|
||||
host kernel modules, gpsd, PipeWire, network orin-nano-env-setup
|
||||
```
|
||||
|
||||
A layer may depend on the one below it and on the bus. Nothing depends sideways:
|
||||
no service waits on another service's process, only on the broker.
|
||||
|
||||
## The binding constraint: 8 GB, shared
|
||||
|
||||
The Orin Nano has 8 GB of LPDDR5 **shared between CPU and GPU**. There is no
|
||||
separate VRAM to fall back on — a model loaded onto the GPU takes memory away
|
||||
from everything else on the board. Roughly 6–7 GB is left once JetPack and the OS
|
||||
have taken their share.
|
||||
|
||||
| Consumer | Resident | Note |
|
||||
|---|---|---|
|
||||
| OS + JetPack | ~1–1.5 GB | before anything of ours starts |
|
||||
| mosquitto, acquisition, `svc-gw` | tens of MB | not a factor |
|
||||
| Valhalla server | small; tiles are **mmap'd page cache** | evictable — see below |
|
||||
| Whisper | 273 MB (tiny) → 3.9 GB (large) | `small` is 244 M params; `large` does not fit |
|
||||
| GPT-SoVITS | upstream guidance assumes an 8 GB VRAM card | the largest unknown; **measure before designing around it** |
|
||||
| vision (Phase 5) | — | competes directly with voice for the same pool |
|
||||
|
||||
Four rules follow.
|
||||
|
||||
**The cloud LLM is load-bearing.** [OVERVIEW.md](../OVERVIEW.md) puts reasoning in
|
||||
the cloud. That decision is what makes a local voice stack fit at all — moving the
|
||||
LLM on-device collapses this budget entirely. It is not a free choice to revisit.
|
||||
|
||||
**Size Whisper by vocabulary, not by benchmark.** In-car commands are a narrow,
|
||||
predictable set. `large` buys accuracy this application cannot spend, at a cost
|
||||
this board cannot pay.
|
||||
|
||||
**Voice and vision cannot both be resident.** Phase 5 has to plan for eviction,
|
||||
not addition. Worth knowing now, while vision is still deferred.
|
||||
|
||||
**Valhalla's memory-mapped tiles are not a leak.** They appear as page cache and
|
||||
are evictable. Under pressure the kernel drops tile pages and re-faults them on
|
||||
next use — that costs routing latency, not an OOM. This is the graceful failure
|
||||
in the system and should be left alone.
|
||||
|
||||
## Startup tiers
|
||||
|
||||
Ordered by what is lost if a tier is late.
|
||||
|
||||
| Tier | Units | Why here |
|
||||
|---|---|---|
|
||||
| 0 | kernel modules, gpsd, network | Host layer; nothing of ours can precede it |
|
||||
| 1 | `mosquitto`, `can-bridge`, `recorder` | **Data is lost if late.** Must reach ready fastest |
|
||||
| 2 | `decoder`, `gnss`, `obd-poller` | Derive from tier 1; a late start loses nothing |
|
||||
| 3 | `svc-gw`, `svc-nav` | Serving; a display can wait |
|
||||
| 4 | voice — ASR, TTS | Interactive; seconds late is harmless |
|
||||
|
||||
**Capture first** is the whole principle. A CAN frame missed at tier 1 is gone
|
||||
forever; a map that loads five seconds into the drive costs nothing. Ordering the
|
||||
boot the intuitive way — get the screen up quickly — trades irreplaceable data for
|
||||
a cosmetic gain.
|
||||
|
||||
## Failure isolation
|
||||
|
||||
- `Restart=always` on every unit. A service dying mid-drive is a normal event.
|
||||
- Units order themselves `After=` the broker and nothing else. No service may wait
|
||||
on another service, or a restart cascades.
|
||||
- The broker is the single point of failure, and that is accepted: retained topics
|
||||
make reconnection cheap, so a client that comes back has state on its first
|
||||
message rather than waiting for the next publish.
|
||||
- No service holds another's state. Anything worth surviving a restart is either
|
||||
retained on the bus or on disk.
|
||||
|
||||
## Ignition off
|
||||
|
||||
Shutdown is the reverse of startup, so tier 1 dies last.
|
||||
|
||||
The only thing that *must* complete is the recorder's flush. It therefore owns its
|
||||
own flush and shutdown handling rather than trusting the OS to get there in time —
|
||||
a truncated session is the one failure that cannot be recovered by restarting
|
||||
anything.
|
||||
|
||||
## Storage
|
||||
|
||||
Self-built binaries live under `/home/mikkeli/infra/` per the host repo's
|
||||
guidelines. Data does not.
|
||||
|
||||
| Data | Size | Growth |
|
||||
|---|---|---|
|
||||
| Map artifacts | GB | Replaced wholesale, quarterly at most |
|
||||
| Voice models | GB | Static |
|
||||
| Recorded sessions | GB per drive | **Unbounded** |
|
||||
|
||||
Recordings are the problem. Without a retention policy the disk fills mid-drive
|
||||
and capture stops — silently, because a full disk is not a crash. A retention
|
||||
policy is a tier-1 concern, not housekeeping.
|
||||
|
||||
## Audio
|
||||
|
||||
The Orin owns the car's single aux input, via a USB DAC.
|
||||
|
||||
```
|
||||
GPT-SoVITS (TTS) ──┐
|
||||
├──▶ PipeWire mixer ──▶ USB DAC ──▶ aux in
|
||||
A2DP sink ─────────┘ │
|
||||
(media from the tablet) └── ducking: media drops under TTS
|
||||
|
||||
mic ──▶ Whisper (ASR)
|
||||
```
|
||||
|
||||
Three reasons the aux cable belongs here rather than on the tablet:
|
||||
|
||||
- **The aux cable should belong to the fixed unit.** The display is meant to be
|
||||
removable; anything holding a permanent cable to the car is not removable.
|
||||
- **Ducking becomes a local policy** on one box instead of coordination between
|
||||
two. Without it the assistant talks over the music.
|
||||
- **The direction works.** Linux is a capable A2DP *sink*, so the tablet can send
|
||||
media here. The reverse — this box as an A2DP source into an Android sink — is
|
||||
not viable, since stock Android builds generally do not enable A2DP sink.
|
||||
|
||||
Unplug the tablet and the car keeps audio and voice. The Orin depends on the
|
||||
display unit for nothing.
|
||||
+37
-11
@@ -19,31 +19,57 @@ contracts repo, not copied into each consumer.
|
||||
|---|---|
|
||||
| [project-yukart](gitea:yukart/project-yukart) | Hub: overview, roadmap, architecture |
|
||||
| [orin-nano-env-setup](gitea:yukart/orin-nano-env-setup) | Jetson host bring-up |
|
||||
| [app-flutter](gitea:yukart/app-flutter) | Flutter display client |
|
||||
| [svc-gw](gitea:yukart/svc-gw) | Telemetry gateway — aggregation and the HTTP surface |
|
||||
| [svc-nav](gitea:yukart/svc-nav) | Navigation service — routing and guidance |
|
||||
| [tool-mapmint](gitea:yukart/tool-mapmint) | Map artifact minting, workstation-side |
|
||||
| [tool-mock](gitea:yukart/tool-mock) | Mocks and fixtures — a fake car on a workstation |
|
||||
|
||||
## 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 |
|
||||
| `contracts` | Topic namespace, payload schemas, Z27AG DBC | Consumed by every other repo, including non-Jetson clients; must not be duplicated |
|
||||
| `svc-core` | Acquisition + processing cluster: `can-bridge`, `obd-poller`, `gnss`, `decoder`, `recorder` | Developed and deployed as one unit on the Orin |
|
||||
| `app-esp32` | ESP32 RLCD/e-ink display firmware (Class B) | Embedded toolchain, flashed not deployed |
|
||||
| `app-hud` | Local renderer for a directly-driven screen (Class A) | Its own toolchain and graphics stack — **in question**, see the display contract |
|
||||
| `tool-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.
|
||||
Repositories are named `<group>-<thing>`, with **no project prefix**. The `yukart`
|
||||
Gitea org already namespaces everything, so a `yukart-` prefix would be a constant
|
||||
occupying the most significant characters of every name — pure noise in a sorted
|
||||
index, and the reason the earlier scheme scattered.
|
||||
|
||||
| Group | Meaning |
|
||||
|---|---|
|
||||
| `svc-` | A service running on the Orin |
|
||||
| `app-` | A display client, running off the Orin |
|
||||
| `tool-` | Off-car tooling; never deployed to the car |
|
||||
|
||||
The goal is that **lexical order equals semantic grouping**: opening the org
|
||||
listing puts every service together, every client together, every tool together.
|
||||
The group segment does that work because it is the first thing that varies.
|
||||
|
||||
Ungrouped singletons — `contracts` — are fine where a category has exactly one
|
||||
member and always will.
|
||||
|
||||
`project-yukart` and `orin-nano-env-setup` predate this and keep their names
|
||||
rather than paying a rename for two repos that are stable and easy to find.
|
||||
|
||||
### Reusability
|
||||
|
||||
Nothing in a name marks a repo as reusable. A component that turns out to be
|
||||
genuinely reusable outside Yukart **leaves the org** rather than being renamed —
|
||||
location says it, not spelling.
|
||||
|
||||
## 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
|
||||
is likely to be reused. Keep those generic and outside the org — e.g. an ESP32
|
||||
project skeleton with the toolchain, flashing scripts and CI already wired up.
|
||||
|
||||
### Gitea template repositories
|
||||
|
||||
+25
-3
@@ -31,7 +31,7 @@ capture decodable later.
|
||||
## Phase 2 — Decode
|
||||
|
||||
- [ ] Correlate captures against the event log; identify recurring frame IDs
|
||||
- [ ] Build the Z27AG DBC incrementally in `yukart-contracts`
|
||||
- [ ] Build the Z27AG DBC incrementally in `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
|
||||
@@ -52,14 +52,36 @@ 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)
|
||||
- [ ] `app-hud` — directly-driven screen (Class A)
|
||||
- [ ] `app-esp32` — 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.
|
||||
|
||||
## Navigation · *parallel workstream*
|
||||
|
||||
Not a phase — it runs alongside, and its early output feeds Phase 2. Design is
|
||||
in [ARCHITECTURE.md](ARCHITECTURE.md#navigation); code in `svc-nav` (runtime)
|
||||
and `tool-mapmint` (artifact builds).
|
||||
|
||||
- [ ] Confirm Valhalla builds and runs on the Jetson (arm64) — do this first,
|
||||
it is the one real integration risk
|
||||
- [ ] Build the admin database with `valhalla_build_admins` — this is what sets
|
||||
left-hand traffic, and it must run before the tile build
|
||||
- [ ] Build Valhalla routing tiles from a Geofabrik Japan extract, starting with
|
||||
the region actually driven rather than all 2.3 GB of it
|
||||
- [ ] Pull a Japan basemap extract from the Protomaps daily build with
|
||||
`go-pmtiles`; static-serve it, with `localIdeographFontFamily` set for CJK
|
||||
- [ ] MapLibre page rendering the basemap from a replayed GNSS trace
|
||||
- [ ] Moving map live in the car — **blocked on gpsd in Phase 0**
|
||||
- [ ] Meili map matching over recorded sessions, as Phase 2 ground truth
|
||||
- [ ] Routing and turn-by-turn guidance onto `yukart/nav/guidance`
|
||||
|
||||
The map-matching item is worth pulling forward: it validates decoded CAN speed
|
||||
against OSM speed limits and road geometry, which is otherwise guesswork.
|
||||
|
||||
## Deferred
|
||||
|
||||
- Live off-board telemetry over LTE — hardware is present, but local logging
|
||||
|
||||
Reference in New Issue
Block a user