shared/message_keys.json plus C and Kotlin codegen #7

Open
opened 2026-08-31 17:14:35 +02:00 by robert · 0 comments
robert commented 2026-08-31 17:14:35 +02:00 (Migrated from git.butzei.de)

Goal

Define the AppMessage contract once and generate both sides. The Android side cannot read package.json symbolic keys, so numeric keys must be fixed and shared.

Acceptance criteria

  • shared/message_keys.json defines every key with a fixed numeric id, direction and type
  • Generator emits watchapp/src/c/proto.h with the key constants
  • Generator emits a Kotlin object with matching constants for the companion
  • Generation wired into both builds, or a script plus a CI check that the checked-in output is current
  • Contract covers: SPEED_MMS, AVG_SPEED_MMS, DISTANCE_M, ELAPSED_S, SPEED_SOURCE, GPS_QUALITY, NAV_TURN, NAV_DIST_M, NAV_STREET, NAV_THEN_TURN, NAV_REMAIN_M, NAV_ETA_S, NAV_STATE, MAP_POLYLINE, MAP_HEADING, HR_BPM, CMD

Files

  • shared/message_keys.json
  • watchapp/src/c/proto.h
  • companion/.../pebble/Proto.kt

Notes

Speeds are mm/s and distances integer metres so the watch never needs floating point.

Update — 2026-08-31

The contract must also carry a protocol version field - see the dedicated issue. Add it here
rather than bolting it on later, since both generators need to emit it.

The contract must also carry CONFIG_PAGES — page descriptors sent from the phone to the watch,
so the display can be reconfigured without a new build. See docs/DESIGN.md section 1.

Update — 2026-09-01: the contract was missing half its keys

The AppMessage table in docs/PLAN.md predates D16 (cadence and power) and D23 (configurable pages).
It carries none of MAX_SPEED, AVG_HR, HR_ZONE, the power keys, the cadence keys or the lap keys,
and #61's CONFIG_PAGES message does not exist in it at all.

This issue lands in Phase 0. Generating both sides from a contract missing half its keys is
exactly the retrofit that #53 exists to make expensive.

The contract now lives in docs/PROTOCOL.md and that document is authoritative. PLAN.md's table
is marked as the superseded snapshot it is. Generate from PROTOCOL.md, not from PLAN.md (D28).

Added criteria:

  • shared/message_keys.json covers every key in docs/PROTOCOL.md sections 2 and 3
  • Each FieldId in #58 either has a wire key or is documented as watch-local (HR, CLOCK)
  • The reserved unavailable sentinel is defined per type, so -- is distinguishable from zero
  • CONFIG_PAGES and CONFIG_SEQ included, so #61 needs no contract change later
  • HR_SAMPLES batch format included (PROTOCOL.md section 5)
  • NAV_CUE_CONFIDENCE included, so a geometric guess is distinguishable from a matched turn
  • The update-rate policy in PROTOCOL.md section 4 is generated as constants, not left as prose
    — rates are part of the contract, not an implementation detail
  • Codegen fails loudly on a duplicate key id or a gap in the enum

Update — 2026-09-02: generate from the revised contract

docs/PROTOCOL.md changed materially on 2026-09-02 (D39, D43, D44, D46). Generate from the current
document, not from any earlier copy:

  • Per-type unavailable sentinels (0xFF / 0xFFFF / 0xFFFFFFFF / zero-length), excluded
    from every declared range — the old text gave one generic 0xFFFF… while SPEED_MMS
    simultaneously advertised the sentinel as a legal speed
  • STATE_ACK (6) and CMD_TIME (74) — command queueing across a dropout (D43)
  • SCREEN_W (75), SCREEN_H (76), SCREEN_SHAPE (77) — the phone was never told which display
    it was drawing for (NFR-C12)
  • NAV_REJOIN_BEARING (48), NAV_REJOIN_M (49) — off-route bearing arrow (D42)
  • Map keys renumbered and re-scoped: MAP_ANCHOR_LAT (50), MAP_ANCHOR_LON (51),
    MAP_POLYLINE (52), MAP_SEQ (53), MAP_CUES (54), MAP_POS (55), MAP_HEADING (56).
    MAP_SCALE_M is gone — scale is watch-local now (D39)
  • HR_SAMPLES carries (uint8 dt, uint8 bpm) pairs after a uint32 base_timestamp, not a
    fixed-cadence run (D46)
  • Group heartbeat intervals from PROTOCOL §4 available to both sides — staleness is measured
    per group, not per key (D44)
## Goal Define the AppMessage contract once and generate both sides. The Android side cannot read `package.json` symbolic keys, so numeric keys must be fixed and shared. ## Acceptance criteria - [ ] `shared/message_keys.json` defines every key with a fixed numeric id, direction and type - [ ] Generator emits `watchapp/src/c/proto.h` with the key constants - [ ] Generator emits a Kotlin object with matching constants for the companion - [ ] Generation wired into both builds, or a script plus a CI check that the checked-in output is current - [ ] Contract covers: SPEED_MMS, AVG_SPEED_MMS, DISTANCE_M, ELAPSED_S, SPEED_SOURCE, GPS_QUALITY, NAV_TURN, NAV_DIST_M, NAV_STREET, NAV_THEN_TURN, NAV_REMAIN_M, NAV_ETA_S, NAV_STATE, MAP_POLYLINE, MAP_HEADING, HR_BPM, CMD ## Files - `shared/message_keys.json` - `watchapp/src/c/proto.h` - `companion/.../pebble/Proto.kt` ## Notes Speeds are mm/s and distances integer metres so the watch never needs floating point. ## Update — 2026-08-31 The contract must also carry a **protocol version field** - see the dedicated issue. Add it here rather than bolting it on later, since both generators need to emit it. The contract must also carry **`CONFIG_PAGES`** — page descriptors sent from the phone to the watch, so the display can be reconfigured without a new build. See `docs/DESIGN.md` section 1. ## Update — 2026-09-01: the contract was missing half its keys The AppMessage table in `docs/PLAN.md` predates D16 (cadence and power) and D23 (configurable pages). It carries none of `MAX_SPEED`, `AVG_HR`, `HR_ZONE`, the power keys, the cadence keys or the lap keys, and #61's `CONFIG_PAGES` message does not exist in it at all. This issue lands in **Phase 0**. Generating both sides from a contract missing half its keys is exactly the retrofit that #53 exists to make expensive. **The contract now lives in `docs/PROTOCOL.md` and that document is authoritative.** PLAN.md's table is marked as the superseded snapshot it is. Generate from PROTOCOL.md, not from PLAN.md (D28). Added criteria: - [ ] `shared/message_keys.json` covers **every** key in docs/PROTOCOL.md sections 2 and 3 - [ ] Each `FieldId` in #58 either has a wire key or is documented as watch-local (`HR`, `CLOCK`) - [ ] The reserved unavailable sentinel is defined per type, so `--` is distinguishable from zero - [ ] `CONFIG_PAGES` and `CONFIG_SEQ` included, so #61 needs no contract change later - [ ] `HR_SAMPLES` batch format included (PROTOCOL.md section 5) - [ ] `NAV_CUE_CONFIDENCE` included, so a geometric guess is distinguishable from a matched turn - [ ] The **update-rate policy in PROTOCOL.md section 4 is generated as constants**, not left as prose — rates are part of the contract, not an implementation detail - [ ] Codegen fails loudly on a duplicate key id or a gap in the enum ## Update — 2026-09-02: generate from the revised contract `docs/PROTOCOL.md` changed materially on 2026-09-02 (D39, D43, D44, D46). Generate from the current document, not from any earlier copy: - [ ] **Per-type unavailable sentinels** (`0xFF` / `0xFFFF` / `0xFFFFFFFF` / zero-length), excluded from every declared range — the old text gave one generic `0xFFFF…` while `SPEED_MMS` simultaneously advertised the sentinel as a legal speed - [ ] `STATE_ACK` (6) and `CMD_TIME` (74) — command queueing across a dropout (D43) - [ ] `SCREEN_W` (75), `SCREEN_H` (76), `SCREEN_SHAPE` (77) — the phone was never told which display it was drawing for (NFR-C12) - [ ] `NAV_REJOIN_BEARING` (48), `NAV_REJOIN_M` (49) — off-route bearing arrow (D42) - [ ] Map keys renumbered and re-scoped: `MAP_ANCHOR_LAT` (50), `MAP_ANCHOR_LON` (51), `MAP_POLYLINE` (52), `MAP_SEQ` (53), `MAP_CUES` (54), `MAP_POS` (55), `MAP_HEADING` (56). `MAP_SCALE_M` is gone — scale is watch-local now (D39) - [ ] `HR_SAMPLES` carries `(uint8 dt, uint8 bpm)` pairs after a `uint32 base_timestamp`, **not** a fixed-cadence run (D46) - [ ] Group **heartbeat** intervals from PROTOCOL §4 available to both sides — staleness is measured per group, not per key (D44)
Sign in to join this conversation.
No description provided.