Repository navigation
fix: do not align ahead of the accessory on secondary key reports - #275
Open
lonsdaleite wants to merge 3 commits into
Open
lonsdaleite wants to merge 3 commits into
lonsdaleite wants to merge 3 commits into
Conversation
A secondary key is shared by up to two days of primary indices, and `_fetch_accessory_reports` aligned to the highest of them. The next fetch starts its search at the maximum index derived from that alignment, so it matched the same secondary key at a higher index again, and the alignment kept moving ahead of the accessory: by about 150 indices (38 hours) in a few hours for an AirTag that was away from its owner in the morning. Align to the lowest index sharing the key, with the alignment date moved back by the width of the range, so that the minimum index never exceeds the index the accessory used and the maximum index never falls below it. For a primary key the range is a single index and nothing changes.
…lignment A fetch can return many reports for the same secondary key, and finding its range takes up to 192 `keys_at` calls (about 30 ms), so the range is now kept per key for the duration of the fetch. Primary keys belong to a single index and are not cached. The existing test started from an alignment newer than the backdated date, so every update was rejected and it passed even without any alignment. Add a test that starts from a week-old alignment, which a secondary report has to move up to the lower end of the key's range without passing the accessory.
lonsdaleite
marked this pull request as ready for review
October 10, 2026 10:06
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.
Part of #273: the overshoot from secondary key reports, as suggested in this comment. It does not fix the tag reported in #273 itself, see Not covered.
Problem
A secondary key is shared by up to two days of primary indices:
_secondary_keys_atreturnsind // 96 + 1and+ 2, soS(s)is generated for every primary index in[(s - 2) * 96, s * 96 - 1]. On a report under a secondary key,_fetch_accessory_reportsaligned to the highest of the indices it had generated the key for.That overshoot also grows over repeated fetches. The next fetch starts its search at
get_max_index(now), which is the alignment index plus the time since the report. Reports arrive late, so the search ceiling is now above the previous alignment, the same secondary key is matched at a higher index, and the alignment moves further ahead. It stops only when the key's range runs out ats * 96 - 1. Becauseupdate_alignmentnever moves back, a correct observation of the primary key cannot undo it.Observed
An official AirTag (paired 2022), tracked through hass-FindMy with local BLE matching and an account fetching reports every 15 minutes:
index - (unix_time // 900)= −1827446 for days, matching the alignment record from the iCloud export (−1827447). The tag's primary index runs in step with the clock.primary // 96 + 2(atprimary % 96= 17–18), as expected.Change
_align_to_reportaligns to the lowest index sharing the matched key, with the alignment date moved back by the width of the range (high - lowintervals):low + (high - low) + time since the report=high + time since the report) never falls below it, so the search ceiling is not lowered. This is the concern from Old AirTag: alignment from the first fetch is ~40 h ahead of what the tag broadcasts; with the BLE-observed alignment no reports are found #273 about taking a lower index with the same date.For a primary key the range is a single index, so primary reports align exactly as before.
update_alignmentstill ignores anything older than the current alignment date, so a secondary report no longer moves an alignment that came from a newer primary observation.key_to_indonly holds the indices generated so far, because keys are generated in batches from the ceiling down, so_key_index_boundsextends the range of a secondary key to all indiceskeys_atreturns it for (at most 192). That takes up to 192keys_atcalls (about 30 ms), and a fetch can return many reports for one secondary key, so the range is kept per key for the duration of the fetch.Not covered
primary // 96, beyond the+1/+2that_secondary_keys_atgenerates. For such a tag the lower end of the key's range is still above the index it uses, so the alignment would still end up ahead of it. That is a separate gap in the key model; this change does not search further ahead, and the invariant above only holds for tags that use one of the keyskeys_atreturns.update_alignmentonly accepts higher indices. Such an accessory stays misaligned until its real index catches up (up to about two days), or until the alignment is corrected by hand.Testing
tests/test_alignment.py:pytest,ruff check,ruff format --checkandbasedpyright(versions fromuv.lock) pass.