From b522479d11532ae506ecb6e0ae7364e893f8d888 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Thu, 22 Oct 2026 21:12:56 +0700 Subject: [PATCH] PowerSaving for companions --- examples/companion_radio/MyMesh.cpp | 45 +++++++++++++++- examples/companion_radio/NodePrefs.h | 38 +++++++++++++- examples/companion_radio/PowerSaving.h | 73 ++++++++++++++++++++++++++ examples/companion_radio/main.cpp | 28 +++++++++- src/Dispatcher.h | 5 ++ src/helpers/StaticPoolPacketManager.h | 3 ++ 6 files changed, 188 insertions(+), 4 deletions(-) create mode 100644 examples/companion_radio/PowerSaving.h diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index f32ea67ae..1fbdf1239 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -925,7 +925,14 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.tx_power_dbm = LORA_TX_POWER; _prefs.gps_enabled = 0; // GPS disabled by default _prefs.gps_interval = 0; // No automatic GPS updates by default - _prefs.radio_fem_rxgain = 1; + +// PowerSaving variant +#ifdef RADIO_FEM_RXGAIN + _prefs.radio_fem_rxgain = RADIO_FEM_RXGAIN; +#else + _prefs.radio_fem_rxgain = 0; +#endif + #ifdef RADIO_FEM_TXGAIN _prefs.radio_fem_txgain = RADIO_FEM_TXGAIN; // board-specific default for PA #else @@ -940,6 +947,15 @@ MyMesh::MyMesh(mesh::Radio &radio, mesh::RNG &rng, mesh::RTCClock &rtc, SimpleMe _prefs.rx_boosted_gain = 1; // enabled by default #endif #endif + + // PowerSaving variant + // PowerSaving defaults + _prefs.powersaving_enabled = 1; + + // RXPS defaults - Balanced profile + _prefs.rxps.enabled = 1; + _prefs.rxps.level = 6; + _prefs.rxps.preamble = 16; } void MyMesh::begin() { @@ -1003,6 +1019,10 @@ void MyMesh::begin() { _store->loadChannels(this); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + + // PowerSaving variant + _prefs.getRadioPrefs()->applyRxPowerSaving(_prefs.sf, _prefs.bw); + radio_driver.setTxPower(_prefs.tx_power_dbm); radio_driver.setRxBoostedGainMode(_prefs.rx_boosted_gain); @@ -1448,6 +1468,10 @@ void MyMesh::handleCmdFrame(size_t len) { savePrefs(); radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + + // PowerSaving variant + _prefs.getRadioPrefs()->applyRxPowerSaving(_prefs.sf, _prefs.bw); + MESH_DEBUG_PRINTLN("OK: CMD_SET_RADIO_PARAMS: f=%d, bw=%d, sf=%d, cr=%d", freq, bw, (uint32_t)sf, (uint32_t)cr); @@ -1705,7 +1729,11 @@ void MyMesh::handleCmdFrame(size_t len) { telemetry.addTemperature(TELEM_CHANNEL_SELF, temperature); // Built-in MCU Temperature } + // PowerSaving variant // query other sensors -- target specific + if (sensors.getLocationProvider() != NULL && sensors.getLocationProvider()->isPowerSavingEnabled()) { + sensors.getLocationProvider()->syncTime(); // Request for GPS sync if GPS is in PowerSaving + } sensors.querySensors(0xFF, telemetry); int i = 0; @@ -2181,6 +2209,18 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char* } #endif + // PowerSaving variant + if (strcmp(command, "powersaving on") == 0 || strcmp(command, "powersaving off") == 0) { + _prefs.powersaving_enabled = strcmp(command, "powersaving on") == 0 ? 1 : 0; + savePrefs(); + strcpy(reply, _prefs.powersaving_enabled ? "on" : "off"); + return true; + } + if (strcmp(command, "powersaving") == 0) { + strcpy(reply, _prefs.powersaving_enabled ? "on" : "off"); + return true; + } + if (strcmp(command, "board") == 0) { strcpy(reply, board.getManufacturerName()); return true; @@ -2448,7 +2488,8 @@ bool MyMesh::advert() { } } +// PowerSaving variant // To check if there is pending work bool MyMesh::hasPendingWork() const { - return _mgr->getOutboundTotal() > 0 || dirty_contacts_expiry != 0; + return _mgr->getOutboundTotal() > 0 || dirty_contacts_expiry != 0 || isTransmitting() || _prefs.getRadioPrefs()->isRxPowerSavingCalibrationActive(); } diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index 46a3dbb7c..f16fc22ad 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -37,6 +37,10 @@ public: uint32_t gps_interval = 0; // GPS read interval in seconds uint8_t autoadd_config = 0; // bitmask for auto-add contacts config uint8_t rx_boosted_gain = 0; // SX126x RX boosted gain mode (0=power saving, 1=boosted) + + // PowerSaving variant + RxPowerSavingConfig rxps; + uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA) uint8_t radio_fem_txgain = 0; // external LoRa FEM TX gain (low by default) uint8_t _client_repeat = 0; // DEPRECATED -> use repeat.disable_fwd @@ -48,6 +52,10 @@ public: char default_scope_name[31]; uint8_t default_scope_key[16]; int8_t tz_offset = 0; + + // PowerSaving variant + uint8_t powersaving_enabled = 0; // boolean + #ifdef ENABLE_WIFI_INTERFACE #ifndef WIFI_SSID #define WIFI_SSID "" @@ -81,10 +89,19 @@ private: def("agc_int", _parent->agc_reset_interval); def("hash_mode", _parent->path_hash_mode); def("multi_ack", _parent->multi_acks); + // PowerSaving variant + def("rxps_en", _parent->rxps.enabled); + def("rxps_rx_us", _parent->rxps.rx_us); + def("rxps_sleep_us", _parent->rxps.sleep_us); + def("rxps_level", _parent->rxps.level); + def("rxps_preamble", _parent->rxps.preamble); } public: RadioPrefs(NodePrefs* parent) : _parent(parent) { } + // PowerSaving variant + RxPowerSavingConfig* getRxPowerSavingConfig() override { return &_parent->rxps; } + // CommonRadioPrefs interface float getFreq() const override { return _parent->freq; } void setFreq(float f) override { _parent->freq = f; markDirty(); } @@ -172,6 +189,18 @@ private: }; CompanionPrefs companion; + // PowerSaving variant + class PowerPrefs : public ConfigSerializer { // same keys as CommonCLI + NodePrefs* _parent; + protected: + void structure() override { + def("pwr_sav_en", _parent->powersaving_enabled); + } + public: + PowerPrefs(NodePrefs* parent) : _parent(parent) { } + }; + PowerPrefs power; + DynamicConfigSerializer custom; #ifdef ENABLE_WIFI_INTERFACE @@ -200,13 +229,17 @@ protected: def("gps", gps); def("repeat", repeat); def("comp", companion); + + // PowerSaving variant + def("power", power); + def("custom", custom); #ifdef ENABLE_WIFI_INTERFACE def("wifi", wifi); #endif } public: - NodePrefs() : radio(this), gps(this), companion(this), custom(&radio) + NodePrefs() : radio(this), gps(this), companion(this), power(this), custom(&radio) #ifdef ENABLE_WIFI_INTERFACE , wifi(this) #endif @@ -220,6 +253,9 @@ public: void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; } CommonRadioPrefs* getRadioPrefs() { return &radio; } + // PowerSaving variant + const CommonRadioPrefs* getRadioPrefs() const { return &radio; } + KeyValueStore* getCustom() { return &custom; } bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || custom.isDirty(); } diff --git a/examples/companion_radio/PowerSaving.h b/examples/companion_radio/PowerSaving.h new file mode 100644 index 000000000..6ff120930 --- /dev/null +++ b/examples/companion_radio/PowerSaving.h @@ -0,0 +1,73 @@ +#pragma once + +#if defined(ESP32_PLATFORM) +// PowerSaving for companions +static void enableCompanionPowerSaving() { +#if defined(BLE_PIN_CODE) && !CONFIG_IDF_TARGET_ESP32C6 + // Enable BLE sleep + esp_bt_sleep_enable(); +#endif + + // Configure Power Management +#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 + +#if (defined(ARDUINO_USB_CDC_ON_BOOT) && ARDUINO_USB_CDC_ON_BOOT) || defined(ENABLE_USB_INTERFACE) + // Disable automatic light sleep for USB CDC Serial and USB Companion + pm_config = { .max_freq_mhz = 80, .min_freq_mhz = 40, .light_sleep_enable = false }; +#else + pm_config = { .max_freq_mhz = 80, .min_freq_mhz = 40, .light_sleep_enable = true }; +#endif + + 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); + } +} + +static void disableCompanionPowerSaving() { + // Disable BLE sleep + esp_bt_sleep_disable(); + + // Configure Power Management +#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 + +#if defined(ESP32_CPU_FREQ) + pm_config = { .max_freq_mhz = ESP32_CPU_FREQ, .min_freq_mhz = ESP32_CPU_FREQ, .light_sleep_enable = false }; +#else + pm_config = { .max_freq_mhz = 80, .min_freq_mhz = 80, .light_sleep_enable = false }; +#endif + + 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); + } +} + +static void applyCompanionPowerSaving(bool powersaving_enabled) { + if (the_mesh.getNodePrefs()->powersaving_enabled) { + enableCompanionPowerSaving(); + } else { + disableCompanionPowerSaving(); + } +} +#endif \ No newline at end of file diff --git a/examples/companion_radio/main.cpp b/examples/companion_radio/main.cpp index 839d49583..c1ba967bd 100644 --- a/examples/companion_radio/main.cpp +++ b/examples/companion_radio/main.cpp @@ -2,6 +2,12 @@ #include #include "MyMesh.h" +#ifdef ESP32_PLATFORM +#include "esp_pm.h" +#include "esp_bt.h" +#include "PowerSaving.h" +#endif + // Believe it or not, this std C function is busted on some platforms! static uint32_t _atoi(const char* sp) { uint32_t n = 0; @@ -287,6 +293,10 @@ void setup() { #endif board.onBootComplete(); + +#if defined(ESP32_PLATFORM) + applyCompanionPowerSaving(the_mesh.getNodePrefs()->powersaving_enabled); +#endif } void loop() { @@ -302,12 +312,28 @@ void loop() { external_watchdog.loop(); #endif - if (!the_mesh.hasPendingWork()) { + if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { #if defined(NRF52_PLATFORM) board.sleep(0); // nrf ignores seconds param, sleeps whenever possible +#elif defined(ESP32_PLATFORM) + #if defined(BLE_PIN_CODE) + if (!bluetooth_interface.isReadBusy() && !bluetooth_interface.isWriteBusy()) { // BLE is not busy + vTaskDelay(pdMS_TO_TICKS(10)); // attempt to sleep + } + #elif defined(ENABLE_USB_INTERFACE) + vTaskDelay(pdMS_TO_TICKS(10)); // attempt to sleep + #endif #endif } +#if defined(ESP32_PLATFORM) + static uint8_t applied_powersaving = the_mesh.getNodePrefs()->powersaving_enabled; + if (the_mesh.getNodePrefs()->powersaving_enabled != applied_powersaving) { // changed via CLI + applied_powersaving = the_mesh.getNodePrefs()->powersaving_enabled; + applyCompanionPowerSaving(applied_powersaving); + } +#endif + #ifdef ENABLE_WIFI_INTERFACE if (wifi_enabled) { // RP2040 has no WiFi event callbacks, so poll the link state instead diff --git a/src/Dispatcher.h b/src/Dispatcher.h index aad6cba3e..3adb58ff6 100644 --- a/src/Dispatcher.h +++ b/src/Dispatcher.h @@ -98,6 +98,9 @@ public: virtual Packet* removeOutboundByIdx(int i) = 0; virtual void queueInbound(Packet* packet, uint32_t scheduled_for) = 0; virtual Packet* getNextInbound(uint32_t now) = 0; + + // PowerSaving variant + virtual int getInboundTotal() const { return 0; } // delayed (rxdelay) packets not yet processed }; typedef uint32_t DispatcherAction; @@ -173,6 +176,8 @@ protected: virtual unsigned long getDutyCycleWindowMs() const { return 3600000; } public: + // PowerSaving variant + bool isTransmitting() const { return outbound != nullptr; } void begin(); void loop(); diff --git a/src/helpers/StaticPoolPacketManager.h b/src/helpers/StaticPoolPacketManager.h index f63700e51..9d3b9d0ab 100644 --- a/src/helpers/StaticPoolPacketManager.h +++ b/src/helpers/StaticPoolPacketManager.h @@ -38,4 +38,7 @@ public: mesh::Packet* removeOutboundByIdx(int i) override; void queueInbound(mesh::Packet* packet, uint32_t scheduled_for) override; mesh::Packet* getNextInbound(uint32_t now) override; + + // PowerSaving variant + int getInboundTotal() const override { return rx_queue.count(); } }; \ No newline at end of file