From 4d9e15d467a942183aad3104fc2219be9b40f5d3 Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sun, 18 Oct 2026 14:01:21 +0700 Subject: [PATCH] GPS PowerSaving --- .../sensors/EnvironmentSensorManager.cpp | 125 +++++++++++++----- .../sensors/EnvironmentSensorManager.h | 4 + src/helpers/sensors/LocationProvider.h | 51 +++++++ .../sensors/MicroNMEALocationProvider.h | 20 ++- 4 files changed, 167 insertions(+), 33 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 0dc892c3a..8f08988d7 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -165,7 +165,11 @@ static RAK12035_SoilMoisture RAK12035; #endif #ifdef RAK_WISBLOCK_GPS -static uint32_t gpsResetPin = 0; +// PowerSaving variant +// -1 = no enable pin; out-of-range values are no-ops in pinMode/digitalWrite, +// while 0 would be a real GPIO (P0.00 = LFXO crystal on nRF52) +static uint32_t gpsResetPin = -1; + static bool i2cGPSFlag = false; static bool serialGPSFlag = false; #ifndef TELEM_RAK12500_ADDRESS @@ -181,6 +185,9 @@ class RAK12500LocationProvider : public LocationProvider { int _sats = 0; long _epoch = 0; bool _fix = false; + + // PowerSaving variant + int _pin_en = -1; public: long getLatitude() override { return _lat; } long getLongitude() override { return _lng; } @@ -193,18 +200,28 @@ public: void begin() override { } void stop() override { } void loop() override { - if (ublox_GNSS.getGnssFixOk(8)) { - _fix = true; - _lat = ublox_GNSS.getLatitude(2) / 10; - _lng = ublox_GNSS.getLongitude(2) / 10; - _alt = ublox_GNSS.getAltitude(2); - _sats = ublox_GNSS.getSIV(2); - } else { - _fix = false; + // PowerSaving variant + static long next_gps_update = 0; + if ((int32_t)(millis() - next_gps_update) >= 0) { + if (ublox_GNSS.getGnssFixOk(8)) { + _fix = true; + _lat = ublox_GNSS.getLatitude(2) / 10; + _lng = ublox_GNSS.getLongitude(2) / 10; + _alt = ublox_GNSS.getAltitude(2); + _sats = ublox_GNSS.getSIV(2); + } else { + _fix = false; + } + _epoch = ublox_GNSS.getUnixEpoch(2); + + next_gps_update = millis() + 1000; } - _epoch = ublox_GNSS.getUnixEpoch(2); } bool isEnabled() override { return true; } + + // PowerSaving variant + void setPinEn(int pin_en) override { _pin_en = pin_en; } + int getPinEn() override { return _pin_en; } }; static RAK12500LocationProvider RAK12500_provider; @@ -731,8 +748,12 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val #if ENV_INCLUDE_GPS if (gps_detected && strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { + // PowerSaving variant + gps_active = false; // Disabled by CLI or App stop_gps(); } else { + // PowerSaving variant + gps_active = true; // Enabled by CLI or App start_gps(); } return true; @@ -805,14 +826,10 @@ void EnvironmentSensorManager::rakGPSInit(){ Serial1.begin(9600); #endif - //search for the correct IO standby pin depending on socket used - if(gpsIsAwake(WB_IO2)){ - } - else if(gpsIsAwake(WB_IO4)){ - } - else if(gpsIsAwake(WB_IO5)){ - } - else{ + // search for the correct IO standby pin depending on socket used + if (gpsIsAwake(WB_IO2)) { + _location->setPinEn(WB_IO2); // WB_IO2 is the power switch for all sensor and IO slots + } else { MESH_DEBUG_PRINTLN("No GPS found"); gps_active = false; gps_detected = false; @@ -866,13 +883,9 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ _location = &RAK12500_provider; return true; - } else if (Serial1.available()) { + } else if (Serial1.available()) { // RAK12501 (L76K) on UART MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on"); -#ifdef PIN_GPS_EN - if(PIN_GPS_EN){ - gpsResetPin = PIN_GPS_EN; - } -#endif + gpsResetPin = ioPin; // WB_IO2 serialGPSFlag = true; gps_active = true; gps_detected = true; @@ -888,7 +901,16 @@ bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ #endif void EnvironmentSensorManager::start_gps() { - gps_active = true; + // PowerSaving variant + if (_location->isPowerSavingEnabled()) { + gps_wake = true; // gps_active is unchanged (true for GPS sleep, false for GPS off) + _location->syncTime(); // Clear GPS data and force sync time + _location->setNextSleep(); // Next time to off + } else { + gps_active = true; + gps_wake = true; + } + #ifdef RAK_WISBLOCK_GPS #ifndef RAK_3401 pinMode(gpsResetPin, OUTPUT); @@ -906,7 +928,16 @@ void EnvironmentSensorManager::start_gps() { } void EnvironmentSensorManager::stop_gps() { - gps_active = false; + // PowerSaving variant + if (_location->isPowerSavingEnabled()) { + gps_wake = false; // gps_active is unchanged (true for GPS sleep, false for GPS off) + _location->stopTimeSync(); // Stop time sync + _location->setNextWake(); // Next time to on + } else { + gps_active = false; + gps_wake = false; + } + #ifdef RAK_WISBLOCK_GPS #ifndef RAK_3401 // rak3401 shouldn't turn off WB_IO2 as it powers the PA pinMode(gpsResetPin, OUTPUT); @@ -927,13 +958,41 @@ void EnvironmentSensorManager::stop_gps() { void EnvironmentSensorManager::loop() { #if ENV_INCLUDE_GPS - static unsigned long next_gps_update = 0; - if (gps_active) { + static long next_gps_update = 0; + + // PowerSaving variant + if (_location->isPowerSavingEnabled() && gps_detected && gps_active) { + // Handle change in PowerSaving mode + _location->updatePowerSavingSettings(gps_wake); + + if (gps_wake) { + // GPS is awake: check whether it should sleep + if ((int32_t)(millis() - _location->getNextSleep()) >= 0) { + POWERSAVING_DEBUG_PRINTLN("GPS wake timeout. Enter sleep"); + stop_gps(); + } else if (!_location->waitingTimeSync()) { + POWERSAVING_DEBUG_PRINTLN("GPS set. Enter sleep early"); + stop_gps(); + } + } else { + // GPS is asleep: check whether it should wake + if ((int32_t)(millis() - _location->getNextWake()) >= 0) { + POWERSAVING_DEBUG_PRINTLN("GPS sleep timeout. Wakeup."); + start_gps(); + } else if (_location->waitingTimeSync()) { + POWERSAVING_DEBUG_PRINTLN("CLI gps sync. Wakeup"); + start_gps(); + } + } + } + + if ((!_location->isPowerSavingEnabled() && gps_active) || (_location->isPowerSavingEnabled() && gps_wake)) { _location->loop(); } - if ((long)(millis() - next_gps_update) > 0) { - if(gps_active){ + // PowerSaving variant + if ((int32_t)(millis() - next_gps_update) >= 0) { + if((!_location->isPowerSavingEnabled() && gps_active) || (_location->isPowerSavingEnabled() && gps_wake)){ #ifdef RAK_WISBLOCK_GPS if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; @@ -951,8 +1010,12 @@ void EnvironmentSensorManager::loop() { MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); } #endif + + // In powersaving mode, GPS is on and off. Only update data when GPS is on + if(_location->isPowerSavingEnabled()) next_gps_update = millis() + (gps_update_interval_sec * 1000); } - next_gps_update = millis() + (gps_update_interval_sec * 1000); + + if(!_location->isPowerSavingEnabled()) next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif #if ENV_INCLUDE_BME680_BSEC diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 17706cbc4..8ef8d417c 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -21,6 +21,10 @@ protected: bool gps_detected = false; bool gps_active = false; + + // PowerSaving variant + bool gps_wake = false; // In PowerSaving, gps_active can have gps_wake true or false + uint32_t gps_update_interval_sec = 1; #if ENV_INCLUDE_GPS diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index bc4f1fe76..61f3acaaf 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -9,9 +9,55 @@ class LocationProvider { protected: bool _time_sync_needed = true; + // PowerSaving variant + uint8_t* powersaving_enabled = nullptr; + bool _powersaving_enabled_prev = false; // Track previous PowerSaving state + unsigned long _wake_duration_secs = 86400; // Full day + unsigned long _sleep_duration_secs = 0; // No off + unsigned long _next_wake = 0; + unsigned long _next_sleep = 0; + unsigned long _last_valid_time_sync = 0; + public: virtual void syncTime() { _time_sync_needed = true; } virtual bool waitingTimeSync() { return _time_sync_needed; } + + // PowerSaving variant + virtual void stopTimeSync() { _time_sync_needed = false; } + + virtual void setPowerSavingProfile(uint8_t &enabled, unsigned long wake_duration_secs, + unsigned long sleep_duration_secs) { + powersaving_enabled = &enabled; + _wake_duration_secs = wake_duration_secs; + _sleep_duration_secs = sleep_duration_secs; + } + + virtual bool isPowerSavingEnabled() const { + return powersaving_enabled != nullptr && *powersaving_enabled != 0; + } + + virtual void updatePowerSavingSettings(bool gps_wake) { + bool powersaving_enabled = isPowerSavingEnabled(); + + // PowerSaving changed from OFF to ON + if (!_powersaving_enabled_prev && powersaving_enabled) { + if (gps_wake) { + syncTime(); // Wake when powersaving on + setNextSleep(); // On, set next sleep + } else { + setNextWake(); // Off, set next wake + } + } + + _powersaving_enabled_prev = powersaving_enabled; + } + + virtual void setNextWake() { _next_wake = millis() + _sleep_duration_secs * 1000UL; } + virtual unsigned long getNextWake() { return _next_wake; } + virtual void setNextSleep() { _next_sleep = millis() + _wake_duration_secs * 1000UL; } + virtual unsigned long getNextSleep() { return _next_sleep; } + virtual unsigned long getLastValidTimeSync() { return _last_valid_time_sync; } + virtual long getLatitude() = 0; virtual long getLongitude() = 0; virtual long getAltitude() = 0; @@ -27,6 +73,11 @@ public: virtual void loop() = 0; virtual bool isEnabled() = 0; + // PowerSaving variant + virtual void setPinEn(int pin_en) = 0; + virtual int getPinEn() = 0; + + // Format compact diagnostics in the caller-provided buffer. Providers that // have more information (for example UART counters) can override this. virtual void formatDiagnostics(char* out, size_t out_size) { diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 37ef5762c..1899fa242 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -157,6 +157,15 @@ public : } } + // PowerSaving variant + void setPinEn(int pin_en) override { + _pin_en = pin_en; + } + + int getPinEn() override { + return _pin_en; + } + void syncTime() override { nmea.clear(); LocationProvider::syncTime(); } long getLatitude() override { return nmea.getLatitude(); } long getLongitude() override { return nmea.getLongitude(); } @@ -166,7 +175,9 @@ public : return alt; } long satellitesCount() override { return nmea.getNumSatellites(); } - bool isValid() override { return nmea.isValid(); } + + // PowerSaving variant + bool isValid() override { return nmea.isValid() && nmea.getYear() < 2030; } // TODO: Pending better validation long getTimestamp() override { DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond()); @@ -247,9 +258,14 @@ public : _clock->setCurrentTime(getTimestamp()); _time_sync_needed = false; _last_time_sync = millis(); + + // PowerSaving variant + _last_valid_time_sync = _clock->getCurrentTime(); } } - if (isValid()) { + + // PowerSaving variant + if (isValid() && satellitesCount() >= 5) { time_valid ++; } }