var he = require('he'); var Promise = require('es6-promise').Promise; window.bootstrap = require('bootstrap'); import Cookies from 'js-cookie'; 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); }, }); function get_control_option_value(obj) { let ctrl,id,val,opt; let radio = false; let checked = false; if (typeof (obj) === 'string') { id = obj; ctrl = $(`#${id}`); } else { id = $(obj).attr('id'); ctrl = $(obj); } if(ctrl.attr('type') === 'checkbox'){ opt = $(obj).checked?id.replace('cmd_opt_', ''):''; val = true; } else { opt = id.replace('cmd_opt_', ''); val = $(obj).val(); val = `${val.includes(" ") ? '"' : ''}${val}${val.includes(" ") ? '"' : ''}`; } return { opt, val }; } function handleNVSVisible() { let nvs_previous_checked = isEnabled(Cookies.get("show-nvs")); $('input#show-nvs')[0].checked = nvs_previous_checked; if ($('input#show-nvs')[0].checked || recovery) { $('*[href*="-nvs"]').show(); } else { $('*[href*="-nvs"]').hide(); } } function concatenateOptions(options) { let commandLine = ' '; for (const [option, value] of Object.entries(options)) { if (option !== 'n' && option !== 'o') { commandLine += `-${option} `; if (value !== true) { commandLine += `${value} `; } } } return commandLine; } function isEnabled(val) { return val != undefined && typeof val === 'string' && val.match("[Yy1]"); } 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: { 'label': '', 'icon': 'media_bluetooth_on' }, bt_disconnected: { 'label': '', 'icon': 'media_bluetooth_off' }, bt_neutral: { 'label': '', 'icon': 'bluetooth' }, bt_connecting: { 'label': '', 'icon': 'bluetooth_searching' }, bt_connected: { 'label': '', 'icon': 'bluetooth_connected' }, bt_disabled: { 'label': '', 'icon': 'bluetooth_disabled' }, play_arrow: { 'label': '', 'icon': 'play_circle_filled' }, pause: { 'label': '', 'icon': 'pause_circle' }, stop: { 'label': '', 'icon': 'stop_circle' }, '': { 'label': '', 'icon': '' } }; const batIcons = [ { icon: "battery_0_bar", label: '▪', ranges: [{ f: 5.8, t: 6.8 }, { f: 8.8, t: 10.2 }] }, { icon: "battery_2_bar", label: '▪▪', ranges: [{ f: 6.8, t: 7.4 }, { f: 10.2, t: 11.1 }] }, { icon: "battery_3_bar", label: '▪▪▪', ranges: [{ f: 7.4, t: 7.5 }, { f: 11.1, t: 11.25 }] }, { icon: "battery_4_bar", label: '▪▪▪▪', ranges: [{ f: 7.5, t: 7.8 }, { f: 11.25, t: 11.7 }] } ]; const btStateIcons = [ { desc: 'Idle', sub: ['bt_neutral'] }, { desc: 'Discovering', sub: ['bt_connecting'] }, { desc: 'Discovered', sub: ['bt_connecting'] }, { desc: 'Unconnected', sub: ['bt_disconnected'] }, { desc: 'Connecting', sub: ['bt_connecting'] }, { 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 = { OK: 0, FAIL: 1, DISC: 2, LOST: 3, RESTORE: 4, ETH: 5 } 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', }; let flashState = { NONE: 0, REBOOT_TO_RECOVERY: 2, SET_FWURL: 5, FLASHING: 6, DONE: 7, UPLOADING: 8, ERROR: 9, UPLOADCOMPLETE: 10, _state: -1, olderRecovery: false, statusText: '', flashURL: '', flashFileName: '', statusPercent: 0, Completed: false, recovery: false, prevRecovery: false, updateModal: new bootstrap.Modal(document.getElementById('otadiv'), {}), reset: function () { this.olderRecovery = false; this.statusText = ''; this.statusPercent = -1; this.flashURL = ''; this.flashFileName = undefined; this.UpdateProgress(); $('#rTable tr.release').removeClass('table-success table-warning'); $('.flact').prop('disabled', false); $('#flashfilename').value = null; $('#fw-url-input').value = null; if (!this.isStateError()) { $('span#flash-status').html(''); $('#fwProgressLabel').parent().removeClass('bg-danger'); } this._state = this.NONE return this; }, isStateUploadComplete: function () { return this._state == this.UPLOADCOMPLETE; }, isStateError: function () { return this._state == this.ERROR; }, isStateNone: function () { return this._state == this.NONE; }, isStateRebootRecovery: function () { return this._state == this.REBOOT_TO_RECOVERY; }, isStateSetUrl: function () { return this._state == this.SET_FWURL; }, isStateFlashing: function () { return this._state == this.FLASHING; }, isStateDone: function () { return this._state == this.DONE; }, isStateUploading: function () { return this._state == this.UPLOADING; }, init: function () { this._state = this.NONE; return this; }, SetStateError: function () { this._state = this.ERROR; $('#fwProgressLabel').parent().addClass('bg-danger'); return this; }, SetStateNone: function () { this._state = this.NONE; return this; }, SetStateRebootRecovery: function () { this._state = this.REBOOT_TO_RECOVERY; // Reboot system to recovery mode this.SetStatusText('Starting recovery mode.') $.ajax({ url: '/recovery.json', context: this, dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ timestamp: Date.now(), }), error: function (xhr, _ajaxOptions, thrownError) { this.setOTAError(`Unexpected error while trying to restart to recovery. (status=${xhr.status ?? ''}, error=${thrownError ?? ''} ) `); }, complete: function (response) { this.SetStatusText('Waiting for system to boot.') }, }); return this; }, SetStateSetUrl: function () { this._state = this.SET_FWURL; this.statusText = 'Sending firmware download location.'; let confData = { fwurl: { value: this.flashURL, type: 33, } }; post_config(confData); return this; }, SetStateFlashing: function () { this._state = this.FLASHING; return this; }, SetStateDone: function () { this._state = this.DONE; this.reset(); return this; }, SetStateUploading: function () { this._state = this.UPLOADING; return this.SetStatusText('Sending file to device.'); }, SetStateUploadComplete: function () { this._state = this.UPLOADCOMPLETE; return this; }, isFlashExecuting: function () { return true === (this._state != this.UPLOADING && (this.statusText !== '' || this.statusPercent >= 0)); }, toString: function () { let keys = Object.keys(this); return keys.find(x => this[x] === this._state); }, setOTATargets: function () { this.flashURL = ''; this.flashFileName = ''; this.flashURL = $('#fw-url-input').val(); let fileInput = $('#flashfilename')[0].files; if (fileInput.length > 0) { this.flashFileName = fileInput[0]; } if (this.flashFileName.length == 0 && this.flashURL.length == 0) { this.setOTAError('Invalid url or file. Cannot start OTA'); } return this; }, setOTAError: function (message) { this.SetStateError().SetStatusPercent(0).SetStatusText(message).reset(); return this; }, ShowDialog: function () { if (!this.isStateNone()) { this.updateModal.show(); $('.flact').prop('disabled', true); } return this; }, SetStatusPercent: function (pct) { var pctChanged = (this.statusPercent != pct); this.statusPercent = pct; if (pctChanged) { if (!this.isStateUploading() && !this.isStateFlashing()) { this.SetStateFlashing(); } if (pct == 100) { if (this.isStateFlashing()) { this.SetStateDone(); } else if (this.isStateUploading()) { this.statusPercent = 0; this.SetStateFlashing(); } } this.UpdateProgress().ShowDialog(); } return this; }, SetStatusText: function (txt) { var changed = (this.statusText != txt); this.statusText = txt; if (changed) { $('span#flash-status').html(this.statusText); this.ShowDialog(); } return this; }, UpdateProgress: function () { $('.progress-bar') .css('width', this.statusPercent + '%') .attr('aria-valuenow', this.statusPercent) .text(this.statusPercent + '%') $('.progress-bar').html((this.isStateDone() ? 100 : this.statusPercent) + '%'); return this; }, StartOTA: function () { this.logEvent(this.StartOTA.name); $('#fwProgressLabel').parent().removeClass('bg-danger'); this.setOTATargets(); if (this.isStateError()) { return this; } if (!recovery) { this.SetStateRebootRecovery(); } else { this.SetStateFlashing().TargetReadyStartOTA(); } return this; }, UploadLocalFile: function () { this.SetStateUploading(); const xhttp = new XMLHttpRequest(); xhttp.context = this; var boundHandleUploadProgressEvent = this.HandleUploadProgressEvent.bind(this); var boundsetOTAError = this.setOTAError.bind(this); xhttp.upload.addEventListener("progress", boundHandleUploadProgressEvent, false); xhttp.onreadystatechange = function () { if (xhttp.readyState === 4) { if (xhttp.status === 0 || xhttp.status === 404) { boundsetOTAError(`Upload Failed. Recovery version might not support uploading. Please use web update instead.`); } } }; xhttp.open('POST', '/flash.json', true); xhttp.send(this.flashFileName); }, TargetReadyStartOTA: function () { if (recovery && this.prevRecovery && !this.isStateRebootRecovery() && !this.isStateFlashing()) { // this should only execute once, while being in a valid state return this; } this.logEvent(this.TargetReadyStartOTA.name); if (!recovery) { console.error('Event TargetReadyStartOTA fired in the wrong mode '); return this; } this.prevRecovery = true; if (this.flashFileName !== '') { this.UploadLocalFile(); } else if (this.flashURL != '') { this.SetStateSetUrl(); } else { this.setOTAError('Invalid URL or file name while trying to start the OTa process') } }, HandleUploadProgressEvent: function (data) { this.logEvent(this.HandleUploadProgressEvent.name); this.SetStateUploading().SetStatusPercent(Math.round(data.loaded / data.total * 100)).SetStatusText('Uploading file to device'); }, EventTargetStatus: function (data) { if (!this.isStateNone()) { this.logEvent(this.EventTargetStatus.name); } if (data.ota_pct ?? -1 >= 0) { this.olderRecovery = true; this.SetStatusPercent(data.ota_pct); } if ((data.ota_dsc ?? '') != '') { this.olderRecovery = true; this.SetStatusText(data.ota_dsc); } if (data.recovery != undefined) { this.recovery = data.recovery === 1 ? true : false; } if (this.isStateRebootRecovery() && this.recovery) { this.TargetReadyStartOTA(); } }, EventOTAMessageClass: function (data) { this.logEvent(this.EventOTAMessageClass.name); var otaData = JSON.parse(data); this.SetStatusPercent(otaData.ota_pct).SetStatusText(otaData.ota_dsc); }, logEvent: function (fun) { console.log(`${fun}, flash state ${this.toString()}, recovery: ${this.recovery}, ota pct: ${this.statusPercent}, ota desc: ${this.statusText}`); } }; window.hideSurrounding = function (obj) { $(obj).parent().parent().hide(); } let presetsloaded = false; let is_i2c_locked = false; let statusInterval = 2000; let messageInterval = 2500; function post_config(data) { let confPayload = { timestamp: Date.now(), config: data }; $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify(confPayload), error: handleExceptionResponse, }); } window.hFlash = function () { // reset file upload selection if any; $('#flashfilename').value = null flashState.StartOTA(); } window.handleReboot = function (link) { if (link == 'reboot_ota') { $('#reboot_ota_nav').removeClass('active').prop("disabled", true); delayReboot(500, '', 'reboot_ota'); } else { $('#reboot_nav').removeClass('active'); delayReboot(500, '', link); } } function parseSqueezeliteCommandLine(commandLine) { const options = {}; let output, name; let otherValues = ''; const argRegex = /("[^"]+"|'[^']+'|\S+)/g; const args = commandLine.match(argRegex); let i = 0; while (i < args.length) { const arg = args[i]; if (arg.startsWith('-')) { const option = arg.slice(1); if (option === '') { otherValues += args.slice(i).join(' '); break; } let value = true; if (i + 1 < args.length && !args[i + 1].startsWith('-')) { value = args[i + 1].replace(/"/g, '').replace(/'/g, ''); i++; } options[option] = value; } else { otherValues += arg + ' '; } i++; } otherValues = otherValues.trim(); output = getOutput(options); name = getName(options); let otherOptions={btname:null,n:null}; // assign o and n options to otheroptions if present if (options.o && output.toUpperCase() === 'BT') { let temp = parseSqueezeliteCommandLine(options.o); if(temp.name) { otherOptions.btname = temp.name; } delete options.o; } if (options.n) { otherOptions['n'] = options.n; delete options.n; } return { name, output, options, otherValues,otherOptions }; } function getOutput(options) { let output; if (options.o){ output = options.o.replace(/"/g, '').replace(/'/g, ''); /* set output as the first alphanumerical word in the command line */ if (output.indexOf(' ') > 0) { output = output.substring(0, output.indexOf(' ')); } } return output; } function getName(options) { let name; /* if n option present, assign to name variable */ if (options.n){ name = options.n.replace(/"/g, '').replace(/'/g, ''); } return name; } function isConnected() { return ConnectedTo.hasOwnProperty('ip') && ConnectedTo.ip != '0.0.0.0' && ConnectedTo.ip != ''; } function getIcon(icons) { return isConnected() ? icons.icon : icons.label; } function handlebtstate(data) { let icon = ''; let tt = ''; if (data.bt_status !== undefined && data.bt_sub_status !== undefined) { const iconindex = btStateIcons[data.bt_status].sub[data.bt_sub_status]; if (iconindex) { icon = btIcons[iconindex]; tt = btStateIcons[data.bt_status].desc; } else { icon = btIcons.bt_connected; tt = 'Output status'; } } $('#o_type').attr('title', tt); $('#o_bt').html(isConnected() ? icon.label : icon.text); } function handleTemplateTypeRadio(outtype) { $('#o_type').children('span').css({ display: 'none' }); let changed = false; if (outtype === 'bt') { changed = output !== 'bt' && output !== ''; output = 'bt'; } else if (outtype === 'spdif') { changed = output !== 'spdif' && output !== ''; output = 'spdif'; } else { changed = output !== 'i2s' && output !== ''; output = 'i2s'; } $('#' + output).prop('checked', true); $('#o_' + output).css({ display: 'inline' }); if (changed) { Object.keys(commandDefaults[output]).forEach(function (key) { $(`#cmd_opt_${key}`).val(commandDefaults[output][key]); }); } } function handleExceptionResponse(xhr, _ajaxOptions, thrownError) { console.log(xhr.status); console.log(thrownError); if (thrownError !== '') { showLocalMessage(thrownError, 'MESSAGING_ERROR'); } } function HideCmdMessage(cmdname) { $('#toast_' + cmdname) .removeClass('table-success') .removeClass('table-warning') .removeClass('table-danger') .addClass('table-success') .removeClass('show'); $('#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) .removeClass('table-success') .removeClass('table-warning') .removeClass('table-danger') .addClass(color) .addClass('show'); 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); } let releaseURL = 'https://api.github.com/repos/sle118/squeezelite-esp32/releases'; let recovery = false; let messagesHeld = false; let commandBTSinkName = ''; const commandHeader = 'squeezelite '; const commandDefaults = { i2s: { b: "500:2000", C: "30", W: "", Z: "96000", o: "I2S" }, spdif: { b: "500:2000", C: "30", W: "", Z: "48000", o: "SPDIF" }, bt: { b: "500:2000", C: "30", W: "", Z: "44100", o: "BT" }, }; let validOptions = { codecs: ['flac', 'pcm', 'mp3', 'ogg', 'aac', 'wma', 'alac', 'dsd', 'mad', 'mpg'] }; //let blockFlashButton = false; let apList = null; //let selectedSSID = ''; //let checkStatusInterval = null; let messagecount = 0; let messageseverity = 'MESSAGING_INFO'; let SystemConfig = {}; let LastCommandsState = null; var output = ''; let hostName = ''; let versionName = 'Squeezelite-ESP32'; let prevmessage = ''; let project_name = versionName; let depth = 16; let board_model = ''; let platform_name = versionName; let preset_name = ''; let btSinkNamesOptSel = '#cfg-audio-bt_source-sink_name'; let ConnectedTo = {}; let ConnectingToSSID = {}; let lmsBaseUrl; let prevLMSIP = ''; 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 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; } function handleHWPreset(allfields, reboot) { const selJson = JSON.parse(allfields[0].value); var cmd = allfields[0].attributes.cmdname.value; console.log(`selected model: ${selJson.name}`); let confPayload = { timestamp: Date.now(), config: { model_config: { value: selJson.name, type: 33 } } }; for (const [name, value] of Object.entries(selJson.config)) { const storedval = (typeof value === 'string' || value instanceof String) ? value : JSON.stringify(value); confPayload.config[name] = { value: storedval, type: 33, } showCmdMessage( cmd, 'MESSAGING_INFO', `Setting ${name}=${storedval} `, true ); } showCmdMessage( cmd, 'MESSAGING_INFO', `Committing `, true ); $.ajax({ url: '/config.json', dataType: 'text', method: 'POST', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify(confPayload), error: function (xhr, _ajaxOptions, thrownError) { handleExceptionResponse(xhr, _ajaxOptions, thrownError); showCmdMessage( cmd, 'MESSAGING_ERROR', `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status} `, true ); }, success: function (response) { showCmdMessage( cmd, 'MESSAGING_INFO', `Saving complete `, true ); console.log(response); if (reboot) { delayReboot(2500, cmd); } }, }); } // pull json file from https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/b462691f69e2ad31ac95c547af6ec97afb0f53db/squeezelite-esp32-presets.json and function loadPresets() { if ($("#cfg-hw-preset-model_config").length == 0) return; if (presetsloaded) return; presetsloaded = true; $('#cfg-hw-preset-model_config').html(''); $.getJSON( 'https://gist.githubusercontent.com/sle118/dae585e157b733a639c12dc70f0910c5/raw/', { _: new Date().getTime() }, function (data) { $.each(data, function (key, val) { $('#cfg-hw-preset-model_config').append(``); if (preset_name !== '' && preset_name == val.name) { $('#cfg-hw-preset-model_config').val(preset_name); } }); if (preset_name !== '') { ('#prev_preset').show().val(preset_name); } } ).fail(function (jqxhr, textStatus, error) { const err = textStatus + ', ' + error; console.log('Request Failed: ' + err); } ); } function delayReboot(duration, cmdname, ota = 'reboot') { const url = '/' + ota + '.json'; $('tbody#tasks').empty(); $('#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'); $("button[onclick*='handleReboot']").addClass('rebooting'); $.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'); 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} -o ${output} `; $('.sqcmd').each(function () { let { opt, val } = get_control_option_value($(this)); if ((opt && opt.length>0 ) && typeof(val) == 'boolean' || val.length > 0) { const optStr=opt===':'?opt:(` -${opt} `); val = typeof(val) == 'boolean'?'':val; commandLine += `${optStr} ${val}`; } }); const resample=$('#cmd_opt_R input[name=resample]:checked'); if (resample.length>0 && resample.attr('suffix')!=='') { commandLine += resample.attr('suffix'); // now check resample_i option and if checked, add suffix to command line if ($('#resample_i').is(":checked") && resample.attr('aint') =='true') { commandLine += $('#resample_i').attr('suffix'); } } if (output === 'bt') { showCmdMessage( 'cfg-audio-tmpl', 'MESSAGING_INFO', 'Remember to configure the Bluetooth audio device name.\n', true ); } commandLine += concatenateOptions(options); 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 && JSON.parse(response.responseText).result === 'OK' ) { showCmdMessage('cfg-audio-tmpl', 'MESSAGING_INFO', 'Done.\n', true); if (apply) { delayReboot(1500, 'cfg-audio-tmpl'); } } else if (JSON.parse(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(), }), }); } function setPlatformFilter(val) { if ($('.upf').filter(function () { return $(this).text().toUpperCase() === val.toUpperCase() }).length > 0) { $('#splf').val(val).trigger('input'); return true; } return false; } 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 } function renderError(opt,error){ const fieldname = `cmd_opt_${opt}`; let errorFieldName=`${fieldname}-error`; let errorField=$(`#${errorFieldName}`); let field=$(`#${fieldname}`); if (!errorField || errorField.length ==0) { field.after(`
`); errorField=$(`#${errorFieldName}`); } if(error.length ==0){ errorField.hide(); field.removeClass('is-invalid'); field.addClass('is-valid'); errorField.text(''); } else { errorField.show(); errorField.text(error); field.removeClass('is-valid'); field.addClass('is-invalid'); } return errorField; } $(document).ready(function () { $('.material-icons').each(function (_index, entry) { entry.attributes['icon'] = entry.textContent; }); setIcons(true); handleNVSVisible(); flashState.init(); $('#fw-url-input').on('input', function () { if ($(this).val().length > 8 && ($(this).val().startsWith('http://') || $(this).val().startsWith('https://'))) { $('#start-flash').show(); } else { $('#start-flash').hide(); } }); $('.upSrch').on('input', function () { const val = this.value; $("#rTable tr").removeClass(this.id + '_hide'); if (val.length > 0) { $(`#rTable td:nth-child(${$(this).parent().index() + 1})`).filter(function () { return !$(this).text().toUpperCase().includes(val.toUpperCase()); }).parent().addClass(this.id + '_hide'); } $('[class*="_hide"]').hide(); $('#rTable tr').not('[class*="_hide"]').show() }); setTimeout(refreshAP, 1500); /* add validation for cmd_opt_c, which accepts a comma separated list. getting known codecs from validOptions.codecs array use bootstrap classes to highlight the error with an overlay message */ $('#options input').on('input', function () { const { opt, val } = get_control_option_value(this); if (opt === 'c' || opt === 'e') { const fieldname = `cmd_opt_${opt}_codec-error`; const values = val.split(',').map(function (item) { return item.trim(); }); /* get a list of invalid codecs */ const invalid = values.filter(function (item) { return !validOptions.codecs.includes(item); }); renderError(opt,invalid.length > 0 ? `Invalid codec(s) ${invalid.join(', ')}` : ''); } /* add validation for cmd_opt_m, which accepts a mac_address */ if (opt === 'm') { const mac_regex = /^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$/; renderError(opt,mac_regex.test(val) ? '' : 'Invalid MAC address'); } if (opt === 'r') { const rateRegex = /^(\d+\.?\d*|\.\d+)-(\d+\.?\d*|\.\d+)$|^(\d+\.?\d*)$|^(\d+\.?\d*,)+\d+\.?\d*$/; renderError(opt,rateRegex.test(val)?'':`Invalid rate(s) ${val}. Acceptable format: |-|,,`); } } ); $('#WifiConnectDialog')[0].addEventListener('shown.bs.modal', function (event) { $("*[class*='connecting']").hide(); if (event?.relatedTarget) { ConnectingToSSID.Action = ConnectingToActions.CONN; if ($(event.relatedTarget).children('td:eq(1)').text() == ConnectedTo.ssid) { ConnectingToSSID.Action = ConnectingToActions.STS; } else { if (!$(event.relatedTarget).is(':last-child')) { ConnectingToSSID.ssid = $(event.relatedTarget).children('td:eq(1)').text(); $('#manual_ssid').val(ConnectingToSSID.ssid); } else { ConnectingToSSID.Action = ConnectingToActions.MAN; ConnectingToSSID.ssid = ''; $('#manual_ssid').val(ConnectingToSSID.ssid); } } } if (ConnectingToSSID.Action !== ConnectingToActions.STS) { $('.connecting-init').show(); $('#manual_ssid').trigger('focus'); } else { handleWifiDialog(); } }); $('#WifiConnectDialog')[0].addEventListener('hidden.bs.modal', function () { $('#WifiConnectDialog input').val(''); }); $('#uCnfrm')[0].addEventListener('shown.bs.modal', function () { $('#selectedFWURL').text($('#fw-url-input').val()); }); $('input#show-commands')[0].checked = LastCommandsState === 1; $('a[href^="#tab-commands"]').hide(); $('#load-nvs').on('click', function () { $('#nvsfilename').trigger('click'); }); $('#nvsfilename').on('change', function () { if (typeof window.FileReader !== 'function') { throw "The file API isn't supported on this browser."; } if (!this.files) { throw 'This browser does not support the `files` property of the file input.'; } if (!this.files[0]) { return undefined; } const file = this.files[0]; let fr = new FileReader(); fr.onload = function (e) { let data = {}; try { data = JSON.parse(e.target.result); } catch (ex) { alert('Parsing failed!\r\n ' + ex); } $('input.nvs').each(function (_index, entry) { $(this).parent().removeClass('bg-warning').removeClass('bg-success'); if (data[entry.id]) { if (data[entry.id] !== entry.value) { console.log( 'Changed ' + entry.id + ' ' + entry.value + '==>' + data[entry.id] ); $(this).parent().addClass('bg-warning'); $(this).val(data[entry.id]); } else { $(this).parent().addClass('bg-success'); } } }); var changed = $("input.nvs").children('.bg-warning'); if (changed) { alert('Highlighted values were changed. Press Commit to change on the device'); } } fr.readAsText(file); this.value = null; } ); $('#clear-syslog').on('click', function () { messagecount = 0; messageseverity = 'MESSAGING_INFO'; $('#msgcnt').text(''); $('#syslogTable').html(''); }); $('#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 () { }); }); $('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(); } }); $('#disable-squeezelite').on('click', function () { // this.checked = this.checked ? 1 : 0; // $('#disable-squeezelite').prop('checked') if (this.checked) { // Store the current value before overwriting it const currentValue = $('#cmd_opt_s').val(); $('#cmd_opt_s').data('originalValue', currentValue); // Overwrite the value with '-disable' $('#cmd_opt_s').val('-disable'); } else { // Retrieve the original value const originalValue = $('#cmd_opt_s').data('originalValue'); // Restore the original value if it exists, otherwise set it to an empty string $('#cmd_opt_s').val(originalValue ? originalValue : ''); } }); $('input#show-nvs').on('click', function () { this.checked = this.checked ? 1 : 0; Cookies.set("show-nvs", this.checked ? 'Y' : 'N'); handleNVSVisible(); }); $('#btn_reboot_recovery').on('click', function () { handleReboot('recovery'); }); $('#btn_reboot').on('click', function () { handleReboot('reboot'); }); $('#btn_flash').on('click', function () { hFlash(); }); $('#save-autoexec1').on('click', function () { saveAutoexec1(false); }); $('#commit-autoexec1').on('click', function () { saveAutoexec1(true); }); $('#btn_disconnect').on('click', function () { ConnectedTo = {}; refreshAPHTML2(); $.ajax({ url: '/connect.json', dataType: 'text', method: 'DELETE', cache: false, contentType: 'application/json; charset=utf-8', data: JSON.stringify({ timestamp: Date.now(), }), }); }); $('#btnJoin').on('click', function () { handleConnect(); }); $('#reboot_nav').on('click', function () { handleReboot('reboot'); }); $('#reboot_ota_nav').on('click', function () { handleReboot('reboot_ota'); }); $('#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 () { post_config(getConfigJson(false)); }); $('#fwUpload').on('click', function () { const fileInput = document.getElementById('flashfilename').files; if (fileInput.length === 0) { alert('No file selected!'); } else { $('#fw-url-input').value = null; flashState.StartOTA(); } }); $('[name=output-tmpl]').on('click', function () { handleTemplateTypeRadio(this.id); }); $('#chkUpdates').on('click', function () { $('#rTable').html(''); $.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 cfg = namecomponents[2]; const branch = namecomponents[3]; var bits = ver.substr(ver.lastIndexOf('-') + 1); bits = (bits == '32' || bits == '16') ? bits : ''; 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, '- ').encodeHTML(); $('#rTable').append(` ${ver}${new Date(release.created_at).toLocalShort()} ${cfg}${branch}${bits}` ); }); if (i > 7) { $('#releaseTable').append( "" + "" + "" + '' + '' ); $('#showallbutton').on('click', function () { $('tr.hide').removeClass('hide'); $('tr#showall').addClass('hide'); }); } $('#searchfw').css('display', 'inline'); if (!setPlatformFilter(platform_name)) { setPlatformFilter(project_name) } $('#rTable tr.release').on('click', function () { var url = this.attributes['fwurl'].value; if (lmsBaseUrl) { url = url.replace(/.*\/download\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/'); } $('#fw-url-input').val(url); $('#start-flash').show(); $('#rTable tr.release').removeClass('table-success table-warning'); $(this).addClass('table-success table-warning'); }); }).fail(function () { alert('failed to fetch release history!'); }); }); $('#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!'); }); }); $('#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(); getMessages(); checkStatus(); }); // eslint-disable-next-line no-unused-vars window.setURL = function (button) { let url = button.dataset.url; $('[data-bs-url^="http"]') .addClass('btn-success') .removeClass('btn-danger'); $('[data-bs-url="' + url + '"]') .addClass('btn-danger') .removeClass('btn-success'); // if user can proxy download through LMS, modify the URL if (lmsBaseUrl) { url = url.replace(/.*\/download\//, lmsBaseUrl + '/plugins/SqueezeESP32/firmware/'); } $('#fwurl').val(url); } function rssiToIcon(rssi) { if (rssi >= -55) { return { 'label': '****', 'icon': `signal_wifi_statusbar_4_bar` }; } else if (rssi >= -60) { return { 'label': '***', 'icon': `network_wifi_3_bar` }; } else if (rssi >= -65) { return { 'label': '**', 'icon': `network_wifi_2_bar` }; } else if (rssi >= -70) { return { 'label': '*', 'icon': `network_wifi_1_bar` }; } else { return { 'label': '.', 'icon': `signal_wifi_statusbar_null` }; } } function refreshAP() { if (ConnectedTo?.urc === connectReturnCode.ETH) return; $.ajaxSetup({ timeout: 3000 //Time in milliseconds }); $.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) { const rssi_icon = rssiToIcon(rssi); const auth_icon = { label: auth == 0 ? '🔓' : '🔒', icon: auth == 0 ? 'no_encryption' : 'lock' }; return `${ssid} ${getIcon(rssi_icon)} ${getIcon(auth_icon)} `; } 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 (ConnectedTo.ssid && (ConnectedTo.urc === connectReturnCode.OK || ConnectedTo.urc === connectReturnCode.RESTORE)) { const wifiSelector = `#wifiTable td:contains("${ConnectedTo.ssid}")`; if ($(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).length == 0) { $('#wifiTable').prepend(`${formatAP(ConnectedTo.ssid, ConnectedTo.rssi ?? 0, 0)}`); } $(wifiSelector).filter(function () { return $(this).text() === ConnectedTo.ssid; }).siblings().first().html('✓').parent().addClass((ConnectedTo.urc === connectReturnCode.OK ? 'table-success' : 'table-warning')); $('span#foot-if').html(`SSID: ${ConnectedTo.ssid}, IP: ${ConnectedTo.ip}`); $('#wifiStsIcon').html(rssiToIcon(ConnectedTo.rssi)); } else if (ConnectedTo?.urc !== connectReturnCode.ETH) { $('span#foot-if').html(''); } } function refreshETH() { if (ConnectedTo.urc === connectReturnCode.ETH) { $('span#foot-if').html(`Network: Ethernet, IP: ${ConnectedTo.ip}`); } } 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 btExists(name) { return getBTSinkOpt(name).length > 0; } function getBTSinkOpt(name) { return $(`${btSinkNamesOptSel} option:contains('${name}')`); } function getMessages() { $.ajaxSetup({ timeout: messageInterval //Time in milliseconds }); $.getJSON('/messages.json', 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': flashState.EventOTAMessageClass(msg.message); 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': if ($("#cfg-audio-bt_source-sink_name").is('input')) { var attr = $("#cfg-audio-bt_source-sink_name")[0].attributes; var attrs = ''; for (var j = 0; j < attr.length; j++) { if (attr.item(j).name != "type") { attrs += `${attr.item(j).name} = "${attr.item(j).value}" `; } } var curOpt = $("#cfg-audio-bt_source-sink_name")[0].value; $("#cfg-audio-bt_source-sink_name").replaceWith(` `); } JSON.parse(msg.message).forEach(function (btEntry) { // // if (!btExists(btEntry.name)) { $("#cfg-audio-bt_source-sink_name").append(``); showMessage({ type: msg.type, message: `BT Audio device found: ${btEntry.name} RSSI: ${btEntry.rssi} ` }, msgTime); } getBTSinkOpt(btEntry.name).attr('data-bs-description', `${btEntry.name} (${btEntry.rssi}dB)`) .attr('rssi', btEntry.rssi) .attr('value', btEntry.name) .text(`${btEntry.name} [${btEntry.rssi}dB]`).trigger('change'); }); $(btSinkNamesOptSel).append($(`${btSinkNamesOptSel} option`).remove().sort(function (a, b) { console.log(`${parseInt($(a).attr('rssi'))} < ${parseInt($(b).attr('rssi'))} ? `); return parseInt($(a).attr('rssi')) < parseInt($(b).attr('rssi')) ? 1 : -1; })); break; default: break; } } setTimeout(getMessages, messageInterval); }).fail(function (xhr, ajaxOptions, thrownError) { if (xhr.status == 404) { $('.orec').hide(); // system commands won't be available either messagesHeld = true; } else { handleExceptionResponse(xhr, ajaxOptions, thrownError); } if (xhr.status == 0 && xhr.readyState == 0) { // probably a timeout. Target is rebooting? setTimeout(getMessages, messageInterval * 2); // increase duration if a failure happens } else if (!messagesHeld) { // 404 here means we rebooted to an old recovery setTimeout(getMessages, messageInterval); // increase duration if a failure happens } } ); /* 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 (locRecovery === 1) { recovery = true; $('.recovery_element').show(); $('.ota_element').hide(); $('#boot-button').html('Reboot'); $('#boot-form').attr('action', '/reboot_ota.json'); } else { if (!recovery && messagesHeld) { messagesHeld = false; setTimeout(getMessages, messageInterval); // increase duration if a failure happens } 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 !== ConnectedTo.urc || data.ssid !== ConnectedTo.ssid || data.gw !== ConnectedTo.gw || data.netmask !== ConnectedTo.netmask || data.ip !== ConnectedTo.ip || data.rssi !== ConnectedTo.rssi) } function handleWifiDialog(data) { if ($('#WifiConnectDialog').is(':visible')) { if (ConnectedTo.ip) { $('#ipAddress').text(ConnectedTo.ip); } if (ConnectedTo.ssid) { $('#connectedToSSID').text(ConnectedTo.ssid); } if (ConnectedTo.gw) { $('#gateway').text(ConnectedTo.gw); } if (ConnectedTo.netmask) { $('#netmask').text(ConnectedTo.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.value); } if (SystemConfig.ap_pwd) { $('#apPass').text(SystemConfig.ap_pwd.value); } if (!data) { return; } else { switch (data.urc) { case connectReturnCode.OK: if (data.ssid && data.ssid === ConnectingToSSID.ssid) { $("*[class*='connecting']").hide(); $('.connecting-success').show(); ConnectingToSSID.Action = ConnectingToActions.STS; } break; case connectReturnCode.FAIL: // if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid == data.ssid) { $("*[class*='connecting']").hide(); $('.connecting-fail').show(); } break; case connectReturnCode.LOST: break; case connectReturnCode.RESTORE: if (ConnectingToSSID.Action != ConnectingToActions.STS && ConnectingToSSID.ssid != data.ssid) { $("*[class*='connecting']").hide(); $('.connecting-fail').show(); } break; case connectReturnCode.DISC: // that's a manual disconnect // if ($('#wifi-status').is(':visible')) { // $('#wifi-status').slideUp('fast', function() {}); // $('span#foot-wifi').html(''); // } break; default: break; } } } } function setIcons(offline) { $('.material-icons').each(function (_index, entry) { entry.textContent = entry.attributes[offline ? 'aria-label' : 'icon'].value; }); } function handleNetworkStatus(data) { setIcons(!isConnected()); if (hasConnectionChanged(data) || !data.urc) { ConnectedTo = data; $(".if_eth").hide(); $('.if_wifi').hide(); if (!data.urc || ConnectedTo.urc != connectReturnCode.ETH) { $('.if_wifi').show(); refreshAPHTML2(); } else { $(".if_eth").show(); refreshETH(); } } 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. */ for (const iconEntry of batIcons) { for (const entryRanges of iconEntry.ranges) { if (inRange(voltage, entryRanges.f, entryRanges.t)) { return { label: iconEntry.label, icon: iconEntry.icon }; } } } return { label: '▪▪▪▪', icon: "battery_full" }; } function checkStatus() { $.ajaxSetup({ timeout: statusInterval //Time in milliseconds }); $.getJSON('/status.json', function (data) { handleRecoveryMode(data); handleNVSVisible(); handleNetworkStatus(data); handlebtstate(data); flashState.EventTargetStatus(data); if(data.depth) { depth = data.depth; if(depth==16){ $('#cmd_opt_R').show(); } else{ $('#cmd_opt_R').hide(); } } if (data.project_name && data.project_name !== '') { project_name = data.project_name; } if (data.platform_name && data.platform_name !== '') { platform_name = data.platform_name; } if (board_model === '') board_model = project_name; if (board_model === '') board_model = 'Squeezelite-ESP32'; if (data.version && data.version !== '') { versionName = data.version; $("#navtitle").html(`${board_model}${recovery ? '
[recovery]' : ''}`); $('span#foot-fw').html(`fw: ${versionName}, mode: ${recovery ? "Recovery" : project_name}`); } else { $('span#flash-status').html(''); } if (data.Voltage) { const bat_icon = batteryToIcon(data.Voltage); $('#battery').html(`${getIcon(bat_icon)}`); $('#battery').attr("aria-label", bat_icon.label); $('#battery').attr("icon", bat_icon.icon); $('#battery').show(); } else { $('#battery').hide(); } if ((data.message ?? '') != '' && prevmessage != data.message) { // supporting older recovery firmwares - messages will come from the status.json structure prevmessage = data.message; showLocalMessage(data.message, 'MESSAGING_INFO') } is_i2c_locked = data.is_i2c_locked; if (is_i2c_locked) { $('flds-cfg-hw-preset').hide(); } else { $('flds-cfg-hw-preset').show(); } $("button[onclick*='handleReboot']").removeClass('rebooting'); if (typeof lmsBaseUrl == "undefined" || data.lms_ip != prevLMSIP && data.lms_ip && data.lms_port) { const baseUrl = 'http://' + data.lms_ip + ':' + data.lms_port; prevLMSIP = data.lms_ip; $.ajax({ url: baseUrl + '/plugins/SqueezeESP32/firmware/-check.bin', type: 'HEAD', dataType: 'text', cache: false, error: function () { // define the value, so we don't check it any more. lmsBaseUrl = ''; }, success: function () { lmsBaseUrl = baseUrl; } }); } $('#o_jack').css({ display: Number(data.Jack) ? 'inline' : 'none' }); setTimeout(checkStatus, statusInterval); }).fail(function (xhr, ajaxOptions, thrownError) { handleExceptionResponse(xhr, ajaxOptions, thrownError); if (xhr.status == 0 && xhr.readyState == 0) { // probably a timeout. Target is rebooting? setTimeout(checkStatus, messageInterval * 2); // increase duration if a failure happens } else { setTimeout(checkStatus, messageInterval); // increase duration if a failure happens } }); } // 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); const allfields = fields?.querySelectorAll('select,input'); if (cmdstring === 'cfg-hw-preset') return handleHWPreset(allfields, reboot); cmdstring += ' '; if (fields) { for (const field of allfields) { let qts = ''; let opt = ''; let attr = field.attributes; let isSelect = $(field).is('select'); const hasValue = attr?.hasvalue?.value === 'true'; const validVal = (isSelect && field.value !== '--') || (!isSelect && field.value !== ''); if (!hasValue || hasValue && validVal) { 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 (attr?.value !== '') { qts = /\s/.test(field.value) ? '"' : ''; cmdstring += opt + ' ' + qts + field.value + qts + ' '; } } else { // this is a checkbox if (field?.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: function (xhr, _ajaxOptions, thrownError) { var cmd = JSON.parse(this.data).command; if (xhr.status == 404) { showCmdMessage( cmd.substr(0, cmd.indexOf(' ')), 'MESSAGING_ERROR', `${recovery ? 'Limited recovery mode active. Unsupported action ' : 'Unexpected error while processing command'}`, true ); } else { handleExceptionResponse(xhr, _ajaxOptions, thrownError); showCmdMessage( cmd.substr(0, cmd.indexOf(' ') - 1), 'MESSAGING_ERROR', `Unexpected error ${(thrownError !== '') ? thrownError : 'with return status = ' + xhr.status}`, true ); } }, success: function (response) { $('.orec').show(); console.log(response); if ( JSON.parse(response).Result === 'Success' && reboot ) { delayReboot(2500, button.attributes.cmdname.value); } }, }); } function getLongOps(data, name, longopts) { return data.values[name] !== undefined ? data.values[name][longopts] : ""; } function getCommands() { $.ajaxSetup({ timeout: 7000 //Time in milliseconds }); $.getJSON('/commands.json', function (data) { console.log(data); $('.orec').show(); 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 += `
${command.help.encodeHTML().replace(/\n/g, '
')}
`; 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 += '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 += `${arg.checkbox ? '
' : ''}Previous value: ${arg.checkbox ? (curvalue ? 'Checked' : 'Unchecked') : (curvalue || '')}${arg.checkbox ? '' : '
'}`; }); } innerhtml += `
`; if (isConfig) { innerhtml += ` `; } else { innerhtml += ``; } innerhtml += '
'; if (isConfig) { $(targetDiv).append(innerhtml); } else { $('#commands-list').append(innerhtml); } } }); $(".sclk").off('click').on('click', function () { runCommand(this, false); }); $(".cclk").off('click').on('click', function () { runCommand(this, true); }); 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 = '--'; } } }); } }); loadPresets(); }).fail(function (xhr, ajaxOptions, thrownError) { if (xhr.status == 404) { $('.orec').hide(); } else { handleExceptionResponse(xhr, ajaxOptions, thrownError); } $('#commands-list').empty(); }); } function getConfig() { $.ajaxSetup({ timeout: 7000 //Time in milliseconds }); $.getJSON('/config.json', function (entries) { $('#nvsTable tr').remove(); const data = (entries.config ? entries.config : entries); SystemConfig = data; commandBTSinkName = ''; Object.keys(data) .sort() .forEach(function (key) { let val = data[key].value; if (key === 'autoexec1') { /* call new function to parse the squeezelite options */ processSqueezeliteCommandLine(val); } else if (key === 'host_name') { val = val.replaceAll('"', ''); $('input#dhcp-name1').val(val); $('input#dhcp-name2').val(val); if ($('#cmd_opt_n').length == 0) { $('#cmd_opt_n').val(val); } document.title = val; hostName = val; } else if (key === 'rel_api') { releaseURL = val; } else if (key === 'enable_airplay') { $("#s_airplay").css({ display: isEnabled(val) ? 'inline' : 'none' }) } else if (key === 'enable_cspot') { $("#s_cspot").css({ display: isEnabled(val) ? 'inline' : 'none' }) } else if (key == 'preset_name') { preset_name = val; } else if (key == 'board_model') { board_model = val; } $('tbody#nvsTable').append( '' + '' + key + '' + "" + "" ); if (entries.gpio) { $('#pins').show(); $('tbody#gpiotable tr').remove(); entries.gpio.forEach(function (gpioEntry) { $('tbody#gpiotable').append( '' + gpioEntry.group + '' + gpioEntry.name + '' + gpioEntry.gpio + '' + (gpioEntry.fixed ? 'Fixed' : 'Configuration') + '' ); }); } else { $('#pins').hide(); } }).fail(function (xhr, ajaxOptions, thrownError) { handleExceptionResponse(xhr, ajaxOptions, thrownError); }); } function processSqueezeliteCommandLine(val) { const parsed = parseSqueezeliteCommandLine(val); if (parsed.output.toUpperCase().startsWith('I2S')) { handleTemplateTypeRadio('i2s'); } else if (parsed.output.toUpperCase().startsWith('SPDIF')) { handleTemplateTypeRadio('spdif'); } else if (parsed.output.toUpperCase().startsWith('BT')) { if(parsed.otherOptions.btname){ commandBTSinkName= parsed.otherOptions.btname; } handleTemplateTypeRadio('bt'); } Object.keys(parsed.options).forEach(function (key) { const option = parsed.options[key]; if (!$(`#cmd_opt_${key}`).hasOwnProperty('checked')) { $(`#cmd_opt_${key}`).val(option); } else { $(`#cmd_opt_${key}`)[0].checked = option; } }); if (parsed.options.hasOwnProperty('u')) { // parse -u v[:i] and check the appropriate radio button with id #resample_v const [resampleValue, resampleInterpolation] = parsed.options.u.split(':'); $(`#resample_${resampleValue}`).prop('checked', true); // if resampleinterpolation is set, check resample_i checkbox if (resampleInterpolation) { $('#resample_i').prop('checked', true); } } if (parsed.options.hasOwnProperty('s')) { // parse -u v[:i] and check the appropriate radio button with id #resample_v if(parsed.options.s === '-disable'){ $('#disable-squeezelite')[0].checked = true; } else { $('#disable-squeezelite')[0].checked = 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)); }