Skip to content

Feature request: add Play Sound button #17

Closed

Description

@YOZZOPANOZZO

Hi!

First of all, thank you for the amazing work! It’s great to be able to track AirTags on Android finally.
The only feature I’ve been missing is the Play Sound button. Is it by any chance on the roadmap?

Thanks!

Activity

  1. parawanderer commented on Jun 19, 2025

    @parawanderer
    Owner

    Hi @YOZZOPANOZZO , glad you found this useful!

    This is a request to the upstream from me actually, https://github.com/malmeloo/FindMy.py

    Specifically, see this issue: malmeloo/FindMy.py#88

    I'm mostly just concerned with the Android UI wrapper in this project but if this gets implemented per that issue then I am happy to add a button for it (pretty easy/low effort)

  2. ubrt commented on Aug 22, 2026

    @ubrt
    Contributor

    Thanks for the awesome work on this app, wanted to share some concrete work on this feature. For my use case, playing a sound is critical. I'd actually built this first as a standalone Android app of my own (TrackerHunter) just as a fun project, connecting to nearby AirTags over BLE by MAC address and triggering their sound directly, for tags that had been separated from their owner.

    The GATT protocol logic there (which service/opcode to use for DULT, generic FindMy, and AirTag-specific accessories) is based on AirGuard (https://github.com/seemoo-lab/AirGuard) (Apache-2.0) — verified against their AppleFindMy.kt/GoogleFindMyNetwork.kt source.

    Once that worked, I ported the same logic into this app: ubrt/OpenTagViewer@feature/beep-nearby-tag (https://github.com/ubrt/OpenTagViewer/tree/feature/beep-nearby-tag). It adds a "play sound nearby" entry on the device screen plus a "continuous ping" toggle right on the map (repeated scan+trigger, for walking toward a tag by ear), and it's been tested against real AirTags on a physical device.

    Image

    Why this can't live in FindMy.py alone in my oppionion: @malmeloo's PoC branch (feat/accessory-communication) uses bleak.BleakClient directly. Bleak does have an Android backend, but it's built specifically for python-for-android/Kivy (imports jnius and android.permissions, both P4A-only modules), not the completely different Java bridge Chaquopy uses. Chaquopy can bridge Python to Java directly, so it's not strictly impossible to drive Android's BLE stack from Python that way, but that stack is entirely callback-based, and routing every one of those across the Python/Java boundary is a worse fit than just writing it in Java, where the app already handles this. Either way, the scan and GATT write end up as native Android code regardless of what FindMy.py ships.

    What does belong in FindMy.py is knowing which BLE MAC address an accessory is currently expected to advertise. That's key-rotation math, already 90% there via RollingKeyPairSource; I added a small convenience (current_keys()/current_mac_addresses()) and opened it upstream: ubrt/FindMy.py@feature/current-mac-candidates (https://github.com/ubrt/FindMy.py/tree/feature/current-mac-candidates). The Android side scans for that address and does the GATT handshake using three AirGuard-derived protocols in priority order, needed because not every AirTag state exposes the same characteristic, so relying on just one misses devices.

    Happy to open this as a PR against this repo if there's interest — wanted to check first given @parawanderer's comment above ties this to the FindMy.py issue, and rt if there's already a preferred direction.

  3. parawanderer commented on Aug 22, 2026

    @parawanderer
    Owner

    @ubrt this is great, and yes please open the PR.

    Your reasoning about the split is right, and it supersedes my 2025 comment above. I'd assumed this was waiting on FindMy.py because that is where everything else Apple-shaped lives here. It isn't: the scan and the GATT write end up as native Android code whichever way it goes, and what belongs upstream is the key-rotation math that says which MAC to look for. current_keys()/current_mac_addresses() on RollingKeyPairSource is exactly the right size for that.

    I checked the bleak point rather than take it on trust, and it is worse than "built for P4A" - every file in the backend is:

    File Import
    defs.py from jnius import autoclass, cast
    utils.py from jnius import PythonJavaClass
    scanner.py from android.broadcast import BroadcastReceiver, from android.permissions import Permission, request_permissions
    client.py from android.broadcast import BroadcastReceiver

    There is a recipes/ directory in there too, which is a python-for-android build concept. Chaquopy ships neither jnius nor the android module, so this is not a port with some work: it is a rewrite against a different bridge. That is the same shape as unicorn in AGENTS.md rule 4 - a Python dependency that assumes a runtime this app does not have - and the answer there was the same one you reached, which is to do it in Java.

    The README still lists this as 🔴 BLOCKED by 🙏Feature Request. That line is wrong, but don't touch it in your PR - that whole wishlist section is stale in more ways than this one and I'd rather tidy it in one go afterwards than have you writing README churn that conflicts with it.

    I've read through feature/beep-nearby-tag. A few things I want to say plainly, because they are not the norm for a drop-in branch:

    • The header on BleGattSoundTrigger names AirGuard, its licence, and the specific files the UUIDs and opcodes were verified against. That's AGENTS.md rule 8 done before anyone asked for it.
    • BleAccessorySoundTrigger writes down what has not been run on a device, citing rule 2. That is worth more to me than a green tick.
    • Strings in all ten locales, Java rather than Kotlin, Lombok and RxJava3, and the BLE pieces constructor-injected so the orchestration is testable without hardware. You clearly read the rules (or had your agent read them 😄) rather than skimming them.

    Three things to sort out, none of them objections:

    1. The FindMy.py pin. The branch moves it to ubrt/FindMy.py@29c2160, which you've already flagged as temporary. This repo pins my fork, so the convenience methods want porting there first and the pin coming back. Open that as a PR against parawanderer/FindMy.py and I'll take it; worth sending to @malmeloo too, since it is generally useful and my fork is not where it should end up living.

    2. Licensing. This repo is MIT and AirGuard is Apache-2.0. Deriving the constants and the priority order is fine, but Apache-2.0 terms have to survive for what's derived, so it can't just be silently MIT. Your Javadoc notice is the right instinct. I'd like a NOTICE file, or an entry in the README credits, so it's stated somewhere a person looks rather than only in a header. Happy to write that part myself if you'd rather not.

    3. It needs a rebase. The branch is based on 4713711, which was the 1.3.0 exporter release. main has moved a fair amount since, including a whole iCloud flow inside the app. MapsActivity is where the conflicts will be, since you touch ~194 lines of it.

    On testing: I can't verify the GATT paths. Nobody here can review protocol code into correctness, and by your own note the orchestration hasn't run end-to-end on a device. So tell me in the PR what you actually ran and against what hardware. Which tag models, whether a tag in "separated from owner" state behaves differently from one near its owner, and whether the three-protocol fallback was observed picking the second or third path rather than only reasoned about. I'd rather ship it saying "tested against these two, unknown for the rest" than imply coverage nobody has.

    Also happy to hear what the continuous ping is like in practice. Walking toward a tag by ear is the use case that makes this feature worth having, and it's the part a test can say nothing about.


    (Note: response & initial review drafted via Claude Code, but I sign off on it 100%)

  4. malmeloo commented on Aug 22, 2026

    @malmeloo

    That change would also be very useful for hass-FindMy! Feel free to send it in as a PR <3 (to findmy.py i mean)

  5. parawanderer commented on Aug 22, 2026

    @parawanderer
    Owner

    @ubrt, random side-question: have you by any chance had a look at the local area bluetooth scan side of this topic? Or just the "sound" feature?

    They're basically related, and the app is currently not doing BLE scans to locate tags in its local area yet. Which means it is missing historical location reports sourced from potential scans, and only shows location reports from apple's FindMy network.

    I'm curious if this sort of feature can be built directly on top of your work here now or not 🤔

    EDIT: so I thought about this for a bit, and it seems to me that you basically HAD to implement the BLE logic that this app needed to provide "local area airtag scanning" by construction. The app just has to take your scan outcomes and add it to the history database of location data. Apple iPhones also just report their own GPS locations at the end of the day. This would be a minor change from your logic, and close the final suspected issue causing people to claim their iPads' and iPhones' official FindMy apps have more frequent location updates than this app!

  6. ubrt commented on Aug 22, 2026

    @ubrt
    Contributor

    Yes, I've built exactly that — TrackerHunter is a continuous local-scan app: foreground service, records every Find My accessory it sees with the phone's GPS position and RSSI, keeps history in Room, and estimates a distance trend from signal strength. I've just made it public (Apache-2.0), so the scanning service, the beacon detection and the RSSI estimate are all there to look at.

    Short version: yes, this branch is most of the hard part of the recognition problem, and the rest is a different kind of work rather than a harder one.

    What PR #139 already gives you:

    • current_mac_addresses() answers "which address should my accessory be advertising right now" — that's the whole recognition step. A local scan is otherwise just a pile of anonymous rotating addresses.
    • NearbyAccessoryScanner + BleAccessoryMatcher already scan unfiltered and match sightings against those addresses.
    • BLUETOOTH_SCAN/CONNECT are declared, and ACCESS_FINE_LOCATION plus a foregroundServiceType="location" service already existed.

    What's missing is the scanning discipline, not the identification: that PR does a 15-second burst on demand. Continuous background scanning is a different power and lifecycle problem, and wants its own foreground service.

    One thing that makes this more viable here than it looks. An accessory only broadcasts the full offline-finding beacon (type 0x12, length 0x19, carrying the public key) once it is separated from its owner; with an owner device nearby it emits a short form instead. Most users of this app have no Apple device at all — so their tags are permanently separated, permanently broadcasting the full beacon, and permanently locally detectable. The awkward state is the exception here, not the rule.

    I have a measurement for that from instrumenting the scanner while answering your separated-vs-nearby question on the PR: a single 15-second scan in an ordinary flat picked up three separated Find My accessories unprompted, none of them mine. Whatever else is hard about this feature, finding tags is not.

    That also gives you a choice of matching strategy. The MAC carries key bytes 0–5 and the payload carries the rest, so a separated beacon lets you reconstruct the entire advertising key and match it against keys_between() — no MAC precomputation at all. MAC matching via current_mac_addresses() is the cheaper path; key reconstruction is the more robust one.

    Three design notes from having built the scanning side:

    1. Don't call Python per scan result. currentMacAddresses() starts an interpreter. For continuous scanning, precompute a MAC→beaconId map once per rotation window (~15 min) and match in Java against that.
    2. A locally-sourced report is not the same claim as a network one. "My phone stood here and saw it" versus "Apple's network reported it" differ in provenance and accuracy, and LocationReport currently has no column saying which. Mixing them silently would make the history screen quietly misleading — and adding that column is a schema change, so rule 1 applies (migration, migration test, exported JSON).
    3. RSSI gives you proximity, not position. What it is genuinely good for is a warmer/colder indicator while walking toward a tag, which pairs naturally with the continuous ping in that PR.

    Happy to take this on as a follow-up once #139 is settled.

  7. added
    @hardwareTalking to tags and accessories directly over Bluetooth: scanning, ringing, protocol captures
    fixed in app 1.1.0Addressed by fixes shipping in the Android app 1.1.0 release
    on Sep 13, 2026
  8. parawanderer commented on Sep 16, 2026

    @parawanderer
    Owner

    Hi, thanks to @ubrt this feature is now available in this release! 🎉🎉🎉🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

@appIssues regarding the OpenTagViewer Android app@hardwareTalking to tags and accessories directly over Bluetooth: scanning, ringing, protocol capturesenhancementNew feature or requestfixed in app 1.1.0Addressed by fixes shipping in the Android app 1.1.0 release

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions