From 0b9b6ec5441345a82da4439ba34469f79b6a0a4b Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 18 Oct 2026 21:24:57 +0700 Subject: [PATCH] PowerSaving for sensors --- examples/simple_sensor/SensorMesh.cpp | 47 ++++++++++++++++++++++++++- examples/simple_sensor/SensorMesh.h | 4 +++ examples/simple_sensor/main.cpp | 37 +++++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/examples/simple_sensor/SensorMesh.cpp b/examples/simple_sensor/SensorMesh.cpp index b18606681..00948af26 100644 --- a/examples/simple_sensor/SensorMesh.cpp +++ b/examples/simple_sensor/SensorMesh.cpp @@ -46,6 +46,11 @@ #define SENSOR_READ_INTERVAL_SECS 60 #endif +// PowerSaving variant +#ifndef GPS_READ_INTERVAL_SECS + #define GPS_READ_INTERVAL_SECS 60 +#endif + /* ------------------------------ Code -------------------------------- */ #define FIRMWARE_VER_LEVEL 1 @@ -150,7 +155,14 @@ uint8_t SensorMesh::handleRequest(ClientInfo* from, uint32_t sender_timestamp, u telemetry.reset(); telemetry.addVoltage(TELEM_CHANNEL_SELF, (float)board.getBattMilliVolts() / 1000.0f); + + // PowerSaving variant // query other sensors -- target specific + if (sensors.getLocationProvider() != NULL && + sensors.getLocationProvider()->isPowerSavingEnabled() && + getRTCClock()->getCurrentTime() - sensors.getLocationProvider()->getLastValidTimeSync() >= GPS_READ_INTERVAL_SECS) { + sensors.getLocationProvider()->syncTime(); // Request for GPS sync if GPS is in PowerSaving if later than read interval + } sensors.querySensors(0xFF & perm_mask, telemetry); // allow all telemetry permissions for admin or guest // TODO: let requester know permissions they have: telemetry.addPresence(TELEM_CHANNEL_SELF, perms); float temperature = board.getMCUTemperature(); @@ -820,10 +832,30 @@ SensorMesh::SensorMesh(mesh::MainBoard& board, mesh::Radio& radio, mesh::Millise _prefs.gps_enabled = 0; _prefs.gps_interval = 0; _prefs.advert_loc_policy = ADVERT_LOC_PREFS; - _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 _prefs.radio_fem_txgain = 0; +#endif memset(default_scope.key, 0, sizeof(default_scope.key)); + + // 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 SensorMesh::begin(FILESYSTEM* fs) { @@ -856,6 +888,8 @@ void SensorMesh::begin(FILESYSTEM* fs) { } 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); board.attachDynamicPrefs(_prefs.getCustom()); @@ -1018,12 +1052,16 @@ void SensorMesh::loop() { if (set_radio_at && millisHasNowPassed(set_radio_at)) { // apply pending (temporary) radio params set_radio_at = 0; // clear timer radio_driver.setParams(pending_freq, pending_bw, pending_sf, pending_cr); + // PowerSaving variant + _prefs.getRadioPrefs()->applyRxPowerSaving(pending_sf, pending_bw); MESH_DEBUG_PRINTLN("Temp radio params"); } if (revert_radio_at && millisHasNowPassed(revert_radio_at)) { // revert radio params to orig revert_radio_at = 0; // clear timer radio_driver.setParams(_prefs.freq, _prefs.bw, _prefs.sf, _prefs.cr); + // PowerSaving variant + _prefs.getRadioPrefs()->applyRxPowerSaving(_prefs.sf, _prefs.bw); MESH_DEBUG_PRINTLN("Radio params restored"); } @@ -1114,3 +1152,10 @@ void SensorMesh::loop() { dirty_contacts_expiry = 0; } } + +// PowerSaving variant +// To check if there is pending work +bool SensorMesh::hasPendingWork() const { + return _mgr->getOutboundTotal() > 0 || _mgr->getInboundTotal() > 0 || // rxdelay packets wait in the inbound queue + isTransmitting() || _prefs.getRadioPrefs()->isRxPowerSavingCalibrationActive(); +} diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 590ca0c76..1ad3aa26c 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -52,6 +52,10 @@ public: void begin(FILESYSTEM* fs); void loop(); void handleCommand(ClientInfo* from, uint32_t sender_timestamp, char* command, char* reply); + + // PowerSaving variant + // To check if there is pending work + bool hasPendingWork() const; // CommonCLI callbacks const char* getFirmwareVer() override { return FIRMWARE_VERSION; } diff --git a/examples/simple_sensor/main.cpp b/examples/simple_sensor/main.cpp index 66991daad..3d6162293 100644 --- a/examples/simple_sensor/main.cpp +++ b/examples/simple_sensor/main.cpp @@ -52,6 +52,10 @@ void halt() { static char command[160]; +// PowerSaving variant +// For power saving +unsigned long POWERSAVING_FIRSTSLEEP_SECS = 120; // The first sleep (if enabled) from boot + void setup() { Serial.begin(115200); delay(1000); @@ -62,6 +66,13 @@ void setup() { external_watchdog.begin(); #endif +// PowerSaving variant +#if defined(MESH_DEBUG) && defined(NRF52_PLATFORM) + // give some extra time for serial to settle so + // boot debug messages can be seen on terminal + delay(5000); +#endif + #ifdef DISPLAY_CLASS if (display.begin()) { display.startFrame(); @@ -106,6 +117,16 @@ void setup() { command[0] = 0; + // PowerSaving variant +#if ENV_INCLUDE_GPS == 1 + // Apply PowerSaving profile for GPS + if (sensors.getLocationProvider() != NULL) { + // GPS on and off duration in seconds + sensors.getLocationProvider()->setPowerSavingProfile(the_mesh.getNodePrefs()->powersaving_enabled, 600, + 86400); // Max 10 minutes, 1 day + } +#endif + sensors.begin(); the_mesh.begin(fs); @@ -164,4 +185,20 @@ void loop() { #ifdef HAS_EXTERNAL_WATCHDOG external_watchdog.loop(); #endif + + // PowerSaving variant + if (the_mesh.getNodePrefs()->powersaving_enabled && !the_mesh.hasPendingWork()) { +#if defined(NRF52_PLATFORM) + board.sleep(0); // nrf ignores seconds param, sleeps whenever possible +#else + if (the_mesh.millisHasNowPassed(POWERSAVING_FIRSTSLEEP_SECS * 1000)) { // To check if it is time to sleep + board.sleep(30); // Sleep. Wake up after a while or when receiving a LoRa packet + } +#endif + } + + if (the_mesh.getNodePrefs()->reboot_interval > 0 && + the_mesh.millisHasNowPassed(the_mesh.getNodePrefs()->reboot_interval * 3600000)) { + board.reboot(); + } }