Compare commits

...

6 Commits

Author SHA1 Message Date
Ricardo Guzman (Richonguzman) e35d5f6a9e about update 2026-06-11 01:55:45 -04:00
Ricardo Guzman (Richonguzman) 96432b8624 NEW WEB 2026-06-11 01:39:34 -04:00
Ricardo Guzman (Richonguzman) 25ea82737a cad test 3 2026-06-10 10:46:07 -04:00
Ricardo Guzman (Richonguzman) df22b9dbc0 readme and date update 2026-06-10 00:27:15 -04:00
Ricardo Guzman (Richonguzman) 9898e123d2 CAD with backoff process 2026-06-10 00:25:27 -04:00
Ricardo Guzman (Richonguzman) a97757c0b2 test CAD 2026-06-09 22:46:41 -04:00
12 changed files with 1194 additions and 1013 deletions
+1
View File
@@ -51,6 +51,7 @@ ____________________________________________________
<br /> <br />
# Timeline (Versions): # Timeline (Versions):
- 2026-06-10 CAD (Channel Activity Detection) and DIFS (Distributed Inter-Frame Space) added.
- 2026-03-25 More Boards, SDK update, OTA fix, GPS process update. - 2026-03-25 More Boards, SDK update, OTA fix, GPS process update.
- 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added. - 2026-02-26 9M2IBR ESP32 1W (400M30S) + GPS board added.
- 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution. - 2026-02-25 Code Improvements: reduced String comparisons and improved logic for faster code execution.
+4 -4
View File
@@ -32,16 +32,16 @@ lib_deps =
adafruit/Adafruit Si7021 Library @ 1.5.3 adafruit/Adafruit Si7021 Library @ 1.5.3
arduino-libraries/NTPClient @ 3.2.1 arduino-libraries/NTPClient @ 3.2.1
ayushsharma82/ElegantOTA @ 3.1.7 ayushsharma82/ElegantOTA @ 3.1.7
bblanchon/ArduinoJson @ 7.4.2 bblanchon/ArduinoJson @ 7.4.3
jgromes/RadioLib @ 7.6.0 jgromes/RadioLib @ 7.6.0
knolleary/PubSubClient @ 2.8 knolleary/PubSubClient @ 2.8
ESP32Async/AsyncTCP @ 3.4.10 ESP32Async/AsyncTCP @ 3.4.10
ESP32Async/ESPAsyncWebServer @ 3.10.0 ESP32Async/ESPAsyncWebServer @ 3.11.1
mikalhart/TinyGPSPlus @ 1.0.3 mikalhart/TinyGPSPlus @ 1.0.3
richonguzman/APRSPacketLib @ 1.0.4 richonguzman/APRSPacketLib @ 1.0.4
display_libs = display_libs =
adafruit/Adafruit GFX Library @ 1.11.9 adafruit/Adafruit GFX Library @ 1.12.6
adafruit/Adafruit SSD1306 @ 2.5.10 adafruit/Adafruit SSD1306 @ 2.5.17
usb_flags= usb_flags=
-DARDUINO_USB_MODE=1 -DARDUINO_USB_MODE=1
-DARDUINO_USB_CDC_ON_BOOT=1 -DARDUINO_USB_CDC_ON_BOOT=1
+6 -5
View File
@@ -28,9 +28,9 @@
"messagesToRF": false, "messagesToRF": false,
"objectsToRF": false, "objectsToRF": false,
"server": "rotate.aprs2.net", "server": "rotate.aprs2.net",
"passcode": "XYZVW", "passcode": "XYZVW",
"port": 14580, "port": 14580,
"filter": "m/10" "filter": "m/10"
}, },
"personalNote": "", "personalNote": "",
"blacklist": "", "blacklist": "",
@@ -47,10 +47,11 @@
"rxSignalBandwidth": 125000, "rxSignalBandwidth": 125000,
"txActive": false, "txActive": false,
"txFreq": 433775000, "txFreq": 433775000,
"txSpreadingFactor": 12, "txSpreadingFactor": 9,
"txCodingRate4": 5, "txCodingRate4": 7,
"txSignalBandwidth": 125000, "txSignalBandwidth": 125000,
"power": 20 "power": 20,
"cadActive": true
}, },
"display": { "display": {
"alwaysOn": true, "alwaysOn": true,
+1080 -978
View File
File diff suppressed because one or more lines are too long
+50 -6
View File
@@ -118,7 +118,7 @@ function loadSettings(settings) {
document.getElementById("beacon.latitude").value = settings.beacon.latitude; document.getElementById("beacon.latitude").value = settings.beacon.latitude;
document.getElementById("beacon.longitude").value = settings.beacon.longitude; document.getElementById("beacon.longitude").value = settings.beacon.longitude;
document.getElementById("beacon.interval").value = settings.beacon.interval; document.getElementById("beacon.interval").value = settings.beacon.interval;
document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime; document.getElementById("other.rememberStationTime").value = settings.other.rememberStationTime;
document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS; document.getElementById("beacon.sendViaAPRSIS").checked = settings.beacon.sendViaAPRSIS;
document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF; document.getElementById("beacon.sendViaRF").checked = settings.beacon.sendViaRF;
@@ -148,12 +148,26 @@ function loadSettings(settings) {
document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor; document.getElementById("lora.rxSpreadingFactor").value = settings.lora.rxSpreadingFactor;
document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4; document.getElementById("lora.rxCodingRate4").value = settings.lora.rxCodingRate4;
document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth; document.getElementById("lora.rxSignalBandwidth").value = settings.lora.rxSignalBandwidth;
LoRaRxCheckbox.checked = settings.lora.rxActive;
LoRaRxFreq.disabled = !LoRaRxCheckbox.checked;
LoRaRxSpreadingFactor.disabled = !LoRaRxCheckbox.checked;
LoRaRxCodingRate.disabled = !LoRaRxCheckbox.checked;
LoRaRxSignalBandwidth.disabled = !LoRaRxCheckbox.checked;
document.getElementById("lora.txActive").checked = settings.lora.txActive; document.getElementById("lora.txActive").checked = settings.lora.txActive;
document.getElementById("lora.txFreq").value = settings.lora.txFreq; document.getElementById("lora.txFreq").value = settings.lora.txFreq;
document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor; document.getElementById("lora.txSpreadingFactor").value = settings.lora.txSpreadingFactor;
document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4; document.getElementById("lora.txCodingRate4").value = settings.lora.txCodingRate4;
document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth; document.getElementById("lora.txSignalBandwidth").value = settings.lora.txSignalBandwidth;
document.getElementById("lora.power").value = settings.lora.power; document.getElementById("lora.power").value = settings.lora.power;
document.getElementById("lora.cadActive").checked = settings.lora.cadActive;
LoRaTxCheckbox.checked = settings.lora.txActive;
LoRaTxFreq.disabled = !LoRaTxCheckbox.checked;
LoRaTxSpreadingFactor.disabled = !LoRaTxCheckbox.checked;
LoRaTxCodingRate.disabled = !LoRaTxCheckbox.checked;
LoRaTxSignalBandwidth.disabled = !LoRaTxCheckbox.checked;
LoRaPower.disabled = !LoRaTxCheckbox.checked;
LoRaCadActive.disabled = !LoRaTxCheckbox.checked;
// Display // Display
document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn; document.getElementById("display.alwaysOn").checked = settings.display.alwaysOn;
@@ -195,7 +209,7 @@ function loadSettings(settings) {
TelemetryCheckbox.checked = settings.wxsensor.active; TelemetryCheckbox.checked = settings.wxsensor.active;
TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked; TelemetryHeightCorrection.disabled = !TelemetryCheckbox.checked;
TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked; TelemetryTempCorrection.disabled = !TelemetryCheckbox.checked;
// SYSLOG // SYSLOG
document.getElementById("syslog.active").checked = settings.syslog.active; document.getElementById("syslog.active").checked = settings.syslog.active;
document.getElementById("syslog.server").value = settings.syslog.server; document.getElementById("syslog.server").value = settings.syslog.server;
@@ -205,7 +219,7 @@ function loadSettings(settings) {
SyslogServer.disabled = !SyslogCheckbox.checked; SyslogServer.disabled = !SyslogCheckbox.checked;
SyslogPort.disabled = !SyslogCheckbox.checked; SyslogPort.disabled = !SyslogCheckbox.checked;
SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked; SyslogBeaconOverTCPIP.disabled = !SyslogCheckbox.checked;
// TNC // TNC
if (settings.tnc) { if (settings.tnc) {
document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer; document.getElementById("tnc.enableServer").checked = settings.tnc.enableServer;
@@ -390,6 +404,36 @@ TelemetryCheckbox.addEventListener("change", function () {
TelemetryTempCorrection.disabled = !this.checked; TelemetryTempCorrection.disabled = !this.checked;
}); });
// LoRa Rx Switch
const LoRaRxCheckbox = document.querySelector('input[name="lora.rxActive"]');
const LoRaRxFreq = document.querySelector('[name="lora.rxFreq"]');
const LoRaRxSpreadingFactor = document.querySelector('[name="lora.rxSpreadingFactor"]');
const LoRaRxCodingRate = document.querySelector('[name="lora.rxCodingRate4"]');
const LoRaRxSignalBandwidth = document.querySelector('[name="lora.rxSignalBandwidth"]');
LoRaRxCheckbox.addEventListener("change", function () {
LoRaRxFreq.disabled = !this.checked;
LoRaRxSpreadingFactor.disabled = !this.checked;
LoRaRxCodingRate.disabled = !this.checked;
LoRaRxSignalBandwidth.disabled = !this.checked;
});
// LoRa Tx Switch
const LoRaTxCheckbox = document.querySelector('input[name="lora.txActive"]');
const LoRaTxFreq = document.querySelector('[name="lora.txFreq"]');
const LoRaTxSpreadingFactor = document.querySelector('[name="lora.txSpreadingFactor"]');
const LoRaTxCodingRate = document.querySelector('[name="lora.txCodingRate4"]');
const LoRaTxSignalBandwidth = document.querySelector('[name="lora.txSignalBandwidth"]');
const LoRaPower = document.querySelector('[name="lora.power"]');
const LoRaCadActive = document.querySelector('[name="lora.cadActive"]');
LoRaTxCheckbox.addEventListener("change", function () {
LoRaTxFreq.disabled = !this.checked;
LoRaTxSpreadingFactor.disabled = !this.checked;
LoRaTxCodingRate.disabled = !this.checked;
LoRaTxSignalBandwidth.disabled = !this.checked;
LoRaPower.disabled = !this.checked;
LoRaCadActive.disabled = !this.checked;
});
// Syslog Switches // Syslog Switches
const SyslogCheckbox = document.querySelector('input[name="syslog.active"]'); const SyslogCheckbox = document.querySelector('input[name="syslog.active"]');
const SyslogServer = document.querySelector('input[name="syslog.server"]'); const SyslogServer = document.querySelector('input[name="syslog.server"]');
@@ -559,7 +603,7 @@ function loadReceivedPackets(packets) {
packets.forEach((packet) => { packets.forEach((packet) => {
const element = document.createElement("tr"); const element = document.createElement("tr");
element.innerHTML = ` element.innerHTML = `
<td>${packet.rxTime}</td> <td>${packet.rxTime}</td>
<td>${packet.packet}</td> <td>${packet.packet}</td>
@@ -592,7 +636,7 @@ document.querySelector('a[href="/received-packets"]').addEventListener('click',
document.getElementById('received-packets').classList.remove('d-none'); document.getElementById('received-packets').classList.remove('d-none');
document.getElementById('configuration').classList.add('d-none'); document.getElementById('configuration').classList.add('d-none');
document.querySelector('button[type=submit]').remove(); document.querySelector('button[type=submit]').remove();
fetchReceivedPackets(); fetchReceivedPackets();
+1
View File
@@ -86,6 +86,7 @@ public:
int txCodingRate4; int txCodingRate4;
long txSignalBandwidth; long txSignalBandwidth;
int power; int power;
bool cadActive;
}; };
class Display { class Display {
+2 -2
View File
@@ -68,8 +68,8 @@ ___________________________________________________________________*/
#endif #endif
String versionDate = "2026-04-21"; String versionDate = "2026-06-11";
String versionNumber = "3.2.4"; String versionNumber = "3.3.0";
Configuration Config; Configuration Config;
WiFiClient aprsIsClient; WiFiClient aprsIsClient;
WiFiClient mqttClient; WiFiClient mqttClient;
+1 -3
View File
@@ -16,7 +16,7 @@
* along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>. * along with LoRa APRS iGate. If not, see <https://www.gnu.org/licenses/>.
*/ */
#include <APRSPacketLib.h> #include <APRSPacketLib.h>
#include <WiFiClient.h> #include <WiFiClient.h>
#include "configuration.h" #include "configuration.h"
#include "network_manager.h" #include "network_manager.h"
@@ -47,7 +47,6 @@ extern bool modemLoggedToAPRSIS;
extern bool backupDigiMode; extern bool backupDigiMode;
extern String versionNumber; extern String versionNumber;
uint32_t lastRxTime = millis();
bool passcodeValid = false; bool passcodeValid = false;
uint32_t lastServerCheck = 0; uint32_t lastServerCheck = 0;
@@ -403,7 +402,6 @@ namespace APRS_IS_Utils {
String aprsisPacket = aprsIsClient.readStringUntil('\r'); String aprsisPacket = aprsIsClient.readStringUntil('\r');
aprsisPacket.trim(); // Serial.println(aprsisPacket); aprsisPacket.trim(); // Serial.println(aprsisPacket);
processAPRSISPacket(aprsisPacket); processAPRSISPacket(aprsisPacket);
lastRxTime = millis();
} }
} }
#endif #endif
+5 -2
View File
@@ -106,6 +106,7 @@ bool Configuration::writeFile() {
data["lora"]["txCodingRate4"] = loramodule.txCodingRate4; data["lora"]["txCodingRate4"] = loramodule.txCodingRate4;
data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth; data["lora"]["txSignalBandwidth"] = loramodule.txSignalBandwidth;
data["lora"]["power"] = loramodule.power; data["lora"]["power"] = loramodule.power;
data["lora"]["cadActive"] = loramodule.cadActive;
int rxSpreadingFactor = loramodule.rxSpreadingFactor; int rxSpreadingFactor = loramodule.rxSpreadingFactor;
int txSpreadingFactor = loramodule.txSpreadingFactor; int txSpreadingFactor = loramodule.txSpreadingFactor;
@@ -290,7 +291,6 @@ bool Configuration::readFile() {
#endif #endif
digi.backupDigiMode = data["digi"]["backupDigiMode"] | false; digi.backupDigiMode = data["digi"]["backupDigiMode"] | false;
if (data["lora"]["rxActive"].isNull() || if (data["lora"]["rxActive"].isNull() ||
data["lora"]["rxFreq"].isNull() || data["lora"]["rxFreq"].isNull() ||
data["lora"]["rxSpreadingFactor"].isNull() || data["lora"]["rxSpreadingFactor"].isNull() ||
@@ -301,7 +301,8 @@ bool Configuration::readFile() {
data["lora"]["txSpreadingFactor"].isNull() || data["lora"]["txSpreadingFactor"].isNull() ||
data["lora"]["txCodingRate4"].isNull() || data["lora"]["txCodingRate4"].isNull() ||
data["lora"]["txSignalBandwidth"].isNull() || data["lora"]["txSignalBandwidth"].isNull() ||
data["lora"]["power"].isNull()) needsRewrite = true; data["lora"]["power"].isNull() ||
data["lora"]["cadActive"].isNull()) needsRewrite = true;
loramodule.rxActive = data["lora"]["rxActive"] | true; loramodule.rxActive = data["lora"]["rxActive"] | true;
loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000; loramodule.rxFreq = data["lora"]["rxFreq"] | 433775000;
loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12; loramodule.rxSpreadingFactor = data["lora"]["rxSpreadingFactor"] | 12;
@@ -313,6 +314,7 @@ bool Configuration::readFile() {
loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5; loramodule.txCodingRate4 = data["lora"]["txCodingRate4"] | 5;
loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000; loramodule.txSignalBandwidth = data["lora"]["txSignalBandwidth"] | 125000;
loramodule.power = data["lora"]["power"] | 20; loramodule.power = data["lora"]["power"] | 20;
loramodule.cadActive = data["lora"]["cadActive"] | true;
if (data["display"]["alwaysOn"].isNull() || if (data["display"]["alwaysOn"].isNull() ||
data["display"]["timeout"].isNull() || data["display"]["timeout"].isNull() ||
@@ -503,6 +505,7 @@ void Configuration::setDefaultValues() {
loramodule.txCodingRate4 = 5; loramodule.txCodingRate4 = 5;
loramodule.txSignalBandwidth = 125000; loramodule.txSignalBandwidth = 125000;
loramodule.power = 20; loramodule.power = 20;
loramodule.cadActive = true;
display.alwaysOn = true; display.alwaysOn = true;
display.timeout = 4; display.timeout = 4;
+41 -4
View File
@@ -28,15 +28,19 @@
#include "utils.h" #include "utils.h"
extern Configuration Config; extern Configuration Config;
extern NetworkManager *networkManager; extern NetworkManager *networkManager;
extern uint32_t lastRxTime;
extern bool packetIsBeacon; extern bool packetIsBeacon;
extern std::vector<ReceivedPacket> receivedPackets; extern std::vector<ReceivedPacket> receivedPackets;
bool operationDone = true; bool operationDone = true;
bool transmitFlag = true; bool transmitFlag = true;
#define DIFS_SLOTS 2 // Number of secuential CAD slots to consider a free channel to Tx
int backoffMax = 4; // Max Backoff value (number of CAD slots to wait before Tx)
#ifdef HAS_SX1262 #ifdef HAS_SX1262
SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN); SX1262 radio = new Module(RADIO_CS_PIN, RADIO_DIO1_PIN, RADIO_RST_PIN, RADIO_BUSY_PIN);
@@ -167,6 +171,34 @@ namespace LoRa_Utils {
radio.setBandwidth(signalBandwidth); radio.setBandwidth(signalBandwidth);
} }
bool doCAD() { // CAD (Channel Activity Detection)
return radio.scanChannel() != RADIOLIB_CHANNEL_FREE; // false=channel free | true=RADIOLIB_LORA_DETECTED or CAD failed
}
bool doDIFS() {
for (uint8_t i = DIFS_SLOTS; i > 0; i--) {
if (doCAD()) return false;
}
return true;
}
void waitForDIFS() {
while (!doDIFS()) {
Serial.println("CAD/DIFS failed, retry...");
}
}
void doBEB() {
int backoffCounter = random(1, backoffMax + 1);
while (backoffCounter > 0) {
if (doCAD()) {
waitForDIFS(); // busy channel: freeze backoff and restart DIFS
} else {
backoffCounter--;
}
}
}
void sendNewPacket(const String& newPacket) { void sendNewPacket(const String& newPacket) {
if (!Config.loramodule.txActive) return; if (!Config.loramodule.txActive) return;
@@ -179,6 +211,12 @@ namespace LoRa_Utils {
#ifdef INTERNAL_LED_PIN #ifdef INTERNAL_LED_PIN
if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode if (Config.digi.ecoMode != 1) digitalWrite(INTERNAL_LED_PIN, HIGH); // disabled in Ultra Eco Mode
#endif #endif
if (Config.loramodule.cadActive) {
waitForDIFS(); // DIFS (Distributed Inter-Frame Space)
doBEB(); // BEB (Binary Exponential Backoff)
}
int state = radio.transmit("\x3c\xff\x01" + newPacket); int state = radio.transmit("\x3c\xff\x01" + newPacket);
transmitFlag = true; transmitFlag = true;
if (state == RADIOLIB_ERR_NONE) { if (state == RADIOLIB_ERR_NONE) {
@@ -250,7 +288,6 @@ namespace LoRa_Utils {
} else { } else {
packet = ""; packet = "";
} }
lastRxTime = millis();
return packet; return packet;
} }
} else if (state == RADIOLIB_ERR_CRC_MISMATCH) { } else if (state == RADIOLIB_ERR_CRC_MISMATCH) {
+1 -9
View File
@@ -25,15 +25,11 @@
#include "utils.h" #include "utils.h"
#include <vector> #include <vector>
#define SECS_TO_WAIT 3 // soon to be deleted...
extern Configuration Config; extern Configuration Config;
extern uint32_t lastRxTime;
extern String fourthLine; extern String fourthLine;
extern bool shouldSleepLowVoltage; extern bool shouldSleepLowVoltage;
uint32_t lastTxTime = millis();
std::vector<LastHeardStation> lastHeardStations; std::vector<LastHeardStation> lastHeardStations;
std::vector<String> blacklist; std::vector<String> blacklist;
std::vector<String> managers; std::vector<String> managers;
@@ -214,15 +210,11 @@ namespace STATION_Utils {
} }
void processOutputPacketBuffer() { void processOutputPacketBuffer() {
int timeToWait = SECS_TO_WAIT * 1000; // 3 segs between packet Tx and also Rx ??? if (outputPacketBuffer.size() > 0) {
uint32_t lastRx = millis() - lastRxTime;
uint32_t lastTx = millis() - lastTxTime;
if (outputPacketBuffer.size() > 0 && lastTx > timeToWait && lastRx > timeToWait) {
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true; if (outputPacketBuffer[0].isBeacon) packetIsBeacon = true;
LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet); LoRa_Utils::sendNewPacket(outputPacketBuffer[0].packet);
if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false; if (outputPacketBuffer[0].isBeacon) packetIsBeacon = false;
outputPacketBuffer.erase(outputPacketBuffer.begin()); outputPacketBuffer.erase(outputPacketBuffer.begin());
lastTxTime = millis();
} }
if (shouldSleepLowVoltage) { if (shouldSleepLowVoltage) {
while (outputPacketBuffer.size() > 0) { while (outputPacketBuffer.size() > 0) {
+2
View File
@@ -213,6 +213,8 @@ namespace WEB_Utils {
Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4); Config.loramodule.txCodingRate4 = getParamIntSafe("lora.txCodingRate4", Config.loramodule.txCodingRate4);
Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth); Config.loramodule.txSignalBandwidth = getParamIntSafe("lora.txSignalBandwidth", Config.loramodule.txSignalBandwidth);
Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power); Config.loramodule.power = getParamIntSafe("lora.power", Config.loramodule.power);
if (Config.loramodule.txActive)
Config.loramodule.cadActive = request->hasParam("lora.cadActive", true);
Config.display.alwaysOn = request->hasParam("display.alwaysOn", true); Config.display.alwaysOn = request->hasParam("display.alwaysOn", true);
if (!Config.display.alwaysOn) { if (!Config.display.alwaysOn) {