';
document.getElementById('btnScanAPs').classList.add('disabled');
//document.getElementById('btnConnectWiFi').classList.add('disabled');
getJSON('/scanaps', (err, aps) => {
document.getElementById('btnScanAPs').classList.remove('disabled');
//document.getElementById('btnConnectWiFi').classList.remove('disabled');
console.log(aps);
if (err) {
this.displayAPs({ connected: { name: '', passphrase: '' }, accessPoints: [] });
}
else {
this.displayAPs(aps);
}
});
};
displayAPs(aps) {
let div = '';
let nets = [];
for (let i = 0; i < aps.accessPoints.length; i++) {
let ap = aps.accessPoints[i];
let p = nets.find(elem => elem.name === ap.name);
if (typeof p !== 'undefined' && p) {
p.channel = p.strength > ap.strength ? p.channel : ap.channel;
p.macAddress = p.strength > ap.strength ? p.macAddress : ap.macAddress;
p.strength = Math.max(p.strength, ap.strength);
}
else
nets.push(ap);
}
// Sort by the best signal strength.
nets.sort((a, b) => b.strength - a.strength);
for (let i = 0; i < nets.length; i++) {
let ap = nets[i];
div += `
${ap.name}${this.displaySignal(ap.strength)}
`;
}
let divAps = document.getElementById('divAps');
divAps.setAttribute('data-lastloaded', new Date().getTime());
divAps.innerHTML = div;
//document.getElementsByName('ssid')[0].value = aps.connected.name;
//document.getElementsByName('passphrase')[0].value = aps.connected.passphrase;
//this.procWifiStrength(aps.connected);
};
selectSSID(el) {
let obj = {
name: el.querySelector('span.ssid').innerHTML,
encryption: el.getAttribute('data-encryption'),
strength: parseInt(el.getAttribute('data-strength'), 10),
channel: parseInt(el.getAttribute('data-channel'), 10)
}
console.log(obj);
document.getElementsByName('ssid')[0].value = obj.name;
};
calcWaveStrength(sig) {
let wave = 0;
if (sig > -90) wave++;
if (sig > -80) wave++;
if (sig > -70) wave++;
if (sig > -67) wave++;
if (sig > -30) wave++;
return wave;
};
displaySignal(sig) {
return `
`;
};
saveNetwork() {
let obj = {
connType: document.getElementById('cbHardwired').checked ? document.getElementById('cbFallbackWireless').checked ? 3 : 2 : 1,
wifi: {},
ethernet: {}
};
if (obj.connType >= 2) {
// We are connecting to a LAN but we need the user to be sure about this since
// the information needs to be correct. Incorrect settings can destroy the board.
obj.ethernet = {
boardType: parseInt(document.getElementById('selETHBoardType').value, 10),
phyType: parseInt(document.getElementById('selETHPhyType').value, 10),
phyAddress: parseInt(document.getElementById('selETHAddress').value, 10),
dhcp: document.getElementById('cbUseDHCP').checked,
CLKMode: parseInt(document.getElementById('selETHClkMode').value, 10),
PWRPin: parseInt(document.getElementById('selETHPWRPin').value, 10),
MDCPin: parseInt(document.getElementById('selETHMDCPin').value, 10),
MDIOPin: parseInt(document.getElementById('selETHMDIOPin').value, 10),
ip: document.getElementById('fldIPAddress').value,
subnet: document.getElementById('fldSubnetMask').value,
gateway: document.getElementById('fldGateway').value,
dns1: document.getElementById('fldDNS1').value,
dns2: document.getElementById('fldDNS2').value
}
let boardType = this.ethBoardTypes.find(elem => obj.ethernet.boardType === elem.val);
let phyType = this.ethPhyTypes.find(elem => obj.ethernet.phyType === elem.val);
let clkMode = this.ethClockModes.find(elem => obj.ethernet.CLKMode === elem.val);
let div = document.createElement('div');
let html = `
`;
html += '
BEWARE ... WARNING ... DANGER
';
html += '';
html += '
If this shade is already paired with a motor then changing the rolling code WILL cause it to stop working. Rolling codes are tied to the remote address and the Somfy motor expects these to be sequential.
';
html += '
If you hesitated just a little bit do not press the red button. Green represents safety so press it, wipe the sweat from your brow, and go through the normal pairing process.'
html += '
';
html += ``;
html += '';
html += '
'
html += `
`
html += ``
html += ``
html += `
`;
div.innerHTML = html;
document.getElementById('somfyShade').appendChild(div);
}
});
}
setPaired(shadeId, paired) {
let obj = { shadeId: shadeId, paired: paired || false };
let div = document.getElementById('divPairing');
let overlay = typeof div === 'undefined' ? undefined : waitMessage(div);
putJSON('/setPaired', obj, (err, shade) => {
if (overlay) overlay.remove();
if (err) {
console.log(err);
errorMessage(div, err.message);
}
else if (div) {
console.log(shade);
document.getElementById('somfyMain').style.display = 'none';
document.getElementById('somfyShade').style.display = '';
document.getElementById('btnSaveShade').style.display = 'inline-block';
document.getElementById('btnLinkRemote').style.display = '';
document.getElementsByName('shadeAddress')[0].value = shade.remoteAddress;
document.getElementsByName('shadeName')[0].value = shade.name;
document.getElementsByName('shadeUpTime')[0].value = shade.upTime;
document.getElementsByName('shadeDownTime')[0].value = shade.downTime;
let ico = document.getElementById('icoShade');
ico.style.setProperty('--shade-position', `${shade.position}%`);
ico.setAttribute('data-shadeid', shade.shadeId);
if (shade.paired) {
document.getElementById('btnUnpairShade').style.display = 'inline-block';
document.getElementById('btnPairShade').style.display = 'none';
}
else {
document.getElementById('btnPairShade').style.display = 'inline-block';
document.getElementById('btnUnpairShade').style.display = 'none';
}
this.setLinkedRemotesList(shade);
div.remove();
}
});
}
pairShade(shadeId) {
let div = document.createElement('div');
let html = `
`;
html += '
Follow the instructions below to pair this shade with a Somfy motor
'
html += '';
html += '
';
html += '
Open the shade memory using an existing remote
';
html += '
Press the prog button on the back of the remote until the shade jogs
';
html += '
After the shade jogs press the Prog button below
';
html += '
The shade should jog again indicating that the shade is paired
';
html += '
If the shade jogs, you can press the shade paired button.
'
html += '
If the shade does not jog, press the prog button again.
'
html += '
'
html += `
`
html += ``
html += ``
html += ``
html += `
`;
div.innerHTML = html;
document.getElementById('somfyShade').appendChild(div);
return div;
};
unpairShade(shadeId) {
let div = document.createElement('div');
let html = `
`;
html += '
Follow the instructions below to unpair this shade from a Somfy motor
'
html += '';
html += '
';
html += '
Open the shade memory using an existing remote
';
html += '
Press the prog button on the back of the remote until the shade jogs
';
html += '
After the shade jogs press the Prog button below
';
html += '
The shade should jog again indicating that the shade is unpaired
';
html += '
If the shade jogs, you can press the shade unpaired button.
'
html += '
If the shade does not jog, press the prog button again until the shade jogs.
Press any button on the remote to link it to this shade. This will not change the pairing for the remote and this screen will close when the remote is detected.
';
html += '';
html += `
`;
html += '
';
div.innerHTML = html;
document.getElementById('somfyShade').appendChild(div);
return div;
};
unlinkRemote(shadeId, remoteAddress) {
let prompt = promptMessage(document.getElementById('fsSomfySettings'), 'Are you sure you want to unlink this remote from the shade?', () => {
let obj = {
shadeId: shadeId,
remoteAddress: remoteAddress
};
let overlay = waitMessage(prompt);
putJSON('/unlinkRemote', obj, (err, shade) => {
console.log(shade);
overlay.remove();
prompt.remove();
this.setLinkedRemotesList(shade);
});
});
};
deviationChanged(el) {
document.getElementById('spanDeviation').innerText = (el.value / 100).fmt('#,##0.00');
};
rxBandwidthChanged(el) {
document.getElementById('spanRxBandwidth').innerText = (el.value / 100).fmt('#,##0.00');
};
txPowerChanged(el) {
console.log(el.value);
let lvls = [-30, -20, -15, -10, -6, 0, 5, 7, 10, 11, 12];
document.getElementById('spanTxPower').innerText = lvls[el.value];
};
processShadeTarget(el, shadeId) {
let positioner = document.querySelector(`.shade-positioner[data-shadeid="${shadeId}"]`);
if (positioner) {
positioner.querySelector(`.shade-target`).innerHTML = el.value;
somfy.sendCommand(shadeId, el.value);
}
}
processShadeTiltTarget(el, shadeId) {
let positioner = document.querySelector(`.shade-positioner[data-shadeid="${shadeId}"]`);
if (positioner) {
positioner.querySelector(`.shade-tilt-target`).innerHTML = el.value;
somfy.sendTiltCommand(shadeId, el.value);
}
}
openSetPosition(shadeId) {
console.log('Opening Shade Positioner');
if (typeof shadeId === 'undefined') {
return;
}
else {
let shade = document.querySelector(`div.somfyShadeCtl[data-shadeid="${shadeId}"]`);
if (shade) {
let ctls = document.querySelectorAll('.shade-positioner');
for (let i = 0; i < ctls.length; i++) {
console.log('Closing shade positioner');
ctls[i].remove();
}
let currPos = parseInt(shade.getAttribute('data-target'), 0);
let elname = shade.querySelector(`.shadectl-name`);
let shadeName = elname.innerHTML;
let html = `
${shadeName}
`;
html += ``;
html += ``;
if (makeBool(shade.getAttribute('data-tilt'))) {
let currTiltPos = parseInt(shade.getAttribute('data-tilttarget'), 10);
html += ``;
html += ``;
}
html += `
`;
let div = document.createElement('div');
div.setAttribute('class', 'shade-positioner');
div.setAttribute('data-shadeid', shadeId);
div.addEventListener('onclick', (event) => { event.stopPropagation(); });
div.innerHTML = html;
shade.appendChild(div);
document.body.addEventListener('click', () => {
let ctls = document.querySelectorAll('.shade-positioner');
for (let i = 0; i < ctls.length; i++) {
console.log('Closing shade positioner');
ctls[i].remove();
}
}, { once: true });
}
}
}
};
var somfy = new Somfy();
class MQTT {
async init() { this.loadMQTT(); }
async loadMQTT() {
let overlay = waitMessage(document.getElementById('fsMQTTSettings'));
getJSON('/mqttsettings', (err, settings) => {
overlay.remove();
if (err) {
console.log(err);
}
else {
console.log(settings);
let dd = document.getElementsByName('mqtt-protocol')[0];
for (let i = 0; i < dd.options.length; i++) {
if (dd.options[i].text === settings.proto) {
dd.selectedIndex = i;
break;
}
}
if (dd.selectedIndex < 0) dd.selectedIndex = 0;
document.getElementsByName('mqtt-host')[0].value = settings.hostname;
document.getElementsByName('mqtt-port')[0].value = settings.port;
document.getElementsByName('mqtt-username')[0].value = settings.username;
document.getElementsByName('mqtt-password')[0].value = settings.password;
document.getElementsByName('mqtt-topic')[0].value = settings.rootTopic;
document.getElementsByName('mqtt-enabled')[0].checked = settings.enabled;
}
});
};
connectMQTT() {
if (document.getElementById('btnConnectMQTT').classList.contains('disabled')) return;
document.getElementById('btnConnectMQTT').classList.add('disabled');
let obj = {
enabled: document.getElementsByName('mqtt-enabled')[0].checked,
protocol: document.getElementsByName('mqtt-protocol')[0].value,
hostname: document.getElementsByName('mqtt-host')[0].value,
port: parseInt(document.getElementsByName('mqtt-port')[0].value, 10),
username: document.getElementsByName('mqtt-username')[0].value,
password: document.getElementsByName('mqtt-password')[0].value,
rootTopic: document.getElementsByName('mqtt-topic')[0].value
}
console.log(obj);
if (isNaN(obj.port) || obj.port < 0) {
errorMessage(document.getElementById('fsMQTTSettings'), 'Invalid port number. Likely ports are 1183, 8883 for MQTT/S or 80,443 for HTTP/S');
return;
}
let overlay = waitMessage(document.getElementById('fsMQTTSettings'));
putJSON('/connectmqtt', obj, (err, response) => {
overlay.remove();
document.getElementById('btnConnectMQTT').classList.remove('disabled');
console.log(response);
});
};
};
var mqtt = new MQTT();
class Firmware {
async init() { }
backup() {
var link = document.createElement('a');
link.href = '/backup';
link.setAttribute('download', 'backup');
document.body.appendChild(link);
link.click();
link.remove();
};
restore() {
let div = this.createFileUploader('/restore');
let inst = div.querySelector('div[id=divInstText]');
inst.innerHTML = '
Select a backup file that you would like to restore then press the Upload File button.
';
document.getElementById('fsUpdates').appendChild(div);
};
createFileUploader(service) {
let div = document.createElement('div');
div.setAttribute('id', 'divUploadFile');
div.setAttribute('class', 'instructions');
div.style.width = '100%';
let html = `
`;
html += ``;
html += ``;
html += ``;
html += ``
html += `
`
html += ``
html += `
`;
html += `
`;
div.innerHTML = html;
return div;
};
updateFirmware() {
let div = this.createFileUploader('/updateFirmware');
let inst = div.querySelector('div[id=divInstText]');
inst.innerHTML = '
Select a binary file [SomfyController.ino.esp32.bin] containing the device firmware then press the Upload File button.
';
document.getElementById('fsUpdates').appendChild(div);
};
updateApplication() {
let div = this.createFileUploader('/updateApplication');
general.reloadApp = true;
let inst = div.querySelector('div[id=divInstText]');
inst.innerHTML = '
Select a binary file [SomfyController.littlefs.bin] containing the littlefs data for the application then press the Upload File button.
';
inst.innerHTML += '
A backup file for your configuration will be downloaded to your browser. If the application update process fails please restore this file using the restore button
';
document.getElementById('fsUpdates').appendChild(div);
};
async uploadFile(service, el) {
let field = el.querySelector('input[type="file"]');
let filename = field.value;
console.log(filename);
switch (service) {
case '/updateApplication':
if (filename.indexOf('.littlefs') === -1 || !filename.endsWith('.bin')) {
errorMessage(el, 'This file is not a valid littleFS file system.');
return;
}
// The first thing we need to do is backup the configuration. So lets do this
// in a promise.
await new Promise((resolve, reject) => {
firmware.backup();
try {
// Next we need to download the current configuration data.
getText('/shades.cfg', (err, cfg) => {
if (err)
reject(err);
else {
resolve();
console.log(cfg);
}
});
} catch (err) {
reject(err);
serviceError(el, err);
return;
}
}).catch((err) => {
serviceError(el, err);
});
break;
case '/updateFirmware':
if (filename.indexOf('.ino.') === -1 || !filename.endsWith('.bin')) {
errorMessage(el, 'This file is not a valid firmware binary file.');
return;
}
break;
case '/restore':
if (!filename.endsWith('.backup') || filename.indexOf('ESPSomfyRTS') === -1) {
errorMessage(el, 'This file is not a valid backup file')
return;
}
}
let formData = new FormData();
let btnUpload = el.querySelector('button[id="btnUploadFile"]');
let btnCancel = el.querySelector('button[id="btnClose"]');
btnUpload.style.display = 'none';
field.disabled = true;
let btnSelectFile = el.querySelector('div[id="btn-select-file"]');
let prog = el.querySelector('div[id="progFileUpload"]');
prog.style.display = '';
btnSelectFile.style.visibility = 'hidden';
formData.append('file', field.files[0]);
let xhr = new XMLHttpRequest();
xhr.open('POST', service, true);
xhr.upload.onprogress = function (evt) {
let pct = evt.total ? Math.round((evt.loaded / evt.total) * 100) : 0;
prog.style.setProperty('--progress', `${pct}%`);
prog.setAttribute('data-progress', `${pct}%`);
console.log(evt);
};
xhr.onerror = function (err) {
console.log(err);
};
xhr.onload = function () {
console.log('File upload load called');
btnCancel.innerText = 'Close';
switch (service) {
case '/restore':
(async () => {
await somfy.init();
if (document.getElementById('divUploadFile')) document.getElementById('divUploadFile').remove();
})();
break;
case '/updateApplication':
break;
}
};
xhr.send(formData);
};
};
var firmware = new Firmware();