code wiki / (root) / nx_iot_control_page.nx

nx_iot_control_page.nx source

↩ module page · 160 lines · 8802 B

1// nx_iot_control_page.nx -- emit a ZERO-JS, CROSS-BROWSER IoT CONTROL page. 2// 3// Operator 2026-06-22: "work via nishi browser and 3rd party like chrome/firefox/edge 4// and simple for other people to use aka reproducible." The cross-browser + reproducible 5// answer is SERVER-RENDERED HTML + CSS with ZERO <script>: controls are real <form> POSTs 6// to the hub's /home/cmd, so they actuate in EVERY browser with nothing to install (the 7// same no-JS doctrine the reader arc proved). Capability-driven (nx_iot_capability): a 8// switch gets On/Off, a colour bulb gets brightness/colour/warmth/scene -- chosen per 9// device, never hardcoded. Library organ: cp_head/cp_card/cp_foot/cp_find + a demo main() 10// (the gate calls the functions; on import the demo main is stripped). 11// 12// NEVER-BRICK #26: the page only emits on/off/brightness/scene COMMANDS (no firmware). 13// license_tier: ORIGINAL 14 15import "nx_syscalls.nx" 16import "nx_iot_capability.nx" 17 18const CP_OUT: *u8 = "web_assets/iot_control.html" 19const CP_CAP: i64 = 262144 20 21func cp_cat(dst: *u8, off: i64, s: *u8) -> i64 { 22 var o: i64 = off 23 var i: i64 = 0 24 while s[i] != 0 as u8 { dst[o] = s[i]; o = o + 1; i = i + 1 } 25 return o 26} 27func cp_catn(dst: *u8, off: i64, v: i64) -> i64 { 28 var o: i64 = off 29 var n: i64 = v 30 if n < 0 { dst[o] = 45 as u8; o = o + 1; n = 0 - n } 31 let tmp: *u8 = sys_mmap(32) 32 var k: i64 = 0 33 if n == 0 { tmp[0] = 48 as u8; k = 1 } 34 while n > 0 { tmp[k] = (48 + (n % 10)) as u8; n = n / 10; k = k + 1 } 35 var j: i64 = k - 1 36 while j >= 0 { dst[o] = tmp[j]; o = o + 1; j = j - 1 } 37 return o 38} 39// substring search (for the gate's cross-browser assertions) 40func cp_find(buf: *u8, len: i64, pat: *u8, pat_len: i64) -> i64 { 41 if pat_len > len { return 0 - 1 } 42 var i: i64 = 0 43 let end: i64 = len - pat_len + 1 44 var hit: i64 = 0 - 1 45 while i < end { 46 if hit == (0 - 1) { 47 var j: i64 = 0 48 var ok: i64 = 1 49 while j < pat_len { 50 if (buf[i + j] as i64) != (pat[j] as i64) { ok = 0; j = pat_len } else { j = j + 1 } 51 } 52 if ok == 1 { hit = i } 53 } 54 i = i + 1 55 } 56 return hit 57} 58 59// One control form that POSTs id+cmd(+value) to the hub. Zero JS -> works in any browser. 60func cp_cmd_open(h: *u8, off: i64, id: *u8) -> i64 { 61 var w: i64 = off 62 w = cp_cat(h, w, "<form class=\"w\" method=\"post\" action=\"/home/cmd\"><input type=\"hidden\" name=\"id\" value=\"" as *u8) 63 w = cp_cat(h, w, id) 64 w = cp_cat(h, w, "\">" as *u8) 65 return w 66} 67 68// Capability-driven control widgets, each a real <form> POST. Never hardcoded. 69func cp_widgets(h: *u8, off: i64, id: *u8, caps: i64, is_on: i64) -> i64 { 70 var w: i64 = off 71 if nx_iot_cap_has(caps, NX_IOT_CAP_ONOFF) == 1 { 72 w = cp_cmd_open(h, w, id) 73 if is_on == 1 { w = cp_cat(h, w, "<button class=\"b act\" name=\"cmd\" value=\"on\">On</button><button class=\"b\" name=\"cmd\" value=\"off\">Off</button>" as *u8) } 74 else { w = cp_cat(h, w, "<button class=\"b\" name=\"cmd\" value=\"on\">On</button><button class=\"b act\" name=\"cmd\" value=\"off\">Off</button>" as *u8) } 75 w = cp_cat(h, w, "</form>" as *u8) 76 } 77 if nx_iot_cap_has(caps, NX_IOT_CAP_BRIGHTNESS) == 1 { 78 w = cp_cmd_open(h, w, id) 79 w = cp_cat(h, w, "<label>Brightness<input type=\"range\" name=\"level\" min=\"0\" max=\"100\" value=\"70\"></label><button class=\"b\" name=\"cmd\" value=\"bri\">Set</button></form>" as *u8) 80 } 81 if nx_iot_cap_has(caps, NX_IOT_CAP_COLOR) == 1 { 82 w = cp_cmd_open(h, w, id) 83 w = cp_cat(h, w, "<button class=\"sw\" name=\"cmd\" value=\"c:f85149\" style=\"background:#f85149\"></button><button class=\"sw\" name=\"cmd\" value=\"c:3fb950\" style=\"background:#3fb950\"></button><button class=\"sw\" name=\"cmd\" value=\"c:4cc2ff\" style=\"background:#4cc2ff\"></button></form>" as *u8) 84 } 85 if nx_iot_cap_has(caps, NX_IOT_CAP_COLOR_TEMP) == 1 { 86 w = cp_cmd_open(h, w, id) 87 w = cp_cat(h, w, "<label>Warmth<input type=\"range\" name=\"level\" min=\"" as *u8) 88 w = cp_catn(h, w, NX_IOT_COLOR_TEMP_MIN) 89 w = cp_cat(h, w, "\" max=\"" as *u8) 90 w = cp_catn(h, w, NX_IOT_COLOR_TEMP_MAX) 91 w = cp_cat(h, w, "\" value=\"4000\"></label><button class=\"b\" name=\"cmd\" value=\"ct\">Set</button></form>" as *u8) 92 } 93 if nx_iot_cap_has(caps, NX_IOT_CAP_SCENE) == 1 { 94 w = cp_cmd_open(h, w, id) 95 w = cp_cat(h, w, "<select name=\"level\"><option value=\"relax\">Relax</option><option value=\"bright\">Bright</option></select><button class=\"b\" name=\"cmd\" value=\"scene\">Go</button></form>" as *u8) 96 } 97 return w 98} 99 100// One device control card. is_on shows current state; controls are capability-gated. 101func cp_card(h: *u8, off: i64, id: *u8, name: *u8, room: *u8, vlabel: *u8, dev_class: i64, is_on: i64) -> i64 { 102 var w: i64 = off 103 let caps: i64 = nx_iot_cap_for_class(0, dev_class) 104 w = cp_cat(h, w, "<div class=\"card\"><div class=\"cn\">" as *u8) 105 w = cp_cat(h, w, name) 106 w = cp_cat(h, w, "</div><div class=\"cr\">" as *u8) 107 w = cp_cat(h, w, room) 108 w = cp_cat(h, w, " &middot; " as *u8) 109 w = cp_cat(h, w, vlabel) 110 w = cp_cat(h, w, "</div><div class=\"row\">" as *u8) 111 w = cp_widgets(h, w, id, caps, is_on) 112 w = cp_cat(h, w, "</div></div>\n" as *u8) 113 return w 114} 115 116func cp_head(h: *u8, off: i64) -> i64 { 117 var w: i64 = off 118 w = cp_cat(h, w, "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\">\n" as *u8) 119 w = cp_cat(h, w, "<meta name=\"viewport\" content=\"width=device-width,initial-scale=1\">\n" as *u8) 120 w = cp_cat(h, w, "<title>Nishi IoT &mdash; Control</title>\n" as *u8) 121 w = cp_cat(h, w, "<style>\n" as *u8) 122 w = cp_cat(h, w, "*{box-sizing:border-box}body{margin:0;background:#0b0f14;color:#e6edf3;font:15px/1.5 system-ui,Segoe UI,Roboto,sans-serif}\n" as *u8) 123 w = cp_cat(h, w, "header,main{max-width:960px;margin:0 auto;padding:16px 20px}h1{font-size:24px;margin:.2em 0}.sub{color:#8aa0b4;font-size:13px}\n" as *u8) 124 w = cp_cat(h, w, ".grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:12px}\n" as *u8) 125 w = cp_cat(h, w, ".card{background:#131b24;border:1px solid #243140;border-radius:10px;padding:12px 13px}\n" as *u8) 126 w = cp_cat(h, w, ".cn{font-weight:600}.cr{color:#8aa0b4;font-size:12.5px;margin:.1em 0 .6em}\n" as *u8) 127 w = cp_cat(h, w, ".row{display:flex;flex-wrap:wrap;gap:7px;align-items:center}form.w{display:inline-flex;gap:4px;align-items:center;margin:0}\n" as *u8) 128 w = cp_cat(h, w, ".b{background:#1b2735;color:#e6edf3;border:1px solid #243140;border-radius:6px;padding:5px 10px;font-weight:600;cursor:pointer}\n" as *u8) 129 w = cp_cat(h, w, ".b.act{background:#4cc2ff;color:#04121c;border-color:#4cc2ff}.sw{width:18px;height:18px;border-radius:50%;border:1px solid #0006;cursor:pointer}\n" as *u8) 130 w = cp_cat(h, w, "label{color:#8aa0b4;font-size:12px;display:inline-flex;flex-direction:column}input[type=range]{width:90px}select{background:#0e1620;color:#e6edf3;border:1px solid #243140;border-radius:6px}\n" as *u8) 131 w = cp_cat(h, w, "</style></head><body>\n" as *u8) 132 w = cp_cat(h, w, "<header><h1>Nishi IoT &mdash; Control</h1><p class=\"sub\">Sovereign LAN hub &middot; zero JavaScript &middot; works in any browser. Controls POST to the hub; no app, no cloud.</p></header>\n" as *u8) 133 w = cp_cat(h, w, "<main><div class=\"grid\">\n" as *u8) 134 return w 135} 136 137func cp_foot(h: *u8, off: i64) -> i64 { 138 var w: i64 = off 139 w = cp_cat(h, w, "</div></main>\n" as *u8) 140 w = cp_cat(h, w, "<footer style=\"max-width:960px;margin:0 auto;padding:8px 20px 40px;color:#8aa0b4;font-size:12px\">Emitted by <code>nx_iot_control_page</code> &middot; 0 &lt;script&gt; &middot; reproducible: open in Nishi / Chrome / Firefox / Edge</footer>\n" as *u8) 141 w = cp_cat(h, w, "</body></html>\n" as *u8) 142 return w 143} 144 145func main() -> i64 { 146 let h: *u8 = sys_mmap(CP_CAP) 147 var w: i64 = 0 148 w = cp_head(h, w) 149 w = cp_card(h, w, "d8:0d:17:a1:c2:09" as *u8, "Kitchen Switch" as *u8, "Kitchen" as *u8, "Kasa HS210" as *u8, NX_IOT_CLASS_SWITCH, 1) 150 w = cp_card(h, w, "a8:bb:50:11:22:33" as *u8, "Living Room Lamp" as *u8, "Living Room" as *u8, "WiZ colour" as *u8, NX_IOT_CLASS_BULB_COLOR, 0) 151 w = cp_card(h, w, "magichome-aabbcc" as *u8, "Hallway Dimmer" as *u8, "Hallway" as *u8, "dimmer" as *u8, NX_IOT_CLASS_DIMMER, 1) 152 w = cp_foot(h, w) 153 let fd: i64 = sys_openat_wr(CP_OUT, 0x1a4) 154 if fd < 0 { sys_write(2, "FATAL: cannot open web_assets/iot_control.html\n" as *u8, 47); sys_exit(2); return 2 } 155 sys_write(fd, h, w) 156 sys_close(fd) 157 sys_write(1, "[nx_iot_control_page] wrote iot_control.html\n" as *u8, 45) 158 sys_exit(0) 159 return 0 160}