Repository navigation
Conversation
There was a problem hiding this comment.
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
needHighAccuracyModeflag 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
needHighAccuracyModewhich 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.
|
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 One downside to this approach is that the phone will still send the 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. |
There was a problem hiding this comment.
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) }
}
There was a problem hiding this comment.
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 fromhandleGeoUpdateonsensorWorkerScope(Main) and can touchgetZones()/the mutable zone caches. For the same reason as inhandleLocationUpdate, run this computation onioScopeand await it so zone-cache access stays on the IO side andsetupBackgroundLocation()sees the updated value.
)
requestSingleAccurateLocation()
} else {
updateNeedHighAccuracyMode(geofencingEvent.triggeringLocation!!)
getEnabledServers(zoneLocation).forEach {
ioScope.launch { sendLocationUpdate(geofencingEvent.triggeringLocation!!, it, trigger) }
}
| } else { | ||
| HighAccuracyLocationService.updateNotificationAddress(applicationContext, location) | ||
| updateNeedHighAccuracyMode(location) | ||
| // Send new location to Home Assistant | ||
| serverIds.forEach { | ||
| ioScope.launch { sendLocationUpdate(location, it, trigger) } | ||
| } | ||
| setupBackgroundLocation() | ||
| } |
There was a problem hiding this comment.
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
updateNeedHighAccuracyModecallsgetZones(serverId)while other concurrent coroutines can also callgetZones(e.g.createGeofencingRequestusesioScope.async { getZones(serverId, forceRefresh = true) }).getZonesmutates/reads the sharedzones/zonesLastReceivedmutableMapOfcaches without synchronization, so the added per-update calls increase the chance of data races / concurrent modification issues. Consider serializing access (e.g. aMutexinsidegetZones, 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
| 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 | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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
needHighAccuracyModeis 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")
}
There was a problem hiding this comment.
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 callingupdateNeedHighAccuracyMode. 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
@Volatileonly provides visibility; it does not order the read/compute/write performed byupdateNeedHighAccuracyMode. 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.createGeofencingRequeststarts 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 {
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.
5cd2435 to
9bffbcc
Compare
…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.
|
I ended up just using a different solution. Trying to modify Home Assistant was too challenging. |

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
Select exactly one option that describes AI usage in this contribution:
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