code.js 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324
  1. // First, checks if it isn't implemented yet.
  2. if (!String.prototype.format) {
  3. String.prototype.format = function() {
  4. var args = arguments;
  5. return this.replace(/{(\d+)}/g, function(match, number) {
  6. return typeof args[number] != 'undefined' ?
  7. args[number] :
  8. match;
  9. });
  10. };
  11. }
  12. if (!String.prototype.encodeHTML) {
  13. String.prototype.encodeHTML = function () {
  14. return this.replace(/&/g, '&')
  15. .replace(/</g, '&lt;')
  16. .replace(/>/g, '&gt;')
  17. .replace(/"/g, '&quot;')
  18. .replace(/'/g, '&apos;');
  19. };
  20. }
  21. var nvs_type_t = {
  22. NVS_TYPE_U8: 0x01,
  23. /*!< Type uint8_t */
  24. NVS_TYPE_I8: 0x11,
  25. /*!< Type int8_t */
  26. NVS_TYPE_U16: 0x02,
  27. /*!< Type uint16_t */
  28. NVS_TYPE_I16: 0x12,
  29. /*!< Type int16_t */
  30. NVS_TYPE_U32: 0x04,
  31. /*!< Type uint32_t */
  32. NVS_TYPE_I32: 0x14,
  33. /*!< Type int32_t */
  34. NVS_TYPE_U64: 0x08,
  35. /*!< Type uint64_t */
  36. NVS_TYPE_I64: 0x18,
  37. /*!< Type int64_t */
  38. NVS_TYPE_STR: 0x21,
  39. /*!< Type string */
  40. NVS_TYPE_BLOB: 0x42,
  41. /*!< Type blob */
  42. NVS_TYPE_ANY: 0xff /*!< Must be last */
  43. };
  44. pillcolors = {
  45. 'MESSAGING_INFO' : 'badge-success',
  46. 'MESSAGING_WARNING' : 'badge-warning',
  47. 'MESSAGING_ERROR' : 'badge-danger'
  48. }
  49. var task_state_t = {
  50. 0: "eRunning",
  51. /*!< A task is querying the state of itself, so must be running. */
  52. 1: "eReady",
  53. /*!< The task being queried is in a read or pending ready list. */
  54. 2: "eBlocked",
  55. /*!< The task being queried is in the Blocked state. */
  56. 3: "eSuspended",
  57. /*!< The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */
  58. 4: "eDeleted"
  59. }
  60. var escapeHTML = function(unsafe) {
  61. return unsafe.replace(/[&<"']/g, function(m) {
  62. switch (m) {
  63. case '&':
  64. return '&amp;';
  65. case '<':
  66. return '&lt;';
  67. case '"':
  68. return '&quot;';
  69. default:
  70. return '&#039;';
  71. }
  72. });
  73. };
  74. function handleTemplateTypeRadio(outtype){
  75. if (outtype == 'bt') {
  76. $("#btsinkdiv").parent().show(200);
  77. $("#btsinkdiv").show();
  78. $('#bt').prop('checked',true);
  79. o_bt.setAttribute("display", "inline");
  80. o_spdif.setAttribute("display", "none");
  81. o_i2s.setAttribute("display", "none");
  82. output = 'bt';
  83. } else if (outtype == 'spdif') {
  84. $("#btsinkdiv").parent().hide(200);
  85. $("#btsinkdiv").show();
  86. $('#spdif').prop('checked',true);
  87. o_bt.setAttribute("display", "none");
  88. o_spdif.setAttribute("display", "inline");
  89. o_i2s.setAttribute("display", "none");
  90. output = 'spdif';
  91. } else {
  92. $("#btsinkdiv").parent().hide(200);
  93. $("#btsinkdiv").show();
  94. $('#i2s').prop('checked',true);
  95. o_bt.setAttribute("display", "none");
  96. o_spdif.setAttribute("display", "none");
  97. o_i2s.setAttribute("display", "inline");
  98. output = 'i2s';
  99. }
  100. }
  101. function handleExceptionResponse(xhr, ajaxOptions, thrownError){
  102. console.log(xhr.status);
  103. console.log(thrownError);
  104. if (thrownError != '') showLocalMessage(thrownError, 'MESSAGING_ERROR');
  105. }
  106. function HideCmdMessage(cmdname){
  107. $('#toast_'+cmdname).css('display','none');
  108. $('#toast_'+cmdname).removeClass('table-success').removeClass('table-warning').removeClass('table-danger').addClass('table-success');
  109. $('#msg_'+cmdname).html('');
  110. }
  111. function showCmdMessage(cmdname,msgtype, msgtext,append=false){
  112. color='table-success';
  113. if (msgtype == 'MESSAGING_WARNING') {
  114. color='table-warning';
  115. } else if (msgtype == 'MESSAGING_ERROR') {
  116. color ='table-danger';
  117. }
  118. $('#toast_'+cmdname).css('display','block');
  119. $('#toast_'+cmdname).removeClass('table-success').removeClass('table-warning').removeClass('table-danger').addClass(color);
  120. escapedtext=escapeHTML(msgtext.substring(0, msgtext.length - 1)).replace(/\n/g, '<br />');
  121. escapedtext=($('#msg_'+cmdname).html().length>0 && append?$('#msg_'+cmdname).html()+'<br/>':'')+escapedtext;
  122. $('#msg_'+cmdname).html(escapedtext);
  123. }
  124. var releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases';
  125. var recovery = false;
  126. var enableAPTimer = true;
  127. var enableStatusTimer = true;
  128. var commandHeader = 'squeezelite -b 500:2000 -d all=info -C 30 -W';
  129. var pname, ver, otapct, otadsc;
  130. var blockAjax = false;
  131. var blockFlashButton = false;
  132. var dblclickCounter = 0;
  133. var apList = null;
  134. var selectedSSID = "";
  135. var refreshAPInterval = null;
  136. var checkStatusInterval = null;
  137. var messagecount=0;
  138. var messageseverity="MESSAGING_INFO";
  139. var StatusIntervalActive = false;
  140. var RefreshAPIIntervalActive = false;
  141. var LastRecoveryState = null;
  142. var LastCommandsState = null;
  143. var output = '';
  144. Promise.prototype.delay = function(duration) {
  145. return this.then(function(value) {
  146. return new Promise(function(resolve) {
  147. setTimeout(function() {
  148. resolve(value)
  149. }, duration)
  150. })
  151. }, function(reason) {
  152. return new Promise(function(resolve, reject) {
  153. setTimeout(function() {
  154. reject(reason)
  155. }, duration)
  156. })
  157. })
  158. }
  159. function stopCheckStatusInterval() {
  160. if (checkStatusInterval != null) {
  161. clearTimeout(checkStatusInterval);
  162. checkStatusInterval = null;
  163. }
  164. StatusIntervalActive = false;
  165. }
  166. function stopRefreshAPInterval() {
  167. if (refreshAPInterval != null) {
  168. clearTimeout(refreshAPInterval);
  169. refreshAPInterval = null;
  170. }
  171. RefreshAPIIntervalActive = false;
  172. }
  173. function startCheckStatusInterval() {
  174. StatusIntervalActive = true;
  175. checkStatusInterval = setTimeout(checkStatus, 3000);
  176. }
  177. function startRefreshAPInterval() {
  178. RefreshAPIIntervalActive = true;
  179. refreshAPInterval = setTimeout(refreshAP(false), 4500); // leave enough time for the initial scan
  180. }
  181. function RepeatCheckStatusInterval() {
  182. if (StatusIntervalActive)
  183. startCheckStatusInterval();
  184. }
  185. function RepeatRefreshAPInterval() {
  186. if (RefreshAPIIntervalActive)
  187. startRefreshAPInterval();
  188. }
  189. function getConfigJson(slimMode) {
  190. var config = {};
  191. $("input.nvs").each(function() {
  192. var key = $(this)[0].id;
  193. var val = $(this).val();
  194. if (!slimMode) {
  195. var nvs_type = parseInt($(this)[0].attributes.nvs_type.nodeValue, 10);
  196. if (key != '') {
  197. config[key] = {};
  198. if (nvs_type == nvs_type_t.NVS_TYPE_U8 ||
  199. nvs_type == nvs_type_t.NVS_TYPE_I8 ||
  200. nvs_type == nvs_type_t.NVS_TYPE_U16 ||
  201. nvs_type == nvs_type_t.NVS_TYPE_I16 ||
  202. nvs_type == nvs_type_t.NVS_TYPE_U32 ||
  203. nvs_type == nvs_type_t.NVS_TYPE_I32 ||
  204. nvs_type == nvs_type_t.NVS_TYPE_U64 ||
  205. nvs_type == nvs_type_t.NVS_TYPE_I64) {
  206. config[key].value = parseInt(val);
  207. } else {
  208. config[key].value = val;
  209. }
  210. config[key].type = nvs_type;
  211. }
  212. } else {
  213. config[key] = val;
  214. }
  215. });
  216. var key = $("#nvs-new-key").val();
  217. var val = $("#nvs-new-value").val();
  218. if (key != '') {
  219. if (!slimMode) {
  220. config[key] = {};
  221. config[key].value = val;
  222. config[key].type = 33;
  223. } else {
  224. config[key] = val;
  225. }
  226. }
  227. return config;
  228. }
  229. function onFileLoad(elementId, event) {
  230. var data = {};
  231. try {
  232. data = JSON.parse(elementId.srcElement.result);
  233. } catch (e) {
  234. alert('Parsing failed!\r\n ' + e);
  235. }
  236. $("input.nvs").each(function() {
  237. var key = $(this)[0].id;
  238. var val = $(this).val();
  239. if (data[key]) {
  240. if (data[key] != val) {
  241. console.log("Changed " & key & " " & val & "==>" & data[key]);
  242. $(this).val(data[key]);
  243. }
  244. } else {
  245. console.log("Value " & key & " missing from file");
  246. }
  247. });
  248. }
  249. function onChooseFile(event, onLoadFileHandler) {
  250. if (typeof window.FileReader !== 'function')
  251. throw ("The file API isn't supported on this browser.");
  252. input = event.target;
  253. if (!input)
  254. throw ("The browser does not properly implement the event object");
  255. if (!input.files)
  256. throw ("This browser does not support the `files` property of the file input.");
  257. if (!input.files[0])
  258. return undefined;
  259. file = input.files[0];
  260. fr = new FileReader();
  261. fr.onload = onLoadFileHandler;
  262. fr.readAsText(file);
  263. input.value = "";
  264. }
  265. function delay_reboot(duration,cmdname, ota=false){
  266. url= (ota?'/reboot_ota.json':'/reboot.json');
  267. $("tbody#tasks").empty();
  268. $("#tasks_sect").css('visibility','collapse');
  269. Promise.resolve(cmdname).delay(duration).then(function(cmdname) {
  270. if(cmdname?.length >0){
  271. showCmdMessage(cmdname,'MESSAGING_WARNING','Rebooting the ESP32.\n',true);
  272. }
  273. else {
  274. showLocalMessage('MESSAGING_WARNING','Rebooting the ESP32.\n')
  275. }
  276. console.log('now triggering reboot');
  277. $.ajax({
  278. url: this.url,
  279. dataType: 'text',
  280. method: 'POST',
  281. cache: false,
  282. contentType: 'application/json; charset=utf-8',
  283. data: JSON.stringify({
  284. 'timestamp': Date.now()
  285. }),
  286. error: handleExceptionResponse,
  287. complete: function(response) {
  288. console.log('reboot call completed');
  289. Promise.resolve(cmdname).delay(6000).then(function(cmdname) {
  290. if(cmdname?.length >0) HideCmdMessage(cmdname);
  291. getCommands();
  292. getConfig();
  293. });
  294. }
  295. });
  296. });
  297. }
  298. function save_autoexec1(apply){
  299. showCmdMessage('cfg-audio-tmpl','MESSAGING_INFO',"Saving.\n",false);
  300. var commandLine = commandHeader + ' -n "' + $("#player").val() + '"';
  301. if (output == 'bt') {
  302. if($("#btsinkdiv").val()?.length!=0){
  303. commandLine += ' -o "BT -n \'' + $("#btsinkdiv").val() + '\'" -Z 192000';
  304. }
  305. else {
  306. showCmdMessage('cfg-audio-tmpl','MESSAGING_ERROR',"BT Sink Name required for output bluetooth.\n",true);
  307. return;
  308. }
  309. } else if (output == 'spdif') {
  310. commandLine += ' -o SPDIF -Z 192000';
  311. } else {
  312. commandLine += ' -o I2S';
  313. }
  314. if ($("#optional").val() != '') {
  315. commandLine += ' ' + $("#optional").val();
  316. }
  317. var data = {
  318. 'timestamp': Date.now()
  319. };
  320. autoexec = $("#disable-squeezelite").prop('checked') ? "0" : "1";
  321. data['config'] = {
  322. autoexec1: { value: commandLine, type: 33 },
  323. autoexec: { value: autoexec, type: 33 }
  324. }
  325. $.ajax({
  326. url: '/config.json',
  327. dataType: 'text',
  328. method: 'POST',
  329. cache: false,
  330. contentType: 'application/json; charset=utf-8',
  331. data: JSON.stringify(data),
  332. error: handleExceptionResponse,
  333. complete: function(response) {
  334. if(JSON.parse(response?.responseText)?.result == "OK"){
  335. showCmdMessage('cfg-audio-tmpl','MESSAGING_INFO',"Done.\n",true);
  336. if (apply) {
  337. delay_reboot(1500,"cfg-audio-tmpl");
  338. }
  339. }
  340. else if(response.responseText) {
  341. showCmdMessage('cfg-audio-tmpl','MESSAGING_WARNING',JSON.parse(response.responseText).Result + "\n",true);
  342. }
  343. else {
  344. showCmdMessage('cfg-audio-tmpl','MESSAGING_ERROR',response.responseText+'\n');
  345. }
  346. console.log(response.responseText);
  347. }
  348. });
  349. console.log('sent data:', JSON.stringify(data));
  350. }
  351. $(document).ready(function() {
  352. // $(".dropdown-item").on("click", function(e){
  353. // var linkText = $(e.relatedTarget).text(); // Get the link text
  354. // });
  355. $("input#show-commands")[0].checked = LastCommandsState == 1 ? true : false;
  356. $('a[href^="#tab-commands"]').hide();
  357. $("#load-nvs").click(function() {
  358. $("#nvsfilename").trigger('click');
  359. });
  360. $("#wifi-status").on("click", ".ape", function() {
  361. $("#wifi").slideUp("fast", function() {});
  362. $("#connect-details").slideDown("fast", function() {});
  363. });
  364. $("#clear-syslog").on("click",function(){
  365. messagecount=0;
  366. messageseverity="MESSAGING_INFO";
  367. $('#msgcnt').text('');
  368. $("#syslogTable").html('');
  369. });
  370. $("#manual_add").on("click", ".ape", function() {
  371. selectedSSID = $(this).text();
  372. $("#ssid-pwd").text(selectedSSID);
  373. $("#wifi").slideUp("fast", function() {});
  374. $("#connect_manual").slideDown("fast", function() {});
  375. $("#connect").slideUp("fast", function() {});
  376. //update wait screen
  377. $("#loading").show();
  378. $("#connect-success").hide();
  379. $("#connect-fail").hide();
  380. });
  381. $("#wifi-list").on("click", ".ape", function() {
  382. selectedSSID = $(this).text();
  383. $("#ssid-pwd").text(selectedSSID);
  384. $("#wifi").slideUp("fast", function() {});
  385. $("#connect_manual").slideUp("fast", function() {});
  386. $("#connect").slideDown("fast", function() {});
  387. //update wait screen
  388. $("#loading").show();
  389. $("#connect-success").hide();
  390. $("#connect-fail").hide();
  391. });
  392. $("#cancel").on("click", function() {
  393. selectedSSID = "";
  394. $("#connect").slideUp("fast", function() {});
  395. $("#connect_manual").slideUp("fast", function() {});
  396. $("#wifi").slideDown("fast", function() {});
  397. });
  398. $("#manual_cancel").on("click", function() {
  399. selectedSSID = "";
  400. $("#connect").slideUp("fast", function() {});
  401. $("#connect_manual").slideUp("fast", function() {});
  402. $("#wifi").slideDown("fast", function() {});
  403. });
  404. $("#join").on("click", function() {
  405. performConnect();
  406. });
  407. $("#manual_join").on("click", function() {
  408. performConnect($(this).data('connect'));
  409. });
  410. $("#ok-details").on("click", function() {
  411. $("#connect-details").slideUp("fast", function() {});
  412. $("#wifi").slideDown("fast", function() {});
  413. });
  414. $("#ok-credits").on("click", function() {
  415. $("#credits").slideUp("fast", function() {});
  416. $("#app").slideDown("fast", function() {});
  417. });
  418. $("#acredits").on("click", function(event) {
  419. event.preventDefault();
  420. $("#app").slideUp("fast", function() {});
  421. $("#credits").slideDown("fast", function() {});
  422. });
  423. $("#ok-connect").on("click", function() {
  424. $("#connect-wait").slideUp("fast", function() {});
  425. $("#wifi").slideDown("fast", function() {});
  426. });
  427. $("#disconnect").on("click", function() {
  428. $("#connect-details-wrap").addClass('blur');
  429. $("#diag-disconnect").slideDown("fast", function() {});
  430. });
  431. $("#no-disconnect").on("click", function() {
  432. $("#diag-disconnect").slideUp("fast", function() {});
  433. $("#connect-details-wrap").removeClass('blur');
  434. });
  435. $("#yes-disconnect").on("click", function() {
  436. stopCheckStatusInterval();
  437. selectedSSID = "";
  438. $("#diag-disconnect").slideUp("fast", function() {});
  439. $("#connect-details-wrap").removeClass('blur');
  440. $.ajax({
  441. url: '/connect.json',
  442. dataType: 'text',
  443. method: 'DELETE',
  444. cache: false,
  445. contentType: 'application/json; charset=utf-8',
  446. data: JSON.stringify({
  447. 'timestamp': Date.now()
  448. })
  449. });
  450. startCheckStatusInterval();
  451. $("#connect-details").slideUp("fast", function() {});
  452. $("#wifi").slideDown("fast", function() {})
  453. });
  454. $("input#show-commands").on("click", function() {
  455. this.checked = this.checked ? 1 : 0;
  456. if (this.checked) {
  457. $('a[href^="#tab-commands"]').show();
  458. LastCommandsState = 1;
  459. } else {
  460. LastCommandsState = 0;
  461. $('a[href^="#tab-commands"]').hide();
  462. }
  463. });
  464. $("input#show-nvs").on("click", function() {
  465. this.checked = this.checked ? 1 : 0;
  466. if (this.checked) {
  467. $('a[href^="#tab-nvs"]').show();
  468. } else {
  469. $('a[href^="#tab-nvs"]').hide();
  470. }
  471. });
  472. $("#save-as-nvs").on("click", function() {
  473. var data = {
  474. 'timestamp': Date.now()
  475. };
  476. var config = getConfigJson(true);
  477. const a = document.createElement("a");
  478. a.href = URL.createObjectURL(
  479. new Blob([JSON.stringify(config, null, 2)], {
  480. type: "text/plain"
  481. }));
  482. a.setAttribute("download", "nvs_config" + Date.now() + "json");
  483. document.body.appendChild(a);
  484. a.click();
  485. document.body.removeChild(a);
  486. console.log('sent config JSON with headers:', JSON.stringify(headers));
  487. console.log('sent config JSON with data:', JSON.stringify(data));
  488. });
  489. $("#save-nvs").on("click", function() {
  490. var headers = {};
  491. var data = {
  492. 'timestamp': Date.now()
  493. };
  494. var config = getConfigJson(false);
  495. data['config'] = config;
  496. $.ajax({
  497. url: '/config.json',
  498. dataType: 'text',
  499. method: 'POST',
  500. cache: false,
  501. headers: headers,
  502. contentType: 'application/json; charset=utf-8',
  503. data: JSON.stringify(data),
  504. error: handleExceptionResponse
  505. });
  506. console.log('sent config JSON with headers:', JSON.stringify(headers));
  507. console.log('sent config JSON with data:', JSON.stringify(data));
  508. });
  509. $("#fwUpload").on("click", function() {
  510. var upload_path = "/flash.json";
  511. var fileInput = document.getElementById("flashfilename").files;
  512. if (fileInput.length == 0) {
  513. alert("No file selected!");
  514. } else {
  515. var file = fileInput[0];
  516. var xhttp = new XMLHttpRequest();
  517. xhttp.onreadystatechange = function() {
  518. if (xhttp.readyState == 4) {
  519. if (xhttp.status == 200) {
  520. showLocalMessage(xhttp.responseText, 'MESSAGING_INFO')
  521. } else if (xhttp.status == 0) {
  522. showLocalMessage("Upload connection was closed abruptly!", 'MESSAGING_ERROR');
  523. } else {
  524. showLocalMessage(xhttp.status + " Error!\n" + xhttp.responseText, 'MESSAGING_ERROR');
  525. }
  526. }
  527. };
  528. xhttp.open("POST", upload_path, true);
  529. xhttp.send(file);
  530. }
  531. enableStatusTimer = true;
  532. });
  533. $("#flash").on("click", function() {
  534. var data = {
  535. 'timestamp': Date.now()
  536. };
  537. if (blockFlashButton) return;
  538. blockFlashButton = true;
  539. var url = $("#fwurl").val();
  540. data['config'] = {
  541. fwurl: {
  542. value: url,
  543. type: 33
  544. }
  545. };
  546. $.ajax({
  547. url: '/config.json',
  548. dataType: 'text',
  549. method: 'POST',
  550. cache: false,
  551. contentType: 'application/json; charset=utf-8',
  552. data: JSON.stringify(data),
  553. error: handleExceptionResponse
  554. });
  555. enableStatusTimer = true;
  556. });
  557. $('[name=output-tmpl]').on("click", function() {
  558. handleTemplateTypeRadio(this.id);
  559. });
  560. $('#fwcheck').on("click", function() {
  561. $("#releaseTable").html("");
  562. $.getJSON(releaseURL, function(data) {
  563. var i = 0;
  564. var branches = [];
  565. data.forEach(function(release) {
  566. namecomponents=release.name.split('#');
  567. ver=namecomponents[0];
  568. idf=namecomponents[1];
  569. cfg=namecomponents[2];
  570. branch=namecomponents[3];
  571. if (!branches.includes(branch)) {
  572. branches.push(branch);
  573. }
  574. });
  575. var fwb;
  576. branches.forEach(function(branch) {
  577. fwb += '<option value="' + branch + '">' + branch + '</option>';
  578. });
  579. $("#fwbranch").append(fwb);
  580. data.forEach(function(release) {
  581. var url = '';
  582. release.assets.forEach(function(asset) {
  583. if (asset.name.match(/\.bin$/)) {
  584. url = asset.browser_download_url;
  585. }
  586. });
  587. namecomponents = release.name.split('#');
  588. ver=namecomponents[0];
  589. idf=namecomponents[1];
  590. cfg=namecomponents[2];
  591. branch=namecomponents[3];
  592. var body = release.body;
  593. body = body.replace(/\'/ig, "\"");
  594. body = body.replace(/[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/, "$1");
  595. body = body.replace(/- \(.+?\) /g, "- ");
  596. var [date, time] = release.created_at.split('T');
  597. var trclass = (i++ > 6) ? ' hide' : '';
  598. $("#releaseTable").append(
  599. "<tr class='release" + trclass + "'>" +
  600. "<td data-toggle='tooltip' title='" + body + "'>" + ver + "</td>" +
  601. "<td>" + date + "</td>" +
  602. "<td>" + cfg + "</td>" +
  603. "<td>" + idf + "</td>" +
  604. "<td>" + branch + "</td>" +
  605. "<td><input type='button' class='btn btn-success' value='Select' data-url='" + url + "' onclick='setURL(this);' /></td>" +
  606. "</tr>"
  607. );
  608. });
  609. if (i > 7) {
  610. $("#releaseTable").append(
  611. "<tr id='showall'>" +
  612. "<td colspan='6'>" +
  613. "<input type='button' id='showallbutton' class='btn btn-info' value='Show older releases' />" +
  614. "</td>" +
  615. "</tr>"
  616. );
  617. $('#showallbutton').on("click", function() {
  618. $("tr.hide").removeClass("hide");
  619. $("tr#showall").addClass("hide");
  620. });
  621. }
  622. $("#searchfw").css("display", "inline");
  623. })
  624. .fail(function() {
  625. alert("failed to fetch release history!");
  626. });
  627. });
  628. $('input#searchinput').on("input", function() {
  629. var s = $('input#searchinput').val();
  630. var re = new RegExp(s, "gi");
  631. if (s.length == 0) {
  632. $("tr.release").removeClass("hide");
  633. } else if (s.length < 3) {
  634. $("tr.release").addClass("hide");
  635. } else {
  636. $("tr.release").addClass("hide");
  637. $("tr.release").each(function(tr) {
  638. $(this).find('td').each(function() {
  639. if ($(this).html().match(re)) {
  640. $(this).parent().removeClass('hide');
  641. }
  642. });
  643. });
  644. }
  645. });
  646. $("#fwbranch").change(function(e) {
  647. var branch = this.value;
  648. var re = new RegExp('^' + branch + '$', "gi");
  649. $("tr.release").addClass("hide");
  650. $("tr.release").each(function(tr) {
  651. $(this).find('td').each(function() {
  652. console.log($(this).html());
  653. if ($(this).html().match(re)) {
  654. $(this).parent().removeClass('hide');
  655. }
  656. });
  657. });
  658. });
  659. $('#boot-button').on("click", function() {
  660. enableStatusTimer = true;
  661. });
  662. $('#reboot-button').on("click", function() {
  663. enableStatusTimer = true;
  664. });
  665. $('#updateAP').on("click", function() {
  666. refreshAP(true);
  667. console.log("refresh AP");
  668. });
  669. //first time the page loads: attempt to get the connection status and start the wifi scan
  670. refreshAP(false);
  671. getConfig();
  672. getCommands();
  673. //start timers
  674. startCheckStatusInterval();
  675. //startRefreshAPInterval();
  676. $('[data-toggle="tooltip"]').tooltip({
  677. html: true,
  678. placement: 'right',
  679. });
  680. });
  681. function setURL(button) {
  682. var url = button.dataset.url;
  683. $("#fwurl").val(url);
  684. $('[data-url^="http"]').addClass("btn-success").removeClass("btn-danger");
  685. $('[data-url="' + url + '"]').addClass("btn-danger").removeClass("btn-success");
  686. }
  687. function performConnect(conntype) {
  688. //stop the status refresh. This prevents a race condition where a status
  689. //request would be refreshed with wrong ip info from a previous connection
  690. //and the request would automatically shows as succesful.
  691. stopCheckStatusInterval();
  692. //stop refreshing wifi list
  693. stopRefreshAPInterval();
  694. var pwd;
  695. var dhcpname;
  696. if (conntype == 'manual') {
  697. //Grab the manual SSID and PWD
  698. selectedSSID = $('#manual_ssid').val();
  699. pwd = $("#manual_pwd").val();
  700. dhcpname = $("#dhcp-name2").val();;
  701. } else {
  702. pwd = $("#pwd").val();
  703. dhcpname = $("#dhcp-name1").val();;
  704. }
  705. //reset connection
  706. $("#loading").show();
  707. $("#connect-success").hide();
  708. $("#connect-fail").hide();
  709. $("#ok-connect").prop("disabled", true);
  710. $("#ssid-wait").text(selectedSSID);
  711. $("#connect").slideUp("fast", function() {});
  712. $("#connect_manual").slideUp("fast", function() {});
  713. $("#connect-wait").slideDown("fast", function() {});
  714. $.ajax({
  715. url: '/connect.json',
  716. dataType: 'text',
  717. method: 'POST',
  718. cache: false,
  719. // headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname },
  720. contentType: 'application/json; charset=utf-8',
  721. data: JSON.stringify({
  722. 'timestamp': Date.now(),
  723. 'ssid': selectedSSID,
  724. 'pwd': pwd,
  725. 'host_name': dhcpname
  726. }),
  727. error: handleExceptionResponse
  728. });
  729. //now we can re-set the intervals regardless of result
  730. startCheckStatusInterval();
  731. startRefreshAPInterval();
  732. }
  733. function rssiToIcon(rssi) {
  734. if (rssi >= -60) {
  735. return 'w0';
  736. } else if (rssi >= -67) {
  737. return 'w1';
  738. } else if (rssi >= -75) {
  739. return 'w2';
  740. } else {
  741. return 'w3';
  742. }
  743. }
  744. function refreshAP(force) {
  745. if (!enableAPTimer && !force) return;
  746. $.getJSON("/scan.json", async function(data) {
  747. await sleep(2000);
  748. $.getJSON("/ap.json", function(data) {
  749. if (data.length > 0) {
  750. //sort by signal strength
  751. data.sort(function(a, b) {
  752. var x = a["rssi"];
  753. var y = b["rssi"];
  754. return ((x < y) ? 1 : ((x > y) ? -1 : 0));
  755. });
  756. apList = data;
  757. refreshAPHTML(apList);
  758. }
  759. });
  760. });
  761. }
  762. function refreshAPHTML(data) {
  763. var h = "";
  764. data.forEach(function(e, idx, array) {
  765. h += '<div class="ape{0}"><div class="{1}"><div class="{2}">{3}</div></div></div>'.format(idx === array.length - 1 ? '' : ' brdb', rssiToIcon(e.rssi), e.auth == 0 ? '' : 'pw', e.ssid);
  766. h += "\n";
  767. });
  768. $("#wifi-list").html(h)
  769. }
  770. function getMessages() {
  771. $.getJSON("/messages.json?1", async function(data) {
  772. for (const msg of data) {
  773. var msg_age = msg["current_time"] - msg["sent_time"];
  774. var msg_time = new Date();
  775. msg_time.setTime(msg_time.getTime() - msg_age);
  776. switch (msg["class"]) {
  777. case "MESSAGING_CLASS_OTA":
  778. //message: "{"ota_dsc":"Erasing flash complete","ota_pct":0}"
  779. var ota_data = JSON.parse(msg["message"]);
  780. if (ota_data.hasOwnProperty('ota_pct') && ota_data['ota_pct'] != 0) {
  781. otapct = ota_data['ota_pct'];
  782. $('.progress-bar').css('width', otapct + '%').attr('aria-valuenow', otapct);
  783. $('.progress-bar').html(otapct + '%');
  784. }
  785. if (ota_data.hasOwnProperty('ota_dsc') && ota_data['ota_dsc'] != '') {
  786. otadsc = ota_data['ota_dsc'];
  787. $("span#flash-status").html(otadsc);
  788. if (msg.type == "MESSAGING_ERROR" || otapct > 95) {
  789. blockFlashButton = false;
  790. enableStatusTimer = true;
  791. }
  792. }
  793. break;
  794. case "MESSAGING_CLASS_STATS":
  795. // for task states, check structure : task_state_t
  796. var stats_data = JSON.parse(msg["message"]);
  797. console.log(msg_time.toLocaleString() + " - Number of tasks on the ESP32: " + stats_data["ntasks"]);
  798. console.log(msg_time.toLocaleString() + '\tname' + '\tcpu' + '\tstate' + '\tminstk' + '\tbprio' + '\tcprio' + '\tnum');
  799. if(stats_data["tasks"]){
  800. if($("#tasks_sect").css('visibility') =='collapse'){
  801. $("#tasks_sect").css('visibility','visible');
  802. }
  803. var trows="";
  804. stats_data["tasks"].sort(function(a, b){
  805. return (b.cpu-a.cpu);
  806. }).forEach(function(task) {
  807. console.log(msg_time.toLocaleString() + '\t' + task["nme"] + '\t' + task["cpu"] + '\t' + task_state_t[task["st"]] + '\t' + task["minstk"] + '\t' + task["bprio"] + '\t' + task["cprio"] + '\t' + task["num"]);
  808. trows+='<tr class="table-primary"><th scope="row">' + task["num"]+ '</th><td>' + task["nme"] + '</td><td>' + task["cpu"] + '</td><td>' + task_state_t[task["st"]] + '</td><td>' + task["minstk"]+ '</td><td>' + task["bprio"]+ '</td><td>' + task["cprio"] + '</td></tr>'
  809. });
  810. $("tbody#tasks").html(trows);
  811. }
  812. else if($("#tasks_sect").css('visibility') =='visible'){
  813. $("tbody#tasks").empty();
  814. $("#tasks_sect").css('visibility','collapse');
  815. }
  816. break;
  817. case "MESSAGING_CLASS_SYSTEM":
  818. var r = showMessage(msg,msg_time, msg_age);
  819. break;
  820. case "MESSAGING_CLASS_CFGCMD":
  821. var msgparts=msg["message"].split(/([^\n]*)\n(.*)/gs);
  822. showCmdMessage(msgparts[1],msg['type'],msgparts[2],true);
  823. break;
  824. default:
  825. break;
  826. }
  827. }
  828. })
  829. .fail( handleExceptionResponse);
  830. /*
  831. Minstk is minimum stack space left
  832. Bprio is base priority
  833. cprio is current priority
  834. nme is name
  835. st is task state. I provided a "typedef" that you can use to convert to text
  836. cpu is cpu percent used
  837. */
  838. }
  839. function handleRecoveryMode(data){
  840. if (data.hasOwnProperty('recovery')) {
  841. if (LastRecoveryState != data["recovery"]) {
  842. LastRecoveryState = data["recovery"];
  843. $("input#show-nvs")[0].checked = LastRecoveryState == 1 ? true : false;
  844. }
  845. if ($("input#show-nvs")[0].checked) {
  846. $('a[href^="#tab-nvs"]').show();
  847. } else {
  848. $('a[href^="#tab-nvs"]').hide();
  849. }
  850. enableStatusTimer = true;
  851. if (data["recovery"] === 1) {
  852. recovery = true;
  853. $("#reboot_ota_nav").show();
  854. $("#reboot_nav").hide();
  855. $("#otadiv").show();
  856. $('#uploaddiv').show();
  857. $("footer.footer").removeClass('sl');
  858. $("footer.footer").addClass('bg-warning');
  859. $("#mainnav").addClass('bg-warning');
  860. $('[name=secnav]').addClass('bg-warning');
  861. $("#boot-button").html('Reboot');
  862. $("#boot-form").attr('action', '/reboot_ota.json');
  863. } else {
  864. recovery = false;
  865. $("#reboot_ota_nav").hide();
  866. $("#reboot_nav").show();
  867. $("#otadiv").hide();
  868. $('#uploaddiv').hide();
  869. $("footer.footer").removeClass('bg-warning');
  870. $('#mainnav').removeClass('bg-warning');
  871. $('[name=secnav]').removeClass('bg-warning');
  872. $("footer.footer").addClass('sl');
  873. $("#boot-button").html('Recovery');
  874. $("#boot-form").attr('action', '/recovery.json');
  875. }
  876. }
  877. }
  878. function handleWifiStatus(data){
  879. if (data.hasOwnProperty('ssid') && data['ssid'] != "") {
  880. if (data["ssid"] === selectedSSID) {
  881. //that's a connection attempt
  882. if (data["urc"] === 0) {
  883. //got connection
  884. $("#connected-to span").text(data["ssid"]);
  885. $("#connect-details h1").text(data["ssid"]);
  886. $("#ip").text(data["ip"]);
  887. $("#netmask").text(data["netmask"]);
  888. $("#gw").text(data["gw"]);
  889. $("#wifi-status").slideDown("fast", function() {});
  890. $("span#foot-wifi").html(", SSID: <strong>" + data["ssid"] + "</strong>, IP: <strong>" + data["ip"] + "</strong>");
  891. //unlock the wait screen if needed
  892. $("#ok-connect").prop("disabled", false);
  893. //update wait screen
  894. $("#loading").hide();
  895. $("#connect-success").text("Your IP address now is: " + data["ip"]);
  896. $("#connect-success").show();
  897. $("#connect-fail").hide();
  898. enableAPTimer = false;
  899. } else if (data["urc"] === 1) {
  900. //failed attempt
  901. $("#connected-to span").text('');
  902. $("#connect-details h1").text('');
  903. $("#ip").text('0.0.0.0');
  904. $("#netmask").text('0.0.0.0');
  905. $("#gw").text('0.0.0.0');
  906. $("span#foot-wifi").html("");
  907. //don't show any connection
  908. $("#wifi-status").slideUp("fast", function() {});
  909. //unlock the wait screen
  910. $("#ok-connect").prop("disabled", false);
  911. //update wait screen
  912. $("#loading").hide();
  913. $("#connect-fail").show();
  914. $("#connect-success").hide();
  915. enableAPTimer = true;
  916. enableStatusTimer = true;
  917. }
  918. } else if (data.hasOwnProperty('urc') && data['urc'] === 0) {
  919. //ESP32 is already connected to a wifi without having the user do anything
  920. if (!($("#wifi-status").is(":visible"))) {
  921. $("#connected-to span").text(data["ssid"]);
  922. $("#connect-details h1").text(data["ssid"]);
  923. $("#ip").text(data["ip"]);
  924. $("#netmask").text(data["netmask"]);
  925. $("#gw").text(data["gw"]);
  926. $("#wifi-status").slideDown("fast", function() {});
  927. $("span#foot-wifi").html(", SSID: <strong>" + data["ssid"] + "</strong>, IP: <strong>" + data["ip"] + "</strong>");
  928. }
  929. enableAPTimer = false;
  930. }
  931. } else if (data.hasOwnProperty('urc') && data['urc'] === 2) {
  932. //that's a manual disconnect
  933. if ($("#wifi-status").is(":visible")) {
  934. $("#wifi-status").slideUp("fast", function() {});
  935. $("span#foot-wifi").html("");
  936. }
  937. enableAPTimer = true;
  938. enableStatusTimer = true;
  939. }
  940. }
  941. function checkStatus() {
  942. RepeatCheckStatusInterval();
  943. if (!enableStatusTimer) return;
  944. if (blockAjax) return;
  945. blockAjax = true;
  946. getMessages();
  947. $.getJSON("/status.json", function(data) {
  948. handleRecoveryMode(data);
  949. handleWifiStatus(data);
  950. if (data.hasOwnProperty('project_name') && data['project_name'] != '') {
  951. pname = data['project_name'];
  952. }
  953. if (data.hasOwnProperty('version') && data['version'] != '') {
  954. ver = data['version'];
  955. $("span#foot-fw").html("fw: <strong>" + ver + "</strong>, mode: <strong>" + pname + "</strong>");
  956. } else {
  957. $("span#flash-status").html('');
  958. }
  959. if (data.hasOwnProperty('Voltage')) {
  960. var voltage = data['Voltage'];
  961. var layer;
  962. /* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated
  963. as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely
  964. following the graph here:
  965. https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages
  966. using the 0.2C discharge profile for the rest of the values.
  967. */
  968. if (voltage > 0) {
  969. if (inRange(voltage, 5.8, 6.8) || inRange(voltage, 8.8, 10.2)) {
  970. layer = bat0;
  971. } else if (inRange(voltage, 6.8, 7.4) || inRange(voltage, 10.2, 11.1)) {
  972. layer = bat1;
  973. } else if (inRange(voltage, 7.4, 7.5) || inRange(voltage, 11.1, 11.25)) {
  974. layer = bat2;
  975. } else if (inRange(voltage, 7.5, 7.8) || inRange(voltage, 11.25, 11.7)) {
  976. layer = bat3;
  977. } else {
  978. layer = bat4;
  979. }
  980. layer.setAttribute("display","inline");
  981. }
  982. }
  983. if (data.hasOwnProperty('Jack')) {
  984. var jack = data['Jack'];
  985. if (jack == '1') {
  986. o_jack.setAttribute("display", "inline");
  987. }
  988. }
  989. blockAjax = false;
  990. })
  991. .fail(function(xhr, ajaxOptions, thrownError) {
  992. handleExceptionResponse(xhr, ajaxOptions, thrownError);
  993. blockAjax = false;
  994. });
  995. }
  996. function runCommand(button, reboot) {
  997. cmdstring = button.attributes.cmdname.value;
  998. showCmdMessage(button.attributes.cmdname.value,'MESSAGING_INFO',"Executing.",false);
  999. fields = document.getElementById("flds-" + cmdstring);
  1000. cmdstring += ' ';
  1001. if (fields) {
  1002. allfields = fields.querySelectorAll("select,input");
  1003. for (i = 0; i < allfields.length; i++) {
  1004. attr = allfields[i].attributes;
  1005. qts = '';
  1006. opt = '';
  1007. isSelect = allfields[i].attributes?.class?.value == "custom-select";
  1008. if ((isSelect && allfields[i].selectedIndex != 0) || !isSelect) {
  1009. if (attr.longopts.value !== "undefined") {
  1010. opt += '--' + attr.longopts.value;
  1011. } else if (attr.shortopts.value !== "undefined") {
  1012. opt = '-' + attr.shortopts.value;
  1013. }
  1014. if (attr.hasvalue.value == "true") {
  1015. if (allfields[i].value != '') {
  1016. qts = (/\s/.test(allfields[i].value)) ? '"' : '';
  1017. cmdstring += opt + ' '+ qts + allfields[i].value + qts + ' ';
  1018. }
  1019. } else {
  1020. // this is a checkbox
  1021. if (allfields[i].checked) cmdstring += opt + ' ';
  1022. }
  1023. }
  1024. }
  1025. }
  1026. console.log(cmdstring);
  1027. var data = {
  1028. 'timestamp': Date.now()
  1029. };
  1030. data['command'] = cmdstring;
  1031. $.ajax({
  1032. url: '/commands.json',
  1033. dataType: 'text',
  1034. method: 'POST',
  1035. cache: false,
  1036. contentType: 'application/json; charset=utf-8',
  1037. data: JSON.stringify(data),
  1038. error: handleExceptionResponse,
  1039. complete: function(response) {
  1040. //var returnedResponse = JSON.parse(response.responseText);
  1041. console.log(response.responseText);
  1042. if (response.responseText && JSON.parse(response.responseText).Result == "Success" && reboot) {
  1043. delay_reboot(2500,button.attributes.cmdname.value);
  1044. }
  1045. }
  1046. });
  1047. enableStatusTimer = true;
  1048. }
  1049. function getCommands() {
  1050. $.getJSON("/commands.json", function(data) {
  1051. console.log(data);
  1052. data.commands.forEach(function(command) {
  1053. if ($("#flds-" + command.name).length == 0) {
  1054. cmd_parts = command.name.split('-');
  1055. isConfig = cmd_parts[0]=='cfg';
  1056. targetDiv= '#tab-' + cmd_parts[0]+'-'+cmd_parts[1];
  1057. innerhtml = '';
  1058. //innerhtml+='<tr class="table-light"><td>'+(isConfig?'<h1>':'');
  1059. innerhtml += '<div class="card text-white bg-primary mb-3"><div class="card-header">' + escapeHTML(command.help).replace(/\n/g, '<br />') + '</div><div class="card-body">';
  1060. innerhtml += '<fieldset id="flds-' + command.name + '">';
  1061. if (command.hasOwnProperty("argtable")) {
  1062. command.argtable.forEach(function(arg) {
  1063. placeholder = arg?.datatype || '';
  1064. ctrlname = command.name + '-' + arg.longopts;
  1065. curvalue = data.values?. [command.name]?. [arg.longopts];
  1066. var attributes = 'hasvalue=' + arg.hasvalue + ' ';
  1067. //attributes +='datatype="'+arg.datatype+'" ';
  1068. attributes += 'longopts="' + arg.longopts + '" ';
  1069. attributes += 'shortopts="' + arg.shortopts + '" ';
  1070. attributes += 'checkbox=' + arg.checkbox + ' ';
  1071. attributes += 'cmdname="' + command.name + '" ';
  1072. attributes += 'id="' + ctrlname + '" name="' + ctrlname + '" hasvalue="' + arg.hasvalue + '" ';
  1073. extraclass = ((arg.mincount>0)?'bg-success':'');
  1074. if(arg.glossary == 'hidden'){
  1075. attributes += ' style="visibility: hidden;"';
  1076. }
  1077. if (arg.checkbox) {
  1078. innerhtml += '<div class="form-check"><label class="form-check-label">';
  1079. innerhtml += '<input type="checkbox" ' + attributes + ' class="form-check-input '+extraclass+'" value="" >' + arg.glossary.encodeHTML() + '<small class="form-text text-muted">Previous value: ' + (curvalue?"Checked":"Unchecked") + '</small></label>';
  1080. } else {
  1081. innerhtml += '<div class="form-group" ><label for="' + ctrlname + '">' + arg.glossary.encodeHTML() + '</label>';
  1082. if (placeholder.includes('|')) {
  1083. placeholder = placeholder.replace('<', '').replace('>', '');
  1084. innerhtml += '<select ' + attributes + ' class="form-control '+extraclass+'"';
  1085. placeholder = '--|' + placeholder;
  1086. placeholder.split('|').forEach(function(choice) {
  1087. innerhtml += '<option >' + choice + '</option>';
  1088. });
  1089. innerhtml += '</select>';
  1090. } else {
  1091. innerhtml += '<input type="text" class="form-control '+extraclass+'" placeholder="' + placeholder + '" ' + attributes + '>';
  1092. }
  1093. innerhtml += '<small class="form-text text-muted">Previous value: ' + (curvalue || '') + '</small>';
  1094. }
  1095. innerhtml += '</div>';
  1096. });
  1097. }
  1098. innerhtml +='<div style="margin-top: 16px;">';
  1099. innerhtml += '<div class="toast show" role="alert" aria-live="assertive" aria-atomic="true" style="display: none;" id="toast_'+command.name+'">';
  1100. innerhtml += '<div class="toast-header"><strong class="mr-auto">Result</strong><button type="button" class="ml-2 mb-1 close" data-dismiss="toast" aria-label="Close" onclick="$(this).parent().parent().hide()">';
  1101. innerhtml += '<span aria-hidden="true">×</span></button></div><div class="toast-body" id="msg_'+command.name+'"></div></div>'
  1102. if (isConfig) {
  1103. innerhtml += '<button type="submit" class="btn btn-info" id="btn-save-' + command.name + '" cmdname="' + command.name + '" onclick="runCommand(this,false)">Save</button>';
  1104. innerhtml += '<button type="submit" class="btn btn-warning" id="btn-commit-' + command.name + '" cmdname="' + command.name + '" onclick="runCommand(this,true)">Apply</button>';
  1105. } else {
  1106. innerhtml += '<button type="submit" class="btn btn-success" id="btn-run-' + command.name + '" cmdname="' + command.name + '" onclick="runCommand(this,false)">Execute</button>';
  1107. }
  1108. innerhtml+= '</div></fieldset></div></div>';
  1109. if (isConfig) {
  1110. $(targetDiv).append(innerhtml);
  1111. } else {
  1112. $("#commands-list").append(innerhtml);
  1113. }
  1114. }
  1115. });
  1116. data.commands.forEach(function(command) {
  1117. $('[cmdname='+command.name+']:input').val('');
  1118. $('[cmdname='+command.name+']:checkbox').prop('checked',false);
  1119. if (command.hasOwnProperty("argtable")) {
  1120. command.argtable.forEach(function(arg) {
  1121. ctrlselector = '#' + command.name + '-' + arg.longopts;
  1122. ctrlValue = data.values?.[command.name]?.[arg.longopts];
  1123. if (arg.checkbox) {
  1124. $(ctrlselector)[0].checked = ctrlValue;
  1125. } else {
  1126. if(ctrlValue!=undefined) $(ctrlselector).val( ctrlValue ).change();
  1127. if ($(ctrlselector)[0].value.length == 0 && (arg?.datatype || '').includes('|')) {
  1128. $(ctrlselector)[0].value = '--';
  1129. }
  1130. }
  1131. });
  1132. }
  1133. });
  1134. })
  1135. .fail(function(xhr, ajaxOptions, thrownError) {
  1136. handleExceptionResponse(xhr, ajaxOptions, thrownError);
  1137. $("#commands-list").empty();
  1138. blockAjax = false;
  1139. });
  1140. }
  1141. function getConfig() {
  1142. $.getJSON("/config.json", function(entries) {
  1143. $("#nvsTable tr").remove();
  1144. data = entries.hasOwnProperty('config') ? entries.config : entries;
  1145. Object.keys(data).sort().forEach(function(key, i) {
  1146. if (data.hasOwnProperty(key)) {
  1147. val = data[key].value;
  1148. if (key == 'autoexec') {
  1149. if (data["autoexec"].value === "0") {
  1150. $("#disable-squeezelite")[0].checked = true;
  1151. } else {
  1152. $("#disable-squeezelite")[0].checked = false;
  1153. }
  1154. } else if (key == 'autoexec1') {
  1155. var re = /-o\s?(["][^"]*["]|[^-]+)/g;
  1156. var m = re.exec(val);
  1157. if (m[1].toUpperCase().startsWith('I2S')) {
  1158. handleTemplateTypeRadio('i2s');
  1159. } else if (m[1].toUpperCase().startsWith('SPDIF')) {
  1160. handleTemplateTypeRadio('spdif');
  1161. } else if (m[1].toUpperCase().startsWith('"BT')) {
  1162. handleTemplateTypeRadio('bt');
  1163. var re2=/["]BT\s*-n\s*'([^"]+)'/g;
  1164. var m2=re2.exec(m[1]);
  1165. if(m2.length>=2){
  1166. $("#btsinkdiv").val(m2[1]);
  1167. }
  1168. }
  1169. } else if (key == 'host_name') {
  1170. val = val.replaceAll('"', '');
  1171. $("input#dhcp-name1").val(val);
  1172. $("input#dhcp-name2").val(val);
  1173. $("#player").val(val);
  1174. }
  1175. $("tbody#nvsTable").append(
  1176. "<tr>" +
  1177. "<td>" + key + "</td>" +
  1178. "<td class='value'>" +
  1179. "<input type='text' class='form-control nvs' id='" + key + "' nvs_type=" + data[key].type + " >" +
  1180. "</td>" +
  1181. "</tr>"
  1182. );
  1183. $("input#" + key).val(data[key].value);
  1184. }
  1185. });
  1186. $("tbody#nvsTable").append("<tr><td><input type='text' class='form-control' id='nvs-new-key' placeholder='new key'></td><td><input type='text' class='form-control' id='nvs-new-value' placeholder='new value' nvs_type=33 ></td></tr>");
  1187. if (entries.hasOwnProperty('gpio')) {
  1188. $("tbody#gpiotable tr").remove();
  1189. entries.gpio.forEach(function(gpio_entry) {
  1190. cl = gpio_entry.fixed ? "table-secondary" : "table-primary";
  1191. $("tbody#gpiotable").append('<tr class=' + cl + '><th scope="row">' + gpio_entry.group + '</th><td>' + gpio_entry.name + '</td><td>' + gpio_entry.gpio + '</td><td>' + (gpio_entry.fixed ? 'Fixed':'Configuration') + '</td></tr>');
  1192. });
  1193. }
  1194. })
  1195. .fail(function(xhr, ajaxOptions, thrownError) {
  1196. handleExceptionResponse(xhr, ajaxOptions, thrownError);
  1197. blockAjax = false;
  1198. });
  1199. }
  1200. function showLocalMessage(message,severity, age = 0){
  1201. msg={
  1202. 'type':'Local',
  1203. 'message':message,
  1204. 'type' : severity
  1205. }
  1206. showMessage(msg,severity,age);
  1207. }
  1208. function showMessage(msg, msg_time,age = 0) {
  1209. color='table-success';
  1210. if (msg['type'] == 'MESSAGING_WARNING') {
  1211. color='table-warning';
  1212. if(messageseverity=='MESSAGING_INFO'){
  1213. messageseverity = 'MESSAGING_WARNING';
  1214. }
  1215. } else if (msg['type'] == 'MESSAGING_ERROR') {
  1216. if(messageseverity=='MESSAGING_INFO' || messageseverity=='MESSAGING_WARNING'){
  1217. messageseverity = 'MESSAGING_ERROR';
  1218. }
  1219. color ='table-danger';
  1220. }
  1221. if(++messagecount>0){
  1222. $('#msgcnt').removeClass('badge-success');
  1223. $('#msgcnt').removeClass('badge-warning');
  1224. $('#msgcnt').removeClass('badge-danger');
  1225. $('#msgcnt').addClass(pillcolors[messageseverity]);
  1226. $('#msgcnt').text(messagecount);
  1227. }
  1228. $("#syslogTable").append(
  1229. "<tr class='" + color + "'>" +
  1230. "<td>" + msg_time.toLocaleString() + "</td>" +
  1231. "<td>" + escapeHTML(msg["message"]).replace(/\n/g, '<br />') + "</td>" +
  1232. "</tr>"
  1233. );
  1234. }
  1235. function inRange(x, min, max) {
  1236. return ((x - min) * (x - max) <= 0);
  1237. }
  1238. function sleep(ms) {
  1239. return new Promise(resolve => setTimeout(resolve, ms));
  1240. }