Skip to content
AF
Free Tool

FTMS lab: decode treadmill, bike and rower Bluetooth data

An FTMS machine data value is a Flags field followed only by the fields its bits announce, so the same characteristic arrives in different lengths and you cannot read it at fixed offsets. In Treadmill Data (0x2ACD), Instantaneous Speed is present when bit 0 (“More Data”) is 0, every other field when its own bit is 1, and the Bluetooth SIG’s structure gives each field’s size and resolution: speed in 1/100 km/h, Indoor Bike cadence in 1/2 rpm. This lab connects a treadmill, indoor bike, rower or cross trainer over Web Bluetooth and decodes every value against that structure, or decodes hex you paste, with no machine at all.

or replay a synthetic session:

Paste a value

Bytes as hex, in the order they arrive (spaces, commas or 0x prefixes are fine). The samples are synthetic, built from the SIG’s structure by the same module that decodes them.

Sample: Instantaneous Speed 1050 (×0.01 km/h), Total Distance 1234 m, Inclination 15 and Ramp Angle 9 (×0.1), Elapsed Time 600 s.

Treadmill Data (0x2ACD) · 13 octets · little-endian

0C041A04D204000F0009005802
FieldPresent whenOctetsRawValue
Flagsboolean[16]alwayspresent0–110360x040C
Instantaneous Speeduint16bit 0 of Flags is 0present2–3105010.5 kilometre per hour× 0.01 from Represented values: M = 1, d = -2, b = 0 beside Unit is 1/100 of a kilometer per hour
Average Speeduint16bit 1 of Flags is 1absent———
Total Distanceuint24bit 2 of Flags is 1present4–612341234 metre
Inclinationsint16bit 3 of Flags is 1present7–8151.5 percentage× 0.1 from Represented values: M = 1, d = -1, b = 0 beside Unit is 1/10 of a percent
Ramp Angle Settingsint16bit 3 of Flags is 1present9–1090.9 degree× 0.1 from Represented values: M = 1, d = -1, b = 0 beside Unit is 1/10 of a degree
Positive Elevation Gainuint16bit 4 of Flags is 1absent———
Negative Elevation Gainuint16bit 4 of Flags is 1absent———
Instantaneous Pace (Time per 500 meters)uint16bit 5 of Flags is 1absent———
Average Pace (Time per 500 meters)uint16bit 6 of Flags is 1absent———
Total Energyuint16bit 7 of Flags is 1absent———
Energy Per Houruint16bit 7 of Flags is 1absent———
Energy Per Minuteuint8bit 7 of Flags is 1absent———
Heart Rateuint8bit 8 of Flags is 1absent———
Metabolic Equivalentuint8bit 9 of Flags is 1absent———
Elapsed Timeuint16bit 10 of Flags is 1present11–12600600 second
Remaining Timeuint16bit 11 of Flags is 1absent———
Force On Beltsint16bit 12 of Flags is 1absent———
Power Outputsint16bit 12 of Flags is 1absent———
Flags bit by bit 0x040C
BitValueThe SIG’s description
00More Data
10Average Speed present
21Total Distance present
31Inclination and Ramp Angle Setting present
40Elevation Gain present
50Instantaneous Pace present
60Average Pace present
70Expended Energy present
80Heart Rate present
90Metabolic Equivalent present
101Elapsed Time present
110Remaining Time present
120Force On Belt and Power Output present
13–15000bReserved for Future Use

The structures it decodes against

Each table is the layout the lab compiled from the Bluetooth SIG’s GATT Specification Supplement files at commit 408f4a0 of the SIG’s public repository (read 2026-10-17), through this site’s generated copy. The machine data characteristics, then the Supported Range characteristics the lab reads once after connecting:

Treadmill Data (0x2ACD) · 19 fields
FieldTypeSizePresent whenUnit and resolution
Flagsboolean[16]2alwaysflags
Instantaneous Speeduint160 or 2bit 0 of Flags is 0kilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Average Speeduint160 or 2bit 1 of Flags is 1kilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Total Distanceuint240 or 3bit 2 of Flags is 1metre
Inclinationsint160 or 2bit 3 of Flags is 1percentage, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Ramp Angle Settingsint160 or 2bit 3 of Flags is 1degree, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Positive Elevation Gainuint160 or 2bit 4 of Flags is 1metre, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Negative Elevation Gainuint160 or 2bit 4 of Flags is 1metre, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Instantaneous Pace (Time per 500 meters)uint160 or 2bit 5 of Flags is 1second
Average Pace (Time per 500 meters)uint160 or 2bit 6 of Flags is 1second
Total Energyuint160 or 2bit 7 of Flags is 1kilogram calorie
Energy Per Houruint160 or 2bit 7 of Flags is 1kilogram calorie
Energy Per Minuteuint80 or 1bit 7 of Flags is 1kilogram calorie
Heart Rateuint80 or 1bit 8 of Flags is 1beats per minute
Metabolic Equivalentuint80 or 1bit 9 of Flags is 1metabolic equivalent
Elapsed Timeuint160 or 2bit 10 of Flags is 1second
Remaining Timeuint160 or 2bit 11 of Flags is 1second
Force On Beltsint160 or 2bit 12 of Flags is 1newton
Power Outputsint160 or 2bit 12 of Flags is 1watt

Treadmill Data on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.treadmill_data.yaml

Cross Trainer Data (0x2ACE) · 21 fields
FieldTypeSizePresent whenUnit and resolution
Flagsboolean[24]3alwaysflags
Instantaneous Speeduint160 or 2bit 0 of Flags is 0kilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Average Speeduint160 or 2bit 1 of Flags is 1kilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Total Distanceuint240 or 3bit 2 of Flags is 1metre
Steps Per Minuteuint160 or 2bit 3 of Flags is 1per minute
Average Step Rateuint160 or 2bit 3 of Flags is 1per minute
Stride Countuint160 or 2bit 4 of Flags is 1—, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Positive Elevation Gainuint160 or 2bit 5 of Flags is 1metre
Negative Elevation Gainuint160 or 2bit 5 of Flags is 1metre
Inclinationsint160 or 2bit 6 of Flags is 1percentage, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Ramp Settingsint160 or 2bit 6 of Flags is 1degree, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Resistance Leveluint80 or 1bit 7 of Flags is 1—, × 10 (Represented values: M = 1, d = 1, b = 0) — the same field also says Unit is 1
Instantaneous Powersint160 or 2bit 8 of Flags is 1watt
Average Powersint160 or 2bit 9 of Flags is 1watt
Total Energyuint160 or 2bit 10 of Flags is 1kilogram calorie
Energy Per Houruint160 or 2bit 10 of Flags is 1kilogram calorie
Energy Per Minuteuint80 or 1bit 10 of Flags is 1kilogram calorie
Heart Rateuint80 or 1bit 11 of Flags is 1beats per minute
Metabolic Equivalentuint80 or 1bit 12 of Flags is 1metabolic equivalent, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Elapsed Timeuint160 or 2bit 13 of Flags is 1second
Remaining Timeuint160 or 2bit 14 of Flags is 1second

Cross Trainer Data on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.cross_trainer_data.yaml

Step Climber Data (0x2ACF) · 13 fields
FieldTypeSizePresent whenUnit and resolution
Flagsboolean[16]2alwaysflags
Floorsuint160 or 2bit 0 of Flags is 0—
Step Countuint160 or 2bit 0 of Flags is 0—
Steps Per Minuteuint160 or 2bit 1 of Flags is 1per minute
Average Step Rateuint160 or 2bit 2 of Flags is 1per minute
Positive Elevation Gainuint160 or 2bit 3 of Flags is 1metre
Total Energyuint160 or 2bit 4 of Flags is 1kilogram calorie
Energy Per Houruint160 or 2bit 4 of Flags is 1kilogram calorie
Energy Per Minuteuint80 or 1bit 4 of Flags is 1kilogram calorie
Heart Rateuint80 or 1bit 5 of Flags is 1beats per minute
Metabolic Equivalentuint80 or 1bit 6 of Flags is 1metabolic equivalent, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Elapsed Timeuint160 or 2bit 7 of Flags is 1second
Remaining Timeuint160 or 2bit 8 of Flags is 1second

Step Climber Data on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.step_climber_data.yaml

Stair Climber Data (0x2AD0) · 13 fields
FieldTypeSizePresent whenUnit and resolution
Flagsboolean[16]2alwaysflags
Floorsuint160 or 2bit 0 of Flags is 0—
Steps Per Minuteuint160 or 2bit 1 of Flags is 1per minute
Average Step Rateuint160 or 2bit 2 of Flags is 1per minute
Positive Elevation Gainuint160 or 2bit 3 of Flags is 1metre
Stride Countuint160 or 2bit 4 of Flags is 1—
Total Energyuint160 or 2bit 5 of Flags is 1kilogram calorie
Energy Per Houruint160 or 2bit 5 of Flags is 1kilogram calorie
Energy Per Minuteuint80 or 1bit 5 of Flags is 1kilogram calorie
Heart Rateuint80 or 1bit 6 of Flags is 1beats per minute
Metabolic Equivalentuint80 or 1bit 7 of Flags is 1metabolic equivalent, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Elapsed Timeuint160 or 2bit 8 of Flags is 1second
Remaining Timeuint160 or 2bit 9 of Flags is 1second

Stair Climber Data on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.stair_climber_data.yaml

Rower Data (0x2AD1) · 17 fields
FieldTypeSizePresent whenUnit and resolution
Flagsboolean[16]2alwaysflags
Stroke Rateuint80 or 1bit 0 of Flags is 0per minute, × 0.5 (Represented values: M = 1, d = 0, b = -1)
Stroke Countuint160 or 2bit 0 of Flags is 0—
Average Stroke Rateuint80 or 1bit 1 of Flags is 1per minute, × 0.5 (Represented values: M = 1, d = 0, b = -1)
Total Distanceuint240 or 3bit 2 of Flags is 1metre
Instantaneous Pace (Time per 500 meters)uint160 or 2bit 3 of Flags is 1second
Average Pace (Time per 500 meters)uint160 or 2bit 4 of Flags is 1second
Instantaneous Powersint160 or 2bit 5 of Flags is 1watt
Average Powersint160 or 2bit 6 of Flags is 1watt
Resistance Leveluint80 or 1bit 7 of Flags is 1—, × 10 (Represented values: M = 1, d = 1, b = 0) — the same field also says Unit is 1
Total Energyuint160 or 2bit 8 of Flags is 1kilogram calorie
Energy Per Houruint160 or 2bit 8 of Flags is 1kilogram calorie
Energy Per Minuteuint80 or 1bit 8 of Flags is 1kilogram calorie
Heart Rateuint80 or 1bit 9 of Flags is 1beats per minute
Metabolic Equivalentuint80 or 1bit 10 of Flags is 1metabolic equivalent, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Elapsed Timeuint160 or 2bit 11 of Flags is 1second
Remaining Timeuint160 or 2bit 12 of Flags is 1second

Rower Data on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.rower_data.yaml

Indoor Bike Data (0x2AD2) · 16 fields
FieldTypeSizePresent whenUnit and resolution
Flagsboolean[16]2alwaysflags
Instantaneous Speeduint160 or 2bit 0 of Flags is 0kilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Average Speeduint160 or 2bit 1 of Flags is 1kilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Instantaneous Cadenceuint160 or 2bit 2 of Flags is 1revolution per minute, × 0.5 (Represented values: M = 1, d = 0, b = -1)
Average Cadenceuint160 or 2bit 3 of Flags is 1revolution per minute, × 0.5 (Represented values: M = 1, d = 0, b = -1)
Total Distanceuint240 or 3bit 4 of Flags is 1metre
Resistance Leveluint80 or 1bit 5 of Flags is 1—, × 10 (Represented values: M = 1, d = 1, b = 0) — the same field also says Unit is 1
Instantaneous Powersint160 or 2bit 6 of Flags is 1watt
Average Powersint160 or 2bit 7 of Flags is 1watt
Total Energyuint160 or 2bit 8 of Flags is 1kilogram calorie
Energy Per Houruint160 or 2bit 8 of Flags is 1kilogram calorie
Energy Per Minuteuint80 or 1bit 8 of Flags is 1kilogram calorie
Heart Rateuint80 or 1bit 9 of Flags is 1beats per minute
Metabolic Equivalentuint80 or 1bit 10 of Flags is 1metabolic equivalent, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Elapsed Timeuint160 or 2bit 11 of Flags is 1second
Remaining Timeuint160 or 2bit 12 of Flags is 1second

Indoor Bike Data on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.indoor_bike_data.yaml

Supported Speed Range (0x2AD4) · 3 fields
FieldTypeSizePresent whenUnit and resolution
Minimum Speeduint162alwayskilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Maximum Speeduint162alwayskilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)
Minimum Incrementuint162alwayskilometre per hour, × 0.01 (Represented values: M = 1, d = -2, b = 0)

Supported Speed Range on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.supported_speed_range.yaml

Supported Inclination Range (0x2AD5) · 3 fields
FieldTypeSizePresent whenUnit and resolution
Minimum Inclinationsint162alwayspercentage, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Maximum Inclinationsint162alwayspercentage, × 0.1 (Represented values: M = 1, d = -1, b = 0)
Minimum Incrementuint162alwayspercentage, × 0.1 (Represented values: M = 1, d = -1, b = 0)

Supported Inclination Range on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.supported_inclination_range.yaml

Supported Resistance Level Range (0x2AD6) · 3 fields
FieldTypeSizePresent whenUnit and resolution
Minimum Resistance Leveluint81always—, × 10 (Represented values: M = 1, d = 1, b = 0) — the same field also says Unit is 1
Maximum Resistance Leveluint81always—, × 10 (Represented values: M = 1, d = 1, b = 0) — the same field also says Unit is 1
Minimum Incrementuint81always—, × 10 (Represented values: M = 1, d = 1, b = 0) — the same field also says Unit is 1

Supported Resistance Level Range on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.supported_resistance_level_range.yaml

Supported Heart Rate Range (0x2AD7) · 3 fields
FieldTypeSizePresent whenUnit and resolution
Minimum Heart Rateuint81alwaysbeats per minute
Maximum Heart Rateuint81alwaysbeats per minute
Minimum Incrementuint81alwaysbeats per minute

Supported Heart Rate Range on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.supported_heart_rate_range.yaml

Supported Power Range (0x2AD8) · 3 fields
FieldTypeSizePresent whenUnit and resolution
Minimum Powersint162alwayswatt
Maximum Powersint162alwayswatt
Minimum Incrementuint162alwayswatt

Supported Power Range on the Bluetooth GATT reference · the SIG’s file gss/org.bluetooth.characteristic.supported_power_range.yaml

Fitness Machine Feature (0x2ACC) · no structure: shown raw

The SIG's public repository has no gss structure for this characteristic. The lab shows its bytes as hex.

Fitness Machine Feature on the Bluetooth GATT reference

Training Status (0x2AD3) · no structure: shown raw

The SIG's public repository has no gss structure for this characteristic. The lab shows its bytes as hex.

Training Status on the Bluetooth GATT reference

Fitness Machine Control Point (0x2AD9) · no structure: shown raw

The SIG's public repository has no gss structure for this characteristic. The lab shows its bytes as hex.

Fitness Machine Control Point on the Bluetooth GATT reference

Fitness Machine Status (0x2ADA) · no structure: shown raw

The SIG's public repository has no gss structure for this characteristic. The lab shows its bytes as hex.

Fitness Machine Status on the Bluetooth GATT reference

How to read an FTMS value

  1. Flags first. Treadmill, Indoor Bike and Rower Data open with a two-octet Flags field (boolean[16]); Cross Trainer Data’s is three octets (boolean[24]). Read it as a little-endian number.
  2. Walk the fields in order. For each field, its “Present if” line names one bit and the value that makes it present. Present: read its size and move on. Absent: it takes no octets. Several fields can share one bit (Total Energy, Energy Per Hour and Energy Per Minute all hang on one), and bit 0 is the odd one out: Present if bit 0 of Flags field is set to 0.
  3. Scale each value. The SIG gives a Represented values: M = …, d = …, b = … line where the raw number is not the unit, and the lab multiplies by M × 10d × 2b. Instantaneous Speed: Represented values: M = 1, d = -2, b = 0, beside Unit is 1/100 of a kilometer per hour. Instantaneous Cadence: Represented values: M = 1, d = 0, b = -1, beside Unit is 1/2 of a revolution per minute.
  4. Merge across notifications. A machine with more to say than fits in one value can set bit 0 (“More Data”), which drops Instantaneous Speed from that value. The lab’s “Latest values” panel keeps the newest reading of each field from whichever notification carried it; the treadmill replay sends such a split packet every fifth second.

Where the decoder reads past the SIG’s file

  • Byte order. The YAML gives sizes, not byte order. The lab reads little-endian, as the Web Bluetooth specification’s heart-rate example does (result.heartRate = data.getUint16(index, /*littleEndian=*/true);, read 2026-10-31).
  • The scale formula. The files do not spell out how M, d and b combine. The lab uses M × 10d × 2b, and its test suite checks that product against every “Unit is” sentence in the dataset it can parse (b = −10 beside “1/1024 second”, M = 5 and d = −3 beside “0.005 kilogram”, and so on): 60 lines, all agreeing except 8.
  • Resistance Level. 6 of those 8 are FTMS resistance fields, where the SIG’s own lines disagree: Represented values: M = 1, d = 1, b = 0 (a factor of 10) sits beside Unit is 1. The lab shows the value both ways and picks neither.
  • No structure, no decoding. At this commit the SIG’s repository has no gss file for Fitness Machine Feature (0x2ACC), Training Status (0x2AD3), Fitness Machine Control Point (0x2AD9), Fitness Machine Status (0x2ADA). The lab shows their bytes as hex and does not decode them from memory. That is also why it has no control panel: setting a target speed or power means writing op codes to the Control Point, and the layout of those op codes is not in the files this site reads. The lab never writes to the machine.

Which browsers can connect

Web Bluetooth is a Chromium feature. MDN's compatibility data lists it in Chrome (requestDevice since version 56), Edge, Opera and Samsung Internet, with the note that Chrome's Linux support is not enabled by default, and marks the API experimental. It lists no support in Firefox, in Safari on macOS or iOS, or in Android's WebView.

Source: MDN browser-compat-data, api/Bluetooth.json, read 2026-11-05. Elsewhere the paste box and replays still work.

What the lab cannot tell you

  • Accuracy. The bytes say what the machine reports, not whether its belt speed or power reading is right.
  • Which fields a model supports before it sends them. Fitness Machine Feature declares that, and it is one of the characteristics shown raw here.
  • Anything about a machine that does not advertise the Fitness Machine service: the browser’s chooser only lists devices that do. For any other device, use the BLE GATT explorer.

The integration side is in what FTMS is, treadmill app integration, indoor bike trainer integration and rowing machine data; the radio question in ANT+ vs Bluetooth. Power meters and cadence sensors have their own cycling power and cadence lab.

How this works, and what it sends

Nothing leaves your device. The browser shows its own device chooser, and the page can reach only the machine you pick and only its Fitness Machine service. It subscribes to the characteristics that notify (except the Control Point), reads the Supported Range and Feature characteristics once, and writes nothing. Values are decoded in this tab and held in memory until you close it; this site has no server code behind the page and stores nothing.

The samples and replays are generated, not recorded from a machine: their numbers are made up, and they are built by the same module that decodes them, from the field names in the SIG’s structure.

The structures come from the Bluetooth SIG’s public repository (https://bitbucket.org/bluetooth-SIG/public), read by this site’s generator on 2026-10-17. The SIG’s text is quoted with attribution: Copyright © Bluetooth SIG, Inc., which describes these files as proprietary to it. This site is not affiliated with Bluetooth SIG.

A free tool from AIFitnessAPI. More at free tools for health-app builders.