From 7c574c62d7f3ae1453d72afdf0179b33edc26c6b Mon Sep 17 00:00:00 2001 From: Kevin Le Date: Sat, 17 Oct 2026 11:57:03 +0700 Subject: [PATCH] Custom GPS PS for meshsolar, tracker, x1 and T1000e --- variants/heltec_mesh_solar/target.cpp | 73 +++++++++++++++++--- variants/heltec_mesh_solar/target.h | 1 + variants/heltec_tracker/target.cpp | 73 +++++++++++++++++--- variants/heltec_tracker/target.h | 1 + variants/meshtracker_x1/MeshTrackerX1Board.h | 6 +- variants/meshtracker_x1/target.cpp | 73 +++++++++++++++++--- variants/meshtracker_x1/target.h | 1 + variants/t1000-e/target.cpp | 72 ++++++++++++++++--- variants/t1000-e/target.h | 1 + 9 files changed, 259 insertions(+), 42 deletions(-) diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index 94e2684c1..aef713f30 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -31,14 +31,30 @@ mesh::LocalIdentity radio_new_identity() { void SolarSensorManager::start_gps() { if (!gps_active) { - gps_active = true; + if (_location->isPowerSavingEnabled()) { + gps_wake = true; // gps_active is true + _location->syncTime(); // Clear GPS data and force sync time + _location->setNextSleep(); // Next time to off + } else { + gps_active = true; + gps_wake = true; + } + _location->begin(); } } void SolarSensorManager::stop_gps() { if (gps_active) { - gps_active = false; + if (_location->isPowerSavingEnabled()) { + gps_wake = false; // gps_active is unchanged (true) even the GPS sleep (e.g: off) + _location->stopTimeSync(); // Stop time sync + _location->setNextWake(); // Next time to on + } else { + gps_active = false; + gps_wake = false; // When GPS is off, wake is false to be sure + } + _location->stop(); } } @@ -68,16 +84,51 @@ bool SolarSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& void SolarSensorManager::loop() { static long next_gps_update = 0; - _location->loop(); + // PowerSaving + if (_location->isPowerSavingEnabled() && gps_detected && gps_active) { + // Handle change in PowerSaving mode + _location->updatePowerSavingSettings(gps_wake); - if (millis() > next_gps_update) { - if (_location->isValid()) { - node_lat = ((double)_location->getLatitude())/1000000.; - node_lon = ((double)_location->getLongitude())/1000000.; - node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + 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(); + } } - next_gps_update = millis() + 1000; + } + + if ((!_location->isPowerSavingEnabled() && gps_active) || (_location->isPowerSavingEnabled() && gps_wake)) { + _location->loop(); + } + + if ((int32_t)(millis() - next_gps_update) >= 0) { + if ((!_location->isPowerSavingEnabled() && gps_active) || (_location->isPowerSavingEnabled() && gps_wake)) { + if (_location->isValid()) { + node_lat = ((double)_location->getLatitude()) / 1000000.; + node_lon = ((double)_location->getLongitude()) / 1000000.; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + } + + // In powersaving mode, GPS is on and off. Only update data when GPS is on + if (_location->isPowerSavingEnabled()) next_gps_update = millis() + 1000; + } + + if (!_location->isPowerSavingEnabled()) next_gps_update = millis() + 1000; } } @@ -99,8 +150,10 @@ const char* SolarSensorManager::getSettingValue(int i) const { bool SolarSensorManager::setSettingValue(const char* name, const char* value) { if (gps_detected && strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { + gps_active = false; // Disabled by CLI or App stop_gps(); } else { + gps_active = true; // Enabled by CLI or App start_gps(); } return true; diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index 8e13a9601..f80d22473 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -16,6 +16,7 @@ class SolarSensorManager : public SensorManager { bool gps_active = false; bool gps_detected = false; + bool gps_wake = false; // In PowerSaving, gps_active can have gps_wake true or false LocationProvider* _location; void start_gps(); diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index f32c41ff4..67effdb0d 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -45,14 +45,31 @@ mesh::LocalIdentity radio_new_identity() { void HWTSensorManager::start_gps() { if (!gps_active) { _location->begin(); // Claims periph_power via RefCountedDigitalPin - gps_active = true; + + 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; + } + Serial1.println("$CFGSYS,h35155*68"); // Configure GPS for all constellations } } void HWTSensorManager::stop_gps() { if (gps_active) { - gps_active = false; + if (_location->isPowerSavingEnabled()) { + gps_wake = false; // gps_active is unchanged (true) even the GPS sleep (e.g: off) + _location->stopTimeSync(); // Stop time sync + _location->setNextWake(); // Next time to on + } else { + gps_active = false; + gps_wake = false; // When GPS is off, wake is false to be sure + } + _location->stop(); // Releases periph_power via RefCountedDigitalPin } } @@ -73,16 +90,50 @@ bool HWTSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& t void HWTSensorManager::loop() { static long next_gps_update = 0; - _location->loop(); + // PowerSaving + if (_location->isPowerSavingEnabled() && gps_active) { + // Handle change in PowerSaving mode + _location->updatePowerSavingSettings(gps_wake); - if (millis() > next_gps_update) { - if (gps_active && _location->isValid()) { - node_lat = ((double)_location->getLatitude())/1000000.; - node_lon = ((double)_location->getLongitude())/1000000.; - node_altitude = ((double)_location->getAltitude()) / 1000.0; - MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + 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(); + } } - next_gps_update = millis() + 1000; + } + + if ((!_location->isPowerSavingEnabled() && gps_active) || (_location->isPowerSavingEnabled() && gps_wake)) { + _location->loop(); + } + + if ((int32_t)(millis() - next_gps_update) >= 0) { + if ((!_location->isPowerSavingEnabled() && gps_active) || (_location->isPowerSavingEnabled() && gps_wake)) { + if (gps_active && _location->isValid()) { + node_lat = ((double)_location->getLatitude()) / 1000000.; + node_lon = ((double)_location->getLongitude()) / 1000000.; + node_altitude = ((double)_location->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + } + + // In powersaving mode, GPS is on and off. Only update data when GPS is on + if (_location->isPowerSavingEnabled()) next_gps_update = millis() + 1000; + } + + if (!_location->isPowerSavingEnabled()) next_gps_update = millis() + 1000; } } @@ -100,8 +151,10 @@ const char* HWTSensorManager::getSettingValue(int i) const { bool HWTSensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { + gps_active = false; // Disabled by CLI or App stop_gps(); } else { + gps_active = true; // Enabled by CLI or App start_gps(); } return true; diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 2931eda81..0fe3d0251 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -15,6 +15,7 @@ class HWTSensorManager : public SensorManager { bool gps_active = false; + bool gps_wake = false; // In PowerSaving, gps_active can have gps_wake true or false LocationProvider * _location; void start_gps(); diff --git a/variants/meshtracker_x1/MeshTrackerX1Board.h b/variants/meshtracker_x1/MeshTrackerX1Board.h index 51f022e0b..e153dd7fb 100644 --- a/variants/meshtracker_x1/MeshTrackerX1Board.h +++ b/variants/meshtracker_x1/MeshTrackerX1Board.h @@ -46,7 +46,9 @@ public: return 0; } - void powerOff() override { + void shutdownPeripherals() override { + NRF52Board::shutdownPeripherals(); + #ifdef HAS_GPS digitalWrite(GPS_VRTC_EN, LOW); digitalWrite(GPS_RESET, LOW); @@ -81,7 +83,5 @@ public: #ifdef BUTTON_PIN nrf_gpio_cfg_sense_input(BUTTON_PIN, NRF_GPIO_PIN_PULLDOWN, NRF_GPIO_PIN_SENSE_HIGH); #endif - - sd_power_system_off(); } }; diff --git a/variants/meshtracker_x1/target.cpp b/variants/meshtracker_x1/target.cpp index 5b90d6609..6dc2bc0df 100644 --- a/variants/meshtracker_x1/target.cpp +++ b/variants/meshtracker_x1/target.cpp @@ -27,7 +27,15 @@ mesh::LocalIdentity radio_new_identity() { } void MeshTrackerX1SensorManager::start_gps() { - gps_active = true; + if (_nmea->isPowerSavingEnabled()) { + gps_wake = true; // gps_active is unchanged (true for GPS sleep, false for GPS off) + _nmea->syncTime(); // Clear GPS data and force sync time + _nmea->setNextSleep(); // Next time to off + } else { + gps_active = true; + gps_wake = true; + } + digitalWrite(GPS_EN, HIGH); delay(10); digitalWrite(GPS_RTC_INT, HIGH); @@ -36,7 +44,15 @@ void MeshTrackerX1SensorManager::start_gps() { } void MeshTrackerX1SensorManager::stop_gps() { - gps_active = false; + if (_nmea->isPowerSavingEnabled()) { + gps_wake = false; // gps_active is unchanged (true) even the GPS sleep (e.g: off) + _nmea->stopTimeSync(); // Stop time sync + _nmea->setNextWake(); // Next time to on + } else { + gps_active = false; + gps_wake = false; // When GPS is off, wake is false to be sure + } + digitalWrite(GPS_VRTC_EN, HIGH); // keep GPS RTC alive for faster fix on wake digitalWrite(GPS_RTC_INT, LOW); // make sure this is LOW so we can pulse it to wake airohaEnterSleep(_nmea); @@ -77,16 +93,51 @@ bool MeshTrackerX1SensorManager::querySensors(uint8_t requester_permissions, Cay void MeshTrackerX1SensorManager::loop() { static long next_gps_update = 0; + // PowerSaving + if (_nmea->isPowerSavingEnabled() && gps_active) { + // Handle change in PowerSaving mode + _nmea->updatePowerSavingSettings(gps_wake); - _nmea->loop(); - - if (millis() > next_gps_update) { - if (gps_active && _nmea->isValid()) { - node_lat = ((double)_nmea->getLatitude())/1000000.; - node_lon = ((double)_nmea->getLongitude())/1000000.; - node_altitude = ((double)_nmea->getAltitude()) / 1000.0; + if (gps_wake) { + // GPS is awake: check whether it should sleep + if ((int32_t)(millis() - _nmea->getNextSleep()) >= 0) { + POWERSAVING_DEBUG_PRINTLN("GPS wake timeout. Enter sleep"); + stop_gps(); + } else if (!_nmea->waitingTimeSync()) { + POWERSAVING_DEBUG_PRINTLN("GPS set. Enter sleep early"); + stop_gps(); + } + } else { + // GPS is asleep: check whether it should wake + if ((int32_t)(millis() - _nmea->getNextWake()) >= 0) { + POWERSAVING_DEBUG_PRINTLN("GPS sleep timeout. Wakeup."); + start_gps(); + } else if (_nmea->waitingTimeSync()) { + POWERSAVING_DEBUG_PRINTLN("CLI gps sync. Wakeup"); + start_gps(); + } } - next_gps_update = millis() + 1000; + } + + if ((!_nmea->isPowerSavingEnabled() && gps_active) || (_nmea->isPowerSavingEnabled() && gps_wake)) { + _nmea->loop(); + } + + if ((int32_t)(millis() - next_gps_update) >= 0) { + if ((!_nmea->isPowerSavingEnabled() && gps_active) || (_nmea->isPowerSavingEnabled() && gps_wake)) { + if (gps_active && _nmea->isValid()) { + node_lat = ((double)_nmea->getLatitude()) / 1000000.; + node_lon = ((double)_nmea->getLongitude()) / 1000000.; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + node_altitude = ((double)_nmea->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + } + + // In powersaving mode, GPS is on and off. Only update data when GPS is on + if (_nmea->isPowerSavingEnabled()) next_gps_update = millis() + 1000; + } + + if (!_nmea->isPowerSavingEnabled()) next_gps_update = millis() + 1000; } } @@ -104,8 +155,10 @@ const char* MeshTrackerX1SensorManager::getSettingValue(int i) const { bool MeshTrackerX1SensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { + gps_active = false; // Disabled by CLI or App stop_gps(); } else { + gps_active = true; // Enabled by CLI or App start_gps(); } return true; diff --git a/variants/meshtracker_x1/target.h b/variants/meshtracker_x1/target.h index 9741efb6a..6138a7ee5 100644 --- a/variants/meshtracker_x1/target.h +++ b/variants/meshtracker_x1/target.h @@ -15,6 +15,7 @@ class MeshTrackerX1SensorManager: public SensorManager { bool gps_active = false; + bool gps_wake = false; // In PowerSaving, gps_active can have gps_wake true or false bool baro_ok = false; Adafruit_SPA06_003 spa06; LocationProvider * _nmea; diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 70a64359f..cb6194d34 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -81,7 +81,15 @@ mesh::LocalIdentity radio_new_identity() { } void T1000SensorManager::start_gps() { - gps_active = true; + if (_nmea->isPowerSavingEnabled()) { + gps_wake = true; // gps_active is unchanged (true for GPS sleep, false for GPS off) + _nmea->syncTime(); // Clear GPS data and force sync time + _nmea->setNextSleep(); // Next time to off + } else { + gps_active = true; + gps_wake = true; + } + digitalWrite(GPS_EN, HIGH); delay(10); digitalWrite(GPS_RTC_INT, HIGH); @@ -90,7 +98,15 @@ void T1000SensorManager::start_gps() { } void T1000SensorManager::stop_gps() { - gps_active = false; + if (_nmea->isPowerSavingEnabled()) { + gps_wake = false; // gps_active is unchanged (true) even the GPS sleep (e.g: off) + _nmea->stopTimeSync(); // Stop time sync + _nmea->setNextWake(); // Next time to on + } else { + gps_active = false; + gps_wake = false; // When GPS is off, wake is false to be sure + } + digitalWrite(GPS_VRTC_EN, HIGH); // keep GPS RTC alive for faster fix on wake digitalWrite(GPS_RTC_INT, LOW); // make sure this is LOW so we can pulse it to wake airohaEnterSleep(_nmea); // send command to put the GPS into RTC backup sleep @@ -122,15 +138,51 @@ bool T1000SensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& void T1000SensorManager::loop() { static long next_gps_update = 0; - _nmea->loop(); + // PowerSaving + if (_nmea->isPowerSavingEnabled() && gps_active) { + // Handle change in PowerSaving mode + _nmea->updatePowerSavingSettings(gps_wake); - if (millis() > next_gps_update) { - if (gps_active && _nmea->isValid()) { - node_lat = ((double)_nmea->getLatitude())/1000000.; - node_lon = ((double)_nmea->getLongitude())/1000000.; - node_altitude = ((double)_nmea->getAltitude()) / 1000.0; + if (gps_wake) { + // GPS is awake: check whether it should sleep + if ((int32_t)(millis() - _nmea->getNextSleep()) >= 0) { + POWERSAVING_DEBUG_PRINTLN("GPS wake timeout. Enter sleep"); + stop_gps(); + } else if (!_nmea->waitingTimeSync()) { + POWERSAVING_DEBUG_PRINTLN("GPS set. Enter sleep early"); + stop_gps(); + } + } else { + // GPS is asleep: check whether it should wake + if ((int32_t)(millis() - _nmea->getNextWake()) >= 0) { + POWERSAVING_DEBUG_PRINTLN("GPS sleep timeout. Wakeup."); + start_gps(); + } else if (_nmea->waitingTimeSync()) { + POWERSAVING_DEBUG_PRINTLN("CLI gps sync. Wakeup"); + start_gps(); + } } - next_gps_update = millis() + 1000; + } + + if ((!_nmea->isPowerSavingEnabled() && gps_active) || (_nmea->isPowerSavingEnabled() && gps_wake)) { + _nmea->loop(); + } + + if ((int32_t)(millis() - next_gps_update) >= 0) { + if ((!_nmea->isPowerSavingEnabled() && gps_active) || (_nmea->isPowerSavingEnabled() && gps_wake)) { + if (gps_active && _nmea->isValid()) { + node_lat = ((double)_nmea->getLatitude()) / 1000000.; + node_lon = ((double)_nmea->getLongitude()) / 1000000.; + MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); + node_altitude = ((double)_nmea->getAltitude()) / 1000.0; + MESH_DEBUG_PRINTLN("lat %f lon %f alt %f", node_lat, node_lon, node_altitude); + } + + // In powersaving mode, GPS is on and off. Only update data when GPS is on + if (_nmea->isPowerSavingEnabled()) next_gps_update = millis() + 1000; + } + + if (!_nmea->isPowerSavingEnabled()) next_gps_update = millis() + 1000; } } @@ -148,8 +200,10 @@ const char* T1000SensorManager::getSettingValue(int i) const { bool T1000SensorManager::setSettingValue(const char* name, const char* value) { if (strcmp(name, "gps") == 0) { if (strcmp(value, "0") == 0) { + gps_active = false; // Disabled by CLI or App stop_gps(); } else { + gps_active = true; // Enabled by CLI or App start_gps(); } return true; diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index db003cc5f..cd91ceec9 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -14,6 +14,7 @@ class T1000SensorManager: public SensorManager { bool gps_active = false; + bool gps_wake = false; // In PowerSaving, gps_active can have gps_wake true or false LocationProvider * _nmea; void start_gps();