makes GPIO_PIN_RST optional for the sx1276
[ExpressLRS.git] / src / html / scan.js
blob745a6adfa16d3b02239bdc17e504d834bfedb13a
1 document.addEventListener("DOMContentLoaded", get_mode, false);
2 var scanTimer = undefined;
4 function _(el) {
5     return document.getElementById(el);
8 function getPwmFormData()
10     let ch = 0;
11     let inField;
12     let outData = [];
13     while (inField = _(`pwm_${ch}_ch`))
14     {
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}`);
25         outData.push(raw);
26         ++ch;
27     }
29     let outForm = new FormData();
30     outForm.append('pwm', outData.join(','));
31     return outForm;
34 function updatePwmSettings(arPwm)
36     if (arPwm === undefined)
37         return;
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>`);
59     });
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';
71 function get_mode() {
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;
80             } else {
81                 _('apmode').style.display = 'block';
82                 if (data.ssid) {
83                     _('homenet').textContent = data.ssid;
84                 } else {
85                     _('connect').style.display = 'none';
86                 }
87                 scanTimer = setInterval(get_networks, 2000);
88             }
89             if (data.modelid !== undefined)
90                 _('modelid').value = data.modelid;
91             updatePwmSettings(data.pwm);
92         }
93     };
94     xmlhttp.open("POST", json_url, true);
95     xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
96     xmlhttp.send();
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);
108         }
109     };
110     xmlhttp.open("POST", json_url, true);
111     xmlhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
112     xmlhttp.send();
115 function hasErrorParameter() {
116     var tmp = [], result = false;
117     location.search
118         .substr(1)
119         .split("&")
120         .forEach(function (item) {
121             tmp = item.split("=");
122             if (tmp[0] === "error") result = true;
123         });
124     return result;
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';
131     }
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:*/
140     var currentFocus;
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*/
146         closeAllLists();
147         currentFocus = -1;
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:*/
171                     closeAllLists();
172                 })(b));
173                 a.appendChild(b);
174             }
175         }
176     }
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:*/
187             currentFocus++;
188             /*and and make the current item more visible:*/
189             addActive(x);
190         } else if (e.keyCode == 38) { //up
191             /*If the arrow UP key is pressed,
192             decrease the currentFocus variable:*/
193             currentFocus--;
194             /*and and make the current item more visible:*/
195             addActive(x);
196         } else if (e.keyCode == 13) {
197             /*If the ENTER key is pressed, prevent the form from being submitted,*/
198             e.preventDefault();
199             if (currentFocus > -1) {
200                 /*and simulate a click on the "active" item:*/
201                 if (x) x[currentFocus].click();
202             }
203         }
204     });
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:*/
209         removeActive(x);
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");
214     }
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");
219         }
220     }
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]);
228             }
229         }
230     }
231     /*execute a function when someone clicks in the document:*/
232     document.addEventListener("click", (e) => {
233         closeAllLists(e.target);
234     });
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");
249     ajax.send(formdata);
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() {
265             cuteAlert({
266                 type: 'success',
267                 title: "Update Succeeded",
268                 message: data.msg
269             });
270         }
271         // This is basically a delayed display of the success dialog with a fake progress
272         var percent = 0;
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;
281                 show_message();
282             }
283         }, 100);
284     } else if (data.status === 'mismatch') {
285         cuteAlert({
286             type: 'question',
287             title: "Targets Mismatch",
288             message: data.msg,
289             confirmText: "Flash anyway",
290             cancelText: "Cancel"
291         }).then((e)=>{
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);
299                         cuteAlert({
300                             type: "info",
301                             title: "Force Update",
302                             message: data.msg
303                         });
304                     }
305                     else {
306                         cuteAlert({
307                             type: "error",
308                             title: "Force Update",
309                             message: "An error occurred trying to force the update"
310                         });
311                     }
312                 }
313             };
314             xmlhttp.open("POST", "/forceupdate", true);
315             var data = new FormData();
316             data.append("action", e);
317             xmlhttp.send(data);
318         });
319     } else {
320         cuteAlert({
321             type: 'error',
322             title: "Update Failed",
323             message: data.msg
324         });
325     }
328 function errorHandler(event) {
329     _("status").innerHTML = "";
330     _("progressBar").value = 0;
331     cuteAlert({
332         type: "error",
333         title: "Update Failed",
334         message: event.target.responseText
335     });
338 function abortHandler(event) {
339     _("status").innerHTML = "";
340     _("progressBar").value = 0;
341     cuteAlert({
342         type: "info",
343         title: "Update Aborted",
344         message: event.target.responseText
345     });
348 _('upload_form').addEventListener('submit', (e) => {
349     e.preventDefault();
350     uploadFile();
353 //=========================================================
355 function callback(title, msg, url, getdata) {
356     return function(e) {
357         e.stopPropagation();
358         e.preventDefault();
359         xmlhttp = new XMLHttpRequest();
360         xmlhttp.onreadystatechange = function () {
361             if (this.readyState == 4) {
362                 if (this.status == 200) {
363                     cuteAlert({
364                         type: "info",
365                         title: title,
366                         message: this.responseText
367                     });
368                 }
369                 else {
370                     cuteAlert({
371                         type: "error",
372                         title: title,
373                         message: msg
374                     });
375                 }
376             }
377         };
378         xmlhttp.open("POST", url, true);
379         if (getdata) data = getdata();
380         else data = null;
381         xmlhttp.send(data);
382     }
385 _('sethome').addEventListener('submit', callback("Set Home Network", "An error occurred setting the home network", "/sethome", function() {
386     return new FormData(_('sethome'));
387 }));
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/
400 function cuteAlert({
401     type,
402     title,
403     message,
404     buttonText = "OK",
405     confirmText = "OK",
406     cancelText = "Cancel",
407     closeStyle,
408   }) {
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";
418       }
420       let btnTemplate = `<button class="alert-button ${type}-bg ${type}-btn">${buttonText}</button>`;
421       if (type === "question") {
422         btnTemplate = `
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>
426 </div>
428       }
430       let svgTemplate = `
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"/>
435 </svg>
437       if (type === "success") {
438         svgTemplate = `
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"/>
442 </svg>
444       }
445       if (type === "info") {
446         svgTemplate = `
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"/>
450 </svg>
452       }
454       const template = `
455 <div class="alert-wrapper">
456   <div class="alert-frame">
457     <div class="alert-header ${type}-bg">
458       <span class="${closeStyleTemplate}">X</span>
459       ${svgTemplate}
460     </div>
461     <div class="alert-body">
462       <span class="alert-title">${title}</span>
463       <span class="alert-message">${message}</span>
464       ${btnTemplate}
465     </div>
466   </div>
467 </div>
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();
478         resolve();
479       }
480       function confirmIt() {
481         alertWrapper.remove();
482         resolve("confirm");
483       }
484       function stopProp(e) {
485         e.stopPropagation();
486       }
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);
494       } else {
495         const alertButton = document.querySelector(".alert-button");
497         alertButton.addEventListener("click", resolveIt);
498       }
500       alertClose.addEventListener("click", resolveIt);
501       alertWrapper.addEventListener("click", resolveIt);
502       alertFrame.addEventListener("click", stopProp);
503     });
504   }