code.js 39 KB

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