diff --git a/docs/BOUNDARY.md b/docs/BOUNDARY.md index faf0bf6..872f94c 100644 --- a/docs/BOUNDARY.md +++ b/docs/BOUNDARY.md @@ -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/+` and `yukart/gnss/+`. 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/` instead. - Any version beyond `proto: 0`. A breaking change bumps it; the client checks it. diff --git a/docs/GUIDELINES.md b/docs/GUIDELINES.md index e15308d..336ed92 100644 --- a/docs/GUIDELINES.md +++ b/docs/GUIDELINES.md @@ -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 diff --git a/schema/signals.json b/schema/signals.json index 0e72b0d..c4480c9 100644 --- a/schema/signals.json +++ b/schema/signals.json @@ -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/. 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": { + "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", @@ -477,6 +527,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 +568,72 @@ "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 + ] } } } diff --git a/schema/topics.json b/schema/topics.json index 032d55d..2974e96 100644 --- a/schema/topics.json +++ b/schema/topics.json @@ -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,13 @@ "publisher": "gnss", "description": "value is an object: lat, lon, alt_m, speed_kph, heading_deg, fix, satellites." }, + "yukart/gnss/": { + "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/host/": { "payload": "envelope", "retained": true,