Update 1.0.9

Fix issues with long SSID names
Make RSSI testing for remote receive tuning.
This commit is contained in:
Robert Strouse 2023-01-29 19:29:12 -08:00
parent 26714f71b7
commit 92ea0f6c99
7 changed files with 137 additions and 99 deletions

View file

@ -2,7 +2,7 @@
#ifndef configsettings_h #ifndef configsettings_h
#define configsettings_h #define configsettings_h
#define FW_VERSION "v1.0.8" #define FW_VERSION "v1.0.9"
enum DeviceStatus { enum DeviceStatus {
DS_OK = 0, DS_OK = 0,
DS_ERROR = 1, DS_ERROR = 1,
@ -38,7 +38,7 @@ class WifiSettings: BaseSettings {
WifiSettings(); WifiSettings();
char serverId[10] = ""; char serverId[10] = "";
char hostname[32] = "ESPSomfyRTS"; char hostname[32] = "ESPSomfyRTS";
char ssid[32] = ""; char ssid[64] = "";
char passphrase[32] = ""; char passphrase[32] = "";
bool ssdpBroadcast = true; bool ssdpBroadcast = true;
bool begin(); bool begin();

View file

@ -13,6 +13,8 @@ extern ConfigSettings settings;
extern Web webServer; extern Web webServer;
extern SocketEmitter sockEmit; extern SocketEmitter sockEmit;
extern MQTTClass mqtt; extern MQTTClass mqtt;
extern rebootDelay_t rebootDelay;
void Network::end() { void Network::end() {
sockEmit.end(); sockEmit.end();
SSDP.end(); SSDP.end();
@ -21,10 +23,10 @@ void Network::end() {
} }
bool Network::setup() { bool Network::setup() {
WiFi.persistent(false); WiFi.persistent(false);
Serial.print("WiFi Mode: "); //Serial.print("WiFi Mode: ");
Serial.println(WiFi.getMode()); //Serial.println(WiFi.getMode());
if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true); if(WiFi.status() == WL_CONNECTED) WiFi.disconnect(true);
WiFi.mode(WIFI_AP_STA); WiFi.mode(WIFI_STA);
settings.WIFI.printNetworks(); settings.WIFI.printNetworks();
sockEmit.begin(); sockEmit.begin();
if(!this->connect()) this->openSoftAP(); if(!this->connect()) this->openSoftAP();
@ -36,6 +38,7 @@ void Network::loop() {
this->lastEmit = millis(); this->lastEmit = millis();
this->emitSockets(); this->emitSockets();
} }
else sockEmit.loop();
if(settings.WIFI.ssdpBroadcast) { if(settings.WIFI.ssdpBroadcast) {
if(!SSDP.isStarted) SSDP.begin(); if(!SSDP.isStarted) SSDP.begin();
SSDP.loop(); SSDP.loop();
@ -47,8 +50,8 @@ void Network::emitSockets() {
if(WiFi.status() == WL_CONNECTED) { if(WiFi.status() == WL_CONNECTED) {
if(abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 2 || WiFi.channel() != this->lastChannel) { if(abs(abs(WiFi.RSSI()) - abs(this->lastRSSI)) > 2 || WiFi.channel() != this->lastChannel) {
char buf[50]; char buf[128];
sprintf(buf, "{\"ssid\":\"%s\", \"strength\":%d, \"channel\":%d}", WiFi.SSID(), WiFi.RSSI(), WiFi.channel()); sprintf(buf, "{\"ssid\":\"%s\",\"strength\":%d,\"channel\":%d}", WiFi.SSID(), WiFi.RSSI(), WiFi.channel());
sockEmit.sendToClients("wifiStrength", buf); sockEmit.sendToClients("wifiStrength", buf);
this->lastRSSI = WiFi.RSSI(); this->lastRSSI = WiFi.RSSI();
this->lastChannel = WiFi.channel(); this->lastChannel = WiFi.channel();
@ -263,18 +266,31 @@ bool Network::openSoftAP() {
while ((WiFi.status() != WL_CONNECTED)) while ((WiFi.status() != WL_CONNECTED))
{ {
for(int i = 0; i < 3; i++) { for(int i = 0; i < 3; i++) {
delay(100); //delay(100);
//digitalWrite(LED_BUILTIN, HIGH); //digitalWrite(LED_BUILTIN, HIGH);
delay(100); //delay(100);
//digitalWrite(LED_BUILTIN, LOW); //digitalWrite(LED_BUILTIN, LOW);
} }
int clients = WiFi.softAPgetStationNum(); int clients = WiFi.softAPgetStationNum();
if(clients > 0)
Serial.print(clients);
else
Serial.print(".");
delay(100);
webServer.loop(); webServer.loop();
if(millis() - this->lastEmit > 1500) {
if(this->connect()) {}
this->lastEmit = millis();
this->emitSockets();
if(clients > 0)
Serial.print(clients);
else
Serial.print(".");
c++;
}
sockEmit.loop();
if(rebootDelay.reboot && millis() > rebootDelay.rebootTime) {
this->end();
ESP.restart();
break;
}
// If no clients have connected in 3 minutes from starting this server reboot this pig. This will // If no clients have connected in 3 minutes from starting this server reboot this pig. This will
// force a reboot cycle until we have some response. That is unless the SSID has been cleared. // force a reboot cycle until we have some response. That is unless the SSID has been cleared.
if(clients == 0 && strlen(settings.WIFI.ssid) > 0 && millis() - startTime > 3 * 60000) { if(clients == 0 && strlen(settings.WIFI.ssid) > 0 && millis() - startTime > 3 * 60000) {
@ -289,8 +305,10 @@ bool Network::openSoftAP() {
WiFi.softAPdisconnect(true); WiFi.softAPdisconnect(true);
return false; return false;
} }
if(++c % 100 == 0) { if(c == 100) {
Serial.println(); Serial.println();
c = 0;
} }
yield();
} }
} }

View file

@ -13,6 +13,7 @@ extern SomfyShadeController somfy;
extern SocketEmitter sockEmit; extern SocketEmitter sockEmit;
extern MQTTClass mqtt; extern MQTTClass mqtt;
uint8_t rxmode = 0; // Indicates whether the radio is in receive mode. Just to ensure there isn't more than one interrupt hooked.
#define SYMBOL 640 #define SYMBOL 640
#if defined(ESP8266) #if defined(ESP8266)
#define RECEIVE_ATTR ICACHE_RAM_ATTR #define RECEIVE_ATTR ICACHE_RAM_ATTR
@ -254,12 +255,7 @@ bool SomfyShadeController::begin() {
pref.begin("Shades"); pref.begin("Shades");
pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds)); pref.getBytes("shadeIds", this->m_shadeIds, sizeof(this->m_shadeIds));
pref.end(); pref.end();
this->transceiver.begin(); //this->transceiver.begin();
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
if(i != 0) Serial.print(",");
Serial.print(this->m_shadeIds[i]);
}
Serial.println();
sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds)); sortArray<uint8_t>(this->m_shadeIds, sizeof(this->m_shadeIds));
for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) { for(uint8_t i = 0; i < sizeof(this->m_shadeIds); i++) {
if(i != 0) Serial.print(","); if(i != 0) Serial.print(",");
@ -461,8 +457,8 @@ void SomfyShade::load() {
memset(linkedAddresses, 0x00, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES); memset(linkedAddresses, 0x00, sizeof(uint32_t) * SOMFY_MAX_LINKED_REMOTES);
snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId); snprintf(shadeKey, sizeof(shadeKey), "SomfyShade%u", this->shadeId);
// Now load up each of the shades into memory. // Now load up each of the shades into memory.
Serial.print("key:"); //Serial.print("key:");
Serial.println(shadeKey); //Serial.println(shadeKey);
pref.begin(shadeKey); pref.begin(shadeKey);
pref.getString("name", this->name, sizeof(this->name)); pref.getString("name", this->name, sizeof(this->name));
this->paired = pref.getBool("paired", false); this->paired = pref.getBool("paired", false);
@ -513,9 +509,7 @@ void SomfyShade::publish() {
void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); } void SomfyShade::emitState(const char *evt) { this->emitState(255, evt); }
void SomfyShade::emitState(uint8_t num, const char *evt) { void SomfyShade::emitState(uint8_t num, const char *evt) {
char buf[220]; char buf[220];
char shadeKey[15]; snprintf(buf, sizeof(buf), "{\"shadeId\":%d,\"remoteAddress\":%d,\"name\":\"%s\",\"direction\":%d,\"position\":%d,\"target\":%d}", this->shadeId, this->getRemoteAddress(), this->name, this->direction, this->position, this->target);
snprintf(shadeKey, sizeof(shadeKey), "Shade_%u", this->shadeId);
sprintf(buf, "{\"shadeId\":%d, \"remoteAddress\":%d, \"name\":\"%s\", \"direction\":%d, \"position\":%d, \"target\":%d}", this->shadeId, this->getRemoteAddress(), this->name, this->direction, this->position, this->target);
if(num >= 255) sockEmit.sendToClients(evt, buf); if(num >= 255) sockEmit.sendToClients(evt, buf);
else sockEmit.sendToClient(num, evt, buf); else sockEmit.sendToClient(num, evt, buf);
if(mqtt.connected()) { if(mqtt.connected()) {
@ -1086,9 +1080,11 @@ void Transceiver::clearReceived(void) {
attachInterrupt(interruptPin, handleReceive, CHANGE); attachInterrupt(interruptPin, handleReceive, CHANGE);
} }
void Transceiver::enableReceive(void) { void Transceiver::enableReceive(void) {
if(rxmode > 0) return;
if(this->config.enabled) { if(this->config.enabled) {
Serial.print("Enabling receive on Pin #"); Serial.print("Enabling receive on Pin #");
Serial.println(this->config.RXPin); Serial.println(this->config.RXPin);
rxmode = 1;
pinMode(this->config.RXPin, INPUT); pinMode(this->config.RXPin, INPUT);
interruptPin = digitalPinToInterrupt(this->config.RXPin); interruptPin = digitalPinToInterrupt(this->config.RXPin);
ELECHOUSE_cc1101.SetRx(); ELECHOUSE_cc1101.SetRx();
@ -1096,8 +1092,10 @@ void Transceiver::enableReceive(void) {
} }
} }
void Transceiver::disableReceive(void) { void Transceiver::disableReceive(void) {
rxmode = 0;
if(interruptPin > 0) detachInterrupt(interruptPin); if(interruptPin > 0) detachInterrupt(interruptPin);
interruptPin = 0; interruptPin = 0;
} }
bool Transceiver::toJSON(JsonObject& obj) { bool Transceiver::toJSON(JsonObject& obj) {
Serial.println("Setting Transceiver Json"); Serial.println("Setting Transceiver Json");
@ -1281,15 +1279,14 @@ void transceiver_config_t::apply() {
if(this->enabled) { if(this->enabled) {
Serial.print("Applying radio settings "); Serial.print("Applying radio settings ");
Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin); Serial.printf("SCK:%u MISO:%u MOSI:%u CSN:%u RX:%u TX:%u\n", this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin, this->RXPin, this->TXPin);
ELECHOUSE_cc1101.Init();
ELECHOUSE_cc1101.setGDO(this->RXPin, this->TXPin); ELECHOUSE_cc1101.setGDO(this->RXPin, this->TXPin);
ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin); ELECHOUSE_cc1101.setSpiPin(this->SCKPin, this->MISOPin, this->MOSIPin, this->CSNPin);
ELECHOUSE_cc1101.Init();
ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet. ELECHOUSE_cc1101.setMHZ(this->frequency); // Here you can set your basic frequency. The lib calculates the frequency automatically (default = 433.92).The cc1101 can: 300-348 MHZ, 387-464MHZ and 779-928MHZ. Read More info from datasheet.
ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz. ELECHOUSE_cc1101.setRxBW(this->rxBandwidth); // Set the Receive Bandwidth in kHz. Value from 58.03 to 812.50. Default is 812.50 kHz.
ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max! ELECHOUSE_cc1101.setPA(this->txPower); // Set TxPower. The following settings are possible depending on the frequency band. (-30 -20 -15 -10 -6 0 5 7 10 11 12) Default is max!
ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode. //ELECHOUSE_cc1101.setCCMode(this->internalCCMode); // set config for internal transmission mode.
ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK. //ELECHOUSE_cc1101.setModulation(this->modulationMode); // set modulation mode. 0 = 2-FSK, 1 = GFSK, 2 = ASK/OOK, 3 = 4-FSK, 4 = MSK.
if (!ELECHOUSE_cc1101.getCC1101()) { if (!ELECHOUSE_cc1101.getCC1101()) {
Serial.println("Error setting up the radio"); Serial.println("Error setting up the radio");
} }
@ -1336,7 +1333,7 @@ void Transceiver::loop() {
this->clearReceived(); this->clearReceived();
somfy.processFrame(this->frame, false); somfy.processFrame(this->frame, false);
char buf[177]; char buf[177];
sprintf(buf, "{\"encKey\":%d, \"address\":%d, \"rcode\":%d, \"command\":\"%s\", \"rssi\":%d}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd), this->frame.rssi); snprintf(buf, sizeof(buf), "{\"encKey\":%d,\"address\":%d,\"rcode\":%d,\"command\":\"%s\",\"rssi\":%d}", this->frame.encKey, this->frame.remoteAddress, this->frame.rollingCode, translateSomfyCommand(this->frame.cmd), this->frame.rssi);
sockEmit.sendToClients("remoteFrame", buf); sockEmit.sendToClients("remoteFrame", buf);
} }
} }

Binary file not shown.

Binary file not shown.

View file

@ -961,7 +961,7 @@ void Web::begin() {
bool reboot; bool reboot;
if (ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true; if (ssid.compareTo(settings.WIFI.ssid) != 0) reboot = true;
if (passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true; if (passphrase.compareTo(settings.WIFI.passphrase) != 0) reboot = true;
if (!settings.WIFI.ssidExists(ssid.c_str())) { if (!settings.WIFI.ssidExists(ssid.c_str()) && ssid.length() > 0) {
server.send(400, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"WiFi Network Does not exist\"}"); server.send(400, _encoding_json, "{\"status\":\"ERROR\",\"desc\":\"WiFi Network Does not exist\"}");
} }
else { else {

View file

@ -1,5 +1,4 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
@ -167,7 +166,7 @@
}); });
}; };
setAppVersion() { document.getElementById('spanAppVersion').innerText = 'v1.0.8'; }; setAppVersion() { document.getElementById('spanAppVersion').innerText = 'v1.0.9'; };
setTimeZones() { setTimeZones() {
let dd = document.getElementById('selTimeZone'); let dd = document.getElementById('selTimeZone');
dd.length = 0; dd.length = 0;
@ -535,6 +534,7 @@
procRemoteFrame(frame) { procRemoteFrame(frame) {
console.log(frame); console.log(frame);
document.getElementById('spanRssi').innerHTML = frame.rssi; document.getElementById('spanRssi').innerHTML = frame.rssi;
document.getElementById('spanFrameCount').innerHTML = parseInt(document.getElementById('spanFrameCount').innerHTML, 10) + 1
let lnk = document.getElementById('divLinking'); let lnk = document.getElementById('divLinking');
if (lnk) { if (lnk) {
let obj = { let obj = {
@ -1016,8 +1016,24 @@
document.getElementById('fsUpdates').appendChild(div); document.getElementById('fsUpdates').appendChild(div);
}; };
async uploadFile(service, el) { async uploadFile(service, el) {
let formData = new FormData();
let field = el.querySelector('input[type="file"]'); 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;
}
break;
case '/updateFirmware':
if (filename.indexOf('.ino.esp') === -1 || !filename.endsWith('.bin')) {
errorMessage(el, 'This file is not a valid firmware binary file.');
return;
}
break;
}
let formData = new FormData();
let btnUpload = el.querySelector('button[id="btnUploadFile"]'); let btnUpload = el.querySelector('button[id="btnUploadFile"]');
let btnCancel = el.querySelector('button[id="btnClose"]'); let btnCancel = el.querySelector('button[id="btnClose"]');
btnUpload.style.display = 'none'; btnUpload.style.display = 'none';
@ -1026,7 +1042,6 @@
let prog = el.querySelector('div[id="progFileUpload"]'); let prog = el.querySelector('div[id="progFileUpload"]');
prog.style.display = ''; prog.style.display = '';
btnSelectFile.style.visibility = 'hidden'; btnSelectFile.style.visibility = 'hidden';
formData.append('file', field.files[0]); formData.append('file', field.files[0]);
let xhr = new XMLHttpRequest(); let xhr = new XMLHttpRequest();
xhr.open('POST', service, true); xhr.open('POST', service, true);
@ -1234,72 +1249,80 @@
wms[i].remove(); wms[i].remove();
} }
waitMessage(document.getElementById('divContainer')).classList.add('socket-wait'); waitMessage(document.getElementById('divContainer')).classList.add('socket-wait');
socket = new WebSocket(`ws://${location.host}:8080`); try {
socket.onmessage = (evt) => { console.log(location);
if (evt.data.startsWith('42')) { socket = new WebSocket(`ws://${window.location.hostname}:8080/`);
let ndx = evt.data.indexOf(','); socket.onmessage = (evt) => {
let eventName = evt.data.substring(3, ndx); if (evt.data.startsWith('42')) {
let data = evt.data.substring(ndx + 1, evt.data.length - 1); let ndx = evt.data.indexOf(',');
try { let eventName = evt.data.substring(3, ndx);
var reISO = /^(\d{4}|\+010000)-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/; let data = evt.data.substring(ndx + 1, evt.data.length - 1);
var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/; try {
var msg = JSON.parse(data, (key, value) => { var reISO = /^(\d{4}|\+010000)-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*))(?:Z|(\+|-)([\d|:]*))?$/;
if (typeof value === 'string') { var reMsAjax = /^\/Date\((d|-|.*)\)[\/|\\]$/;
var a = reISO.exec(value); var msg = JSON.parse(data, (key, value) => {
if (a) return new Date(value); if (typeof value === 'string') {
a = reMsAjax.exec(value); var a = reISO.exec(value);
if (a) { if (a) return new Date(value);
var b = a[1].split(/[-+,.]/); a = reMsAjax.exec(value);
return new Date(b[0] ? +b[0] : 0 - +b[1]); if (a) {
var b = a[1].split(/[-+,.]/);
return new Date(b[0] ? +b[0] : 0 - +b[1]);
}
} }
return value;
});
switch (eventName) {
case 'wifiStrength':
wifi.procWifiStrength(msg);
break;
case 'remoteFrame':
somfy.procRemoteFrame(msg);
break;
case 'shadeState':
somfy.procShadeState(msg);
break;
case 'shadeRemoved':
break;
case 'shadeAdded':
break;
} }
return value; } catch (err) {
}); console.log({ eventName: eventName, data: data, err: err });
switch (eventName) {
case 'wifiStrength':
wifi.procWifiStrength(msg);
break;
case 'remoteFrame':
somfy.procRemoteFrame(msg);
break;
case 'shadeState':
somfy.procShadeState(msg);
break;
case 'shadeRemoved':
break;
case 'shadeAdded':
break;
} }
} catch (err) {
console.log({ eventName: eventName, data: data, err: err });
} }
};
socket.onopen = (evt) => {
if (tConnect) clearTimeout(tConnect);
tConnect = null;
console.log({ msg: 'open', evt: evt });
sockIsOpen = true;
connecting = false;
let wms = document.getElementsByClassName('socket-wait');
for (let i = 0; i < wms.length; i++) {
wms[i].remove();
}
};
socket.onclose = (evt) => {
waitMessage(document.getElementById('divContainer')).classList.add('socket-wait');
if (evt.wasClean) {
console.log({ msg: 'close-clean', evt: evt });
tConnect = setTimeout(() => { reopenSocket(); }, 10000);
console.log('Reconnecting socket in 10 seconds');
}
else {
console.log({ msg: 'close-died', reason: evt.reason, evt: evt, sock: socket });
tConnect = setTimeout(() => { reopenSocket(); }, 3000);
}
};
socket.onerror = (evt) => {
console.log({ msg: 'socket error', evt: evt, sock: socket });
} }
}; } catch (err) {
socket.onopen = (evt) => { console.log({
if (tConnect) clearTimeout(tConnect); msg: 'Websocket connection error', err: err
tConnect = null; });
console.log({ msg: 'open', evt: evt }); tConnect = setTimeout(() => { reopenSocket(); }, 5000);
sockIsOpen = true;
connecting = false;
let wms = document.getElementsByClassName('socket-wait');
for (let i = 0; i < wms.length; i++) {
wms[i].remove();
}
};
socket.onclose = (evt) => {
waitMessage(document.getElementById('divContainer')).classList.add('socket-wait');
if (evt.wasClean) {
console.log({ msg: 'close-clean', evt: evt });
tConnect = setTimeout(() => { reopenSocket(); }, 10000);
console.log('Reconnecting socket in 10 seconds');
}
else {
console.log({ msg: 'close-died', reason: evt.reason, evt: evt });
tConnect = setTimeout(() => { reopenSocket(); }, 3000);
}
};
socket.onerror = (evt) => {
console.log({ msg: 'socket error', evt: evt });
} }
} }
function reopenSocket() { function reopenSocket() {
@ -1550,7 +1573,7 @@
</div> </div>
<div style="display:inline-block;vertical-align:top;padding-left:14px;"> <div style="display:inline-block;vertical-align:top;padding-left:14px;">
<div class="field-group" style=""> <div class="field-group" style="">
<label style="display:block;width:100%;text-align:center;">RSSI</label> <label style="display:block;width:100%;text-align:center;">RSSI: <span id="spanFrameCount" style="color:silver;">0</span></label>
<span id="spanRssi" name="rssi" style="display:block;font-size:32px;width:100%;text-align:center;">---</span> <span id="spanRssi" name="rssi" style="display:block;font-size:32px;width:100%;text-align:center;">---</span>
<span style="display: block; color: #00bcd4;width:100%;text-align:center;">dBm</span> <span style="display: block; color: #00bcd4;width:100%;text-align:center;">dBm</span>
</div> </div>
@ -1600,7 +1623,7 @@
</div> </div>
<script type="text/javascript"> <script type="text/javascript">
waitMessage(document.getElementById('divShadeControls')); waitMessage(document.getElementById('divShadeControls'));
general.init(); somfy.init(); wifi.init(); mqtt.init(); general.init(); somfy.init(); mqtt.init(); wifi.init();
let tabs = document.querySelectorAll('div.tab-container > span'); let tabs = document.querySelectorAll('div.tab-container > span');
tabs.forEach((tab) => { tabs.forEach((tab) => {
tab.addEventListener('click', (evt) => { tab.addEventListener('click', (evt) => {