code.js 42 KB

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