From df5a768c5d2d5b9a47a513092c4193ccbc3f14d1 Mon Sep 17 00:00:00 2001 From: Florent Date: Sun, 1 Nov 2026 20:08:16 -0400 Subject: [PATCH 01/23] gps: add GpsDriver, handle the hardware and software switches in the drivers --- examples/companion_radio/ui-new/UITask.cpp | 8 +- examples/companion_radio/ui-tiny/UITask.cpp | 8 +- examples/simple_repeater/MyMesh.h | 3 + examples/simple_room_server/MyMesh.h | 3 + examples/simple_sensor/SensorMesh.h | 3 + src/helpers/CommonCLI.cpp | 18 ++ src/helpers/CommonCLI.h | 2 + src/helpers/sensors/AirohaGpsDriver.h | 83 +++++ .../sensors/EnvironmentSensorManager.cpp | 249 +++------------ .../sensors/EnvironmentSensorManager.h | 5 - src/helpers/sensors/GpsDriver.h | 18 ++ src/helpers/sensors/LocationProvider.h | 19 ++ .../sensors/MicroNMEALocationProvider.h | 287 +++++++++--------- src/helpers/sensors/NmeaGpsDriver.h | 262 ++++++++++++++++ src/helpers/sensors/RakGpsDriver.h | 112 +++++++ src/helpers/sensors/RakLocationProvider.h | 144 +++++++++ variants/gat562_30s_mesh_kit/target.cpp | 7 +- variants/lilygo_techo/target.cpp | 5 +- variants/rak3401/target.cpp | 8 +- variants/rak4631/target.cpp | 7 +- variants/t1000-e/platformio.ini | 1 + variants/t1000-e/target.cpp | 24 +- variants/t1000-e/target.h | 6 +- variants/thinknode_m1/target.cpp | 59 +--- variants/thinknode_m1/target.h | 3 - variants/thinknode_m1/variant.h | 2 + variants/thinknode_m5/target.cpp | 5 +- variants/thinknode_m5/variant.h | 3 +- 28 files changed, 889 insertions(+), 465 deletions(-) create mode 100644 src/helpers/sensors/AirohaGpsDriver.h create mode 100644 src/helpers/sensors/GpsDriver.h create mode 100644 src/helpers/sensors/NmeaGpsDriver.h create mode 100644 src/helpers/sensors/RakGpsDriver.h create mode 100644 src/helpers/sensors/RakLocationProvider.h diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index 55cae1fe33f..cfe34cf2a91 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -392,16 +392,16 @@ class HomeScreen : public UIScreen { char buf[50]; int y = 18; bool gps_state = _task->getGPSState(); -#ifdef PIN_GPS_SWITCH - bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); + bool hw_gps_state = nmea != NULL && nmea->isHardwareEnabled(); + if (nmea != NULL && nmea->hasHardwareEnableState()) { if (gps_state != hw_gps_state) { strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); } else { strcpy(buf, gps_state ? "gps on" : "gps off"); } -#else + } else { strcpy(buf, gps_state ? "gps on" : "gps off"); -#endif + } display.setColor(UIColor::primary_txt); display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index 6bf33849767..035340a8fc2 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -254,16 +254,16 @@ class HomeScreen : public UIScreen { char buf[50]; int y = 8; bool gps_state = _task->getGPSState(); -#ifdef PIN_GPS_SWITCH - bool hw_gps_state = digitalRead(PIN_GPS_SWITCH); + bool hw_gps_state = nmea != NULL && nmea->isHardwareEnabled(); + if (nmea != NULL && nmea->hasHardwareEnableState()) { if (gps_state != hw_gps_state) { strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); } else { strcpy(buf, gps_state ? "gps on" : "gps off"); } -#else + } else { strcpy(buf, gps_state ? "gps on" : "gps off"); -#endif + } display.setColor(UIColor::primary_txt); display.drawTextLeftAlign(0, y, buf); if (nmea == NULL) { diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index dc89325861c..1d975a94d34 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -159,6 +159,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { + char period[11]; + snprintf(period, sizeof(period), "%lu", (unsigned long)_prefs.gps_period); + sensors.setSettingValue("gps_period", period); sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 4dee8bdbf40..5f8de73a3d8 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -168,6 +168,9 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { + char period[11]; + snprintf(period, sizeof(period), "%lu", (unsigned long)_prefs.gps_period); + sensors.setSettingValue("gps_period", period); sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index 590ca0c7657..b93939de3f5 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -173,6 +173,9 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { + char period[11]; + snprintf(period, sizeof(period), "%lu", (unsigned long)_prefs.gps_period); + sensors.setSettingValue("gps_period", period); sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); } #endif diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 5d7501d9985..613f2578cb4 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -368,6 +368,24 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } else { strcpy(reply, "gps toggle not found"); } + } else if (memcmp(command, "gps period ", 11) == 0) { + char* end = nullptr; + unsigned long period = strtoul(command + 11, &end, 10); + if (command[11] == '\0' || *end != '\0' || period > 86400) { + strcpy(reply, "gps period must be 0-86400 seconds"); + } else { + char period_value[11]; + snprintf(period_value, sizeof(period_value), "%lu", period); + if (_sensors->setSettingValue("gps_period", period_value) && + _sensors->setSettingValue("gps", "1")) { + _prefs->gps_period = (uint32_t)period; + _prefs->gps_enabled = 1; + savePrefs(); + strcpy(reply, "ok"); + } else { + strcpy(reply, "gps provider not found"); + } + } } else if (memcmp(command, "gps sync", 8) == 0) { LocationProvider * l = _sensors->getLocationProvider(); if (l != NULL) { diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 675df7a4be7..06778a602d5 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -61,6 +61,7 @@ class NodePrefs : public ConfigSerializer { // Gps settings uint8_t gps_enabled = 0; uint32_t gps_interval = 0; // in seconds + uint32_t gps_period = 0; // periodic acquisition interval in seconds uint8_t advert_loc_policy = 0; uint32_t discovery_mod_timestamp = 0; float adc_multiplier = 0; @@ -158,6 +159,7 @@ class NodePrefs : public ConfigSerializer { void structure() override { def("en", _parent->gps_enabled); // boolean def("int", _parent->gps_interval); // interval in seconds + def("period", _parent->gps_period); // periodic acquisition interval in seconds def("adv_loc", _parent->advert_loc_policy); } public: diff --git a/src/helpers/sensors/AirohaGpsDriver.h b/src/helpers/sensors/AirohaGpsDriver.h new file mode 100644 index 00000000000..36d5eb6b5d6 --- /dev/null +++ b/src/helpers/sensors/AirohaGpsDriver.h @@ -0,0 +1,83 @@ +#pragma once + +#include "NmeaGpsDriver.h" + +class AirohaGpsDriver : public NmeaGpsDriver { + bool waitFor(const char* prefix, uint32_t timeout_ms) { // TODO: Refactor so it doesn't block the loop ! + char sentence[100]; + size_t length = 0; + const size_t prefixLength = strlen(prefix); + const uint32_t deadline = millis() + timeout_ms; + + while ((int32_t)(millis() - deadline) < 0) { + if (available() <= 0) { + yield(); + continue; + } + + const char c = (char)read(); +#ifdef GPS_NMEA_DEBUG + Serial.print(c); +#endif + if (c == '$') length = 0; + if (length < sizeof(sentence) - 1) { + sentence[length++] = c; + sentence[length] = '\0'; + if (length >= prefixLength && strncmp(sentence, prefix, prefixLength) == 0) { + return true; + } + } + } + return false; + } + + void enterBackupSleep() { + for (uint8_t attempt = 0; attempt < 3; attempt++) { + drain(); + sendNmeaSentence("$PAIR650,0"); + if (waitFor("$PAIR001,650,0", 50)) { +#ifdef GPS_NMEA_DEBUG + Serial.printf("Airoha RTC backup sleep accepted after %u attempts\r\n", attempt + 1); +#endif + waitFor("$PAIR650,0", 50); + return; + } + } + } + +public: + AirohaGpsDriver(HardwareSerial& serial) + : NmeaGpsDriver(serial, -1, -1, -1, -1, nullptr, HIGH, LOW, 115200) {} + + void initialize() { + NmeaGpsDriver::begin(); + reset(); + } + + void begin() override { + NmeaGpsDriver::begin(); + digitalWrite(GPS_EN, HIGH); + delay(10); + digitalWrite(GPS_RTC_INT, HIGH); + delay(5); + digitalWrite(GPS_RTC_INT, LOW); + } + + void reset() override { + digitalWrite(GPS_RESET, HIGH); + delay(10); + digitalWrite(GPS_RESET, LOW); + } + + void stop() override { + digitalWrite(GPS_VRTC_EN, HIGH); + digitalWrite(GPS_RTC_INT, LOW); + enterBackupSleep(); + digitalWrite(GPS_EN, LOW); + NmeaGpsDriver::stop(); + } + + bool isEnabled() const override { + return digitalRead(GPS_EN) == HIGH; + } +}; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 0dc892c3a52..2861fac2fa4 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -160,56 +160,6 @@ static Adafruit_VL53L0X VL53L0X; static RAK12035_SoilMoisture RAK12035; #endif -#if ENV_INCLUDE_GPS && defined(RAK_BOARD) && !defined(RAK_WISMESH_TAG) -#define RAK_WISBLOCK_GPS -#endif - -#ifdef RAK_WISBLOCK_GPS -static uint32_t gpsResetPin = 0; -static bool i2cGPSFlag = false; -static bool serialGPSFlag = false; -#ifndef TELEM_RAK12500_ADDRESS -#define TELEM_RAK12500_ADDRESS 0x42 //RAK12500 Ublox GPS via i2c -#endif -#include -static SFE_UBLOX_GNSS ublox_GNSS; - -class RAK12500LocationProvider : public LocationProvider { - long _lat = 0; - long _lng = 0; - long _alt = 0; - int _sats = 0; - long _epoch = 0; - bool _fix = false; -public: - long getLatitude() override { return _lat; } - long getLongitude() override { return _lng; } - long getAltitude() override { return _alt; } - long satellitesCount() override { return _sats; } - bool isValid() override { return _fix; } - long getTimestamp() override { return _epoch; } - void sendSentence(const char * sentence) override { } - void reset() override { } - 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; - } - _epoch = ublox_GNSS.getUnixEpoch(2); - } - bool isEnabled() override { return true; } -}; - -static RAK12500LocationProvider RAK12500_provider; -#endif - // ============================================================ // I2C bus scanner // Probes every valid address and records which ones ACK. @@ -624,12 +574,8 @@ static const size_t SENSOR_TABLE_SIZE = (sizeof(SENSOR_TABLE) / sizeof(SENSOR_TA bool EnvironmentSensorManager::begin() { #if ENV_INCLUDE_GPS - #ifdef RAK_WISBLOCK_GPS - rakGPSInit(); - #else initBasicGPS(); #endif - #endif #if ENV_PIN_SDA && ENV_PIN_SCL #ifdef NRF52_PLATFORM @@ -684,9 +630,11 @@ bool EnvironmentSensorManager::begin() { bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) { next_available_channel = TELEM_CHANNEL_SELF + 1; - if (requester_permissions & TELEM_PERM_LOCATION && gps_active) { + #if ENV_INCLUDE_GPS + if (requester_permissions & TELEM_PERM_LOCATION && _location->isEnabled()) { telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } + #endif if (requester_permissions & TELEM_PERM_ENVIRONMENT) { for (int i = 0; i < _active_sensor_count; i++) { @@ -702,7 +650,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen int EnvironmentSensorManager::getNumSettings() const { int settings = 0; #if ENV_INCLUDE_GPS - if (gps_detected) settings++; // only show GPS setting if GPS is detected + if (gps_detected) settings += 2; // GPS enablement and periodic acquisition #endif return settings; } @@ -713,6 +661,9 @@ const char* EnvironmentSensorManager::getSettingName(int i) const { if (gps_detected && i == settings++) { return "gps"; } + if (gps_detected && i == settings++) { + return "gps_period"; + } #endif return NULL; } @@ -721,7 +672,13 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const { int settings = 0; #if ENV_INCLUDE_GPS if (gps_detected && i == settings++) { - return gps_active ? "1" : "0"; + return _location->isGpsOn() ? "1" : "0"; + } + if (gps_detected && i == settings++) { + static char period[11]; + snprintf(period, sizeof(period), "%lu", + (unsigned long)_location->getGpsPeriod()); + return period; } #endif return NULL; @@ -737,6 +694,13 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val } return true; } + if (gps_detected && strcmp(name, "gps_period") == 0) { + char* end = nullptr; + unsigned long period = strtoul(value, &end, 10); + if (*value == '\0' || *end != '\0' || period > 86400) return false; + _location->setGpsPeriod((uint32_t)period); + return true; + } if (strcmp(name, "gps_interval") == 0) { uint32_t interval_seconds = atoi(value); gps_update_interval_sec = interval_seconds > 0 ? interval_seconds : 1; @@ -748,157 +712,35 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val #if ENV_INCLUDE_GPS void EnvironmentSensorManager::initBasicGPS() { - - Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); - - #ifdef GPS_BAUD_RATE - Serial1.begin(GPS_BAUD_RATE); - #else - Serial1.begin(9600); - #endif - - // Try to detect if GPS is physically connected to determine if we should expose the setting - _location->begin(); - _location->reset(); + start_gps(); #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("No GPS wake/reset pin found for this board. Continuing on..."); #endif - // Give the GPS time to power up and emit its first NMEA sentence. Cold-start - // modules can take several seconds, so poll for up to ~5s instead of checking - // once after a fixed 1s delay -- returning as soon as any data appears, so - // fast modules add no boot delay (issue #2258). #ifdef ENV_SKIP_GPS_DETECT gps_detected = true; #else - gps_detected = false; - for (int i = 0; i < 20 && !gps_detected; i++) { - delay(250); - gps_detected = (Serial1.available() > 0); - } + gps_detected = _location->detect(5000); #endif if (gps_detected) { MESH_DEBUG_PRINTLN("GPS detected"); - #ifdef PERSISTANT_GPS - gps_active = true; + #if defined(PERSISTANT_GPS) return; + #else + if (_location->keepEnabledAfterDetection()) { + return; + } #endif } else { MESH_DEBUG_PRINTLN("No GPS detected"); } - _location->stop(); - gps_active = false; //Set GPS visibility off until setting is changed -} - -// gps code for rak might be moved to MicroNMEALoactionProvider -// or make a new location provider ... -#ifdef RAK_WISBLOCK_GPS -void EnvironmentSensorManager::rakGPSInit(){ - - Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); - - #ifdef GPS_BAUD_RATE - Serial1.begin(GPS_BAUD_RATE); - #else - 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{ - MESH_DEBUG_PRINTLN("No GPS found"); - gps_active = false; - gps_detected = false; - Serial1.end(); - return; - } - - #ifndef FORCE_GPS_ALIVE // for use with repeaters, until GPS toggle is implimented - //Now that GPS is found and set up, set to sleep for initial state stop_gps(); - #endif } -bool EnvironmentSensorManager::gpsIsAwake(uint8_t ioPin){ - - #if defined(ETHERNET_ENABLED) && defined(RAK_BOARD) - if (ioPin == WB_IO2) { - // WB_IO2 powers the Ethernet module on RAK baseboards. - return false; - } - #endif - - #ifndef RAK_3401 - //set initial waking state - pinMode(ioPin,OUTPUT); - digitalWrite(ioPin,LOW); - delay(500); - digitalWrite(ioPin,HIGH); - #endif - - // give gps time to power up - delay(500); - - //Try to init RAK12500 on I2C - if (ublox_GNSS.begin(Wire) == true){ - MESH_DEBUG_PRINTLN("RAK12500 GPS init correctly with pin %i",ioPin); - ublox_GNSS.setI2COutput(COM_TYPE_UBX); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_GLONASS); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_BEIDOU); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_IMES); - ublox_GNSS.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS); - ublox_GNSS.setMeasurementRate(1000); - ublox_GNSS.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); - gpsResetPin = ioPin; - i2cGPSFlag = true; - gps_active = true; - gps_detected = true; - - _location = &RAK12500_provider; - return true; - } else if (Serial1.available()) { - MESH_DEBUG_PRINTLN("Serial GPS init correctly and is turned on"); -#ifdef PIN_GPS_EN - if(PIN_GPS_EN){ - gpsResetPin = PIN_GPS_EN; - } -#endif - serialGPSFlag = true; - gps_active = true; - gps_detected = true; - return true; - } - - #ifndef RAK_3401 - pinMode(ioPin, INPUT); - #endif - MESH_DEBUG_PRINTLN("GPS did not init with this IO pin... try the next"); - return false; -} -#endif - void EnvironmentSensorManager::start_gps() { - gps_active = true; - #ifdef RAK_WISBLOCK_GPS - #ifndef RAK_3401 - pinMode(gpsResetPin, OUTPUT); - digitalWrite(gpsResetPin, HIGH); - #endif - return; - #endif - - _location->begin(); - _location->reset(); + _location->setGpsOn(true); #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("Start GPS is N/A on this board. Actual GPS state unchanged"); @@ -906,20 +748,11 @@ void EnvironmentSensorManager::start_gps() { } void EnvironmentSensorManager::stop_gps() { - gps_active = false; - #ifdef RAK_WISBLOCK_GPS - #ifndef RAK_3401 // rak3401 shouldn't turn off WB_IO2 as it powers the PA - pinMode(gpsResetPin, OUTPUT); - digitalWrite(gpsResetPin, LOW); - #endif - return; - #endif + _location->setGpsOn(false); - _location->stop(); - - #ifndef PIN_GPS_EN +#ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("Stop GPS is N/A on this board. Actual GPS state unchanged"); - #endif +#endif } #endif // ENV_INCLUDE_GPS @@ -928,30 +761,16 @@ void EnvironmentSensorManager::loop() { #if ENV_INCLUDE_GPS static unsigned long next_gps_update = 0; - if (gps_active) { - _location->loop(); - } + _location->loop(); if ((long)(millis() - next_gps_update) > 0) { - if(gps_active){ - #ifdef RAK_WISBLOCK_GPS - if ((i2cGPSFlag || serialGPSFlag) && _location->isValid()) { + if (_location->isEnabled() && _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); } - #else - 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); - } - #endif - } next_gps_update = millis() + (gps_update_interval_sec * 1000); } #endif diff --git a/src/helpers/sensors/EnvironmentSensorManager.h b/src/helpers/sensors/EnvironmentSensorManager.h index 17706cbc492..425415a1701 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.h +++ b/src/helpers/sensors/EnvironmentSensorManager.h @@ -20,7 +20,6 @@ class EnvironmentSensorManager : public SensorManager { uint8_t next_available_channel = TELEM_CHANNEL_SELF + 1; bool gps_detected = false; - bool gps_active = false; uint32_t gps_update_interval_sec = 1; #if ENV_INCLUDE_GPS @@ -28,10 +27,6 @@ class EnvironmentSensorManager : public SensorManager { void start_gps(); void stop_gps(); void initBasicGPS(); - #ifdef RAK_BOARD - void rakGPSInit(); - bool gpsIsAwake(uint8_t ioPin); - #endif #endif public: diff --git a/src/helpers/sensors/GpsDriver.h b/src/helpers/sensors/GpsDriver.h new file mode 100644 index 00000000000..b8cf4a5d569 --- /dev/null +++ b/src/helpers/sensors/GpsDriver.h @@ -0,0 +1,18 @@ +#pragma once + +#include + +class GpsDriver { +public: + virtual ~GpsDriver() = default; + + virtual void begin() = 0; + virtual void reset() = 0; + virtual void stop() = 0; + virtual bool isEnabled() const = 0; + virtual bool hasHardwareEnableState() const { return false; } + virtual bool isHardwareEnabled() const { return true; } + // Returns true when the switch explicitly enables GPS in software. + virtual bool pollGpsSwitch() { return false; } + virtual bool detect(uint32_t timeout_ms) = 0; +}; diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index bc4f1fe7648..93cecf5bedf 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -8,8 +8,11 @@ class LocationProvider { protected: bool _time_sync_needed = true; + bool _gpsOn = false; public: + virtual ~LocationProvider() = default; + virtual void syncTime() { _time_sync_needed = true; } virtual bool waitingTimeSync() { return _time_sync_needed; } virtual long getLatitude() = 0; @@ -21,6 +24,22 @@ class LocationProvider { virtual void sendSentence(const char * sentence); virtual bool waitFor(const char* prefix, uint32_t timeout_ms) { return false; } virtual void drain() { } + virtual bool detect(uint32_t timeout_ms) { return false; } + virtual bool keepEnabledAfterDetection() const { return false; } + virtual void setGpsPeriod(uint32_t seconds) { } + virtual uint32_t getGpsPeriod() const { return 0; } + virtual bool hasHardwareEnableState() const { return false; } + virtual bool isHardwareEnabled() const { return true; } + virtual bool isGpsOn() const { return _gpsOn; } + virtual void setGpsOn(bool on) { + _gpsOn = on; + if (on) { + begin(); + reset(); + } else { + stop(); + } + } virtual void reset() = 0; virtual void begin() = 0; virtual void stop() = 0; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 37ef5762c48..2e9ff9cc480 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -1,69 +1,30 @@ #pragma once +#include "NmeaGpsDriver.h" #include "LocationProvider.h" #include #include -#include #include -#ifndef GPS_EN - #ifdef PIN_GPS_EN - #define GPS_EN PIN_GPS_EN - #else - #define GPS_EN (-1) - #endif -#endif - -#ifndef GPS_EN_ACTIVE - #ifdef PIN_GPS_EN_ACTIVE - #define GPS_EN_ACTIVE PIN_GPS_EN_ACTIVE - #else - #define GPS_EN_ACTIVE HIGH - #endif -#endif - -#ifndef GPS_RESET - #ifdef PIN_GPS_RESET - #define GPS_RESET PIN_GPS_RESET - #else - #define GPS_RESET (-1) - #endif -#endif - -#ifndef GPS_RESET_ACTIVE - #ifdef PIN_GPS_RESET_ACTIVE - #define GPS_RESET_ACTIVE PIN_GPS_RESET_ACTIVE - #else - #define GPS_RESET_ACTIVE LOW - #endif +#ifndef GPS_PERIODIC_ACQUISITION_TIMEOUT_MS + #define GPS_PERIODIC_ACQUISITION_TIMEOUT_MS 120000UL #endif class MicroNMEALocationProvider : public LocationProvider { char _nmeaBuffer[100]; MicroNMEA nmea; mesh::RTCClock* _clock; - Stream* _gps_serial; - RefCountedDigitalPin* _peripher_power; - int8_t _claims = 0; - int _pin_reset; - int _pin_en; + NmeaGpsDriver* _driver; + bool _ownsDriver = false; unsigned long next_check = 0; long time_valid = 0; unsigned long _last_time_sync = 0; - static const unsigned long TIME_SYNC_INTERVAL = 1800000; // Re-sync every 30 minutes - -#ifdef GPS_L76K - void configureL76K() { - // Reapply the constellation mode at each provider start instead of - // assuming it survives a receiver power cycle. - // Mode 7 enables GPS + BeiDou + GLONASS. - delay(250); - MicroNMEA::sendSentence(*_gps_serial, "$PCAS04,7"); - delay(250); - } -#endif - // Observation-only diagnostics. These fields do not change GPS power, - // timing, reset behaviour, or parser input. + uint32_t _gpsPeriodSeconds = 0; + unsigned long _nextAcquisition = 0; + unsigned long _acquisitionStarted = 0; + bool _acquiringPeriodically = false; + static const unsigned long TIME_SYNC_INTERVAL = 1800000; + uint32_t _uart_bytes = 0; uint32_t _nmea_checksum_ok = 0; uint32_t _nmea_checksum_bad = 0; @@ -88,79 +49,121 @@ class MicroNMEALocationProvider : public LocationProvider { } } -public : - MicroNMEALocationProvider(Stream& ser, mesh::RTCClock* clock = NULL, int pin_reset = GPS_RESET, int pin_en = GPS_EN,RefCountedDigitalPin* peripher_power=NULL) : - nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _gps_serial(&ser), _peripher_power(peripher_power), _pin_reset(pin_reset), _pin_en(pin_en) { - if (_pin_reset != -1) { - pinMode(_pin_reset, OUTPUT); - digitalWrite(_pin_reset, GPS_RESET_ACTIVE); + void startPeriodicAcquisition() { + nmea.clear(); + time_valid = 0; + _driver->begin(); + _driver->reset(); + _acquiringPeriodically = true; + _acquisitionStarted = millis(); + } + + void finishPeriodicAcquisition() { + _driver->stop(); + _acquiringPeriodically = false; + _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; + } + + void updateTime() { + if (!isValid()) time_valid = 0; + + if ((long)(millis() - next_check) <= 0) return; + next_check = millis() + 1000; + + if (!_time_sync_needed && _clock != nullptr && + (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) { + _time_sync_needed = true; } - if (_pin_en != -1) { - pinMode(_pin_en, OUTPUT); - digitalWrite(_pin_en, !GPS_EN_ACTIVE); + if (_time_sync_needed && time_valid > 2 && _clock != nullptr) { + _clock->setCurrentTime(getTimestamp()); + _time_sync_needed = false; + _last_time_sync = millis(); } + if (isValid()) time_valid++; } - void claim() { - _claims++; - if (_peripher_power) _peripher_power->claim(); - } +public: + MicroNMEALocationProvider(NmeaGpsDriver& driver, mesh::RTCClock* clock = nullptr) + : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _driver(&driver) {} - void release() { - if (_claims == 0) return; // avoid negative _claims - _claims--; - if (_peripher_power) _peripher_power->release(); + // Temporary migration adapter for targets and downstream boards. New code + // should inject a GpsDriver; this constructor will be removed once targets + // have been migrated. + MicroNMEALocationProvider(HardwareSerial& serial, mesh::RTCClock* clock = nullptr, + int pin_reset = GPS_RESET, int pin_en = GPS_EN, + RefCountedDigitalPin* peripher_power = nullptr) + : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), + _driver(new NmeaGpsDriver(serial, PIN_GPS_TX, PIN_GPS_RX, pin_reset, pin_en, + peripher_power)), + _ownsDriver(true) {} + + MicroNMEALocationProvider(Stream& serial, mesh::RTCClock* clock = nullptr, + int pin_reset = GPS_RESET, int pin_en = GPS_EN, + RefCountedDigitalPin* peripher_power = nullptr) + : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), + _driver(new NmeaGpsDriver(serial, PIN_GPS_TX, PIN_GPS_RX, pin_reset, pin_en, + peripher_power)), + _ownsDriver(true) {} + + ~MicroNMEALocationProvider() override { + if (_ownsDriver) delete _driver; } void begin() override { _begin_calls++; - claim(); - if (_pin_en != -1) { - digitalWrite(_pin_en, GPS_EN_ACTIVE); - } - if (_pin_reset != -1) { - digitalWrite(_pin_reset, !GPS_RESET_ACTIVE); + _driver->begin(); + if (_gpsPeriodSeconds > 0 && _driver->isEnabled()) { + nmea.clear(); + time_valid = 0; + _acquiringPeriodically = true; + _acquisitionStarted = millis(); } } void reset() override { - if (_pin_reset != -1) { - digitalWrite(_pin_reset, GPS_RESET_ACTIVE); - delay(10); - digitalWrite(_pin_reset, !GPS_RESET_ACTIVE); - } -#ifdef GPS_L76K - // EnvironmentSensorManager calls reset() immediately after begin(). - // Configure only after that reset so the setting is not discarded. - configureL76K(); -#endif + _driver->reset(); } void stop() override { _stop_calls++; - if (_pin_en != -1) { - digitalWrite(_pin_en, !GPS_EN_ACTIVE); - } - if (_pin_reset != -1) { - digitalWrite(_pin_reset, GPS_RESET_ACTIVE); - } - release(); + _acquiringPeriodically = false; + _driver->stop(); } bool isEnabled() override { - // directly read the enable pin if present as gps can be - // activated/deactivated outside of here ... - if (_pin_en != -1) { - return digitalRead(_pin_en) == GPS_EN_ACTIVE; - } else { - return true; // no enable so must be active - } + return _driver->isEnabled(); + } + + bool hasHardwareEnableState() const override { + return _driver->hasHardwareEnableState(); + } + + bool isHardwareEnabled() const override { + return _driver->isHardwareEnabled(); + } + + bool detect(uint32_t timeout_ms) override { + return _driver->detect(timeout_ms); + } + + void setGpsPeriod(uint32_t seconds) override { + _gpsPeriodSeconds = seconds; + _nextAcquisition = millis(); + _acquiringPeriodically = false; + } + + uint32_t getGpsPeriod() const override { + return _gpsPeriodSeconds; + } + + void syncTime() override { + nmea.clear(); + LocationProvider::syncTime(); } - void syncTime() override { nmea.clear(); LocationProvider::syncTime(); } long getLatitude() override { return nmea.getLatitude(); } long getLongitude() override { return nmea.getLongitude(); } - long getAltitude() override { + long getAltitude() override { long alt = 0; nmea.getAltitude(alt); return alt; @@ -168,29 +171,31 @@ public : long satellitesCount() override { return nmea.getNumSatellites(); } bool isValid() override { return nmea.isValid(); } - long getTimestamp() override { - DateTime dt(nmea.getYear(), nmea.getMonth(),nmea.getDay(),nmea.getHour(),nmea.getMinute(),nmea.getSecond()); + long getTimestamp() override { + DateTime dt(nmea.getYear(), nmea.getMonth(), nmea.getDay(), + nmea.getHour(), nmea.getMinute(), nmea.getSecond()); return dt.unixtime(); - } - - void drain() { while (_gps_serial->available()) _gps_serial->read(); } + } + void drain() override { _driver->drain(); } const char* getSentence() const { return nmea.getSentence(); } - void sendSentence(const char *sentence) override { - nmea.sendSentence(*_gps_serial, sentence); + void sendSentence(const char* sentence) override { + _driver->sendNmeaSentence(sentence); } - bool waitFor(const char* prefix, uint32_t timeout_ms) { - size_t plen = strlen(prefix); - uint32_t timeout = millis() + timeout_ms; - while ((int32_t)(millis() - timeout) < 0) { - if (_gps_serial->available()) { - char c = _gps_serial->read(); - #ifdef GPS_NMEA_DEBUG + bool waitFor(const char* prefix, uint32_t timeout_ms) override { + const size_t prefixLength = strlen(prefix); + const uint32_t deadline = millis() + timeout_ms; + while ((int32_t)(millis() - deadline) < 0) { + if (_driver->available() > 0) { + const char c = (char)_driver->read(); +#ifdef GPS_NMEA_DEBUG Serial.print(c); - #endif - if (nmea.process(c) && strncmp(nmea.getSentence(), prefix, plen) == 0) return true; +#endif + if (nmea.process(c) && strncmp(nmea.getSentence(), prefix, prefixLength) == 0) { + return true; + } } else { yield(); } @@ -199,34 +204,35 @@ public : } void loop() override { + if (_driver->pollGpsSwitch()) _gpsOn = true; - while (_gps_serial->available()) { - char c = _gps_serial->read(); + if (_gpsOn && _gpsPeriodSeconds > 0 && !_acquiringPeriodically && + (long)(millis() - _nextAcquisition) >= 0) { + startPeriodicAcquisition(); + } + + while (_driver->available() > 0) { + const char c = (char)_driver->read(); _uart_bytes++; _last_uart_ms = millis(); _uart_seen = true; - #ifdef GPS_NMEA_DEBUG +#ifdef GPS_NMEA_DEBUG Serial.print(c); - #endif - bool parsed = nmea.process(c); +#endif + const bool parsed = nmea.process(c); - // MicroNMEA leaves the completed sentence in its buffer after the - // first line terminator. CRLF therefore counts once: CR observes the - // sentence, then LF clears the now-empty buffer. if ((c == '\0' || c == '\r' || c == '\n') && nmea.getSentence()[0] != '\0') { if (MicroNMEA::testChecksum(nmea.getSentence())) { _nmea_checksum_ok++; _last_nmea_ms = millis(); _nmea_seen = true; - // Refresh fix age only when a newly parsed GGA/RMC sentence - // carries a valid fix. Other valid NMEA sentences must not - // make an old position look fresh. - const char* message_id = nmea.getMessageID(); + const char* messageId = nmea.getMessageID(); if (parsed && isValid() && - (strcmp(message_id, "GGA") == 0 || strcmp(message_id, "RMC") == 0)) { + (strcmp(messageId, "GGA") == 0 || strcmp(messageId, "RMC") == 0)) { _last_fix_ms = millis(); _fix_seen = true; + if (_acquiringPeriodically) finishPeriodicAcquisition(); } } else { _nmea_checksum_bad++; @@ -234,25 +240,12 @@ public : } } - if (!isValid()) time_valid = 0; - - if ((long)(millis() - next_check) > 0) { - next_check = millis() + 1000; - // Re-enable time sync periodically when GPS has valid fix - if (!_time_sync_needed && _clock != NULL && (millis() - _last_time_sync) > TIME_SYNC_INTERVAL) { - _time_sync_needed = true; - } - if (_time_sync_needed && time_valid > 2) { - if (_clock != NULL) { - _clock->setCurrentTime(getTimestamp()); - _time_sync_needed = false; - _last_time_sync = millis(); - } - } - if (isValid()) { - time_valid ++; - } + if (_acquiringPeriodically && + (uint32_t)(millis() - _acquisitionStarted) >= GPS_PERIODIC_ACQUISITION_TIMEOUT_MS) { + finishPeriodicAcquisition(); } + + updateTime(); } void formatDiagnostics(char* out, size_t out_size) override { @@ -265,8 +258,6 @@ public : formatAge(nmea_age, sizeof(nmea_age), _nmea_seen, _last_nmea_ms); formatAge(fix_age, sizeof(fix_age), _fix_seen, _last_fix_ms); - // Compact enough for CommonCLI's 160-byte reply buffer, including the - // "req:" prefix added by CommonCLI. snprintf(out, out_size, "en:%u ub:%lu ua:%s ok:%lu bad:%lu na:%s sat:%ld fix:%u fa:%s bc:%lu sc:%lu", isEnabled() ? 1U : 0U, diff --git a/src/helpers/sensors/NmeaGpsDriver.h b/src/helpers/sensors/NmeaGpsDriver.h new file mode 100644 index 00000000000..f9ea5124aec --- /dev/null +++ b/src/helpers/sensors/NmeaGpsDriver.h @@ -0,0 +1,262 @@ +#pragma once + +#include "GpsDriver.h" + +#include +#include + +#ifndef PIN_GPS_TX + #define PIN_GPS_TX (-1) +#endif + +#ifndef PIN_GPS_RX + #define PIN_GPS_RX (-1) +#endif + +#ifndef GPS_EN + #ifdef PIN_GPS_EN + #define GPS_EN PIN_GPS_EN + #else + #define GPS_EN (-1) + #endif +#endif + +#ifndef GPS_EN_ACTIVE + #ifdef PIN_GPS_EN_ACTIVE + #define GPS_EN_ACTIVE PIN_GPS_EN_ACTIVE + #else + #define GPS_EN_ACTIVE HIGH + #endif +#endif + +#ifndef GPS_RESET + #ifdef PIN_GPS_RESET + #define GPS_RESET PIN_GPS_RESET + #else + #define GPS_RESET (-1) + #endif +#endif + +#ifndef GPS_RESET_ACTIVE + #ifdef PIN_GPS_RESET_ACTIVE + #define GPS_RESET_ACTIVE PIN_GPS_RESET_ACTIVE + #else + #define GPS_RESET_ACTIVE LOW + #endif +#endif + +#ifndef GPS_BAUD_RATE + #define GPS_BAUD_RATE 9600 +#endif + +#ifndef PIN_GPS_SWITCH + #define PIN_GPS_SWITCH (-1) +#endif + +#ifndef GPS_SWITCH_ACTIVE + #define GPS_SWITCH_ACTIVE HIGH +#endif + +#ifndef GPS_SWITCH_IS_HARDWARE + #define GPS_SWITCH_IS_HARDWARE 1 +#endif + +#ifndef GPS_SWITCH_TURNS_GPS_ON + #define GPS_SWITCH_TURNS_GPS_ON 0 +#endif + +class NmeaGpsDriver : public GpsDriver { + Stream* _stream = nullptr; + HardwareSerial* _hardwareSerial = nullptr; + RefCountedDigitalPin* _peripheralPower = nullptr; + int _txPin = -1; + int _rxPin = -1; + int _resetPin = -1; + int _enablePin = -1; + int _enableActive = HIGH; + int _resetActive = LOW; + uint32_t _baudRate = 9600; + int _hardwareEnablePin = -1; + int _hardwareEnableActive = HIGH; + bool _started = false; +#if !GPS_SWITCH_IS_HARDWARE + bool _requestedOn = false; + bool _switchStateKnown = false; + bool _lastSwitchEnabled = false; + unsigned long _nextSwitchPoll = 0; +#endif + + void startHardware() { + if (_stream == nullptr || _started) return; + + if (_hardwareSerial != nullptr) { +#ifdef ESP32_PLATFORM + _hardwareSerial->setPins(_txPin, _rxPin); +#endif + _hardwareSerial->begin(_baudRate); + } + + if (_peripheralPower != nullptr) _peripheralPower->claim(); + + if (_enablePin != -1) digitalWrite(_enablePin, _enableActive); + if (_resetPin != -1) digitalWrite(_resetPin, !_resetActive); + _started = true; + } + + void stopHardware() { + if (!_started) return; + + if (_enablePin != -1) digitalWrite(_enablePin, !_enableActive); + if (_resetPin != -1) digitalWrite(_resetPin, _resetActive); + + if (_peripheralPower != nullptr) _peripheralPower->release(); + _started = false; + } + +public: + NmeaGpsDriver() = default; + + NmeaGpsDriver(Stream& stream, int txPin = PIN_GPS_TX, int rxPin = PIN_GPS_RX, + int resetPin = GPS_RESET, int enablePin = GPS_EN, + RefCountedDigitalPin* peripheralPower = nullptr, + int enableActive = GPS_EN_ACTIVE, int resetActive = GPS_RESET_ACTIVE, + uint32_t baudRate = GPS_BAUD_RATE, + int hardwareEnablePin = PIN_GPS_SWITCH, + int hardwareEnableActive = GPS_SWITCH_ACTIVE) + : _stream(&stream), _peripheralPower(peripheralPower), _txPin(txPin), _rxPin(rxPin), + _resetPin(resetPin), _enablePin(enablePin), _enableActive(enableActive), + _resetActive(resetActive), _baudRate(baudRate), + _hardwareEnablePin(hardwareEnablePin), _hardwareEnableActive(hardwareEnableActive) { + if (_resetPin != -1) { + pinMode(_resetPin, OUTPUT); + digitalWrite(_resetPin, _resetActive); + } + if (_enablePin != -1) { + pinMode(_enablePin, OUTPUT); + digitalWrite(_enablePin, !_enableActive); + } + if (_hardwareEnablePin != -1) { + pinMode(_hardwareEnablePin, INPUT); + } + } + + NmeaGpsDriver(HardwareSerial& serial, int txPin = PIN_GPS_TX, int rxPin = PIN_GPS_RX, + int resetPin = GPS_RESET, int enablePin = GPS_EN, + RefCountedDigitalPin* peripheralPower = nullptr, + int enableActive = GPS_EN_ACTIVE, int resetActive = GPS_RESET_ACTIVE, + uint32_t baudRate = GPS_BAUD_RATE, + int hardwareEnablePin = PIN_GPS_SWITCH, + int hardwareEnableActive = GPS_SWITCH_ACTIVE) + : NmeaGpsDriver(static_cast(serial), txPin, rxPin, resetPin, enablePin, + peripheralPower, enableActive, resetActive, baudRate, + hardwareEnablePin, hardwareEnableActive) { + _hardwareSerial = &serial; + } + + void begin() override { +#if !GPS_SWITCH_IS_HARDWARE + _requestedOn = true; + if (!isHardwareEnabled()) return; +#endif + startHardware(); + } + + void reset() override { + if (!_started) return; + if (_resetPin != -1) { + digitalWrite(_resetPin, _resetActive); + delay(10); + digitalWrite(_resetPin, !_resetActive); + } + +#ifdef GPS_L76K + delay(250); + sendNmeaSentence("$PCAS04,7"); + delay(250); +#endif + } + + void stop() override { +#if !GPS_SWITCH_IS_HARDWARE + _requestedOn = false; +#endif + stopHardware(); + } + + bool isEnabled() const override { + return _started && isHardwareEnabled(); + } + + bool hasHardwareEnableState() const override { + return _hardwareEnablePin != -1; + } + + bool isHardwareEnabled() const override { + return _hardwareEnablePin == -1 || + digitalRead(_hardwareEnablePin) == _hardwareEnableActive; + } + + bool pollGpsSwitch() override { +#if !GPS_SWITCH_IS_HARDWARE + if (_hardwareEnablePin == -1 || + (long)(millis() - _nextSwitchPoll) < 0) return false; + + _nextSwitchPoll = millis() + 1000; + const bool switchEnabled = isHardwareEnabled(); + const bool enablesGps = GPS_SWITCH_TURNS_GPS_ON && _switchStateKnown && + !_lastSwitchEnabled && switchEnabled; + _switchStateKnown = true; + _lastSwitchEnabled = switchEnabled; + + if (enablesGps) _requestedOn = true; + if (_requestedOn && switchEnabled) { + startHardware(); + } else { + stopHardware(); + } + return enablesGps; +#else + return false; +#endif + } + + bool detect(uint32_t timeout_ms) override { + if (available() > 0) return true; + + const uint32_t deadline = millis() + timeout_ms; + while ((int32_t)(millis() - deadline) < 0) { + if (available() > 0) return true; + delay(10); + } + return false; + } + + int available() { + return _stream == nullptr ? 0 : _stream->available(); + } + + int read() { + return _stream == nullptr ? -1 : _stream->read(); + } + + void drain() { + while (available() > 0) read(); + } + + void sendNmeaSentence(const char* sentence) { + if (_stream == nullptr || sentence == nullptr || sentence[0] != '$') return; + + uint8_t checksum = 0; + const char* p = sentence + 1; + while (*p != '\0' && *p != '*') checksum ^= (uint8_t)*p++; + + _stream->print(sentence); + if (*p == '\0') { + char checksumSuffix[6]; + snprintf(checksumSuffix, sizeof(checksumSuffix), "*%02X\r\n", checksum); + _stream->print(checksumSuffix); + } else { + _stream->print("\r\n"); + } + } +}; diff --git a/src/helpers/sensors/RakGpsDriver.h b/src/helpers/sensors/RakGpsDriver.h new file mode 100644 index 00000000000..fb4b772bf41 --- /dev/null +++ b/src/helpers/sensors/RakGpsDriver.h @@ -0,0 +1,112 @@ +#pragma once + +#include "NmeaGpsDriver.h" + +#include + +class RakGpsDriver : public GpsDriver { + NmeaGpsDriver _serialDriver; + TwoWire& _wire; + SFE_UBLOX_GNSS _gnss; + uint8_t _powerPin = 0; + bool _usingRak12500 = false; + bool _detected = false; + + bool probeRak12500(uint8_t powerPin) { +#if defined(ETHERNET_ENABLED) && defined(RAK_BOARD) + if (powerPin == WB_IO2) return false; +#endif + +#ifndef RAK_3401 + pinMode(powerPin, OUTPUT); + digitalWrite(powerPin, LOW); + delay(500); + digitalWrite(powerPin, HIGH); +#endif + delay(500); + + if (_gnss.begin(_wire)) { + _gnss.setI2COutput(COM_TYPE_UBX); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_GPS); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_GALILEO); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_GLONASS); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_SBAS); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_BEIDOU); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_IMES); + _gnss.enableGNSS(true, SFE_UBLOX_GNSS_ID_QZSS); + _gnss.setMeasurementRate(1000); + _gnss.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); + _powerPin = powerPin; + _usingRak12500 = true; + return true; + } + +#ifndef RAK_3401 + pinMode(powerPin, INPUT); +#endif + return false; + } + +public: + RakGpsDriver(HardwareSerial& serial = Serial1, TwoWire& wire = Wire) + : _serialDriver(serial), _wire(wire) {} + + void begin() override { + if (_usingRak12500) { +#ifndef RAK_3401 + pinMode(_powerPin, OUTPUT); + digitalWrite(_powerPin, HIGH); +#endif + return; + } + _serialDriver.begin(); + } + + void reset() override { + if (!_usingRak12500) _serialDriver.reset(); + } + + void stop() override { + if (_usingRak12500) { +#ifndef RAK_3401 + pinMode(_powerPin, OUTPUT); + digitalWrite(_powerPin, LOW); +#endif + return; + } + _serialDriver.stop(); + } + + bool isEnabled() const override { + return _usingRak12500 ? true : _serialDriver.isEnabled(); + } + + bool detect(uint32_t) override { + if (_detected) return true; + + _serialDriver.begin(); + if (probeRak12500(WB_IO2) || probeRak12500(WB_IO4) || probeRak12500(WB_IO5)) { + _detected = true; + return true; + } + + if (_serialDriver.detect(0)) { + _detected = true; + return true; + } + + _serialDriver.stop(); + return false; + } + + bool usesRak12500() const { return _usingRak12500; } + bool isDetected() const { return _detected; } + NmeaGpsDriver& nmeaDriver() { return _serialDriver; } + + bool hasFix() { return _usingRak12500 && _gnss.getGnssFixOk(8); } + long getLatitude() { return _gnss.getLatitude(2) / 10; } + long getLongitude() { return _gnss.getLongitude(2) / 10; } + long getAltitude() { return _gnss.getAltitude(2); } + long getSatellites() { return _gnss.getSIV(2); } + long getTimestamp() { return _gnss.getUnixEpoch(2); } +}; diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h new file mode 100644 index 00000000000..c909b7c4277 --- /dev/null +++ b/src/helpers/sensors/RakLocationProvider.h @@ -0,0 +1,144 @@ +#pragma once + +#include "MicroNMEALocationProvider.h" +#include "RakGpsDriver.h" + +class RakLocationProvider : public LocationProvider { + RakGpsDriver& _driver; + MicroNMEALocationProvider _nmea; + uint32_t _gpsPeriodSeconds = 0; + unsigned long _nextAcquisition = 0; + unsigned long _acquisitionStarted = 0; + bool _acquiringPeriodically = false; + +public: + RakLocationProvider(RakGpsDriver& driver, mesh::RTCClock* clock = nullptr) + : _driver(driver), _nmea(driver.nmeaDriver(), clock) {} + + void syncTime() override { + LocationProvider::syncTime(); + if (!_driver.usesRak12500()) _nmea.syncTime(); + } + + long getLatitude() override { + return _driver.usesRak12500() ? _driver.getLatitude() : _nmea.getLatitude(); + } + + long getLongitude() override { + return _driver.usesRak12500() ? _driver.getLongitude() : _nmea.getLongitude(); + } + + long getAltitude() override { + return _driver.usesRak12500() ? _driver.getAltitude() : _nmea.getAltitude(); + } + + long satellitesCount() override { + return _driver.usesRak12500() ? _driver.getSatellites() : _nmea.satellitesCount(); + } + + bool isValid() override { + return _driver.usesRak12500() ? _driver.hasFix() : _nmea.isValid(); + } + + long getTimestamp() override { + return _driver.usesRak12500() ? _driver.getTimestamp() : _nmea.getTimestamp(); + } + + void sendSentence(const char* sentence) override { + if (!_driver.usesRak12500()) _nmea.sendSentence(sentence); + } + + bool waitFor(const char* prefix, uint32_t timeout_ms) override { + return _driver.usesRak12500() ? false : _nmea.waitFor(prefix, timeout_ms); + } + + void drain() override { + if (!_driver.usesRak12500()) _nmea.drain(); + } + + void reset() override { + _driver.reset(); + } + + void begin() override { + if (_driver.isDetected() && _driver.usesRak12500()) { + _driver.begin(); + _acquiringPeriodically = _gpsPeriodSeconds > 0; + _acquisitionStarted = millis(); + } else if (_driver.isDetected()) { + _nmea.begin(); + } else { + _driver.begin(); + } + } + + void stop() override { + _acquiringPeriodically = false; + if (_driver.usesRak12500()) { + _driver.stop(); + } else { + _nmea.stop(); + } + } + + bool detect(uint32_t timeout_ms) override { + if (!_driver.detect(timeout_ms)) return false; + + if (_driver.usesRak12500()) { + _acquiringPeriodically = _gpsPeriodSeconds > 0; + _acquisitionStarted = millis(); + } else { + _nmea.setGpsPeriod(_gpsPeriodSeconds); + _nmea.begin(); + } + return true; + } + + bool keepEnabledAfterDetection() const override { +#ifdef FORCE_GPS_ALIVE + return true; +#else + return false; +#endif + } + + void loop() override { + if (_driver.usesRak12500()) { + if (_gpsOn && _gpsPeriodSeconds > 0) { + if (!_acquiringPeriodically && + (long)(millis() - _nextAcquisition) >= 0) { + _driver.begin(); + _acquiringPeriodically = true; + _acquisitionStarted = millis(); + } + if (_acquiringPeriodically && isValid()) { + _driver.stop(); + _acquiringPeriodically = false; + _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; + } else if (_acquiringPeriodically && + (uint32_t)(millis() - _acquisitionStarted) >= + GPS_PERIODIC_ACQUISITION_TIMEOUT_MS) { + _driver.stop(); + _acquiringPeriodically = false; + _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; + } + } + } else { + _nmea.loop(); + } + } + + bool isEnabled() override { + return _driver.isEnabled(); + } + + void setGpsPeriod(uint32_t seconds) override { + _gpsPeriodSeconds = seconds; + _nextAcquisition = millis(); + if (!_driver.usesRak12500()) _nmea.setGpsPeriod(seconds); + } + + uint32_t getGpsPeriod() const override { + return _gpsPeriodSeconds; + } +}; diff --git a/variants/gat562_30s_mesh_kit/target.cpp b/variants/gat562_30s_mesh_kit/target.cpp index 8f84a7a19ef..ba81a690d9d 100644 --- a/variants/gat562_30s_mesh_kit/target.cpp +++ b/variants/gat562_30s_mesh_kit/target.cpp @@ -26,9 +26,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS - #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); - EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); + #include + RakGpsDriver gps_driver; + RakLocationProvider location(gps_driver, &rtc_clock); + EnvironmentSensorManager sensors(location); #else EnvironmentSensorManager sensors; #endif diff --git a/variants/lilygo_techo/target.cpp b/variants/lilygo_techo/target.cpp index 12b40d7ee94..394d79c5c43 100644 --- a/variants/lilygo_techo/target.cpp +++ b/variants/lilygo_techo/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -36,5 +37,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - - diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index 5309e6b22b9..2650197704a 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -25,9 +25,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS - #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); - EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); + #include + RakGpsDriver gps_driver; + RakLocationProvider location(gps_driver, &rtc_clock); + EnvironmentSensorManager sensors(location); #else EnvironmentSensorManager sensors; #endif @@ -41,4 +42,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index a41ba720751..06ccd23dbb3 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -25,9 +25,10 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS - #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); - EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); + #include + RakGpsDriver gps_driver; + RakLocationProvider location(gps_driver, &rtc_clock); + EnvironmentSensorManager sensors(location); #else EnvironmentSensorManager sensors; #endif diff --git a/variants/t1000-e/platformio.ini b/variants/t1000-e/platformio.ini index 8456dc91513..ab8b0de0b85 100644 --- a/variants/t1000-e/platformio.ini +++ b/variants/t1000-e/platformio.ini @@ -106,6 +106,7 @@ build_flags = ${t1000-e.build_flags} ; -D BLE_DEBUG_LOGGING=1 ; -D MESH_PACKET_LOGGING=1 ; -D MESH_DEBUG=1 +; -D GPS_NMEA_DEBUG -D OFFLINE_QUEUE_SIZE=256 -D DISPLAY_CLASS=NullDisplayDriver -D PIN_BUZZER=25 diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 70a64359fd1..0d7710b3a5c 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -1,8 +1,8 @@ #include #include "t1000e_sensors.h" #include "target.h" +#include #include -#include T1000eBoard board; @@ -11,8 +11,9 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); -T1000SensorManager sensors = T1000SensorManager(nmea); +AirohaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); +T1000SensorManager sensors(nmea, gps_driver); #ifdef DISPLAY_CLASS NullDisplayDriver display; @@ -82,28 +83,17 @@ mesh::LocalIdentity radio_new_identity() { void T1000SensorManager::start_gps() { gps_active = true; - digitalWrite(GPS_EN, HIGH); - delay(10); - digitalWrite(GPS_RTC_INT, HIGH); - delay(5); - digitalWrite(GPS_RTC_INT, LOW); + _nmea->begin(); } void T1000SensorManager::stop_gps() { gps_active = false; - 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 - digitalWrite(GPS_EN, LOW); + _nmea->stop(); } bool T1000SensorManager::begin() { - // init GPS - Serial1.begin(115200); - digitalWrite(GPS_RESET, HIGH); - delay(10); - digitalWrite(GPS_RESET, LOW); + _gps_driver->initialize(); return true; } diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index db003cc5fa0..4a1a2bb731f 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -12,15 +12,19 @@ #include "NullDisplayDriver.h" #endif +class AirohaGpsDriver; + class T1000SensorManager: public SensorManager { bool gps_active = false; LocationProvider * _nmea; + AirohaGpsDriver* _gps_driver; void start_gps(); void sleep_gps(); void stop_gps(); public: - T1000SensorManager(LocationProvider &nmea): _nmea(&nmea) { } + T1000SensorManager(LocationProvider &nmea, AirohaGpsDriver& gps_driver) + : _nmea(&nmea), _gps_driver(&gps_driver) { } bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP& telemetry) override; void loop() override; diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index c430cee00df..78f5a88e6f7 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -2,6 +2,7 @@ #include "target.h" #include #include +#include ThinkNodeM1Board board; @@ -11,7 +12,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(gps_driver, &rtc_clock); ThinkNodeM1SensorManager sensors = ThinkNodeM1SensorManager(nmea); #ifdef DISPLAY_CLASS @@ -31,34 +33,15 @@ mesh::LocalIdentity radio_new_identity() { } void ThinkNodeM1SensorManager::start_gps() { - if (!gps_active) { - gps_active = true; - _location->begin(); - } + _location->setGpsOn(true); } void ThinkNodeM1SensorManager::stop_gps() { - if (gps_active) { - gps_active = false; - _location->stop(); - } + _location->setGpsOn(false); } bool ThinkNodeM1SensorManager::begin() { - Serial1.begin(9600); - - // Initialize GPS switch pin - pinMode(PIN_GPS_SWITCH, INPUT); - last_gps_switch_state = digitalRead(PIN_GPS_SWITCH); - - // Initialize GPS power pin - pinMode(GPS_EN, OUTPUT); - - // Check initial switch state to determine if GPS should be active - if (last_gps_switch_state == HIGH) { // Switch is HIGH when ON - start_gps(); - } - + start_gps(); return true; } @@ -71,36 +54,11 @@ bool ThinkNodeM1SensorManager::querySensors(uint8_t requester_permissions, Cayen void ThinkNodeM1SensorManager::loop() { static long next_gps_update = 0; - static long last_switch_check = 0; - - // Check GPS switch state every second - if (millis() - last_switch_check > 1000) { - bool current_switch_state = digitalRead(PIN_GPS_SWITCH); - - // Detect switch state change - if (current_switch_state != last_gps_switch_state) { - last_gps_switch_state = current_switch_state; - - if (current_switch_state == HIGH) { // Switch is ON - MESH_DEBUG_PRINTLN("GPS switch ON"); - start_gps(); - } else { // Switch is OFF - MESH_DEBUG_PRINTLN("GPS switch OFF"); - stop_gps(); - } - } - - last_switch_check = millis(); - } - - if (!gps_active) { - return; // GPS is not active, skip further processing - } _location->loop(); if (millis() > next_gps_update) { - if (_location->isValid()) { + if (_location->isEnabled() && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; @@ -120,7 +78,7 @@ const char* ThinkNodeM1SensorManager::getSettingName(int i) const { const char* ThinkNodeM1SensorManager::getSettingValue(int i) const { if (i == 0) { - return gps_active ? "1" : "0"; + return _location->isGpsOn() ? "1" : "0"; } return NULL; } @@ -136,4 +94,3 @@ bool ThinkNodeM1SensorManager::setSettingValue(const char* name, const char* val } return false; // not supported } - diff --git a/variants/thinknode_m1/target.h b/variants/thinknode_m1/target.h index d2d6aed1d50..9c8a6e80f4a 100644 --- a/variants/thinknode_m1/target.h +++ b/variants/thinknode_m1/target.h @@ -14,8 +14,6 @@ #endif class ThinkNodeM1SensorManager : public SensorManager { - bool gps_active = false; - bool last_gps_switch_state = false; LocationProvider* _location; void start_gps(); @@ -45,4 +43,3 @@ extern ThinkNodeM1SensorManager sensors; bool radio_init(); mesh::LocalIdentity radio_new_identity(); - diff --git a/variants/thinknode_m1/variant.h b/variants/thinknode_m1/variant.h index aecf197e62d..3e3f03a74e6 100644 --- a/variants/thinknode_m1/variant.h +++ b/variants/thinknode_m1/variant.h @@ -146,3 +146,5 @@ extern const int SCK; #define PIN_GPS_PPS (36) #define PIN_GPS_STANDBY (34) #define PIN_GPS_SWITCH (33) +#define GPS_SWITCH_IS_HARDWARE (0) +#define GPS_SWITCH_TURNS_GPS_ON (1) diff --git a/variants/thinknode_m5/target.cpp b/variants/thinknode_m5/target.cpp index ec524df749f..0ed9a0d0c5a 100644 --- a/variants/thinknode_m5/target.cpp +++ b/variants/thinknode_m5/target.cpp @@ -17,7 +17,8 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea = MicroNMEALocationProvider(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -46,5 +47,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - - diff --git a/variants/thinknode_m5/variant.h b/variants/thinknode_m5/variant.h index 9b82416b0dd..fefb45d1da4 100644 --- a/variants/thinknode_m5/variant.h +++ b/variants/thinknode_m5/variant.h @@ -25,4 +25,5 @@ #define PIN_GPS_RESET (13) #define PIN_GPS_RX (20) #define PIN_GPS_TX (19) -#define PIN_GPS_SWITCH (10) \ No newline at end of file +#define PIN_GPS_SWITCH (10) +#define GPS_SWITCH_IS_HARDWARE (1) \ No newline at end of file From fe6a47d0c973f2105ab480f24993269aa48cbcd3 Mon Sep 17 00:00:00 2001 From: Florent Date: Mon, 2 Nov 2026 11:09:16 -0400 Subject: [PATCH 02/23] gps: move the gps CLI to GpsPrefs and convert all targets --- examples/companion_radio/MyMesh.cpp | 5 + examples/companion_radio/MyMesh.h | 11 +- examples/companion_radio/NodePrefs.h | 14 ++- examples/companion_radio/ui-new/UITask.cpp | 8 +- examples/companion_radio/ui-orig/UITask.cpp | 5 +- examples/companion_radio/ui-tiny/UITask.cpp | 8 +- examples/simple_repeater/MyMesh.h | 12 +- examples/simple_room_server/MyMesh.h | 12 +- examples/simple_sensor/SensorMesh.h | 10 +- src/helpers/CommonCLI.cpp | 76 +----------- src/helpers/CommonCLI.h | 12 +- src/helpers/CommonGpsPrefs.cpp | 110 ++++++++++++++++++ src/helpers/CommonGpsPrefs.h | 26 +++++ .../sensors/EnvironmentSensorManager.cpp | 18 +-- variants/gat562_mesh_evb_pro/target.cpp | 3 +- variants/gat562_mesh_tracker_pro/target.cpp | 3 +- variants/heltec_mesh_solar/target.cpp | 15 +-- variants/heltec_mesh_solar/target.h | 1 - variants/heltec_rc32/target.cpp | 4 +- variants/heltec_t096/target.cpp | 4 +- variants/heltec_t1/target.cpp | 4 +- variants/heltec_t114/target.cpp | 3 +- variants/heltec_tower_v2/target.cpp | 3 +- variants/heltec_tracker/target.cpp | 30 +++-- variants/heltec_tracker/target.h | 1 - variants/heltec_tracker_v2/target.cpp | 5 +- variants/heltec_v3/target.cpp | 4 +- variants/heltec_v4/target.cpp | 4 +- variants/heltec_v4_r8/target.cpp | 4 +- variants/keepteen_lt1/target.cpp | 4 +- variants/lilygo_t_impulse_plus/target.cpp | 4 +- variants/lilygo_tbeam_1w/target.cpp | 4 +- variants/lilygo_tbeam_SX1262/target.cpp | 4 +- variants/lilygo_tbeam_SX1276/target.cpp | 3 +- .../lilygo_tbeam_supreme_SX1262/target.cpp | 4 +- variants/lilygo_tdeck/target.cpp | 3 +- variants/lilygo_techo_card/target.cpp | 3 +- variants/lilygo_techo_lite/target.cpp | 4 +- variants/meshadventurer/target.cpp | 19 ++- variants/meshadventurer/target.h | 3 +- variants/meshnology_w12/target.cpp | 4 +- variants/meshtracker_x1/target.cpp | 27 ++--- variants/meshtracker_x1/target.h | 2 - variants/minewsemi_me25ls01/target.cpp | 3 +- variants/muzi_base/target.cpp | 22 +--- variants/muzi_base/target.h | 6 +- variants/muzi_base/variant.h | 2 + variants/muziworks_r1_neo/target.cpp | 3 +- variants/nano_g2_ultra/NanoG2UltraGpsDriver.h | 31 +++++ variants/nano_g2_ultra/target.cpp | 35 ++---- variants/nano_g2_ultra/target.h | 8 +- variants/promicro/target.cpp | 4 +- variants/rak3112/target.cpp | 4 +- variants/rak_wismesh_tag/target.cpp | 4 +- variants/sensecap_indicator-espnow/target.cpp | 3 +- variants/sensecap_solar/target.cpp | 4 +- variants/station_g2/target.cpp | 4 +- variants/station_g3_esp32/target.cpp | 3 +- variants/t1000-e/target.cpp | 10 +- variants/t1000-e/target.h | 1 - variants/thinknode_m3/target.cpp | 3 +- variants/thinknode_m4/target.cpp | 3 +- variants/thinknode_m6/target.cpp | 4 +- variants/thinknode_m7/target.cpp | 4 +- variants/thinknode_m8/target.cpp | 3 +- variants/thinknode_m9/target.cpp | 3 +- variants/wio-tracker-l1-1w/target.cpp | 4 +- variants/wio-tracker-l1/target.cpp | 4 +- variants/xiao_c3/target.cpp | 4 +- 69 files changed, 381 insertions(+), 301 deletions(-) create mode 100644 src/helpers/CommonGpsPrefs.cpp create mode 100644 src/helpers/CommonGpsPrefs.h create mode 100644 variants/nano_g2_ultra/NanoG2UltraGpsDriver.h diff --git a/examples/companion_radio/MyMesh.cpp b/examples/companion_radio/MyMesh.cpp index 755c56d14c0..d12c7b8a07d 100644 --- a/examples/companion_radio/MyMesh.cpp +++ b/examples/companion_radio/MyMesh.cpp @@ -2093,6 +2093,11 @@ bool MyMesh::handleCommand(const char* command, uint32_t sender_timestamp, char* return true; } + if (_prefs.getGpsPrefs()->handleCommand(command, sensors, reply)) { + if (_prefs.getGpsPrefs()->isDirty()) { savePrefs(); } + return true; + } + // hook for variant-specific CLI processing if (board.handleCommand(command, sender_timestamp, reply)) { if (_prefs.isDirty()) { savePrefs(); } diff --git a/examples/companion_radio/MyMesh.h b/examples/companion_radio/MyMesh.h index 944790df922..846708b5326 100644 --- a/examples/companion_radio/MyMesh.h +++ b/examples/companion_radio/MyMesh.h @@ -203,12 +203,13 @@ class MyMesh : public BaseChatMesh, public DataStoreHost { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - sensors.setSettingValue("gps", _prefs.gps_enabled ? "1" : "0"); - if (_prefs.gps_interval > 0) { - char interval_str[12]; // Max: 24 hours = 86400 seconds (5 digits + null) - sprintf(interval_str, "%u", _prefs.gps_interval); - sensors.setSettingValue("gps_interval", interval_str); + if (_prefs.gps_interval > 0 && + sensors.getSettingByKey("gps_interval") != nullptr) { + char interval[11]; + snprintf(interval, sizeof(interval), "%lu", (unsigned long)_prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval); } + _prefs.getGpsPrefs()->apply(sensors); } #endif diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index c5d1df04a42..a54eca752b6 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -2,6 +2,7 @@ #include // For uint8_t, uint32_t #include #include +#include #include #define TELEM_MODE_DENY 0 @@ -35,6 +36,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file uint8_t vibe_quiet = 0; uint8_t gps_enabled = 0; // GPS enabled flag (0=disabled, 1=enabled) uint32_t gps_interval = 0; // GPS read interval in seconds + uint32_t gps_period = 0; // periodic acquisition 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) uint8_t radio_fem_rxgain = 0; // external LoRa FEM RX gain (LNA) @@ -151,16 +153,21 @@ class NodePrefs : public ConfigSerializer { // persisted to file }; RadioPrefs radio; - class GPSPrefs : public ConfigSerializer { // COPIED from CommonCLI (for now) + class GPSPrefs : public CommonGpsPrefs { NodePrefs* _parent; protected: void structure() override { def("en", _parent->gps_enabled); // boolean def("int", _parent->gps_interval); // interval in seconds + def("period", _parent->gps_period); // periodic acquisition interval in seconds def("adv_loc", _parent->advert_loc_policy); } public: GPSPrefs(NodePrefs* parent) : _parent(parent) { } + bool isGpsEnabled() const override { return _parent->gps_enabled; } + void setGpsEnabled(bool enabled) override { _parent->gps_enabled = enabled; markDirty(); } + uint32_t getGpsPeriod() const override { return _parent->gps_period; } + void setGpsPeriod(uint32_t seconds) override { _parent->gps_period = seconds; markDirty(); } }; GPSPrefs gps; @@ -275,8 +282,9 @@ class NodePrefs : public ConfigSerializer { // persisted to file void setRepeatEn(bool en) { repeat.disable_fwd = en ? 0 : 1; } CommonRadioPrefs* getRadioPrefs() { return &radio; } + CommonGpsPrefs* getGpsPrefs() { return &gps; } KeyValueStore* getCustom() { return &custom; } - bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || custom.isDirty(); } - void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); custom.clearDirty(); } + bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || gps.isDirty() || custom.isDirty(); } + void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); gps.clearDirty(); custom.clearDirty(); } }; diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index cfe34cf2a91..a1400669a64 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -1182,16 +1182,14 @@ void UITask::toggleGPS() { for (int i = 0; i < num; i++) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { - _sensors->setSettingValue("gps", "0"); - _node_prefs->gps_enabled = 0; + _node_prefs->getGpsPrefs()->setGpsEnabled(*_sensors, false); notify(UIEventType::ack); } else { - _sensors->setSettingValue("gps", "1"); - _node_prefs->gps_enabled = 1; + _node_prefs->getGpsPrefs()->setGpsEnabled(*_sensors, true); notify(UIEventType::ack); } the_mesh.savePrefs(); - showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800); + showAlert(_node_prefs->getGpsPrefs()->isGpsEnabled() ? "GPS: Enabled" : "GPS: Disabled", 800); _next_refresh = 0; break; } diff --git a/examples/companion_radio/ui-orig/UITask.cpp b/examples/companion_radio/ui-orig/UITask.cpp index 8ae57c7a99d..c2fd80fb2a1 100644 --- a/examples/companion_radio/ui-orig/UITask.cpp +++ b/examples/companion_radio/ui-orig/UITask.cpp @@ -609,14 +609,15 @@ void UITask::handleButtonQuadruplePress() { for (int i = 0; i < num; i++) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { - _sensors->setSettingValue("gps", "0"); + _node_prefs->getGpsPrefs()->setGpsEnabled(*_sensors, false); notify(UIEventType::ack); sprintf(_alert, "GPS: Disabled"); } else { - _sensors->setSettingValue("gps", "1"); + _node_prefs->getGpsPrefs()->setGpsEnabled(*_sensors, true); notify(UIEventType::ack); sprintf(_alert, "GPS: Enabled"); } + the_mesh.savePrefs(); break; } } diff --git a/examples/companion_radio/ui-tiny/UITask.cpp b/examples/companion_radio/ui-tiny/UITask.cpp index 035340a8fc2..1b48cca84f9 100644 --- a/examples/companion_radio/ui-tiny/UITask.cpp +++ b/examples/companion_radio/ui-tiny/UITask.cpp @@ -833,16 +833,14 @@ void UITask::toggleGPS() { for (int i = 0; i < num; i++) { if (strcmp(_sensors->getSettingName(i), "gps") == 0) { if (strcmp(_sensors->getSettingValue(i), "1") == 0) { - _sensors->setSettingValue("gps", "0"); - _node_prefs->gps_enabled = 0; + _node_prefs->getGpsPrefs()->setGpsEnabled(*_sensors, false); notify(UIEventType::ack); } else { - _sensors->setSettingValue("gps", "1"); - _node_prefs->gps_enabled = 1; + _node_prefs->getGpsPrefs()->setGpsEnabled(*_sensors, true); notify(UIEventType::ack); } the_mesh.savePrefs(); - showAlert(_node_prefs->gps_enabled ? "GPS: Enabled" : "GPS: Disabled", 800); + showAlert(_node_prefs->getGpsPrefs()->isGpsEnabled() ? "GPS: Enabled" : "GPS: Disabled", 800); _next_refresh = 0; break; } diff --git a/examples/simple_repeater/MyMesh.h b/examples/simple_repeater/MyMesh.h index 1d975a94d34..7e4cfd4f22e 100644 --- a/examples/simple_repeater/MyMesh.h +++ b/examples/simple_repeater/MyMesh.h @@ -159,10 +159,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - char period[11]; - snprintf(period, sizeof(period), "%lu", (unsigned long)_prefs.gps_period); - sensors.setSettingValue("gps_period", period); - sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); + if (sensors.getSettingByKey("gps_interval") != nullptr) { + // gps_interval was introduced only for EnvironmentSensorManager + // and has little interest ... kept only as a legacy + char interval[11]; + snprintf(interval, sizeof(interval), "%lu", (unsigned long)_prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval); + } + _prefs.getGpsPrefs()->apply(sensors); } #endif diff --git a/examples/simple_room_server/MyMesh.h b/examples/simple_room_server/MyMesh.h index 5f8de73a3d8..11367b12465 100644 --- a/examples/simple_room_server/MyMesh.h +++ b/examples/simple_room_server/MyMesh.h @@ -168,10 +168,14 @@ class MyMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - char period[11]; - snprintf(period, sizeof(period), "%lu", (unsigned long)_prefs.gps_period); - sensors.setSettingValue("gps_period", period); - sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); + if (sensors.getSettingByKey("gps_interval") != nullptr) { + // gps_interval was introduced only for EnvironmentSensorManager + // and has little interest ... kept only as a legacy + char interval[11]; + snprintf(interval, sizeof(interval), "%lu", (unsigned long)_prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval); + } + _prefs.getGpsPrefs()->apply(sensors); } #endif diff --git a/examples/simple_sensor/SensorMesh.h b/examples/simple_sensor/SensorMesh.h index b93939de3f5..e6746d470c7 100644 --- a/examples/simple_sensor/SensorMesh.h +++ b/examples/simple_sensor/SensorMesh.h @@ -173,10 +173,12 @@ class SensorMesh : public mesh::Mesh, public CommonCLICallbacks { #if ENV_INCLUDE_GPS == 1 void applyGpsPrefs() { - char period[11]; - snprintf(period, sizeof(period), "%lu", (unsigned long)_prefs.gps_period); - sensors.setSettingValue("gps_period", period); - sensors.setSettingValue("gps", _prefs.gps_enabled?"1":"0"); + if (sensors.getSettingByKey("gps_interval") != nullptr) { + char interval[11]; + snprintf(interval, sizeof(interval), "%lu", (unsigned long)_prefs.gps_interval); + sensors.setSettingValue("gps_interval", interval); + } + _prefs.getGpsPrefs()->apply(sensors); } #endif }; diff --git a/src/helpers/CommonCLI.cpp b/src/helpers/CommonCLI.cpp index 613f2578cb4..ed1b519983b 100644 --- a/src/helpers/CommonCLI.cpp +++ b/src/helpers/CommonCLI.cpp @@ -349,65 +349,11 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re *(dp-1) = 0; // remove last CR } } + } else if (_prefs->getGpsPrefs()->handleCommand(command, *_sensors, reply)) { + if (_prefs->getGpsPrefs()->isDirty()) savePrefs(); } else if (memcmp(command, "region", 6) == 0) { handleRegionCmd(command, reply); #if ENV_INCLUDE_GPS == 1 - } else if (memcmp(command, "gps on", 6) == 0) { - if (_sensors->setSettingValue("gps", "1")) { - _prefs->gps_enabled = 1; - savePrefs(); - strcpy(reply, "ok"); - } else { - strcpy(reply, "gps toggle not found"); - } - } else if (memcmp(command, "gps off", 7) == 0) { - if (_sensors->setSettingValue("gps", "0")) { - _prefs->gps_enabled = 0; - savePrefs(); - strcpy(reply, "ok"); - } else { - strcpy(reply, "gps toggle not found"); - } - } else if (memcmp(command, "gps period ", 11) == 0) { - char* end = nullptr; - unsigned long period = strtoul(command + 11, &end, 10); - if (command[11] == '\0' || *end != '\0' || period > 86400) { - strcpy(reply, "gps period must be 0-86400 seconds"); - } else { - char period_value[11]; - snprintf(period_value, sizeof(period_value), "%lu", period); - if (_sensors->setSettingValue("gps_period", period_value) && - _sensors->setSettingValue("gps", "1")) { - _prefs->gps_period = (uint32_t)period; - _prefs->gps_enabled = 1; - savePrefs(); - strcpy(reply, "ok"); - } else { - strcpy(reply, "gps provider not found"); - } - } - } else if (memcmp(command, "gps sync", 8) == 0) { - LocationProvider * l = _sensors->getLocationProvider(); - if (l != NULL) { - l->syncTime(); - strcpy(reply, "ok"); - } else { - strcpy(reply, "gps provider not found"); - } - } else if (strcmp(command, "gps diag") == 0) { - LocationProvider * l = _sensors->getLocationProvider(); - if (l != NULL) { - const char* setting = _sensors->getSettingByKey("gps"); - bool requested = setting != NULL && strcmp(setting, "1") == 0; - int prefix_len = snprintf(reply, 160, "req:%u ", requested ? 1U : 0U); - if (prefix_len < 0 || prefix_len >= 160) { - reply[159] = 0; - } else { - l->formatDiagnostics(reply + prefix_len, 160 - prefix_len); - } - } else { - strcpy(reply, "gps provider not found"); - } } else if (memcmp(command, "gps setloc", 10) == 0) { _prefs->node_lat = _sensors->node_lat; _prefs->node_lon = _sensors->node_lon; @@ -443,24 +389,6 @@ void CommonCLI::handleCommand(uint32_t sender_timestamp, char* command, char* re } else { strcpy(reply, "error"); } - } else if (memcmp(command, "gps", 3) == 0) { - LocationProvider * l = _sensors->getLocationProvider(); - if (l != NULL) { - bool enabled = l->isEnabled(); // is EN pin on ? - bool fix = l->isValid(); // has fix ? - int sats = l->satellitesCount(); - bool active = !strcmp(_sensors->getSettingByKey("gps"), "1"); - if (enabled) { - sprintf(reply, "on, %s, %s, %d sats", - active?"active":"deactivated", - fix?"fix":"no fix", - sats); - } else { - strcpy(reply, "off"); - } - } else { - strcpy(reply, "Can't find GPS"); - } #endif } else if (memcmp(command, "powersaving on", 14) == 0) { #if defined(NRF52_PLATFORM) diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index 06778a602d5..33e27d2dbfd 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #if defined(WITH_RS232_BRIDGE) || defined(WITH_ESPNOW_BRIDGE) @@ -153,7 +154,7 @@ class NodePrefs : public ConfigSerializer { }; BridgePrefs bridge; - class GPSPrefs : public ConfigSerializer { + class GPSPrefs : public CommonGpsPrefs { NodePrefs* _parent; protected: void structure() override { @@ -164,6 +165,10 @@ class NodePrefs : public ConfigSerializer { } public: GPSPrefs(NodePrefs* parent) : _parent(parent) { } + bool isGpsEnabled() const override { return _parent->gps_enabled; } + void setGpsEnabled(bool enabled) override { _parent->gps_enabled = enabled; markDirty(); } + uint32_t getGpsPeriod() const override { return _parent->gps_period; } + void setGpsPeriod(uint32_t seconds) override { _parent->gps_period = seconds; markDirty(); } }; GPSPrefs gps; @@ -236,10 +241,11 @@ class NodePrefs : public ConfigSerializer { } CommonRadioPrefs* getRadioPrefs() { return &radio; } + CommonGpsPrefs* getGpsPrefs() { return &gps; } KeyValueStore* getCustom() { return &custom; } - bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || custom.isDirty(); } - void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); custom.clearDirty(); } + bool isDirty() const override { return ConfigSerializer::isDirty() || radio.isDirty() || gps.isDirty() || custom.isDirty(); } + void clearDirty() override { ConfigSerializer::clearDirty(); radio.clearDirty(); gps.clearDirty(); custom.clearDirty(); } }; class CommonCLICallbacks { diff --git a/src/helpers/CommonGpsPrefs.cpp b/src/helpers/CommonGpsPrefs.cpp new file mode 100644 index 00000000000..fe29c0c0001 --- /dev/null +++ b/src/helpers/CommonGpsPrefs.cpp @@ -0,0 +1,110 @@ +#include "CommonGpsPrefs.h" +#include + +bool CommonGpsPrefs::setGpsEnabled(SensorManager& sensors, bool enabled) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) return false; + location->setGpsOn(enabled); + setGpsEnabled(enabled); + return true; +} + +bool CommonGpsPrefs::setGpsPeriod(SensorManager& sensors, uint32_t seconds) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) return false; + location->setGpsPeriod(seconds); + setGpsPeriod(seconds); + return true; +} + +bool CommonGpsPrefs::apply(SensorManager& sensors) const { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) return false; + location->setGpsPeriod(getGpsPeriod()); + location->setGpsOn(isGpsEnabled()); + return true; +} + +bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, char* reply) { + if (strcmp(command, "gps") == 0) { + LocationProvider* location = sensors.getLocationProvider(); + char mode[32]; + if (getGpsPeriod() == 0) { + strcpy(mode, "tracking"); + } else { + snprintf(mode, sizeof(mode), "periodic (%lus)", + (unsigned long)getGpsPeriod()); + } + if (location == nullptr) { + strcpy(reply, "gps provider not found"); + } else if (location->isEnabled()) { + sprintf(reply, "on, %s, %s, %d sats, mode: %s", + isGpsEnabled() ? "active" : "deactivated", + location->isValid() ? "fix" : "no fix", + location->satellitesCount(), mode); + } else { + snprintf(reply, 160, "off, mode: %s", mode); + } + return true; + } + if (memcmp(command, "gps on", 6) == 0) { + if (!setGpsEnabled(sensors, true)) { + strcpy(reply, "gps toggle not found"); + } else { + strcpy(reply, "ok"); + } + return true; + } + if (memcmp(command, "gps off", 7) == 0) { + if (!setGpsEnabled(sensors, false)) { + strcpy(reply, "gps toggle not found"); + } else { + strcpy(reply, "ok"); + } + return true; + } + if (strcmp(command, "gps period") == 0) { + if (getGpsPeriod() == 0) { + strcpy(reply, "> 0 (continuous)"); + } else { + sprintf(reply, "> %lu", (unsigned long)getGpsPeriod()); + } + return true; + } + if (memcmp(command, "gps period ", 11) == 0) { + char* end = nullptr; + unsigned long period = strtoul(command + 11, &end, 10); + if (command[11] == '\0' || *end != '\0' || period > 86400) { + strcpy(reply, "gps period must be 0-86400 seconds"); + } else { + if (!setGpsPeriod(sensors, (uint32_t)period) || + !setGpsEnabled(sensors, true)) { + strcpy(reply, "gps provider not found"); + } else { + strcpy(reply, "ok"); + } + } + return true; + } + if (memcmp(command, "gps sync", 8) == 0) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) { + strcpy(reply, "gps provider not found"); + } else { + location->syncTime(); + strcpy(reply, "ok"); + } + return true; + } + if (strcmp(command, "gps diag") == 0) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) { + strcpy(reply, "gps provider not found"); + } else { + int prefix = snprintf(reply, 160, "req:%u ", isGpsEnabled() ? 1U : 0U); + if (prefix >= 0 && prefix < 160) location->formatDiagnostics(reply + prefix, 160 - prefix); + } + return true; + } + return false; +} diff --git a/src/helpers/CommonGpsPrefs.h b/src/helpers/CommonGpsPrefs.h new file mode 100644 index 00000000000..25cce1b11e9 --- /dev/null +++ b/src/helpers/CommonGpsPrefs.h @@ -0,0 +1,26 @@ +#pragma once + +#include "ConfigSerializer.h" +#include "SensorManager.h" + +class CommonGpsPrefs : public ConfigSerializer { + bool _is_dirty = false; + +protected: + CommonGpsPrefs() = default; + +public: + void markDirty() { _is_dirty = true; } + void clearDirty() { _is_dirty = false; } + bool isDirty() const { return _is_dirty; } + + virtual bool isGpsEnabled() const = 0; + virtual void setGpsEnabled(bool enabled) = 0; + virtual uint32_t getGpsPeriod() const = 0; + virtual void setGpsPeriod(uint32_t seconds) = 0; + + bool setGpsEnabled(SensorManager& sensors, bool enabled); + bool setGpsPeriod(SensorManager& sensors, uint32_t seconds); + bool apply(SensorManager& sensors) const; + bool handleCommand(const char* command, SensorManager& sensors, char* reply); +}; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2861fac2fa4..2cf8b66b14b 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -650,7 +650,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen int EnvironmentSensorManager::getNumSettings() const { int settings = 0; #if ENV_INCLUDE_GPS - if (gps_detected) settings += 2; // GPS enablement and periodic acquisition + if (gps_detected) settings += 1; // GPS enablement #endif return settings; } @@ -661,9 +661,6 @@ const char* EnvironmentSensorManager::getSettingName(int i) const { if (gps_detected && i == settings++) { return "gps"; } - if (gps_detected && i == settings++) { - return "gps_period"; - } #endif return NULL; } @@ -674,12 +671,6 @@ const char* EnvironmentSensorManager::getSettingValue(int i) const { if (gps_detected && i == settings++) { return _location->isGpsOn() ? "1" : "0"; } - if (gps_detected && i == settings++) { - static char period[11]; - snprintf(period, sizeof(period), "%lu", - (unsigned long)_location->getGpsPeriod()); - return period; - } #endif return NULL; } @@ -694,13 +685,6 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val } return true; } - if (gps_detected && strcmp(name, "gps_period") == 0) { - char* end = nullptr; - unsigned long period = strtoul(value, &end, 10); - if (*value == '\0' || *end != '\0' || period > 86400) return false; - _location->setGpsPeriod((uint32_t)period); - return true; - } if (strcmp(name, "gps_interval") == 0) { uint32_t interval_seconds = atoi(value); gps_update_interval_sec = interval_seconds > 0 ? interval_seconds : 1; diff --git a/variants/gat562_mesh_evb_pro/target.cpp b/variants/gat562_mesh_evb_pro/target.cpp index 8dc203de979..0aac297a027 100644 --- a/variants/gat562_mesh_evb_pro/target.cpp +++ b/variants/gat562_mesh_evb_pro/target.cpp @@ -26,7 +26,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/gat562_mesh_tracker_pro/target.cpp b/variants/gat562_mesh_tracker_pro/target.cpp index d16b453810b..225aa161964 100644 --- a/variants/gat562_mesh_tracker_pro/target.cpp +++ b/variants/gat562_mesh_tracker_pro/target.cpp @@ -27,7 +27,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index 94e2684c12a..9468f8fc4d8 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -11,7 +11,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); SolarSensorManager sensors = SolarSensorManager(nmea); SolarExternalWatchdog external_watchdog; @@ -30,17 +31,11 @@ mesh::LocalIdentity radio_new_identity() { } void SolarSensorManager::start_gps() { - if (!gps_active) { - gps_active = true; - _location->begin(); - } + _location->setGpsOn(true); } void SolarSensorManager::stop_gps() { - if (gps_active) { - gps_active = false; - _location->stop(); - } + _location->setGpsOn(false); } bool SolarSensorManager::begin() { @@ -91,7 +86,7 @@ const char* SolarSensorManager::getSettingName(int i) const { const char* SolarSensorManager::getSettingValue(int i) const { if (gps_detected && i == 0) { - return gps_active ? "1" : "0"; + return _location->isGpsOn() ? "1" : "0"; } return NULL; } diff --git a/variants/heltec_mesh_solar/target.h b/variants/heltec_mesh_solar/target.h index 8e13a9601c5..97550a00530 100644 --- a/variants/heltec_mesh_solar/target.h +++ b/variants/heltec_mesh_solar/target.h @@ -14,7 +14,6 @@ #endif class SolarSensorManager : public SensorManager { - bool gps_active = false; bool gps_detected = false; LocationProvider* _location; diff --git a/variants/heltec_rc32/target.cpp b/variants/heltec_rc32/target.cpp index cc7e4deb740..e095baab814 100644 --- a/variants/heltec_rc32/target.cpp +++ b/variants/heltec_rc32/target.cpp @@ -20,7 +20,9 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, PIN_GPS_RESET, PIN_GPS_EN); + NmeaGpsDriver gps_driver(Serial1, PIN_GPS_TX, PIN_GPS_RX, PIN_GPS_RESET, + PIN_GPS_EN); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/heltec_t096/target.cpp b/variants/heltec_t096/target.cpp index 30509977eee..d0c2c07e143 100644 --- a/variants/heltec_t096/target.cpp +++ b/variants/heltec_t096/target.cpp @@ -22,7 +22,9 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); +NmeaGpsDriver gps_driver(Serial1, PIN_GPS_TX, PIN_GPS_RX, GPS_RESET, GPS_EN, + &board.periph_power); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/heltec_t1/target.cpp b/variants/heltec_t1/target.cpp index af771582dab..1d9c8baafc8 100644 --- a/variants/heltec_t1/target.cpp +++ b/variants/heltec_t1/target.cpp @@ -21,7 +21,9 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); +NmeaGpsDriver gps_driver(Serial1, PIN_GPS_TX, PIN_GPS_RX, GPS_RESET, GPS_EN, + &board.periph_power); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/heltec_t114/target.cpp b/variants/heltec_t114/target.cpp index 90a902f804a..dc62f83bbb3 100644 --- a/variants/heltec_t114/target.cpp +++ b/variants/heltec_t114/target.cpp @@ -22,7 +22,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/heltec_tower_v2/target.cpp b/variants/heltec_tower_v2/target.cpp index ec8e7a17dac..ae627a0b52d 100644 --- a/variants/heltec_tower_v2/target.cpp +++ b/variants/heltec_tower_v2/target.cpp @@ -12,7 +12,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); TowerV2ExternalWatchdog external_watchdog; diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index f32c41ff477..5903f6597f0 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -17,7 +17,20 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); // GPS_EN (GPIO35) drives N-ch MOSFET → P-ch high-side switch; GPS_RESET (GPIO36) active LOW -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); +class HeltecTrackerGpsDriver : public NmeaGpsDriver { +public: + HeltecTrackerGpsDriver() + : NmeaGpsDriver(Serial1, PIN_GPS_TX, PIN_GPS_RX, GPS_RESET, GPS_EN, + &board.periph_power) {} + + void begin() override { + NmeaGpsDriver::begin(); + sendNmeaSentence("$CFGSYS,h35155*68"); + } +}; + +HeltecTrackerGpsDriver gps_driver; +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); HWTSensorManager sensors = HWTSensorManager(nmea); #ifdef DISPLAY_CLASS @@ -43,18 +56,11 @@ mesh::LocalIdentity radio_new_identity() { } void HWTSensorManager::start_gps() { - if (!gps_active) { - _location->begin(); // Claims periph_power via RefCountedDigitalPin - gps_active = true; - Serial1.println("$CFGSYS,h35155*68"); // Configure GPS for all constellations - } + _location->setGpsOn(true); } void HWTSensorManager::stop_gps() { - if (gps_active) { - gps_active = false; - _location->stop(); // Releases periph_power via RefCountedDigitalPin - } + _location->setGpsOn(false); } bool HWTSensorManager::begin() { @@ -76,7 +82,7 @@ void HWTSensorManager::loop() { _location->loop(); if (millis() > next_gps_update) { - if (gps_active && _location->isValid()) { + if (_location->isEnabled() && _location->isValid()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; @@ -93,7 +99,7 @@ const char* HWTSensorManager::getSettingName(int i) const { } const char* HWTSensorManager::getSettingValue(int i) const { if (i == 0) { - return gps_active ? "1" : "0"; + return _location->isGpsOn() ? "1" : "0"; } return NULL; } diff --git a/variants/heltec_tracker/target.h b/variants/heltec_tracker/target.h index 2931eda816d..f6fea637103 100644 --- a/variants/heltec_tracker/target.h +++ b/variants/heltec_tracker/target.h @@ -14,7 +14,6 @@ #endif class HWTSensorManager : public SensorManager { - bool gps_active = false; LocationProvider * _location; void start_gps(); diff --git a/variants/heltec_tracker_v2/target.cpp b/variants/heltec_tracker_v2/target.cpp index c48a29f6a05..12b01bcc6c6 100644 --- a/variants/heltec_tracker_v2/target.cpp +++ b/variants/heltec_tracker_v2/target.cpp @@ -17,7 +17,9 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); + NmeaGpsDriver gps_driver(Serial1, PIN_GPS_TX, PIN_GPS_RX, GPS_RESET, GPS_EN, + &board.periph_power); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -43,4 +45,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/heltec_v3/target.cpp b/variants/heltec_v3/target.cpp index e6af466a84d..f2f712eb027 100644 --- a/variants/heltec_v3/target.cpp +++ b/variants/heltec_v3/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -43,4 +44,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/heltec_v4/target.cpp b/variants/heltec_v4/target.cpp index 8483f276891..0f87c6b3b36 100644 --- a/variants/heltec_v4/target.cpp +++ b/variants/heltec_v4/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -43,4 +44,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/heltec_v4_r8/target.cpp b/variants/heltec_v4_r8/target.cpp index 0b38531e3c0..27e9f468640 100644 --- a/variants/heltec_v4_r8/target.cpp +++ b/variants/heltec_v4_r8/target.cpp @@ -17,7 +17,9 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock, GPS_RESET, GPS_EN, &board.periph_power); + NmeaGpsDriver gps_driver(Serial1, PIN_GPS_TX, PIN_GPS_RX, GPS_RESET, GPS_EN, + &board.periph_power); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/keepteen_lt1/target.cpp b/variants/keepteen_lt1/target.cpp index f1879ac569c..dd7c6741b5e 100644 --- a/variants/keepteen_lt1/target.cpp +++ b/variants/keepteen_lt1/target.cpp @@ -12,7 +12,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -34,4 +35,3 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/lilygo_t_impulse_plus/target.cpp b/variants/lilygo_t_impulse_plus/target.cpp index 87b3dbfed7e..b6b0c82cf92 100644 --- a/variants/lilygo_t_impulse_plus/target.cpp +++ b/variants/lilygo_t_impulse_plus/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -34,4 +35,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/lilygo_tbeam_1w/target.cpp b/variants/lilygo_tbeam_1w/target.cpp index 6755df09f59..db11747f8b8 100644 --- a/variants/lilygo_tbeam_1w/target.cpp +++ b/variants/lilygo_tbeam_1w/target.cpp @@ -19,7 +19,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -47,4 +48,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); } - diff --git a/variants/lilygo_tbeam_SX1262/target.cpp b/variants/lilygo_tbeam_SX1262/target.cpp index 6e5292a77ec..b97dda41e85 100644 --- a/variants/lilygo_tbeam_SX1262/target.cpp +++ b/variants/lilygo_tbeam_SX1262/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -38,4 +39,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/lilygo_tbeam_SX1276/target.cpp b/variants/lilygo_tbeam_SX1276/target.cpp index 30f23dab78f..be7c8287f8a 100644 --- a/variants/lilygo_tbeam_SX1276/target.cpp +++ b/variants/lilygo_tbeam_SX1276/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/lilygo_tbeam_supreme_SX1262/target.cpp b/variants/lilygo_tbeam_supreme_SX1262/target.cpp index a0b765bf6d1..86b9116c3a3 100644 --- a/variants/lilygo_tbeam_supreme_SX1262/target.cpp +++ b/variants/lilygo_tbeam_supreme_SX1262/target.cpp @@ -19,7 +19,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -35,4 +36,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/lilygo_tdeck/target.cpp b/variants/lilygo_tdeck/target.cpp index d20c56a3e9d..4084a73bc06 100644 --- a/variants/lilygo_tdeck/target.cpp +++ b/variants/lilygo_tdeck/target.cpp @@ -14,7 +14,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider gps(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider gps(gps_driver, &rtc_clock); EnvironmentSensorManager sensors(gps); #ifdef DISPLAY_CLASS diff --git a/variants/lilygo_techo_card/target.cpp b/variants/lilygo_techo_card/target.cpp index 8bbe036338a..ea74bd0ca24 100644 --- a/variants/lilygo_techo_card/target.cpp +++ b/variants/lilygo_techo_card/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/lilygo_techo_lite/target.cpp b/variants/lilygo_techo_lite/target.cpp index 95a62fcd250..6d199fff834 100644 --- a/variants/lilygo_techo_lite/target.cpp +++ b/variants/lilygo_techo_lite/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -35,4 +36,3 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index 8795a4cbe73..faf2e30ef71 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -11,7 +11,8 @@ WRAPPER_CLASS radio_driver(radio, board); ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); MASensorManager sensors = MASensorManager(nmea); #ifdef DISPLAY_CLASS @@ -37,17 +38,13 @@ mesh::LocalIdentity radio_new_identity() { void MASensorManager::start_gps() { - if(!gps_active) { - MESH_DEBUG_PRINTLN("starting GPS"); - gps_active = true; - } + MESH_DEBUG_PRINTLN("starting GPS"); + _location->setGpsOn(true); } void MASensorManager::stop_gps() { - if(gps_active) { - MESH_DEBUG_PRINTLN("stopping GPS"); - gps_active = false; - } + MESH_DEBUG_PRINTLN("stopping GPS"); + _location->setGpsOn(false); } bool MASensorManager::begin() { @@ -67,7 +64,7 @@ bool MASensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& te void MASensorManager::loop() { static long next_gps_update = 0; _location->loop(); - if(millis() > next_gps_update && gps_active) { + if(millis() > next_gps_update && _location->isEnabled()) { if(_location->isValid()) { node_lat = ((double)_location->getLatitude()) / 1000000.; node_lon = ((double)_location->getLongitude()) / 1000000.; @@ -85,7 +82,7 @@ const char* MASensorManager::getSettingName(int i) const { } const char* MASensorManager::getSettingValue(int i) const { if(i == 0) { - return gps_active ? "1" : "0"; + return _location->isGpsOn() ? "1" : "0"; } return NULL; } diff --git a/variants/meshadventurer/target.h b/variants/meshadventurer/target.h index 0749565d363..9581d422cef 100644 --- a/variants/meshadventurer/target.h +++ b/variants/meshadventurer/target.h @@ -15,8 +15,7 @@ #endif class MASensorManager : public SensorManager { - bool gps_active = false; - LocationProvider * _location; + LocationProvider* _location; void start_gps(); void stop_gps(); diff --git a/variants/meshnology_w12/target.cpp b/variants/meshnology_w12/target.cpp index bf04ba36470..a3d6089ffde 100644 --- a/variants/meshnology_w12/target.cpp +++ b/variants/meshnology_w12/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -70,4 +71,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/meshtracker_x1/target.cpp b/variants/meshtracker_x1/target.cpp index 5b90d660934..2dee0ebd137 100644 --- a/variants/meshtracker_x1/target.cpp +++ b/variants/meshtracker_x1/target.cpp @@ -1,7 +1,7 @@ #include #include "target.h" +#include #include -#include MeshTrackerX1Board board; @@ -10,7 +10,8 @@ RADIO_CLASS radio = new Module(P_LORA_NSS, P_LORA_DIO_1, P_LORA_RESET, P_LORA_BU WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock rtc_clock; -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +AirohaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); MeshTrackerX1SensorManager sensors = MeshTrackerX1SensorManager(nmea); #ifdef DISPLAY_CLASS @@ -27,28 +28,16 @@ mesh::LocalIdentity radio_new_identity() { } void MeshTrackerX1SensorManager::start_gps() { - gps_active = true; - digitalWrite(GPS_EN, HIGH); - delay(10); - digitalWrite(GPS_RTC_INT, HIGH); - delay(5); - digitalWrite(GPS_RTC_INT, LOW); + _nmea->setGpsOn(true); } void MeshTrackerX1SensorManager::stop_gps() { - gps_active = false; - 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); - digitalWrite(GPS_EN, LOW); + _nmea->setGpsOn(false); } bool MeshTrackerX1SensorManager::begin() { // init GPS - Serial1.begin(GPS_BAUD_RATE); - digitalWrite(GPS_RESET, HIGH); - delay(10); - digitalWrite(GPS_RESET, LOW); + gps_driver.initialize(); // init SPA06-003 barometer baro_ok = spa06.begin(SPA06_003_DEFAULT_ADDR, &Wire) || spa06.begin(0x76, &Wire); @@ -81,7 +70,7 @@ void MeshTrackerX1SensorManager::loop() { _nmea->loop(); if (millis() > next_gps_update) { - if (gps_active && _nmea->isValid()) { + if (_nmea->isEnabled() && _nmea->isValid()) { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; node_altitude = ((double)_nmea->getAltitude()) / 1000.0; @@ -97,7 +86,7 @@ const char* MeshTrackerX1SensorManager::getSettingName(int i) const { } const char* MeshTrackerX1SensorManager::getSettingValue(int i) const { if (i == 0) { - return gps_active ? "1" : "0"; + return _nmea->isGpsOn() ? "1" : "0"; } return NULL; } diff --git a/variants/meshtracker_x1/target.h b/variants/meshtracker_x1/target.h index 9741efb6af0..e678e29ca68 100644 --- a/variants/meshtracker_x1/target.h +++ b/variants/meshtracker_x1/target.h @@ -14,13 +14,11 @@ #endif class MeshTrackerX1SensorManager: public SensorManager { - bool gps_active = false; bool baro_ok = false; Adafruit_SPA06_003 spa06; LocationProvider * _nmea; void start_gps(); - void sleep_gps(); void stop_gps(); public: MeshTrackerX1SensorManager(LocationProvider &nmea): _nmea(&nmea) { } diff --git a/variants/minewsemi_me25ls01/target.cpp b/variants/minewsemi_me25ls01/target.cpp index 41fc71a7d9d..8d9a6e0721f 100644 --- a/variants/minewsemi_me25ls01/target.cpp +++ b/variants/minewsemi_me25ls01/target.cpp @@ -10,7 +10,8 @@ VolatileRTCClock rtc_clock; extern EnvironmentSensorManager sensors; #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/muzi_base/target.cpp b/variants/muzi_base/target.cpp index 5bd846c9861..b65bafa1d36 100644 --- a/variants/muzi_base/target.cpp +++ b/variants/muzi_base/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); MuziBaseSensorManager sensors = MuziBaseSensorManager(nmea); #else MuziBaseSensorManager sensors; @@ -38,13 +39,7 @@ static void muziPowerOff() { bool MuziBaseSensorManager::begin() { pwr_btn.begin(); - bool ok = EnvironmentSensorManager::begin(); -#if ENV_INCLUDE_GPS - pinMode(PIN_GPS_SWITCH, INPUT); - _last_gps_sw = digitalRead(PIN_GPS_SWITCH); // initial gps state from the switch - if (_last_gps_sw == HIGH) start_gps(); else stop_gps(); -#endif - return ok; + return EnvironmentSensorManager::begin(); } void MuziBaseSensorManager::loop() { @@ -55,17 +50,6 @@ void MuziBaseSensorManager::loop() { _pwr_last_click = t; if (++_pwr_clicks >= USER_BTN_POWER_OFF_CLICKS) muziPowerOff(); } -#if ENV_INCLUDE_GPS - unsigned long now = millis(); - if (now > _next_sw_check) { // check the mode switch ~once a sec - _next_sw_check = now + 1000; - int sw = digitalRead(PIN_GPS_SWITCH); - if (sw != _last_gps_sw) { - _last_gps_sw = sw; - if (sw == HIGH) start_gps(); else stop_gps(); - } - } -#endif EnvironmentSensorManager::loop(); } diff --git a/variants/muzi_base/target.h b/variants/muzi_base/target.h index 45cd595fd66..3f7dcbf5f29 100644 --- a/variants/muzi_base/target.h +++ b/variants/muzi_base/target.h @@ -38,14 +38,10 @@ extern AutoDiscoverRTCClock rtc_clock; // muzi sensor manager, polled from main loop via sensors.loop(): // - user button: USER_BTN_POWER_OFF_CLICKS quick presses -> power off (all builds) -// - gps mode switch on/off (superIO only, see thinknode_m1) +// gps mode is now handled by GpsDriver on superIO class MuziBaseSensorManager : public EnvironmentSensorManager { unsigned long _pwr_last_click = 0; uint8_t _pwr_clicks = 0; -#if ENV_INCLUDE_GPS - int _last_gps_sw = -1; - unsigned long _next_sw_check = 0; -#endif public: #if ENV_INCLUDE_GPS MuziBaseSensorManager(LocationProvider& location) : EnvironmentSensorManager(location) {} diff --git a/variants/muzi_base/variant.h b/variants/muzi_base/variant.h index b007c1f8ba4..0c1245a897b 100644 --- a/variants/muzi_base/variant.h +++ b/variants/muzi_base/variant.h @@ -162,6 +162,8 @@ static const uint8_t AREF = (PIN_AREF); // not used #define SWITCH_MODE1 (32+9) // P1.09 #define SWITCH_MODE2 (12) // P0.12 #define PIN_GPS_SWITCH SWITCH_MODE2 +#define GPS_SWITCH_IS_HARDWARE (0) +#define GPS_SWITCH_TURNS_GPS_ON (1) //////////////////////////////////////////////////////////////////////////////// // Buzzer diff --git a/variants/muziworks_r1_neo/target.cpp b/variants/muziworks_r1_neo/target.cpp index f667a2827e1..3081de2c19b 100644 --- a/variants/muziworks_r1_neo/target.cpp +++ b/variants/muziworks_r1_neo/target.cpp @@ -20,7 +20,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h b/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h new file mode 100644 index 00000000000..3772084b75a --- /dev/null +++ b/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h @@ -0,0 +1,31 @@ +#pragma once + +#include + +class NanoG2UltraGpsDriver : public NmeaGpsDriver { +public: + NanoG2UltraGpsDriver(HardwareSerial& serial) : NmeaGpsDriver(serial) { + pinMode(PIN_GPS_STANDBY, OUTPUT); + digitalWrite(PIN_GPS_STANDBY, LOW); + } + + void begin() override { + if (isEnabled()) return; + + digitalWrite(PIN_GPS_STANDBY, HIGH); + NmeaGpsDriver::begin(); + delay(1000); + } + + void stop() override { + NmeaGpsDriver::stop(); + digitalWrite(PIN_GPS_STANDBY, LOW); + } + + bool detectAtBoot() { + begin(); + const bool wasActive = detect(0); + if (!wasActive) stop(); + return wasActive; + } +}; diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index 69a2772ccb1..ee258f2d912 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -3,6 +3,7 @@ #include #include #include +#include "NanoG2UltraGpsDriver.h" NanoG2Ultra board; @@ -12,8 +13,10 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); -NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea); +// NanoG2UltraGpsDriver now handles the special init needed for NanoG2 +NanoG2UltraGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); +NanoG2UltraSensorManager sensors = NanoG2UltraSensorManager(nmea, gps_driver); #ifdef DISPLAY_CLASS DISPLAY_CLASS display; @@ -27,35 +30,19 @@ bool radio_init() { void NanoG2UltraSensorManager::start_gps() { MESH_DEBUG_PRINTLN("Starting GPS"); - if (!gps_active) { - digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby - Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); - Serial1.begin(9600); - MESH_DEBUG_PRINTLN("Waiting for gps to power up"); - delay(1000); - gps_active = true; - } - _location->begin(); + _location->setGpsOn(true); } void NanoG2UltraSensorManager::stop_gps() { MESH_DEBUG_PRINTLN("Stopping GPS"); - if (gps_active) { - digitalWrite(PIN_GPS_STANDBY, LOW); // sleep GPS - gps_active = false; - } - _location->stop(); + _location->setGpsOn(false); } bool NanoG2UltraSensorManager::begin() { - digitalWrite(PIN_GPS_STANDBY, HIGH); // Wake GPS from standby - Serial1.setPins(PIN_GPS_TX, PIN_GPS_RX); - Serial1.begin(9600); MESH_DEBUG_PRINTLN("Checking GPS switch state"); - delay(1000); // Check initial switch state to determine if GPS should be active - if (Serial1.available() > 0) { + if (_gps_driver->detectAtBoot()) { MESH_DEBUG_PRINTLN("GPS was on at boot, GPS enabled"); start_gps(); } else { @@ -75,14 +62,14 @@ bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, Cayen void NanoG2UltraSensorManager::loop() { static long next_gps_update = 0; - if (!gps_active) { + if (!_location->isEnabled()) { return; // GPS is not active, skip further processing } _location->loop(); if (millis() > next_gps_update) { - if (_location->isValid()) { + if (_location->isEnabled() && _location->isValid()) { node_lat = ((double)_location->getLatitude()) / 1000000.; node_lon = ((double)_location->getLongitude()) / 1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; @@ -105,7 +92,7 @@ const char *NanoG2UltraSensorManager::getSettingName(int i) const { const char *NanoG2UltraSensorManager::getSettingValue(int i) const { if (i == 0) { - return gps_active ? "1" : "0"; + return _location->isGpsOn() ? "1" : "0"; } return NULL; } diff --git a/variants/nano_g2_ultra/target.h b/variants/nano_g2_ultra/target.h index 5c6ebee11df..d540b9ecc6f 100644 --- a/variants/nano_g2_ultra/target.h +++ b/variants/nano_g2_ultra/target.h @@ -14,15 +14,18 @@ #endif #include +class NanoG2UltraGpsDriver; + class NanoG2UltraSensorManager : public SensorManager { - bool gps_active = false; LocationProvider *_location; + NanoG2UltraGpsDriver *_gps_driver; void start_gps(); void stop_gps(); public: - NanoG2UltraSensorManager(LocationProvider &location) : _location(&location) {} + NanoG2UltraSensorManager(LocationProvider &location, NanoG2UltraGpsDriver &gps_driver) + : _location(&location), _gps_driver(&gps_driver) {} bool begin() override; bool querySensors(uint8_t requester_permissions, CayenneLPP &telemetry) override; void loop() override; @@ -44,4 +47,3 @@ extern MomentaryButton user_btn; bool radio_init(); mesh::LocalIdentity radio_new_identity(); - diff --git a/variants/promicro/target.cpp b/variants/promicro/target.cpp index b5a74c90dce..5758d9cda7e 100644 --- a/variants/promicro/target.cpp +++ b/variants/promicro/target.cpp @@ -12,7 +12,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -34,4 +35,3 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/rak3112/target.cpp b/variants/rak3112/target.cpp index 9df1bf8f8ca..9b1d11a0198 100644 --- a/variants/rak3112/target.cpp +++ b/variants/rak3112/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -43,4 +44,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/rak_wismesh_tag/target.cpp b/variants/rak_wismesh_tag/target.cpp index cdf81f4f5b9..3e80ae639be 100644 --- a/variants/rak_wismesh_tag/target.cpp +++ b/variants/rak_wismesh_tag/target.cpp @@ -22,7 +22,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -37,4 +38,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/sensecap_indicator-espnow/target.cpp b/variants/sensecap_indicator-espnow/target.cpp index c84d1883e7b..4895447c043 100644 --- a/variants/sensecap_indicator-espnow/target.cpp +++ b/variants/sensecap_indicator-espnow/target.cpp @@ -8,7 +8,8 @@ ESPNOWRadio radio_driver; ESP32RTCClock rtc_clock; #if defined(ENV_INCLUDE_GPS) -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, (mesh::RTCClock*)&rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, (mesh::RTCClock*)&rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/sensecap_solar/target.cpp b/variants/sensecap_solar/target.cpp index 466415176f8..11586ecd01d 100644 --- a/variants/sensecap_solar/target.cpp +++ b/variants/sensecap_solar/target.cpp @@ -13,7 +13,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -29,4 +30,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/station_g2/target.cpp b/variants/station_g2/target.cpp index 8018c20eb88..f5365eb58c8 100644 --- a/variants/station_g2/target.cpp +++ b/variants/station_g2/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -44,4 +45,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/station_g3_esp32/target.cpp b/variants/station_g3_esp32/target.cpp index d3de074ba81..189169d216e 100644 --- a/variants/station_g3_esp32/target.cpp +++ b/variants/station_g3_esp32/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index 0d7710b3a5c..b96d01f88ae 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -82,13 +82,11 @@ mesh::LocalIdentity radio_new_identity() { } void T1000SensorManager::start_gps() { - gps_active = true; - _nmea->begin(); + _nmea->setGpsOn(true); } void T1000SensorManager::stop_gps() { - gps_active = false; - _nmea->stop(); + _nmea->setGpsOn(false); } @@ -115,7 +113,7 @@ void T1000SensorManager::loop() { _nmea->loop(); if (millis() > next_gps_update) { - if (gps_active && _nmea->isValid()) { + if (_nmea->isEnabled() && _nmea->isValid()) { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; node_altitude = ((double)_nmea->getAltitude()) / 1000.0; @@ -131,7 +129,7 @@ const char* T1000SensorManager::getSettingName(int i) const { } const char* T1000SensorManager::getSettingValue(int i) const { if (i == 0) { - return gps_active ? "1" : "0"; + return _nmea->isGpsOn() ? "1" : "0"; } return NULL; } diff --git a/variants/t1000-e/target.h b/variants/t1000-e/target.h index 4a1a2bb731f..68508bcb5ad 100644 --- a/variants/t1000-e/target.h +++ b/variants/t1000-e/target.h @@ -15,7 +15,6 @@ class AirohaGpsDriver; class T1000SensorManager: public SensorManager { - bool gps_active = false; LocationProvider * _nmea; AirohaGpsDriver* _gps_driver; diff --git a/variants/thinknode_m3/target.cpp b/variants/thinknode_m3/target.cpp index 1e6c2ad13cb..bb5746cac02 100644 --- a/variants/thinknode_m3/target.cpp +++ b/variants/thinknode_m3/target.cpp @@ -11,7 +11,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/thinknode_m4/target.cpp b/variants/thinknode_m4/target.cpp index 7da3f400d48..59a898f5499 100644 --- a/variants/thinknode_m4/target.cpp +++ b/variants/thinknode_m4/target.cpp @@ -11,7 +11,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/thinknode_m6/target.cpp b/variants/thinknode_m6/target.cpp index de167194e40..061f7ad4ca0 100644 --- a/variants/thinknode_m6/target.cpp +++ b/variants/thinknode_m6/target.cpp @@ -12,7 +12,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -32,4 +33,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/thinknode_m7/target.cpp b/variants/thinknode_m7/target.cpp index f4b898d8e93..8e7e8a05a7c 100644 --- a/variants/thinknode_m7/target.cpp +++ b/variants/thinknode_m7/target.cpp @@ -12,7 +12,8 @@ ESP32RTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -81,4 +82,3 @@ mesh::LocalIdentity radio_new_identity() { return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/thinknode_m8/target.cpp b/variants/thinknode_m8/target.cpp index 69f3cc52604..04496d3fc9d 100644 --- a/variants/thinknode_m8/target.cpp +++ b/variants/thinknode_m8/target.cpp @@ -13,7 +13,8 @@ WRAPPER_CLASS radio_driver(radio, board); VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/thinknode_m9/target.cpp b/variants/thinknode_m9/target.cpp index 3cbd7d758a8..990f0388a71 100644 --- a/variants/thinknode_m9/target.cpp +++ b/variants/thinknode_m9/target.cpp @@ -11,7 +11,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS #include -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); diff --git a/variants/wio-tracker-l1-1w/target.cpp b/variants/wio-tracker-l1-1w/target.cpp index 7a573258e6d..cd4b8dac9c5 100644 --- a/variants/wio-tracker-l1-1w/target.cpp +++ b/variants/wio-tracker-l1-1w/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -37,4 +38,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/wio-tracker-l1/target.cpp b/variants/wio-tracker-l1/target.cpp index 7a573258e6d..cd4b8dac9c5 100644 --- a/variants/wio-tracker-l1/target.cpp +++ b/variants/wio-tracker-l1/target.cpp @@ -13,7 +13,8 @@ VolatileRTCClock fallback_clock; AutoDiscoverRTCClock rtc_clock(fallback_clock); #ifdef ENV_INCLUDE_GPS -MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); +NmeaGpsDriver gps_driver(Serial1); +MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors = EnvironmentSensorManager(); @@ -37,4 +38,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - diff --git a/variants/xiao_c3/target.cpp b/variants/xiao_c3/target.cpp index 7f30dc8b60c..d3bbdf1c44f 100644 --- a/variants/xiao_c3/target.cpp +++ b/variants/xiao_c3/target.cpp @@ -17,7 +17,8 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - MicroNMEALocationProvider nmea = MicroNMEALocationProvider(Serial1, &rtc_clock); + NmeaGpsDriver gps_driver(Serial1); + MicroNMEALocationProvider nmea(gps_driver, &rtc_clock); EnvironmentSensorManager sensors = EnvironmentSensorManager(nmea); #else EnvironmentSensorManager sensors; @@ -39,4 +40,3 @@ mesh::LocalIdentity radio_new_identity() { RadioNoiseListener rng(radio); return mesh::LocalIdentity(&rng); // create new random identity } - From 36b53d8de939eac65fbd51791bc11e0b7db94a8b Mon Sep 17 00:00:00 2001 From: Florent Date: Mon, 2 Nov 2026 12:45:05 -0400 Subject: [PATCH 03/23] gps: cache the last fix in LocationProvider (isValid vs hasValidFix), fix and status commands --- docs/cli_commands.md | 37 +++++++++++-- docs/companion_cli.md | 52 +++++++++++++++++++ src/helpers/CommonGpsPrefs.cpp | 48 ++++++++++++++--- src/helpers/sensors/LocationProvider.h | 21 +++++++- .../sensors/MicroNMEALocationProvider.h | 35 +++++++++---- src/helpers/sensors/RakLocationProvider.h | 48 ++++++++++++----- 6 files changed, 208 insertions(+), 33 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index 9fde4d436e5..eb8c6b20c89 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -999,9 +999,16 @@ region save --- ### GPS (When GPS support is compiled in) -#### View or change GPS state +#### View GPS state **Usage:** - `gps` + +**Note:** Reports the requested state, receiver activity, last-fix status (last fix gives number of sats and how long ago was the fix), and mode. In periodic mode, `waiting` means the receiver is off between acquisitions; the last fix remains available. `off(hw)` means a hardware GPS switch is off. + +--- + +#### Change GPS state +**Usage:** - `gps ` **Parameters:** @@ -1009,9 +1016,25 @@ region save **Default:** `off` -**Note:** Output format: -- `off` when the GPS hardware is disabled -- `on, {active|deactivated}, {fix|no fix}, {sat count} sats` when the GPS hardware is enabled +--- + +#### View or change the GPS acquisition period +**Usage:** +- `gps period` +- `gps period ` + +**Parameters:** +- `seconds`: `0` for continuous tracking, or `1`-`86400` for periodic acquisition + +**Note:** A periodic acquisition stops after receiving a valid fix, then resumes after the configured period. + +--- + +#### View the last GPS fix +**Usage:** +- `gps fix` + +**Note:** Returns the last valid latitude, longitude, altitude, and elapsed time since the fix. Returns `no fix` until a valid fix is received. --- @@ -1021,6 +1044,12 @@ region save --- +#### View GPS diagnostics +**Usage:** +- `gps diag` + +--- + #### Set this node's location based on the GPS coordinates **Usage:** - `gps setloc` diff --git a/docs/companion_cli.md b/docs/companion_cli.md index 31a055847b1..24843269e7b 100644 --- a/docs/companion_cli.md +++ b/docs/companion_cli.md @@ -7,6 +7,7 @@ Since version 1.18 meshcore companion radio firmware has a CLI interface, which - [Configuration](#configuration) - [System](#system) - [Radio](#radio) + - [GPS](#gps) - [BLE](#ble) - [Wifi](#wifi) - [UI](#ui) @@ -119,6 +120,57 @@ Radio parameters are common to all roles and handled by `CommonRadioPrefs`. --- +### GPS + +#### View GPS state +**Usage:** +- `gps` + +**Note:** Reports the requested state, receiver activity, last-fix status (last fix gives number of sats and how long ago was the fix), and mode. In periodic mode, `waiting` means the receiver is off between acquisitions; the last fix remains available. `off(hw)` means a hardware GPS switch is off. + +--- + +#### Change GPS state +**Usage:** +- `gps ` + +**Parameters:** +- `state`: `on`|`off` + +--- + +#### View or change the GPS acquisition period +**Usage:** +- `gps period` +- `gps period ` + +**Parameters:** +- `seconds`: `0` for continuous tracking, or `1`-`86400` for periodic acquisition + +**Note:** A periodic acquisition stops after receiving a valid fix, then resumes after the configured period. + +--- + +#### View the last GPS fix +**Usage:** +- `gps fix` + +**Note:** Returns the last valid latitude, longitude, altitude, and elapsed time since the fix. Returns `no fix` until a valid fix is received. + +--- + +#### Sync this node's clock with GPS time +**Usage:** +- `gps sync` + +--- + +#### View GPS diagnostics +**Usage:** +- `gps diag` + +--- + #### View or change this node's rx boosted gain mode (SX12xx and LR1110) **Usage:** - `get radio.rxgain` diff --git a/src/helpers/CommonGpsPrefs.cpp b/src/helpers/CommonGpsPrefs.cpp index fe29c0c0001..248ae540b88 100644 --- a/src/helpers/CommonGpsPrefs.cpp +++ b/src/helpers/CommonGpsPrefs.cpp @@ -37,13 +37,49 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, } if (location == nullptr) { strcpy(reply, "gps provider not found"); - } else if (location->isEnabled()) { - sprintf(reply, "on, %s, %s, %d sats, mode: %s", - isGpsEnabled() ? "active" : "deactivated", - location->isValid() ? "fix" : "no fix", - location->satellitesCount(), mode); + } else if (location->isGpsOn()) { + const char* activity = "active"; + if (!location->isEnabled()) { + if (location->hasHardwareEnableState() && !location->isHardwareEnabled()) { + activity = "off(hw)"; + } else if (getGpsPeriod() > 0) { + activity = "waiting"; + } else { + activity = "inactive"; + } + } + char fix[32]; + if (location->hasValidFix()) { + snprintf(fix, sizeof(fix), "%ld sats %lus ago", + location->satellitesCount(), (unsigned long)location->getLastFixAge()); + } else { + strcpy(fix, "no fix"); + } + snprintf(reply, 160, "on, %s, %s, mode: %s", activity, fix, mode); + } + return true; + } + if (strcmp(command, "gps fix") == 0) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) { + strcpy(reply, "gps provider not found"); + } else if (!location->hasValidFix()) { + strcpy(reply, "no fix"); } else { - snprintf(reply, 160, "off, mode: %s", mode); + const long latitude = location->getLatitude(); + const long longitude = location->getLongitude(); + const uint32_t fixAge = location->getLastFixAge(); + char age[24]; + if (fixAge == UINT32_MAX) { + strcpy(age, "age unavailable"); + } else { + snprintf(age, sizeof(age), "%lus ago", (unsigned long)fixAge); + } + snprintf(reply, 160, "%s%ld.%06ld, %s%ld.%06ld, %ldm, %s", + latitude < 0 ? "-" : "", labs(latitude) / 1000000L, + labs(latitude) % 1000000L, + longitude < 0 ? "-" : "", labs(longitude) / 1000000L, + labs(longitude) % 1000000L, location->getAltitude() / 1000L, age); } return true; } diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index 93cecf5bedf..dc316e1d38f 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -6,9 +6,22 @@ class LocationProvider { +public: + // Last valid fix, always filled in one go so readers get a consistent snapshot. + struct LastFix { + bool valid = false; + long latitude = 0; + long longitude = 0; + long altitude = 0; + long satellites = 0; + long timestamp = 0; + unsigned long acquiredAt = 0; // millis() when the fix was stored + }; + protected: bool _time_sync_needed = true; bool _gpsOn = false; + LastFix _lastFix; public: virtual ~LocationProvider() = default; @@ -19,7 +32,9 @@ class LocationProvider { virtual long getLongitude() = 0; virtual long getAltitude() = 0; virtual long satellitesCount() = 0; - virtual bool isValid() = 0; + virtual bool isValid() = 0; // current fix is valid + virtual bool hasValidFix() { return getLastFix().valid || isValid(); } // a valid fix was obtained at some point + virtual LastFix getLastFix() const { return _lastFix; } // snapshot of the last valid fix virtual long getTimestamp() = 0; virtual void sendSentence(const char * sentence); virtual bool waitFor(const char* prefix, uint32_t timeout_ms) { return false; } @@ -28,6 +43,10 @@ class LocationProvider { virtual bool keepEnabledAfterDetection() const { return false; } virtual void setGpsPeriod(uint32_t seconds) { } virtual uint32_t getGpsPeriod() const { return 0; } + virtual uint32_t getLastFixAge() const { // in seconds, UINT32_MAX if never + const LastFix f = getLastFix(); + return f.valid ? (uint32_t)(millis() - f.acquiredAt) / 1000UL : UINT32_MAX; + } virtual bool hasHardwareEnableState() const { return false; } virtual bool isHardwareEnabled() const { return true; } virtual bool isGpsOn() const { return _gpsOn; } diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 2e9ff9cc480..cceffd8ca31 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -32,10 +32,8 @@ class MicroNMEALocationProvider : public LocationProvider { uint32_t _stop_calls = 0; unsigned long _last_uart_ms = 0; unsigned long _last_nmea_ms = 0; - unsigned long _last_fix_ms = 0; bool _uart_seen = false; bool _nmea_seen = false; - bool _fix_seen = false; static uint32_t ageMs(unsigned long timestamp) { return (uint32_t)(millis() - timestamp); @@ -64,6 +62,18 @@ class MicroNMEALocationProvider : public LocationProvider { _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; } + void saveFix() { + LastFix fix; + fix.latitude = nmea.getLatitude(); + fix.longitude = nmea.getLongitude(); + nmea.getAltitude(fix.altitude); + fix.satellites = nmea.getNumSatellites(); + fix.timestamp = getTimestamp(); + fix.acquiredAt = millis(); + fix.valid = true; + _lastFix = fix; + } + void updateTime() { if (!isValid()) time_valid = 0; @@ -161,17 +171,25 @@ class MicroNMEALocationProvider : public LocationProvider { LocationProvider::syncTime(); } - long getLatitude() override { return nmea.getLatitude(); } - long getLongitude() override { return nmea.getLongitude(); } + long getLatitude() override { + return nmea.isValid() ? nmea.getLatitude() : _lastFix.latitude; + } + long getLongitude() override { + return nmea.isValid() ? nmea.getLongitude() : _lastFix.longitude; + } long getAltitude() override { + if (!nmea.isValid()) return _lastFix.altitude; long alt = 0; nmea.getAltitude(alt); return alt; } - long satellitesCount() override { return nmea.getNumSatellites(); } + long satellitesCount() override { + return nmea.isValid() ? nmea.getNumSatellites() : _lastFix.satellites; + } bool isValid() override { return nmea.isValid(); } long getTimestamp() override { + if (!nmea.isValid()) return _lastFix.timestamp; DateTime dt(nmea.getYear(), nmea.getMonth(), nmea.getDay(), nmea.getHour(), nmea.getMinute(), nmea.getSecond()); return dt.unixtime(); @@ -228,10 +246,9 @@ class MicroNMEALocationProvider : public LocationProvider { _nmea_seen = true; const char* messageId = nmea.getMessageID(); - if (parsed && isValid() && + if (parsed && nmea.isValid() && (strcmp(messageId, "GGA") == 0 || strcmp(messageId, "RMC") == 0)) { - _last_fix_ms = millis(); - _fix_seen = true; + saveFix(); if (_acquiringPeriodically) finishPeriodicAcquisition(); } } else { @@ -256,7 +273,7 @@ class MicroNMEALocationProvider : public LocationProvider { char fix_age[11]; formatAge(uart_age, sizeof(uart_age), _uart_seen, _last_uart_ms); formatAge(nmea_age, sizeof(nmea_age), _nmea_seen, _last_nmea_ms); - formatAge(fix_age, sizeof(fix_age), _fix_seen, _last_fix_ms); + formatAge(fix_age, sizeof(fix_age), _lastFix.valid, _lastFix.acquiredAt); snprintf(out, out_size, "en:%u ub:%lu ua:%s ok:%lu bad:%lu na:%s sat:%ld fix:%u fa:%s bc:%lu sc:%lu", diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h index c909b7c4277..db0b957b355 100644 --- a/src/helpers/sensors/RakLocationProvider.h +++ b/src/helpers/sensors/RakLocationProvider.h @@ -11,6 +11,18 @@ class RakLocationProvider : public LocationProvider { unsigned long _acquisitionStarted = 0; bool _acquiringPeriodically = false; + void saveRak12500Fix() { + LastFix fix; + fix.latitude = _driver.getLatitude(); + fix.longitude = _driver.getLongitude(); + fix.altitude = _driver.getAltitude(); + fix.satellites = _driver.getSatellites(); + fix.timestamp = _driver.getTimestamp(); + fix.acquiredAt = millis(); + fix.valid = true; + _lastFix = fix; + } + public: RakLocationProvider(RakGpsDriver& driver, mesh::RTCClock* clock = nullptr) : _driver(driver), _nmea(driver.nmeaDriver(), clock) {} @@ -21,27 +33,31 @@ class RakLocationProvider : public LocationProvider { } long getLatitude() override { - return _driver.usesRak12500() ? _driver.getLatitude() : _nmea.getLatitude(); + return _driver.usesRak12500() ? _lastFix.latitude : _nmea.getLatitude(); } long getLongitude() override { - return _driver.usesRak12500() ? _driver.getLongitude() : _nmea.getLongitude(); + return _driver.usesRak12500() ? _lastFix.longitude : _nmea.getLongitude(); } long getAltitude() override { - return _driver.usesRak12500() ? _driver.getAltitude() : _nmea.getAltitude(); + return _driver.usesRak12500() ? _lastFix.altitude : _nmea.getAltitude(); } long satellitesCount() override { - return _driver.usesRak12500() ? _driver.getSatellites() : _nmea.satellitesCount(); + return _driver.usesRak12500() ? _lastFix.satellites : _nmea.satellitesCount(); } bool isValid() override { return _driver.usesRak12500() ? _driver.hasFix() : _nmea.isValid(); } + LastFix getLastFix() const override { + return _driver.usesRak12500() ? _lastFix : _nmea.getLastFix(); + } + long getTimestamp() override { - return _driver.usesRak12500() ? _driver.getTimestamp() : _nmea.getTimestamp(); + return _driver.usesRak12500() ? _lastFix.timestamp : _nmea.getTimestamp(); } void sendSentence(const char* sentence) override { @@ -104,18 +120,24 @@ class RakLocationProvider : public LocationProvider { void loop() override { if (_driver.usesRak12500()) { - if (_gpsOn && _gpsPeriodSeconds > 0) { - if (!_acquiringPeriodically && - (long)(millis() - _nextAcquisition) >= 0) { - _driver.begin(); - _acquiringPeriodically = true; - _acquisitionStarted = millis(); + if (_gpsOn) { + if (_gpsPeriodSeconds > 0) { + if (!_acquiringPeriodically && + (long)(millis() - _nextAcquisition) >= 0) { + _driver.begin(); + _acquiringPeriodically = true; + _acquisitionStarted = millis(); + } } - if (_acquiringPeriodically && isValid()) { + + const bool hasCurrentFix = _driver.hasFix(); + if (hasCurrentFix) saveRak12500Fix(); + + if (_gpsPeriodSeconds > 0 && _acquiringPeriodically && hasCurrentFix) { _driver.stop(); _acquiringPeriodically = false; _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; - } else if (_acquiringPeriodically && + } else if (_gpsPeriodSeconds > 0 && _acquiringPeriodically && (uint32_t)(millis() - _acquisitionStarted) >= GPS_PERIODIC_ACQUISITION_TIMEOUT_MS) { _driver.stop(); From 4ac1ed1d9e9662ad3ca25353b487c5e44ec3c49f Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 3 Nov 2026 15:52:59 -0400 Subject: [PATCH 04/23] gps: periodic acquisition rules (timeout, sats and settle time), isEnabled renamed isActive --- src/helpers/CommonGpsPrefs.cpp | 4 ++- src/helpers/sensors/AirohaGpsDriver.h | 2 +- .../sensors/EnvironmentSensorManager.cpp | 4 +-- src/helpers/sensors/GpsDriver.h | 2 +- src/helpers/sensors/LocationProvider.h | 4 +-- .../sensors/MicroNMEALocationProvider.h | 33 +++++++++++++++---- src/helpers/sensors/NmeaGpsDriver.h | 2 +- src/helpers/sensors/RakGpsDriver.h | 4 +-- src/helpers/sensors/RakLocationProvider.h | 11 +++++-- 9 files changed, 46 insertions(+), 20 deletions(-) diff --git a/src/helpers/CommonGpsPrefs.cpp b/src/helpers/CommonGpsPrefs.cpp index 248ae540b88..e08878a6fd9 100644 --- a/src/helpers/CommonGpsPrefs.cpp +++ b/src/helpers/CommonGpsPrefs.cpp @@ -39,7 +39,7 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, strcpy(reply, "gps provider not found"); } else if (location->isGpsOn()) { const char* activity = "active"; - if (!location->isEnabled()) { + if (!location->isActive()) { if (location->hasHardwareEnableState() && !location->isHardwareEnabled()) { activity = "off(hw)"; } else if (getGpsPeriod() > 0) { @@ -56,6 +56,8 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, strcpy(fix, "no fix"); } snprintf(reply, 160, "on, %s, %s, mode: %s", activity, fix, mode); + } else { + snprintf(reply, 160, "off, mode: %s", mode); } return true; } diff --git a/src/helpers/sensors/AirohaGpsDriver.h b/src/helpers/sensors/AirohaGpsDriver.h index 36d5eb6b5d6..cd94d4195e7 100644 --- a/src/helpers/sensors/AirohaGpsDriver.h +++ b/src/helpers/sensors/AirohaGpsDriver.h @@ -77,7 +77,7 @@ class AirohaGpsDriver : public NmeaGpsDriver { NmeaGpsDriver::stop(); } - bool isEnabled() const override { + bool isActive() const override { return digitalRead(GPS_EN) == HIGH; } }; diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index 2cf8b66b14b..ee942df1443 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -631,7 +631,7 @@ bool EnvironmentSensorManager::querySensors(uint8_t requester_permissions, Cayen next_available_channel = TELEM_CHANNEL_SELF + 1; #if ENV_INCLUDE_GPS - if (requester_permissions & TELEM_PERM_LOCATION && _location->isEnabled()) { + if (requester_permissions & TELEM_PERM_LOCATION && _location->isGpsOn()) { telemetry.addGPS(TELEM_CHANNEL_SELF, node_lat, node_lon, node_altitude); } #endif @@ -748,7 +748,7 @@ void EnvironmentSensorManager::loop() { _location->loop(); if ((long)(millis() - next_gps_update) > 0) { - if (_location->isEnabled() && _location->isValid()) { + if (_location->isGpsOn() && _location->hasValidFix()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; MESH_DEBUG_PRINTLN("lat %f lon %f", node_lat, node_lon); diff --git a/src/helpers/sensors/GpsDriver.h b/src/helpers/sensors/GpsDriver.h index b8cf4a5d569..b17a6b27728 100644 --- a/src/helpers/sensors/GpsDriver.h +++ b/src/helpers/sensors/GpsDriver.h @@ -9,7 +9,7 @@ class GpsDriver { virtual void begin() = 0; virtual void reset() = 0; virtual void stop() = 0; - virtual bool isEnabled() const = 0; + virtual bool isActive() const = 0; virtual bool hasHardwareEnableState() const { return false; } virtual bool isHardwareEnabled() const { return true; } // Returns true when the switch explicitly enables GPS in software. diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index dc316e1d38f..5bb6ec6a77d 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -63,14 +63,14 @@ class LocationProvider { virtual void begin() = 0; virtual void stop() = 0; virtual void loop() = 0; - virtual bool isEnabled() = 0; + virtual bool isActive() = 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) { if (out_size == 0) return; snprintf(out, out_size, "en:%u sat:%ld fix:%u", - isEnabled() ? 1U : 0U, + isActive() ? 1U : 0U, satellitesCount(), isValid() ? 1U : 0U); } diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index cceffd8ca31..b07f486b166 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -7,7 +7,16 @@ #include #ifndef GPS_PERIODIC_ACQUISITION_TIMEOUT_MS - #define GPS_PERIODIC_ACQUISITION_TIMEOUT_MS 120000UL + #define GPS_PERIODIC_ACQUISITION_TIMEOUT_MS 300000UL +#endif + +// in periodic mode, keep reading after the first valid fix until enough satellites +// are used or the settle time has elapsed +#ifndef GPS_MIN_SATS_FOR_FIX + #define GPS_MIN_SATS_FOR_FIX 5 +#endif +#ifndef GPS_FIX_SETTLE_MS + #define GPS_FIX_SETTLE_MS 10000UL #endif class MicroNMEALocationProvider : public LocationProvider { @@ -22,6 +31,7 @@ class MicroNMEALocationProvider : public LocationProvider { uint32_t _gpsPeriodSeconds = 0; unsigned long _nextAcquisition = 0; unsigned long _acquisitionStarted = 0; + unsigned long _firstFixInWindow = 0; bool _acquiringPeriodically = false; static const unsigned long TIME_SYNC_INTERVAL = 1800000; @@ -54,6 +64,7 @@ class MicroNMEALocationProvider : public LocationProvider { _driver->reset(); _acquiringPeriodically = true; _acquisitionStarted = millis(); + _firstFixInWindow = 0; } void finishPeriodicAcquisition() { @@ -122,11 +133,12 @@ class MicroNMEALocationProvider : public LocationProvider { void begin() override { _begin_calls++; _driver->begin(); - if (_gpsPeriodSeconds > 0 && _driver->isEnabled()) { + if (_gpsPeriodSeconds > 0 && _driver->isActive()) { nmea.clear(); time_valid = 0; _acquiringPeriodically = true; _acquisitionStarted = millis(); + _firstFixInWindow = 0; } } @@ -140,8 +152,8 @@ class MicroNMEALocationProvider : public LocationProvider { _driver->stop(); } - bool isEnabled() override { - return _driver->isEnabled(); + bool isActive() override { + return _driver->isActive(); } bool hasHardwareEnableState() const override { @@ -249,7 +261,13 @@ class MicroNMEALocationProvider : public LocationProvider { if (parsed && nmea.isValid() && (strcmp(messageId, "GGA") == 0 || strcmp(messageId, "RMC") == 0)) { saveFix(); - if (_acquiringPeriodically) finishPeriodicAcquisition(); + if (_acquiringPeriodically) { + if (_firstFixInWindow == 0) _firstFixInWindow = millis(); + if (nmea.getNumSatellites() >= GPS_MIN_SATS_FOR_FIX || + (uint32_t)(millis() - _firstFixInWindow) >= GPS_FIX_SETTLE_MS) { + finishPeriodicAcquisition(); + } + } } } else { _nmea_checksum_bad++; @@ -257,7 +275,8 @@ class MicroNMEALocationProvider : public LocationProvider { } } - if (_acquiringPeriodically && + // no timeout until a first fix has been obtained (cold start can be long) + if (_acquiringPeriodically && _lastFix.valid && (uint32_t)(millis() - _acquisitionStarted) >= GPS_PERIODIC_ACQUISITION_TIMEOUT_MS) { finishPeriodicAcquisition(); } @@ -277,7 +296,7 @@ class MicroNMEALocationProvider : public LocationProvider { snprintf(out, out_size, "en:%u ub:%lu ua:%s ok:%lu bad:%lu na:%s sat:%ld fix:%u fa:%s bc:%lu sc:%lu", - isEnabled() ? 1U : 0U, + isActive() ? 1U : 0U, (unsigned long)_uart_bytes, uart_age, (unsigned long)_nmea_checksum_ok, diff --git a/src/helpers/sensors/NmeaGpsDriver.h b/src/helpers/sensors/NmeaGpsDriver.h index f9ea5124aec..abecb817603 100644 --- a/src/helpers/sensors/NmeaGpsDriver.h +++ b/src/helpers/sensors/NmeaGpsDriver.h @@ -183,7 +183,7 @@ class NmeaGpsDriver : public GpsDriver { stopHardware(); } - bool isEnabled() const override { + bool isActive() const override { return _started && isHardwareEnabled(); } diff --git a/src/helpers/sensors/RakGpsDriver.h b/src/helpers/sensors/RakGpsDriver.h index fb4b772bf41..b37548528f9 100644 --- a/src/helpers/sensors/RakGpsDriver.h +++ b/src/helpers/sensors/RakGpsDriver.h @@ -77,8 +77,8 @@ class RakGpsDriver : public GpsDriver { _serialDriver.stop(); } - bool isEnabled() const override { - return _usingRak12500 ? true : _serialDriver.isEnabled(); + bool isActive() const override { + return _usingRak12500 ? true : _serialDriver.isActive(); } bool detect(uint32_t) override { diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h index db0b957b355..57ceb5e9fc8 100644 --- a/src/helpers/sensors/RakLocationProvider.h +++ b/src/helpers/sensors/RakLocationProvider.h @@ -137,7 +137,7 @@ class RakLocationProvider : public LocationProvider { _driver.stop(); _acquiringPeriodically = false; _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; - } else if (_gpsPeriodSeconds > 0 && _acquiringPeriodically && + } else if (_gpsPeriodSeconds > 0 && _acquiringPeriodically && _lastFix.valid && (uint32_t)(millis() - _acquisitionStarted) >= GPS_PERIODIC_ACQUISITION_TIMEOUT_MS) { _driver.stop(); @@ -150,8 +150,8 @@ class RakLocationProvider : public LocationProvider { } } - bool isEnabled() override { - return _driver.isEnabled(); + bool isActive() override { + return _driver.isActive(); } void setGpsPeriod(uint32_t seconds) override { @@ -163,4 +163,9 @@ class RakLocationProvider : public LocationProvider { uint32_t getGpsPeriod() const override { return _gpsPeriodSeconds; } + + void setGpsOn(bool on) override { + if (_driver.usesRak12500()) LocationProvider::setGpsOn(on); + else { _gpsOn = on; _nmea.setGpsOn(on); } + } }; From fa7ca3f4181942d4dc623db4d8336ec0a1b5f1e4 Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 3 Nov 2026 21:10:01 -0400 Subject: [PATCH 05/23] rak_gps: simplify the rak driver, which now only handles i2c, for nmea use nmea driver directly --- .../{RakGpsDriver.h => RakI2cGpsDriver.h} | 50 +++++--------- src/helpers/sensors/RakLocationProvider.h | 67 ++++++++++++------- variants/gat562_30s_mesh_kit/target.cpp | 2 +- variants/rak3401/target.cpp | 2 +- variants/rak4631/target.cpp | 2 +- 5 files changed, 61 insertions(+), 62 deletions(-) rename src/helpers/sensors/{RakGpsDriver.h => RakI2cGpsDriver.h} (65%) diff --git a/src/helpers/sensors/RakGpsDriver.h b/src/helpers/sensors/RakI2cGpsDriver.h similarity index 65% rename from src/helpers/sensors/RakGpsDriver.h rename to src/helpers/sensors/RakI2cGpsDriver.h index b37548528f9..ac76e069297 100644 --- a/src/helpers/sensors/RakGpsDriver.h +++ b/src/helpers/sensors/RakI2cGpsDriver.h @@ -1,16 +1,16 @@ #pragma once -#include "NmeaGpsDriver.h" +#include "GpsDriver.h" #include -class RakGpsDriver : public GpsDriver { - NmeaGpsDriver _serialDriver; +// Driver for the RAK12500 (u-blox, I2C). Serial NMEA modules are handled by NmeaGpsDriver. +class RakI2cGpsDriver : public GpsDriver { TwoWire& _wire; SFE_UBLOX_GNSS _gnss; uint8_t _powerPin = 0; - bool _usingRak12500 = false; bool _detected = false; + bool _started = false; bool probeRak12500(uint8_t powerPin) { #if defined(ETHERNET_ENABLED) && defined(RAK_BOARD) @@ -37,7 +37,6 @@ class RakGpsDriver : public GpsDriver { _gnss.setMeasurementRate(1000); _gnss.saveConfigSelective(VAL_CFG_SUBSEC_IOPORT); _powerPin = powerPin; - _usingRak12500 = true; return true; } @@ -48,62 +47,47 @@ class RakGpsDriver : public GpsDriver { } public: - RakGpsDriver(HardwareSerial& serial = Serial1, TwoWire& wire = Wire) - : _serialDriver(serial), _wire(wire) {} + RakI2cGpsDriver(TwoWire& wire = Wire) : _wire(wire) {} void begin() override { - if (_usingRak12500) { + if (!_detected) return; #ifndef RAK_3401 - pinMode(_powerPin, OUTPUT); - digitalWrite(_powerPin, HIGH); + pinMode(_powerPin, OUTPUT); + digitalWrite(_powerPin, HIGH); #endif - return; - } - _serialDriver.begin(); + _started = true; } - void reset() override { - if (!_usingRak12500) _serialDriver.reset(); - } + void reset() override {} void stop() override { - if (_usingRak12500) { + if (_detected) { #ifndef RAK_3401 pinMode(_powerPin, OUTPUT); digitalWrite(_powerPin, LOW); #endif - return; } - _serialDriver.stop(); + _started = false; } bool isActive() const override { - return _usingRak12500 ? true : _serialDriver.isActive(); + return _started; } + // Probes the I2C module on the possible power pins; the module is left powered. bool detect(uint32_t) override { if (_detected) return true; - _serialDriver.begin(); if (probeRak12500(WB_IO2) || probeRak12500(WB_IO4) || probeRak12500(WB_IO5)) { _detected = true; - return true; - } - - if (_serialDriver.detect(0)) { - _detected = true; - return true; + _started = true; } - - _serialDriver.stop(); - return false; + return _detected; } - bool usesRak12500() const { return _usingRak12500; } bool isDetected() const { return _detected; } - NmeaGpsDriver& nmeaDriver() { return _serialDriver; } - bool hasFix() { return _usingRak12500 && _gnss.getGnssFixOk(8); } + bool hasFix() { return _started && _gnss.getGnssFixOk(8); } long getLatitude() { return _gnss.getLatitude(2) / 10; } long getLongitude() { return _gnss.getLongitude(2) / 10; } long getAltitude() { return _gnss.getAltitude(2); } diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h index 57ceb5e9fc8..d2c755e63d5 100644 --- a/src/helpers/sensors/RakLocationProvider.h +++ b/src/helpers/sensors/RakLocationProvider.h @@ -1,11 +1,15 @@ #pragma once #include "MicroNMEALocationProvider.h" -#include "RakGpsDriver.h" +#include "NmeaGpsDriver.h" +#include "RakI2cGpsDriver.h" class RakLocationProvider : public LocationProvider { - RakGpsDriver& _driver; + RakI2cGpsDriver& _driver; // RAK12500 (I2C) + NmeaGpsDriver _nmeaDriver; // serial NMEA module MicroNMEALocationProvider _nmea; + bool _detected = false; + bool _useI2c = false; uint32_t _gpsPeriodSeconds = 0; unsigned long _nextAcquisition = 0; unsigned long _acquisitionStarted = 0; @@ -24,73 +28,74 @@ class RakLocationProvider : public LocationProvider { } public: - RakLocationProvider(RakGpsDriver& driver, mesh::RTCClock* clock = nullptr) - : _driver(driver), _nmea(driver.nmeaDriver(), clock) {} + RakLocationProvider(RakI2cGpsDriver& driver, mesh::RTCClock* clock = nullptr, + HardwareSerial& serial = Serial1) + : _driver(driver), _nmeaDriver(serial), _nmea(_nmeaDriver, clock) {} void syncTime() override { LocationProvider::syncTime(); - if (!_driver.usesRak12500()) _nmea.syncTime(); + if (!_useI2c) _nmea.syncTime(); } long getLatitude() override { - return _driver.usesRak12500() ? _lastFix.latitude : _nmea.getLatitude(); + return _useI2c ? _lastFix.latitude : _nmea.getLatitude(); } long getLongitude() override { - return _driver.usesRak12500() ? _lastFix.longitude : _nmea.getLongitude(); + return _useI2c ? _lastFix.longitude : _nmea.getLongitude(); } long getAltitude() override { - return _driver.usesRak12500() ? _lastFix.altitude : _nmea.getAltitude(); + return _useI2c ? _lastFix.altitude : _nmea.getAltitude(); } long satellitesCount() override { - return _driver.usesRak12500() ? _lastFix.satellites : _nmea.satellitesCount(); + return _useI2c ? _lastFix.satellites : _nmea.satellitesCount(); } bool isValid() override { - return _driver.usesRak12500() ? _driver.hasFix() : _nmea.isValid(); + return _useI2c ? _driver.hasFix() : _nmea.isValid(); } LastFix getLastFix() const override { - return _driver.usesRak12500() ? _lastFix : _nmea.getLastFix(); + return _useI2c ? _lastFix : _nmea.getLastFix(); } long getTimestamp() override { - return _driver.usesRak12500() ? _lastFix.timestamp : _nmea.getTimestamp(); + return _useI2c ? _lastFix.timestamp : _nmea.getTimestamp(); } void sendSentence(const char* sentence) override { - if (!_driver.usesRak12500()) _nmea.sendSentence(sentence); + if (!_useI2c) _nmea.sendSentence(sentence); } bool waitFor(const char* prefix, uint32_t timeout_ms) override { - return _driver.usesRak12500() ? false : _nmea.waitFor(prefix, timeout_ms); + return _useI2c ? false : _nmea.waitFor(prefix, timeout_ms); } void drain() override { - if (!_driver.usesRak12500()) _nmea.drain(); + if (!_useI2c) _nmea.drain(); } void reset() override { - _driver.reset(); + if (!_useI2c) _nmea.reset(); } void begin() override { - if (_driver.isDetected() && _driver.usesRak12500()) { + if (_detected && _useI2c) { _driver.begin(); _acquiringPeriodically = _gpsPeriodSeconds > 0; _acquisitionStarted = millis(); - } else if (_driver.isDetected()) { + } else if (_detected) { _nmea.begin(); } else { - _driver.begin(); + _nmeaDriver.begin(); } } void stop() override { _acquiringPeriodically = false; - if (_driver.usesRak12500()) { + if (_useI2c) { _driver.stop(); } else { _nmea.stop(); @@ -98,9 +103,19 @@ class RakLocationProvider : public LocationProvider { } bool detect(uint32_t timeout_ms) override { - if (!_driver.detect(timeout_ms)) return false; + if (_detected) return true; + + // the serial module is started first, then the I2C module is probed + _nmeaDriver.begin(); + if (_driver.detect(timeout_ms)) { + _useI2c = true; + } else if (!_nmeaDriver.detect(0)) { + _nmeaDriver.stop(); + return false; + } + _detected = true; - if (_driver.usesRak12500()) { + if (_useI2c) { _acquiringPeriodically = _gpsPeriodSeconds > 0; _acquisitionStarted = millis(); } else { @@ -119,7 +134,7 @@ class RakLocationProvider : public LocationProvider { } void loop() override { - if (_driver.usesRak12500()) { + if (_useI2c) { if (_gpsOn) { if (_gpsPeriodSeconds > 0) { if (!_acquiringPeriodically && @@ -151,13 +166,13 @@ class RakLocationProvider : public LocationProvider { } bool isActive() override { - return _driver.isActive(); + return _useI2c ? _driver.isActive() : _nmea.isActive(); } void setGpsPeriod(uint32_t seconds) override { _gpsPeriodSeconds = seconds; _nextAcquisition = millis(); - if (!_driver.usesRak12500()) _nmea.setGpsPeriod(seconds); + if (!_useI2c) _nmea.setGpsPeriod(seconds); } uint32_t getGpsPeriod() const override { @@ -165,7 +180,7 @@ class RakLocationProvider : public LocationProvider { } void setGpsOn(bool on) override { - if (_driver.usesRak12500()) LocationProvider::setGpsOn(on); + if (_useI2c) LocationProvider::setGpsOn(on); else { _gpsOn = on; _nmea.setGpsOn(on); } } }; diff --git a/variants/gat562_30s_mesh_kit/target.cpp b/variants/gat562_30s_mesh_kit/target.cpp index ba81a690d9d..96e38a79ee6 100644 --- a/variants/gat562_30s_mesh_kit/target.cpp +++ b/variants/gat562_30s_mesh_kit/target.cpp @@ -27,7 +27,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - RakGpsDriver gps_driver; + RakI2cGpsDriver gps_driver; RakLocationProvider location(gps_driver, &rtc_clock); EnvironmentSensorManager sensors(location); #else diff --git a/variants/rak3401/target.cpp b/variants/rak3401/target.cpp index 2650197704a..58c0e1d782d 100644 --- a/variants/rak3401/target.cpp +++ b/variants/rak3401/target.cpp @@ -26,7 +26,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - RakGpsDriver gps_driver; + RakI2cGpsDriver gps_driver; RakLocationProvider location(gps_driver, &rtc_clock); EnvironmentSensorManager sensors(location); #else diff --git a/variants/rak4631/target.cpp b/variants/rak4631/target.cpp index 06ccd23dbb3..158de2b5571 100644 --- a/variants/rak4631/target.cpp +++ b/variants/rak4631/target.cpp @@ -26,7 +26,7 @@ AutoDiscoverRTCClock rtc_clock(fallback_clock); #if ENV_INCLUDE_GPS #include - RakGpsDriver gps_driver; + RakI2cGpsDriver gps_driver; RakLocationProvider location(gps_driver, &rtc_clock); EnvironmentSensorManager sensors(location); #else From 3fd36e7ac6c6af70efc66e3f4bc3434636870e03 Mon Sep 17 00:00:00 2001 From: Florent Date: Tue, 3 Nov 2026 21:29:21 -0400 Subject: [PATCH 06/23] gps: adapt ui-new and the sensor managers --- examples/companion_radio/ui-new/UITask.cpp | 51 ++++++++++++++++--- variants/heltec_mesh_solar/target.cpp | 2 +- variants/heltec_tracker/target.cpp | 2 +- variants/meshadventurer/target.cpp | 4 +- variants/meshtracker_x1/target.cpp | 2 +- variants/nano_g2_ultra/NanoG2UltraGpsDriver.h | 2 +- variants/nano_g2_ultra/target.cpp | 4 +- variants/t1000-e/target.cpp | 2 +- variants/thinknode_m1/target.cpp | 2 +- 9 files changed, 53 insertions(+), 18 deletions(-) diff --git a/examples/companion_radio/ui-new/UITask.cpp b/examples/companion_radio/ui-new/UITask.cpp index a1400669a64..6b6af33edd7 100644 --- a/examples/companion_radio/ui-new/UITask.cpp +++ b/examples/companion_radio/ui-new/UITask.cpp @@ -86,6 +86,30 @@ class SplashScreen : public UIScreen { } }; +#if ENV_INCLUDE_GPS == 1 +// exact period with the largest unit that divides it: "30s", "5m", "2h" +static void formatGpsPeriod(char* buf, size_t size, uint32_t seconds) { + if (seconds % 3600 == 0) { + snprintf(buf, size, "%luh", (unsigned long)(seconds / 3600)); + } else if (seconds % 60 == 0) { + snprintf(buf, size, "%lum", (unsigned long)(seconds / 60)); + } else { + snprintf(buf, size, "%lus", (unsigned long)seconds); + } +} + +// age of the last fix, rounded down to a single unit: "45s", "12m", "3h" +static void formatFixAge(char* buf, size_t size, uint32_t seconds) { + if (seconds < 60) { + snprintf(buf, size, "%lus", (unsigned long)seconds); + } else if (seconds < 3600) { + snprintf(buf, size, "%lum", (unsigned long)(seconds / 60)); + } else { + snprintf(buf, size, "%luh", (unsigned long)(seconds / 3600)); + } +} +#endif + class HomeScreen : public UIScreen { enum HomePage { FIRST, @@ -393,14 +417,26 @@ class HomeScreen : public UIScreen { int y = 18; bool gps_state = _task->getGPSState(); bool hw_gps_state = nmea != NULL && nmea->isHardwareEnabled(); - if (nmea != NULL && nmea->hasHardwareEnableState()) { - if (gps_state != hw_gps_state) { - strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); - } else { - strcpy(buf, gps_state ? "gps on" : "gps off"); + uint32_t period = nmea != NULL ? nmea->getGpsPeriod() : 0; + char fix_buf[12] = ""; + if (nmea != NULL) { + uint32_t fix_age = nmea->getLastFixAge(); + if (fix_age == UINT32_MAX) { + strcpy(fix_buf, "no fix"); + } else { + formatFixAge(fix_buf, sizeof(fix_buf), fix_age); + } } + if (nmea != NULL && nmea->hasHardwareEnableState() && gps_state != hw_gps_state) { + strcpy(buf, gps_state ? "gps off(hw)" : "gps off(sw)"); + } else if (!gps_state) { + strcpy(buf, "gps off"); + } else if (period == 0) { + strcpy(buf, "gps on"); } else { - strcpy(buf, gps_state ? "gps on" : "gps off"); + char per[12]; + formatGpsPeriod(per, sizeof(per), period); + snprintf(buf, sizeof(buf), "%s (%s)", nmea != NULL && nmea->isActive() ? "active" : "sleep", per); } display.setColor(UIColor::primary_txt); display.drawTextLeftAlign(0, y, buf); @@ -410,8 +446,7 @@ class HomeScreen : public UIScreen { display.drawTextLeftAlign(0, y, "Can't access GPS"); } else { display.setColor(UIColor::primary_txt); - strcpy(buf, nmea->isValid()?"fix":"no fix"); - display.drawTextRightAlign(display.width()-1, y, buf); + display.drawTextRightAlign(display.width()-1, y, fix_buf); y = y + 12; display.setColor(UIColor::secondary_txt); display.drawTextLeftAlign(0, y, "sat"); diff --git a/variants/heltec_mesh_solar/target.cpp b/variants/heltec_mesh_solar/target.cpp index 9468f8fc4d8..9e7fb04d189 100644 --- a/variants/heltec_mesh_solar/target.cpp +++ b/variants/heltec_mesh_solar/target.cpp @@ -66,7 +66,7 @@ void SolarSensorManager::loop() { _location->loop(); if (millis() > next_gps_update) { - if (_location->isValid()) { + if (_location->isGpsOn() && _location->hasValidFix()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 5903f6597f0..041cb2b858e 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -82,7 +82,7 @@ void HWTSensorManager::loop() { _location->loop(); if (millis() > next_gps_update) { - if (_location->isEnabled() && _location->isValid()) { + if (_location->isGpsOn() && _location->hasValidFix()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index faf2e30ef71..827e23b7305 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -64,8 +64,8 @@ bool MASensorManager::querySensors(uint8_t requester_permissions, CayenneLPP& te void MASensorManager::loop() { static long next_gps_update = 0; _location->loop(); - if(millis() > next_gps_update && _location->isEnabled()) { - if(_location->isValid()) { + if(millis() > next_gps_update && _location->isGpsOn()) { + if(_location->hasValidFix()) { node_lat = ((double)_location->getLatitude()) / 1000000.; node_lon = ((double)_location->getLongitude()) / 1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; diff --git a/variants/meshtracker_x1/target.cpp b/variants/meshtracker_x1/target.cpp index 2dee0ebd137..84e0870e2ba 100644 --- a/variants/meshtracker_x1/target.cpp +++ b/variants/meshtracker_x1/target.cpp @@ -70,7 +70,7 @@ void MeshTrackerX1SensorManager::loop() { _nmea->loop(); if (millis() > next_gps_update) { - if (_nmea->isEnabled() && _nmea->isValid()) { + if (_nmea->isGpsOn() && _nmea->hasValidFix()) { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; node_altitude = ((double)_nmea->getAltitude()) / 1000.0; diff --git a/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h b/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h index 3772084b75a..7e3bd10968b 100644 --- a/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h +++ b/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h @@ -10,7 +10,7 @@ class NanoG2UltraGpsDriver : public NmeaGpsDriver { } void begin() override { - if (isEnabled()) return; + if (isActive()) return; digitalWrite(PIN_GPS_STANDBY, HIGH); NmeaGpsDriver::begin(); diff --git a/variants/nano_g2_ultra/target.cpp b/variants/nano_g2_ultra/target.cpp index ee258f2d912..fedc48e7fa3 100644 --- a/variants/nano_g2_ultra/target.cpp +++ b/variants/nano_g2_ultra/target.cpp @@ -62,14 +62,14 @@ bool NanoG2UltraSensorManager::querySensors(uint8_t requester_permissions, Cayen void NanoG2UltraSensorManager::loop() { static long next_gps_update = 0; - if (!_location->isEnabled()) { + if (!_location->isGpsOn()) { return; // GPS is not active, skip further processing } _location->loop(); if (millis() > next_gps_update) { - if (_location->isEnabled() && _location->isValid()) { + if (_location->isGpsOn() && _location->hasValidFix()) { node_lat = ((double)_location->getLatitude()) / 1000000.; node_lon = ((double)_location->getLongitude()) / 1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; diff --git a/variants/t1000-e/target.cpp b/variants/t1000-e/target.cpp index b96d01f88ae..609c32bc7f2 100644 --- a/variants/t1000-e/target.cpp +++ b/variants/t1000-e/target.cpp @@ -113,7 +113,7 @@ void T1000SensorManager::loop() { _nmea->loop(); if (millis() > next_gps_update) { - if (_nmea->isEnabled() && _nmea->isValid()) { + if (_nmea->isGpsOn() && _nmea->hasValidFix()) { node_lat = ((double)_nmea->getLatitude())/1000000.; node_lon = ((double)_nmea->getLongitude())/1000000.; node_altitude = ((double)_nmea->getAltitude()) / 1000.0; diff --git a/variants/thinknode_m1/target.cpp b/variants/thinknode_m1/target.cpp index 78f5a88e6f7..4183c514fb4 100644 --- a/variants/thinknode_m1/target.cpp +++ b/variants/thinknode_m1/target.cpp @@ -58,7 +58,7 @@ void ThinkNodeM1SensorManager::loop() { _location->loop(); if (millis() > next_gps_update) { - if (_location->isEnabled() && _location->isValid()) { + if (_location->isGpsOn() && _location->hasValidFix()) { node_lat = ((double)_location->getLatitude())/1000000.; node_lon = ((double)_location->getLongitude())/1000000.; node_altitude = ((double)_location->getAltitude()) / 1000.0; From d49621c805c4fef1be68876656df64e4251baf12 Mon Sep 17 00:00:00 2001 From: Florent Date: Wed, 4 Nov 2026 09:02:38 -0400 Subject: [PATCH 07/23] gps: fix_stats command --- docs/cli_commands.md | 8 +++++ docs/companion_cli.md | 8 +++++ src/helpers/CommonGpsPrefs.cpp | 32 +++++++++++++++-- src/helpers/sensors/LocationProvider.h | 20 +++++++++++ .../sensors/MicroNMEALocationProvider.h | 35 ++++++++++++++----- src/helpers/sensors/RakLocationProvider.h | 13 ++++++- 6 files changed, 105 insertions(+), 11 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index eb8c6b20c89..d7b8d5d016f 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1044,6 +1044,14 @@ region save --- +#### View GPS time to first fix statistics +**Usage:** +- `gps fix_stats` + +**Output:** `ttf:/ ok: miss:`: last and maximum time between a start of the GPS and its first valid fix, then the last count of consecutive starts with a fix (`ok`) or without one (`miss`). + +--- + #### View GPS diagnostics **Usage:** - `gps diag` diff --git a/docs/companion_cli.md b/docs/companion_cli.md index 24843269e7b..0b3a27df806 100644 --- a/docs/companion_cli.md +++ b/docs/companion_cli.md @@ -165,6 +165,14 @@ Radio parameters are common to all roles and handled by `CommonRadioPrefs`. --- +#### View GPS time to first fix statistics +**Usage:** +- `gps fix_stats` + +**Output:** `ttf:/ ok: miss:`: last and maximum time between a start of the GPS and its first valid fix, then the last count of consecutive starts with a fix (`ok`) or without one (`miss`). + +--- + #### View GPS diagnostics **Usage:** - `gps diag` diff --git a/src/helpers/CommonGpsPrefs.cpp b/src/helpers/CommonGpsPrefs.cpp index e08878a6fd9..c4936d4f6e7 100644 --- a/src/helpers/CommonGpsPrefs.cpp +++ b/src/helpers/CommonGpsPrefs.cpp @@ -25,6 +25,17 @@ bool CommonGpsPrefs::apply(SensorManager& sensors) const { return true; } +// single unit, rounded down: "45s", "12m", "3h" +static void formatDuration(char* buf, size_t size, uint32_t seconds) { + if (seconds < 60) { + snprintf(buf, size, "%lus", (unsigned long)seconds); + } else if (seconds < 3600) { + snprintf(buf, size, "%lum", (unsigned long)(seconds / 60)); + } else { + snprintf(buf, size, "%luh", (unsigned long)(seconds / 3600)); + } +} + bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, char* reply) { if (strcmp(command, "gps") == 0) { LocationProvider* location = sensors.getLocationProvider(); @@ -50,8 +61,9 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, } char fix[32]; if (location->hasValidFix()) { - snprintf(fix, sizeof(fix), "%ld sats %lus ago", - location->satellitesCount(), (unsigned long)location->getLastFixAge()); + char age[12]; + formatDuration(age, sizeof(age), location->getLastFixAge()); + snprintf(fix, sizeof(fix), "%ld sats %s ago", location->satellitesCount(), age); } else { strcpy(fix, "no fix"); } @@ -134,6 +146,22 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, } return true; } + if (strcmp(command, "gps fix_stats") == 0) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) { + strcpy(reply, "gps provider not found"); + } else { + const LocationProvider::FixStats s = location->getFixStats(); + char last[12] = "-", max[12] = "-"; + if (s.maxTtffMs > 0) { + formatDuration(last, sizeof(last), (s.lastTtffMs + 500) / 1000); + formatDuration(max, sizeof(max), (s.maxTtffMs + 500) / 1000); + } + snprintf(reply, 160, "ttf:%s/%s ok:%lu miss:%lu", last, max, + (unsigned long)s.consecutiveFixes, (unsigned long)s.consecutiveMisses); + } + return true; + } if (strcmp(command, "gps diag") == 0) { LocationProvider* location = sensors.getLocationProvider(); if (location == nullptr) { diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index 5bb6ec6a77d..3d7abd7ae67 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -18,10 +18,29 @@ class LocationProvider { unsigned long acquiredAt = 0; // millis() when the fix was stored }; + struct FixStats { + uint32_t lastTtffMs = 0; //time to first fix in ms + uint32_t maxTtffMs = 0; + uint32_t consecutiveFixes = 0; + uint32_t consecutiveMisses = 0; + }; + protected: bool _time_sync_needed = true; bool _gpsOn = false; LastFix _lastFix; + FixStats _fixStats; + + void fixStatsFix(uint32_t ttffMs) { + _fixStats.lastTtffMs = ttffMs; + if (ttffMs > _fixStats.maxTtffMs) _fixStats.maxTtffMs = ttffMs; + _fixStats.consecutiveFixes++; + _fixStats.consecutiveMisses = 0; + } + void fixStatsMiss() { + _fixStats.consecutiveMisses++; + _fixStats.consecutiveFixes = 0; + } public: virtual ~LocationProvider() = default; @@ -35,6 +54,7 @@ class LocationProvider { virtual bool isValid() = 0; // current fix is valid virtual bool hasValidFix() { return getLastFix().valid || isValid(); } // a valid fix was obtained at some point virtual LastFix getLastFix() const { return _lastFix; } // snapshot of the last valid fix + virtual FixStats getFixStats() const { return _fixStats; } virtual long getTimestamp() = 0; virtual void sendSentence(const char * sentence); virtual bool waitFor(const char* prefix, uint32_t timeout_ms) { return false; } diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index b07f486b166..3c2adc6ea16 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -68,21 +68,30 @@ class MicroNMEALocationProvider : public LocationProvider { } void finishPeriodicAcquisition() { + if (_firstFixInWindow == 0) fixStatsMiss(); _driver->stop(); _acquiringPeriodically = false; _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; } - void saveFix() { + bool saveFix() { + // only store a fix when the snapshot is coherent (sats > 0). + // Returns true if the fix was stored. + if (nmea.getNumSatellites() == 0) return false; + LastFix fix; fix.latitude = nmea.getLatitude(); fix.longitude = nmea.getLongitude(); - nmea.getAltitude(fix.altitude); + // altitude comes from GGA only: keep the previous one if not available + if (!nmea.getAltitude(fix.altitude)) { + fix.altitude = _lastFix.valid ? _lastFix.altitude : 0; + } fix.satellites = nmea.getNumSatellites(); fix.timestamp = getTimestamp(); fix.acquiredAt = millis(); fix.valid = true; _lastFix = fix; + return true; } void updateTime() { @@ -132,7 +141,12 @@ class MicroNMEALocationProvider : public LocationProvider { void begin() override { _begin_calls++; + const bool wasActive = _driver->isActive(); _driver->begin(); + if (!wasActive) { + _acquisitionStarted = millis(); + _firstFixInWindow = 0; + } if (_gpsPeriodSeconds > 0 && _driver->isActive()) { nmea.clear(); time_valid = 0; @@ -183,20 +197,22 @@ class MicroNMEALocationProvider : public LocationProvider { LocationProvider::syncTime(); } + // Position, altitude and satellite count describe the last saved fix + // Before the first saved fix, fall back to the live parser state. long getLatitude() override { - return nmea.isValid() ? nmea.getLatitude() : _lastFix.latitude; + return _lastFix.valid ? _lastFix.latitude : nmea.getLatitude(); } long getLongitude() override { - return nmea.isValid() ? nmea.getLongitude() : _lastFix.longitude; + return _lastFix.valid ? _lastFix.longitude : nmea.getLongitude(); } long getAltitude() override { - if (!nmea.isValid()) return _lastFix.altitude; + if (_lastFix.valid) return _lastFix.altitude; long alt = 0; nmea.getAltitude(alt); return alt; } long satellitesCount() override { - return nmea.isValid() ? nmea.getNumSatellites() : _lastFix.satellites; + return _lastFix.valid ? _lastFix.satellites : nmea.getNumSatellites(); } bool isValid() override { return nmea.isValid(); } @@ -260,9 +276,12 @@ class MicroNMEALocationProvider : public LocationProvider { const char* messageId = nmea.getMessageID(); if (parsed && nmea.isValid() && (strcmp(messageId, "GGA") == 0 || strcmp(messageId, "RMC") == 0)) { - saveFix(); + if (!saveFix()) continue; // incoherent snapshot + if (_firstFixInWindow == 0) { + _firstFixInWindow = millis(); + fixStatsFix((uint32_t)(_firstFixInWindow - _acquisitionStarted)); + } if (_acquiringPeriodically) { - if (_firstFixInWindow == 0) _firstFixInWindow = millis(); if (nmea.getNumSatellites() >= GPS_MIN_SATS_FOR_FIX || (uint32_t)(millis() - _firstFixInWindow) >= GPS_FIX_SETTLE_MS) { finishPeriodicAcquisition(); diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h index d2c755e63d5..7deddf43b16 100644 --- a/src/helpers/sensors/RakLocationProvider.h +++ b/src/helpers/sensors/RakLocationProvider.h @@ -57,6 +57,10 @@ class RakLocationProvider : public LocationProvider { return _useI2c ? _driver.hasFix() : _nmea.isValid(); } + FixStats getFixStats() const override { + return _useI2c ? _fixStats : _nmea.getFixStats(); + } + LastFix getLastFix() const override { return _useI2c ? _lastFix : _nmea.getLastFix(); } @@ -146,7 +150,13 @@ class RakLocationProvider : public LocationProvider { } const bool hasCurrentFix = _driver.hasFix(); - if (hasCurrentFix) saveRak12500Fix(); + if (hasCurrentFix) { + // first fix since the start + if (!_lastFix.valid || (long)(_lastFix.acquiredAt - _acquisitionStarted) < 0) { + fixStatsFix((uint32_t)(millis() - _acquisitionStarted)); + } + saveRak12500Fix(); + } if (_gpsPeriodSeconds > 0 && _acquiringPeriodically && hasCurrentFix) { _driver.stop(); @@ -155,6 +165,7 @@ class RakLocationProvider : public LocationProvider { } else if (_gpsPeriodSeconds > 0 && _acquiringPeriodically && _lastFix.valid && (uint32_t)(millis() - _acquisitionStarted) >= GPS_PERIODIC_ACQUISITION_TIMEOUT_MS) { + fixStatsMiss(); _driver.stop(); _acquiringPeriodically = false; _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; From 22ce208e6802635efe8b013d7d4dfb0256cf58e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?N=C3=ADckolas=20Goline?= Date: Wed, 4 Nov 2026 13:36:33 -0300 Subject: [PATCH 08/23] muzi_base: fix GPS UART pins and re-enable ATGM336H NMEA output The refactor no longer calls Serial1.setPins() on nRF52, so Serial1 uses PIN_SERIAL1_RX/TX directly. Those were swapped (the GPS transmits on P0.20), so no data was received. Fix them and define PIN_GPS_TX/RX by the gps module's side, as other variants do. The ATGM336H keeps its NMEA output config across power cycles, and other firmware may leave every sentence disabled. Re-enable GGA + RMC after each power-up. --- variants/muzi_base/target.cpp | 12 ++++++++++++ variants/muzi_base/target.h | 4 ++++ variants/muzi_base/variant.h | 8 ++++---- 3 files changed, 20 insertions(+), 4 deletions(-) diff --git a/variants/muzi_base/target.cpp b/variants/muzi_base/target.cpp index b65bafa1d36..6653ada1912 100644 --- a/variants/muzi_base/target.cpp +++ b/variants/muzi_base/target.cpp @@ -50,6 +50,18 @@ void MuziBaseSensorManager::loop() { _pwr_last_click = t; if (++_pwr_clicks >= USER_BTN_POWER_OFF_CLICKS) muziPowerOff(); } +#if ENV_INCLUDE_GPS + // the ATGM336H keeps its nmea output config across power cycles (backup + // supply), and other firmware may have turned every sentence off. re-enable + // GGA + RMC after each power-up, once the module has booted. + bool gps_on = _location->isActive(); + if (gps_on && !_gps_was_active) _gps_cfg_at = millis() + 1000; + _gps_was_active = gps_on; + if (_gps_cfg_at && gps_on && (long)(millis() - _gps_cfg_at) > 0) { + _gps_cfg_at = 0; + _location->sendSentence("$PCAS03,1,0,0,0,1,0,0,0,0,0,,,0,0"); + } +#endif EnvironmentSensorManager::loop(); } diff --git a/variants/muzi_base/target.h b/variants/muzi_base/target.h index 3f7dcbf5f29..0c143e1bcbc 100644 --- a/variants/muzi_base/target.h +++ b/variants/muzi_base/target.h @@ -42,6 +42,10 @@ extern AutoDiscoverRTCClock rtc_clock; class MuziBaseSensorManager : public EnvironmentSensorManager { unsigned long _pwr_last_click = 0; uint8_t _pwr_clicks = 0; +#if ENV_INCLUDE_GPS + unsigned long _gps_cfg_at = 0; + bool _gps_was_active = false; +#endif public: #if ENV_INCLUDE_GPS MuziBaseSensorManager(LocationProvider& location) : EnvironmentSensorManager(location) {} diff --git a/variants/muzi_base/variant.h b/variants/muzi_base/variant.h index 0c1245a897b..d42804f6814 100644 --- a/variants/muzi_base/variant.h +++ b/variants/muzi_base/variant.h @@ -54,8 +54,8 @@ static const uint8_t AREF = (PIN_AREF); // not used //////////////////////////////////////////////////////////////////////////////// // UART pin definition -#define PIN_SERIAL1_RX (19) // P0.19 used for GPS RX -#define PIN_SERIAL1_TX (20) // P0.20 used for GPS TX +#define PIN_SERIAL1_RX (20) // P0.20, data from the gps +#define PIN_SERIAL1_TX (19) // P0.19, data to the gps //////////////////////////////////////////////////////////////////////////////// // I2C pin definition @@ -153,8 +153,8 @@ static const uint8_t AREF = (PIN_AREF); // not used //////////////////////////////////////////////////////////////////////////////// // GPS #define HAS_GPS 1 -#define PIN_GPS_RX PIN_SERIAL1_RX -#define PIN_GPS_TX PIN_SERIAL1_TX +#define PIN_GPS_TX PIN_SERIAL1_RX // gps module's TX pin +#define PIN_GPS_RX PIN_SERIAL1_TX // gps module's RX pin #define PIN_GPS_EN (32+1) // P1.01 PWR_IO2 on schematic. gps power, driven by the sensor manager. // superIO 3-position mode switch (pins from the meshtastic muzi_base variant). From d71a4187486b9811cb2bb558d72bb37be63894a9 Mon Sep 17 00:00:00 2001 From: Florent Date: Wed, 4 Nov 2026 16:30:44 -0400 Subject: [PATCH 09/23] gps: serial pin and configuration fixes (nrf52, meshadventurer, tracker_v1, heltec_tracker) --- src/helpers/sensors/AirohaGpsDriver.h | 4 +- src/helpers/sensors/AirohaSleep.h | 19 ------- .../sensors/MicroNMEALocationProvider.h | 10 +--- src/helpers/sensors/NmeaGpsDriver.h | 55 +++++++++---------- src/helpers/sensors/RakLocationProvider.h | 2 +- variants/heltec_tracker/platformio.ini | 4 +- variants/heltec_tracker/target.cpp | 14 +++-- variants/meshadventurer/platformio.ini | 4 +- variants/meshadventurer/target.cpp | 4 +- variants/nano_g2_ultra/NanoG2UltraGpsDriver.h | 2 +- variants/t1000-e/variant.h | 2 + 11 files changed, 48 insertions(+), 72 deletions(-) delete mode 100644 src/helpers/sensors/AirohaSleep.h diff --git a/src/helpers/sensors/AirohaGpsDriver.h b/src/helpers/sensors/AirohaGpsDriver.h index cd94d4195e7..9ba8a55d237 100644 --- a/src/helpers/sensors/AirohaGpsDriver.h +++ b/src/helpers/sensors/AirohaGpsDriver.h @@ -46,8 +46,8 @@ class AirohaGpsDriver : public NmeaGpsDriver { } public: - AirohaGpsDriver(HardwareSerial& serial) - : NmeaGpsDriver(serial, -1, -1, -1, -1, nullptr, HIGH, LOW, 115200) {} + AirohaGpsDriver(UART_TYPE& serial) + : NmeaGpsDriver(serial, -1, -1, -1, -1, nullptr, HIGH, LOW, GPS_BAUD_RATE) {} void initialize() { NmeaGpsDriver::begin(); diff --git a/src/helpers/sensors/AirohaSleep.h b/src/helpers/sensors/AirohaSleep.h deleted file mode 100644 index 6637d0fbfd7..00000000000 --- a/src/helpers/sensors/AirohaSleep.h +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once -#include - -// Board must keep the GPS RTC backup pin powered and can cut GPS power once we return. -// Returns true if the command was acked within 3 attempts, otherwise returns false. -static inline bool airohaEnterSleep(LocationProvider* nmea) { - for (uint8_t attempt = 0; attempt < 3; attempt++) { - nmea->drain(); - nmea->sendSentence("$PAIR650,0"); - if (nmea->waitFor("$PAIR001,650,0", 50)) { // wait for the command received signal - #ifdef GPS_NMEA_DEBUG - Serial.printf("Airoha RTC Backup sleep command accepted by GPS after %u attempts\r\n", attempt + 1); - #endif - nmea->waitFor("$PAIR650,0", 50); // give the GPS 50ms grace to signal it is ready for sleep - return true; - } - } - return false; -} \ No newline at end of file diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 3c2adc6ea16..db279acb937 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -119,15 +119,7 @@ class MicroNMEALocationProvider : public LocationProvider { // Temporary migration adapter for targets and downstream boards. New code // should inject a GpsDriver; this constructor will be removed once targets // have been migrated. - MicroNMEALocationProvider(HardwareSerial& serial, mesh::RTCClock* clock = nullptr, - int pin_reset = GPS_RESET, int pin_en = GPS_EN, - RefCountedDigitalPin* peripher_power = nullptr) - : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), - _driver(new NmeaGpsDriver(serial, PIN_GPS_TX, PIN_GPS_RX, pin_reset, pin_en, - peripher_power)), - _ownsDriver(true) {} - - MicroNMEALocationProvider(Stream& serial, mesh::RTCClock* clock = nullptr, + MicroNMEALocationProvider(UART_TYPE& serial, mesh::RTCClock* clock = nullptr, int pin_reset = GPS_RESET, int pin_en = GPS_EN, RefCountedDigitalPin* peripher_power = nullptr) : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), diff --git a/src/helpers/sensors/NmeaGpsDriver.h b/src/helpers/sensors/NmeaGpsDriver.h index abecb817603..25acff455f8 100644 --- a/src/helpers/sensors/NmeaGpsDriver.h +++ b/src/helpers/sensors/NmeaGpsDriver.h @@ -5,6 +5,14 @@ #include #include +#ifndef UART_TYPE +#ifdef NRF52_PLATFORM + #define UART_TYPE Uart +#else + #define UART_TYPE HardwareSerial +#endif +#endif + #ifndef PIN_GPS_TX #define PIN_GPS_TX (-1) #endif @@ -46,7 +54,11 @@ #endif #ifndef GPS_BAUD_RATE - #define GPS_BAUD_RATE 9600 + #ifdef GPS_BAUDRATE + #define GPS_BAUD_RATE GPS_BAUDRATE + #else + #define GPS_BAUD_RATE 9600 + #endif #endif #ifndef PIN_GPS_SWITCH @@ -66,8 +78,8 @@ #endif class NmeaGpsDriver : public GpsDriver { - Stream* _stream = nullptr; - HardwareSerial* _hardwareSerial = nullptr; + UART_TYPE* _hardwareSerial = nullptr; + RefCountedDigitalPin* _peripheralPower = nullptr; int _txPin = -1; int _rxPin = -1; @@ -87,12 +99,12 @@ class NmeaGpsDriver : public GpsDriver { #endif void startHardware() { - if (_stream == nullptr || _started) return; + if (_hardwareSerial == nullptr || _started) return; if (_hardwareSerial != nullptr) { -#ifdef ESP32_PLATFORM - _hardwareSerial->setPins(_txPin, _rxPin); -#endif + if (_txPin != -1 && _rxPin != -1) { + _hardwareSerial->setPins(_txPin, _rxPin); + } _hardwareSerial->begin(_baudRate); } @@ -116,14 +128,14 @@ class NmeaGpsDriver : public GpsDriver { public: NmeaGpsDriver() = default; - NmeaGpsDriver(Stream& stream, int txPin = PIN_GPS_TX, int rxPin = PIN_GPS_RX, + NmeaGpsDriver(UART_TYPE& serial, int txPin = PIN_GPS_TX, int rxPin = PIN_GPS_RX, int resetPin = GPS_RESET, int enablePin = GPS_EN, RefCountedDigitalPin* peripheralPower = nullptr, int enableActive = GPS_EN_ACTIVE, int resetActive = GPS_RESET_ACTIVE, uint32_t baudRate = GPS_BAUD_RATE, int hardwareEnablePin = PIN_GPS_SWITCH, int hardwareEnableActive = GPS_SWITCH_ACTIVE) - : _stream(&stream), _peripheralPower(peripheralPower), _txPin(txPin), _rxPin(rxPin), + : _hardwareSerial(&serial), _peripheralPower(peripheralPower), _txPin(txPin), _rxPin(rxPin), _resetPin(resetPin), _enablePin(enablePin), _enableActive(enableActive), _resetActive(resetActive), _baudRate(baudRate), _hardwareEnablePin(hardwareEnablePin), _hardwareEnableActive(hardwareEnableActive) { @@ -140,19 +152,6 @@ class NmeaGpsDriver : public GpsDriver { } } - NmeaGpsDriver(HardwareSerial& serial, int txPin = PIN_GPS_TX, int rxPin = PIN_GPS_RX, - int resetPin = GPS_RESET, int enablePin = GPS_EN, - RefCountedDigitalPin* peripheralPower = nullptr, - int enableActive = GPS_EN_ACTIVE, int resetActive = GPS_RESET_ACTIVE, - uint32_t baudRate = GPS_BAUD_RATE, - int hardwareEnablePin = PIN_GPS_SWITCH, - int hardwareEnableActive = GPS_SWITCH_ACTIVE) - : NmeaGpsDriver(static_cast(serial), txPin, rxPin, resetPin, enablePin, - peripheralPower, enableActive, resetActive, baudRate, - hardwareEnablePin, hardwareEnableActive) { - _hardwareSerial = &serial; - } - void begin() override { #if !GPS_SWITCH_IS_HARDWARE _requestedOn = true; @@ -232,11 +231,11 @@ class NmeaGpsDriver : public GpsDriver { } int available() { - return _stream == nullptr ? 0 : _stream->available(); + return _hardwareSerial == nullptr ? 0 : _hardwareSerial->available(); } int read() { - return _stream == nullptr ? -1 : _stream->read(); + return _hardwareSerial == nullptr ? -1 : _hardwareSerial->read(); } void drain() { @@ -244,19 +243,19 @@ class NmeaGpsDriver : public GpsDriver { } void sendNmeaSentence(const char* sentence) { - if (_stream == nullptr || sentence == nullptr || sentence[0] != '$') return; + if (_hardwareSerial == nullptr || sentence == nullptr || sentence[0] != '$') return; uint8_t checksum = 0; const char* p = sentence + 1; while (*p != '\0' && *p != '*') checksum ^= (uint8_t)*p++; - _stream->print(sentence); + _hardwareSerial->print(sentence); if (*p == '\0') { char checksumSuffix[6]; snprintf(checksumSuffix, sizeof(checksumSuffix), "*%02X\r\n", checksum); - _stream->print(checksumSuffix); + _hardwareSerial->print(checksumSuffix); } else { - _stream->print("\r\n"); + _hardwareSerial->print("\r\n"); } } }; diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h index 7deddf43b16..094e70eceec 100644 --- a/src/helpers/sensors/RakLocationProvider.h +++ b/src/helpers/sensors/RakLocationProvider.h @@ -29,7 +29,7 @@ class RakLocationProvider : public LocationProvider { public: RakLocationProvider(RakI2cGpsDriver& driver, mesh::RTCClock* clock = nullptr, - HardwareSerial& serial = Serial1) + UART_TYPE& serial = Serial1) : _driver(driver), _nmeaDriver(serial), _nmea(_nmeaDriver, clock) {} void syncTime() override { diff --git a/variants/heltec_tracker/platformio.ini b/variants/heltec_tracker/platformio.ini index 75d1cadf3cc..3affd8ae0fb 100644 --- a/variants/heltec_tracker/platformio.ini +++ b/variants/heltec_tracker/platformio.ini @@ -31,8 +31,8 @@ build_flags = -D USE_PIN_TFT=1 -D PIN_VEXT_EN=3 ; Vext is connected to VDD which is also connected to OLED & GPS -D PIN_TFT_LEDA_CTL=21 ; LEDK (switches on/off via mosfet to create the ground) - -D PIN_GPS_RX=33 - -D PIN_GPS_TX=34 + -D PIN_GPS_TX=33 ; GPS module TX -> MCU RX (GPIO33) + -D PIN_GPS_RX=34 ; GPS module RX <- MCU TX (GPIO34) -D PIN_GPS_EN=35 ; N-ch MOSFET Q2 drives P-ch high-side switch → active HIGH (default) -D PIN_GPS_RESET=36 -D PIN_GPS_RESET_ACTIVE=LOW diff --git a/variants/heltec_tracker/target.cpp b/variants/heltec_tracker/target.cpp index 041cb2b858e..67f98306c9d 100644 --- a/variants/heltec_tracker/target.cpp +++ b/variants/heltec_tracker/target.cpp @@ -23,9 +23,14 @@ class HeltecTrackerGpsDriver : public NmeaGpsDriver { : NmeaGpsDriver(Serial1, PIN_GPS_TX, PIN_GPS_RX, GPS_RESET, GPS_EN, &board.periph_power) {} - void begin() override { - NmeaGpsDriver::begin(); - sendNmeaSentence("$CFGSYS,h35155*68"); + void reset() override { + NmeaGpsDriver::reset(); + // Send configuration after reset + if (isActive()) { + delay(250); + sendNmeaSentence("$CFGSYS,h35155*68"); + // TODO: remove active wait (250ms is a lot) + } } }; @@ -64,8 +69,7 @@ void HWTSensorManager::stop_gps() { } bool HWTSensorManager::begin() { - // init GPS port - Serial1.begin(115200, SERIAL_8N1, PIN_GPS_RX, PIN_GPS_TX); + // the GPS UART (pins, baud rate) is now configured by gps_driver when the GPS is started return true; } diff --git a/variants/meshadventurer/platformio.ini b/variants/meshadventurer/platformio.ini index 4af0f7f7052..5e35251cc6c 100644 --- a/variants/meshadventurer/platformio.ini +++ b/variants/meshadventurer/platformio.ini @@ -23,8 +23,8 @@ build_flags = -D SX126X_DIO2_AS_RF_SWITCH=false -D SX126X_DIO3_TCXO_VOLTAGE=1.8 -D SX126X_RX_BOOSTED_GAIN=1 - -D PIN_GPS_RX=12 - -D PIN_GPS_TX=15 + -D PIN_GPS_TX=12 ; GPS module TX -> MCU RX (GPIO12) + -D PIN_GPS_RX=15 ; GPS module RX <- MCU TX (GPIO15) -D DISPLAY_CLASS=SSD1306Display build_src_filter = ${esp32_base.build_src_filter} +<../variants/meshadventurer> diff --git a/variants/meshadventurer/target.cpp b/variants/meshadventurer/target.cpp index 827e23b7305..1ccfe151017 100644 --- a/variants/meshadventurer/target.cpp +++ b/variants/meshadventurer/target.cpp @@ -48,9 +48,7 @@ void MASensorManager::stop_gps() { } bool MASensorManager::begin() { - Serial1.setPins(PIN_GPS_RX, PIN_GPS_TX); - Serial1.begin(9600); - delay(500); + // the GPS UART (pins, baud rate) is now configured by gps_driver when the GPS is started return true; } diff --git a/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h b/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h index 7e3bd10968b..76270d3297b 100644 --- a/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h +++ b/variants/nano_g2_ultra/NanoG2UltraGpsDriver.h @@ -4,7 +4,7 @@ class NanoG2UltraGpsDriver : public NmeaGpsDriver { public: - NanoG2UltraGpsDriver(HardwareSerial& serial) : NmeaGpsDriver(serial) { + NanoG2UltraGpsDriver(UART_TYPE& serial) : NmeaGpsDriver(serial) { pinMode(PIN_GPS_STANDBY, OUTPUT); digitalWrite(PIN_GPS_STANDBY, LOW); } diff --git a/variants/t1000-e/variant.h b/variants/t1000-e/variant.h index bd5090733c5..17a5538c4f3 100644 --- a/variants/t1000-e/variant.h +++ b/variants/t1000-e/variant.h @@ -117,6 +117,8 @@ #define GPS_RTC_INT (15) // P0.15 #define GPS_RESETB (46) // P1.14 +#define GPS_BAUDRATE 115200 + //////////////////////////////////////////////////////////////////////////////// // Temp+Lux Sensor From 839bb165fc52190534b6b625537187884b200f4a Mon Sep 17 00:00:00 2001 From: Florent Date: Wed, 4 Nov 2026 20:34:28 -0400 Subject: [PATCH 10/23] gps: guard the clock sync against stalled and too old fixes --- src/helpers/sensors/MicroNMEALocationProvider.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index db279acb937..dddcce1979a 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -70,6 +70,8 @@ class MicroNMEALocationProvider : public LocationProvider { void finishPeriodicAcquisition() { if (_firstFixInWindow == 0) fixStatsMiss(); _driver->stop(); + nmea.clear(); // the parser keeps the last time as valid otherwise + time_valid = 0; _acquiringPeriodically = false; _nextAcquisition = millis() + _gpsPeriodSeconds * 1000UL; } @@ -95,7 +97,7 @@ class MicroNMEALocationProvider : public LocationProvider { } void updateTime() { - if (!isValid()) time_valid = 0; + if (!isValid() || (millis() - _last_nmea_ms) > 2000) time_valid = 0; // no stale time if ((long)(millis() - next_check) <= 0) return; next_check = millis() + 1000; @@ -156,6 +158,8 @@ class MicroNMEALocationProvider : public LocationProvider { _stop_calls++; _acquiringPeriodically = false; _driver->stop(); + nmea.clear(); + time_valid = 0; } bool isActive() override { @@ -274,7 +278,9 @@ class MicroNMEALocationProvider : public LocationProvider { fixStatsFix((uint32_t)(_firstFixInWindow - _acquisitionStarted)); } if (_acquiringPeriodically) { - if (nmea.getNumSatellites() >= GPS_MIN_SATS_FOR_FIX || + // stay on until a pending time sync is done (or the settle time is over) + const bool synced = _clock == nullptr || !_time_sync_needed; + if ((nmea.getNumSatellites() >= GPS_MIN_SATS_FOR_FIX && synced) || (uint32_t)(millis() - _firstFixInWindow) >= GPS_FIX_SETTLE_MS) { finishPeriodicAcquisition(); } From 38fa18efb76c5a5abca3958a512fbb2fb4aed526 Mon Sep 17 00:00:00 2001 From: Florent Date: Thu, 5 Nov 2026 10:16:28 -0400 Subject: [PATCH 11/23] gps: move detection into LocationProvider, remove the legacy constructor --- .../sensors/EnvironmentSensorManager.cpp | 23 ++----------------- src/helpers/sensors/LocationProvider.h | 16 +++++++++++++ .../sensors/MicroNMEALocationProvider.h | 16 ------------- 3 files changed, 18 insertions(+), 37 deletions(-) diff --git a/src/helpers/sensors/EnvironmentSensorManager.cpp b/src/helpers/sensors/EnvironmentSensorManager.cpp index ee942df1443..eafa11a3cc0 100644 --- a/src/helpers/sensors/EnvironmentSensorManager.cpp +++ b/src/helpers/sensors/EnvironmentSensorManager.cpp @@ -696,31 +696,12 @@ bool EnvironmentSensorManager::setSettingValue(const char* name, const char* val #if ENV_INCLUDE_GPS void EnvironmentSensorManager::initBasicGPS() { - start_gps(); - #ifndef PIN_GPS_EN MESH_DEBUG_PRINTLN("No GPS wake/reset pin found for this board. Continuing on..."); #endif -#ifdef ENV_SKIP_GPS_DETECT - gps_detected = true; -#else - gps_detected = _location->detect(5000); -#endif - - if (gps_detected) { - MESH_DEBUG_PRINTLN("GPS detected"); - #if defined(PERSISTANT_GPS) - return; - #else - if (_location->keepEnabledAfterDetection()) { - return; - } - #endif - } else { - MESH_DEBUG_PRINTLN("No GPS detected"); - } - stop_gps(); + gps_detected = _location->autoDetect(); + MESH_DEBUG_PRINTLN("%s", gps_detected ? "GPS detected" : "No GPS detected"); } void EnvironmentSensorManager::start_gps() { diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index 3d7abd7ae67..ad2bba1d3f6 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -61,6 +61,22 @@ class LocationProvider { virtual void drain() { } virtual bool detect(uint32_t timeout_ms) { return false; } virtual bool keepEnabledAfterDetection() const { return false; } + // Power on, detect, then leave the GPS on or off as the board wants. + // Returns true if a GPS is present. + virtual bool autoDetect() { + setGpsOn(true); + #ifdef ENV_SKIP_GPS_DETECT + bool found = true; + #else + bool found = detect(5000); + #endif + #ifdef PERSISTANT_GPS + if (found) return true; + #endif + if (found && keepEnabledAfterDetection()) return true; + setGpsOn(false); + return found; + } virtual void setGpsPeriod(uint32_t seconds) { } virtual uint32_t getGpsPeriod() const { return 0; } virtual uint32_t getLastFixAge() const { // in seconds, UINT32_MAX if never diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index dddcce1979a..ea87ad19327 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -24,7 +24,6 @@ class MicroNMEALocationProvider : public LocationProvider { MicroNMEA nmea; mesh::RTCClock* _clock; NmeaGpsDriver* _driver; - bool _ownsDriver = false; unsigned long next_check = 0; long time_valid = 0; unsigned long _last_time_sync = 0; @@ -118,21 +117,6 @@ class MicroNMEALocationProvider : public LocationProvider { MicroNMEALocationProvider(NmeaGpsDriver& driver, mesh::RTCClock* clock = nullptr) : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), _driver(&driver) {} - // Temporary migration adapter for targets and downstream boards. New code - // should inject a GpsDriver; this constructor will be removed once targets - // have been migrated. - MicroNMEALocationProvider(UART_TYPE& serial, mesh::RTCClock* clock = nullptr, - int pin_reset = GPS_RESET, int pin_en = GPS_EN, - RefCountedDigitalPin* peripher_power = nullptr) - : nmea(_nmeaBuffer, sizeof(_nmeaBuffer)), _clock(clock), - _driver(new NmeaGpsDriver(serial, PIN_GPS_TX, PIN_GPS_RX, pin_reset, pin_en, - peripher_power)), - _ownsDriver(true) {} - - ~MicroNMEALocationProvider() override { - if (_ownsDriver) delete _driver; - } - void begin() override { _begin_calls++; const bool wasActive = _driver->isActive(); From c06def2f4120deede4b9d01ce6e1abdfc2e8d82b Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 7 Nov 2026 09:05:11 -0400 Subject: [PATCH 12/23] gps: once command --- src/helpers/CommonGpsPrefs.cpp | 27 ++++++++++++++++++ src/helpers/sensors/LocationProvider.h | 1 + .../sensors/MicroNMEALocationProvider.h | 28 +++++++++++++++++++ src/helpers/sensors/RakLocationProvider.h | 8 ++++++ 4 files changed, 64 insertions(+) diff --git a/src/helpers/CommonGpsPrefs.cpp b/src/helpers/CommonGpsPrefs.cpp index c4936d4f6e7..926efe71e1a 100644 --- a/src/helpers/CommonGpsPrefs.cpp +++ b/src/helpers/CommonGpsPrefs.cpp @@ -97,6 +97,33 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, } return true; } + if (memcmp(command, "gps once", 8) == 0) { // before "gps on" + LocationProvider* location = sensors.getLocationProvider(); + + if (location == nullptr) { + strcpy(reply, "gps provider not found"); + return true; + } + + if (location->hasHardwareEnableState() && !location->isHardwareEnabled()) { + strcpy(reply, "gps hw switch off"); + return true; + } + + if (strstr(command, " -s") != nullptr) location->syncTime(); + + const char* state = !location->isGpsOn() ? "turning on and acquiring" + : (location->getGpsPeriod() > 0 && !location->isActive()) ? "waking gps" + : "gps already on"; + + if (location->startOnce()) { + snprintf(reply, 160, "ok (%s)", state); + } else { + strcpy(reply, "gps once not supported"); + } + + return true; + } if (memcmp(command, "gps on", 6) == 0) { if (!setGpsEnabled(sensors, true)) { strcpy(reply, "gps toggle not found"); diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index ad2bba1d3f6..b15c0ed2a51 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -86,6 +86,7 @@ class LocationProvider { virtual bool hasHardwareEnableState() const { return false; } virtual bool isHardwareEnabled() const { return true; } virtual bool isGpsOn() const { return _gpsOn; } + virtual bool startOnce() { return false; } virtual void setGpsOn(bool on) { _gpsOn = on; if (on) { diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index ea87ad19327..9a6d800601d 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -32,6 +32,7 @@ class MicroNMEALocationProvider : public LocationProvider { unsigned long _acquisitionStarted = 0; unsigned long _firstFixInWindow = 0; bool _acquiringPeriodically = false; + unsigned long _onceDeadline = 0; static const unsigned long TIME_SYNC_INTERVAL = 1800000; uint32_t _uart_bytes = 0; @@ -119,6 +120,7 @@ class MicroNMEALocationProvider : public LocationProvider { void begin() override { _begin_calls++; + _onceDeadline = 0; const bool wasActive = _driver->isActive(); _driver->begin(); if (!wasActive) { @@ -140,6 +142,7 @@ class MicroNMEALocationProvider : public LocationProvider { void stop() override { _stop_calls++; + _onceDeadline = 0; _acquiringPeriodically = false; _driver->stop(); nmea.clear(); @@ -172,6 +175,16 @@ class MicroNMEALocationProvider : public LocationProvider { return _gpsPeriodSeconds; } + bool startOnce() override { + if (_gpsOn) { // already on, acquire now + _nextAcquisition = millis(); + return true; + } + setGpsOn(true); + _onceDeadline = millis() + GPS_PERIODIC_ACQUISITION_TIMEOUT_MS; + return true; + } + void syncTime() override { nmea.clear(); LocationProvider::syncTime(); @@ -232,6 +245,21 @@ class MicroNMEALocationProvider : public LocationProvider { void loop() override { if (_driver->pollGpsSwitch()) _gpsOn = true; + if (_onceDeadline != 0) { + if (_firstFixInWindow != 0 && (long)(_onceDeadline - (millis() + GPS_FIX_SETTLE_MS)) > 0) { + _onceDeadline = millis() + GPS_FIX_SETTLE_MS; + } + if ((long)(millis() - _onceDeadline) >= 0) { + if (_driver->isActive()) { + setGpsOn(false); + } else { // do not stop twice + _gpsOn = false; + _onceDeadline = 0; + _acquiringPeriodically = false; + } + } + } + if (_gpsOn && _gpsPeriodSeconds > 0 && !_acquiringPeriodically && (long)(millis() - _nextAcquisition) >= 0) { startPeriodicAcquisition(); diff --git a/src/helpers/sensors/RakLocationProvider.h b/src/helpers/sensors/RakLocationProvider.h index 094e70eceec..6ad05125585 100644 --- a/src/helpers/sensors/RakLocationProvider.h +++ b/src/helpers/sensors/RakLocationProvider.h @@ -190,6 +190,14 @@ class RakLocationProvider : public LocationProvider { return _gpsPeriodSeconds; } + bool isGpsOn() const override { + return _useI2c ? _gpsOn : _nmea.isGpsOn(); + } + + bool startOnce() override { + return _useI2c ? false : _nmea.startOnce(); // not supported on the RAK12500 + } + void setGpsOn(bool on) override { if (_useI2c) LocationProvider::setGpsOn(on); else { _gpsOn = on; _nmea.setGpsOn(on); } From 6c9600e1e766c8b58975e425e2af07ffac487e56 Mon Sep 17 00:00:00 2001 From: Florent Date: Sat, 7 Nov 2026 13:45:40 -0400 Subject: [PATCH 13/23] gps: Rework the CLI - more consistency with other cli commands - new format for gps fix - gps format accepts postfix (s/m/d/h) - documentation --- docs/cli_commands.md | 27 +++++- docs/companion_cli.md | 27 +++++- src/helpers/CommonCLI.cpp | 12 +-- src/helpers/CommonGpsPrefs.cpp | 89 ++++++++++--------- src/helpers/sensors/LocationProvider.h | 1 + .../sensors/MicroNMEALocationProvider.h | 4 + src/helpers/sensors/RakLocationProvider.h | 4 + 7 files changed, 110 insertions(+), 54 deletions(-) diff --git a/docs/cli_commands.md b/docs/cli_commands.md index d7b8d5d016f..44ff3a250ba 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1021,10 +1021,10 @@ region save #### View or change the GPS acquisition period **Usage:** - `gps period` -- `gps period ` +- `gps period ` **Parameters:** -- `seconds`: `0` for continuous tracking, or `1`-`86400` for periodic acquisition +- `value`: `0` for continuous tracking, or a period. The value can be postfixed with `s`, `m`, `h` or `d` to specify the unit (seconds by default). **Note:** A periodic acquisition stops after receiving a valid fix, then resumes after the configured period. @@ -1034,7 +1034,12 @@ region save **Usage:** - `gps fix` -**Note:** Returns the last valid latitude, longitude, altitude, and elapsed time since the fix. Returns `no fix` until a valid fix is received. +**Output:** +- `> Fix (n sat): , `: the GPS currently has a fix. +- `> Last fix ago (n sat)[, acq