Reading Cycling Power, Cadence, and Speed Sensors in an App
Last verified August 14, 2026 · 5 min read
A rider can arrive at your app with a power meter, a cadence sensor, a speed sensor, a heart rate strap and a smart trainer, all connected at once. The good news is that the sensor side is one pattern repeated. The hard part is not the radio — it is deciding which of several devices is telling the truth about the same pedal stroke.
Three services, one pattern#
Per the Bluetooth SIG's public assigned-numbers registry:
| Service | Measurement characteristic |
|---|---|
| Cycling Power 0x1818 | Cycling Power Measurement 0x2A63 |
| Cycling Speed and Cadence 0x1816 | CSC Measurement 0x2A5B |
| Running Speed and Cadence 0x1814 | RSC Measurement 0x2A53 |
The flow is identical in all three cases: scan filtered on the service UUID rather than matching device names, connect, discover the service and its characteristics by UUID rather than caching handles, enable notifications on the measurement characteristic, and handle drops as an ordinary part of the lifecycle. Write the transport once and parameterize it by service.
Only the parsing differs, and the contents and layout of each measurement value are defined in the Bluetooth SIG's specification for that service. We are not paraphrasing them here, because a parser reconstructed from prose is a parser that works with the one sensor you tested on. Read the spec for each service you support, and diff any borrowed code against it.
The same reasoning applies to heart rate on service 0x180D, covered in Bluetooth heart rate monitors. Four services, one transport layer, four parsers.
Where these overlap with an FTMS trainer#
This is the part that produces bad data rather than crashes. A smart trainer may expose the standalone Cycling Power service and Indoor Bike Data 0x2AD2 under the Fitness Machine Service 0x1826 at the same time. A rider with a crank-based power meter fitted to a bike on that trainer adds a third claim about the same effort. None of these devices knows the others exist.
Pick one source of truth per metric per session. Decide it at connection time from what is advertised, persist the decision on the session, and do not switch sources mid-ride, because a mid-session switch shows up as a discontinuity that looks physiological and is not. Key the decision on the service, not on the peripheral, since a trainer offering two interfaces is one peripheral making two claims — a "different device, different source" heuristic will not catch it.
The general design is in deduplicating health data, and the trainer-side detail is in indoor bike trainer integration.
Pairing UX is the real product surface#
Everything above is a morning of work. The pairing experience is where cycling apps are actually won or lost, and none of what follows is in any specification — it is judgement from watching this go wrong.
Pair by role, not by device. Riders think in terms of "my power source", "my cadence source", "my heart rate". Ask which sensor fills which role and store that mapping, rather than presenting an undifferentiated list of everything in range and hoping the user recognizes their own hardware.
Remember devices, and remember them per bike. A rider with two bikes has two sets of sensors and expects the app to know which set belongs to which. Persist the platform's device identifier along with the role and a user-editable label, and reconnect known sensors automatically at the start of a ride.
Expect a crowded room. In a garage, a bike shop or a group session, a scan returns many candidate devices, several of them belonging to other people. Filter by service UUID so the list only contains devices that can fill the role being paired, show signal strength if the platform gives it to you, and make it easy to correct a mistake.
Make sensor loss visible without being alarming. A cadence sensor whose battery dies mid-ride should produce a clear "no signal" state, not a plausible zero. A zero is a claim that the rider stopped pedaling. Silence is the truth.
Do not let a missing secondary stream kill the session. If heart rate drops, the ride continues. Mark the range unknown, keep recording everything else, and reconcile later.
Radios, platforms and the browser#
For new development in 2026, BLE is the default radio for fitness sensors. The ANT+ program wind-down is reported rather than something we could verify at source; ANT+ receivers remain relevant mainly through Garmin-ecosystem hardware. ANT+ versus Bluetooth sets out what is reported and what follows for a sensor-heavy app.
On iOS, Apple documents Core Bluetooth as the way to "communicate with Bluetooth low energy and BR/EDR ('Classic') Devices", requires NSBluetoothAlwaysUsageDescription in Info.plist since iOS 13 (NSBluetoothPeripheralUsageDescription on iOS 12 and earlier), and warns that "your app will crash if its Info.plist doesn't include usage description keys for the types of data it needs to access". Apple also documents that you should not subclass Core Bluetooth's classes. See iOS BLE fitness devices.
In the browser, the caniuse dataset puts Web Bluetooth in Chrome 56+, Edge 79+, Opera 43+ and Samsung Internet 6.2+, with no support in Firefox at any version and none in Safari on desktop or iOS. Multi-sensor pairing in a web app is therefore a decision about which users you are willing not to serve. See Web Bluetooth for fitness.
Storage and downstream#
Once the streams are flowing, the interesting problems move up the stack: session assembly, day boundaries and reconciliation with whatever the rider's watch also recorded. See time zones and day boundaries, GPS activity APIs for the outdoor side, and data quality monitoring for catching rides that come out implausible before a user does.
Testing#
A physical device is the only reliable test target for BLE, and a full sensor set is expensive to keep on every desk. Put a seam in front of the transport, record real notification streams once, and let them drive parsing, role assignment, source selection, dropout handling and session assembly in CI. Reserve hardware for the multi-sensor scenarios that fixtures cannot reproduce: a crowded scan, a battery dying mid-ride, a trainer and a power meter arguing. Testing BLE fitness devices and mocking wearable data cover the mechanics.
Frequently asked questions
- What are the BLE UUIDs for cycling power, cycling speed and cadence, and running speed and cadence?
- Per the Bluetooth SIG's assigned-numbers registry, Cycling Power is service 0x1818 with Cycling Power Measurement 0x2A63, Cycling Speed and Cadence is service 0x1816 with CSC Measurement 0x2A5B, and Running Speed and Cadence is service 0x1814 with RSC Measurement 0x2A53. All three follow the same integration pattern: scan filtered on the service UUID, connect, discover by UUID rather than caching handles, and enable notifications on the measurement characteristic. Write the transport once and parameterize it by service. Only parsing differs, and each service's specification from the Bluetooth SIG defines the layout of its measurement value.
- My trainer reports power over both FTMS and the Cycling Power service — which one should I record?
- Pick one and record which one you picked. A smart trainer can expose the standalone Cycling Power service 0x1818 and Indoor Bike Data 0x2AD2 under the Fitness Machine Service 0x1826 at the same time, and a separate crank power meter adds a third claim about the same effort. Decide the source per metric at connection time from what is advertised, persist that decision on the session, and never switch mid-ride, because a switch produces a discontinuity that reads as a physiological event. Key the decision on the service rather than the peripheral, since one trainer making two claims defeats any per-device heuristic.
- How should a cycling app remember multiple BLE sensors between rides?
- Store the pairing by role rather than as a flat device list. Riders think in terms of a power source, a cadence source and a heart rate source, so persist the platform's device identifier together with the role it fills and a user-editable label, and reconnect known sensors automatically at the start of a ride. Scope that mapping per bike, because a rider with two bikes has two sensor sets and expects the app to know which is which. When pairing, filter the scan by service UUID so the list only offers devices that can fill the role being paired, which matters in a garage or a bike shop where many sensors are in range.
Keep reading
From the blog
Findings counted out of this site’s own datasets.
- Apple's Newest HealthKit Types Are a RoadmapApple ships the data type years before most apps ship the feature. The additions since iOS 16 are a public preview of what the platform expects to matter.
Next steps
Was this page useful?
Independent comparison, last reviewed August 14, 2026. Pricing, rate limits, and feature availability change often — confirm current details in each provider’s official documentation before you commit. Product and company names are trademarks of their respective owners; AIFitnessAPI is not affiliated with, endorsed by, or sponsored by any product listed here.
← All connected devices · by AIFitnessAPI