fiatlux/firmware/webdir/index.htmll
2021-11-21 00:34:53 +01:00

1 line
No EOL
14 KiB
Text

<!DOCTYPE html> <html lang=en > <meta charset=utf-8 /> <title>fiatlux v0.2</title> <link rel=stylesheet href="css/picnic.min.css"> <link rel=stylesheet href="css/style.css"> <meta name=viewport content="width=device-width, initial-scale=1.0"> <nav> <a href="#" class=brand > <span>fiatlux v0.2</span> <span class="label warning" id=status_box >Loading...</span> </a> <input id=bmenub type=checkbox class=show > <label for=bmenub class="burger pseudo button">&#9776;</label> <div class=menu > <a href="/#" class="button icon-picture">Dashboard</a> <a href="/#io" class="button icon-puzzle">I/O</a> <a href="/#wifi" class="button icon-puzzle">Wifi Settings</a> <a href="/#ota" class="button icon-picture">System</a> </div> </nav> <main id=page > <section id=ota > <h2>System</h2> <article class=card > <header> <h3>Firmware Update</h3> </header> <div class=table > <div class=row > <span><input id=firmware_file type=file onchange="load_firmware(event)"/></span> </div> <div class=row > <span><input id=transmit_firmware disabled type=submit value=Upload onclick="transmit_firmware(event)"></span> </div> <div class=row id=progress_ct_ota > <div id=progress_bar_ota style="display: block; width:9%; background-color: #00b; padding: unset"></div> </div> </div> </article> <article class=card > <header> <h3>Restart</h3> </header> <div class=table > <div class=row > <span><input type=submit value=Restart onclick="wsWrite('R')"></span> </div> </div> </article> <article class=card > <header> <h3>Reset Config</h3> </header> <div class=table > <div class=row > <span><input type=submit class=warning value=Reset onclick="wsWrite('X')"></span> </div> </div> </article> </section> <section id=io > <h2>I/O</h2> <article class=card > <header> <h3>Protocols</h3> </header> <div class=table > <div class=row > <label>MQTT</label> <span><input type=checkbox class=plain /></span> </div> <div class=row > <span><input type=reset class=button /></span> <span><input type=submit value=Save ></span> </div> </div> </article> <article class=card > <header> <h3>Station Mode <span class="label success">current connection</span></h3> </header> <div class=table > <div class=row > <label>WS2812 via I2S</label> <span><input type=checkbox class=plain /></span> <span><input type=color /></span> </div> <div class=row > <label>APA102C via SPI</label> <span><input type=checkbox class=plain /></span> <span><input type=color /></span> </div> <div class=row > <label>6 Channel SPI Dimmer</label> <span><input type=checkbox class=plain /></span> <span><input type=color /></span> </div> <div class=row > <label>Binary Output on GPIO4</label> <span><input type=checkbox class=plain /></span> <span><button class="toggle button">Toggle</button></span> </div> <div class=row > <label>Binary Output on GPIO5</label> <span><input type=checkbox class=plain /></span> <span><button class="toggle button">Toggle</button></span> </div> <div class=row > <span><input type=reset class=button /></span> <span><input type=submit value=Save ></span> </div> </div> </article> </section> <section id=wifi > <h2>Wifi Settings</h2> <article class=card > <header> <h3>AP Mode</h3> </header> <div class=table > <div class=row > <label>Enable</label> <span><input id=ap_toggle type=checkbox class=plain /></span> </div> <div class=row > <label>SSID</label> <span><input id=ap_ssid type=text placeholder=SSID /></span> </div> <div class=row > <label>Password</label> <span><input id=ap_pw type=password placeholder=Password /></span> </div> <div class=row > <span>AP IP</span> <span><span class=postfill_apip >N/A</span></span> </div> <div class=row > <span>AP MAC</span> <span><span class=postfill_apmac >N/A</span></span> </div> <div class=row > <span><input type=reset class=button /></span> <span><input onclick="ap_update();" type=submit value=Save ></span> </div> </div> </article> <article class=card > <header> <h3>Station Mode <span class="label success">current connection</span></h3> </header> <div class=table > <div class=row > <label>Eanable</label> <span><input id=sta_toggle type=checkbox class=plain /></span> </div> <div class=row > <label>SSID</label> <span><input id=sta_ssid type=text placeholder=SSID /></span> </div> <div class=row > <label>Password</label> <span><input id=sta_pw type=password placeholder=Password /></span> </div> <div class=row > <span>Sation IP</span> <span><span class=postfill_staip >N/A</span></span> </div> <div class=row > <span>Station MAC</span> <span><span class=postfill_stamac >N/A</span></span> </div> <div class=row > <span><input type=reset class=button /></span> <span><input onclick="sta_update();" type=submit value=Save ></span> </div> </div> </article> </section> <section id=dashboard > <h2>Status</h2> <div class=flex > <div> <article class=card > <header> <h3>System</h3> </header> <div class=table > <div class=row > <span>Chip ID</span> <span><span class=postfill_chipid >N/A</span></span> </div> <div class=row > <span>Hostname</span> <span><span class=postfill_hostname >N/A</span></span> </div> <div class=row > <span>Firmware Version</span> <span>N/A</span> </div> <div class=row > <span>Flash ID</span> <span><span class=postfill_flashid >N/A</span></span> </div> <div class=row > <span>Flash size</span> <span><span class=postfill_flashsize >N/A</span> KiB</span> </div> <div class=row > <span>Free heap</span> <span><span class=postfill_heap >N/A</span> bytes</span> </div> <div class=row > <span>Uptime</span> <span><span class=postfill_uptime >N/A</span> s</span> </div> </div> </article> </div> <div> <article class=card > <header> <h3>Network <span class="label success postfill_clientip">current connection</span></h3> </header> <div class=table > <div class=row > <span>Mode</span> <span><span class=postfill_opmode >N/A</span></span> </div> <div class=row > <span>Station SSID</span> <span><span class=postfill_stassid >N/A</span></span> </div> <div class=row > <span>Station IP</span> <span><span class=postfill_staip >N/A</span></span> </div> <div class=row > <span>Station MAC</span> <span><span class=postfill_stamac >N/A</span></span> </div> <div class=row > <span>AP SSID</span> <span><span class=postfill_apssid >N/A</span></span> </div> <div class=row > <span>AP IP</span> <span><span class=postfill_apip >N/A</span></span> </div> <div class=row > <span>AP MAC</span> <span><span class=postfill_apmac >N/A</span></span> </div> </div> </article> </div> <div> <article class=card > <header> <h3>Power</h3> </header> <div class=table > <div class=row > <span>Input</span> <span><span class="label success">5V</span><span class="label success">12V</span></span> </div> <div class=row > <span>Output</span> <span><span class="label warning"><span id=out_voltage >11.2</span>V</span></span> </div> </div> <canvas id=chartCanvas style="height:100px"></canvas> </article> </div> <div> <article class=card > <header> <h3>I/O</h3> </header> <footer> <label> <input type=checkbox name=onoffswitch id=led-switch onclick="gpio()"> <span class="toggle button">toggle signal led</span> </label> </footer> </article> </div> </div> </section> </main> <div id=unused_values style="display:none;"></div> <script type="text/javascript" src="js/smoothie_min.js"></script> <script> var menu = document.getElementById("bmenub"); var voltage = document.getElementById("out_voltage"); var unused_values = {}; DataView.prototype.setChar = function (pos, char) { this.setInt8(pos++, char.charCodeAt(0)); return pos; }; DataView.prototype.setString = function (pos, str) { for (var i = 0; i < str.length; i++) { this.setInt8(pos++, str.charCodeAt(i)); } this.setInt8(pos++, 0); return pos; }; var ws; var retries; var series = new TimeSeries(); function setMsg(cls, text) { sbox = document.getElementById('status_box'); sbox.className = "label " + cls; sbox.innerHTML = text; console.log(text); } function startPolling() { var chart = new SmoothieChart({ millisPerPixel: 111, grid: {fillStyle: '#ffffff', strokeStyle: '#ffffff', borderVisible: false}, labels: {fillStyle: '#000000'}, maxValue: 1024, minValue: 0 }); chart.addTimeSeries(series, {lineWidth: 2, strokeStyle: '#03a9f4', fillStyle: '#f1f5fa'}); var canvas = document.getElementById("chartCanvas"); canvas.width = canvas.clientWidth; canvas.height = canvas.clientHeight; chart.streamTo(canvas, 500); } var receive_chunk_confirmation = () => { }; function onMessage(evt) { retries = 0; if (typeof evt.data == 'string') { var data = JSON.parse(evt.data); for (const [key, value] of Object.entries(data)) { const elements = document.querySelectorAll(".postfill_" + key); if (!elements.length) unused_values[key] = value; else for (i = 0; i < elements.length; ++i) { elements[i].innerHTML = value; } } document.getElementById("unused_values").innerHTML = JSON.stringify(unused_values); } else { var dv = new DataView(evt.data); var cmd = String.fromCharCode(dv.getUint8(0)); var val = dv.getUint16(1); if (cmd === 'G') console.log("LED switched", val); else if ((cmd === 'F') || (cmd === "C")) { receive_chunk_confirmation(dv); } else if (cmd === 'V') { voltage.innerHTML = (val * 13 / 1024).toFixed(2); series.append(new Date().getTime(), val); } else console.log('unknown command', cmd, val); } } function wsOpen() { var uri = "/stream"; if (ws === undefined || ws.readyState !== 0) { if (retries) setMsg("warning", "WebSocket timeout, retrying.."); else setMsg("primary", "Opening WebSocket.."); ws = new WebSocket("ws://" + location.host + uri); ws.binaryType = 'arraybuffer'; ws.onopen = function (evt) { retries = 0; setMsg("success", "WebSocket is open."); }; ws.onerror = function (evt) { console.error(evt); setMsg("error", "WebSocket error!"); /*window.location.reload(true);*/ }; ws.onclose = function (evt) { msgStyle = "warning"; if (!evt.wasClean) msgStyle = "error"; setMsg(msgStyle, "WebSocket closed!"); setTimeout(() => wsOpen(), 0); }; ws.onmessage = function (evt) { onMessage(evt); }; retries = 0; } } function buf2hex(buffer) { // buffer is an ArrayBuffer return [...new Uint8Array(buffer)].map(x => x.toString(16).padStart(2, '0')).join(''); } function wsWrite(data) { //console.info(buf2hex(data)); if (ws.readyState === 3 || retries++ > 5) wsOpen(); else if (ws.readyState === 1) ws.send(data); } function gpio() { if (document.getElementById('led-switch').checked) wsWrite('E'); else wsWrite('D'); } window.onload = function () { wsOpen(); startPolling(); } var sta_toggle = document.getElementById("sta_toggle"); var sta_ssid = document.getElementById("sta_ssid"); var sta_pw = document.getElementById("sta_pw"); function sta_update() { var en = sta_toggle.checked; const ssid = sta_ssid.value; const password = sta_pw.value; const buffer = new ArrayBuffer(ssid.length + password.length + 4); const view1 = new DataView(buffer); var tx_len = 0; view1.setChar(tx_len++, 'S'); view1.setChar(tx_len++, (en ? "E" : "D")); tx_len = view1.setString(tx_len, ssid); tx_len = view1.setString(tx_len, password); wsWrite(buffer); } var ap_toggle = document.getElementById("ap_toggle"); var ap_ssid = document.getElementById("ap_ssid"); var ap_pw = document.getElementById("ap_pw"); function ap_update() { var en = ap_toggle.checked; const ssid = ap_ssid.value; const password = ap_pw.value; const buffer = new ArrayBuffer(ssid.length + password.length + 4); const view1 = new DataView(buffer); var tx_len = 0; view1.setChar(tx_len++, 'A'); view1.setChar(tx_len++, (en ? "E" : "D")); tx_len = view1.setString(tx_len, ssid); tx_len = view1.setString(tx_len, password); wsWrite(buffer); } var makeCRCTable = function () { var c; var crcTable = []; for (var n = 0; n < 256; n++) { c = n; for (var k = 0; k < 8; k++) { c = ((c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1)); } crcTable[n] = c; } return crcTable; } var crc32 = function (buf) { const bufview = new DataView(buf); var crcTable = window.crcTable || (window.crcTable = makeCRCTable()); var crc = 0 ^ (-1); for (var i = 0; i < bufview.byteLength; i++) { crc = (crc >>> 8) ^ crcTable[(crc ^ bufview.getInt8(i)) & 0xFF]; } return (crc ^ (-1)) >>> 0; }; var firmware_file; function load_firmware(evt) { console.log("load_firmware", evt); var file = evt.target.files[0]; if (!file) { return; } var reader = new FileReader(); reader.onload = function (e) { firmware_file = e.target.result; document.getElementById("transmit_firmware").disabled = false; }; reader.readAsArrayBuffer(file) } const chunk_size = 1024; function transmit_firmware_chunk(buf, i) { return new Promise((resolve, reject) => { const begin = i * chunk_size; const end = Math.min((i + 1) * chunk_size, buf.byteLength); const slice = buf.slice(begin, end) var header = new ArrayBuffer(12); var headerview = new DataView(header); headerview.setChar(0, 'F'); headerview.setInt16(4, i); headerview.setInt16(6, (end - begin)); headerview.setInt32(8, crc32(slice)); var frame = new Uint8Array(12 + slice.byteLength); frame.set(new Uint8Array(header), 0); frame.set(new Uint8Array(slice), 12); receive_chunk_confirmation = (dv) => { setTimeout(() => { resolve(i); }, 50); } setTimeout(() => { reject(i); }, 2000); wsWrite(frame.buffer); //build packet: type, seq, len, hash, data console.log(i, (end - begin), crc32(slice), (100*end/buf.byteLength)+"%"); }); } function transmit_firmware_final(buf, hash) { return new Promise((resolve, reject) => { console.log("final: ", buf.byteLength, hash.toString(16)); var frame = new ArrayBuffer(12); var headerview = new DataView(frame); headerview.setChar(0, 'C'); headerview.setInt32(4, buf.byteLength); headerview.setInt32(8, hash); receive_chunk_confirmation = (dv) => { resolve(true); } setTimeout(() => { reject(false); }, 500); wsWrite(frame); }); } function transmit_firmware(evt) { console.log("transmit_firmware", evt); if (firmware_file) { console.log("len", firmware_file.byteLength); //console.log(crc32(firmware_file)); (async () => { const ash = crc32(firmware_file); for (var i = 0; i * chunk_size < firmware_file.byteLength; i++) { update_progress("ota", i * chunk_size / firmware_file.byteLength * 100); await transmit_firmware_chunk(firmware_file, i); } await transmit_firmware_final(firmware_file, crc32(firmware_file)); update_progress("ota", 100); })().then(() => { console.log("transmit_firmware done"); }) } } function update_progress(progressBar, progress) { var iP = Math.floor(progress); var dBar = document.getElementById("progress_bar_" + progressBar); dBar.innerText = iP + "%"; dBar.style.width = progress + "%"; } </script>