diff --git a/docs/cli_commands.md b/docs/cli_commands.md index bc30adbd48..7bd0e3ace3 100644 --- a/docs/cli_commands.md +++ b/docs/cli_commands.md @@ -1026,7 +1026,21 @@ region save **Parameters:** - `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. +**Note:** A periodic acquisition stops after receiving a valid fix, then resumes after the configured period. Without a fix it stops after 5 minutes (`GPS_PERIODIC_ACQUISITION_TIMEOUT_MS`), or, until the first fix since boot, after the first fix timeout (see `gps firstfix`). + +--- + +#### View or change the first fix timeout +**Usage:** +- `gps firstfix` +- `gps firstfix ` + +**Parameters:** +- `value`: `0` for no limit, or a duration up to `1d`. The value can be postfixed with `s`, `m`, `h` or `d` to specify the unit (seconds by default). + +**Default:** `1h` (`GPS_FIRST_FIX_TIMEOUT_MS`) + +**Note:** In periodic mode, limits an acquisition until the first valid fix since boot. A cold start can take much longer than the usual 5 minutes with a weak sky view; after the limit the GPS sleeps for the period and tries again. Saved with the other GPS settings. --- diff --git a/examples/companion_radio/NodePrefs.h b/examples/companion_radio/NodePrefs.h index c53879767b..09dbba0e8f 100644 --- a/examples/companion_radio/NodePrefs.h +++ b/examples/companion_radio/NodePrefs.h @@ -38,6 +38,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file uint32_t gps_interval = 0; // GPS read interval in seconds uint32_t gps_period = 0; // periodic acquisition interval in seconds int32_t gps_fence = -1; // GPS_GUARD fence in metres, 0 = off, -1 = build default + int32_t gps_first_fix = -1; // first-fix timeout in seconds, 0 = no limit, -1 = build default uint8_t gps_guard = 1; // GPS_GUARD on/off 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) @@ -169,6 +170,7 @@ class NodePrefs : public ConfigSerializer { // persisted to file 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); + def("ffix", _parent->gps_first_fix); #ifdef GPS_GUARD def("fence", _parent->gps_fence); def("guard", _parent->gps_guard); @@ -180,6 +182,12 @@ class NodePrefs : public ConfigSerializer { // persisted to file 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(); } + bool getFirstFixTimeout(uint32_t& seconds) const override { + if (_parent->gps_first_fix < 0) return false; + seconds = (uint32_t)_parent->gps_first_fix; + return true; + } + void setFirstFixTimeout(uint32_t seconds) override { _parent->gps_first_fix = (int32_t)seconds; markDirty(); } bool getGuardFence(uint32_t& metres) const override { if (_parent->gps_fence < 0) return false; metres = (uint32_t)_parent->gps_fence; diff --git a/src/helpers/CommonCLI.h b/src/helpers/CommonCLI.h index cb85f6f993..24b1fe26bd 100644 --- a/src/helpers/CommonCLI.h +++ b/src/helpers/CommonCLI.h @@ -64,6 +64,7 @@ class NodePrefs : public ConfigSerializer { uint32_t gps_interval = 0; // in seconds uint32_t gps_period = 0; // periodic acquisition interval in seconds int32_t gps_fence = -1; // GPS_GUARD fence in metres, 0 = off, -1 = build default + int32_t gps_first_fix = -1; // first-fix timeout in seconds, 0 = no limit, -1 = build default uint8_t gps_guard = 1; // GPS_GUARD on/off uint8_t advert_loc_policy = 0; uint32_t discovery_mod_timestamp = 0; @@ -164,6 +165,7 @@ class NodePrefs : public ConfigSerializer { 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); + def("ffix", _parent->gps_first_fix); #ifdef GPS_GUARD def("fence", _parent->gps_fence); def("guard", _parent->gps_guard); @@ -175,6 +177,12 @@ class NodePrefs : public ConfigSerializer { 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(); } + bool getFirstFixTimeout(uint32_t& seconds) const override { + if (_parent->gps_first_fix < 0) return false; + seconds = (uint32_t)_parent->gps_first_fix; + return true; + } + void setFirstFixTimeout(uint32_t seconds) override { _parent->gps_first_fix = (int32_t)seconds; markDirty(); } bool getGuardFence(uint32_t& metres) const override { if (_parent->gps_fence < 0) return false; metres = (uint32_t)_parent->gps_fence; diff --git a/src/helpers/CommonGpsPrefs.cpp b/src/helpers/CommonGpsPrefs.cpp index cdc889d00e..d9768fe1eb 100644 --- a/src/helpers/CommonGpsPrefs.cpp +++ b/src/helpers/CommonGpsPrefs.cpp @@ -46,6 +46,8 @@ bool CommonGpsPrefs::apply(SensorManager& sensors) const { LocationProvider* location = sensors.getLocationProvider(); if (location == nullptr) return false; applyGuardReference(sensors); + uint32_t first_fix; + if (getFirstFixTimeout(first_fix)) location->setFirstFixTimeout(first_fix); location->setGpsPeriod(getGpsPeriod()); location->setGpsOn(isGpsEnabled()); return true; @@ -62,6 +64,20 @@ static void formatDuration(char* buf, size_t size, uint32_t seconds) { } } +// "[s|m|h|d]", seconds by default +static bool parseDuration(const char* text, uint32_t& seconds) { + char* end; + unsigned long value = strtoul(text, &end, 10); + unsigned long mult = 1; + if (*end == 'm') mult = 60; + else if (*end == 'h') mult = 3600; + else if (*end == 'd') mult = 86400; + if (mult > 1 || *end == 's') end++; // skip the suffix char + if (text[0] == '\0' || *end != '\0') return false; + seconds = (uint32_t)(value * mult); + return true; +} + bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, char* reply) { if (strcmp(command, "gps") == 0) { LocationProvider* location = sensors.getLocationProvider(); @@ -180,17 +196,10 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, return true; } if (memcmp(command, "gps period ", 11) == 0) { - char* end; - unsigned long period = strtoul(command + 11, &end, 10); - unsigned long mult = 1; - if (*end == 'm') mult = 60; - else if (*end == 'h') mult = 3600; - else if (*end == 'd') mult = 86400; - if (mult > 1 || *end == 's') end++; // skip the suffix char - - if (command[11] == '\0' || *end != '\0') { + uint32_t period; + if (!parseDuration(command + 11, period)) { strcpy(reply, "Error, invalid gps period"); - } else if (!setGpsPeriod(sensors, (uint32_t)(period * mult)) || + } else if (!setGpsPeriod(sensors, period) || !setGpsEnabled(sensors, true)) { strcpy(reply, "Error, gps provider not found"); } else { @@ -198,6 +207,31 @@ bool CommonGpsPrefs::handleCommand(const char* command, SensorManager& sensors, } return true; } + if (strcmp(command, "gps firstfix") == 0) { + LocationProvider* location = sensors.getLocationProvider(); + if (location == nullptr) { + strcpy(reply, "Error, gps provider not found"); + } else if (location->getFirstFixTimeout() == 0) { + strcpy(reply, "> 0 (no limit)"); + } else { + sprintf(reply, "> %lu", (unsigned long)location->getFirstFixTimeout()); + } + return true; + } + if (memcmp(command, "gps firstfix ", 13) == 0) { + LocationProvider* location = sensors.getLocationProvider(); + uint32_t seconds; + if (location == nullptr) { + strcpy(reply, "Error, gps provider not found"); + } else if (!parseDuration(command + 13, seconds) || seconds > 86400UL) { // at most a day + strcpy(reply, "Error, invalid first fix timeout"); + } else { + location->setFirstFixTimeout(seconds); + setFirstFixTimeout(seconds); // saved by the caller (isDirty) + strcpy(reply, "OK"); + } + return true; + } if (memcmp(command, "gps sync", 8) == 0) { LocationProvider* location = sensors.getLocationProvider(); if (location == nullptr) { diff --git a/src/helpers/CommonGpsPrefs.h b/src/helpers/CommonGpsPrefs.h index c1e30b8eaf..5502cba802 100644 --- a/src/helpers/CommonGpsPrefs.h +++ b/src/helpers/CommonGpsPrefs.h @@ -18,6 +18,9 @@ class CommonGpsPrefs : public ConfigSerializer { virtual void setGpsEnabled(bool enabled) = 0; virtual uint32_t getGpsPeriod() const = 0; virtual void setGpsPeriod(uint32_t seconds) = 0; + // Saved first-fix timeout in seconds; false = not set, use the build default. + virtual bool getFirstFixTimeout(uint32_t& seconds) const { return false; } + virtual void setFirstFixTimeout(uint32_t seconds) { } // Reference position for the anti-spoofing guard (GPS_GUARD), e.g. the configured node position. virtual bool getGuardReference(double& lat, double& lon) const { return false; } diff --git a/src/helpers/sensors/LocationProvider.h b/src/helpers/sensors/LocationProvider.h index 5b5b69c044..2e73034399 100644 --- a/src/helpers/sensors/LocationProvider.h +++ b/src/helpers/sensors/LocationProvider.h @@ -81,6 +81,9 @@ class LocationProvider { } virtual void setGpsPeriod(uint32_t seconds) { } virtual uint32_t getGpsPeriod() const { return 0; } + // periodic mode: limit of an acquisition before the first fix since boot, 0 = no limit + virtual void setFirstFixTimeout(uint32_t seconds) { } + virtual uint32_t getFirstFixTimeout() 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; diff --git a/src/helpers/sensors/MicroNMEALocationProvider.h b/src/helpers/sensors/MicroNMEALocationProvider.h index 09b4d95546..93b2dbfc4d 100644 --- a/src/helpers/sensors/MicroNMEALocationProvider.h +++ b/src/helpers/sensors/MicroNMEALocationProvider.h @@ -12,6 +12,12 @@ #ifndef GPS_PERIODIC_ACQUISITION_TIMEOUT_MS #define GPS_PERIODIC_ACQUISITION_TIMEOUT_MS 300000UL #endif +// default limit for the periodic window before the first fix since boot (a cold start +// can take up to ~30 min with a weak sky view); 0 = keep searching until a fix. +// Can be changed at runtime with `gps firstfix`. +#ifndef GPS_FIRST_FIX_TIMEOUT_MS + #define GPS_FIRST_FIX_TIMEOUT_MS 3600000UL +#endif // in periodic mode, keep reading after the first valid fix until enough satellites // are used or the settle time has elapsed @@ -36,6 +42,7 @@ class MicroNMEALocationProvider : public LocationProvider { long time_valid = 0; unsigned long _last_time_sync = 0; uint32_t _gpsPeriodSeconds = 0; + uint32_t _firstFixTimeoutSeconds = GPS_FIRST_FIX_TIMEOUT_MS / 1000UL; unsigned long _nextAcquisition = 0; unsigned long _acquisitionStarted = 0; unsigned long _firstFixInWindow = 0; @@ -199,6 +206,14 @@ class MicroNMEALocationProvider : public LocationProvider { return _gpsPeriodSeconds; } + void setFirstFixTimeout(uint32_t seconds) override { + _firstFixTimeoutSeconds = seconds; + } + + uint32_t getFirstFixTimeout() const override { + return _firstFixTimeoutSeconds; + } + uint32_t getAcquisitionAge() const override { return (uint32_t)(millis() - _acquisitionStarted) / 1000UL; } @@ -363,10 +378,13 @@ class MicroNMEALocationProvider : public LocationProvider { } } - // 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(); + // a longer limit until a first fix has been obtained (cold start can be long) + if (_acquiringPeriodically) { + const uint32_t limit = _lastFix.valid ? GPS_PERIODIC_ACQUISITION_TIMEOUT_MS + : _firstFixTimeoutSeconds * 1000UL; + if (limit > 0 && (uint32_t)(millis() - _acquisitionStarted) >= limit) { + finishPeriodicAcquisition(); + } } updateTime();