Fix Companion device power saving

This commit is contained in:
mikecarper committed 2026-08-15 17:58:37 -07:00
1 parent 9161dfd7d5
commit 933697f65c
16 files changed
+1959 -1821

No files matched your search

+2 -2
View File
@@ -96,7 +96,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| System | [`get/set system.watchdog`](cli_commands.md#enable-or-disable-the-nrf52-system-watchdog) | nRF52 | Yes | Yes | Yes |
| System | [`get public.key`](cli_commands.md#view-this-nodes-public-key) | All text CLI roles | Yes | Yes | Yes |
| System | [`get role`](cli_commands.md#view-this-nodes-configured-role) | All text CLI roles | Yes | Yes | Yes |
| System | [`powersaving`; `powersaving on/off`](cli_commands.md#view-or-change-this-nodes-power-saving-flag-repeater-only) | Supported repeater board | Feature | Feature | Feature |
| System | [`powersaving`; `powersaving on/off`](cli_commands.md#view-or-change-this-nodes-power-saving-flag) | Supported repeater board | Feature | Feature | Feature |
| System | [`get/set reboot.interval`](#reboot-interval) | Full parser | Yes | Yes | Yes |
| Clock sync | [`get/set clock.sync.*`; `clock.sync.mesh now`](cli_commands.md#estimate-and-correct-infrastructure-node-time-after-startup) | Repeater, sensor, and room server; `clock.sync.internet` needs MQTT repeater | Yes | Yes | Yes |
| Routing | [`get/set repeat`](cli_commands.md#view-or-change-this-nodes-repeat-flag) | Forwarding-capable role | Yes | Yes | Yes |
@@ -253,7 +253,7 @@ fix, no WiFi connection, an inactive bridge, or an nRF52 bootloader without
| System | [`get/set system.watchdog`](cli_commands.md#enable-or-disable-the-nrf52-system-watchdog) | nRF52 only | No | No | No | No | No | No | No |
| System | [`get public.key`](cli_commands.md#view-this-nodes-public-key) | Full parser | Yes | Yes | Yes | No | No | Yes | Yes |
| System | [`get role`](cli_commands.md#view-this-nodes-configured-role) | All text CLI roles | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
| System | [`powersaving`; `powersaving on/off`](cli_commands.md#view-or-change-this-nodes-power-saving-flag-repeater-only) | Supported repeater board | Feature | Feature | Feature | No | No | Feature | Feature |
| System | [`powersaving`; `powersaving on/off`](cli_commands.md#view-or-change-this-nodes-power-saving-flag) | Supported repeater board | Feature | Feature | Feature | No | No | Feature | Feature |
| System | [`get/set reboot.interval`](#reboot-interval) | Full parser | Yes | Yes | Yes | No | No | Yes | Yes |
| Clock sync | [`get/set clock.sync.*`; `clock.sync.mesh now`](cli_commands.md#estimate-and-correct-infrastructure-node-time-after-startup) | Repeater, sensor, and room server; `clock.sync.internet` needs MQTT repeater | Yes | Yes | Yes | No | Yes | Yes | Yes |
| Routing | [`get/set repeat`](cli_commands.md#view-or-change-this-nodes-repeat-flag) | Forwarding-capable role | Yes | Yes | Yes | Yes | Yes | Yes | Yes |
+4 -2
View File
@@ -1076,7 +1076,7 @@ get clock.sync.status
---
#### View or change this node's power saving flag (Repeater Only)
#### View or change this node's power saving flag
**Usage:**
- `powersaving`
- `powersaving on`
@@ -1088,7 +1088,9 @@ get clock.sync.status
**Default:** `off`
**Note:** When enabled, device enters sleep mode between radio transmissions. Enabling is refused from the local serial console or while an active USB serial data connection is detected; USB power alone does not block power saving.
**Note:** Infrastructure firmware enters sleep between radio transmissions. It refuses to enable power saving from the local serial console or while an active USB serial data connection is detected; USB power alone does not block power saving.
Full Companion firmware accepts the command from its local USB terminal and exposes the same setting in WebConfig. On ESP32, it lowers the CPU clock to 80 MHz, enables idle yielding, and enables the configured GPS duty cycle. USB, BLE, and WiFi remain available. `powersaving off` restores the board's normal CPU clock and disables the GPS duty cycle. This device setting is separate from LoRa RXPS (`radio.rxps`) and WiFi modem power save (`wifi.powersave`).
---
+15
View File
@@ -65,6 +65,21 @@ set radio.fem.txgain on
The selected states are applied immediately and retained after reboot. FEM TX
gain is reported as unsupported on boards without software-selectable PA gain.
Device power saving is separate from LoRa RXPS. It can be changed in WebConfig
with the **Device power saving** switch or from the USB terminal:
```text
powersaving
powersaving on
powersaving off
```
On ESP32, enabling it lowers the CPU clock to 80 MHz, enables idle yielding,
and enables the configured GPS duty cycle. Disabling it restores the normal CPU
clock and keeps GPS awake. Full Companion transports remain available in both
states; WiFi modem sleep stays enabled when BLE is present because coexistence
requires it. The selected state is retained after reboot.
Artifacts are written to `out/` by default.
On 4 MB ESP32 boards, the full target uses a single 3 MB application
+6
View File
@@ -333,6 +333,10 @@ void DataStore::loadPrefsInt(const char *filename, CompanionNodePrefs& _prefs, d
file.read((uint8_t *)&_prefs.rx_ps_sleep_us, sizeof(_prefs.rx_ps_sleep_us)); // 131
file.read((uint8_t *)&_prefs.rx_ps_level, sizeof(_prefs.rx_ps_level)); // 135
file.read((uint8_t *)&_prefs.rx_ps_preamble, sizeof(_prefs.rx_ps_preamble)); // 136
if (file.available() >= (int)sizeof(_prefs.powersaving_enabled)) {
file.read((uint8_t *)&_prefs.powersaving_enabled,
sizeof(_prefs.powersaving_enabled)); // 137
}
}
file.close();
@@ -395,6 +399,8 @@ bool DataStore::savePrefs(const CompanionNodePrefs& _prefs, double node_lat, dou
sizeof(_prefs.rx_ps_level)) == sizeof(_prefs.rx_ps_level); // 135
success = success && file.write((uint8_t *)&_prefs.rx_ps_preamble,
sizeof(_prefs.rx_ps_preamble)) == sizeof(_prefs.rx_ps_preamble); // 136
success = success && file.write((uint8_t *)&_prefs.powersaving_enabled,
sizeof(_prefs.powersaving_enabled)) == sizeof(_prefs.powersaving_enabled); // 137
#if defined(NRF52_PLATFORM)
success = file.commit(success);
+41 -2
View File
@@ -1355,6 +1355,7 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe
_prefs.rx_ps_preamble = RXPS_FIXED_PREAMBLE;
_prefs.rx_ps_rx_us = RX_POWERSAVING_DEFAULT_RX_US;
_prefs.rx_ps_sleep_us = RX_POWERSAVING_DEFAULT_SLEEP_US;
_prefs.powersaving_enabled = 0;
recalcRxPowerSavingFromLevel(_prefs.rx_ps_level, _prefs.sf, _prefs.bw,
_prefs.rx_ps_preamble, &_prefs.rx_ps_rx_us,
&_prefs.rx_ps_sleep_us);
@@ -1437,6 +1438,7 @@ void MyMesh::begin(bool has_display) {
_prefs.radio_fem_rxgain = constrain(_prefs.radio_fem_rxgain, 0, 1);
_prefs.radio_fem_txgain = constrain(_prefs.radio_fem_txgain, 0, 1);
_prefs.rx_powersaving_enabled = constrain(_prefs.rx_powersaving_enabled, 0, 1);
_prefs.powersaving_enabled = constrain(_prefs.powersaving_enabled, 0, 1);
_prefs.rx_ps_level = constrain(_prefs.rx_ps_level, 0, 10);
if (_prefs.rx_ps_preamble != 16 && _prefs.rx_ps_preamble != 32) {
_prefs.rx_ps_preamble = 0;
@@ -1883,9 +1885,11 @@ void MyMesh::getNodeSnapshot(WebConfigServer::NodeSnapshot& s) {
s.rx_ps_preamble = _prefs.rx_ps_preamble;
s.rx_ps_rx_us = _prefs.rx_ps_rx_us;
s.rx_ps_sleep_us = _prefs.rx_ps_sleep_us;
s.power_saving = _prefs.powersaving_enabled;
s.repeat = _prefs.client_repeat != 0;
s.capabilities = WebConfigServer::CAP_LOCATION | WebConfigServer::CAP_AIRTIME
| WebConfigServer::CAP_RX_DELAY | WebConfigServer::CAP_RX_GAIN;
| WebConfigServer::CAP_RX_DELAY | WebConfigServer::CAP_RX_GAIN
| WebConfigServer::CAP_POWER_SAVING;
if (board.canControlLoRaFemLna()) {
s.capabilities |= WebConfigServer::CAP_FEM_RX_GAIN;
}
@@ -2069,6 +2073,10 @@ void MyMesh::execCommand(char* cmd, char* reply) {
applyAndSaveRxPowerSaving(value, reply);
return;
}
if (strcmp(key, "powersaving") == 0) {
applyAndSavePowerSaving(value, reply);
return;
}
if (strcmp(key, "radio.fem.rxgain") == 0) {
bool enabled;
if (!wcParseBool(value, enabled)) {
@@ -3507,6 +3515,24 @@ bool MyMesh::applyAndSaveFemTxGain(bool enabled) {
return true;
}
bool MyMesh::applyAndSavePowerSaving(const char* value, char* reply) {
bool enabled;
if (strcmp(value, "on") == 0) {
enabled = true;
} else if (strcmp(value, "off") == 0) {
enabled = false;
} else {
strcpy(reply, "Error: use powersaving on or powersaving off");
return false;
}
_prefs.powersaving_enabled = enabled ? 1 : 0;
sensors.setPowerSavingEnabled(enabled);
savePrefs();
snprintf(reply, 160, "OK - powersaving %s", enabled ? "on" : "off");
return true;
}
bool MyMesh::applyAndSaveRxPowerSaving(const char* value, char* reply) {
if (!radio_driver.supportsRxPowerSaving()) {
strcpy(reply, "Error: RX power saving unsupported");
@@ -4395,6 +4421,14 @@ void MyMesh::handleTerminalCommand(char* command) {
}
} else if (strncmp(command, "import ", 7) == 0) {
importTerminalCard(command + 7);
} else if (strcmp(command, "powersaving") == 0
|| strcmp(command, "get powersaving") == 0) {
Serial.printf(" powersaving %s\r\n",
_prefs.powersaving_enabled ? "on" : "off");
} else if (strncmp(command, "powersaving ", 12) == 0) {
char reply[160];
applyAndSavePowerSaving(command + 12, reply);
Serial.printf(" %s\r\n", reply);
} else if (strcmp(command, "get radio.fem.rxgain") == 0) {
if (!board.canControlLoRaFemLna()) {
Serial.print(" ERROR: FEM RX gain control is unsupported on this board\r\n");
@@ -4411,7 +4445,11 @@ void MyMesh::handleTerminalCommand(char* command) {
}
} else if (strncmp(command, "set ", 4) == 0) {
const char* config = command + 4;
if (strncmp(config, "af ", 3) == 0) {
if (strncmp(config, "powersaving ", 12) == 0) {
char reply[160];
applyAndSavePowerSaving(config + 12, reply);
Serial.printf(" %s\r\n", reply);
} else if (strncmp(config, "af ", 3) == 0) {
_prefs.airtime_factor = constrain((float)atof(config + 3), 0.0f, 9.0f);
savePrefs();
Serial.print(" OK\r\n");
@@ -4472,6 +4510,7 @@ void MyMesh::handleTerminalCommand(char* command) {
} else if (strcmp(command, "help") == 0) {
Serial.print("Commands:\r\n");
Serial.print(" set {name|lat|lon|freq|tx|af} {value}\r\n");
Serial.print(" powersaving [on|off]\r\n");
Serial.print(" get radio.fem.rxgain\r\n");
Serial.print(" set radio.fem.rxgain <on|off>\r\n");
Serial.print(" get radio.fem.txgain\r\n");
+1
View File
@@ -278,6 +278,7 @@ private:
void checkSerialInterface();
bool applyAndSaveFemRxGain(bool enabled);
bool applyAndSaveFemTxGain(bool enabled);
bool applyAndSavePowerSaving(const char* value, char* reply);
bool applyAndSaveRxPowerSaving(const char* value, char* reply);
#ifdef ENABLE_USB_INTERFACE
ContactInfo* getTerminalRecipient();
+1
View File
@@ -44,6 +44,7 @@ struct CompanionNodePrefs { // persisted to file
uint32_t rx_ps_sleep_us; // sleep window in microseconds
uint8_t rx_ps_level; // 0=manual timings, 1..10=level-derived
uint8_t rx_ps_preamble; // 0=auto from SF, otherwise 16 or 32
uint8_t powersaving_enabled; // device CPU/GPS idle power saving
// Keep the upstream repeat API while retaining the existing binary prefs
// layout used by this branch.
+79 -34
View File
@@ -3,7 +3,6 @@
#include "MyMesh.h"
#ifdef ESP32_PLATFORM
#include "esp_pm.h"
#include "esp_bt.h"
#endif
@@ -122,6 +121,74 @@ MyMesh the_mesh(radio_driver, fast_rng, rtc_clock, tables, store
/* END GLOBAL OBJECTS */
#ifdef ESP32_PLATFORM
static int8_t applied_power_saving = -1;
static int8_t attempted_power_saving = -1;
static unsigned long power_saving_retry_at = 0;
static uint32_t companionNominalCpuMhz() {
#ifdef ESP32_CPU_FREQ
return ESP32_CPU_FREQ;
#else
return F_CPU / 1000000UL;
#endif
}
static bool applyCompanionPowerSaving(bool enabled) {
const uint32_t nominal_mhz = companionNominalCpuMhz();
const uint32_t target_mhz = enabled && nominal_mhz > 80 ? 80 : nominal_mhz;
if (!setCpuFrequencyMhz(target_mhz)) {
Serial.printf("Device power saving failed: CPU %lu MHz is unsupported\r\n",
(unsigned long)target_mhz);
return false;
}
#if defined(BLE_PIN_CODE) && !CONFIG_IDF_TARGET_ESP32C6
esp_err_t bt_result = enabled ? esp_bt_sleep_enable()
: esp_bt_sleep_disable();
if (bt_result != ESP_OK) {
Serial.printf("Bluetooth sleep %s failed: %s\r\n",
enabled ? "enable" : "disable",
esp_err_to_name(bt_result));
}
#endif
#ifdef WIFI_SSID
#if defined(BLE_PIN_CODE)
// WiFi modem sleep is required for WiFi/BLE coexistence in Full Companion.
WiFi.setSleep(true);
#else
WiFi.setSleep(enabled);
#endif
#endif
Serial.printf("Device power saving %s: CPU %lu MHz\r\n",
enabled ? "on" : "off", (unsigned long)target_mhz);
return true;
}
static void serviceCompanionPowerSaving(bool force = false) {
const int8_t requested = the_mesh.getNodePrefs()->powersaving_enabled ? 1 : 0;
if (!force && applied_power_saving == requested) return;
const unsigned long now = millis();
if (!force && attempted_power_saving == requested
&& power_saving_retry_at != 0
&& (int32_t)(now - power_saving_retry_at) < 0) {
return;
}
attempted_power_saving = requested;
if (applyCompanionPowerSaving(requested != 0)) {
applied_power_saving = requested;
power_saving_retry_at = 0;
} else {
power_saving_retry_at = now + 5000;
if (power_saving_retry_at == 0) power_saving_retry_at = 1;
}
}
#endif
#if defined(ENABLE_USB_INTERFACE)
static char usb_terminal_line[MAX_TRANS_UNIT * 2 + 32];
static size_t usb_terminal_line_len = 0;
@@ -664,12 +731,12 @@ void setup() {
the_mesh.startInterface(interface_manager);
sensors.begin();
#if ENV_INCLUDE_GPS == 1 && defined(BLE_PIN_CODE)
// BLE companions keep GPS duty cycling enabled by default: at most 10
// minutes awake, followed by 5 minutes asleep.
#if ENV_INCLUDE_GPS == 1
// Device power saving applies a 10-minute awake, 5-minute sleep GPS cycle.
if (sensors.getLocationProvider() != NULL) {
sensors.getLocationProvider()->setPowerSavingProfile(600, 300);
sensors.setPowerSavingEnabled(true);
sensors.setPowerSavingEnabled(
the_mesh.getNodePrefs()->powersaving_enabled != 0);
}
#endif
#if ENV_INCLUDE_GPS == 1
@@ -683,34 +750,7 @@ void setup() {
board.onBootComplete();
#ifdef ESP32_PLATFORM
#if !CONFIG_IDF_TARGET_ESP32C6
// Enable BLE sleep
esp_err_t errBLESleep = esp_bt_sleep_enable();
if (errBLESleep == ESP_OK) {
Serial.println("Bluetooth sleep enabled successfully");
} else {
Serial.printf("Bluetooth sleep enable failed: %s\n", esp_err_to_name(errBLESleep));
}
#endif
#if CONFIG_IDF_TARGET_ESP32C3
esp_pm_config_esp32c3_t pm_config;
#elif CONFIG_IDF_TARGET_ESP32S3
esp_pm_config_esp32s3_t pm_config;
#elif CONFIG_IDF_TARGET_ESP32
esp_pm_config_esp32_t pm_config;
#elif CONFIG_IDF_TARGET_ESP32C6
esp_pm_config_t pm_config;
#endif
// Configure Power Management
pm_config = { .max_freq_mhz = 80, .min_freq_mhz = 40, .light_sleep_enable = true };
esp_err_t errPM = esp_pm_configure(&pm_config);
if (errPM == ESP_OK) {
Serial.println("Power Management configured successfully");
} else {
Serial.printf("Power Management failed to configure: %d\r\n", errPM);
}
serviceCompanionPowerSaving(true);
#endif
}
@@ -744,9 +784,14 @@ void loop() {
external_watchdog.loop();
#endif
#ifdef ESP32_PLATFORM
serviceCompanionPowerSaving();
#endif
// USB power alone (for example, a wall charger) must not disable power
// saving. Stay awake only while a computer has an active USB data session.
bool can_sleep = !the_mesh.hasPendingWork();
bool can_sleep = the_mesh.getNodePrefs()->powersaving_enabled
&& !the_mesh.hasPendingWork();
#if defined(NRF52_PLATFORM)
can_sleep = can_sleep && !board.isUsbHostConnected();
#endif
+4 -2
View File
@@ -104,6 +104,7 @@ def default_config(setup_mode):
"rxdelay": 0.0, "txdelay": 0.5, "cad": False, "rxgain": True,
"rxps_enabled": True, "rxps_level": 5, "rxps_preamble": 16,
"rxps_rx_us": 20936, "rxps_sleep_us": 13425,
"powersaving": False,
"repeat": True, "flood_max": 64, "flood_max_advert": 8,
"flood_max_unscoped": 8, "loop_detect": "moderate",
"name": "MockNode", "lat": 39.7392, "lon": -104.9903,
@@ -196,8 +197,8 @@ class State:
"uptime_s": int(time.time() - self.start),
"runtime_slots": 6, "max_slots": 6, "active_slots": self.active_slots,
# Every ordinary repeater feature except an external FEM, plus
# radio RX power saving (bit 12).
"capabilities": 0x17FF,
# radio RX power saving (bit 12) and device power saving (bit 13).
"capabilities": 0x37FF,
"max_cmds": CLI_MAX_CMDS,
}
@@ -207,6 +208,7 @@ class State:
# to produce realistic per-field OK / Error replies for the UI chips).
# ---------------------------------------------------------------------------
BOOL_KEYS = {"cad": ("radio", "cad"), "radio.rxgain": ("radio", "rxgain"),
"powersaving": ("radio", "powersaving"),
"repeat": ("radio", "repeat"), "mqtt.status": ("mqtt", "status"),
"mqtt.packets": ("mqtt", "packets"), "mqtt.raw": ("mqtt", "raw"),
"mqtt.rx": ("mqtt", "rx"), "snmp": ("mqtt", "snmp"),
+2 -1
View File
@@ -19,7 +19,8 @@
static const char* const WC_ALLOWED_SET_KEYS[] = {
// NodePrefs (radio / node)
"name", "lat", "lon", "radio", "tx", "af", "rxdelay", "txdelay",
"cad", "radio.rxgain", "radio.fem.rxgain", "radio.rxps", "repeat",
"cad", "radio.rxgain", "radio.fem.rxgain", "radio.rxps", "powersaving",
"repeat",
"advert.interval", "flood.advert.interval",
"flood.max", "flood.max.advert", "flood.max.unscoped", "loop.detect",
// MQTTPrefs (WiFi / MQTT / misc observer)
File diff suppressed because it is too large. Load diff
+1
View File
@@ -1438,6 +1438,7 @@ void WebConfigServer::handleConfigGet(AsyncWebServerRequest* req) {
radio["rxps_preamble"] = node.rx_ps_preamble;
radio["rxps_rx_us"] = node.rx_ps_rx_us;
radio["rxps_sleep_us"] = node.rx_ps_sleep_us;
radio["powersaving"] = (bool)node.power_saving;
radio["repeat"] = (bool)node.repeat;
radio["flood_max"] = node.flood_max;
radio["flood_max_advert"] = node.flood_max_advert;
+2
View File
@@ -59,6 +59,7 @@ public:
CAP_WIFI_POWER_SAVE = 1UL << 10,
CAP_FEM_RX_GAIN = 1UL << 11,
CAP_RX_POWER_SAVING = 1UL << 12,
CAP_POWER_SAVING = 1UL << 13,
CAP_DELAYS = CAP_RX_DELAY | CAP_TX_DELAY,
};
@@ -86,6 +87,7 @@ public:
uint8_t rx_ps_preamble;
uint32_t rx_ps_rx_us;
uint32_t rx_ps_sleep_us;
uint8_t power_saving;
uint8_t repeat;
uint16_t advert_interval;
uint8_t flood_advert_interval;
@@ -50,6 +50,21 @@ public:
const std::string& text() const { return _text; }
};
TEST(CompanionNodePrefs, DevicePowerSavingIsIndependentFromRxps) {
CompanionNodePrefs prefs = {};
prefs.rx_powersaving_enabled = 1;
prefs.powersaving_enabled = 0;
EXPECT_EQ(1, prefs.rx_powersaving_enabled);
EXPECT_EQ(0, prefs.powersaving_enabled);
prefs.powersaving_enabled = 1;
prefs.rx_powersaving_enabled = 0;
EXPECT_EQ(0, prefs.rx_powersaving_enabled);
EXPECT_EQ(1, prefs.powersaving_enabled);
}
#if 0
// Re-enable test once we can SET fem_ values in companion
TEST(CompanionNodePrefs, RxGainSettingsRoundTripIndependently) {
@@ -10,6 +10,7 @@ TEST(WebConfigKeys, AllowsKnownScalarKeys) {
EXPECT_TRUE(wcIsAllowedSetKey("name"));
EXPECT_TRUE(wcIsAllowedSetKey("radio"));
EXPECT_TRUE(wcIsAllowedSetKey("radio.rxps"));
EXPECT_TRUE(wcIsAllowedSetKey("powersaving"));
EXPECT_TRUE(wcIsAllowedSetKey("repeat"));
EXPECT_TRUE(wcIsAllowedSetKey("wifi.ssid"));
EXPECT_TRUE(wcIsAllowedSetKey("mqtt.iata"));
+3
View File
@@ -387,6 +387,8 @@ body.tab-cli{padding-bottom:0}
<span class="tgl"><input type="checkbox" data-k="radio.rxgain"><u></u></span></div>
<div class="sw" data-cap="2048"><span><b>FEM RX boost</b><i>External front-end-module LNA</i></span>
<span class="tgl"><input type="checkbox" data-k="radio.fem.rxgain"><u></u></span></div>
<div class="sw" data-cap="8192"><span><b>Device power saving</b><i>Reduce CPU and GPS idle power; USB, BLE, and WiFi stay available</i></span>
<span class="tgl"><input type="checkbox" data-k="powersaving"><u></u></span></div>
<div data-cap="4096" id="rxps-wrap">
<div class="sw"><span><b>RX power saving</b><i>Duty-cycle the LoRa receiver; off keeps continuous receive</i></span>
<span class="tgl"><input type="checkbox" data-rxps="enabled"><u></u></span></div>
@@ -738,6 +740,7 @@ function cfgVal(k){ // map a `set` key to its current value string, from st.cfg
case"cad":return r.cad?"on":"off";case"radio.rxgain":return r.rxgain?"on":"off";
case"radio.fem.rxgain":return r.fem_rxgain?"on":"off";
case"radio.rxps":return rxpsCfgValue(r);
case"powersaving":return r.powersaving?"on":"off";
case"repeat":return r.repeat?"on":"off";
case"flood.max":return String(r.flood_max);
case"flood.max.advert":return String(r.flood_max_advert);