import he from 'he'; import { Promise } from 'es6-promise'; if (!String.prototype.format) { Object.assign(String.prototype, { format() { const args = arguments; return this.replace(/{(\d+)}/g, function(match, number) { return typeof args[number] !== 'undefined' ? args[number] : match; }); }, }); } if (!String.prototype.encodeHTML) { Object.assign(String.prototype, { encodeHTML() { return he.encode(this).replace(/\n/g, '
') }, }); } Object.assign(Date.prototype, { toLocalShort() { const opt = { dateStyle: 'short', timeStyle: 'short' }; return this.toLocaleString(undefined, opt); }, }); const nvsTypes = { NVS_TYPE_U8: 0x01, /*! < Type uint8_t */ NVS_TYPE_I8: 0x11, /*! < Type int8_t */ NVS_TYPE_U16: 0x02, /*! < Type uint16_t */ NVS_TYPE_I16: 0x12, /*! < Type int16_t */ NVS_TYPE_U32: 0x04, /*! < Type uint32_t */ NVS_TYPE_I32: 0x14, /*! < Type int32_t */ NVS_TYPE_U64: 0x08, /*! < Type uint64_t */ NVS_TYPE_I64: 0x18, /*! < Type int64_t */ NVS_TYPE_STR: 0x21, /*! < Type string */ NVS_TYPE_BLOB: 0x42, /*! < Type blob */ NVS_TYPE_ANY: 0xff /*! < Must be last */, }; const btIcons = { bt_playing: 'play-circle-fill', bt_disconnected: 'bluetooth-fill', bt_neutral: '', bt_connected: 'bluetooth-connect-fill', bt_disabled: '', play_arrow: 'play-circle-fill', pause: 'pause-circle-fill', stop: 'stop-circle-fill', '': '', }; const btStateIcons = [ { desc: 'Idle', sub: ['bt_neutral'] }, { desc: 'Discovering', sub: ['bt_disconnected'] }, { desc: 'Discovered', sub: ['bt_disconnected'] }, { desc: 'Unconnected', sub: ['bt_disconnected'] }, { desc: 'Connecting', sub: ['bt_disconnected'] }, { desc: 'Connected', sub: ['bt_connected', 'play_arrow', 'bt_playing', 'pause', 'stop'], }, { desc: 'Disconnecting', sub: ['bt_disconnected'] }, ]; const pillcolors = { MESSAGING_INFO: 'badge-success', MESSAGING_WARNING: 'badge-warning', MESSAGING_ERROR: 'badge-danger', }; const connectReturnCode = { UPDATE_CONNECTION_OK : 0, UPDATE_FAILED_ATTEMPT : 1, UPDATE_USER_DISCONNECT : 2, UPDATE_LOST_CONNECTION : 3, UPDATE_FAILED_ATTEMPT_AND_RESTORE : 4 } const taskStates = { 0: 'eRunning', /*! < A task is querying the state of itself, so must be running. */ 1: 'eReady', /*! < The task being queried is in a read or pending ready list. */ 2: 'eBlocked', /*! < The task being queried is in the Blocked state. */ 3: 'eSuspended', /*! < The task being queried is in the Suspended state, or is in the Blocked state with an infinite time out. */ 4: 'eDeleted', }; window.hideSurrounding = function(obj){ $(obj).parent().parent().hide() } window.handleReboot = function(ota){ if(ota){ $('#reboot_ota_nav').removeClass('active'); delayReboot(500,'', true); } else { $('#reboot_nav').removeClass('active'); delayReboot(500,'', false); } } function handlebtstate(data) { let icon = ''; let tt = ''; if (data.bt_status !== undefined && data.bt_sub_status !== undefined) { const iconsvg = btStateIcons[data.bt_status].sub[data.bt_sub_status]; if (iconsvg) { icon = `#${btIcons[iconsvg]}`; tt = btStateIcons[data.bt_status].desc; } else { icon = `#${btIcons.bt_connected}`; tt = 'Output status'; } } $('#o_type').title = tt; $('#o_bt').attr('xlink:href',icon); } function handleTemplateTypeRadio(outtype) { if (outtype === 'bt') { $('#bt').prop('checked', true); $('#o_bt').attr('display', 'inline'); $('#o_spdif').attr('display', 'none'); $('#o_i2s').attr('display', 'none'); output = 'bt'; } else if (outtype === 'spdif') { $('#spdif').prop('checked', true); $('#o_bt').attr('display', 'none'); $('#o_spdif').attr('display', 'inline'); $('#o_i2s').attr('display', 'none'); output = 'spdif'; } else { $('#i2s').prop('checked', true); $('#o_bt').attr('display', 'none'); $('#o_spdif').attr('display', 'none'); $('#o_i2s').attr('display', 'inline'); output = 'i2s'; } } function handleExceptionResponse(xhr, _ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); enableStatusTimer = true; if (thrownError !== '') { showLocalMessage(thrownError, 'MESSAGING_ERROR'); } } function HideCmdMessage(cmdname) { $('#toast_' + cmdname).css('display', 'none'); $('#toast_' + cmdname) .removeClass('table-success') .removeClass('table-warning') .removeClass('table-danger') .addClass('table-success'); $('#msg_' + cmdname).html(''); } function showCmdMessage(cmdname, msgtype, msgtext, append = false) { let color = 'table-success'; if (msgtype === 'MESSAGING_WARNING') { color = 'table-warning'; } else if (msgtype === 'MESSAGING_ERROR') { color = 'table-danger'; } $('#toast_' + cmdname).css('display', 'block'); $('#toast_' + cmdname) .removeClass('table-success') .removeClass('table-warning') .removeClass('table-danger') .addClass(color); let escapedtext = msgtext .substring(0, msgtext.length - 1) .encodeHTML() .replace(/\n/g, '
'); escapedtext = ($('#msg_' + cmdname).html().length > 0 && append ? $('#msg_' + cmdname).html() + '
' : '') + escapedtext; $('#msg_' + cmdname).html(escapedtext); } const releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases'; let recovery = false; var enableStatusTimer = true; const commandHeader = 'squeezelite -b 500:2000 -d all=info -C 30 -W'; let otapct, otadsc; let blockAjax = false; let blockFlashButton = false; let apList = null; //let selectedSSID = ''; //let checkStatusInterval = null; let messagecount = 0; let messageseverity = 'MESSAGING_INFO'; let StatusIntervalActive = false; let LastRecoveryState = null; let SystemConfig={}; let LastCommandsState = null; var output = ''; let hostName = ''; let versionName='SqueezeESP32'; let appTitle=versionName; let ConnectedToSSID={}; let ConnectingToSSID={}; const ConnectingToActions = { 'CONN' : 0,'MAN' : 1,'STS' : 2, } Promise.prototype.delay = function(duration) { return this.then( function(value) { return new Promise(function(resolve) { setTimeout(function() { resolve(value); }, duration); }); }, function(reason) { return new Promise(function(_resolve, reject) { setTimeout(function() { reject(reason); }, duration); }); } ); }; // function stopCheckStatusInterval() { // if (checkStatusInterval != null) { // clearTimeout(checkStatusInterval); // checkStatusInterval = null; // } // StatusIntervalActive = false; // } function startCheckStatusInterval() { StatusIntervalActive = true; setTimeout(checkStatus, 3000); } function RepeatCheckStatusInterval() { if (StatusIntervalActive) { startCheckStatusInterval(); } } function getConfigJson(slimMode) { const config = {}; $('input.nvs').each(function(_index, entry) { if (!slimMode) { const nvsType = parseInt(entry.attributes.nvs_type.value, 10); if (entry.id !== '') { config[entry.id] = {}; if ( nvsType === nvsTypes.NVS_TYPE_U8 || nvsType === nvsTypes.NVS_TYPE_I8 || nvsType === nvsTypes.NVS_TYPE_U16 || nvsType === nvsTypes.NVS_TYPE_I16 || nvsType === nvsTypes.NVS_TYPE_U32 || nvsType === nvsTypes.NVS_TYPE_I32 || nvsType === nvsTypes.NVS_TYPE_U64 || nvsType === nvsTypes.NVS_TYPE_I64 ) { config[entry.id].value = parseInt(entry.value); } else { config[entry.id].value = entry.value; } config[entry.id].type = nvsType; } } else { config[entry.id] = entry.value; } }); const key = $('#nvs-new-key').val(); const val = $('#nvs-new-value').val(); if (key !== '') { if (!slimMode) { config[key] = {}; config[key].value = val; config[key].type = 33; } else { config[key] = val; } } return config; } // eslint-disable-next-line no-unused-vars function onFileLoad(elementId, event) { let data = {}; try { data = JSON.parse(elementId.srcElement.result); } catch (e) { alert('Parsing failed!\r\n ' + e); } $('input.nvs').each(function(_index, entry) { if (data[entry.id]) { if (data[entry.id] !== entry.value) { console.log( 'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id] ); $(this).val(data[entry.id]); } } }); } // eslint-disable-next-line no-unused-vars function onChooseFile(event, onLoadFileHandler) { if (typeof window.FileReader !== 'function') { throw "The file API isn't supported on this browser."; } const input = event.target; if (!input) { throw 'The browser does not properly implement the event object'; } if (!input.files) { throw 'This browser does not support the `files` property of the file input.'; } if (!input.files[0]) { return undefined; } const file = input.files[0]; let fr = new FileReader(); fr.onload = onLoadFileHandler; fr.readAsText(file); input.value = ''; } function delayReboot(duration, cmdname, ota = false) { const url = ota ? '/reboot_ota.json' : '/reboot.json'; $('tbody#tasks').empty(); enableStatusTimer = false; $('#tasks_sect').css('visibility', 'collapse'); Promise.resolve({ cmdname: cmdname, url: url }) .delay(duration) .then(function(data) { if (data.cmdname.length > 0) { showCmdMessage( data.cmdname, 'MESSAGING_WARNING', 'System is rebooting.\n', true ); } else { showLocalMessage('System is rebooting.\n', 'MESSAGING_WARNING'); } console.log('now triggering reboot'); $.ajax({ url: data.url, dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ timestamp: Date.now(), }), error: handleExceptionResponse, complete: function() { console.log('reboot call completed'); enableStatusTimer = true; Promise.resolve(data) .delay(6000) .then(function(rdata) { if (rdata.cmdname.length > 0) { HideCmdMessage(rdata.cmdname); } getCommands(); getConfig(); }); }, }); }); } // eslint-disable-next-line no-unused-vars window.saveAutoexec1 = function(apply) { showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Saving.\n', false); let commandLine = commandHeader + ' -n "' + $('#player').val() + '"'; if (output === 'bt') { commandLine += ' -o "BT" -R -Z 192000'; showCmdMessage( 'cfg-audio-tmpl', 'MESSAGING_INFO', 'Remember to configure the Bluetooth audio device name.\n', true ); } else if (output === 'spdif') { commandLine += ' -o SPDIF -Z 192000'; } else { commandLine += ' -o I2S'; } if ($('#optional').val() !== '') { commandLine += ' ' + $('#optional').val(); } const data = { timestamp: Date.now(), }; data.config = { autoexec1: { value: commandLine, type: 33 }, autoexec: { value: $('#disable-squeezelite').prop('checked') ? '0' : '1', type: 33, }, }; $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), error: handleExceptionResponse, complete: function(response) { if ( response.responseText.result && JSON.parse(response.responseText).result === 'OK' ) { showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\n', true); if (apply) { delayReboot(1500, 'cfg-audio-tmpl'); } } else if (response.responseText.result) { showCmdMessage( 'cfg-audio-tmpl', 'MESSAGING_WARNING', JSON.parse(response.responseText).Result + '\n', true ); } else { showCmdMessage( 'cfg-audio-tmpl', 'MESSAGING_ERROR', response.statusText + '\n' ); } console.log(response.responseText); }, }); console.log('sent data:', JSON.stringify(data)); } window.handleDisconnect = function(){ $.ajax({ url: '/connect.json', dataType: 'text', method: 'DELETE', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ timestamp: Date.now(), }), }); } window.handleConnect = function(){ ConnectingToSSID.ssid = $('#manual_ssid').val(); ConnectingToSSID.pwd = $('#manual_pwd').val(); ConnectingToSSID.dhcpname = $('#dhcp-name2').val(); $("*[class*='connecting']").hide(); $('#ssid-wait').text(ConnectingToSSID.ssid); $('.connecting').show(); $.ajax({ url: '/connect.json', dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ timestamp: Date.now(), ssid: ConnectingToSSID.ssid, pwd: ConnectingToSSID.pwd }), error: handleExceptionResponse, }); // now we can re-set the intervals regardless of result startCheckStatusInterval(); } $(document).ready(function() { setTimeout(refreshAP,1500); $('#WifiConnectDialog').on('shown.bs.modal', function () { $("*[class*='connecting']").hide(); if(ConnectingToSSID.Action!==ConnectingToActions.STS){ $('.connecting-init').show(); $('#manual_ssid').trigger('focus'); } else { handleWifiDialog(); } }) $('#WifiConnectDialog').on('hidden.bs.modal', function () { $('#WifiConnectDialog input').val(''); }) $('input#show-commands')[0].checked = LastCommandsState === 1; $('a[href^="#tab-commands"]').hide(); $('#load-nvs').on('click', function() { $('#nvsfilename').trigger('click'); }); $('#clear-syslog').on('click', function() { messagecount = 0; messageseverity = 'MESSAGING_INFO'; $('#msgcnt').text(''); $('#syslogTable').html(''); }); $('#wifiTable').on('click','tr', function() { ConnectingToSSID.Action=ConnectingToActions.CONN; if($(this).children('td:eq(1)').text() == ConnectedToSSID.ssid){ ConnectingToSSID.Action=ConnectingToActions.STS; return; } if(!$(this).is(':last-child')){ ConnectingToSSID.ssid=$(this).children('td:eq(1)').text(); $('#manual_ssid').val(ConnectingToSSID.ssid); } else { ConnectingToSSID.Action=ConnectingToActions.MAN; ConnectingToSSID.ssid=''; $('#manual_ssid').val(ConnectingToSSID.ssid); } }); // $('#cancel').on('click', function() { // selectedSSID = ''; // $('#connect').slideUp('fast', function() {}); // $('#connect_manual').slideUp('fast', function() {}); // $('#wifi').slideDown('fast', function() {}); // }); // $('#manual_cancel').on('click', function() { // selectedSSID = ''; // $('#connect').slideUp('fast', function() {}); // $('#connect_manual').slideUp('fast', function() {}); // $('#wifi').slideDown('fast', function() {}); // }); // $('#ok-details').on('click', function() { // $('#connect-details').slideUp('fast', function() {}); // $('#wifi').slideDown('fast', function() {}); // }); $('#ok-credits').on('click', function() { $('#credits').slideUp('fast', function() {}); $('#app').slideDown('fast', function() {}); }); $('#acredits').on('click', function(event) { event.preventDefault(); $('#app').slideUp('fast', function() {}); $('#credits').slideDown('fast', function() {}); }); // $('#disconnect').on('click', function() { // $('#connect-details-wrap').addClass('blur'); // $('#diag-disconnect').slideDown('fast', function() {}); // }); // $('#no-disconnect').on('click', function() { // $('#diag-disconnect').slideUp('fast', function() {}); // $('#connect-details-wrap').removeClass('blur'); // }); // $('#yes-disconnect').on('click', function() { // stopCheckStatusInterval(); // selectedSSID = ''; // $('#diag-disconnect').slideUp('fast', function() {}); // $('#connect-details-wrap').removeClass('blur'); // $.ajax({ // url: '/connect.json', // dataType: 'text', // method: 'DELETE', // cache: false, // contentType: 'application/json; charset=utf-8', // data: JSON.stringify({ // timestamp: Date.now(), // }), // }); // startCheckStatusInterval(); // $('#connect-details').slideUp('fast', function() {}); // $('#wifi').slideDown('fast', function() {}); // }); $('input#show-commands').on('click', function() { this.checked = this.checked ? 1 : 0; if (this.checked) { $('a[href^="#tab-commands"]').show(); LastCommandsState = 1; } else { LastCommandsState = 0; $('a[href^="#tab-commands"]').hide(); } }); $('input#show-nvs').on('click', function() { this.checked = this.checked ? 1 : 0; if (this.checked) { $('*[href*="-nvs"]').show(); } else { $('*[href*="-nvs"]').hide(); } }); $('#save-as-nvs').on('click', function() { const config = getConfigJson(true); const a = document.createElement('a'); a.href = URL.createObjectURL( new Blob([JSON.stringify(config, null, 2)], { type: 'text/plain', }) ); a.setAttribute( 'download', 'nvs_config_' + hostName + '_' + Date.now() + 'json' ); document.body.appendChild(a); a.click(); document.body.removeChild(a); }); $('#save-nvs').on('click', function() { const headers = {}; const data = { timestamp: Date.now(), }; const config = getConfigJson(false); data.config = config; $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, headers: headers, contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), error: handleExceptionResponse, }); console.log('sent config JSON with headers:', JSON.stringify(headers)); console.log('sent config JSON with data:', JSON.stringify(data)); }); $('#fwUpload').on('click', function() { const uploadPath = '/flash.json'; if (!recovery) { $('#flash-status').text('Rebooting to recovery. Please try again'); window.handleReboot(false); } const fileInput = document.getElementById('flashfilename').files; if (fileInput.length === 0) { alert('No file selected!'); } else { const file = fileInput[0]; const xhttp = new XMLHttpRequest(); xhttp.onreadystatechange = function() { if (xhttp.readyState === 4) { if (xhttp.status === 200) { showLocalMessage(xhttp.responseText, 'MESSAGING_INFO'); } else if (xhttp.status === 0) { showLocalMessage( 'Upload connection was closed abruptly!', 'MESSAGING_ERROR' ); } else { showLocalMessage( xhttp.status + ' Error!\n' + xhttp.responseText, 'MESSAGING_ERROR' ); } } }; xhttp.open('POST', uploadPath, true); xhttp.send(file); } enableStatusTimer = true; }); $('#flash').on('click', function() { const data = { timestamp: Date.now(), }; if (blockFlashButton) { return; } blockFlashButton = true; const url = $('#fwurl').val(); data.config = { fwurl: { value: url, type: 33, }, }; $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), error: handleExceptionResponse, }); enableStatusTimer = true; }); $('[name=output-tmpl]').on('click', function() { handleTemplateTypeRadio(this.id); }); $('#fwcheck').on('click', function() { $('#releaseTable').html(''); $('#fwbranch').empty(); $.getJSON(releaseURL, function(data) { let i = 0; const branches = []; data.forEach(function(release) { const namecomponents = release.name.split('#'); const branch = namecomponents[3]; if (!branches.includes(branch)) { branches.push(branch); } }); let fwb; branches.forEach(function(branch) { fwb += ''; }); $('#fwbranch').append(fwb); data.forEach(function(release) { let url = ''; release.assets.forEach(function(asset) { if (asset.name.match(/\.bin$/)) { url = asset.browser_download_url; } }); const namecomponents = release.name.split('#'); const ver = namecomponents[0]; const idf = namecomponents[1]; const cfg = namecomponents[2]; const branch = namecomponents[3]; let body = release.body; body = body.replace(/'/gi, '"'); body = body.replace( /[\s\S]+(### Revision Log[\s\S]+)### ESP-IDF Version Used[\s\S]+/, '$1' ); body = body.replace(/- \(.+?\) /g, '- '); const trclass = i++ > 6 ? ' hide' : ''; $('#releaseTable').append( "" + "" + ver + '' + '' + new Date(release.created_at).toLocalShort() + '' + '' + cfg + '' + '' + idf + '' + '' + branch + '' + "" + '' ); }); if (i > 7) { $('#releaseTable').append( "" + "" + "" + '' + '' ); $('#showallbutton').on('click', function() { $('tr.hide').removeClass('hide'); $('tr#showall').addClass('hide'); }); } $('#searchfw').css('display', 'inline'); }).fail(function() { alert('failed to fetch release history!'); }); }); $('input#searchinput').on('input', function() { const s = $('input#searchinput').val(); const re = new RegExp(s, 'gi'); if (s.length === 0) { $('tr.release').removeClass('hide'); } else if (s.length < 3) { $('tr.release').addClass('hide'); } else { $('tr.release').addClass('hide'); $('tr.release').each(function() { $(this) .find('td') .each(function() { if ( $(this) .html() .match(re) ) { $(this) .parent() .removeClass('hide'); } }); }); } }); $('#fwbranch').on('change', function() { const branch = this.value; const re = new RegExp('^' + branch + '$', 'gi'); $('tr.release').addClass('hide'); $('tr.release').each(function() { $(this) .find('td') .each(function() { console.log($(this).html()); if ( $(this) .html() .match(re) ) { $(this) .parent() .removeClass('hide'); } }); }); }); $('#boot-button').on('click', function() { enableStatusTimer = true; }); $('#reboot-button').on('click', function() { enableStatusTimer = true; }); $('#updateAP').on('click', function() { refreshAP(); console.log('refresh AP'); }); // first time the page loads: attempt to get the connection status and start the wifi scan getConfig(); getCommands(); // start timers startCheckStatusInterval(); }); // eslint-disable-next-line no-unused-vars window.setURL = function(button) { const url = button.dataset.url; $('#fwurl').val(url); $('[data-url^="http"]') .addClass('btn-success') .removeClass('btn-danger'); $('[data-url="' + url + '"]') .addClass('btn-danger') .removeClass('btn-success'); } // function performConnect(conntype) { // // stop the status refresh. This prevents a race condition where a status // // request would be refreshed with wrong ip info from a previous connection // // and the request would automatically shows as succesful. // stopCheckStatusInterval(); // // stop refreshing wifi list // let pwd; // let dhcpname; // if (conntype === 'manual') { // // Grab the manual SSID and PWD // selectedSSID = $('#manual_ssid').val(); // pwd = $('#manual_pwd').val(); // dhcpname = $('#dhcp-name2').val(); // } else { // pwd = $('#pwd').val(); // dhcpname = $('#dhcp-name1').val(); // } // // reset connection // $('#connect-success').hide(); // $('#connect-fail').hide(); // $('#ok-connect').prop('disabled', true); // $('#ssid-wait').text(selectedSSID); // $('#connect').slideUp('fast', function() {}); // $('#connect_manual').slideUp('fast', function() {}); // $.ajax({ // url: '/connect.json', // dataType: 'text', // method: 'POST', // cache: false, // // headers: { 'X-Custom-ssid': selectedSSID, 'X-Custom-pwd': pwd, 'X-Custom-host_name': dhcpname }, // contentType: 'application/json; charset=utf-8', // data: JSON.stringify({ // timestamp: Date.now(), // ssid: selectedSSID, // pwd: pwd, // host_name: dhcpname, // }), // error: handleExceptionResponse, // }); // // now we can re-set the intervals regardless of result // startCheckStatusInterval(); // } function rssiToIcon(rssi) { if (rssi >= -55) { return `#signal-wifi-fill`; } else if (rssi >= -60) { return `#signal-wifi-3-fill`; } else if (rssi >= -65) { return `#signal-wifi-2-fill`; } else if (rssi >= -70) { return `#signal-wifi-1-fill`; } else { return `#signal-wifi-line`; } } function refreshAP() { $.getJSON('/scan.json', async function() { await sleep(2000); $.getJSON('/ap.json', function(data) { if (data.length > 0) { // sort by signal strength data.sort(function(a, b) { const x = a.rssi; const y = b.rssi; // eslint-disable-next-line no-nested-ternary return x < y ? 1 : x > y ? -1 : 0; }); apList = data; refreshAPHTML2(apList); } }); }); } function formatAP(ssid, rssi, auth){ return `${ssid} `; } function refreshAPHTML2(data) { let h = ''; $('#wifiTable tr td:first-of-type').text(''); $('#wifiTable tr').removeClass('table-success table-warning'); if(data){ data.forEach(function(e) { h+=formatAP(e.ssid, e.rssi, e.auth); }); $('#wifiTable').html(h); } if($('.manual_add').length == 0){ $('#wifiTable').append(formatAP('Manual add', 0,0)); $('#wifiTable tr:last').addClass('table-light text-dark').addClass('manual_add'); } if(ConnectedToSSID.ssid && ( ConnectedToSSID.urc === connectReturnCode.UPDATE_CONNECTION_OK || ConnectedToSSID.urc === connectReturnCode.UPDATE_FAILED_ATTEMPT_AND_RESTORE )){ const wifiSelector=`#wifiTable td:contains("${ConnectedToSSID.ssid}")`; if($(wifiSelector).filter(function() {return $(this).text() === ConnectedToSSID.ssid; }).length==0){ $('#wifiTable').prepend(`${formatAP(ConnectedToSSID.ssid, ConnectedToSSID.rssi ?? 0, 0)}`); } $(wifiSelector).filter(function() {return $(this).text() === ConnectedToSSID.ssid; }).siblings().first().html('✓').parent().addClass((ConnectedToSSID.urc === connectReturnCode.UPDATE_CONNECTION_OK?'table-success':'table-warning')); $('span#foot-wifi').html(`, SSID: ${ConnectedToSSID.ssid}, IP: ${ConnectedToSSID.ip}`); $('#wifiStsIcon').attr('xlink:href',rssiToIcon(ConnectedToSSID.rssi)); } else { $('span#foot-wifi').html(''); } } function showTask(task) { console.debug( this.toLocaleString() + '\t' + task.nme + '\t' + task.cpu + '\t' + taskStates[task.st] + '\t' + task.minstk + '\t' + task.bprio + '\t' + task.cprio + '\t' + task.num ); $('tbody#tasks').append( '' + task.num + '' + task.nme + '' + task.cpu + '' + taskStates[task.st] + '' + task.minstk + '' + task.bprio + '' + task.cprio + '' ); } function getMessages() { $.getJSON('/messages.json?1', async function(data) { for (const msg of data) { const msgAge = msg.current_time - msg.sent_time; var msgTime = new Date(); msgTime.setTime(msgTime.getTime() - msgAge); switch (msg.class) { case 'MESSAGING_CLASS_OTA': // message: "{"ota_dsc":"Erasing flash complete","ota_pct":0}" var otaData = JSON.parse(msg.message); if ((otaData.ota_pct ?? 0) !== 0) { otapct = otaData.ota_pct; $('.progress-bar') .css('width', otapct + '%') .attr('aria-valuenow', otapct); $('.progress-bar').html(otapct + '%'); } if ((otaData.ota_dsc ??'') !== '') { otadsc = otaData.ota_dsc; $('span#flash-status').html(otadsc); if (msg.type === 'MESSAGING_ERROR' || otapct > 95) { blockFlashButton = false; enableStatusTimer = true; } } break; case 'MESSAGING_CLASS_STATS': // for task states, check structure : task_state_t var statsData = JSON.parse(msg.message); console.debug( msgTime.toLocalShort() + ' - Number of running tasks: ' + statsData.ntasks ); console.debug( msgTime.toLocalShort() + '\tname' + '\tcpu' + '\tstate' + '\tminstk' + '\tbprio' + '\tcprio' + '\tnum' ); if (statsData.tasks) { if ($('#tasks_sect').css('visibility') === 'collapse') { $('#tasks_sect').css('visibility', 'visible'); } $('tbody#tasks').html(''); statsData.tasks .sort(function(a, b) { return b.cpu - a.cpu; }) .forEach(showTask, msgTime); } else if ($('#tasks_sect').css('visibility') === 'visible') { $('tbody#tasks').empty(); $('#tasks_sect').css('visibility', 'collapse'); } break; case 'MESSAGING_CLASS_SYSTEM': showMessage(msg, msgTime); break; case 'MESSAGING_CLASS_CFGCMD': var msgparts = msg.message.split(/([^\n]*)\n(.*)/gs); showCmdMessage(msgparts[1], msg.type, msgparts[2], true); break; case 'MESSAGING_CLASS_BT': JSON.parse(msg.message).forEach(function(btEntry) { showMessage({ type:msg.type, message:`BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} `}, msgTime); }); break; default: break; } } }).fail(handleExceptionResponse); /* Minstk is minimum stack space left Bprio is base priority cprio is current priority nme is name st is task state. I provided a "typedef" that you can use to convert to text cpu is cpu percent used */ } function handleRecoveryMode(data) { const locRecovery= data.recovery ??0; if (LastRecoveryState !== locRecovery) { LastRecoveryState = locRecovery; $('input#show-nvs')[0].checked = LastRecoveryState === 1; } if ($('input#show-nvs')[0].checked) { $('*[href*="-nvs"]').show(); } else { $('*[href*="-nvs"]').hide(); } enableStatusTimer = true; if (locRecovery === 1) { recovery = true; $('.recovery_element').show(); $('.ota_element').hide(); $('#boot-button').html('Reboot'); $('#boot-form').attr('action', '/reboot_ota.json'); } else { recovery = false; $('.recovery_element').hide(); $('.ota_element').show(); $('#boot-button').html('Recovery'); $('#boot-form').attr('action', '/recovery.json'); } } function hasConnectionChanged(data){ // gw: "192.168.10.1" // ip: "192.168.10.225" // netmask: "255.255.255.0" // ssid: "MyTestSSID" return (data.urc !== ConnectedToSSID.urc || data.ssid !== ConnectedToSSID.ssid || data.gw !== ConnectedToSSID.gw || data.netmask !== ConnectedToSSID.netmask || data.ip !== ConnectedToSSID.ip || data.rssi !== ConnectedToSSID.rssi ) } function handleWifiDialog(data){ if($('#WifiConnectDialog').is(':visible')){ if(ConnectedToSSID.ip) { $('#ipAddress').text(ConnectedToSSID.ip); } if(ConnectedToSSID.ssid) { $('#connectedToSSID' ).text(ConnectedToSSID.ssid); } if(ConnectedToSSID.gw) { $('#gateway' ).text(ConnectedToSSID.gw); } if(ConnectedToSSID.netmask) { $('#netmask' ).text(ConnectedToSSID.netmask); } if(ConnectingToSSID.Action===undefined || (ConnectingToSSID.Action && ConnectingToSSID.Action == ConnectingToActions.STS)) { $("*[class*='connecting']").hide(); $('.connecting-status').show(); } if(SystemConfig.ap_ssid){ $('#apName').text(SystemConfig.ap_ssid); } if(SystemConfig.ap_pwd){ $('#apPass').text(SystemConfig.ap_pwd); } if(!data) { return; } else { switch (data.urc) { case connectReturnCode.UPDATE_CONNECTION_OK: if(data.ssid && data.ssid===ConnectingToSSID.ssid){ $("*[class*='connecting']").hide(); $('.connecting-success').show(); ConnectingToSSID.Action = ConnectingToActions.STS; } break; case connectReturnCode.UPDATE_FAILED_ATTEMPT: // if(ConnectingToSSID.Action !=ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid ){ $("*[class*='connecting']").hide(); $('.connecting-fail').show(); } break; case connectReturnCode.UPDATE_LOST_CONNECTION: break; case connectReturnCode.UPDATE_FAILED_ATTEMPT_AND_RESTORE: if(ConnectingToSSID.Action !=ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid ){ $("*[class*='connecting']").hide(); $('.connecting-fail').show(); } break; case connectReturnCode.UPDATE_USER_DISCONNECT: // that's a manual disconnect // if ($('#wifi-status').is(':visible')) { // $('#wifi-status').slideUp('fast', function() {}); // $('span#foot-wifi').html(''); // } break; default: break; } } } } function handleWifiStatus(data) { if(hasConnectionChanged(data)){ ConnectedToSSID=data; refreshAPHTML2(); } handleWifiDialog(data); } function batteryToIcon(voltage) { /* Assuming Li-ion 18650s as a power source, 3.9V per cell, or above is treated as full charge (>75% of capacity). 3.4V is empty. The gauge is loosely following the graph here: https://learn.adafruit.com/li-ion-and-lipoly-batteries/voltages using the 0.2C discharge profile for the rest of the values. */ if (voltage > 0) { if (inRange(voltage, 5.8, 6.8) || inRange(voltage, 8.8, 10.2)) { return `battery-low-line`; } else if (inRange(voltage, 6.8, 7.4) || inRange(voltage, 10.2, 11.1)) { return `battery-low-line`; } else if ( inRange(voltage, 7.4, 7.5) || inRange(voltage, 11.1, 11.25) ) { return `battery-low-line`; } else if ( inRange(voltage, 7.5, 7.8) || inRange(voltage, 11.25, 11.7) ) { return `battery-fill`; } else { return `battery-line`; } } } function checkStatus() { RepeatCheckStatusInterval(); if (!enableStatusTimer) { return; } if (blockAjax) { return; } blockAjax = true; getMessages(); $.getJSON('/status.json', function(data) { handleRecoveryMode(data); handleWifiStatus(data); handlebtstate(data); let pname = ''; if (data.project_name && data.project_name !== '') { pname = data.project_name; } if (data.version && data.version !== '') { versionName=data.version; appTitle= (versionName.toLowerCase().includes('squeezeamp')?"SqueezeAmp":"SqueezeESP32"); $("#navtitle").text= `${appTitle}`; $('span#foot-fw').html(`fw: ${versionName}, mode: ${pname}`); } else { $('span#flash-status').html(''); } if (data.Voltage) { $('#battery').attr('xlink:href', `#${batteryToIcon(data.Voltage)}`); $('#battery').show(); } else { $('#battery').hide(); } $('#o_jack').attr('display', Number(data.Jack) ? 'inline' : 'none'); blockAjax = false; }).fail(function(xhr, ajaxOptions, thrownError) { handleExceptionResponse(xhr, ajaxOptions, thrownError); blockAjax = false; }); } // eslint-disable-next-line no-unused-vars window.runCommand = function(button, reboot) { let cmdstring = button.attributes.cmdname.value; showCmdMessage( button.attributes.cmdname.value, 'MESSAGING_INFO', 'Executing.', false ); const fields = document.getElementById('flds-' + cmdstring); cmdstring += ' '; if (fields) { const allfields = fields.querySelectorAll('select,input'); for (var i = 0; i < allfields.length; i++) { const attr = allfields[i].attributes; let qts = ''; let opt = ''; let isSelect = allfields[i].attributes.class.value === 'custom-select'; if ((isSelect && allfields[i].selectedIndex !== 0) || !isSelect) { if (attr.longopts.value !== 'undefined') { opt += '--' + attr.longopts.value; } else if (attr.shortopts.value !== 'undefined') { opt = '-' + attr.shortopts.value; } if (attr.hasvalue.value === 'true') { if (allfields[i].value !== '') { qts = /\s/.test(allfields[i].value) ? '"' : ''; cmdstring += opt + ' ' + qts + allfields[i].value + qts + ' '; } } else { // this is a checkbox if (allfields[i].checked) { cmdstring += opt + ' '; } } } } } console.log(cmdstring); const data = { timestamp: Date.now(), }; data.command = cmdstring; $.ajax({ url: '/commands.json', dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify(data), error: handleExceptionResponse, complete: function(response) { // var returnedResponse = JSON.parse(response.responseText); console.log(response.responseText); if ( response.responseText && JSON.parse(response.responseText).Result === 'Success' && reboot ) { delayReboot(2500, button.attributes.cmdname.value); } }, }); enableStatusTimer = true; } function getLongOps(data, name, longopts){ return data.values[name]!==undefined?data.values[name][longopts]:""; } function getCommands() { $.getJSON('/commands.json', function(data) { console.log(data); data.commands.forEach(function(command) { if ($('#flds-' + command.name).length === 0) { const cmdParts = command.name.split('-'); const isConfig = cmdParts[0] === 'cfg'; const targetDiv = '#tab-' + cmdParts[0] + '-' + cmdParts[1]; let innerhtml = ''; // innerhtml+=''+(isConfig?'

':''); innerhtml += '
' + command.help.encodeHTML().replace(/\n/g, '
') + '
'; innerhtml += '
'; if (command.argtable) { command.argtable.forEach(function(arg) { let placeholder = arg.datatype || ''; const ctrlname = command.name + '-' + arg.longopts; const curvalue = getLongOps(data,command.name,arg.longopts); let attributes = 'hasvalue=' + arg.hasvalue + ' '; // attributes +='datatype="'+arg.datatype+'" '; attributes += 'longopts="' + arg.longopts + '" '; attributes += 'shortopts="' + arg.shortopts + '" '; attributes += 'checkbox=' + arg.checkbox + ' '; attributes += 'cmdname="' + command.name + '" '; attributes += 'id="' + ctrlname + '" name="' + ctrlname + '" hasvalue="' + arg.hasvalue + '" '; let extraclass = arg.mincount > 0 ? 'bg-success' : ''; if (arg.glossary === 'hidden') { attributes += ' style="visibility: hidden;"'; } if (arg.checkbox) { innerhtml += '
'; } else { innerhtml += '
'; if (placeholder.includes('|')) { extraclass = placeholder.startsWith('+') ? ' multiple ' : ''; placeholder = placeholder .replace('<', '') .replace('=', '') .replace('>', ''); innerhtml += `'; } else { innerhtml += ''; } innerhtml += 'Previous value: ' + (curvalue || '') + ''; } innerhtml += '
'; }); } innerhtml += '
'; innerhtml += ''; if (isConfig) { innerhtml += ''; innerhtml += ''; } else { innerhtml += ''; } innerhtml += '
'; if (isConfig) { $(targetDiv).append(innerhtml); } else { $('#commands-list').append(innerhtml); } } }); data.commands.forEach(function(command) { $('[cmdname=' + command.name + ']:input').val(''); $('[cmdname=' + command.name + ']:checkbox').prop('checked', false); if (command.argtable) { command.argtable.forEach(function(arg) { const ctrlselector = '#' + command.name + '-' + arg.longopts; const ctrlValue = getLongOps(data,command.name,arg.longopts); if (arg.checkbox) { $(ctrlselector)[0].checked = ctrlValue; } else { if (ctrlValue !== undefined) { $(ctrlselector) .val(ctrlValue) .trigger('change'); } if ( $(ctrlselector)[0].value.length === 0 && (arg.datatype || '').includes('|') ) { $(ctrlselector)[0].value = '--'; } } }); } }); }).fail(function(xhr, ajaxOptions, thrownError) { handleExceptionResponse(xhr, ajaxOptions, thrownError); $('#commands-list').empty(); blockAjax = false; }); } function getConfig() { $.getJSON('/config.json', function(entries) { $('#nvsTable tr').remove(); const data = (entries.config? entries.config : entries); SystemConfig = data; Object.keys(data) .sort() .forEach(function(key) { let val = data[key].value; if (key === 'autoexec') { if (data.autoexec.value === '0') { $('#disable-squeezelite')[0].checked = true; } else { $('#disable-squeezelite')[0].checked = false; } } else if (key === 'autoexec1') { const re = /-o\s?(["][^"]*["]|[^-]+)/g; const m = re.exec(val); if (m[1].toUpperCase().startsWith('I2S')) { handleTemplateTypeRadio('i2s'); } else if (m[1].toUpperCase().startsWith('SPDIF')) { handleTemplateTypeRadio('spdif'); } else if (m[1].toUpperCase().startsWith('"BT')) { handleTemplateTypeRadio('bt'); } } else if (key === 'host_name') { val = val.replaceAll('"', ''); $('input#dhcp-name1').val(val); $('input#dhcp-name2').val(val); $('#player').val(val); document.title = val; hostName = val; } $('tbody#nvsTable').append( '' + '' + key + '' + "" + "" ); if (entries.gpio) { $('tbody#gpiotable tr').remove(); entries.gpio.forEach(function(gpioEntry) { $('tbody#gpiotable').append( '' + gpioEntry.group + '' + gpioEntry.name + '' + gpioEntry.gpio + '' + (gpioEntry.fixed ? 'Fixed' : 'Configuration') + '' ); }); } }).fail(function(xhr, ajaxOptions, thrownError) { handleExceptionResponse(xhr, ajaxOptions, thrownError); blockAjax = false; }); } function showLocalMessage(message, severity) { const msg = { message: message, type: severity, }; showMessage(msg, new Date()); } function showMessage(msg, msgTime) { let color = 'table-success'; if (msg.type === 'MESSAGING_WARNING') { color = 'table-warning'; if (messageseverity === 'MESSAGING_INFO') { messageseverity = 'MESSAGING_WARNING'; } } else if (msg.type === 'MESSAGING_ERROR') { if ( messageseverity === 'MESSAGING_INFO' || messageseverity === 'MESSAGING_WARNING' ) { messageseverity = 'MESSAGING_ERROR'; } color = 'table-danger'; } if (++messagecount > 0) { $('#msgcnt').removeClass('badge-success'); $('#msgcnt').removeClass('badge-warning'); $('#msgcnt').removeClass('badge-danger'); $('#msgcnt').addClass(pillcolors[messageseverity]); $('#msgcnt').text(messagecount); } $('#syslogTable').append( "" + '' + msgTime.toLocalShort() + '' + '' + msg.message.encodeHTML() + '' + '' ); } function inRange(x, min, max) { return (x - min) * (x - max) <= 0; } function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }