Skip to main content

Apple

apple-ble

CI PyPI Python License: MIT

Read AirPods battery (case / left / right + charging) and detect nearby Apple devices from Python, straight off the unencrypted Apple Continuity proximity-pairing BLE advertisement — no pairing, no connection.

This library powers the Apple BLE Home Assistant integration, but works standalone in any Python project (or straight from the command line). The parser is pure and dependency-free; bleak is only needed for the optional scanner CLI.

What works: AirPods broadcast their battery in an unencrypted Continuity advert when the lid is opened. What doesn't: Apple Watch / iPhone do not put battery in their adverts — BLE can only tell you an Apple device is nearby. Apple rotates the BLE MAC (~15 min), so there is no stable per-device id.

Features

  • 🔋 AirPods battery — case, left, and right levels plus per-pod and case charging flags, decoded from the manufacturer-0x004C proximity-pairing advert.
  • 👂 In-ear / in-case / lid — which bud is in an ear, how many are in the case, and whether the case lid is open (layout verified on real AirPods Pro 2).
  • 🔐 Exact battery with the AirPods keys — decrypt_status() decrypts the AES half of the advert (1 % battery, charging, connected host) and resolve_rpa() recognises your AirPods behind the rotating address.
  • 🎧 Model detection — AirPods 1/2/3, Pro, Pro 2, Max.
  • 📡 Presence-friendly — exposes the Apple manufacturer id so callers can count nearby Apple devices.
  • 🧪 Pure parser — parse_proximity_pairing(bytes) -> AirPodsData | None, no I/O, trivially unit-testable.
  • 🖥️ CLI — apple-ble scans and prints any AirPods it sees (needs the cli extra).

Requirements

  • Python 3.12 or newer.
  • A Bluetooth LE adapter (built-in or USB) — only for live scanning; the parser itself needs nothing.

Installation

pip install apple-ble            # core (parsing only)
pip install "apple-ble[cli]"     # + bleak scanner CLI

CLI usage

apple-ble    # 15s BLE scan; open your AirPods lid nearby and watch the battery

Library usage

from apple_ble import parse_proximity_pairing, APPLE_MANUFACTURER_ID

# `payload` is manufacturer_data[76] from any BLE stack (bleak, Home Assistant, ...)
data = parse_proximity_pairing(payload)
if data:
    print(data.model, data.left_battery, data.right_battery, data.case_battery)
    print(data.left_charging, data.right_charging, data.case_charging)

With the per-AirPods keys (on a Mac: keychain item MobileBluetooth, account = AirPods address, fields MagicAccIRK / MagicAccEncKey):

from apple_ble import decrypt_status, parse_key, resolve_rpa

irk, enc = parse_key(irk_text), parse_key(enc_text)   # accepts "FE-47-…" or hex
if resolve_rpa(address, irk):                          # these are *your* AirPods
    st = decrypt_status(payload, enc, flipped=data.flipped)
    print(st.left_battery, st.right_battery, st.case_battery, st.host_suffix)

parse_proximity_pairing returns None for anything that isn't a complete AirPods proximity-pairing advert, so it is safe to feed it every Apple advert you see and keep the non-None results.

API

Symbol Description
parse_proximity_pairing(data) Parse manufacturer_data[76] bytes into AirPodsData, or None if it isn't AirPods.
AirPodsData Frozen dataclass: model, left_battery, right_battery, case_battery (int % or None), left_charging, right_charging, case_charging, model_id, flipped, left_in_ear, right_in_ear, pods_in_case, lid_open (None when no bud in the case is visible), color.
decrypt_status(data, enc_key, *, flipped) Decrypt bytes 11–26 → DecryptedStatus (exact %, charging, host_suffix), or None on a wrong key.
resolve_rpa(address, irk) True if the random address was generated from this IRK (keychain byte order).
parse_key(text) Parse a 16-byte key from FE-47-…, fe:47:… or plain hex.
decode_battery_nibble(nibble) Pure helper: a 0x0–0xF battery nibble to a percentage (None when not present).
APPLE_MANUFACTURER_ID 76 (0x004C) — Apple's Bluetooth company id.
MODEL_BY_CHAR Mapping of the model nibble to a human model name.

Credits

Continuity reverse-engineering: furiousMAC/continuity, kavishdevar/librepods, delphiki/AirStatus, d4rken-org/capod. Author @hudsonbrendon.

License

MIT — see LICENSE.

Metadata

Release files for apple-ble 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for apple-ble 0.2.0
File Size Uploaded Provenance Actions
apple_ble-0.2.0.tar.gz 35.5 kB Without provenance Details for apple_ble-0.2.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for apple-ble 0.2.0
File Interpreter ABI Platform Actions
apple_ble-0.2.0-py3-none-any.whl Python 3 none any Details for apple_ble-0.2.0-py3-none-any.whl

Total release size: 46.2 kB

Release files / apple_ble-0.2.0.tar.gz

Download URL apple_ble-0.2.0.tar.gz
Size 35.5 kB
Tags Source
SHA-256 checksum
How to use checksums
f2cb539ec4af194297907373e8a828487b97d334b0f23d7e3c75e3a8e207547b
BLAKE2b-256 checksum
How to use checksums
53d680d44ab0cb3984fce69811e7c6fd223de1d098eec22d8f529e0400d5486b
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.5

Release files / apple_ble-0.2.0-py3-none-any.whl

Download URL apple_ble-0.2.0-py3-none-any.whl
Size 10.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
df04e547fb1ab2eefb72ecd630a68a553015fda6a9b9f59af69eb55d4fcce386
BLAKE2b-256 checksum
How to use checksums
fe86e15ca96632aaac395dc8f5baeac906cdab528ae3ce2ea0d4fe248695f642
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.14.5

Security for apple-ble 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page