Compare commits

...

8 Commits

Author SHA1 Message Date
mikkeli 642be2a5e1 Steering angle (0C0, signed 0.1°), main beam, front fogs; layouts can be signed; dipped beam doubted
/ crosscheck (push) Successful in 2s
Rust consumers (L4T) / Clippy, test, build, glibc for every service that compiles contracts in (push) Successful in 22s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-15 21:21:48 +09:00
mikkeli 0af2c956e4 Z27AG decode pass 2: vehicle speed, coolant, front wiper; wheel speeds little-endian; IMU checked against GNSS
/ crosscheck (push) Successful in 1s
Rust consumers (L4T) / Clippy, test, build, glibc for every service that compiles contracts in (push) Successful in 22s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-15 20:15:07 +09:00
mikkeli b52a7dcedb Checklist v3: IMU calibration (level twice, straight pull-aways and stops); yukart/raw/imu
/ crosscheck (push) Successful in 1s
Rust consumers (L4T) / Clippy, test, build, glibc for every service that compiles contracts in (push) Successful in 19s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-14 13:28:40 +09:00
mikkeli 4cbac36fbe IMU signals in the car's frame (attitude, gravity-free g, yaw rate) on yukart/imu/<signal>
/ crosscheck (push) Successful in 0s
Rust consumers (L4T) / Clippy, test, build, glibc for every service that compiles contracts in (push) Successful in 17s
The WT61's publisher owns mounting and gravity; displays get car-frame values.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-13 23:23:31 +09:00
mikkeli 5d186bfd5d Captures plan: OBD-II through the ELM on the K-line, obd_* names, what each poller state means
/ crosscheck (push) Successful in 1s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-13 22:57:31 +09:00
mikkeli 229d796cfd OBD-II signals renamed obd_*, plus timing, trims, O2; GNSS signals; elm_connected; reboot command
/ crosscheck (push) Successful in 1s
Rust consumers (L4T) / Clippy, test, build, glibc for every service that compiles contracts in (push) Failing after 6s
Decision 30: CAN feeds displays, OBD-II (K-line via the ELM) calibrates, so every
obd2 signal carries the obd_ prefix and the plain names are left for decoded ones.
yukart/gnss/<signal> gives displays the fix as single values; gnss/fix stays for
navigation. Boundary: reboot beside poweroff, same guards.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-13 22:50:03 +09:00
mikkeli 5feba8b354 Decode report: clutch and transmission not on CAN, stated once; no camera trigger plan
/ crosscheck (push) Successful in 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-13 21:48:07 +09:00
mikkeli 0eab0e660e Decode report: reverse rechecked, gear inference dropped for a recommended gear
/ crosscheck (push) Successful in 0s
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz
2026-10-13 21:44:52 +09:00
8 changed files with 563 additions and 113 deletions
+54 -1
View File
@@ -1,6 +1,6 @@
{
"id": "z27ag-captures",
"version": 2,
"version": 3,
"title": "Z27AG decoding captures",
"about": "docs/impl/plans/Z27AG-CAPTURES.md",
"mark_format": "<step> <action>[ #<n>] start|end for a span, <step> <action>[ #<n>] for a point",
@@ -447,6 +447,59 @@
]
}
]
},
{
"title": "IMU calibration (module fixed in its final place)",
"steps": [
{
"id": "27",
"title": "IMU level, facing one way",
"do": "Parked on the flattest ground at hand, engine off or idling, nobody moving inside. Start, keep still 15 s, end.",
"finds": "which way is down for the IMU (with step 28, cancelling the ground's slope)",
"actions": [
{
"label": "imu level A",
"hold_s": 15
}
]
},
{
"id": "28",
"title": "IMU level, turned round",
"do": "The same spot, nose the other way: a rough 180° will do. Still again, 15 s.",
"finds": "the second half of the level: the slope's tilt reverses, the mounting's does not",
"actions": [
{
"label": "imu level B",
"hold_s": 15
}
]
},
{
"id": "29",
"title": "IMU pull-away, straight",
"do": "A straight, flat section. From standstill, a firm pull-away in 1st to about 30. Start as the clutch bites, end as you lift.",
"finds": "which way is forward for the IMU",
"actions": [
{
"label": "imu pull away",
"repeat": 3
}
]
},
{
"id": "30",
"title": "IMU braking, straight",
"do": "The same section. From about 40, a firm (not emergency) stop. Start as you brake, end when stopped.",
"finds": "forward again, from the other side; also braking g",
"actions": [
{
"label": "imu brake",
"repeat": 3
}
]
}
]
}
]
}
+9 -7
View File
@@ -37,7 +37,7 @@ The two channels from decision 24 share the socket but keep different guarantees
| `ack` | reply to a `cmd` | `id`, `ok`, `detail`, `dup` (true when answered from the cache) |
With `source: bus`, `signals` is the newest envelope of every signal svc-gw has
seen on `yukart/vehicle/+` and `yukart/host/+`. svc-gw marks one
seen on `yukart/vehicle/+`, `yukart/host/+`, `yukart/gnss/+` and `yukart/imu/+`. svc-gw marks one
`quality: "stale"` when it has not been refreshed for 5 s, or is known only from
the broker's retained copy (which may predate a reboot). A signal once seen is
never removed.
@@ -66,18 +66,20 @@ tablet, or an Orin behind a replugged hub, looks connected at the link layer.
|---|---|---|
| `ping` | — | `ack` with `detail: "pong"` |
| `mark` | `text`, `delay_ms` (optional: how long the command waited before this send) | stamped into the open recording; fails with no session open |
| `poweroff` | `confirm: "poweroff"`, `boot_id` (from this connection's `hello`) | acks, then shuts the host down cleanly (`systemctl poweroff`) about 2 s later. Refused unless svc-gw runs with `--allow-poweroff` (the Orin's unit only), and when `boot_id` is not the current boot's |
| `poweroff` | `confirm: "poweroff"`, `boot_id` (from this connection's `hello`) | acks, then shuts the host down cleanly (`systemctl poweroff`) about 2 s later. Refused unless svc-gw runs with `--allow-power` (the Orin's unit only), and when `boot_id` is not the current boot's |
| `reboot` | `confirm: "reboot"`, `boot_id` | the same, with `systemctl reboot`; the same flag and refusals |
A repeated `id` returns the original `ack` (last 256 kept, shared across
connections) and never re-runs the command.
`poweroff` is the exception to retry-until-acked: the ack cache does not survive
the reboot it causes, so a resend could shut the next boot down too. The client
sends it once and does not resend it; `boot_id` makes svc-gw refuse one from a
previous boot regardless.
`poweroff` and `reboot` are the exception to retry-until-acked: the ack cache does
not survive the restart they cause, so a resend could shut the next boot down (or
reboot it again). The client sends them once and does not resend; `boot_id` makes
svc-gw refuse one from a previous boot regardless.
## Not yet
- TTS utterances (id, chunk sequence, terminator, expiry) — when there is a TTS.
- `gnss`: its fix is an object, which the envelope's `value` does not allow yet.
- `gnss/fix` as one object (navigation's input): the envelope's `value` does not allow
one yet. Displays get the fix as single values on `yukart/gnss/<signal>` instead.
- Any version beyond `proto: 0`. A breaking change bumps it; the client checks it.
+1 -1
View File
@@ -9,7 +9,7 @@ type: guidelines
- ⚠ **The unit appears in both the topic name and the payload, so they can
contradict each other.** Generating both from one entry is what stops that; a
hand-written `"unit"` reintroduces the failure it was meant to prevent.
- Signal names carry their unit (`vehicle_speed_kph`, `coolant_temp_c`), so a unit
- Signal names carry their unit (`gnss_speed_kph`, `obd_coolant_temp_c`), so a unit
mismatch is visible at the call site rather than one registry lookup away.
## 2. Two clocks, and they answer different questions
+38 -15
View File
@@ -1,7 +1,7 @@
---
type: plan
status: ready
updated: 2026-10-13
updated: 2026-10-14
---
# Z27AG decoding captures: what to do in the car
@@ -13,10 +13,11 @@ ground truth. Prior art and first candidates: [the prior-art report](../reports/
| In the session directory | From | Use |
|---|---|---|
| `can-*.log(.zst)` | the PEAK, every frame, kernel timestamps | what gets decoded; also holds every OBD request (`7DF`) and reply (`7E8`) |
| `vehicle.ndjson` | `obd-poller` via the bus | the same OBD replies as values (rpm, speed, throttle, load, MAP, MAF ~10 Hz; coolant, intake air, fuel 1 Hz); `env.timestamp` equals the reply frame's timestamp |
| `can-*.log(.zst)` | the PEAK, every frame, kernel timestamps | what gets decoded (nothing transmits on CAN: OBD-II is on the K-line) |
| `vehicle.ndjson` | `decoder` and `obd-poller` via the bus | decoded CAN signals, and the `obd_*` references from the K-line through the ELM327 (rpm, speed, throttle, load, MAF, timing, O2 every round; coolant, intake air, trims every 5 s); `env.timestamp` is the reply's arrival at the Orin |
| `events.ndjson` | marks from the app | when each step happened |
| `gnss.ndjson` | gpsd (SIM7600) | speed and position for the drive |
| `imu.ndjson` | `imu` via the bus (`yukart/raw/imu`) | every IMU sample, raw module axes, Orin receive time: calibration (27–30) and, once calibrated, motion for the drive |
## Operating it from the app
@@ -33,8 +34,9 @@ ground truth. Prior art and first candidates: [the prior-art report](../reports/
session` when there is no traffic yet (ignition on, PEAK plugged). A mark sent with
the link down waits (`unacked`) and goes when it is back, with its delay noted.
- The **Mark** button on the main screen is still there for anything off-plan.
- **OBD is working** when `engine_rpm`, `vehicle_speed_kph` and the rest appear in
the signal list and move. If they never appear, see *If OBD stays silent*.
- **OBD is working** when `obd_engine_rpm`, `obd_coolant_temp_c` and the rest appear
in the signal list and move (the ELM on the OBD port, through the Y harness beside
the PEAK). If they never appear, see *If OBD stays silent*.
## The steps
@@ -52,14 +54,20 @@ recorder refuses marks with "no session" otherwise); engine off, doors shut.
- **Engine running, parked** (14–18): start and idle, held revs in neutral (no OBD
on this car's CAN: the revs are approximate, read off the tacho), blips, A/C load, radiator fan.
- **Driving** (19–22): steady speeds, stops, corners, each gear. A passenger presses,
or skip the marks: GNSS speed is recorded continuously, and gear comes from
rpm ÷ speed.
or skip the marks: GNSS speed is recorded continuously. The gear is not on CAN and
is not inferred (decode report).
- **On jack stands** (23–26, since v2): front wheels up, rears chocked, handbrake
off. Unpowered steering; speedometer readings held in 2nd and 3rd (fronts turn,
rears stay still, which tells the wheels apart); braking from speed with vacuum
assist; reverse. Stands replace the driving speeds where a drive is not practical.
- **IMU calibration** (27–30, since v3): once the IMU is fixed in its final place. Level
twice on the same spot, facing one way and then turned round (a rough 180° is fine:
the ground's slope reverses and cancels, the mounting's tilt does not); then three
firm pull-aways and three firm stops on a straight, flat section (forward, signed by
which kind of stretch it is). Any session holds raw IMU samples (`imu.ndjson`), so
these can go in their own short session or with anything else.
Version 2 (2026-10-13) adds the stands section and writes down the conditions the
Version 3 (2026-10-14) adds the IMU calibration section. Version 2 (2026-10-13) adds the stands section and writes down the conditions the
first run showed matter: engine off and handbrake on for 1–13, foot brake held for
the handbrake step, neutral with the clutch up for the revs.
@@ -80,18 +88,33 @@ The parked steps can be split over several sessions; the drive is the important
## If OBD stays silent
`yukart/system/obd-poller` says why (visible in `mosquitto_sub -v -t 'yukart/system/#'`
on the Orin):
on the Orin), and the signal list's `elm_connected` shows the Bluetooth link:
- `bus idle: not polling`: no traffic: ignition off or PEAK unplugged.
- `no ECU answers 01 00`: the engine ECU does not do standard mode 01 on this bus.
Then OBDb's MUT-III (`21 xx`) requests are the next thing to try.
- `ELM …: … (unplugged, asleep or out of range)`: no Bluetooth link. The ELM in the
port? Its light on? A phone app holding it (it takes one connection)? Retried
every minute.
- `ECU not answering (…): ignition off?`: the ELM is there, the car is not. The
adapter's own words are in the brackets (`BUS INIT: ...ERROR`, `NO DATA`).
- `ECU silent for 60 s: adapter released`: ignition off for a minute; the poller let
go so the ELM can sleep, and tries again every minute.
- To see what answers, on the Orin: `sudo systemctl stop yukart-obd-poller`,
`yukart-obd-poller --once` (supported PIDs and one value each, with reply times),
`sudo systemctl start yukart-obd-poller`. Never two pollers at once: their
replies would cross.
`yukart-obd-poller --elm $(. /etc/default/yukart-elm; echo $ELM_ADDR) --once`
(adapter, protocol, supported PIDs and one value each, with reply times), then
`sudo systemctl start yukart-obd-poller`.
## Afterwards
**IMU calibration**, on the car Orin, from the session that holds steps 27–30:
```bash
yukart-imu --calibrate /var/lib/yukart/recordings/<session> # print: down, forward, slope, spread
yukart-imu --calibrate /var/lib/yukart/recordings/<session> --write # and save /var/lib/yukart/imu/mount.json
```
The service picks the new mounting up within seconds; roll and pitch should then read
about 0 parked on level ground. Steps 27–28 alone also work: forward then stays the
`--forward` axis, laid flat, until a session with 29–30 is calibrated.
Sessions are in `/var/lib/yukart/recordings/` on the car Orin. Copy one off with
`rsync -a mikkeli-orin-nano:/var/lib/yukart/recordings/<session>/ …` into
`yukart-data`, with a line in its README of what was done.
+9 -4
View File
@@ -46,10 +46,15 @@ jack-stands session and a GNSS-matched drive settle both the byte order and the
## Not on this bus (or not seen)
- **Clutch pedal:** nothing changed in 3 presses, engine off.
- **Gear position:** nothing in 5 gears, reverse or neutral. Expected for a manual. The
gear can be computed from rpm ÷ wheel speed once the scale is known.
- **Reverse:** no reverse bit found. The reverse lamp is probably wired directly.
- **Clutch and transmission are not on this bus.** Nothing changed for the clutch
(3 presses), any of the 5 gears, neutral or reverse: nothing on the car consumes
them, so no frame carries them. Reverse was rechecked against both reverse
stretches (engine off and engine on) versus every other position: no bit
separates them.
- **Gear is not inferred from rpm ÷ wheel speed** (owner's call, 2026-10-13): the
clutch is invisible, so the ratio lies whenever it is down or slipping, and the
gear matters most at a standstill, where the ratio says nothing. A *recommended*
gear from speed and rpm is the useful form, if any.
- **Steering angle:** not analysed yet (step 5 was done with the engine on, at the end).
## Not analysed yet
+78
View File
@@ -0,0 +1,78 @@
---
type: report
date: 2026-10-15
---
# Z27AG CAN: second pass, against OBD-II and GNSS
Sessions `000007-20261015T192938` and `000008-20261015T200202` (car Orin): doors, tailgate,
bonnet and wipers (checklist step 10–11), then a short drive with GNSS (3D fix, helix antenna)
and OBD-II through the ELM327 on the K-line, all on the Orin's clock. The first time CAN, OBD-II
and GNSS were recorded together. About 30 minutes; 1190 OBD-II samples of each fast value.
## Confirmed
| Signal | Where | Evidence | Registered as |
|---|---|---|---|
| Engine speed | `210` bytes 4–5, BE, 1 rpm (pass 1) | equal to OBD-II `0C`: median ratio 1.0000, median difference 4 rpm, 1190 samples | `engine_rpm` (unchanged) |
| Coolant temperature | `449` byte 2, −40 °C | equal to OBD-II `05` within ±1 °C (median 0.0) over 86–97 °C; no other byte tracks it | `coolant_temp_c` |
| Vehicle speed | `231` bytes 6–7, **little-endian**, **0.0577 km/h per count** | steady cruising (> 15 km/h, GNSS change < 1 km/h over 2 s, 117 fixes): spread 2.6 % against GNSS | `vehicle_speed_kph` |
| Four wheel speeds | `235` four words, **little-endian** (pass 1 left the byte order open) | big-endian misses by 28 km/h rms; little-endian fits. Words 0, 1: 0.0578; words 2, 3: 0.0581 km/h per count against GNSS | not yet: which word is which wheel waits for the jack-stands run (fronts turn, rears stay) |
Scales are against **GNSS**, so they include this car's tyres today. **OBD-II speed reads 3 % low**
against GNSS (0.9706): the ECU's figure is no better a reference than the ABS frames. The two
word pairs of `235` differ by 0.5 %, the usual size of a front/rear tyre difference.
## Doors, tailgate, bonnet, wipers
| Thing | Bit | Notes |
|---|---|---|
| Driver's door | `423.0.4` | its own bit (pass 1); also sets any-door |
| Passenger, rear left, rear right | only `423.2.1` (any door) | no bit of their own |
| Tailgate | only `423.2.1` | with the driver's door open, `423.0.4` cleared on closing it while `423.2.1` stayed set until the tailgate shut |
| Bonnet | nothing | a mechanical latch with no switch, as expected |
| Front wiper running | `423.3.3` | set through "low" and "high"; nothing in a 4 s "interval" window. Medium confidence; also explains pass 1's unexplained `423.3.3` stretch |
## IMU calibration, checked against GNSS
Level from session `000006` (two still stretches, the ground's 1.9° slope cancelled), forward from
`000008` (three stops, one launch; 3512 samples, mean spread 12.7°). The module sits upside down,
about 24° off level, turned so its X axis points forward. Calibrated, the marked stretches read:
| Stretch | IMU longitudinal (0.25 s mean, peak) | GNSS |
|---|---|---|
| Stop, 48 → 0 km/h | −0.35 g | −0.28 g |
| Stop, 45 → 0 | −0.17 g | −0.17 g |
| Stop, 40 → 0 | −0.21 g | −0.18 g |
| Launch | +0.32 g | +0.36 g |
Lateral stays at about 0 in all four: forward is straight ahead. GNSS reports about once a second,
which smooths short peaks.
## Session 000009: lights at night, corners
`z27ag-20261015-203117-lights-corners-drive`: step 9 at night, then a 35-minute errand drive
(no OBD-II: the obd-poller was off after the freeze). References: GNSS and the calibrated IMU.
| Signal | Where | Evidence | Registered as |
|---|---|---|---|
| Main beam | `423.2.7` | set exactly through the marked stretch; also fired once with the other lights off: flash-to-pass | `main_beam` |
| Front fog lamps | `423.2.4` | set exactly through the marked stretch (lights on) | `front_fogs` |
| **Steering wheel angle** | `0C0` bytes 0–1, little-endian, **signed**, **0.1° per count**, positive left | correlation **+0.995** with how sharply the car turns (IMU yaw rate ÷ speed), 1021 s of driving. Lock to lock −479…+471°; with a 2.5 m wheelbase that is a 16.3:1 steering ratio, typical. Straight driving reads −2.7° (camber or alignment) | `steering_angle_deg` (the layout format gained `signed`) |
| Dipped beam? | `423.3.1` | clean in daylight on the 13th; at night it flickers on for 0.1–0.2 s at each switch movement. The cluster has **no dipped-beam lamp** (only side lights, main beam and fogs, no rear fogs), and `423` matches the cluster's lamps, so this bit is probably something else that moves with the lights (illumination, dimming). A walk through Off → Auto → Dim → Main settles it | `dipped_beam` kept, description says so |
**IMU, lateral axis:** lateral g against speed × yaw rate gives a slope of **−0.998** (correlation
−0.91; the sign is right: yaw counts a left turn positive, lateral g counts right positive). With the
stops above, both horizontal axes of the calibration are confirmed.
`149` bytes 1–2 is not a yaw-rate sensor (correlation 0.02 with the IMU's yaw rate).
## Not found
- **Intake air temperature:** no byte of `449` tracks OBD-II `0F` (29–51 °C here).
- `449` bytes 0, 1, 3 move like temperatures but match nothing on OBD-II.
## Notes on the recording
- **The Orin froze mid-drive** (19:59; hardware watchdog reset at 20:01:47), splitting the drive
over two sessions. orin-nano-env-setup has the investigation.
- OBD-II through the ELM gave each fast value about every 2 s (K-line, ~250 ms per request).
+352 -84
View File
@@ -1,6 +1,136 @@
{
"$comment": "Single source for signal identity. Topic name and envelope unit are both derived from these entries and are never authored separately. source=obd2 is standardised; source=host is the Orin itself (orin-status), not the car. source=z27ag is reverse-engineered from the car's own broadcast frames, and its 'can' block is the only place its layout is written down: id (11-bit, hex string), byte (first data byte, 0-7), length in bits (1, 8 or 16), bit (length 1 only: 0 = LSB of that byte), order (length 16: big = byte first is most significant, little = the reverse), scale and offset (value = raw * scale + offset; booleans are raw != 0). 'evidence' names the contracts report that found it.",
"$comment": "Single source for signal identity. Topic name and envelope unit are both derived from these entries and are never authored separately. source=obd2 is standardised (OBD-II mode 01 over the K-line, through the ELM327): calibration references, every name prefixed obd_ (project-yukart decision 30); source=host is the Orin itself (orin-status), not the car. source=gnss is the receiver (svc-core gnss), on yukart/gnss/<signal>. source=imu is the WT61 IMU, already turned into the car's frame (mounting and gravity handled by its publisher), on yukart/imu/<signal>. source=z27ag is reverse-engineered from the car's own broadcast frames, and its 'can' block is the only place its layout is written down: id (11-bit, hex string), byte (first data byte, 0-7), length in bits (1, 8 or 16), bit (length 1 only: 0 = LSB of that byte), order (length 16: big = byte first is most significant, little = the reverse), signed (lengths 8 and 16: two's complement; default unsigned), scale and offset (value = raw * scale + offset; booleans are raw != 0). 'evidence' names the contracts report that found it.",
"signals": {
"obd_vehicle_speed_kph": {
"unit": "kph",
"type": "number",
"source": "obd2",
"description": "Road speed",
"range": [
0,
255
]
},
"obd_coolant_temp_c": {
"unit": "celsius",
"type": "number",
"source": "obd2",
"description": "Engine coolant temperature",
"range": [
-40,
215
]
},
"obd_intake_air_temp_c": {
"unit": "celsius",
"type": "number",
"source": "obd2",
"description": "Intake air temperature",
"range": [
-40,
215
]
},
"obd_throttle_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Throttle position",
"range": [
0,
100
]
},
"obd_engine_load_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Calculated engine load",
"range": [
0,
100
]
},
"obd_fuel_level_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Fuel tank level",
"range": [
0,
100
]
},
"obd_intake_pressure_kpa": {
"unit": "kpa",
"type": "number",
"source": "obd2",
"description": "Intake manifold absolute pressure",
"range": [
0,
255
]
},
"obd_maf_gps": {
"unit": "g/s",
"type": "number",
"source": "obd2",
"description": "Mass air flow rate",
"range": [
0,
656
]
},
"obd_engine_rpm": {
"unit": "rpm",
"type": "number",
"source": "obd2",
"description": "Engine speed (OBD-II 0C): the reference for the decoded engine_rpm",
"range": [
0,
8000
]
},
"obd_timing_advance_deg": {
"unit": "degree",
"type": "number",
"source": "obd2",
"description": "Ignition timing advance before TDC, cylinder 1",
"range": [
-64,
64
]
},
"obd_stft_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Short-term fuel trim, bank 1: positive adds fuel",
"range": [
-100,
100
]
},
"obd_ltft_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Long-term fuel trim, bank 1: positive adds fuel",
"range": [
-100,
100
]
},
"obd_o2_b1s1_v": {
"unit": "V",
"type": "number",
"source": "obd2",
"description": "Upstream oxygen sensor voltage (narrowband: ~0.1 lean … ~0.9 rich)",
"range": [
0,
1.275
]
},
"engine_rpm": {
"unit": "rpm",
"type": "number",
@@ -20,86 +150,6 @@
"evidence": "Z27AG-DECODE-1"
}
},
"vehicle_speed_kph": {
"unit": "kph",
"type": "number",
"source": "obd2",
"description": "Road speed",
"range": [
0,
255
]
},
"coolant_temp_c": {
"unit": "celsius",
"type": "number",
"source": "obd2",
"description": "Engine coolant temperature",
"range": [
-40,
215
]
},
"intake_air_temp_c": {
"unit": "celsius",
"type": "number",
"source": "obd2",
"description": "Intake air temperature",
"range": [
-40,
215
]
},
"throttle_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Throttle position",
"range": [
0,
100
]
},
"engine_load_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Calculated engine load",
"range": [
0,
100
]
},
"fuel_level_pct": {
"unit": "percent",
"type": "number",
"source": "obd2",
"description": "Fuel tank level",
"range": [
0,
100
]
},
"intake_pressure_kpa": {
"unit": "kpa",
"type": "number",
"source": "obd2",
"description": "Intake manifold absolute pressure",
"range": [
0,
255
]
},
"maf_gps": {
"unit": "g/s",
"type": "number",
"source": "obd2",
"description": "Mass air flow rate",
"range": [
0,
656
]
},
"accel_pedal_pct": {
"unit": "percent",
"type": "number",
@@ -118,6 +168,26 @@
"evidence": "Z27AG-DECODE-1"
}
},
"steering_angle_deg": {
"unit": "degree",
"type": "number",
"source": "z27ag",
"description": "Steering wheel angle: positive to the left. About -2.7° driving straight; lock to lock about ±475° (ratio about 16:1)",
"range": [
-600,
600
],
"can": {
"id": "0x0C0",
"byte": 0,
"length": 16,
"order": "little",
"signed": true,
"scale": 0.1,
"offset": 0,
"evidence": "Z27AG-DECODE-2"
}
},
"brake_pedal": {
"unit": "",
"type": "boolean",
@@ -187,7 +257,7 @@
"unit": "",
"type": "boolean",
"source": "z27ag",
"description": "Dipped headlights on",
"description": "Probably not dipped beam: clean in daylight (2026-10-13) but flickering at each switch movement at night (2026-10-15), and the Z27AG's cluster has no dipped-beam lamp (side lights, main beam and fogs only), so frame 423, the cluster's status, has little reason to carry it. Kept under this name until a light-switch walk shows what it is",
"can": {
"id": "0x423",
"byte": 3,
@@ -196,11 +266,37 @@
"evidence": "Z27AG-DECODE-1"
}
},
"main_beam": {
"unit": "",
"type": "boolean",
"source": "z27ag",
"description": "Main beam lamp on, flash-to-pass included",
"can": {
"id": "0x423",
"byte": 2,
"bit": 7,
"length": 1,
"evidence": "Z27AG-DECODE-2"
}
},
"front_fogs": {
"unit": "",
"type": "boolean",
"source": "z27ag",
"description": "Front fog lamps on (they need side lights or dipped beam)",
"can": {
"id": "0x423",
"byte": 2,
"bit": 4,
"length": 1,
"evidence": "Z27AG-DECODE-2"
}
},
"door_open": {
"unit": "",
"type": "boolean",
"source": "z27ag",
"description": "Any door open",
"description": "Any door open, the tailgate included (the bonnet has no switch)",
"can": {
"id": "0x423",
"byte": 2,
@@ -213,7 +309,7 @@
"unit": "",
"type": "boolean",
"source": "z27ag",
"description": "Driver's door open",
"description": "Driver's door open: the only door with its own bit",
"can": {
"id": "0x423",
"byte": 0,
@@ -235,6 +331,56 @@
"evidence": "Z27AG-DECODE-1"
}
},
"vehicle_speed_kph": {
"unit": "kph",
"type": "number",
"source": "z27ag",
"description": "Road speed from the ABS (CAN), scaled against GNSS on this car's tyres: OBD-II speed reads 3 % lower",
"range": [
0,
250
],
"can": {
"id": "0x231",
"byte": 6,
"length": 16,
"order": "little",
"scale": 0.0577,
"offset": 0,
"evidence": "Z27AG-DECODE-2"
}
},
"coolant_temp_c": {
"unit": "celsius",
"type": "number",
"source": "z27ag",
"description": "Engine coolant temperature (CAN): equal to OBD-II's within 1 °C over 86-97 °C",
"range": [
-40,
215
],
"can": {
"id": "0x449",
"byte": 2,
"length": 8,
"scale": 1,
"offset": -40,
"evidence": "Z27AG-DECODE-2"
}
},
"front_wiper": {
"unit": "",
"type": "boolean",
"source": "z27ag",
"description": "Front wiper running (low or high; interval not seen in a 4 s window). Medium confidence",
"can": {
"id": "0x423",
"byte": 3,
"bit": 3,
"length": 1,
"evidence": "Z27AG-DECODE-2"
}
},
"orin_cpu_temp_c": {
"unit": "celsius",
"type": "number",
@@ -477,6 +623,12 @@
10000
]
},
"elm_connected": {
"unit": "",
"type": "boolean",
"source": "host",
"description": "Bluetooth link to the OBD-II adapter (the ELM327 on the K-line) is up: obd-poller holds it while the ECU answers"
},
"ups_battery_v": {
"unit": "V",
"type": "number",
@@ -512,6 +664,122 @@
"type": "string",
"source": "host",
"description": "charging, discharging (running on battery) or idle (on input power, not charging)"
},
"gnss_fix": {
"unit": "",
"type": "string",
"source": "gnss",
"description": "Fix quality: none, 2d or 3d (gpsd TPV mode)"
},
"gnss_speed_kph": {
"unit": "kph",
"type": "number",
"source": "gnss",
"description": "Speed over ground; null without a fix",
"range": [
0,
300
]
},
"gnss_heading_deg": {
"unit": "degree",
"type": "number",
"source": "gnss",
"description": "Course over ground, true north; null without a fix or at a standstill",
"range": [
0,
360
]
},
"gnss_lat": {
"unit": "degree",
"type": "number",
"source": "gnss",
"description": "Latitude, WGS84",
"range": [
-90,
90
]
},
"gnss_lon": {
"unit": "degree",
"type": "number",
"source": "gnss",
"description": "Longitude, WGS84",
"range": [
-180,
180
]
},
"gnss_alt_m": {
"unit": "m",
"type": "number",
"source": "gnss",
"description": "Altitude above mean sea level; null without a 3D fix",
"range": [
-500,
9000
]
},
"gnss_satellites": {
"unit": "",
"type": "number",
"source": "gnss",
"description": "Satellites used in the fix",
"range": [
0,
64
]
},
"imu_roll_deg": {
"unit": "degree",
"type": "number",
"source": "imu",
"description": "Body roll in the car's frame: positive leaning right (right side down)",
"range": [
-180,
180
]
},
"imu_pitch_deg": {
"unit": "degree",
"type": "number",
"source": "imu",
"description": "Body pitch in the car's frame: positive nose up",
"range": [
-90,
90
]
},
"imu_accel_lon_g": {
"unit": "g",
"type": "number",
"source": "imu",
"description": "Longitudinal acceleration in the car's frame, gravity removed: positive forward (speeding up), negative braking",
"range": [
-4,
4
]
},
"imu_accel_lat_g": {
"unit": "g",
"type": "number",
"source": "imu",
"description": "Lateral acceleration in the car's frame, gravity removed: positive to the right (a right turn reads positive)",
"range": [
-4,
4
]
},
"imu_yaw_rate_dps": {
"unit": "degree/s",
"type": "number",
"source": "imu",
"description": "Yaw rate: positive turning left (counter-clockwise from above)",
"range": [
-500,
500
]
}
}
}
+22 -1
View File
@@ -12,7 +12,7 @@
"payload": "envelope",
"retained": true,
"rate": "1-50/s",
"publisher": "decoder, obd-poller",
"publisher": "decoder (source z27ag), obd-poller (source obd2)",
"description": "One topic per signal, named exactly as in signals.json."
},
"yukart/gnss/fix": {
@@ -22,6 +22,27 @@
"publisher": "gnss",
"description": "value is an object: lat, lon, alt_m, speed_kph, heading_deg, fix, satellites."
},
"yukart/gnss/<signal>": {
"payload": "envelope",
"retained": true,
"rate": "1-10/s",
"publisher": "gnss",
"description": "The receiver's fix as single values, one topic per signal, named exactly as in signals.json (source=gnss). For displays; yukart/gnss/fix stays the composite for navigation."
},
"yukart/imu/<signal>": {
"payload": "envelope",
"retained": true,
"rate": "10-50/s",
"publisher": "imu",
"description": "The IMU (WT61) in the car's frame: attitude, gravity-free acceleration, yaw rate; one topic per signal, named exactly as in signals.json (source=imu)."
},
"yukart/raw/imu": {
"payload": "json",
"retained": false,
"rate": "10-100/s",
"publisher": "imu",
"description": "Every IMU sample, raw: module axes, not calibrated. {mono (ns, CLOCK_MONOTONIC), t (µs, realtime), accel [x,y,z] g, gyro [x,y,z] deg/s, angle [roll,pitch,yaw] deg (the module's own)}. Not an envelope; for the recorder (imu.ndjson), so a later calibration can reprocess a session. Displays use yukart/imu/<signal>."
},
"yukart/host/<signal>": {
"payload": "envelope",
"retained": true,