Fix Companion device power saving

This commit is contained in:
mikecarper committed 2026-08-15 17:58:37 -07:00
1 parent b8d274c90e
commit 843d309e2b
16 files changed
+1959 -1821

No files matched your search

+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