1 document.addEventListener("DOMContentLoaded", get_mode, false);
2 var scanTimer = undefined;
5 return document.getElementById(el);
8 function getPwmFormData()
13 while (inField = _(`pwm_${ch}_ch`))
15 let inChannel = inField.value;
16 let invert = _(`pwm_${ch}_inv`).checked ? 1 : 0;
17 let failsafeField = _(`pwm_${ch}_fs`);
18 let failsafe = failsafeField.value;
19 if (failsafe > 2011) failsafe = 2011;
20 if (failsafe < 988) failsafe = 988;
21 failsafeField.value = failsafe;
23 let raw = (invert << 14) | (inChannel << 10) | (failsafe - 988);
24 //console.log(`PWM ${ch} input=${inChannel} fs=${failsafe} inv=${invert} raw=${raw}`);
29 let outForm = new FormData();
30 outForm.append('pwm', outData.join(','));
34 function updatePwmSettings(arPwm)
36 if (arPwm === undefined)
38 // arPwm is an array of raw integers [49664,50688,51200]. 10 bits of failsafe position, 4 bits of input channel, 1 bit invert
39 let htmlFields = ['<table class="pwmtbl"><tr><th>Output</th><th>Input</th><th>Invert?</th><th>Failsafe</th></tr>'];
40 arPwm.forEach((item, index) => {
41 let failsafe = (item & 1023) + 988; // 10 bits
42 let ch = (item >> 10) & 15; // 4 bits
43 let inv = (item >> 14) & 1;
44 htmlFields.push(`<tr><th>${index+1}</th><td><select id="pwm_${index}_ch">
45 <option value="0"${(ch===0) ? ' selected' : ''}>ch1</option>
46 <option value="1"${(ch===1) ? ' selected' : ''}>ch2</option>
47 <option value="2"${(ch===2) ? ' selected' : ''}>ch3</option>
48 <option value="3"${(ch===3) ? ' selected' : ''}>ch4</option>
49 <option value="4"${(ch===4) ? ' selected' : ''}>ch5 (AUX1)</option>
50 <option value="5"${(ch===5) ? ' selected' : ''}>ch6 (AUX2)</option>
51 <option value="6"${(ch===6) ? ' selected' : ''}>ch7 (AUX3)</option>
52 <option value="7"${(ch===7) ? ' selected' : ''}>ch8 (AUX4)</option>
53 <option value="8"${(ch===8) ? ' selected' : ''}>ch9 (AUX5)</option>
54 <option value="9"${(ch===9) ? ' selected' : ''}>ch10 (AUX6)</option>
55 <option value="10"${(ch===10) ? ' selected' : ''}>ch11 (AUX7)</option>
56 <option value="11"${(ch===11) ? ' selected' : ''}>ch12 (AUX8)</option>
57 </select></td><td><input type="checkbox" id="pwm_${index}_inv"${(inv) ? ' checked' : ''}></td>
58 <td><input id="pwm_${index}_fs" value="${failsafe}" size="4"/></td></tr>`);
60 htmlFields.push('<tr><td colspan="4"><input type="submit" value="Set PWM Output"></td></tr></table>');
62 let grp = document.createElement('DIV');
63 grp.setAttribute('class', 'group');
64 grp.innerHTML = htmlFields.join('');
66 _('pwm').appendChild(grp);
67 _('pwm').addEventListener('submit', callback('Set PWM Output', 'Unknown error', '/pwm', getPwmFormData));
68 _('pwm_container').style.display = 'block';
72 var json_url = 'mode.json';
73 xmlhttp = new XMLHttpRequest();
74 xmlhttp.onreadystatechange = function () {
75 if (this.readyState == 4 && this.status == 200) {
76 var data = JSON.parse(this.responseText);
77 if (data.mode==="STA") {
78 _('stamode').style.display = 'block';
79 _('ssid').textContent = data.ssid;
81 _('apmode').style.display = 'block';
83 _('homenet').textContent = data.ssid;
85 _('connect').style.display = 'none';
87 scanTimer = setInterval(get_networks, 2000);
89 if (data.modelid !== undefined)
90 _('modelid').value = data.modelid;
91 updatePwmSettings(data.pwm);
94 xmlhttp.open("POST", json_url, true);
95 xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
99 function get_networks() {
100 var json_url = 'networks.json';
101 xmlhttp = new XMLHttpRequest();
102 xmlhttp.onreadystatechange = function () {
103 if (this.readyState == 4 && this.status == 200) {
104 var data = JSON.parse(this.responseText);
105 _('loader').style.display = 'none';
106 autocomplete(_('network'), data);
107 clearInterval(scanTimer);
110 xmlhttp.open("POST", json_url, true);
111 xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
115 function hasErrorParameter() {
116 var tmp = [], result = false;
120 .forEach(function (item) {
121 tmp = item.split("=");
122 if (tmp[0] === "error") result = true;
127 function show(elements, specifiedDisplay) {
128 elements = elements.length ? elements : [elements];
129 for (var index = 0; index < elements.length; index++) {
130 elements[index].style.display = specifiedDisplay || 'block';
134 var elements = document.querySelectorAll('#failed');
135 if (hasErrorParameter()) show(elements);
137 function autocomplete(inp, arr) {
138 /*the autocomplete function takes two arguments,
139 the text field element and an array of possible autocompleted values:*/
142 /*execute a function when someone writes in the text field:*/
143 function handler(e) {
144 var a, b, i, val = this.value;
145 /*close any already open lists of autocompleted values*/
148 /*create a DIV element that will contain the items (values):*/
149 a = document.createElement("DIV");
150 a.setAttribute("id", this.id + "autocomplete-list");
151 a.setAttribute("class", "autocomplete-items");
152 /*append the DIV element as a child of the autocomplete container:*/
153 this.parentNode.appendChild(a);
154 /*for each item in the array...*/
155 for (i = 0; i < arr.length; i++) {
156 /*check if the item starts with the same letters as the text field value:*/
157 if (arr[i].substr(0, val.length).toUpperCase() == val.toUpperCase()) {
158 /*create a DIV element for each matching element:*/
159 b = document.createElement("DIV");
160 /*make the matching letters bold:*/
161 b.innerHTML = "<strong>" + arr[i].substr(0, val.length) + "</strong>";
162 b.innerHTML += arr[i].substr(val.length);
163 /*insert a input field that will hold the current array item's value:*/
164 b.innerHTML += "<input type='hidden' value='" + arr[i] + "'>";
165 /*execute a function when someone clicks on the item value (DIV element):*/
166 b.addEventListener("click", ((arg) => (e) => {
167 /*insert the value for the autocomplete text field:*/
168 inp.value = arg.getElementsByTagName("input")[0].value;
169 /*close the list of autocompleted values,
170 (or any other open lists of autocompleted values:*/
177 inp.addEventListener("input", handler);
178 inp.addEventListener("click", handler);
180 /*execute a function presses a key on the keyboard:*/
181 inp.addEventListener("keydown", (e) => {
182 var x = _(this.id + "autocomplete-list");
183 if (x) x = x.getElementsByTagName("div");
184 if (e.keyCode == 40) {
185 /*If the arrow DOWN key is pressed,
186 increase the currentFocus variable:*/
188 /*and and make the current item more visible:*/
190 } else if (e.keyCode == 38) { //up
191 /*If the arrow UP key is pressed,
192 decrease the currentFocus variable:*/
194 /*and and make the current item more visible:*/
196 } else if (e.keyCode == 13) {
197 /*If the ENTER key is pressed, prevent the form from being submitted,*/
199 if (currentFocus > -1) {
200 /*and simulate a click on the "active" item:*/
201 if (x) x[currentFocus].click();
205 function addActive(x) {
206 /*a function to classify an item as "active":*/
207 if (!x) return false;
208 /*start by removing the "active" class on all items:*/
210 if (currentFocus >= x.length) currentFocus = 0;
211 if (currentFocus < 0) currentFocus = (x.length - 1);
212 /*add class "autocomplete-active":*/
213 x[currentFocus].classList.add("autocomplete-active");
215 function removeActive(x) {
216 /*a function to remove the "active" class from all autocomplete items:*/
217 for (var i = 0; i < x.length; i++) {
218 x[i].classList.remove("autocomplete-active");
221 function closeAllLists(elmnt) {
222 /*close all autocomplete lists in the document,
223 except the one passed as an argument:*/
224 var x = document.getElementsByClassName("autocomplete-items");
225 for (var i = 0; i < x.length; i++) {
226 if (elmnt != x[i] && elmnt != inp) {
227 x[i].parentNode.removeChild(x[i]);
231 /*execute a function when someone clicks in the document:*/
232 document.addEventListener("click", (e) => {
233 closeAllLists(e.target);
237 //=========================================================
239 function uploadFile() {
240 var file = _("firmware_file").files[0];
241 var formdata = new FormData();
242 formdata.append("upload", file, file.name);
243 var ajax = new XMLHttpRequest();
244 ajax.upload.addEventListener("progress", progressHandler, false);
245 ajax.addEventListener("load", completeHandler, false);
246 ajax.addEventListener("error", errorHandler, false);
247 ajax.addEventListener("abort", abortHandler, false);
248 ajax.open("POST", "/update");
252 function progressHandler(event) {
253 //_("loaded_n_total").innerHTML = "Uploaded " + event.loaded + " bytes of " + event.total;
254 var percent = Math.round((event.loaded / event.total) * 100);
255 _("progressBar").value = percent;
256 _("status").innerHTML = percent + "% uploaded... please wait";
259 function completeHandler(event) {
260 _("status").innerHTML = "";
261 _("progressBar").value = 0;
262 var data = JSON.parse(event.target.responseText);
263 if (data.status === 'ok') {
264 function show_message() {
267 title: "Update Succeeded",
271 // This is basically a delayed display of the success dialog with a fake progress
273 var interval = setInterval(()=>{
274 percent = percent + 2;
275 _("progressBar").value = percent;
276 _("status").innerHTML = percent + "% flashed... please wait";
277 if (percent == 100) {
278 clearInterval(interval);
279 _("status").innerHTML = "";
280 _("progressBar").value = 0;
284 } else if (data.status === 'mismatch') {
287 title: "Targets Mismatch",
289 confirmText: "Flash anyway",
292 xmlhttp = new XMLHttpRequest();
293 xmlhttp.onreadystatechange = function () {
294 if (this.readyState == 4) {
295 _("status").innerHTML = "";
296 _("progressBar").value = 0;
297 if (this.status == 200) {
298 var data = JSON.parse(this.responseText);
301 title: "Force Update",
308 title: "Force Update",
309 message: "An error occurred trying to force the update"
314 xmlhttp.open("POST", "/forceupdate", true);
315 var data = new FormData();
316 data.append("action", e);
322 title: "Update Failed",
328 function errorHandler(event) {
329 _("status").innerHTML = "";
330 _("progressBar").value = 0;
333 title: "Update Failed",
334 message: event.target.responseText
338 function abortHandler(event) {
339 _("status").innerHTML = "";
340 _("progressBar").value = 0;
343 title: "Update Aborted",
344 message: event.target.responseText
348 _('upload_form').addEventListener('submit', (e) => {
353 //=========================================================
355 function callback(title, msg, url, getdata) {
359 xmlhttp = new XMLHttpRequest();
360 xmlhttp.onreadystatechange = function () {
361 if (this.readyState == 4) {
362 if (this.status == 200) {
366 message: this.responseText
378 xmlhttp.open("POST", url, true);
379 if (getdata) data = getdata();
385 _('sethome').addEventListener('submit', callback("Set Home Network", "An error occurred setting the home network", "/sethome", function() {
386 return new FormData(_('sethome'));
388 _('connect').addEventListener('click', callback("Connect to Home Network", "An error occurred connecting to the Home network", "/connect", null));
389 _('access').addEventListener('click', callback("Access Point", "An error occurred starting the Access Point", "/access", null));
390 _('forget').addEventListener('click', callback("Forget Home Network", "An error occurred forgetting the home network", "/forget", null));
391 if (_('modelmatch') != undefined) {
392 _('modelmatch').addEventListener('submit', callback("Set Model Match", "An error occurred updating the model match number", "/model",
393 () => { return new FormData(_('modelmatch')); }));
396 //=========================================================
398 // Alert box design by Igor Ferrão de Souza: https://www.linkedin.com/in/igor-ferr%C3%A3o-de-souza-4122407b/
406 cancelText = "Cancel",
409 return new Promise((resolve) => {
410 setInterval(() => {}, 5000);
411 const body = document.querySelector("body");
413 const scripts = document.getElementsByTagName("script");
415 let closeStyleTemplate = "alert-close";
416 if (closeStyle === "circle") {
417 closeStyleTemplate = "alert-close-circle";
420 let btnTemplate = `<button class="alert-button ${type}-bg ${type}-btn">${buttonText}</button>`;
421 if (type === "question") {
423 <div class="question-buttons">
424 <button class="confirm-button error-bg error-btn">${confirmText}</button>
425 <button class="cancel-button question-bg question-btn">${cancelText}</button>
431 <svg class="alert-img" xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 52 52" xmlns:v="https://vecta.io/nano">
432 <path d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm0 50C12.767 50 2 39.233 2 26S12.767 2 26 2s24 10.767 24 24-10.767 24-24
433 24zm9.707-33.707a1 1 0 0 0-1.414 0L26 24.586l-8.293-8.293a1 1 0 0 0-1.414 1.414L24.586 26l-8.293 8.293a1 1 0 0 0 0 1.414c.195.195.451.293.707.293s.512-.098.707
434 -.293L26 27.414l8.293 8.293c.195.195.451.293.707.293s.512-.098.707-.293a1 1 0 0 0 0-1.414L27.414 26l8.293-8.293a1 1 0 0 0 0-1.414z"/>
437 if (type === "success") {
439 <svg class="alert-img" xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 52 52" xmlns:v="https://vecta.io/nano">
440 <path d="M26 0C11.664 0 0 11.663 0 26s11.664 26 26 26 26-11.663 26-26S40.336 0 26 0zm0 50C12.767 50 2 39.233 2 26S12.767 2 26 2s24 10.767 24 24-10.767 24-24
441 24zm12.252-34.664l-15.369 17.29-9.259-7.407a1 1 0 0 0-1.249 1.562l10 8a1 1 0 0 0 1.373-.117l16-18a1 1 0 1 0-1.496-1.328z"/>
445 if (type === "info") {
447 <svg class="alert-img" xmlns="http://www.w3.org/2000/svg" fill="#fff" viewBox="0 0 64 64" xmlns:v="https://vecta.io/nano">
448 <path d="M38.535 47.606h-4.08V28.447a1 1 0 0 0-1-1h-4.52a1 1 0 1 0 0 2h3.52v18.159h-5.122a1 1 0 1 0 0 2h11.202a1 1 0 1 0 0-2z"/>
449 <circle cx="32" cy="18" r="3"/><path d="M32 0C14.327 0 0 14.327 0 32s14.327 32 32 32 32-14.327 32-32S49.673 0 32 0zm0 62C15.458 62 2 48.542 2 32S15.458 2 32 2s30 13.458 30 30-13.458 30-30 30z"/>
455 <div class="alert-wrapper">
456 <div class="alert-frame">
457 <div class="alert-header ${type}-bg">
458 <span class="${closeStyleTemplate}">X</span>
461 <div class="alert-body">
462 <span class="alert-title">${title}</span>
463 <span class="alert-message">${message}</span>
470 body.insertAdjacentHTML("afterend", template);
472 const alertWrapper = document.querySelector(".alert-wrapper");
473 const alertFrame = document.querySelector(".alert-frame");
474 const alertClose = document.querySelector(`.${closeStyleTemplate}`);
476 function resolveIt() {
477 alertWrapper.remove();
480 function confirmIt() {
481 alertWrapper.remove();
484 function stopProp(e) {
488 if (type === "question") {
489 const confirmButton = document.querySelector(".confirm-button");
490 const cancelButton = document.querySelector(".cancel-button");
492 confirmButton.addEventListener("click", confirmIt);
493 cancelButton.addEventListener("click", resolveIt);
495 const alertButton = document.querySelector(".alert-button");
497 alertButton.addEventListener("click", resolveIt);
500 alertClose.addEventListener("click", resolveIt);
501 alertWrapper.addEventListener("click", resolveIt);
502 alertFrame.addEventListener("click", stopProp);