TL;DR: Home Assistant now knows which room my keys, the backpack, the suitcase and even the car are in. They’re 11 Apple AirTags, and the same Bluetooth trilateration engine that locates us locates them too, all of it locally.
- The previous post ended with “more things to locate: watches, keys with a Bluetooth tag…”. Done.
- An AirTag can’t be recognized the way an iPhone can, and Bermuda can’t handle it: it changes its Bluetooth address every quarter of an hour. But with its keys you can work out which address it will use at any moment.
- The keys are pulled out once, with a Mac, and never leave my server: Home Assistant only gets the addresses.
- From there on, an AirTag is just another device: floor plan, history, training, Home Assistant entities and even the 3D house.
- An AirTag broadcasts far less than a phone, and that forced two changes: training them separately and listening to them for longer.
- Everything runs locally, without going through Apple’s cloud or the Find My network. I built it with AI in an afternoon and a morning.
I open the Position tab of the engine’s web app, and the ground floor plan has more squares than circles. The circles are Ruth and me, in the living room. The squares are things: the house keys, in the entrance hallway; the Mazda keys, in the handbag, in the living room; two more AirTags, in the kitchen. If I switch the plan to the basement, the car is in the garage, so is the suitcase, and the orange Volvo key is in the hall, where it always is.
An AirTag isn’t an iPhone (and Bermuda can’t handle it)
To follow our iPhones, Home Assistant uses the Private BLE Device integration. An iPhone changes its Bluetooth address every so often so nobody can follow it, but with one of its keys, the IRK, you can still recognize it. The trilateration engine relies on that.
An AirTag has no IRK. It changes its address every 15 minutes while it’s near its owner, and once a day when it has been away for a while. And that address isn’t random: it comes from keys that only the AirTag and its owner’s Apple account know. That’s how Apple’s Find My network works: any iPhone passing by hears it and sends its location, encrypted, so only its owner can read it.
That’s also why Bermuda, the most popular Bluetooth trilateration integration for Home Assistant, is no use here: it recognizes phones by their IRK, iBeacons and devices that always keep the same address. An AirTag is none of the three.
The Find My app tells me the keys are “at home”. Sure, but in which room?
I looked for anyone who had already solved it. The closest is hass-FindMy, a Home Assistant integration by the author of FindMy.py: it recognizes your AirTags with their keys and tells you which proxy hears them best. That fell short for me: the proxy that hears something best isn’t always in the same room. That’s what happened to me with Bermuda and the phones, and why I built my own trilateration engine.
The AirTag keys: a Mac and FindMy.py
An AirTag’s keys are only held by the devices on your Apple account. The easiest way to get them is a Mac signed into your account and the FindMy.py library, which decrypts them from the Find My app’s files:
python3.13 -m venv findmy-env
source findmy-env/bin/activate
pip install findmy
python3 -m findmy decrypt --out-dir airtags
The Mac asks for the keychain password for an entry called “BeaconStore”, which holds the key to those files. You have to be sitting in front of it: over SSH, the prompt never shows up.

Watch your macOS version. This works first time up to macOS 14. On macOS 15 it sometimes does too, and if not, there’s a much more complicated method that requires temporarily disabling one of the Mac’s security protections. On macOS 26, the latest, Apple protects the key to those files better, and so far nobody has managed to get it out, not even with that protection disabled: FindMy.py’s documentation explains it. So if you have a Mac on macOS 14 or earlier, don’t update it: it’s your key to adding new AirTags.
The result is the keys of every device on the account, as JSON: iPhones, Macs, AirPods… and the 11 AirTags. Those keys are like a password: with them, anyone could track my AirTags. They live on the server, in a folder that stays out of git, and the engine’s container can only read them.
First, check that the Bluetooth proxies hear them
That was the first thing we did: work out each AirTag’s address at that moment and look for it among what the proxies were hearing. It worked: 10 of the 11 could be heard, by up to 20 of the 21 proxies, Shellys included. Near their owner they send a short message, and away from it a long one, but both carry the address you can work out.
How it fits into the trilateration engine
The idea is simple: if I know which address each AirTag is going to use, from there on it’s just like a phone.
- The engine reads the keys and works out the addresses each AirTag may use in the coming hours.
- It passes them to my Home Assistant integration. Only the addresses, which the AirTag already broadcasts over the air: the keys never leave the engine, just as the iPhones’ IRK never leaves Home Assistant.
- The integration looks for those addresses among everything the proxies hear and sends the readings to the engine under a pseudonym, as it does with the phones.
- From there, everything is the same: floor plan, history, training and Home Assistant entities.

The engine remembers which key each AirTag is on, so after a restart it doesn’t have to look for it again. If an AirTag goes unheard for weeks, the engine may lose track of it; then you just pull its keys out with the Mac again.
To work out the addresses I didn’t want to depend on the whole of FindMy.py: I copied and adapted just the part I needed, which is MIT-licensed.
For the techies: how the address is derived
- The keys: each AirTag has a public master key and two secrets, one for when it’s near its owner and another for when it’s away. Each secret yields a chain of keys, each one derived from the previous one (ANSI X9.63 KDF), and from it that moment’s public key, on the P-224 curve. The Bluetooth address is the first 6 bytes of that public key.
- The pace: the “near” key moves on every 15 minutes; the “away” one, once a day.
- The window: around the key it reckons is current, the engine sends the integration the addresses for the previous 36 hours and the next 12, and refreshes them every minute. Every time it hears an AirTag with a specific key, it uses that key to re-center the window.
- The protocol: the engine first checks that the integration speaks its version and only then sends it the addresses, with a separate WebSocket command.
- The state: which key each AirTag is on is saved every 5 minutes to a file, with no keys, just a fingerprint to know which AirTag it belongs to.
Built with AI, again
Like the engine, I built this with AI agents in GitHub Copilot, but this time with two models: Claude Opus 5.5 writes and GPT-6-Astra reviews. Astra reviewed the plan before I started and then the code, over four rounds.
Training an AirTag without spoiling the phones
In the previous post I explained that you can teach the engine the house: you stand still at a spot with your phone and it learns what that spot sounds like. My first question was: if I train with an AirTag, do the phones get worse? They do.
Almost everything it learns is shared by all devices: how much to correct each proxy, how much the walls take away and how the signal drops with distance. An AirTag broadcasts more weakly and usually sits in a bag or hangs on a wall. The engine would have taken those differences as if they came from the proxies, and that would have moved the phones.
Now learning happens on two levels:
- What’s shared, learned only from the phones’ sessions. An automated test checks that what’s learned for the phones comes out identical, with or without AirTag sessions.
- What’s specific to each AirTag: how strongly it broadcasts and what it sounds like at the spots where I train it. It’s only used to locate that AirTag. If I delete its session, it goes back to how it was.
And you can train several at once: I left each one in its place and started 8 sessions in under half an hour.

That 0% doesn’t mean the training went wrong. It’s how often the engine got it right during the session: while the key sat still in the hall, the engine kept putting it somewhere else. That led me to the next problem.
The key that lived in the garage
The orange Volvo key lives in the basement hall, a meter from the garage. I trained it for ten minutes in its spot and the engine wouldn’t budge: garage.

The training had gone fine: with the data from those ten minutes, the point that fit best was exactly where the key was. The problem was something else: an AirTag barely talks.
The engine decides based on what the proxies heard in the last 3 seconds. With a phone, that’s more than ten readings. With an AirTag, just a few, from about three proxies, with one or two readings each: thin clues that change all the time.
And with thin clues, the big area wins. The engine spreads the probability over points 50 cm apart, and the area with more points gets more probability. The garage has 151; the hall, 22.

To test it, I replayed the training session with the recorded data, but looking at the last 15 seconds instead of 3:
So now, for AirTags only, the engine looks at 15 seconds. The phones stay the same. The downside: if I grab the keys and take them away, it takes a few more seconds to notice.
In the first 27 hours after the change, the orange key was in the hall 91% of the time.
A web app for many devices
The Position tab was designed for two people. With eleven more things, names got cut off and the floor plan was too small. Now:
- The floor plan takes up almost the whole screen, zooms with the wheel or two fingers and pans by dragging. On wide screens it rotates to make the most of the width.
- People are circles, and things, squares.
- Below it there’s a card for each one, split into people and objects. Tap one and the plan moves to it and shows its details.

In the 3D house too
The 3D house draws them too. Each AirTag is a pin with its own icon in the room where it is, with its lines to the key proxies. Hover over one and a tooltip tells you where it is, since when, and how confident the engine is.

Q&A
Does it drain the AirTag’s battery?
No. The proxies only listen to what the AirTag already broadcasts for the Find My network.
Does Find My still work?
Yes. I don’t touch anything on the AirTag or the Apple account: I only listen.
Does it work locally, without internet?
Yes. The proxies, Home Assistant and the engine all run at home, without Apple’s cloud. You only need the Mac once, to get the keys out.
Do I need a Mac?
Yes, one on macOS 14 or earlier, to get the keys out once, and again if you re-pair the AirTag. On macOS 26 it’s no longer possible.
Does it work with other Find My tags?
In theory, yes: all Find My network accessories use the same system. I’ve only tested it with AirTags.
Is it safe?
The keys only live on the server, out of git, and the engine can only read them. They never show up in the logs or the web app. Home Assistant only gets the addresses for the coming hours, which the AirTag already broadcasts over the air.
Where’s the code?
Like the engine, it’s not public for now.


Comments
via GitHub DiscussionsSign in with GitHub to join the conversation. Comments are public and stored in GitHub Discussions.