Hacking Bluetooth Sports Tech for Fun and Profit — Part 3: The Strap That Ships an SDK

After a chest strap that leaked its ECG by accident and a band that hid everything behind one undocumented byte, here's the device that just tells you how it works, and what we built on top of it.


The first two parts of this series were about prising data out of devices that didn't want to give it up. The Wahoo TRACKR handed over its raw ECG to anyone who knew the magic byte — but only because nobody had thought to lock the door. The WHOOP (coming in Part 4) made me sniff its own app talking to it before it would say a single word.

The Polar H10 is the opposite kind of device. Polar publishes an SDK.

Not a marketing "SDK" that wraps a cloud API — an actual, public, official BLE SDK with the on-device protocol documented in the repository: the measurement service, the control commands, the frame formats, even the offline-recording feature flags. Polar Electro have been making heart-rate straps in Finland since the late 1970s, and somewhere along the way they decided that letting developers read the raw signal was a feature, not a threat. After two devices' worth of reverse-engineering, it's a strange feeling to open a strap's protocol and find the manufacturer has already written it down.

So this post is less a break-in and more a tour — of what the H10 exposes, the one place it still made me work, and the app that openness ultimately made possible.

PMD: the measurement service

The H10's interesting surface is the PMD service (Polar Measurement Data, UUID fb005c80-…). It has a control characteristic (fb005c81) you write commands to, and a data characteristic (fb005c82) the samples stream back on. No bonding, no auth, no handshake: connect, ask, it streams.

Reading the control point tells you what the strap supports. On the H10 that's two things: ECG and a 3-axis accelerometer. You start a stream by writing a START command with the type and the settings you want:

  • ECG — 130 Hz, delivered as signed 24-bit microvolt samples. Roughly 73 samples per notification at the H10's 232-byte MTU.
  • ACC — up to 200 Hz, 16-bit milli-g, selectable ±2 / ±4 / ±8 g. 36 (x, y, z) triples per notification.

And the part that makes it genuinely useful for research: both stream at once. A ten-second capture yields roughly 1,300 ECG samples and 2,000 accelerometer samples, time-stamped and interleavable — clean cardiac electrical signal alongside the motion that might be corrupting it. Our tooling reimplements this natively in Python (bleak) with no SDK dependency, because the published documentation is enough to talk to the strap directly:

disco polar <addr> -d 30

— connect, stream ECG + ACC for thirty seconds, decode, plot. The microvolt values come out signed and sane, with clean R-peaks at a resting rhythm, because Polar tells you they're int24 µV rather than leaving you to infer the packing the way the Wahoo did.

The one part that still made me work

Part 1 left a thread dangling: the H10 has a file system — a service that responded but looked "gated behind an initial handshake." I'd assumed unlocking it meant replaying whatever secret sequence the Polar Flow app performs at sync.

It didn't. The file-transfer protocol — Polar's PS-FTP (0000feee) — needed two unglamorous things, neither of them a secret:

  1. BLE bonding. The strap wants a bonded connection before it'll serve files. (macOS can't initiate BLE bonding programmatically; this is exactly the job the Raspberry Pi from Part 1, with its BlueZ pairing agent, exists to do.)
  2. A sequence-counter reset. The H10 silently drops PS-FTP frames whose sequence number isn't zero at the start of an operation. Our transmit counter was global and never reset between operations — so the first query worked and every subsequent one vanished with no error at all. Reset the counter to zero before each operation and the whole thing comes alive.

That second one cost longer than I'd care to divulge. There was no authentication wall, just a strap that's quietly strict about where you start counting. With it fixed, the full cached-recording cycle works: set the strap's clock, start a recording, disconnect, train, reconnect, stop, and pull the session file (a protobuf blob of packed heart-rate and beat-to-beat R-R arrays) back off the device. Offline recording needs the H10 on firmware 4.2.0 or later; older firmware rejects it.

Even the open vendor, it turns out, leaves one thing sharp. But that one sharp edge, versus a whole device behind a sniffer, is a good trade.

From the bench to the rack

Here is where the openness paid off. Because the H10's protocol is documented and stable — not a moving target a firmware update might silently break — it became the sensor behind a real application: Palæstra, an iOS app for strength training. (Palæstra — Latin, from the Greek palaistra: a wrestling school; by extension, any place of athletic training.)

The idea is simple. Heart-rate variability — the beat-to-beat variation in R-R intervals — tracks autonomic recovery. Between sets of a heavy lift, HRV climbs back as the parasympathetic system reasserts itself, and how fast it climbs is a readout of how recovered you are for the next set. Palæstra reads the strap's R-R intervals live and computes a rolling 30-second RMSSD, surfacing it as a recovery ring in the between-sets view — a ring that fills as your HRV slopes up and plateaus, telling you when you've actually recovered rather than when the clock says you have.

Note the deliberate choice: for the live recovery signal, Palæstra does not use the 130 Hz PMD ECG stream. It uses the strap's standard BLE Heart Rate Service R-R intervals — the same 0x2A37 characteristic any heart-rate consumer reads, which the H10 reports in the standard uint16, 1/1024-second format. RMSSD doesn't need the raw waveform; it needs accurate beat-to-beat timing, and the H10's R-R output is already clean. The full PMD ECG stream is there if a later phase wants it — at three times the packet rate — but the recovery algorithm doesn't.

Why a chest strap at all, when every other fitness device is on the wrist? Because the wrist is the worst possible place to measure HRV during strength work. Optical (PPG) sensors infer beats from blood-volume changes through the skin, and a hard set — grip, forearm tension, the whole arm braced and shaking — is the condition under which wrist PPG fails to generate viable signal. A chest strap measures the heart's electrical signal directly, through ECG-grade electrodes, and keeps clean R-R timing straight through the grind. The same site hierarchy runs through Part 5 of this series: for beat-to-beat accuracy, electrical exceeds optical, and chest exceeds wrist.

Each finished session exports to a FIT file — heart rate, HRV, sets, loads, and a session RPE — so it sideloads into intervals.icu with no double entry, and pushes to the project's backend (Promus) over a private Tailscale network. The natural next domain is climbing, where the problem is the same shape: grip-heavy, intermittent, isometric load that wrist optical can't follow and a chest strap can.

The lesson

Three devices in, the most useful one in the kit is the one I didn't have to break into. The Wahoo was a lucky accident; the WHOOP was a fight. The Polar H10 came fully documented, streamed clean ECG and R-R on request, and went straight from a research bench into an app I actually use between sets.

There's a quiet argument in here for the vendors: openness cost Polar nothing, and it's the reason their strap is the one doing work in the palæstra, rather than sitting in the drawer of devices I reverse-engineered once and never touched again.

Next in the series: back to the hard way. The WHOOP doesn't ship an SDK — and getting it to speak took a Raspberry Pi, two Nordic sniffers, and a carefully-timed Bluetooth toggle.


This is part 3 of "Hacking Bluetooth Sports Tech for Fun and Profit" — a series documenting BLE reverse-engineering of fitness wearables. Tools: VESTIGATOR (Python + bleak), Pi Lab (FastAPI + BlueZ D-Bus), Palæstra (iOS). All research conducted on personally-owned devices.