Files
svc-core/docs/impl/plans/INITIAL.md
T

7.1 KiB
Raw Blame History

type, status, updated
type status updated
plan active 2026-10-12

Initial plan

Order

  1. recorder — raw CAN and GNSS to disk. Built first because every later piece is developed against what it records.
  2. obd-poller — standard OBD-II PIDs at a low rate, the cross-check for reverse-engineered signals. The first transmitter: can_pcan leaves listen-only for it, a deliberate step.
  3. can-bridge and gnss — publish onto the broker once it exists (project-yukart Phase 3; MQTT at ~890 frames/s is still unproven).
  4. decoder — frames to named signals via the DBC in contracts.

Alongside: orin-status — in place (2026-10-12), the first publisher on the bus. Below.

Recorder

Reads SocketCAN and gpsd directly. It is tier 1, and the broker is a single point of failure that raw capture should not share. When can-bridge exists, the recorder still owns raw capture; it subscribes to the bus only for decoded signals.

Session triggers

Trigger Starts on Ends on
auto the first CAN frame --idle (15 s) with no frames
manual ctl start [label] ctl stop, or shutdown
  • No traffic, no session. The PEAK attached at a desk, attached to a parked car with the bus asleep, or unplugged, produces nothing — the socket is retried every second in silence.
  • Cranking stays inside one session. A bus dropout or adapter re-enumeration shorter than the idle timeout is recorded as can_link events, not as a split.
  • Blips are discarded. An auto session with under --min-secs (3 s) of traffic and no marks is deleted at close — door unlocks and brief plug-ins.
  • ctl start during an auto session promotes it to manual. After ctl stop, auto start waits for the bus to go idle, so a stop sticks.
  • ctl auto off (or --no-auto) leaves only manual sessions.

The power service planned in orin-nano-env-setup (docs/impl/plans/POWER.md) is the intended second client: ctl mark crank for a recovered input loss, and systemctl stop yukart-recorder before poweroff.

Session layout

/var/lib/yukart/recordings/
  .last-seq                    high-water mark; numbers are never reused
  000042-20261009T163736/      sequence number orders; the local time is for humans
    session.json               status open → closed | truncated; trigger, label, host,
                               boot_id, start/end realtime + monotonic + clock sync,
                               frame and drop counts, end reason
    can-0001.log.zst           candump -L, closed chunks (5 min or 64 MB), zstd
    can-0002.log               the open chunk, plain
    gnss.ndjson                {"rx": <local receive time>, "msg": <gpsd report>}
    events.ndjson              marks, can_link, gnss_link, clock_step, drops, storage_bound

zstdcat can-*.log.zst can-*.log | canplayer replays a session.

Failsafes

Failure Behaviour
Power loss, crash, SIGKILL open chunk is fdatasync'd every second; at the next start the session is marked truncated and its chunks compressed
Crash mid-compression compression writes .zst.tmp then renames; the plain chunk is removed only after
Adapter unplugged, link down reopen every second; recorded as can_link events
gpsd down reconnect every 3 s; CAN capture unaffected
Writer slower than the bus 100 000-frame queue (~110 s); overflow counted as queue_drops
Kernel socket overflow SO_RXQ_OVFL counted as kernel_drops
Disk filling oldest closed sessions deleted to stay under --cap-gb (50) and above --min-free-gb (20); the open session is never deleted
Control socket fails logged; auto capture continues

Measured on the first capture: ~33 KB/s of candump text, ~5.5 KB/s after zstd — about 20 MB per hour of driving. The 50 GB default holds years of it; the cap is there for the failure where something else fills the disk.

Time

The car unit has no RTC battery yet and can start with a stale clock that chrony steps later. CAN frames carry the kernel's receive timestamp (SO_TIMESTAMP, CLOCK_REALTIME), and GNSS reports carry their local receive time — the same clock, so CAN and GNSS line up within a session however wrong that clock is.

To get absolute time back afterwards:

  • session.json records the kernel's sync state at start and end.
  • clock_step events record every step over 50 ms with its size.
  • Each TPV holds GNSS UTC beside its local receive time; their difference is the clock error, plus the SIM7600's NMEA latency (no PPS: expect tens to hundreds of milliseconds, constant enough to calibrate out).

At 1 Hz GNSS against ~890 frames/s, alignment to a few hundred milliseconds is what this gives. It is enough to match CAN speed against GNSS speed; it is not enough to time individual frames against position.

Open

  • Session marking from the app. app-flutter's mark button reaches the recorder through svc-gw, which will forward to the control socket.
  • Native builds. scripts/deploy-recorder.sh cross-compiles from the workstation while the development Orin is offline; check-glibc.sh gates it.
  • Copy-off. Recordings stay on the car until the parked-at-home step in the power plan, or a manual rsync.

orin-status

The Orin's own health as envelopes on yukart/host/<signal>, retained, once a second; names and units from contracts signals.json (source host). Tier 2, ordered after the broker only.

Group Source
Temperatures thermal zones cpu, gpu, soc* (hottest), tj; the cv* zones read EAGAIN while the vision accelerators are off and are skipped
Load and memory /proc/stat, the GPU's load (tenths of a percent), /proc/meminfo (one pool for CPU and GPU)
Power and fan onboard INA3221 VDD_IN; pwm_tach
Network /sys/class/net counters → kbit/s; WiFi level from /proc/net/wireless; the modem from mmcli -m any -J every 5 s on its own thread; the tablet link is carrier on usb0/usb1, counted on l4tbr0
UPS INA219 on /dev/i2c-7 0x41, below
  • A reading that fails is not published. Its retained value ages and svc-gw shows it stale.
  • Service state on yukart/system/orin-status: ready, or degraded with the failed sources; last will stopping.

UPS battery

  • Read-only. Only register pointers are ever written; the INA219 stays at its power-on configuration. Current is shunt voltage (10 µV per bit) over 0.01 Ω (Waveshare's demo code), positive when charging. Bus voltage is 4 mV per bit.
  • State: charging above +50 mA, discharging below −50 mA (on battery: input lost), idle between.
  • Charge is an estimate from a 21700 resting-voltage table per cell, the reading corrected by the pack's I·R drop (0.15 Ω, a rough figure) and smoothed. Waveshare's demo maps 9.0–12.6 V linearly instead, which swings with load. Steadiest at rest on input power, least reliable while charging.
  • The ignition-off policy (orin-nano-env-setup docs/impl/plans/POWER.md) needs the same readings. It should take them from here, or live here, rather than open the INA219 a second time.