Add location reporting as a device_tracker - #46
Open
jlclemmons wants to merge 2 commits into
Open
Conversation
The companion registers sensors but never reports position, so Home Assistant has no device_tracker for the machine and cannot use it for presence or zones. Add a location module that holds a GeoClue2 client and posts each fix to the existing update_location webhook. Home Assistant creates the device_tracker from the first update, so no registration step is needed. It is off unless a "location" section enables it, and adds no dependency: dbus_next is already required for notifications. Notes on the implementation: - The accuracy level requested is EXACT. Lower levels rely on wifi/cell lookups, and where those databases have no coverage GeoClue returns no location at all rather than a coarse one, so anything less can silently never produce a fix. EXACT is only a hint about which source GeoClue may use, though, not a floor on what it delivers. - Updates are driven by GeoClue's DistanceThreshold rather than a timer, since a GNSS fix is expensive on battery. That means the last emitted fix can be a threshold's worth of distance behind where the device came to rest, so the default is a fairly tight 30m. - A stationary device stops emitting entirely, which is exactly when a lost device still needs to report, so a heartbeat reports the position when nothing has moved. It re-reads the client's current location rather than resending the last payload: update_location carries no timestamp, so Home Assistant stamps whatever arrives as current, and replaying a cached fix would assert a stale position indefinitely while looking fresh. Re-reading costs nothing, as GeoClue serves the last fix its source produced rather than powering up a receiver to answer. - For the same reason, fixes are dropped when they are less accurate than max_accuracy or older than max_age, and nothing is sent at all when there is no usable fix. A tracker that goes stale is honest; one that is confidently in the wrong place is not. - Altitude, speed and course are only included when non-zero. GeoClue reports 0.0 for values its source cannot determine, and Home Assistant would otherwise render those as real readings. - The desktop_id must match an installed .desktop file: GeoClue's agent authorizes by that id, and an unknown one leaves the client started but never receiving a fix. - The config section and every knob in it carry pydantic defaults rather than being merely Optional. Pydantic treats Optional without a default as required-but-nullable, so without this an existing configuration file, which has no location section at all, would stop validating and the application would refuse to start. The README is explicit that the quality of this depends entirely on what the machine can position with. A device with a GNSS receiver gets metres; a desktop without one falls back to a wifi lookup that can be wrong by a street. Hence max_accuracy defaulting to 500m, which rejects cell-tower-grade fixes without discarding the wifi positioning such a machine depends on. Tested on a FuriPhone FLX1s+ running FuriOS (Phosh, geoclue 2.7.1 with the hybris GNSS source), reporting to Home Assistant 2026.7 over HTTPS. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cover what the module decides rather than what GeoClue does: which fixes are worth asserting as the device's position, and what the heartbeat reports. The heartbeat cases are the ones worth having. Reporting the current fix instead of replaying the last payload is the difference between a tracker that converges on where the device stopped and one that asserts a stale position forever, and neither is visible in Home Assistant, which stamps whatever arrives as current either way. The tests drive the event loop themselves rather than taking a marker from pytest-asyncio: it is in requirements.txt but not in the tox environment, so a marker would pass in CI and silently skip under tox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
Adds optional location reporting: a GeoClue2 client that posts fixes to the existing
update_locationwebhook, which makes Home Assistant create adevice_trackerfor the machine so it can be used for presence and zones. This is the "roaming laptops" direction from the README's to-do list, though it does not attempt the remote/local instance part.locationsection sets"enabled": true.dbus_nextis already required for notifications.How well it works depends on the machine
Worth being upfront, since the answer is very different per device:
max_accuracydefaults to 500m for that reason: it rejects cell-tower-grade fixes without discarding the wifi positioning a desktop depends on. Devices with GNSS can set it far tighter.Design notes
Two things that are less obvious than they look, both documented in the commit and the README:
update_locationcarries no timestamp, so Home Assistant stamps whatever arrives as current. Replaying a cached fix therefore asserts a stale position indefinitely while looking freshly updated. Re-reading costs nothing, since GeoClue serves the last fix its source produced rather than powering up a receiver to answer. It exists at all becauseDistanceThresholdmeans a stationary device stops emitting entirely, which is when a lost device most needs to report.max_accuracyor older thanmax_age, and nothing is sent when there is no usable fix.RequestedAccuracyLevel = EXACTis only a hint about which source GeoClue may use, not a floor on what it delivers, so coarse fixes still arrive and would otherwise be plotted as the device's position. A tracker that goes stale is honest; one that is confidently in the wrong place is not.Similarly,
distance_thresholddefaults to a fairly tight 30m because the last emitted fix can be a threshold's worth of distance behind where the device actually came to rest.Testing
halinuxcompanion/test_location.pyadds 15 tests covering config defaults and the reporting rules, including that the heartbeat reports the current fix rather than a stale one. They drive the event loop directly instead of using apytest-asynciomarker, since that plugin is inrequirements.txtbut not in the tox environment, where a marked test would silently skip.pycodestyle --max-line-length=120is clean across the package.Happy to adjust any of the defaults, or to gate the feature differently, if you would rather it were shaped another way.
Developed with AI assistance (Claude); the commits carry a
Co-Authored-Bytrailer to that effect. Everything above has been reviewed and tested on real hardware.