nx_iot_clock_master.nx
buildroot/runtime/nx_iot_clock_master.nx
about
nx_iot_clock_master.nx -- single monotonic tick source for the hub.
Roadmap: NISHI_IOT_HUB_ROADMAP.md Epoch 4 (SYNCHRONIZE).
S-class invariants: S0 (bit-equal per tick) + S3 (atomic scene
transitions composed by callers) + S6 (no cloud) + S7 (sealed-enum
verdict on every operation).
Why this primitive exists (gap-analysis F1/F3 closure):
Every IoT incumbent that runs scenes lets each device animate off
its own local crystal -- Home Assistant fires a set-command then
the bulb's firmware times the fade. N crystals drift, N timers
trip at different real-times, scenes desync minute by minute.
We replace that with a single hub-side monotonic tick that every
device-driver advances against. No bulb runs an autonomous
animation; every frame is computed at the hub at tick T and the
command emitted before tick T+1. Per cardinal
feedback-honest-perf-verdict-no-aspirational-claims, this is
the structural difference that makes S3 deliverable -- not a
heuristic, an architecture.
What this primitive does today:
- holds a tick state (i64 monotonic counter + wall-ms anchor)
- advances ticks on demand against sys_now_ms()
- supports a pre-stage queue: callers register "at tick K, run
callback X with arg A" -- a fixed-capacity sorted insert
- drain_due() returns the IDs of every entry whose target_tick
is <= current; caller dispatches them in returned order
What it doesn't do yet (queued, separate slices):
- UDP multicast tick broadcast for multi-device sync -- queued
for nx_iot_scene_atomic.nx; this primitive is the on-hub
scheduler only
- per-device ack tracking for committed ticks -- belongs in
nx_iot_watchdog.nx
- tick-time NTP correction -- v2; v1 trusts sys_now_ms
Wire-up notes for the hub daemon:
The hub's main loop calls nx_iot_clock_tick(state) in a busy-poll
with a small sys_sleep_ms(33) at the bottom for 30 Hz cadence.
dependencies 0 imports · 1 importers
imports: none
imported by: nx_iot_clock_master_test.nx
structs
| 145 | struct IotClockState |
| 166 | struct IotClockEntry |
consts
| 111 | const NX_IOT_TICK_VERDICT_UNKNOWN: i64 = 0 |
| 112 | const NX_IOT_TICK_VERDICT_OK: i64 = 1 |
| 113 | const NX_IOT_TICK_VERDICT_NO_ADVANCE: i64 = 2 // same wall-ms as before |
| 114 | const NX_IOT_TICK_VERDICT_OVERFLOW: i64 = 3 // queue saturated |
| 115 | const NX_IOT_TICK_VERDICT_NOT_FOUND: i64 = 4 // cancel/peek miss |
| 116 | const NX_IOT_TICK_VERDICT_BAD_ARG: i64 = 5 |
| 117 | const NX_IOT_TICK_VERDICT_N: i64 = 6 |
| 132 | const NX_IOT_TICK_RATE_HZ: i64 = 30 |
| 133 | const NX_IOT_TICK_PERIOD_MS: i64 = 33 // 1000 / 30 rounded down |
functions
| 104 | func nx_iot_tick_min(a: i64, b: i64) -> i64 |
| 119 | func nx_iot_tick_verdict_is_valid(v: i64) -> i64 called by 1: main |
| 174 | func nx_iot_clock_init(state: *IotClockState, queue_cap: i64, called by 1: main |
| 193 | func nx_iot_clock_tick(state: *IotClockState, wall_ms_now: i64, called by 1: main |
| 213 | func nx_iot_clock_schedule(state: *IotClockState, queue: *IotClockEntry, called by 1: main |
| 276 | func nx_iot_clock_drain_due(state: *IotClockState, queue: *IotClockEntry, |
| 341 | func nx_iot_clock_cancel(state: *IotClockState, queue: *IotClockEntry, called by 1: main |
| 381 | func nx_iot_clock_peek_next(state: *IotClockState, queue: *IotClockEntry, called by 1: main |