Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 15 additions & 1 deletion docs/cli_commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <value>`

**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.

---

Expand Down
8 changes: 8 additions & 0 deletions examples/companion_radio/NodePrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
8 changes: 8 additions & 0 deletions src/helpers/CommonCLI.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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;
Expand Down
54 changes: 44 additions & 10 deletions src/helpers/CommonGpsPrefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -62,6 +64,20 @@ static void formatDuration(char* buf, size_t size, uint32_t seconds) {
}
}

// "<n>[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();
Expand Down Expand Up @@ -180,24 +196,42 @@ 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 {
strcpy(reply, "OK");
}
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) {
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/CommonGpsPrefs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
3 changes: 3 additions & 0 deletions src/helpers/sensors/LocationProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
26 changes: 22 additions & 4 deletions src/helpers/sensors/MicroNMEALocationProvider.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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();
Expand Down
Loading