nx_iot_scene_sync.nx
buildroot/runtime/nx_iot_scene_sync.nx
about
nx_iot_scene_sync.nx -- the "lights stay in sync, self-fixing" organ.
Roadmap: NISHI_IOT_HUB_ROADMAP.md Epoch 4 (SYNCHRONIZE). Closes the
operator's ORIGINAL complaint -- "the lights blink and desync all the
time" -- as a REPEATABLE, SELF-FIXING loop, not a one-off.
THE DESYNC PROBLEM (operator, 2026-05-16 + 2026-06-20 "i still want the
lights to sync that are blinking as a repeatable self fixing thing"):
A scene (all lights = same colour/brightness/on) drifts apart because
(a) lights get the command at slightly different times (skew), and
(b) a light that drops WiFi + re-pairs comes back at its DEFAULT state
-- out of sync with the rest -- and stays wrong until a human re-sets it.
THE FIX (two layers, both already in the hub):
1. PREVENT skew: apply a scene ATOMICALLY -- all lights at the same
tick (nx_iot_clock_master drains a scene all-or-nothing). The
daemon wires that; this organ holds the SINGLE SOURCE OF TRUTH for
what the scene SHOULD be.
2. SELF-FIX drift: this organ tracks, per device, which scene EPOCH it
last applied. A device that dropped + healed (nx_iot_watchdog ->
re-provision -> nx_iot_anchor re-adopt) is marked desynced; the loop
re-applies the current desired scene to it -> back in sync. Every
heal -> auto re-sync. REPEATABLE: each drop is fixed the same way,
and the MEASURED `syncs_restored` counts the manual re-sets spared.
WHAT THIS ORGAN IS: the pure SYNC-STATE machine. desired scene + per
device applied-epoch + convergence (all_in_sync / needs_resync /
resync). No network, no device writes -- the driver (kasa pt_light
etc., already brick-safe) applies the computed scene; the clock master
makes it atomic; the watchdog triggers the heal. Pure-buffer, so it
gate-proves offline + is portable. Single-responsibility (#9): it owns
"are the lights in sync, and what brings a stray one back".
MEASURED-EXCEED (feedback-no-wave-measured-exceed; mirrors anchor's
duplicates_prevented + watchdog's recover_count): `syncs_restored` is
the count of times a drifted light was auto-pulled back to the scene --
the manual re-sets the operator did NOT have to do. The gate asserts it.
genealogy_id: NISHI_IOT_HUB_ROADMAP.md Epoch 4 + project-iot-hub-multi-vendor-kickoff-2026-05-16
license_tier: ORIGINAL
dependencies 0 imports · 1 importers
imports: none
imported by: nx_iot_scene_sync_test.nx
structs
| 83 | struct IotSceneReg |
consts
| 60 | const NX_IOT_SYNC_UNKNOWN: i64 = 0 |
| 61 | const NX_IOT_SYNC_OK: i64 = 1 // device matches the desired scene |
| 62 | const NX_IOT_SYNC_NEEDS_RESYNC: i64 = 2 // device drifted -> re-apply needed |
| 63 | const NX_IOT_SYNC_FULL: i64 = 3 // registry at capacity |
| 64 | const NX_IOT_SYNC_NOT_FOUND: i64 = 4 |
| 65 | const NX_IOT_SYNC_BAD_ARG: i64 = 5 |
| 66 | const NX_IOT_SYNC_N: i64 = 6 |
| 75 | const NX_IOT_SYNC_EPOCH_NONE: i64 = -1 |
| 101 | const NX_IOT_SYNC_DEV_STRIDE: i64 = 2 |
functions
| 68 | func nx_iot_sync_verdict_is_valid(v: i64) -> i64 called by 1: main |
| 103 | func nx_iot_sync_dev_lid(devs: *i64, slot: i64) -> i64 called by 1: nx_iot_sync_find |
| 106 | func nx_iot_sync_dev_epoch(devs: *i64, slot: i64) -> i64 |
| 110 | func nx_iot_sync_find(reg: *IotSceneReg, devs: *i64, logical_id: i64) -> i64 |
| 124 | func nx_iot_scene_init(reg: *IotSceneReg, cap: i64) -> i64 called by 1: main |
| 147 | func nx_iot_scene_set(reg: *IotSceneReg, on: i64, bri: i64, called by 1: main |
| 164 | func nx_iot_scene_add_device(reg: *IotSceneReg, devs: *i64, |
| 180 | func nx_iot_scene_in_sync(reg: *IotSceneReg, devs: *i64, logical_id: i64) -> i64 |
| 188 | func nx_iot_scene_needs_resync(reg: *IotSceneReg, devs: *i64, logical_id: i64) -> i64 |
| 198 | func nx_iot_scene_mark_applied(reg: *IotSceneReg, devs: *i64, logical_id: i64) -> i64 |
| 208 | func nx_iot_scene_mark_desynced(reg: *IotSceneReg, devs: *i64, logical_id: i64) -> i64 |
| 223 | func nx_iot_scene_resync(reg: *IotSceneReg, devs: *i64, logical_id: i64) -> i64 |
| 236 | func nx_iot_scene_all_in_sync(reg: *IotSceneReg, devs: *i64) -> i64 |
| 246 | func nx_iot_scene_count_desynced(reg: *IotSceneReg, devs: *i64) -> i64 |
| 260 | func nx_iot_scene_resync_all(reg: *IotSceneReg, devs: *i64) -> i64 |
| 277 | func nx_iot_scene_epoch(reg: *IotSceneReg) -> i64 called by 1: main |
| 281 | func nx_iot_scene_count(reg: *IotSceneReg) -> i64 called by 1: main |
| 285 | func nx_iot_scene_syncs_restored(reg: *IotSceneReg) -> i64 called by 1: main |
| 289 | func nx_iot_scene_desyncs_seen(reg: *IotSceneReg) -> i64 called by 1: main |
| 293 | func nx_iot_scene_get_brightness(reg: *IotSceneReg) -> i64 called by 1: main |
| 297 | func nx_iot_scene_get_on(reg: *IotSceneReg) -> i64 called by 1: main |