Skip to content

Enrolling devices

How-to · Last verified against firmware v4.0.6 on 2026-05-11.

Some devices Just Work — drop in a Blue Charm beacon and ESPresense finds it. Others need an IRK, a setting flipped in a vendor app, or a workaround that the community has spent years arguing about. This page is the per-device recipe sheet, scoped to the recipes that have actually been confirmed in Discussions.

Source matrix: canonical pin #2324 “Enrolling devices: what works, what doesn’t, and where to look” by @Terastar-Paperclip.

DevicePathStatus
iPhone / iPadPair to ESPresense node, IRK auto-captured✅ Settled
iPhone (iOS 17+, in-firmware pair fails)Mac → Keychain Access fallback✅ Settled (fallback)
Apple WatchPair from watch Settings → Bluetooth (firmware emulates an HRM)✅ Settled
Apple Watch (fallback)Mac → Keychain Access lookup✅ Settled (fallback)
AirPods / BeatsX / HomePodnone❌ No settled path
Withings ScanWatchAndroid HCI snoop → Wireshark → IRK✅ Settled (involved)
Polar HR straps (H10 / H9)Broadcast HR mode on, generic name: enrollment✅ Works
Polar Loop (2025)none❌ No settled path
Moto Tag / Galaxy SmartTag / Find-My-networknone⚠️ Limited; identifier rotates
Android phonesHA Companion app ble_transmitter✅ Settled
Amazfit / Mi Band / Zepp watchesVendor app → enable Discoverable✅ Settled

If your device is not in the table, start at /devices for the known-working list. If it advertises an iBeacon or Eddystone profile, no enrollment is needed — the firmware picks it up on first sight.

Source: #1348 · existing recipe lives on /apple. Confirmed by @DTTerastar (maintainer) and multiple users.

The settled path is to put the firmware in enrollment mode and pair the phone over BLE. The phone exchanges its IRK during the secure pairing handshake; the firmware reads it from the BLE bond store and publishes a retained config to MQTT.

  1. Open http://<node-ip>/ui/#/devices on the same network as the node.

  2. Type a friendly name (e.g., dt-phone) in the Enroll field and click Enroll. The node starts advertising a Heart Rate Monitor service named ESPresense for 120 seconds.

  3. On the iPhone: Settings → Bluetooth. Tap the new ESPresense entry and accept the secure pairing prompt.

  4. The enroll prompt clears automatically. The firmware publishes:

    espresense/settings/irk:<32-hex>/config (retained)
    {"id": "dt-phone", "name": "dt-phone"}
  5. Other nodes pick up the retained config and resolve the same iPhone the next time it advertises.

A subset of iPhones on iOS 17+ accept the pair but never deliver the IRK to the firmware (#1348, tracked). The settled fallback is to read the IRK out of iCloud Keychain on a paired Mac. The full procedure (Keychain Access → search bluetoothPublic: XX:XX:... → Show Password) is on /apple → Lookup Method. The decoded 32-character hex IRK goes into the same espresense/settings/irk:<hex>/config topic as above.

Source: #492. Partial — not a calibration knob. The community has not settled this.

iPhones suppress nearby BLE advertisements when locked unless something on the device has a reason to talk. Reports:

  • @DTTerastar: “My iPhone blasts out nearby info every 500ms no matter what” — works without intervention on his unit.
  • @51av0sh: a paired Apple Watch keeps the phone broadcasting for ~20 minutes after lock.
  • @huytd2k: installing the room-assistant iOS app keeps it advertising.
  • Universal Clipboard / Handoff, iCloud Family Sharing, and iCloud Photo backup have all been reported to keep an iPhone broadcasting through lock — none of them is a guaranteed fix.

If you hit this, the IRK enrollment is not the problem. Add an Apple Watch on the same Apple ID, or accept a “presence with hysteresis” approach in Home Assistant.

Source: #2099 · canonical recipe on /apple → Apple Watch. Confirmed by @DTTerastar (maintainer): current firmware emulates a Heart Rate Monitor well enough that watchOS pairs to it natively from the watch’s Bluetooth settings.

The settled path is identical to the iPhone path: put the firmware in enrollment mode, then pair from the watch.

  1. Open http://<node-ip>/ui/#/devices on the same network as the node.

  2. Type a friendly name (e.g., dt-watch) in the Enroll field and click Enroll. The node starts advertising a Heart Rate Monitor service named ESPresense for 120 seconds.

  3. On the Apple Watch, open Settings → Bluetooth. Wait for ESPresense to appear (some watchOS versions surface it under “Health Devices”) and tap it. Accept the pair request.

  4. The Enroll prompt clears automatically. The firmware publishes:

    espresense/settings/irk:<32-hex>/config (retained)
    {"id": "dt-watch", "name": "dt-watch"}
  5. Other nodes pick up the retained config and resolve the same watch the next time it advertises.

If pairing doesn’t start within ~30 seconds, toggle Bluetooth off and back on (Settings → Bluetooth) on the watch and click Enroll again to restart the 2-minute window.

If the watch never produces the pairing prompt (older firmware, edge-case watchOS version), read the watch’s IRK from your iCloud Keychain on a paired Mac. The full procedure (Keychain Access → search bluetoothPublic: XX:XX:... → Show Password) is on /apple → Lookup Method. The watch’s Bluetooth address is at Watch → Settings → About. The decoded 32-character hex IRK goes into the same espresense/settings/irk:<hex>/config topic as the iPhone path.

Source: #1531. Contributors: @DrSpaldo, @dxmnkd316, @nonanonymousanon.

What’s true:

  • AirPods are visible in the fingerprints list as apple:0707:<len> or similar Continuity payloads — but multiple sets of AirPods produce the same fingerprint, so they’re not uniquely identifiable.
  • @dxmnkd316 reports their AirPods kept the same Bluetooth address for ~8 hours instead of the expected ~15 minutes. Address persistence appears generation- and firmware-dependent, so even pinning by MAC is unreliable.
  • macOS-extracted IRKs can sometimes be obtained for AirPods Pro 2 and later (the same Public: XX:XX:... Keychain entry trick as iPhone), but the success rate varies and the key may stop resolving after a firmware update.

If you need “is somebody wearing their AirPods” presence, an apple:0707:* count over time can be useful as a soft signal, but don’t wire it to identity.

Source: #1247. Contributors: @ginkel (asker), @max00346 (procedure), @rkrkrk0987 (Android 15 path), @wanghuangjie (MQTT example), @vincenttor (Wireshark help).

Withings devices don’t pair to the ESPresense node — they pair only to the Withings app, which lives on the phone. The settled path is to capture the pairing handshake on an Android phone you control, extract the IRK from the HCI snoop log, and publish it manually.

You will need: an Android phone with developer options, a USB cable, adb, and Wireshark.

  1. Android → Settings → System → Developer options. Turn on:

    • Enable Bluetooth HCI snoop log (a.k.a. “Bluetooth Debugging to File” on some OEM skins)
    • USB debugging
  2. Pair the ScanWatch to that Android phone through the Withings app. Wear the watch close enough that the pairing actually completes.

  3. Pull the bug report:

    Terminal window
    adb bugreport scanwatch

    The output is a .zip.

  4. Find the HCI log inside the zip at FS/data/log/bt/*_hci.log (Android 14 and below) or FS/data/misc/bluetooth/logs/* (Android 15+, per @rkrkrk0987).

  5. Open the log in Wireshark. Filter on btsmp. Find the Identity Information packet — that’s the IRK delivered by the watch during the secure-pairing handshake.

  6. Copy the 32-character hex IRK out of the packet (little-endian — Wireshark shows it big-endian for some captures; if resolution fails, byte-reverse).

  7. Publish to MQTT with retain:

    espresense/settings/irk:<32-hex>/config (retained)
    {"id": "scanwatch", "name": "Withings ScanWatch"}

Same irk: topic shape as the iPhone path — once it’s published retained, every ESPresense node in the deployment picks it up.

Source: #2054 “Polar loop compatibility?”. Asker: @jacksaturn. The community has not validated the 2025 Polar Loop.

These work when Broadcast Heart Rate is enabled in Polar Flow / Polar Beat. With broadcast on, the strap advertises with a stable BLE MAC and a recognizable manufacturer/service signature, and ESPresense fingerprints it as name:polar-h10-… (or similar — the kebabified BLE-advertised name). Enroll exactly like a generic named device:

espresense/settings/name:polar-h10-12345678/config (retained)
{"id": "polar-strap", "name": "Polar H10"}

Same caveat as Garmin Instinct Solar on /devices: the strap only broadcasts while Heart Rate Broadcast is on. If you take it off and put it back, broadcast is off until you turn it on again.

Moto Tag / Galaxy SmartTag / Find-My-network

Section titled “Moto Tag / Galaxy SmartTag / Find-My-network”

Source: canonical pin #2324. Greenfield — the community has not settled a path.

What you can do:

  • Count, don’t identify. A rolling count of distinct apple:findmy MACs near a node can tell you “more than usual” but not “Bob’s tag is here.”
  • Track the carrier, not the tag. If the tag rides in your bag and your phone is in your pocket, track the phone via IRK.
  • OpenHaystack beacons: if you flash an ESP32 or an nRF as an OpenHaystack tag, the BLE address is yours — pin it in ESPresense by MAC like any other beacon. This is on /devices.

Apple AirTags are explicitly on the /devices “known to not work” list for individual tracking. Galaxy SmartTags are listed as “randomized mac addresses.”

Source: /android · #880 (asker @moblsu, thread unanswered).

Android does not deliver an IRK to a non-bonded peripheral and rotates the BLE MAC. The settled path is to run the Home Assistant Companion App with BLE Transmitter enabled — the phone broadcasts an iBeacon with a UUID you choose, and ESPresense fingerprints that UUID as a stable identity.

  1. Install Home Assistant Companion App on the phone.
  2. In the app: Settings → Manage Sensors → BLE Transmitter → enable. Optionally configure the UUID (default is per-device).
  3. Whitelist the app from battery optimization (Android will kill it otherwise).
  4. ESPresense sees the phone as an iBeacon-shaped identity. Enroll it the same way you would any iBeacon — by the UUID/major/minor.

Beacon Scope (standalone Android app, Play Store) is the alternative if you don’t run Home Assistant.

Source: #1202. Contributor: @SteveDockar.

These devices keep a static BLE MAC and fingerprint as mifit:<mac>. The catch is they don’t pair to ESPresense — you have to flip them to broadcast and then write the config to MQTT yourself.

  1. In the Zepp / Mi Fitness app: Profile → watch/band → Bluetooth visibility (also labeled “Discoverable” on some models): on. Without this, the watch only talks to a paired phone and ESPresense never sees it.

  2. Find the fingerprint. Open http://<node-ip>/ui/#/devices and look for a mifit:<mac> entry that appears when the watch is nearby.

  3. Publish a retained config to MQTT:

    espresense/settings/mifit:<aa:bb:cc:dd:ee:ff>/config (retained)
    {"id": "amazfit-balance", "name": "Amazfit Balance"}
  4. The device now shows up as amazfit-balance across every node in the deployment.

Confirmed working units from the community: Amazfit Balance, Band, Bip S, Bip 3 Pro, GTS 2 Mini, GTS 4 Mini, GTR 2e, Xiaomi Mi Band. Full list on /devices.

Whatever path got you here, the device should now show up under its friendly name at:

  • http://<node-ip>/ui/#/devices (per-node view)
  • espresense/devices/<id>/<room> MQTT topic (RSSI + distance per node)
  • Companion’s device list, if you run it

If distances look off, head to Calibration — that’s where to fix RSSI@1m, per-node offsets, and absorption. Enrollment only gets the device seen; calibration is what makes distances accurate.

If you find a working recipe for a device that’s flagged ⚠️ or ❌ above, please post to the linked Discussion. The matrix at the top is maintained from confirmed community reports — your one-line “this worked for me” is what moves a device from ⚠️ to ✅.