2
0

code.js 41 KB

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