Skip to content

Reverse-engineering the Xiaomi Mi Band into a live footpod for Zwift

barban · 18 August 2026

This is the origin story of OmniBandBridge (OBB): the reverse-engineering that made the whole app possible. It documents the protocol, not the app’s source, and it publishes no secret keys.

Clip a modern Xiaomi Mi Band to your shoe, start a treadmill run in the Mi Fitness app, and the band turns into a genuinely good little footpod: it reports cadence, stride length, pace, speed — and even advanced running dynamics like ground-contact time (GCT) and vertical oscillation (VO). All the most useful metrics are there and they seem accurate enough.

The problem is what you can do with that data. Mi Fitness keeps it to itself:

  • it does not re-broadcast the live stream as a standard sensor, so you can’t use the band as a footpod in Zwift or on a treadmill app;
  • and the workout it saves is a summary — the per-second cadence/stride/ground-contact stream you paid for never leaves the app.

And it’s not as if the market is spoiling you for choice. Dedicated running footpods have quietly become an endangered species: Garmin retired its Running Dynamics Pod, Zwift discontinued its own RunPod (now practically unobtainable), and Under Armour stopped making its HOVR connected shoes (you can still find and buy them, but they’re end-of-life). What’s left on sale today is essentially Stryd — excellent, but pricey — and the more affordable COROS POD 2. For a metric set this rich, repurposing a ~€40 band you might already own starts to look very tempting.

I wanted both: my band as a live cadence/speed sensor for Zwift, and the full stream recorded and exportable. Neither existed. So I went down to the wire to see how the band actually talks.

The Mi Band has two personalities. On the wrist it reports heart rate, steps, calories — the usual. Clip it to the shoe (Xiaomi calls this the “Runner Clip”; internally the mode is BEAN) and it switches to a completely different data stream: no HR (it can’t see your wrist), but a rich per-second feed of running biomechanics derived from the accelerometer on your foot.

Everything interesting for a footpod lives in that second personality — and, crucially, the band only enters it when explicitly told to. Miss that step and you get wrist data dressed up as a workout.

On Android the Mi Band speaks over Bluetooth Classic (BR/EDR), RFCOMM/SPP — not BLE. Application data is wrapped in a simple framed protocol (a 0xA5A5 header, a type byte, a sequence number, a length and a CRC-16), and inside those frames rides a Protobuf message the Xiaomi firmware calls a WearPacket.

Reverse-engineering this meant capturing the Bluetooth HCI snoop log on an Android phone while Mi Fitness ran a real treadmill session, then decoding it frame by frame — cross-checked against the excellent open-source work in Gadgetbridge and AstroBox, and later confirmed against the decompiled Mi Fitness app itself.

The band won’t talk to anything that can’t prove it belongs to you. Access is gated by a 16-byte auth key.

A common misconception is that this key is a fixed factory value, or something you can sniff off the band. It’s neither. On this generation it’s a random secret minted during pairing and escrowed to your Xiaomi account in the cloud; tools recover it from your own Mi Fitness logs or cloud account. It changes every time you re-pair, and a band that’s already bound never hands it out again over the air.

With the key in hand, the connection opens with a four-step challenge–response handshake: phone and band each send a random nonce and an HMAC proving they hold the same key. Neither side ever transmits the key itself — the handshake only proves possession of it and derives a fresh, per-session encryption key (a small HKDF-style construction Xiaomi labels miwear-auth). From there every packet is encrypted with AES-128.

I’m deliberately not publishing any real keys or session material here; the mechanism above is the whole story, and the rest is your own secret.

Once the session is up, everything is a WearPacket: a type (ACCOUNT, SYSTEM, FITNESS, …) and an id (the command), with a Protobuf sub-message carried in a type-specific field. Workouts live under the FITNESS type, whose sub-protocol has its own little table of command IDs — start a workout, stream data, stop, and so on.

Two of those IDs turned out to be the whole ballgame: the command that flips the band into footpod mode, and the packet that carries the live stream.

This is the part nobody else had documented when I started my journey, and the reason a naïve implementation gets “wrist data during a footpod workout.” Before you start the workout you must send a SYSTEM command, id 15, that switches the band from wrist to pod mode:

WearPacket { type = SYSTEM, id = 15 }
system → { field_35 → { field_3 → { field_1 = 1 } } } // 1 = pod mode ON, 0 = wrist
# on the wire (plain, 13 bytes):
08 02 10 0f 22 07 9a 02 04 1a 02 08 01

The band echoes the new mode back and ACKs it. You can even query the current mode (a SYSTEM/id 14 request) and watch the flag flip from 0 to 1 — which is exactly how I confirmed it from the capture:

Time Direction Packet mode flag
17:53:04 ← band mode query response 0 (wrist)
17:53:04 → band SYSTEM/15 {…=1} (switch)
17:53:06 ← band mode query response 1 (pod)

There’s a short negotiation handshake around it (a couple of undocumented “footpod config” IDs the phone and band exchange first), then you send a normal start-workout request with the wear mode set to BEAN and the sport type (indoor run, treadmill, walk…). The band replies OK and the stream begins.

During an active workout the band emits a real-time packet — WearSportDataV2A — at roughly 1 Hz. This is the data OBB exists to liberate:

Field Meaning
frequency cadence (steps/min)
current_speed speed (km/h)
current_pace pace (s/km)
stride stride length
distance cumulative distance
on_ground / off_ground ground-contact and flight time (ms)
vertical_amplitude vertical oscillation
run_style foot-strike type (see below)
heart_rate bpm — but 0 in footpod mode

A few things I learned the hard way:

  • The first ~10 seconds are all zeros. The band ships an initial burst of empty packets while its step detector and accelerometer stabilise; real numbers arrive only after warm-up. If your parser assumes the first packet is real, your run starts with garbage.
  • There is no heart rate on the foot. In footpod mode HR is always 0 — the sensor is on your shoe, not your wrist. (Mi Fitness fakes it back into its saved file by feeding HR to the band from the phone; OBB instead sources HR from a real chest strap or watch.)
  • run_style is foot-strike, not gait. This field reports how your foot lands — midfoot, forefoot or heel — per sample, straight from the band.

Mi Fitness wraps all this in dozens of post-authentication sync messages — history, goals, weather, clock, device info. Almost none of it matters for receiving footpod data. The stripped-down sequence OBB uses, validated on real hardware, is just:

  1. Connect (Classic SPP) and run the auth handshake.
  2. Set the current time on the band.
  3. Wait ~5 seconds. (Empirically required — skip it and the band answers “busy.”)
  4. Footpod negotiation + SYSTEM/15 → pod mode ON.
  5. Start workout, wear mode = BEAN.
  6. Receive WearSportDataV2A at ~1 Hz.
  7. Stop.

That’s it. Everything else Mi Fitness does is optional.

Closing the loop: back to a standard sensor

Section titled “Closing the loop: back to a standard sensor”

Decoding the stream is only half the point. The other half is making it useful without asking Zwift to understand a word of Xiaomi’s protocol.

So OBB re-broadcasts the footpod metrics as a bog-standard Bluetooth Running Speed and Cadence (RSC) peripheral — the same SIG profile (0x1814) a Stryd or any commercial footpod uses. Cadence, speed and stride get mapped straight onto the RSC measurement packet, and to Zwift, Garmin Connect, a treadmill app or nRF Connect it simply looks like a normal footpod. (OBB can also expose the same data over DirCon/TCP, and add treadmill FTMS speed/incline and external HR — but the RSC bridge is the heart of it.)

The full arc: a €40 band → an encrypted Classic-Bluetooth Protobuf stream → a standard BLE sensor your apps already speak.

Although I developed and validated everything on a Mi Band 10, the application protocol is shared across the Mi Band 8/9/10 family — same auth, same framing, same WearPacket, same footpod concept. The only real per-model difference is the transport (some models default to BLE rather than Classic). Whether the footpod mode is available at all is a firmware capability the band reports at runtime, not a per-model gate — so the right approach is to try the activation and degrade gracefully if a given band or sub-model doesn’t support it, rather than hard-code a model whitelist.

This work stands on the shoulders of two open-source projects whose Xiaomi implementations were invaluable references: Gadgetbridge and AstroBox. The findings here were later cross-validated against the decompiled Mi Fitness app.

Everything above is interoperability reverse-engineering: reading a protocol so software you choose can use data from a device you own. It publishes no credentials, and it doesn’t circumvent any content protection — it just lets your own band’s numbers reach your own training apps.

If you want to actually use it, that’s what OmniBandBridge is: a free Android app that does all of the above — footpod mode, live RSC/FTMS/HR re-broadcast to Zwift and treadmills, personalized metabolic power, and full workout export. See What OBB does and Getting started.