Repository navigation
Conversation
…ewer one Location fixes from the geofence and background paths were each sent from their own coroutine, and the "not latest" check only saw a send once its HTTP call had returned. Two fixes arriving together both passed the check and raced to the server, so an older fix could land last and flip the person entity back out of a zone it had just entered. Sends now go through LocationSendQueue: one at a time per server, with fixes waiting behind an in-flight send collapsing into a single pending slot that only a newer fix can replace. Superseded fixes are logged as SKIPPED_NOT_LATEST. When a newer background fix replaces a geofence fix it inherits the geofence's debounce bypass, so a zone transition can't be collapsed and then debounced into no send. The "not latest" check compares against the last sent fix's own time rather than the wall clock of the previous send, which would have skipped a newer fix queued behind an in-flight one. Servers are independent, so an unreachable server no longer delays the others. Maps written on the send path are ConcurrentHashMaps since servers send concurrently, and LocationUpdateTrigger moves out of the companion object so the queue and its test can import it.
Contributor
There was a problem hiding this comment.
🟡 Changes recommended
Unresolved timestamp-handling and zone-only history redaction issues affect location correctness and privacy.
3 open issues
What changed in this PR
This PR adds a per-server location queue to prevent overlapping updates from sending stale positions after newer ones in the Android companion app.
Changes:
- Serializes sends per server and collapses pending fixes to the newest position.
- Preserves geofence debounce bypass and tracks fix timestamps separately from send timing.
- Adds tests for ordering, superseding fixes, debounce inheritance, and server independence.
| File | Description |
|---|---|
app/src/testFull/kotlin/io/homeassistant/companion/android/sensors/LocationSendQueueTest.kt |
Tests queue ordering and per-server behavior. |
app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt |
Routes location updates through the queue and adjusts send checks. |
app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSendQueue.kt |
Implements serialized sends and pending-fix replacement. |
Review effort: Balanced
…guard before send The superseded-fix history row logged a null payload, which stores raw coordinates for a server configured for zone-only location. The payload builder is now extracted from sendLocationUpdate into buildLocationPayload and the superseded path logs the same zone-only payload the other skip rows use. The future-timestamp check moves ahead of the queue so a future-dated fix is logged as SKIPPED_FUTURE without entering it, where it could have displaced a valid pending fix and then been rejected at send time. It moves rather than being duplicated: a fix cannot become future-dated while it waits. lastSentLocationTime is recorded after the skip checks and before the network call, so an older fix queued behind an in-flight one is still skipped when the server applies the newer update but its response is lost. lastLocationSend and lastUpdateLocation keep updating on success only since they drive the debounce and duplicate windows.
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.


Summary
Fixes a race that could flip a person entity back out of a zone right after entering it.
How it was found. A zone-enter automation fired on arriving home, and a second automation conditioned on the person being home then saw them as away a moment later. The recorder shows the
mobile_appdevice tracker (exact GPS, home zone radius 75 m) goinghomeat 73 m from the zone center with 9 m accuracy, thennot_home207 ms later at 113 m with 7 m accuracy, thenhomeagain 13 s later. The second fix is farther back along the approach path, so it is an older position delivered after a newer one, not GPS noise. A similar same-second burst of three updates appeared earlier the same evening. Core 2026.10.2, Pixel 10 Pro connected to Android Auto, likely switching from cellular to home Wi-Fi at the time. The on-device location history was not captured for this event, so the attribution below comes from the code rather than from phone logs.Cause. Location fixes from the geofence and background paths were each sent from their own coroutine, and
lastLocationSendwas only recorded once the HTTP call returned. Two fixes arriving within the same delivery window, most likely the geofenceENTERand a background fix of the same approach, both passed the "not latest" check and raced to the server. Whichever HTTP call finished last won, so an older fix could land after a newer one. Theupdate_locationwebhook carries no fix timestamp, so Core cannot drop an out-of-order update; the app has to.Repro. Enable zone tracking and background location, drive into a zone while the connection is changing or slow, and compare the app's location history (Settings > Companion app > Troubleshooting > Location tracking history) with the person entity's history. Expect two
SENTrows within a few hundred milliseconds of each other, and the person entity sometimes ending on the older position.Change. Sends now go through a small
LocationSendQueue:Location.time) can replace, so a backlog becomes one send of the latest position. Superseded fixes are logged asSKIPPED_NOT_LATESTin the location history.ConcurrentHashMaps, since servers now send concurrently.LocationUpdateTriggermoves out of the companion object so the queue and its test can import it.Impact. Location updates reach the server in timestamp order per server. Multi-server users with one unreachable server keep getting timely updates on the others. The five second debounce now applies to closely spaced background fixes as intended, where before the race let the second one through.
Checklist
Select exactly one option that describes AI usage in this contribution:
Any other notes
LocationSendQueueTestcovers the ordering, collapse, geofence bypass carry-over, and per-server independence with aTestDispatcherand a gated fake send. The skip checks insidesendLocationUpdateare not unit tested because the manager builds its ownCoroutineScopeand reads the wall clock directly; making that injectable is a larger change than this fix.