Skip to content

Don't use geofence for setting high accuracy mode. - #7375

Closed
eyal0 wants to merge 23 commits into
home-assistant:mainfrom
eyal0:ignore_geofence
Closed

eyal0 wants to merge 23 commits into
home-assistant:mainfrom
eyal0:ignore_geofence

Conversation

@eyal0

@eyal0 eyal0 commented Aug 26, 2026

Copy link
Copy Markdown

Summary

The geofence shape for a zone and the shape calculated by Home Assistant are not the same. Turn high accuracy mode on and off based on location updates instead of counting on the geofence.

This fixes #7349

Checklist

  • New or updated tests have been added to cover the changes following the testing guidelines.
  • The code follows the project's code style and best_practices.
  • The changes have been thoroughly tested, and edge cases have been considered.
  • Changes are backward compatible whenever feasible. Any breaking changes are documented in the changelog for users and/or in the code for developers depending on the relevance.
  • I have read the Open Home Foundation AI Policy.

Select exactly one option that describes AI usage in this contribution:

  • I have not used AI for this contribution.
  • AI assistance was used for this contribution.
  • AI fully generated the code for this contribution, but I've reviewed and understood it before submitting and will respond without AI during review.

Screenshots

Link to pull request in documentation repositories

User Documentation: home-assistant/companion.home-assistant#

Developer Documentation: home-assistant/developers.home-assistant#

Any other notes

Copilot AI lite review requested due to automatic review settings August 26, 2026 18:20

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR addresses #7349 by changing how high accuracy location mode is toggled: instead of relying on geofence enter/exit events (which can diverge from Home Assistant’s own zone calculation), it derives the “need high accuracy” state from incoming location updates.

Changes:

  • Extend zone containment calculation to support an additional “expanded radius” (trigger range) when evaluating whether a location is within a zone.
  • Replace geofence-event-based high accuracy toggling state with a GPS-derived needHighAccuracyMode flag updated during location/geofence updates.
  • Centralize the expanded geofence suffix into a constant to reduce string duplication.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
common/src/main/kotlin/io/homeassistant/companion/android/common/data/integration/ZoneAttributes.kt Adds an optional extra radius parameter to zone containment checks to support “expanded” trigger ranges.
app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt Switches high-accuracy enablement logic to be driven by location updates and introduces helper logic for expanded-zone evaluation.
Suppressed comments (1)

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:603

  • The log message says "not in zone" / "in zone", but the condition is now needHighAccuracyMode which is computed as "inside expanded trigger range but not inside the inner zone". This makes the logs misleading (e.g., when already inside the zone, it will log "not in zone"). Update the message to describe the trigger-range/need semantics.
            if (!forceHighAccuracyModeOn && !forceHighAccuracyModeOff) {
                if (!needHighAccuracyMode) {
                    Timber.d("High accuracy mode disabled, because not in zone $highAccuracyExpZones")
                } else {
                    Timber.d("High accuracy mode enabled, because in zone $highAccuracyExpZones")
                }
            }

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@eyal0

eyal0 commented Aug 26, 2026 •

Copy link
Copy Markdown
Author

This is an alternate solution, instead of #7351.

The problem is that geofences set in Android and the actual zone that we care about are not the same, even if we set them with the same radius. The math that Home Assistant is doing doesn't match the Android calculation. This is causing the high accuracy mode to turn off before we are actually in the zone.

The fix is to not rely on the geofence messages from Android Play Services. Instead, for every location update, both geofence and otherwise, we re-calculate the idea of whether or not we are in a zone that needs high accuracy. We then call setupBackgroundLocation, which turns the high accuracy mode on or off in Play Services.

One downside to this approach is that the phone will still send the zone_entered and zone_exited message when the geofence triggers it, based on the shape of the zone that Android uses, not the Home Assistant shape. Those messages of type android.zone_entered and android.zone_exited will arrive to Home Assistant around the same time as Home Assistant updates zones but not the exactly the same. In practice, this doesn't matter because Home Assistant is calculating the zone on its own. And anyway, we already have this problem. The problem is not getting any worse.

This PR also fixes another bug when there are multiple zones. What we want is to turn on high accuracy when we are in an expanded zone but not in the zone. It's a donut shape. And we want the union of all those donuts. The old code was taking a union of all the expanded zones and subtracting the union of all the zones. That is not the same shape in the case of overlapping zones. This PR fixes that along the way and the code makes more sense now.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

Suppressed comments (2)

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:767

  • updateNeedHighAccuracyMode is launched asynchronously, but setupBackgroundLocation() immediately evaluates getHighAccuracyModeState() which depends on needHighAccuracyMode. This can make high-accuracy mode toggle one update late (and makes the behavior timing-dependent).
                HighAccuracyLocationService.updateNotificationAddress(applicationContext, location)
                ioScope.launch {
                    updateNeedHighAccuracyMode(location)
                }

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:883

  • Same ordering issue as handleLocationUpdate(): needHighAccuracyMode is updated in a fire-and-forget coroutine, but setupBackgroundLocation() runs right after and may evaluate the old value.
            ioScope.launch {
                updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!)
            }
            getEnabledServers(zoneLocation).forEach {
                ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) }
            }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Suppressed comments (1)

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:880

  • updateNeedHighAccuracyMode() is also called from handleGeoUpdate on sensorWorkerScope (Main) and can touch getZones()/the mutable zone caches. For the same reason as in handleLocationUpdate, run this computation on ioScope and await it so zone-cache access stays on the IO side and setupBackgroundLocation() sees the updated value.
            )
            requestSingleAccurateLocation()
        } else {
            updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!)
            getEnabledServers(zoneLocation).forEach {
                ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) }
            }

Comment on lines 764 to 772
} else {
HighAccuracyLocationService.updateNotificationAddress(applicationContext, location)
updateNeedHighAccuracyMode(location)
// Send new location to Home Assistant
serverIds.forEach {
ioScope.launch { sendLocationUpdate(location, it, trigger) }
}
setupBackgroundLocation()
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:1152

  • updateNeedHighAccuracyMode calls getZones(serverId) while other concurrent coroutines can also call getZones (e.g. createGeofencingRequest uses ioScope.async { getZones(serverId, forceRefresh = true) }). getZones mutates/reads the shared zones/zonesLastReceived mutableMapOf caches without synchronization, so the added per-update calls increase the chance of data races / concurrent modification issues. Consider serializing access (e.g. a Mutex inside getZones, or confining zone-cache operations to a single-thread dispatcher).
                for (serverId in getEnabledServers(zoneLocation)) {
                    for (zone in getZones(serverId)) {
                        val requestId = "${serverId}_${zone.entityId}"
                        if (!highAccuracyZones.contains(requestId)) continue
                        if (isLocationInZone(location, zone)) continue
                        if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue

Comment on lines +1143 to +1161
val triggerRange = getHighAccuracyModeTriggerRange()
val highAccuracyZones = getHighAccuracyModeZones(false)

if (triggerRange > 0) {
for (serverId in getEnabledServers(zoneLocation)) {
for (zone in getZones(serverId)) {
val requestId = "${serverId}_${zone.entityId}"
if (!highAccuracyZones.contains(requestId)) continue
if (isLocationInZone(location, zone)) continue
if (!isLocationInZone(location, zone, extraRadiusMeters = triggerRange.toFloat())) continue

needHighAccuracyMode = true
if (!previousNeedHighAccuracyMode) {
Timber.d("Updated need high accuracy mode from GPS: true, in $requestId")
}
return@withContext
}
}
}

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

Suppressed comments (1)

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:606

  • These log messages say "in zone", but needHighAccuracyMode is set when the device is inside the expanded trigger range while still outside the inner zone. Updating the wording will make logs match the new behavior and avoid confusion when debugging.
                if (!needHighAccuracyMode) {
                    Timber.d("High accuracy mode disabled, because not in zone")
                } else {
                    Timber.d("High accuracy mode enabled, because in zone")
                }

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

@eyal0
eyal0 requested a balanced review from Copilot August 28, 2026 20:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.

Suppressed comments (3)

Previously missed (3) — in code that hasn't changed since the last review.

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:877

  • This update is skipped when the geofence location exceeds minAccuracy: that branch requests a single accurate location, but the callback at lines 1332–1342 sends it directly without calling updateNeedHighAccuracyMode. Consequently, entering an expanded zone with an initially poor fix leaves high-accuracy mode off even after the replacement fix arrives. Please update this state from the accurate callback or route that fix through the common location-processing path.
            updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!)

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:187

  • @Volatile only provides visibility; it does not order the read/compute/write performed by updateNeedHighAccuracyMode. Each broadcast starts a separate coroutine, and that calculation suspends on settings and zone reads, so an older location can resume after a newer one and overwrite this flag, starting or stopping high-accuracy mode from stale coordinates. Serialize the whole calculation or track and reject locations older than the last evaluated timestamp.
        @Volatile
        private var needHighAccuracyMode = false

app/src/full/kotlin/io/homeassistant/companion/android/sensors/LocationSensorManager.kt:1073

  • This global mutex is held across the network getZones() call and covers every server. createGeofencingRequest starts per-server refreshes concurrently, but they are now serialized; one slow or unreachable server also blocks cached zone access and zone-only location handling for all other servers. Use per-server locking (or equivalent keyed request deduplication) so only requests for the same cache entry serialize.
        return zonesMutex.withLock {

@eyal0

eyal0 commented Sep 2, 2026

Copy link
Copy Markdown
Author
image

In this test, with both regular and my patched home assistant companion, you can see in the first two rows how high accuracy mode is turned off early and then a whole minute later the device is finally marked as home.

In my test build, we can see how the high accuracy mode turns off right when the test build also marks the device as home.

In both cases, we are in exact mode and the zone is not being sent to Home Assistant. In the test version, the companion is turning off the high accuracy mode exactly when I get home, unlike the release version, where the high accuracy mode is turned off too early, causing a delay in detecting the arrival at home.

eyal0 and others added 22 commits September 2, 2026 16:16
The geofence shape for a zone and the shape calculated by Home
Assistant are not the same.  Turn high accuracy mode on and off based
on location updates instead of counting on the geofence.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Refactored the getZones function to use a Mutex for thread safety when accessing the zones map. This change ensures that concurrent access to zone data is properly synchronized, preventing potential race conditions. Additionally, cleaned up the logic for updating the need for high accuracy mode by removing unnecessary coroutine context switching.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
… before setting up background location. This ensures that setupBackgroundLocation is only called when necessary, improving efficiency.
…zone management. Simplified getZones method by directly returning zones without locking, improving code readability and performance.
…y mode. This allows for more accurate detection of when high accuracy mode has stopped, preventing unnecessary restarts and improving overall efficiency in location updates.
@eyal0

eyal0 commented Sep 27, 2026

Copy link
Copy Markdown
Author

I ended up just using a different solution. Trying to modify Home Assistant was too challenging.

@eyal0 eyal0 closed this Oct 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Location update is failing when coming out of high precision mode

2 participants