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}