Wait until after GPS lock hold before updating position, if we can. (#8064)

* Wait until after GPS lock hold before updating position, if we can.

After the recent patch, we hold lock for a bit before updating the position.
The positions that come in after the hold are genuinely better positions
 than what we've been doing before. However, they only come 20 seconds
 after we've got lock.

Previously, we would update the local position as soon as we got a lock as well
as at the end of the hold, since a hold was not always possible.
With this patch, if the settings allow, we should skip that first local position update.

Fixes https://github.com/meshtastic/firmware/issues/8029

* Fix falling edge handling.

* spelling

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Congeal lock handling

* Add named constants

* define unit to avoid confusion

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* ifdef, not if.

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Add handling for when we first turn on.

* Don't run if not active

* Reset fixhold

* Logic fixes

* Add path for ACTIVE--> IDLE --> ACTIVE

Previously we only covered HARDSLEEP --> ACTIVE.

* Change hold time to gps_update_interval - 10s

* Update comment

* Add extra buffer to avoid re-starting hold

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Ben Meadors <benmmeadors@gmail.com>
This commit is contained in:
Tom Fifield
2025-10-07 22:24:09 +11:00
committed by GitHub
parent 81a5aeff74
commit 468b40e8db
2 changed files with 90 additions and 40 deletions

View File

@@ -1031,7 +1031,7 @@ void GPS::down()
LOG_DEBUG("%us until next search", sleepTime / 1000);
// If update interval less than 10 seconds, no attempt to sleep
if (updateInterval <= 10 * 1000UL || sleepTime == 0)
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS || sleepTime == 0)
setPowerState(GPS_IDLE);
else {
@@ -1102,6 +1102,29 @@ int32_t GPS::runOnce()
publishUpdate();
}
// ======================== GPS_ACTIVE state ========================
// In GPS_ACTIVE state, GPS is powered on and we're receiving NMEA messages.
// We use the following logic to determine when to update the local position
// or time by running GPS::publishUpdate.
// Note: Local position update is asynchronous to position broadcast. We
// generally run this state every gps_update_interval seconds, and in most cases
// gps_update_interval is faster than the position broadcast interval so there's a
// fresh position ready when the device wants to broadcast one on the mesh.
//
// 1. Got a time for the first time --> set the time, don't publish.
// 2. Got a lock for the first time
// --> If gps_update_interval is <= 10s --> publishUpdate
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
// 3. Got a lock after turning back on
// --> If gps_update_interval is <= 10s --> publishUpdate
// --> Otherwise, hold for MIN(gps_update_interval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS, 20s)
// 4. Hold has expired
// --> If we have a time and a location --> publishUpdate
// --> down()
// 5. Search time has expired
// --> If we have a time and a location --> publishUpdate
// --> If we had a location before but don't now --> publishUpdate
// --> down()
if (whileActive()) {
// if we have received valid NMEA claim we are connected
setConnected();
@@ -1111,55 +1134,81 @@ int32_t GPS::runOnce()
if (!config.position.fixed_position && powerState != GPS_ACTIVE && scheduling.isUpdateDue())
up();
// If we've already set time from the GPS, no need to ask the GPS
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
gotTime = true;
shouldPublish = true;
}
// quality of the previous fix. We set it to 0 when we go down, so it's a way
// to check if we're getting a lock after being GPS_OFF.
uint8_t prev_fixQual = fixQual;
bool gotLoc = lookForLocation();
if (gotLoc && !hasValidLocation) { // declare that we have location ASAP
LOG_DEBUG("hasValidLocation RISING EDGE");
hasValidLocation = true;
shouldPublish = true;
// Hold for 20secs after getting a lock to download ephemeris etc
fixHoldEnds = millis() + 20000;
}
if (gotLoc && prev_fixQual == 0) { // just got a lock after turning back on.
fixHoldEnds = millis() + 20000;
shouldPublish = true; // Publish immediately, since next publish is at end of hold
}
if (powerState == GPS_ACTIVE) {
// if gps_update_interval is <=10s, GPS never goes off, so we treat that differently
uint32_t updateInterval = Default::getConfiguredOrDefaultMs(config.position.gps_update_interval);
bool tooLong = scheduling.searchedTooLong();
if (tooLong)
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
// 1. Got a time for the first time
bool gotTime = (getRTCQuality() >= RTCQualityGPS);
if (!gotTime && lookForTime()) { // Note: we count on this && short-circuiting and not resetting the RTC time
gotTime = true;
}
// Once we get a location we no longer desperately want an update
if ((gotLoc && gotTime) || tooLong) {
// 2. Got a lock for the first time, or 3. Got a lock after turning back on
bool gotLoc = lookForLocation();
if (gotLoc) {
#ifdef GPS_DEBUG
if (!hasValidLocation) { // declare that we have location ASAP
LOG_DEBUG("hasValidLocation RISING EDGE");
}
#endif
if (updateInterval <= GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS) {
hasValidLocation = true;
shouldPublish = true;
} else if (!hasValidLocation || prev_fixQual == 0 || (fixHoldEnds + GPS_THREAD_INTERVAL) < millis()) {
hasValidLocation = true;
// Hold for up to 20secs after getting a lock to download ephemeris etc
uint32_t holdTime = updateInterval - GPS_UPDATE_ALWAYS_ON_THRESHOLD_MS;
if (holdTime > GPS_FIX_HOLD_MAX_MS)
holdTime = GPS_FIX_HOLD_MAX_MS;
fixHoldEnds = millis() + holdTime;
#ifdef GPS_DEBUG
LOG_DEBUG("Holding for %ums after lock", holdTime);
#endif
}
}
bool tooLong = scheduling.searchedTooLong();
if (tooLong && !gotLoc) {
LOG_WARN("Couldn't publish a valid location: didn't get a GPS lock in time");
// we didn't get a location during this ack window, therefore declare loss of lock
if (hasValidLocation) {
LOG_DEBUG("hasValidLocation FALLING EDGE");
}
p = meshtastic_Position_init_default;
hasValidLocation = false;
}
if (millis() > fixHoldEnds) {
shouldPublish = true; // publish our update at the end of the lock hold
publishUpdate();
down();
p = meshtastic_Position_init_default;
hasValidLocation = false;
shouldPublish = true;
#ifdef GPS_DEBUG
} else {
LOG_DEBUG("hasValidLocation FALLING EDGE");
#endif
}
}
// Hold has expired , Search time has expired, we got a time only, or we never needed to hold.
bool holdExpired = (fixHoldEnds != 0 && millis() > fixHoldEnds);
if (shouldPublish || tooLong || holdExpired) {
if (gotTime && hasValidLocation) {
shouldPublish = true;
}
if (shouldPublish) {
fixHoldEnds = 0;
publishUpdate();
}
// There's a chance we just got a time, so keep going to see if we can get a location too
if (tooLong || holdExpired) {
down();
}
#ifdef GPS_DEBUG
} else if (fixHoldEnds != 0) {
LOG_DEBUG("Holding for GPS data download: %d ms (numSats=%d)", fixHoldEnds - millis(), p.sats_in_view);
#endif
}
}
// If state has changed do a publish
publishUpdate();
// ===================== end GPS_ACTIVE state ========================
if (config.position.fixed_position == true && hasValidLocation)
return disable(); // This should trigger when we have a fixed position, and get that first position