celesto
Secure and persistent computer for AI agents
Short authenticated Bluetooth sessions, a fixed list of CoAP routes, no cloud. How the Air's protocol works and what it can do.
Developer toolsSwift · HTMLGitHub @medparOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Pebble — native, offline iPhone companion for the Fitbit Air, built on its Bluetooth/DTLS/CoAP protocol
Loading the full README.
Read the full README ↗A native, offline iPhone companion for the Fitbit Air. Alarms, vibration alerts, live heart rate and evidence-based training and recovery trends, built on short authenticated Bluetooth sessions that share the band with Google Health. No firmware changes, no account, no cloud.
Website: medpar.github.io/pebble
Independent interoperability research. Not affiliated with, endorsed by or supported by Google or Fitbit. Fitbit and Fitbit Air are trademarks of Google LLC. Use it only with a band you own, at your own risk.
The Air talks to phones over Google's open-source Golden Gate stack: a tiny IP network carried over Bluetooth LE, secured with DTLS, speaking CoAP with protobuf payloads. Pebble embeds the same stack (plus Mbed TLS) in a small C bridge and talks to the band the way the official app does — only through a short, fixed list of routes.
iPhone (Pebble) Fitbit Air
─────────────────────────────────────────────────────────────────────────
Protobuf Alarms · Settings · LiveActivity · DeviceInfo · EventSet
CoAP GET /alarms · PUT|POST /settings · GET /liveactivity …
DTLS 1.2 PSK, TLS_PSK_WITH_AES_128_CCM (0xC0A4), extended master secret
band = DTLS client, phone = DTLS server
NIP IPv4 / UDP, port 5684 both ends
Gattlink reliable, windowed fragmentation (sequence numbers + ACKs)
BLE GATT service 0xFD62 · phone writes ABBAFF01 · band notifies ABBAFF02
Every Air has two 16-byte pre-shared keys (primary and secondary), each with a
32-bit key ID, held by Google's cloud. When a session starts the band, acting as
DTLS client, sends a PSK identity of the form MD-<key ID, 8 hex>-<expiry, 8 hex>.
Pebble looks up the matching raw key and completes the handshake as the server.
The suite was confirmed from a live Bluetooth trace, and offline decryption of
that trace verified both Finished messages and all 131 application records
before any custom code touched the band.
You need your own band's keys. They were recovered for this project with a
scoped, single-use mitmproxy addon (scripts/device_key_capture.py)
that watched the official app's own traffic and made a bounded, single-use
read of the paired Air's keys. It never saves credentials, cookies or unrelated bodies. The full
method — and its limits — is in the research log. Pebble
imports a JSON file shaped like
{"keys":{"primaryKey":{"key":"<base64>","keyId":123},"secondaryKey":{…}}},
moves it into the device-only Keychain and deletes the file.
The Air accepts one secure session at a time. If Pebble held the link, Google Health would stop syncing. So every job is a short session: connect, handshake, do one thing, disconnect. An activity read takes about 3 s, a full sync about 5 s, and every session has a 35 s hard timeout.
| Route | Method | What it carries |
|---|---|---|
/alarms | GET | Capabilities (up to 8 alarms, smart wake) and alarm entries |
/alarms | POST | Create one alarm Configuration |
/alarms/{id} | PUT / DELETE | Replace or delete one alarm |
/settings/capabilities | GET | Supported settings, haptic intensities (1 off · 2 low · 3 high), goal types |
/settings | PUT | Query the haptic setting — this protocol uses PUT for a read |
/settings | POST | Change the haptic intensity (the band buzzes as a preview) |
/liveactivity | GET | Steps (field 2), heart rate (field 7), confidence (field 8) |
/device/info | GET | Battery percentage (field 13) |
An alarm Configuration is: enabled, repeats, smart wake, recurring, a time
(hour, minute, second) and a weekday bitmask with Monday = bit 0. Unknown
protobuf fields are preserved on every edit.
What the band can tell the phone, and what it can be made to do.
Reads. Steps and heart rate snapshots (/liveactivity), battery
(/device/info), alarms and settings. Each reading is timestamped; an old
count is never shown as today's.
Live heart rate. With Share heart rate on in Google Health, the Air also
exposes the standard Bluetooth Heart Rate Profile (0x180D, measurement
0x2A37): beats per minute plus RR intervals in 1/1024 s. Pebble uses it for
heart-rate-reserve zones, RMSSD over the last 120 RR intervals and Edwards
training load. This is a read-only, unauthenticated path that needs no keys.
Events. The band is also a CoAP client: it POSTs an EventSet to /events
and /eventsSecure on the phone. Event IDs are four ASCII characters packed
big-endian — rset, bsnc, cgps, ping, aofl, wrs+ and wrs- (wrist
on/off). This Air has only ever sent rset. Pebble's Listen for band events
screen shows any code it receives, unknown ones included.
Vibration. There is no "buzz" command. The only verified way to make the Air vibrate is to change its haptic intensity (Low ↔ High), which plays a preview, then restore it. Each change is a Bluetooth round trip of about a second, so patterns differ by pulse count and spacing, not rhythm:
| Pattern | Pulses | Gap between pulses |
|---|---|---|
| Single | 1 | — |
| Double | 2 | 1.8 s |
| Triple | 3 | 1.8 s |
| Long gap | 2 | 3.4 s |
| Urgent | 5 | 0.9 s |
A pulse is: toggle → hold 0.3 s → restore. The original intensity is journaled first, and read back once at the end. Gaps under about 1.2 s feel identical on the wrist because they sit at the round-trip floor.
This is the beginning of the README. Read the rest at the original. Read the full README ↗