code wiki / (root) / nx_iot_capability.nx

nx_iot_capability.nx source

↩ module page · 198 lines · 7106 B

1// nx_iot_capability.nx -- per-device capability model for the hub UI. 2// 3// Roadmap: NISHI_IOT_HUB_ROADMAP.md Epoch 6 (CALIBRATE) + the 4// nishifamily.com/iot control-panel substrate. Operator ask 5// (2026-06-18): manage devices "with the ability to change the lights 6// on what capabilities they have like color and light intensity." 7// 8// The triad: nx_iot_anchor = WHO (stable identity), the vendor driver 9// = HOW (wire protocol), this = WHAT-IT-CAN-DO. The /iot UI reads a 10// device's capability set and renders exactly the right controls -- a 11// toggle for a switch, a slider for a dimmer, a colour wheel + temp 12// slider for a colour bulb. Data-driven (CLAUDE.md #11): the UI never 13// hardcodes per-device controls; it asks this model. 14// 15// Capabilities are bit flags so a device advertises a SET. Device 16// classes map to default capability sets; per-vendor quirks refine. 17// 18// Pure caller-buffer, no syscalls, no imports -> portable + sovereign. 19// 20// license_tier: ORIGINAL 21// 22// nx_capability_claims: 23// needs: [pointer_arithmetic, bit_ops] 24// provides: [capability_flags, class_to_caps, caps_has, 25// caps_clamp_level, caps_emit_json] 26// safety: [no_unchecked_deref, no_floating_point, no_syscall, 27// bounded_iteration, data_driven] 28// verdict: [sealed_enum_class, no_silent_failure] 29// license: ORIGINAL 30// kind: iot_runtime_primitive 31// sss: [S6, S7] 32 33// ---- Capability bit flags ----------------------------------------- 34 35const NX_IOT_CAP_ONOFF: i64 = 1 // bit 0 -> render a toggle 36const NX_IOT_CAP_BRIGHTNESS: i64 = 2 // bit 1 -> render a 0-100 slider 37const NX_IOT_CAP_COLOR: i64 = 4 // bit 2 -> render a hue/sat picker 38const NX_IOT_CAP_COLOR_TEMP: i64 = 8 // bit 3 -> render a Kelvin slider 39const NX_IOT_CAP_SCENE: i64 = 16 // bit 4 -> render a scene list 40const NX_IOT_CAP_ENERGY: i64 = 32 // bit 5 -> render energy readout 41 42// ---- Sealed enum: device class ------------------------------------ 43 44const NX_IOT_CLASS_UNKNOWN: i64 = 0 45const NX_IOT_CLASS_SWITCH: i64 = 1 46const NX_IOT_CLASS_PLUG: i64 = 2 47const NX_IOT_CLASS_PLUG_ENERGY: i64 = 3 48const NX_IOT_CLASS_DIMMER: i64 = 4 49const NX_IOT_CLASS_BULB_WHITE: i64 = 5 50const NX_IOT_CLASS_BULB_TUNABLE: i64 = 6 51const NX_IOT_CLASS_BULB_COLOR: i64 = 7 52const NX_IOT_CLASS_N: i64 = 8 53 54func nx_iot_class_is_valid(c: i64) -> i64 { 55 if c < 0 { return 0 } 56 if c >= NX_IOT_CLASS_N { return 0 } 57 return 1 58} 59 60// ---- Control ranges (data, not magic numbers) --------------------- 61 62const NX_IOT_BRIGHTNESS_MIN: i64 = 0 63const NX_IOT_BRIGHTNESS_MAX: i64 = 100 64const NX_IOT_COLOR_TEMP_MIN: i64 = 2700 // warm white, Kelvin 65const NX_IOT_COLOR_TEMP_MAX: i64 = 6500 // cool daylight, Kelvin 66 67// ---- local string helper ------------------------------------------ 68 69func nx_iot_cap_puts_z(out: *u8, off: i64, s: *u8) -> i64 { 70 var i: i64 = 0 71 var keep: i64 = 1 72 while keep == 1 { 73 let ch: u8 = s[i] 74 if ch == (0 as u8) { keep = 0 } 75 else { 76 out[off + i] = ch 77 i = i + 1 78 } 79 } 80 return off + i 81} 82 83// ---- class -> default capability set ------------------------------ 84 85func nx_iot_cap_for_class(vendor: i64, dev_class: i64) -> i64 { 86 if dev_class == NX_IOT_CLASS_SWITCH { return NX_IOT_CAP_ONOFF } 87 if dev_class == NX_IOT_CLASS_PLUG { return NX_IOT_CAP_ONOFF } 88 if dev_class == NX_IOT_CLASS_PLUG_ENERGY { 89 return NX_IOT_CAP_ONOFF | NX_IOT_CAP_ENERGY 90 } 91 if dev_class == NX_IOT_CLASS_DIMMER { 92 return NX_IOT_CAP_ONOFF | NX_IOT_CAP_BRIGHTNESS 93 } 94 if dev_class == NX_IOT_CLASS_BULB_WHITE { 95 return NX_IOT_CAP_ONOFF | NX_IOT_CAP_BRIGHTNESS 96 } 97 if dev_class == NX_IOT_CLASS_BULB_TUNABLE { 98 return NX_IOT_CAP_ONOFF | NX_IOT_CAP_BRIGHTNESS | NX_IOT_CAP_COLOR_TEMP 99 } 100 if dev_class == NX_IOT_CLASS_BULB_COLOR { 101 return NX_IOT_CAP_ONOFF | NX_IOT_CAP_BRIGHTNESS | NX_IOT_CAP_COLOR | NX_IOT_CAP_COLOR_TEMP | NX_IOT_CAP_SCENE 102 } 103 return 0 104} 105 106// ---- queries ------------------------------------------------------ 107 108func nx_iot_cap_has(caps: i64, flag: i64) -> i64 { 109 if (caps & flag) != 0 { return 1 } 110 return 0 111} 112 113// Clamp a brightness/level request to the supported range; if the 114// device has no brightness capability, returns -1. 115func nx_iot_cap_clamp_brightness(caps: i64, v: i64) -> i64 { 116 if nx_iot_cap_has(caps, NX_IOT_CAP_BRIGHTNESS) == 0 { return -1 } 117 if v < NX_IOT_BRIGHTNESS_MIN { return NX_IOT_BRIGHTNESS_MIN } 118 if v > NX_IOT_BRIGHTNESS_MAX { return NX_IOT_BRIGHTNESS_MAX } 119 return v 120} 121 122func nx_iot_cap_clamp_color_temp(caps: i64, k: i64) -> i64 { 123 if nx_iot_cap_has(caps, NX_IOT_CAP_COLOR_TEMP) == 0 { return -1 } 124 if k < NX_IOT_COLOR_TEMP_MIN { return NX_IOT_COLOR_TEMP_MIN } 125 if k > NX_IOT_COLOR_TEMP_MAX { return NX_IOT_COLOR_TEMP_MAX } 126 return k 127} 128 129// ---- emit capability descriptor for the UI ------------------------ 130// 131// Writes a JSON array of the supported capability names, e.g. 132// ["on_off","brightness","color","color_temp","scene"] 133// The /iot front-end maps each name to a control widget. Returns the 134// byte length, or -1 if the buffer is too small. 135 136func nx_iot_cap_emit_json(caps: i64, out: *u8, out_cap: i64) -> i64 { 137 if out_cap < 96 { return -1 } 138 var o: i64 = 0 139 out[o] = 0x5B as u8 // [ 140 o = o + 1 141 var first: i64 = 1 142 if nx_iot_cap_has(caps, NX_IOT_CAP_ONOFF) == 1 { 143 out[o] = 0x22 as u8 144 o = o + 1 145 o = nx_iot_cap_puts_z(out, o, "on_off") 146 out[o] = 0x22 as u8 147 o = o + 1 148 first = 0 149 } 150 if nx_iot_cap_has(caps, NX_IOT_CAP_BRIGHTNESS) == 1 { 151 if first == 0 { out[o] = 0x2C as u8; o = o + 1 } 152 out[o] = 0x22 as u8 153 o = o + 1 154 o = nx_iot_cap_puts_z(out, o, "brightness") 155 out[o] = 0x22 as u8 156 o = o + 1 157 first = 0 158 } 159 if nx_iot_cap_has(caps, NX_IOT_CAP_COLOR) == 1 { 160 if first == 0 { out[o] = 0x2C as u8; o = o + 1 } 161 out[o] = 0x22 as u8 162 o = o + 1 163 o = nx_iot_cap_puts_z(out, o, "color") 164 out[o] = 0x22 as u8 165 o = o + 1 166 first = 0 167 } 168 if nx_iot_cap_has(caps, NX_IOT_CAP_COLOR_TEMP) == 1 { 169 if first == 0 { out[o] = 0x2C as u8; o = o + 1 } 170 out[o] = 0x22 as u8 171 o = o + 1 172 o = nx_iot_cap_puts_z(out, o, "color_temp") 173 out[o] = 0x22 as u8 174 o = o + 1 175 first = 0 176 } 177 if nx_iot_cap_has(caps, NX_IOT_CAP_SCENE) == 1 { 178 if first == 0 { out[o] = 0x2C as u8; o = o + 1 } 179 out[o] = 0x22 as u8 180 o = o + 1 181 o = nx_iot_cap_puts_z(out, o, "scene") 182 out[o] = 0x22 as u8 183 o = o + 1 184 first = 0 185 } 186 if nx_iot_cap_has(caps, NX_IOT_CAP_ENERGY) == 1 { 187 if first == 0 { out[o] = 0x2C as u8; o = o + 1 } 188 out[o] = 0x22 as u8 189 o = o + 1 190 o = nx_iot_cap_puts_z(out, o, "energy") 191 out[o] = 0x22 as u8 192 o = o + 1 193 first = 0 194 } 195 out[o] = 0x5D as u8 // ] 196 o = o + 1 197 return o 198}