code.js 45 KB

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