https://github.com/hudsonbrendon/apple-ble
Pure-Python parser for Apple Continuity BLE proximity-pairing adverts — AirPods battery and nearby Apple device presence
https://github.com/hudsonbrendon/apple-ble
airpods apple ble bluetooth continuity home-assistant python
Last synced: 1 day ago
JSON representation
Pure-Python parser for Apple Continuity BLE proximity-pairing adverts — AirPods battery and nearby Apple device presence
- Host: GitHub
- URL: https://github.com/hudsonbrendon/apple-ble
- Owner: hudsonbrendon
- License: mit
- Created: 2026-06-01T14:39:36.000Z (24 days ago)
- Default Branch: main
- Last Pushed: 2026-06-01T21:04:09.000Z (24 days ago)
- Last Synced: 2026-06-01T23:08:00.397Z (24 days ago)
- Topics: airpods, apple, ble, bluetooth, continuity, home-assistant, python
- Language: Python
- Size: 33.2 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# apple-ble
[](https://github.com/hudsonbrendon/apple-ble/actions/workflows/ci.yml)
[](https://pypi.org/project/apple-ble/)
[](https://pypi.org/project/apple-ble/)
[](LICENSE)
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**](https://github.com/hudsonbrendon/ha-apple-ble),
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.
- 🎧 **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
```bash
pip install apple-ble # core (parsing only)
pip install "apple-ble[cli]" # + bleak scanner CLI
```
## CLI usage
```bash
apple-ble # 15s BLE scan; open your AirPods lid nearby and watch the battery
```
## Library usage
```python
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)
```
`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`. |
| `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](https://github.com/furiousMAC/continuity),
[kavishdevar/librepods](https://github.com/kavishdevar/librepods),
[delphiki/AirStatus](https://github.com/delphiki/AirStatus),
[d4rken-org/capod](https://github.com/d4rken-org/capod).
Author [@hudsonbrendon](https://github.com/hudsonbrendon).
## License
MIT — see [LICENSE](LICENSE).