A Python library for interacting with Garmin proprietary BLE protocols.
Project description
A clean-room Python implementation of Garmin's proprietary BLE protocol (GFDI V2). Stream live telemetry from your Garmin watch directly to your computer: no cloud, no phone, no Garmin Connect required.
Features
- Live Telemetry — stream real-time sensor data over BLE without Garmin Connect:
- ❤️ Heart Rate & Resting Heart Rate
- 🚶 Daily Steps & Goal
- 📊 Heart Rate Variability (HRV)
- 🫁 Blood Oxygen (SpO2)
- 🌬️ Respiration Rate
- 🔥 Calories (total & active)
- ⚡ Intensity Minutes
- 🧘 Stress Level
- 🔋 Body Battery
- ⌚ Accelerometer
- On-Demand Service Registration — telemetry services start only when you ask. No unwanted data streaming.
- Protocol Decoding — full implementation of the Garmin GFDI V2 stack:
- Automated handshake (
CLOSE_ALL,REGISTER_ML) - MLR (Multi-Link Routing) packet multiplexing
- COBS (Consistent Overhead Byte Stuffing) encoding/decoding
- Compiled Protobufs for
gdi_smart_proto - CRC16 integrity checking
- Automated handshake (
- Automatic Reconnection — survives BLE drops with exponential backoff
- Keep-Alive Heartbeat — periodic time-sync to maintain the link
- Hackable — pure Python, no binary blobs, no proprietary SDKs
Installation
pip install garmin-ble
Or install from source with dev dependencies:
git clone https://github.com/gwerneckp/garmin-ble.git
cd garmin-ble
pip install -e ".[dev]"
Quick Start
import asyncio
from garmin_ble import GarminClient, GarminService
def on_heart_rate(hr, resting_hr):
print(f"❤️ {hr} BPM (Resting: {resting_hr} BPM)")
async def main():
client = GarminClient()
client.on("hr", on_heart_rate)
if await client.connect():
# Register and start only the services you want
await client.register_and_start_service(GarminService.REALTIME_HR)
print("Connected! Streaming data...")
await client.start_sync_loop()
asyncio.run(main())
[!TIP] Make sure your watch is not connected to your phone via Bluetooth — Garmin watches only allow one BLE connection at a time.
[!IMPORTANT] Telemetry services are not auto-started. Use
register_and_start_service()for each service you need (e.g.,GarminService.REALTIME_STEPS,GarminService.REALTIME_ACCELEROMETER). Only the GFDI control channel is registered automatically.
See the examples/ directory for more complete usage patterns — including basic_telemetry.py (simple start), full_demo.py (advanced features), and tilt_volume.py (accelerometer-driven Mac volume control).
Status & Roadmap
See the GitHub Issues for the full breakdown of planned features, known gaps, and in-progress work. Milestones map to release versions:
| Phase | Goal | Status |
|---|---|---|
| 1 | 🏗️ BLE transport & handshake | ✅ Done |
| 2 | 📡 Live telemetry streaming | ✅ Done |
| 3 | 🧠 Protobuf settings & device state | 🔄 In progress |
| 4 | 🔔 Notifications & media control | ⏳ Planned |
| 5 | 📁 File transfers (FIT / GPX downloads) | ⏳ Planned |
| 6 | 🗄️ Persistence & dashboard | ⏳ Planned |
Design Philosophy
garmin-ble is a wire-protocol library, not a feature-complete application. It knows how to encode, send, decode, and respond to Garmin's BLE protobufs — and nothing more.
This means:
- The library does not fetch weather from OpenWeatherMap, sync calendars via CalDAV, or play sounds when the watch says "find my phone".
- Instead it provides the building blocks — protobuf encode/decode, callbacks for incoming messages, and transport helpers.
- Callers wire up the OS integration, external APIs, and user-facing features.
This keeps the library focused, testable, and free of the endless feature creep that plagues integration-heavy projects.
Consider using Gadgetbridge instead if: you want a full-featured open-source replacement for the Garmin Connect app on your Android phone.
Project Mission
Own your data. Garmin devices capture a wealth of physiological data, but Garmin Connect locks it behind a cloud service. This library gives you direct, programmatic access to your watch over BLE — no internet required.
Acknowledgements & License
This project builds on the extraordinary reverse-engineering work of the Gadgetbridge team. The protocol logic, COBS decoding, and .proto schemas are derived from their open-source Java implementation.
Licensed under the GNU Affero General Public License v3.0 (AGPL-3.0) — see LICENSE for details.
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file garmin_ble-0.2.3.tar.gz.
File metadata
- Download URL: garmin_ble-0.2.3.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e71d33e3c23faf616a3d4f588278b17f71340993facb67f6926d3916daff71a5
|
|
| MD5 |
c9e365c6fdae8547f5a4096ac5e4f59b
|
|
| BLAKE2b-256 |
fd8cfcb3548c7bff9e78c6e02b5a990194a54a90d21401fbc26cc69255033549
|
Provenance
The following attestation bundles were made for garmin_ble-0.2.3.tar.gz:
Publisher:
publish.yml on gwerneckp/garmin-ble
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
garmin_ble-0.2.3.tar.gz -
Subject digest:
e71d33e3c23faf616a3d4f588278b17f71340993facb67f6926d3916daff71a5 - Sigstore transparency entry: 1575590788
- Sigstore integration time:
-
Permalink:
gwerneckp/garmin-ble@3003b4f2b16ecd16be9b10d763da253463ca92cf -
Branch / Tag:
refs/heads/main - Owner: https://github.com/gwerneckp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3003b4f2b16ecd16be9b10d763da253463ca92cf -
Trigger Event:
push
-
Statement type:
File details
Details for the file garmin_ble-0.2.3-py3-none-any.whl.
File metadata
- Download URL: garmin_ble-0.2.3-py3-none-any.whl
- Upload date:
- Size: 43.6 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
36c811cf151bb2e13e6b5b3f957862a8a56942b098057a09a6fea337e64f44a6
|
|
| MD5 |
03a0514e2f6d0aefa2c11f066175c51e
|
|
| BLAKE2b-256 |
38f422dcf87a5f3a55860d3c10b465dd052f38e07e67d9fbab2ce55b6d8621db
|
Provenance
The following attestation bundles were made for garmin_ble-0.2.3-py3-none-any.whl:
Publisher:
publish.yml on gwerneckp/garmin-ble
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
garmin_ble-0.2.3-py3-none-any.whl -
Subject digest:
36c811cf151bb2e13e6b5b3f957862a8a56942b098057a09a6fea337e64f44a6 - Sigstore transparency entry: 1575590802
- Sigstore integration time:
-
Permalink:
gwerneckp/garmin-ble@3003b4f2b16ecd16be9b10d763da253463ca92cf -
Branch / Tag:
refs/heads/main - Owner: https://github.com/gwerneckp
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@3003b4f2b16ecd16be9b10d763da253463ca92cf -
Trigger Event:
push
-
Statement type: