From 34140cc666fc0973a8c599b8f66e2f428c2fbf1d Mon Sep 17 00:00:00 2001 From: Mikkeli Date: Tue, 13 Oct 2026 21:31:51 +0900 Subject: [PATCH] Decoded Z27AG signals: layouts in signals.json can blocks, the only source engine_rpm moves to source z27ag (no OBD-II on this car's CAN). Ten more: accelerator, brake, handbrake, indicators, side lights, dipped beam, doors, A/C compressor. Guideline 6: no service writes a CAN ID of its own. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01DDwfd6Ki8UfG4hK6W5YpJz --- README.md | 7 ++- docs/GUIDELINES.md | 12 +++- schema/signals.json | 150 +++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 163 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index cb678c3..b4ddaa4 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,14 @@ definitions from here. |---|---| | [`schema/topics.json`](schema/topics.json) | Topic namespace: payload kind, retention, rate, publisher | | [`schema/envelope.json`](schema/envelope.json) | JSON Schema for the decoded-signal envelope | -| [`schema/signals.json`](schema/signals.json) | Signal registry — the single source for name, unit, type and range | +| [`schema/signals.json`](schema/signals.json) | Signal registry — the single source for name, unit, type and range, and for where each decoded Z27AG signal sits in its CAN frame (`can` block) | | [`docs/BOUNDARY.md`](docs/BOUNDARY.md) | Display boundary protocol: what crosses between `svc-gw` and the app | | [`checklists/`](checklists/) | Recording checklists as data; the app bundles exact copies (its **Captures** page) | -The Z27AG DBC lands here too, once Phase 2 produces one. +Decoded Z27AG signals (source `z27ag`) carry their frame layout in `signals.json` itself, +so name, unit and bits cannot drift apart. A DBC for tools like SavvyCAN or cantools, +if one is wanted, is generated from it, never edited by hand. Findings behind each +layout: [`docs/impl/reports/`](docs/impl/reports/). - For implementation practice guidelines, refer to the [guidelines](docs/GUIDELINES.md); - For implementation plans and history, refer to [them](docs/impl/); diff --git a/docs/GUIDELINES.md b/docs/GUIDELINES.md index bbe3b08..e15308d 100644 --- a/docs/GUIDELINES.md +++ b/docs/GUIDELINES.md @@ -33,4 +33,14 @@ type: guidelines - There is no version in any topic or payload. A recorded session names the contracts tag it was captured under, which is what keeps old captures readable. -## 6. To be added +## 6. A decoded signal's layout lives only in its registry entry +- Every `source: z27ag` signal has a `can` block in `signals.json`: frame ID, byte, + bit or length, byte order, scale and offset. The decoder reads those blocks and + nothing else, so a correction lands in one file and every consumer sees it. +- ⚠ **No CAN ID or bit position is written in any service's code.** A layout + hard-coded downstream is a second source that silently disagrees the day the + registry is corrected. +- Each block names the report with its evidence (`evidence`). A layout without + evidence is a guess and does not go in. + +## 7. To be added diff --git a/schema/signals.json b/schema/signals.json index 076e46a..0e72b0d 100644 --- a/schema/signals.json +++ b/schema/signals.json @@ -1,15 +1,24 @@ { - "$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=z27ag is reverse-engineered and lives in the DBC. source=host is the Orin itself (orin-status), not the car.", + "$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.", "signals": { "engine_rpm": { "unit": "rpm", "type": "number", - "source": "obd2", + "source": "z27ag", "description": "Engine speed", "range": [ 0, 8000 - ] + ], + "can": { + "id": "0x210", + "byte": 4, + "length": 16, + "order": "big", + "scale": 1, + "offset": 0, + "evidence": "Z27AG-DECODE-1" + } }, "vehicle_speed_kph": { "unit": "kph", @@ -91,6 +100,141 @@ 656 ] }, + "accel_pedal_pct": { + "unit": "percent", + "type": "number", + "source": "z27ag", + "description": "Accelerator pedal position (0x00 at rest, 0xFA flat)", + "range": [ + 0, + 100 + ], + "can": { + "id": "0x210", + "byte": 0, + "length": 8, + "scale": 0.4, + "offset": 0, + "evidence": "Z27AG-DECODE-1" + } + }, + "brake_pedal": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Brake pedal switch: pressed", + "can": { + "id": "0x231", + "byte": 4, + "bit": 1, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "handbrake": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Handbrake applied", + "can": { + "id": "0x239", + "byte": 0, + "bit": 2, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "indicator_left": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Left indicator lamp, as it flashes (on and off at the flasher's rate)", + "can": { + "id": "0x423", + "byte": 2, + "bit": 6, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "indicator_right": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Right indicator lamp, as it flashes. Hazards were seen on the left bit only: open", + "can": { + "id": "0x423", + "byte": 2, + "bit": 5, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "side_lights": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Side (position) lights on; also on with dipped beam", + "can": { + "id": "0x423", + "byte": 2, + "bit": 2, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "dipped_beam": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Dipped headlights on", + "can": { + "id": "0x423", + "byte": 3, + "bit": 1, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "door_open": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Any door open", + "can": { + "id": "0x423", + "byte": 2, + "bit": 1, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "driver_door_open": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "Driver's door open", + "can": { + "id": "0x423", + "byte": 0, + "bit": 4, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, + "ac_compressor": { + "unit": "", + "type": "boolean", + "source": "z27ag", + "description": "A/C compressor engaged (engine running)", + "can": { + "id": "0x13D", + "byte": 6, + "bit": 7, + "length": 1, + "evidence": "Z27AG-DECODE-1" + } + }, "orin_cpu_temp_c": { "unit": "celsius", "type": "number",