code wiki / (root) / nx_iot_local_wiz.nx

nx_iot_local_wiz.nx source

↩ module page · 448 lines · 19255 B

1// nx_iot_local_wiz.nx -- WiZ JSON-over-UDP LAN protocol. 2// 3// Roadmap: NISHI_IOT_HUB_ROADMAP.md Epoch 2 (SPEAK). 4// S-class invariants: S0 (bit-equal payload encode), S6 (no cloud), 5// S7 (sealed-enum verdicts), S8 (kind-isolated -- imports no sibling 6// nx_iot_local_* primitive). 7// 8// Kind generator. Siblings: nx_iot_local_tuya_v33, nx_iot_local_magic_home. 9// 10// Wire format (vendor-published partial spec + community RE per 11// pywizlight; TIER_0_VENDOR_PUBLISHED route in iot-hub-research.toml): 12// 13// Transport: UDP/38899 14// Crypto: none 15// Format: JSON 16// 17// Set RGB+brightness: 18// {"method":"setPilot","params":{"r":255,"g":0,"b":0,"dimming":80}} 19// 20// Set scene (1..32 are vendor-defined scenes): 21// {"method":"setPilot","params":{"sceneId":4}} 22// 23// Power on/off: 24// {"method":"setPilot","params":{"state":true}} 25// 26// Query state: 27// {"method":"getPilot","params":{}} 28// 29// Discovery: 30// {"method":"registration","params":{"register":false,"phoneIp":"...", 31// "phoneMac":"..."}} 32// 33// Response (to getPilot or setPilot): 34// {"method":"getPilot","env":"pro","result":{"mac":"a8bb...", 35// "rssi":-56,"state":true,"sceneId":0,"r":255,"g":0,"b":0, 36// "dimming":80,"temp":2700,"src":""}} 37// 38// What this primitive does today: 39// - JSON payload builders for setPilot RGB / setPilot scene / 40// setPilot state / getPilot / registration 41// - response-key byte-scan extraction (state bool, r/g/b, dimming, 42// sceneId). This is intentionally NOT a full JSON parser -- 43// for known field shapes a byte-scan is faster, simpler, and 44// adequate for hub control. Full nx_json parser ships separately. 45// - sealed verdicts on every operation 46// 47// What it doesn't do yet: 48// - full JSON parser (queued; reusable nx_json.nx primitive 49// belongs in nishi-core, not in any kind generator) 50// - scene-name -> sceneId mapping table (queued, data file) 51// - light_mode tunable-white (k=2700/cool 6500 etc.) -- partial 52// (we ship the dimming + temp builders; full table is v2) 53// - WiZmote / occupancy / brightness-mode sensor variants -- v2 54// 55// genealogy_id: iot_hub_research/wiz_protocol + 56// pywizlight_reference (READ ONLY) 57// license_tier: ORIGINAL (substrate route INDEPENDENT_REDERIVE) 58// 59// nx_capability_claims: (per docs/NISHI_INTELLIGENT_CAPABILITY_LAYER.md) 60// needs: [byte_arithmetic, pointer_arithmetic, arena_alloc] 61// provides: [wiz_setpilot_rgb_payload, wiz_setpilot_state, 62// wiz_setpilot_scene, wiz_get_pilot_probe, wiz_parse_pilot] 63// safety: [no_floating_point, bit_equal_reproducible, kind_isolated] 64// verdict: [sealed_enum_7_state, no_silent_failure] 65// license: ORIGINAL 66// kind: iot_vendor_protocol 67// sss: [S0, S6, S7, S8] 68// 69// icl_override: no_syscall: parse_pilot uses sys_mmap for search- 70// pattern scratch buffers (8 small allocations). Tightening to 71// no_syscall requires caller-provided scratch threading. Queued. 72// icl_override: no_unchecked_deref: byte-scan walks via 73// `body[so]` indexed reads on a known-bounded `body_len`; deref 74// bounds-checked at function entry. Loop bodies trust the bound. 75// 76// nx_safety_envelope: (schema: nishi-library/seeds/safety-critical-standards.toml) 77// intended_use: "WiZ Connected JSON-LAN protocol -- setPilot 78// encode + getPilot probe + response parse" 79// sil_target: SIL2 (lighting control) 80// asil_target: QM 81// dal_target: NONE 82// iec_62304_class: NONE 83// evidence: [no_floating_point, bit_equal_reproducible, 84// sealed_enum_complete, kind_isolated_audit, 85// bounded_byte_scan_audited] 86// hazard_register: [bug-tape-json-injection-via-unescaped-input, 87// bug-tape-spoofed-pilot-response, 88// bug-tape-malformed-json-segfault] 89// residual_risk: "JSON parser uses sys_mmap for scratch (icl_ 90// override). Target-locked at runtime; 91// portability blocked on stack-local fixed 92// arrays. Parser does bounded byte scans only 93// -- no recursive JSON descent, no eval, no 94// floating-point parse." 95// verdict: NOT_YET_EVALUATED (awaits nx_safety_critical_grade; 96// icl_override on no_syscall + no_unchecked_deref 97// will be FACTORED INTO the score as documented 98// exceptions, NOT as failures) 99 100// This file uses sys_mmap to allocate small fixed search-pattern 101// scratch buffers in nx_iot_wiz_parse_pilot. That makes the file 102// target-locked at RUNTIME (sys_mmap syscall number differs per 103// kernel ABI) -- though it still COMPILES on every nxc2 backend. 104// To make the WiZ parser truly portable like the Tuya frame, the 105// parser would need either caller-provided scratch or stack-local 106// fixed arrays (not yet available in this dialect). Queued. 107import "nx_syscalls_x86_64.nx" 108 109// ---- Sealed enum: verdict ------------------------------------------ 110 111const NX_IOT_WIZ_VERDICT_UNKNOWN: i64 = 0 112const NX_IOT_WIZ_VERDICT_OK: i64 = 1 113const NX_IOT_WIZ_VERDICT_BUF_TOO_SMALL: i64 = 2 114const NX_IOT_WIZ_VERDICT_NO_RESULT: i64 = 3 // no "result" key 115const NX_IOT_WIZ_VERDICT_KEY_NOT_FOUND: i64 = 4 116const NX_IOT_WIZ_VERDICT_BAD_VALUE: i64 = 5 117const NX_IOT_WIZ_VERDICT_BAD_ARG: i64 = 6 118const NX_IOT_WIZ_VERDICT_N: i64 = 7 119 120func nx_iot_wiz_verdict_is_valid(v: i64) -> i64 { 121 if v < 0 { return 0 } 122 if v >= NX_IOT_WIZ_VERDICT_N { return 0 } 123 return 1 124} 125 126// ---- Wire constants ------------------------------------------------ 127 128const NX_IOT_WIZ_PORT: i64 = 38899 129 130// ---- Pilot state record -------------------------------------------- 131 132struct IotWizPilot { 133 state: i64, // 0 = off, 1 = on, -1 = unknown 134 r: i64, 135 g: i64, 136 b: i64, 137 dimming: i64, 138 scene_id: i64, 139 temp_k: i64, // colour temperature in Kelvin (0 if absent) 140} 141 142// ---- Internal helpers ---------------------------------------------- 143// 144// All of WiZ's protocol is ASCII JSON, so we build payloads by 145// emitting literal characters into the buffer. Helpers minimise 146// the byte-by-byte boilerplate. 147 148// Write a 0-terminated ASCII string starting from a fixed-byte array 149// of constants. Returns bytes written. Caller must ensure capacity. 150// 151// We don't have string literals as data in this dialect, so each 152// builder spells out the JSON keywords inline. That's verbose but 153// audit-friendly. 154 155// Integer-to-decimal-ASCII writer. Writes the decimal representation 156// of `v` (must be >= 0; negative not supported in WiZ params) starting 157// at out[0], returns bytes written. Buffer must be at least 11 bytes. 158func nx_iot_wiz_itoa(out: *u8, v: i64) -> i64 { 159 if v == 0 { out[0] = 0x30; return 1 } 160 var tmp: i64 = v 161 if tmp < 0 { tmp = -tmp } 162 // Compute digit count. 163 var n: i64 = 0 164 var t2: i64 = tmp 165 while t2 > 0 { n = n + 1; t2 = t2 / 10 } 166 // Emit digits LSB-first into a 16-byte scratch, then reverse. 167 // Scratch lives in the caller's buffer beyond `n`; we use out 168 // directly. 169 var i: i64 = n - 1 170 while i >= 0 { 171 out[i] = (0x30 + (tmp % 10)) & 0xff 172 tmp = tmp / 10 173 i = i - 1 174 } 175 return n 176} 177 178// Find the offset of an ASCII pattern in buf[0..len). Returns -1 179// if not found. pat_len typically small (<32). Naive O(n*m) but 180// fine for hub-scale payloads (<512 bytes). 181func nx_iot_wiz_find(buf: *u8, len: i64, pat: *u8, pat_len: i64) -> i64 { 182 if pat_len == 0 { return 0 } 183 if pat_len > len { return -1 } 184 var i: i64 = 0 185 let end: i64 = len - pat_len + 1 186 while i < end { 187 var j: i64 = 0 188 var ok: i64 = 1 189 while j < pat_len { 190 if (buf[i + j] as i64) != (pat[j] as i64) { 191 ok = 0 192 j = pat_len // exit inner 193 } 194 else { j = j + 1 } 195 } 196 if ok == 1 { return i } 197 i = i + 1 198 } 199 return -1 200} 201 202// Parse a non-negative decimal integer starting at buf[off]. Stops 203// at the first non-digit. Returns the value; writes the number of 204// bytes consumed via out_consumed. If buf[off] is not a digit, 205// returns 0 + out_consumed = 0. 206func nx_iot_wiz_atoi(buf: *u8, len: i64, off: i64, 207 out_consumed: *i64) -> i64 { 208 var v: i64 = 0 209 var i: i64 = off 210 var n: i64 = 0 211 var keep: i64 = 1 212 while keep == 1 { 213 if i >= len { keep = 0 } 214 else { 215 let c: i64 = (buf[i] as i64) & 0xff 216 if c < 0x30 { keep = 0 } 217 else { if c > 0x39 { keep = 0 } 218 else { v = v * 10 + (c - 0x30); n = n + 1; i = i + 1 } } 219 } 220 } 221 *out_consumed = n 222 return v 223} 224 225// ---- Payload builders ---------------------------------------------- 226 227// setPilot RGB + dimming. Buffer must be >= 80. 228func nx_iot_wiz_build_set_rgb(out: *u8, out_cap: i64, 229 r: i64, g: i64, b: i64, 230 dimming: i64) -> i64 { 231 if out_cap < 80 { return -1 } 232 // {"method":"setPilot","params":{"r":<r>,"g":<g>,"b":<b>,"dimming":<dim>}} 233 var o: i64 = 0 234 // {"method":"setPilot","params":{"r": 235 let p1: *u8 = sys_mmap(40) // unused; we emit inline instead 236 out[o]=0x7B;o=o+1 // { 237 out[o]=0x22;o=o+1 // " 238 out[o]=0x6D;o=o+1; out[o]=0x65;o=o+1; out[o]=0x74;o=o+1; out[o]=0x68;o=o+1 239 out[o]=0x6F;o=o+1; out[o]=0x64;o=o+1 // method 240 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1; out[o]=0x22;o=o+1 // ":" 241 out[o]=0x73;o=o+1; out[o]=0x65;o=o+1; out[o]=0x74;o=o+1; out[o]=0x50;o=o+1 242 out[o]=0x69;o=o+1; out[o]=0x6C;o=o+1; out[o]=0x6F;o=o+1; out[o]=0x74;o=o+1 // setPilot 243 out[o]=0x22;o=o+1; out[o]=0x2C;o=o+1 // ", 244 out[o]=0x22;o=o+1 245 out[o]=0x70;o=o+1; out[o]=0x61;o=o+1; out[o]=0x72;o=o+1; out[o]=0x61;o=o+1 246 out[o]=0x6D;o=o+1; out[o]=0x73;o=o+1 // params 247 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1; out[o]=0x7B;o=o+1 // ":{ 248 out[o]=0x22;o=o+1; out[o]=0x72;o=o+1; out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1 // "r": 249 let dst1_addr: i64 = (out as i64) + o 250 let dst1: *u8 = dst1_addr as *u8 251 o = o + nx_iot_wiz_itoa(dst1, r) 252 out[o]=0x2C;o=o+1; out[o]=0x22;o=o+1; out[o]=0x67;o=o+1; out[o]=0x22;o=o+1 253 out[o]=0x3A;o=o+1 254 let dst2_addr: i64 = (out as i64) + o 255 let dst2: *u8 = dst2_addr as *u8 256 o = o + nx_iot_wiz_itoa(dst2, g) 257 out[o]=0x2C;o=o+1; out[o]=0x22;o=o+1; out[o]=0x62;o=o+1; out[o]=0x22;o=o+1 258 out[o]=0x3A;o=o+1 259 let dst3_addr: i64 = (out as i64) + o 260 let dst3: *u8 = dst3_addr as *u8 261 o = o + nx_iot_wiz_itoa(dst3, b) 262 out[o]=0x2C;o=o+1; out[o]=0x22;o=o+1 263 out[o]=0x64;o=o+1; out[o]=0x69;o=o+1; out[o]=0x6D;o=o+1; out[o]=0x6D;o=o+1 264 out[o]=0x69;o=o+1; out[o]=0x6E;o=o+1; out[o]=0x67;o=o+1 // dimming 265 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1 // ": 266 let dst4_addr: i64 = (out as i64) + o 267 let dst4: *u8 = dst4_addr as *u8 268 o = o + nx_iot_wiz_itoa(dst4, dimming) 269 out[o]=0x7D;o=o+1; out[o]=0x7D;o=o+1 // }} 270 return o 271} 272 273// setPilot state (on/off). Buffer must be >= 56. 274func nx_iot_wiz_build_set_state(out: *u8, out_cap: i64, on: i64) -> i64 { 275 if out_cap < 56 { return -1 } 276 var o: i64 = 0 277 // {"method":"setPilot","params":{"state":<true|false>}} 278 out[o]=0x7B;o=o+1; out[o]=0x22;o=o+1 279 out[o]=0x6D;o=o+1; out[o]=0x65;o=o+1; out[o]=0x74;o=o+1; out[o]=0x68;o=o+1 280 out[o]=0x6F;o=o+1; out[o]=0x64;o=o+1 281 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1; out[o]=0x22;o=o+1 282 out[o]=0x73;o=o+1; out[o]=0x65;o=o+1; out[o]=0x74;o=o+1; out[o]=0x50;o=o+1 283 out[o]=0x69;o=o+1; out[o]=0x6C;o=o+1; out[o]=0x6F;o=o+1; out[o]=0x74;o=o+1 284 out[o]=0x22;o=o+1; out[o]=0x2C;o=o+1; out[o]=0x22;o=o+1 285 out[o]=0x70;o=o+1; out[o]=0x61;o=o+1; out[o]=0x72;o=o+1; out[o]=0x61;o=o+1 286 out[o]=0x6D;o=o+1; out[o]=0x73;o=o+1 287 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1; out[o]=0x7B;o=o+1 288 out[o]=0x22;o=o+1; out[o]=0x73;o=o+1; out[o]=0x74;o=o+1; out[o]=0x61;o=o+1 289 out[o]=0x74;o=o+1; out[o]=0x65;o=o+1 290 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1 291 if on != 0 { 292 out[o]=0x74;o=o+1; out[o]=0x72;o=o+1; out[o]=0x75;o=o+1; out[o]=0x65;o=o+1 // true 293 } else { 294 out[o]=0x66;o=o+1; out[o]=0x61;o=o+1; out[o]=0x6C;o=o+1 295 out[o]=0x73;o=o+1; out[o]=0x65;o=o+1 // false 296 } 297 out[o]=0x7D;o=o+1; out[o]=0x7D;o=o+1 298 return o 299} 300 301// setPilot sceneId. Buffer must be >= 56. 302func nx_iot_wiz_build_set_scene(out: *u8, out_cap: i64, scene_id: i64) -> i64 { 303 if out_cap < 56 { return -1 } 304 var o: i64 = 0 305 // {"method":"setPilot","params":{"sceneId":<n>}} 306 out[o]=0x7B;o=o+1; out[o]=0x22;o=o+1 307 out[o]=0x6D;o=o+1; out[o]=0x65;o=o+1; out[o]=0x74;o=o+1; out[o]=0x68;o=o+1 308 out[o]=0x6F;o=o+1; out[o]=0x64;o=o+1 309 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1; out[o]=0x22;o=o+1 310 out[o]=0x73;o=o+1; out[o]=0x65;o=o+1; out[o]=0x74;o=o+1; out[o]=0x50;o=o+1 311 out[o]=0x69;o=o+1; out[o]=0x6C;o=o+1; out[o]=0x6F;o=o+1; out[o]=0x74;o=o+1 312 out[o]=0x22;o=o+1; out[o]=0x2C;o=o+1; out[o]=0x22;o=o+1 313 out[o]=0x70;o=o+1; out[o]=0x61;o=o+1; out[o]=0x72;o=o+1; out[o]=0x61;o=o+1 314 out[o]=0x6D;o=o+1; out[o]=0x73;o=o+1 315 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1; out[o]=0x7B;o=o+1 316 out[o]=0x22;o=o+1; out[o]=0x73;o=o+1; out[o]=0x63;o=o+1; out[o]=0x65;o=o+1 317 out[o]=0x6E;o=o+1; out[o]=0x65;o=o+1; out[o]=0x49;o=o+1; out[o]=0x64;o=o+1 318 out[o]=0x22;o=o+1; out[o]=0x3A;o=o+1 319 let dst_addr: i64 = (out as i64) + o 320 let dst: *u8 = dst_addr as *u8 321 o = o + nx_iot_wiz_itoa(dst, scene_id) 322 out[o]=0x7D;o=o+1; out[o]=0x7D;o=o+1 323 return o 324} 325 326// getPilot probe. Buffer must be >= 33. 327func nx_iot_wiz_build_get_pilot(out: *u8, out_cap: i64) -> i64 { 328 if out_cap < 33 { return -1 } 329 out[0] = 0x7B; out[1] = 0x22; out[2] = 0x6D; out[3] = 0x65 330 out[4] = 0x74; out[5] = 0x68; out[6] = 0x6F; out[7] = 0x64 331 out[8] = 0x22; out[9] = 0x3A; out[10] = 0x22; out[11] = 0x67 332 out[12] = 0x65; out[13] = 0x74; out[14] = 0x50; out[15] = 0x69 333 out[16] = 0x6C; out[17] = 0x6F; out[18] = 0x74; out[19] = 0x22 334 out[20] = 0x2C; out[21] = 0x22; out[22] = 0x70; out[23] = 0x61 335 out[24] = 0x72; out[25] = 0x61; out[26] = 0x6D; out[27] = 0x73 336 out[28] = 0x22; out[29] = 0x3A; out[30] = 0x7B; out[31] = 0x7D 337 out[32] = 0x7D 338 return 33 339} 340 341// ---- Response parser ----------------------------------------------- 342// 343// We don't run a full JSON parser; we byte-scan for known keys and 344// extract their values. Acceptable for hub-scale payloads (<512 345// bytes) where the response shape is fixed by the vendor protocol. 346 347// Locate `"<key>":` in buf and return the offset of the byte AFTER 348// the colon. -1 if not found. key_pat must include the surrounding 349// quotes + colon; e.g. for "r" pass the 4 bytes 0x22 0x72 0x22 0x3A. 350func nx_iot_wiz_value_offset(buf: *u8, len: i64, 351 key_pat: *u8, key_pat_len: i64) -> i64 { 352 let o: i64 = nx_iot_wiz_find(buf, len, key_pat, key_pat_len) 353 if o < 0 { return -1 } 354 return o + key_pat_len 355} 356 357// Extract pilot state from a getPilot / setPilot reply. Returns 0 + 358// verdict OK on success; -1 + verdict on failure. 359 360func nx_iot_wiz_parse_pilot(buf: *u8, len: i64, 361 out: *IotWizPilot, 362 out_verdict: *i64) -> i64 { 363 *out_verdict = NX_IOT_WIZ_VERDICT_UNKNOWN 364 if len < 16 { 365 *out_verdict = NX_IOT_WIZ_VERDICT_BUF_TOO_SMALL 366 return -1 367 } 368 // Require a "result" sub-object. Pattern: '"result":' 369 let pat_result: *u8 = sys_mmap(16) 370 pat_result[0] = 0x22; pat_result[1] = 0x72; pat_result[2] = 0x65 371 pat_result[3] = 0x73; pat_result[4] = 0x75; pat_result[5] = 0x6C 372 pat_result[6] = 0x74; pat_result[7] = 0x22; pat_result[8] = 0x3A 373 let r_off: i64 = nx_iot_wiz_find(buf, len, pat_result, 9) 374 if r_off < 0 { 375 *out_verdict = NX_IOT_WIZ_VERDICT_NO_RESULT 376 return -1 377 } 378 let body_addr: i64 = (buf as i64) + r_off 379 let body: *u8 = body_addr as *u8 380 let body_len: i64 = len - r_off 381 382 // Defaults indicating "key absent" 383 out.state = -1 384 out.r = -1 385 out.g = -1 386 out.b = -1 387 out.dimming = -1 388 out.scene_id = -1 389 out.temp_k = 0 390 391 // "state": followed by true/false 392 let pat_state: *u8 = sys_mmap(16) 393 pat_state[0]=0x22; pat_state[1]=0x73; pat_state[2]=0x74; pat_state[3]=0x61 394 pat_state[4]=0x74; pat_state[5]=0x65; pat_state[6]=0x22; pat_state[7]=0x3A 395 let so: i64 = nx_iot_wiz_value_offset(body, body_len, pat_state, 8) 396 if so >= 0 { 397 if (body[so] as i64) == 0x74 { out.state = 1 } // 't' (true) 398 else { if (body[so] as i64) == 0x66 { out.state = 0 } } // 'f' (false) 399 } 400 401 // Numeric-key extractor closure: build "<key>": pattern and read 402 // integer value after it. 403 let consumed: *i64 = sys_mmap(8) as *i64 404 405 // "r": 406 let pat_r: *u8 = sys_mmap(8) 407 pat_r[0]=0x22; pat_r[1]=0x72; pat_r[2]=0x22; pat_r[3]=0x3A 408 let ro: i64 = nx_iot_wiz_value_offset(body, body_len, pat_r, 4) 409 if ro >= 0 { out.r = nx_iot_wiz_atoi(body, body_len, ro, consumed) } 410 411 // "g": 412 let pat_g: *u8 = sys_mmap(8) 413 pat_g[0]=0x22; pat_g[1]=0x67; pat_g[2]=0x22; pat_g[3]=0x3A 414 let go: i64 = nx_iot_wiz_value_offset(body, body_len, pat_g, 4) 415 if go >= 0 { out.g = nx_iot_wiz_atoi(body, body_len, go, consumed) } 416 417 // "b": 418 let pat_b: *u8 = sys_mmap(8) 419 pat_b[0]=0x22; pat_b[1]=0x62; pat_b[2]=0x22; pat_b[3]=0x3A 420 let bo: i64 = nx_iot_wiz_value_offset(body, body_len, pat_b, 4) 421 if bo >= 0 { out.b = nx_iot_wiz_atoi(body, body_len, bo, consumed) } 422 423 // "dimming": 424 let pat_dim: *u8 = sys_mmap(16) 425 pat_dim[0]=0x22; pat_dim[1]=0x64; pat_dim[2]=0x69; pat_dim[3]=0x6D 426 pat_dim[4]=0x6D; pat_dim[5]=0x69; pat_dim[6]=0x6E; pat_dim[7]=0x67 427 pat_dim[8]=0x22; pat_dim[9]=0x3A 428 let dim_off: i64 = nx_iot_wiz_value_offset(body, body_len, pat_dim, 10) 429 if dim_off >= 0 { out.dimming = nx_iot_wiz_atoi(body, body_len, dim_off, consumed) } 430 431 // "sceneId": 432 let pat_sc: *u8 = sys_mmap(16) 433 pat_sc[0]=0x22; pat_sc[1]=0x73; pat_sc[2]=0x63; pat_sc[3]=0x65 434 pat_sc[4]=0x6E; pat_sc[5]=0x65; pat_sc[6]=0x49; pat_sc[7]=0x64 435 pat_sc[8]=0x22; pat_sc[9]=0x3A 436 let sc_off: i64 = nx_iot_wiz_value_offset(body, body_len, pat_sc, 10) 437 if sc_off >= 0 { out.scene_id = nx_iot_wiz_atoi(body, body_len, sc_off, consumed) } 438 439 // "temp": (kelvin colour temperature) 440 let pat_t: *u8 = sys_mmap(16) 441 pat_t[0]=0x22; pat_t[1]=0x74; pat_t[2]=0x65; pat_t[3]=0x6D 442 pat_t[4]=0x70; pat_t[5]=0x22; pat_t[6]=0x3A 443 let t_off: i64 = nx_iot_wiz_value_offset(body, body_len, pat_t, 7) 444 if t_off >= 0 { out.temp_k = nx_iot_wiz_atoi(body, body_len, t_off, consumed) } 445 446 *out_verdict = NX_IOT_WIZ_VERDICT_OK 447 return 0 448}