<!DOCTYPE html> <html lang="en"> <head> <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"> </head> <body> <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">☰</label> <div class="menu"> <a href="/#" class="button icon-picture">Dashboard</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"> <div class="full" id="progress_ct_ota"> <div id="progress_bar_ota" style="background:#0074d9; display: block; width:9%; color: #fff"></div> </div> </div> <div class="row"> <span><input id="transmit_firmware" disabled type="submit" value="Upload" onclick="transmit_firmware(event)"></span> </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> <article class="card"> <header> <h3>Syslog</h3> </header> <div class="table"> <div class="row"> <pre id="syslog"></pre> </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 syslog = document.getElementById("syslog"); var unused_values = {}; DataView.prototype.setChar = function (pos, char) { this.setInt8(pos++, char.charCodeAt(0)); return pos; }; DataView.prototype.getChar = function (pos) { return String.fromCharCode(this.getInt8(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.info(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 if (cmd === 'L') { var len = dv.getUint8(1); var offset = dv.getUint16(2); var str = ""; for (var i = 0; i < len; i++) str += dv.getChar(4 + i); syslog.innerHTML = syslog.innerHTML.slice(0, offset) + str; } 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 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) { 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 = 512; 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({cmd: dv.getChar(0), ret: dv.getUint8(1), val: dv.getUint16(2)}); }, 50); } setTimeout(() => { reject({frame_error: i}); }, 2000); wsWrite(frame.buffer); }); } function transmit_firmware_final(buf, hash) { return new Promise((resolve, reject) => { 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({cmd: dv.getChar(0), ret: dv.getUint8(1), val: dv.getUint16(2)}); } setTimeout(() => { reject({final_error: 0}); }, 500); wsWrite(frame); }); } function transmit_firmware(evt) { console.log("transmit_firmware begin"); if (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); const reply = await transmit_firmware_chunk(firmware_file, i); console.log("reply", reply) } 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> </body> </html>