New to this? Start with Build your own app for the ZBike Infinity, which explains how to use this guide with an AI assistant and lets you download it as a file ready to give to one.
How to read from and control the Zycle ZBike Infinity indoor bike over Bluetooth Low Energy: Fitness Machine, Cycling Power, Device Information and the Zycle service.
- Applies to: ZBike Infinity, firmware 1.39 and later
- Edition: 1.0 (web edition, October 2026), based on the Integration Guide V1.0 of 31 August 2026
- Published by: Zycle, for third-party integration
- Questions about this guide: https://help.zycle.eu/hc/requests/new
Key rules (read this first)
These are the rules an application must follow to work correctly with the machine. If you are using an AI assistant to write your application, give it this whole document and ask it to follow these rules.
- Scan for the name
ZInfinity, or for service0x1826or0x1818. Never filter a scan on0x180Aor on the Zycle service UUID: they are not advertised. - Request an ATT MTU of at least 185 octets right after connecting, before subscribing to anything. Without it, the Zycle Power Diagram and Command notifications never arrive.
- Do not pair or bond. Every characteristic in this guide is open.
- Subscribe to indications on the Fitness Machine Control Point (
0x2AD9) before writing to it, and write to it with Write Request (write with response). - Write Request Control (
00) first, then the target-setting procedure you need. Only four op codes work:0x00,0x04,0x05,0x11. - FTMS and Cycling Power values are little-endian. The Zycle service is big-endian, except the ERG target inside Zycle Information.
- The resistance scale is a plain integer from 0 to 200, everywhere. Do not apply the 0.1 resolution that the FTMS specification defines for resistance.
- To send a gradient, use Set Indoor Bike Simulation Parameters (
0x11) and always send all four parameters. Set Target Inclination is not supported. - Expect
80 xx 05(Control not permitted) when another app or an ANT+ device holds control. Keep reading data and retry later; do not disconnect and reconnect to force it. - Never read, write or subscribe to anything not described in this guide. The reserved characteristics and commands start calibration or drivetrain configuration procedures, and a machine left part-way through one can require Zycle support to recover.
Contents
1 About this guide
1.1 Purpose
This guide describes how an application, a game, a simulator or a head unit made by anyone other than Zycle can talk to the ZBike Infinity over Bluetooth Low Energy: how to find it, how to connect to it, how to read what the rider is doing, and how to control the resistance it applies.
It is not a restatement of the Bluetooth specifications. Where the machine follows a standard service (Fitness Machine, Cycling Power), this guide documents what that service actually contains on this machine, which optional fields are present, and what the values mean in practice, so that a developer can build a correct integration without inferring the machine's behaviour from the standards.
Everything in this guide applies to the ZBike Infinity only. Earlier Zycle and Bkool trainers expose different services and are outside its scope. The machine identifies itself over Bluetooth as ZInfinity.
1.2 Scope
This guide covers the Fitness Machine Service, the Cycling Power Service, the Device Information Service, and the part of the Zycle proprietary service that is published for third-party use.
It does not cover:
- other vendors' proprietary services that are also present on the machine;
- the first-party parts of the Zycle service (factory procedures), which are reserved to Zycle applications;
- communication with the machine over ANT+.
A client will encounter some of the reserved items on the machine. Section 2.1 says how to recognise them; the rule is simply to leave them alone. They are not published because they are not safe to explore: several of them start hardware calibration or drivetrain configuration. Read and write only what this guide describes.
1.3 Firmware versions
This guide describes firmware 1.39 of the ZBike Infinity. Later versions will also be compliant; if for any reason they are not, a new version of this guide will be released.
The running firmware version can be read in two ways: as a text string from the Device Information Service (section 6), and as a pair of octets from the Zycle service (section 7.4).
1.4 Conventions
- Byte order is not the same in every service. The three Bluetooth SIG services are little-endian, as their specifications require. The Zycle proprietary service is big-endian.
- Octet offsets are zero-based and count from the first octet of the characteristic value.
- Hexadecimal is written
0x2AD2for scalars and as space-separated octets,74 08 A8 0C, for frames. - 16-bit UUIDs are shown in their short form. The full 128-bit form is the usual Bluetooth Base UUID expansion,
0000xxxx-0000-1000-8000-00805F9B34FB. - Shall and must mark a requirement a client has to meet for the integration to work; should marks strong advice; may marks something optional.
1.5 Support
Zycle publishes this document so that third-party products can communicate appropriately with the machine. For questions about this guide, and requests for behaviour it does not describe, submit a request at https://help.zycle.eu/hc/requests/new.
Zycle does not provide support for third-party applications, nor for code written with AI tools from this guide.
2 The machine at a glance
2.1 Services exposed
The ZBike Infinity presents several GATT services. Three of them are standard Bluetooth SIG services that any client may use, one is the Zycle proprietary service, and the rest are other vendors' proprietary services which a third-party client should discover and ignore.
| Service | UUID | What a third-party client can do with it |
|---|---|---|
| Fitness Machine (FTMS) | 0x1826 |
Read live data and control the resistance. The primary interface. |
| Cycling Power (CPS) | 0x1818 |
Read power and crank/flywheel revolutions, as a power meter. |
| Device Information (DIS) | 0x180A |
Read manufacturer, model, firmware and serial number. |
| Zycle service | db6dba4b-a18d-44ae-90f1-38d01dec8f51 |
Read mode, engaged virtual gear, odometer, pedal-stroke diagram and firmware version. Only the characteristics listed in section 7. |
| Other vendor-specific services | various | Ignore. Not covered by this guide. |
2.2 Which service to use for what
| What you want | Read it from | Why |
|---|---|---|
| Instantaneous power | FTMS Indoor Bike Data, CPS Measurement or Zycle Information | All carry the same value. Use whichever service you already subscribe to. |
| Cadence | FTMS Indoor Bike Data | A direct field in 0.5 rpm units. CPS can also give it, but only by differentiating crank revolutions between notifications (section 5.4). |
| Speed | FTMS Indoor Bike Data | Takes into account the virtual gear ratio in simulation mode, and a fixed default gear ratio in all other modes. Better than computing it from flywheel revolutions. |
| Distance | Zycle Information | Gives the lifetime odometer, so the client decides when a session starts and subtracts the initial value. Indoor Bike Data also has a partial odometer, but the machine decides when it starts. |
| Elapsed time | FTMS Indoor Bike Data | Machine-side session timer. Counts only time spent pedalling. |
| Applied resistance | FTMS Indoor Bike Data | On the same 0–200 scale the control point accepts. |
| Controlling resistance, target power or gradient | FTMS Control Point | The only control surface available to third parties. |
| Engaged virtual gear and current mode | Zycle Information | The only way to obtain this. |
| Pedal-stroke power diagram | Zycle Power Diagram | The only way to obtain this. |
| Firmware version, model, serial number | Device Information | If only the firmware version is needed, the Zycle Command characteristic also gives it. |
2.3 A minimum viable integration
An application that wants to show power and cadence and control the machine needs nothing beyond the Fitness Machine Service:
- Scan for a peripheral advertising the complete local name
ZInfinity, or advertising the Fitness Machine service UUID0x1826. - Connect and request an ATT MTU of 185 octets.
- Discover services and characteristics.
- Subscribe to indications on the Fitness Machine Control Point before writing to it, and to notifications on Indoor Bike Data.
- Write Request Control, then whichever target-setting procedure your application needs.
- Read Indoor Bike Data notifications, which arrive once per second.
Sections 3 and 4 give the detail. Everything else in this guide is either a different way to get the same data, or data that the standard services cannot express.
3 Connecting
3.1 Advertising and discovery
The machine advertises continuously, as a connectable and scannable undirected advertiser on the 1M PHY, at an interval of 250 ms and a transmit power of +8 dBm. Advertising does not time out. It stops only when the machine is holding its maximum number of connections (section 3.5), and resumes as soon as a connection is dropped.
| Where | Data type | Contents |
|---|---|---|
| Advertising payload | Flags | General discoverable, BR/EDR not supported. |
| Advertising payload | Complete Local Name | ZInfinity |
| Advertising payload | Complete list of 16-bit service UUIDs |
0x1826 Fitness Machine, 0x1818 Cycling Power, and one vendor-specific UUID not covered by this guide. |
| Advertising payload | Service data | Two octets belonging to that same vendor-specific service. Ignore them. |
| Scan response | Appearance | 1156 — Cycling: Cycling Power Sensor. |
Filter on the device name or on 0x1826 / 0x1818. Do not filter a scan on the Device Information UUID or on the Zycle service UUID: neither appears in the advertising data, so a scan filtered on them will never find the machine. The appearance value is in the scan response, so a client that filters on appearance must perform an active scan.
The name is fixed in firmware and is the same on every unit, so it identifies the model but not the individual machine. Where a rider owns more than one, distinguish them by the identifier your platform gives the device, or read the serial number from the Device Information Service after connecting (section 6). Note that iOS and Web Bluetooth do not expose the Bluetooth address (section 9).
3.2 Connection establishment and parameters
The machine is a peripheral and never initiates a connection. Its preferred connection parameters are:
| Parameter | Value |
|---|---|
| Connection interval | 80 ms minimum, 100 ms maximum |
| Peripheral latency | 0 |
| Supervision timeout | 4000 ms |
A client may connect with different parameters. If it does, the machine asks for its own: five seconds after connecting it sends a connection-parameter update request, and repeats it every thirty seconds while the request goes unaccepted, up to three times.
The machine produces one notification per second on each subscribed characteristic (section 3.6), so a connection interval much longer than 100 ms combined with a supervision timeout shorter than four seconds will start dropping them.
3.3 ATT MTU
ATT carries every notification in a single packet whose payload is the negotiated MTU minus three octets of ATT header. The default MTU is 23 octets, which leaves 20 octets of payload. A notification longer than that is not fragmented and not truncated: the machine simply cannot send it, and the client sees nothing at all on that characteristic while everything else keeps working. The symptom is silence on the long characteristics only, which looks like a subscription failure rather than a size problem.
The MTU is negotiated once per connection, by an ATT Exchange MTU Request that the client sends; the peripheral cannot start it. The result is the smaller of the two sides' maxima. This machine supports 185 octets, giving 182 octets of payload, which is enough for every frame in this guide.
| Frame | Octets | Fits in the default 23-octet MTU? |
|---|---|---|
| FTMS Indoor Bike Data | 15 | Yes |
| CPS Measurement | 14 | Yes |
| FTMS Control Point indication | 3 | Yes |
| FTMS Status notification | 1–7 | Yes |
| Zycle Information | 15 | Yes |
| Zycle Command notification | 28 | No. Needs an MTU of at least 31 octets. |
| Zycle Power Diagram notification | 100 | No. Needs an MTU of at least 103 octets. |
Request the largest MTU your stack allows, and do it before writing to any Client Characteristic Configuration descriptor. Requesting 185 or more is enough; asking for more than the machine supports is harmless. A client that only uses the standard services works at the default MTU, but cannot receive the two long Zycle frames. On some platforms the operating system negotiates the MTU itself (section 9).
3.4 Security, pairing and bonding
Every characteristic described in this guide is open: no authentication, no authorisation, no encryption requirement. The machine actively declines pairing (a pairing request is answered with Pairing Not Supported) and it stores no bonds. A client shall not initiate pairing or bonding.
Because there is no bonding, nothing ties a machine to a particular client, and any client in radio range can connect and take control. Section 3.7 describes how the machine arbitrates between clients.
3.5 Concurrent connections
The machine accepts two simultaneous connections and advertises whenever it holds fewer than two. This is deliberate: a rider may run a game on one connection and the Zycle app on the other, or a training application and a recording head unit.
- Both connections see the same machine state, and both receive notifications independently.
- Neither connection is told that the other exists. A client subscribed to Fitness Machine Status is told when the other changes a target (section 4.6); nothing else the other connection does is announced.
- Only one of them can control the machine at a time. Which one is decided by the rule in section 3.7, and a client that loses is told so in the control point's response.
- A client shall therefore treat every value it reads as the machine's state rather than as an echo of what it last commanded, and shall not assume a target it set is still in force.
3.6 Notification timing
Almost every notification on the machine happens once per second. Each subscribed characteristic is sent once per second, whether or not its contents have changed, on each connection that subscribed to it. There is no way to ask for a faster or slower rate, and none of these characteristics notifies on change.
There are three exceptions:
- The Fitness Machine Control Point is an indication sent in answer to a write, once per write and never repeated.
- The Fitness Machine Status characteristic is a notification sent at the moment the machine's state changes, once and never repeated (section 4.6).
- The Zycle Power Diagram is driven by crank rotation, not by the timer: it arrives roughly once per pedal revolution while the rider is pedalling, and not at all when they stop.
3.7 Control arbitration between clients
The machine can be commanded from more than one place: either of its two Bluetooth connections, and ANT+. Allowing two of them to adjust the resistance at the same time would be troublesome, so the machine arbitrates. The rule is first come, first served, held until release:
- When no one holds control, the first command received takes it.
- While a source holds control, its own commands are accepted and everyone else's are refused with
0x05— Control not permitted. - A Bluetooth client holds control until it disconnects. It does not time out, because a simulator may legitimately go quiet for a long time.
- Control is per source, not per channel. An application that commands the machine over more than one protocol on the same connection counts as one source and does not compete with itself.
A client that is refused should keep reading data, which is never gated by arbitration, and may keep retrying: as soon as the other client disconnects, the next command succeeds.
A client shall not try to force control by disconnecting and reconnecting. It does not help: reconnecting does not displace a controller that is still connected.
4 Fitness Machine Service
The Fitness Machine Service, UUID 0x1826, is the primary interface to the machine. It carries almost every quantity a training application needs and it is the only way to control the resistance. A client that implements nothing else can still be a complete training application.
4.1 Characteristics
| UUID | Characteristic | Properties | Octets | Section |
|---|---|---|---|---|
0x2ACC |
Fitness Machine Feature | Read | 8 | 4.2 |
0x2AD2 |
Indoor Bike Data | Notify | 15 | 4.3 |
0x2AD6 |
Supported Resistance Level Range | Read | 6 | 4.4 |
0x2AD8 |
Supported Power Range | Read | 6 | 4.4 |
0x2AD9 |
Fitness Machine Control Point | Write, Indicate | 1–7 in, 3 out | 4.5 |
0x2ADA |
Fitness Machine Status | Notify | 1–7 | 4.6 |
Every value in this service is little-endian.
4.2 Fitness Machine Feature
Two 32-bit fields, read once. The values are fixed in firmware and do not change at run time. A client may hard-code its behaviour instead of reading them, but reading them is the better habit, because it keeps the client correct across firmware versions.
Fitness Machine Features 86 50 00 00 = 0x00005086 Target Setting Features 0C 20 00 00 = 0x0000200C
| Field | Bit | Meaning |
|---|---|---|
| Fitness Machine Features | 1 | Cadence Supported |
| Fitness Machine Features | 2 | Total Distance Supported |
| Fitness Machine Features | 7 | Resistance Level Supported |
| Fitness Machine Features | 12 | Elapsed Time Supported |
| Fitness Machine Features | 14 | Power Measurement Supported |
| Target Setting Features | 2 | Resistance Target Setting Supported |
| Target Setting Features | 3 | Power Target Setting Supported |
| Target Setting Features | 13 | Indoor Bike Simulation Parameters Supported |
Every other bit is zero, and every zero bit means what it says. In particular the machine does not declare Average Speed, Inclination, Elevation Gain, Expended Energy, Heart Rate, Remaining Time, Spin Down Control or Wheel Circumference Configuration, and it implements none of them. A client shall not attempt a procedure whose feature bit is clear.
4.3 Indoor Bike Data
This is the characteristic a client spends its life reading. It notifies once per second, always fifteen octets, always with the same flags and therefore always with the same fields in the same places.
4.3.1 Frame layout
| Offset | Size | Field | Type | Unit | Notes |
|---|---|---|---|---|---|
| 0 | 2 | Flags | uint16 | — | Always 0x0874. |
| 2 | 2 | Instantaneous Speed | uint16 | 0.01 km/h | Simulated road speed. See 4.3.3. |
| 4 | 2 | Instantaneous Cadence | uint16 | 0.5 rpm | Divide by two for rpm. Zero when the rider stops pedalling. |
| 6 | 3 | Total Distance | uint24 | metre | Partial odometer, not a lifetime total. See 4.3.3. |
| 9 | 2 | Resistance Level | sint16 | — | Applied resistance, 0–200. Same scale as the control point. |
| 11 | 2 | Instantaneous Power | sint16 | watt | Measured instantaneous power. |
| 13 | 2 | Elapsed Time | uint16 | second | Machine-side workout timer. See 4.3.3. |
4.3.2 Worked example
A rider turning 90 rpm at 250 W, half an hour into a session, having covered 16.533 km since the machine was powered on:
74 08 A8 0C B4 00 95 40 00 54 00 FA 00 2D 07
| Octets | Field | Raw | Decoded |
|---|---|---|---|
74 08 |
Flags | 0x0874 | speed, cadence, distance, resistance, power and elapsed time present |
A8 0C |
Instantaneous Speed | 3240 | 32.40 km/h |
B4 00 |
Instantaneous Cadence | 180 | 90 rpm |
95 40 00 |
Total Distance | 16533 | 16.533 km since power-on |
54 00 |
Resistance Level | 84 | 84 of 200 |
FA 00 |
Instantaneous Power | 250 | 250 W |
2D 07 |
Elapsed Time | 1837 | 30 min 37 s |
Note the byte order in every multi-octet field: A8 0C is 0x0CA8 = 3240, not 0xA80C.
4.3.3 What the fields mean on this machine
- Total Distance is a partial odometer. It starts from zero on every reboot and counts with every pedal stroke. It is not the lifetime odometer of the bike, which is in Zycle Information (section 7.2).
- Instantaneous Speed is a simulated speed. There is no real wheel on this machine. The value is the road speed the machine calculates from the current cadence, the engaged virtual gear and the configured wheel diameter. Outside simulation mode, where virtual gears do not apply, a standard fixed gear ratio is assumed.
- Resistance Level is what is applied. It is the physical position of the brake, scaled to 0–200. In power and simulation modes it moves continuously as the machine works to hold the target.
- Elapsed Time is the time spent actually pedalling. It starts from zero on every reboot, begins counting the first time pedalling is detected, and only counts time spent pedalling.
4.4 Supported ranges
Two read-only characteristics report the limits of the target-setting procedures. Each is three little-endian 16-bit values: minimum, maximum and minimum increment.
| Characteristic | Value | Minimum | Maximum | Increment |
|---|---|---|---|---|
| Supported Resistance Level Range | 00 00 C8 00 01 00 |
0 | 200 | 1 |
| Supported Power Range | 00 00 A0 0F 01 00 |
0 W | 4000 W | 1 W |
The resistance range, the Resistance Level field of Indoor Bike Data and the Set Target Resistance Level operand all use the same plain integer 0–200 scale, so a client can write a value, read it back from the notification and compare like with like.
Note for generic FTMS libraries: the FTMS specification defines a resolution of 0.1 for the resistance range and for the Set Target Resistance Level operand. On this machine the raw values are used as plain integers: the maximum is 200, and writing 150 sets 150 of 200. Do not multiply or divide by ten.
The power range is a declaration of the control range, not a promise about the rider. A target of 4000 W will be accepted; what the rider actually produces is another matter. At the other end, a target of 0 W is accepted and releases the brake as far as it goes.
4.5 Fitness Machine Control Point
All control goes through this one characteristic. A client writes a one-octet op code followed by that op code's operands, and the machine answers with a three-octet indication.
4.5.1 Using the control point
- Subscribe to indications on the control point before writing to it. The machine answers every write with an indication. A client that has not enabled the characteristic's Client Characteristic Configuration descriptor gets no answer, and cannot tell a command that worked from one that was refused.
- Use Write Request, not Write Without Response. Write Without Response is not among the control point's properties.
A write is one op code octet followed by that procedure's operands. Operands are little-endian. The machine reads exactly as many operand octets as the op code defines and ignores anything beyond them. Write the exact length the op code calls for: a shorter write is not padded with zeros, and what the machine reads in place of the missing octets is undefined.
The response is always three octets: 80 (Response Code), the op code of the request, and the result code (section 4.5.6).
4.5.2 Request Control — op code 0x00
The specification requires a client to take control before issuing any target-setting procedure.
write : 00 indication : 80 00 01 success
4.5.3 Set Target Resistance Level — op code 0x04
One operand octet, an integer on the 0–200 scale of section 4.4. Writing it puts the machine into basic resistance mode, in which the brake holds a fixed position and the power the rider produces is whatever that position and their cadence produce. Values above 200 are clamped to 200.
write : 04 96 set resistance to 150 of 200 indication : 80 04 01 success
This is the right mode for a manual resistance control, and the wrong mode for anything that requires a specific power or specific simulation conditions such as a gradient.
4.5.4 Set Target Power — op code 0x05
Two operand octets in watts, a little-endian signed 16-bit value. Negative values are clamped to zero. Writing it puts the machine into power mode (ERG mode), in which the machine continuously adjusts the brake to hold the requested power regardless of cadence.
write : 05 2C 01 target power 300 W (0x012C little-endian) indication : 80 05 01 success
A client may re-send the target it already set. There is no separate "ERG off" procedure: to leave power mode, write one of the other two target-setting procedures; each switches the machine into its own mode.
4.5.5 Set Indoor Bike Simulation Parameters — op code 0x11
Six operand octets describing the virtual road. This is the procedure a game or a route simulator uses: the machine computes the resistance from the gradient, the rider's power, the engaged virtual gear and the coefficients below.
| Offset | Size | Operand | Type | Unit | Resolution |
|---|---|---|---|---|---|
| 1 | 2 | Wind Speed | sint16 | metre per second | 0.001 |
| 3 | 2 | Grade | sint16 | per cent | 0.01 |
| 5 | 1 | Coefficient of Rolling Resistance (Crr) | uint8 | unitless | 0.0001 |
| 6 | 1 | Wind Resistance Coefficient (Cw) | uint8 | kg per metre | 0.01 |
write : 11 00 00 2C 01 33 33
op wind grade Crr Cw
wind speed : 0.000 m/s
grade : +3.00 %
Crr : 0.0051
Cw : 0.51 kg/m
indication : 80 11 01 success
Grade is signed: a descent is a negative value in two's complement, so −3.00 % is −300, which is 0xFED4, written D4 FE.
All four operands are applied together, so a client that wants to change only the gradient must resend the coefficients with it. The machine keeps the last values it was given; it does not merge a partial update.
There is no separate "simulation off" procedure: to leave simulation mode, write one of the other two target-setting procedures.
4.5.6 Responses and result codes
| Result code | Name | When the machine sends it |
|---|---|---|
0x01 |
Success | The procedure was accepted and applied. |
0x02 |
Op Code not supported | The machine does not implement this procedure. The four op codes in sections 4.5.2 to 4.5.5 are the only ones it implements. |
0x05 |
Control not permitted | Another client or an ANT+ peer currently holds control of the machine. The write was discarded. See section 3.7. |
A write that changed something is followed by a Fitness Machine Status notification (section 4.6):
write : 05 2C 01 set target power indication : 80 05 01 applied notification : 08 2C 01 target power changed write : 07 Start or Resume (not implemented) indication : 80 07 02 op code not supported write : 05 2C 01 set target power indication : 80 05 05 refused: another client is in control
4.5.7 Procedures the machine does not implement
Every other op code defined by the specification is answered 0x02 — Op Code not supported — and has no effect. That includes Reset, Set Target Speed, Set Target Inclination, Set Target Heart Rate, Start or Resume, Stop or Pause, the targeted energy, step and stride procedures, Set Wheel Circumference and Spin Down Control.
Two consequences are worth spelling out. First, there is no way to start, stop, pause or reset the machine over Bluetooth. Second, even though it may sound appropriate, Set Target Inclination is not the way to send a gradient: Set Indoor Bike Simulation Parameters is (section 4.5.5).
4.6 Fitness Machine Status
The Fitness Machine Status characteristic, UUID 0x2ADA, is how the machine announces a change to its own state, as opposed to answering a request. It is notify-only: subscribe to it, and expect nothing from a read.
- The notification reaches every subscribed connection, including the one whose write caused the change. It is the only way a client learns that the other connection (section 3.5) has moved the target.
- It is event-driven, not periodic: it is sent once, at the moment the change takes effect.
- The frame is one op code octet followed by that op code's parameter, little-endian, with no flags and no length prefix: one to seven octets in total.
The machine sends three of the op codes the specification defines:
| Op code | Name | Parameter |
|---|---|---|
0x07 |
Target Resistance Level Changed | One octet: the resistance level now in force, 0–200. |
0x08 |
Target Power Changed | sint16 little-endian, watts. The target the machine applied, which is not always the target written: a negative value is clamped to zero. |
0x12 |
Indoor Bike Simulation Parameters Changed | The same six operand octets as the control point procedure that set them, in the same order and units. |
The indication always comes first and the status notification second. A client may treat the indication as the answer to its own write, and the notification as a statement about the machine:
write : 05 2C 01 set target power 300 W indication : 80 05 01 accepted notification : 08 2C 01 target power changed write : 04 96 set resistance level 150 indication : 80 04 01 accepted notification : 07 96 target resistance level changed write : 11 00 00 2C 01 33 33 indication : 80 11 01 accepted notification : 12 00 00 2C 01 33 33
A procedure that was refused (0x05) or is not implemented (0x02) changed nothing, and produces no status notification.
5 Cycling Power Service
The Cycling Power Service, UUID 0x1818, presents the machine as a power meter. It is useful when the client is a head unit or recorder that speaks the power-meter profile and not FTMS, and when the client wants crank revolution counts rather than a cadence figure. For everything else, the Fitness Machine Service is the better source, and the two agree.
5.1 Characteristics
| UUID | Characteristic | Properties | Octets | Value |
|---|---|---|---|---|
0x2A63 |
Cycling Power Measurement | Notify | 14 | See 5.3 |
0x2A65 |
Cycling Power Feature | Read | 4 | 0C 00 00 00 |
0x2A5D |
Sensor Location | Read | 1 |
0x00 — Other |
The Cycling Power Control Point is not present. The service is read and notify only.
5.2 Cycling Power Feature
The feature value is 0x0000000C:
| Bit | Feature | What it means here |
|---|---|---|
| 2 | Wheel Revolution Data Supported | The measurement carries wheel revolution fields. Read section 5.5 before using them. |
| 3 | Crank Revolution Data Supported | The measurement carries crank revolution fields, from which cadence can be derived (section 5.4). |
Pedal Power Balance, Accumulated Torque, the extreme magnitude and angle fields, Accumulated Energy and offset compensation are not declared and not present. The pedal-stroke information is not here; the machine exposes an enhanced version of it through the Zycle service instead (section 7.3).
5.3 Cycling Power Measurement
Fourteen octets, notified once per second, with a fixed flags value and therefore a fixed layout.
| Offset | Size | Field | Type | Unit | Notes |
|---|---|---|---|---|---|
| 0 | 2 | Flags | uint16 | — | Always 0x0030: wheel and crank revolution data present. |
| 2 | 2 | Instantaneous Power | sint16 | watt | The same value the Fitness Machine Service reports. |
| 4 | 4 | Cumulative Wheel Revolutions | uint32 | revolution | Flywheel revolutions, not road-wheel revolutions. See 5.5. |
| 8 | 2 | Last Wheel Event Time | uint16 | 1/2048 s | Wraps every 32 s. |
| 10 | 2 | Cumulative Crank Revolutions | uint16 | revolution | Crank revolutions since the machine started the workout. Wraps at 65 536. |
| 12 | 2 | Last Crank Event Time | uint16 | 1/1024 s | Wraps every 64 s. See 5.4. |
An example frame from the same rider as section 4.3.2 (250 W, 90 rpm, half an hour in):
30 00 FA 00 DB 60 00 00 00 68 C3 0A 00 B4
| Octets | Field | Decoded |
|---|---|---|
30 00 |
Flags | wheel and crank revolution data present |
FA 00 |
Instantaneous Power | 250 W |
DB 60 00 00 |
Cumulative Wheel Revolutions | 24 795 flywheel revolutions |
00 68 |
Last Wheel Event Time | 26 624 counts of 1/2048 s |
C3 0A |
Cumulative Crank Revolutions | 2 755 crank revolutions |
00 B4 |
Last Crank Event Time | 46 080 counts of 1/1024 s |
5.4 Deriving cadence from crank revolutions
Cadence is not a field in this service. A client that has any use for the Fitness Machine Service should take cadence from Indoor Bike Data instead, where it is a plain field. If strictly needed, cadence can be computed from two notifications by dividing the change in crank revolutions by the change in crank event time:
cadence [rpm] = (revs2 - revs1) * 60 * 1024 / (time2 - time1) notification 1 : 30 00 FA 00 DB 60 00 00 00 68 C3 0A 00 B4 notification 3 : 30 00 FA 00 F6 60 00 00 00 78 C6 0A 00 BC d revs : 3 d time : 2048 counts = 2.000 s cadence : 90.0 rpm
Handle the wrap-around of both counters (use unsigned 16-bit subtraction). The two frames above are two notification intervals apart, because 90 rpm is 1.5 revolutions per second and a whole number of revolutions needs an even number of seconds. That is the general case: the counters advance in whole revolutions, so consecutive notifications give a coarse figure, and a client that wants a stable cadence should compute it over a short window of frames. The machine may repeat an identical frame, since it notifies on a timer rather than on a crank event; ignore frames whose crank event time has not changed.
5.5 The wheel revolution fields on this machine
There is no physical wheel on the machine. The rider's cranks drive a flywheel through a fixed pulley ratio, so Cumulative Wheel Revolutions reports flywheel revolutions. It is not a road-wheel count, it does not account for the engaged virtual gear, and no wheel circumference turns it into a road distance. For virtual speed, use Indoor Bike Data.
6 Device Information Service
The Device Information Service, UUID 0x180A, carries five read-only strings. It is not advertised; discover it after connecting. Its main use to a third-party client is reading the firmware version and the serial number, to tell one machine from another.
| UUID | Characteristic | Contents |
|---|---|---|
0x2A29 |
Manufacturer Name String |
ZYCLE, fixed. |
0x2A24 |
Model Number String |
ZInfinity, fixed: the same string the machine advertises as its name. |
0x2A27 |
Hardware Revision String | For diagnostic information. |
0x2A26 |
Firmware Revision String | For diagnostic information. The running firmware version followed by the build date and time, separated by spaced hyphens, for example V1.39 - Aug 24 2026 - 14:17:32. |
0x2A25 |
Serial Number String | Two hyphen-separated 8-digit hexadecimal words derived from the device address, for example A1B2C3D4-0000E5F6. Stable for the life of the machine and unique to it. |
The Zycle service offers the firmware version a second way, as two numeric octets (section 7.4).
7 Zycle service
The Zycle proprietary service carries three things the standard services cannot express: which mode the machine is in, which virtual gear the rider has engaged, and the shape of their pedal stroke. A client that does not need any of those can ignore this service completely and lose nothing else.
This service provides no control. Every published characteristic in it is read-only or a diagnostic channel. Target power, resistance and simulation parameters all go through the Fitness Machine Control Point (section 4.5), on the same connection, at the same time.
Only part of the service is published. The rest are factory configurations and procedures, reserved to Zycle applications and not documented here. The published subset is complete in itself: nothing in this section depends on anything that is not in it.
7.1 Service and characteristics
The service and its characteristics use 128-bit vendor UUIDs sharing a common base. Each characteristic differs from the others only in the third and fourth octets, shown below as a 16-bit alias.
service db6dba4b-a18d-44ae-90f1-38d01dec8f51
characteristics db6dXXXX-a18d-44ae-90f1-38d01dec8f51
^^^^ the 16-bit alias from the table below
| Alias | Full UUID | Characteristic | Properties | Octets | Purpose |
|---|---|---|---|---|---|
0x0001 |
db6d0001-a18d-44ae-90f1-38d01dec8f51 |
Information | Read, Notify | 15 | Live power, cadence, odometer, mode and engaged gear. Section 7.2. |
0x0002 |
— | Reserved | — | — | Zycle internal use. Do not read, write or subscribe. |
0x0003 |
— | Reserved | — | — | Zycle internal use. Do not read, write or subscribe. |
0x0004 |
db6d0004-a18d-44ae-90f1-38d01dec8f51 |
Power Diagram | Read, Notify | 100 | Per-revolution pedal-stroke power diagram. Section 7.3. |
0x0005 |
db6d0005-a18d-44ae-90f1-38d01dec8f51 |
Command | Write Without Response, Notify | 28 | Firmware version. Section 7.4. |
This service is big-endian, the opposite of the three standard services. The one exception is noted in section 7.2.1.
The service UUID is not advertised, so a client discovers it after connecting. If it is absent, the firmware predates it; a client should treat the service, and each characteristic in it, as optional and fall back to the standard services.
7.2 Information
Fifteen octets, notified once per second while subscribed. Subscribe rather than read.
| Offset | Size | Field | Type | Unit | Notes |
|---|---|---|---|---|---|
| 0 | 2 | Power | uint16 | watt | Same value as the standard services. |
| 2 | 2 | Cadence | uint16 | rpm | Revolutions per minute. |
| 4 | 4 | Odometer | uint32 | metre | Lifetime total. |
| 8 | 1 | Bike Mode | uint8 | — | See 7.2.1. |
| 9 | 1 | Parameter 1 | uint8 | — | Meaning depends on Bike Mode. |
| 10 | 1 | Parameter 2 | uint8 | — | Meaning depends on Bike Mode. |
| 11 | 2 | Balance | uint16 | 0.1 % | Share of the revolution attributable to the left half of the stroke. Neutral is 500. |
| 13 | 2 | Smoothness | uint16 | 0.1 % | Proprietary 0–1000 pedalling-regularity score. |
7.2.1 Bike Mode and the two parameters
Bike Mode says what the machine is currently doing, and decides what the two parameter octets mean. The three modes below are the ones a client will see on a machine being ridden.
| Mode | Name | Parameter 1 | Parameter 2 |
|---|---|---|---|
0x01 |
Basic resistance | Current resistance level, 0–200 | Always 0 |
0x02 |
ERG (power) | Target power, low octet | Target power, high octet |
0x04 |
Simulation | Engaged chainring index | Engaged sprocket index |
The mode follows whichever target-setting procedure was last accepted: for example, a client that sets a target power will see the mode become 0x02 within a second.
Any other value means the machine is not in a state a rider can ride (starting up, in error, etc.). A client shall treat an unrecognised mode as "not ridable" and ignore the two parameter octets.
In power mode the target is a 16-bit value split across the two octets, and, uniquely in this service, it is little-endian: Parameter 1 carries the low octet and Parameter 2 the high octet.
Information : 00 FA 00 5A 00 12 D6 87 02 2C 01 02 00 02 DE
^^ ^^ parameters 1 and 2
target power = parameter1 | (parameter2 << 8)
= 0x2C | (0x01 << 8) = 300 W
7.2.2 Balance and Smoothness
Both are pedalling-quality estimates computed on the machine, in units of 0.1 %.
- Balance is the share of the revolution's power contribution attributable to the left half of the pedal stroke, derived from the same per-revolution torque samples that produce the Power Diagram. 500 is even.
- Smoothness is a proprietary 0–1000 score in which higher is smoother.
Both are reset when the machine detects that the rider has stopped: Balance returns to 500 and Smoothness to 0.
7.2.3 Worked example
The same rider as section 4.3.2, in simulation mode with the first chainring and the eighth sprocket engaged:
00 FA 00 5A 00 12 D6 87 04 01 08 02 00 02 DE
| Octets | Field | Decoded |
|---|---|---|
00 FA |
Power | 250 W |
00 5A |
Cadence | 90 rpm |
00 12 D6 87 |
Odometer | 1 234 567 m, lifetime |
04 |
Bike Mode | simulation |
01 |
Parameter 1 | chainring index 1 (the first chainring) |
08 |
Parameter 2 | sprocket index 8 (the eighth sprocket) |
02 00 |
Balance | 51.2 % to the left |
02 DE |
Smoothness | 73.4 % of the scale |
7.2.4 Using the gear indices
The indices are positions in the machine's configured drivetrain, numbered from one: not tooth counts and not ratios. Index 1 always exists and index 0 never occurs.
The two indices count in opposite directions, as is common in cycling: chainrings are ordered from fewest teeth upwards, so chainring 1 is the smallest, whereas sprockets are ordered from most teeth downwards, so sprocket 1 is the largest. That way, the lower the number, the easier the gear.
The drivetrain itself (number of chainrings and sprockets, and teeth on each) can only be configured from the Zycle app. For reference, there is a maximum of 24 sprockets for single-chainring configurations and 12 sprockets for double-chainring configurations, and the bike may be configured with as few as 9 sprockets in any configuration.
7.3 Power Diagram
One hundred octets describing the rider's power through a single crank revolution: twenty-five pairs, each an angle followed by a power value, both uint16 big-endian.
| Field | Type | Unit | Notes |
|---|---|---|---|
| Angle | uint16 | degree | Crank angle. The machine sends twenty-four points at 15° intervals, from 0 to 345. |
| Power | uint16 | 0.1 W | Power at that angle. Divide by ten for watts. |
octets 0-3 : angle 0, power at 0° octets 4-7 : angle 15, power at 15° ... octets 92-95 : angle 345, power at 345° octets 96-99 : angle 0, power repeated from the first point (padding)
The twenty-fifth pair is padding, not a data point: its angle is 0 and its power repeats the first point's value, so that a client which draws the pairs in order closes the curve.
When drawing the diagram as a polar plot, scale the radial axis so that it does not start at zero watts; otherwise the differences in power through the stroke are too small to see and the diagram looks like a circle.
This characteristic is driven by crank rotation, not by the one-second timer. While the rider is pedalling it updates roughly once per revolution; when they stop, notifications stop. At one hundred octets, it is also the frame most likely to be blocked by an unnegotiated ATT MTU (section 3.3).
7.4 Command
The Command characteristic is a request-and-response channel. A client writes a two-octet big-endian command value, optionally followed by parameters, using Write Without Response, and reads the response from a notification on the same characteristic. Subscribe to notifications before writing.
At the moment only one command is available to third-party clients. Every other command value is reserved: do not send it.
| Command | Name | Parameters | Response |
|---|---|---|---|
0x0001 |
Version | None | Two octets: firmware major, firmware minor. |
write : 00 01
notification : 01 27 00 00 00 00 00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00 00
firmware : 0x01 . 0x27 = version 1.39
The first octet is the major version and the second the minor, each as a plain integer. This is the same version the Device Information Service reports as a string (section 6). Responses are always twenty-eight octets, zero-padded after the meaningful part.
8 Worked integrations
This section is informative. It puts the preceding sections together for the three kinds of application that account for almost every integration.
8.1 A structured-workout application
An application that drives the rider through target powers (intervals, ramps, a training plan) needs the Fitness Machine Service and nothing else.
- Scan for
ZInfinityor for service0x1826, and connect. - Request an ATT MTU of 185 octets.
- Discover services and characteristics.
- Subscribe to indications on the Control Point before writing to it, and to notifications on Indoor Bike Data.
- Read Supported Power Range and clamp your targets to it (0–4000 W on present firmware).
- Write Request Control (
00) and check the indication. - Write Set Target Power (
05plus the target, little-endian) at the start of each interval. - Read power and cadence from the Indoor Bike Data notifications, once per second.
To end the session, stop writing targets. There is no "stop" procedure.
8.2 A route or gradient simulator
A game or a route replay wants the rider to feel the terrain, and to be able to shift gears.
- Connect and subscribe exactly as in 8.1.
- Write Set Indoor Bike Simulation Parameters (
11) with the gradient at the rider's current position, together with the wind speed and the rolling and wind resistance coefficients you have chosen for the rider and bike. All four are sent together every time; the machine does not merge partial updates. - Re-send whenever the gradient changes.
- Read power and cadence from the Indoor Bike Data notifications, once per second. If desired, also read speed, which takes the engaged gear into account and is therefore the most realistic source.
- If the application shows the engaged gear, subscribe to Zycle Information and read the two mode parameters (section 7.2).
8.3 A recorder or head unit
An application that only records what the rider does, or a head unit that treats the machine as a power meter, needs no control at all.
- Connect. Subscribe to Indoor Bike Data (recommended if the application records cadence) or to Cycling Power Measurement.
- To show a pedal-stroke diagram, subscribe to notifications on the Zycle Power Diagram characteristic (this needs the larger MTU, section 3.3).
- If desired, take distance from Zycle Information. It is a lifetime odometer; subtract the initial value for a session distance.
- Do not write to the Control Point.
This integration can safely run alongside any other client, as it never attempts to control the bike.
9 Platform notes
This section is informative. It collects the platform details that most often stop a first integration from working.
9.1 Web Bluetooth (browser apps)
- Works in Chrome and Edge on Windows, macOS, ChromeOS and Android. It does not work in Safari, in Firefox, or in any browser on iPhone and iPad.
- The page must be served over HTTPS (or from
localhost), andnavigator.bluetooth.requestDevice()must be called from a user action such as a button click. - List every service you will use in
optionalServices, or the browser will refuse access to it after connecting:
const device = await navigator.bluetooth.requestDevice({
filters: [{ name: 'ZInfinity' }],
optionalServices: [0x1826, 0x1818, 0x180a, 'db6dba4b-a18d-44ae-90f1-38d01dec8f51']
});
- There is no API to request the MTU: the browser and operating system negotiate it. If the Zycle Power Diagram or Command notifications never arrive, the MTU negotiated on that platform is too small.
- Write to the FTMS Control Point with
writeValueWithResponse(), and to the Zycle Command characteristic withwriteValueWithoutResponse(). - Chrome blocks reading the Serial Number String (
0x2A25). Usedevice.idto tell machines apart.
9.2 Android
- Request the runtime permissions
BLUETOOTH_SCANandBLUETOOTH_CONNECT(Android 12 and later), or location permission on older versions. - Call
requestMtu(185)(or higher) after connecting and wait foronMtuChangedbefore enabling notifications. - Android performs one GATT operation at a time: queue reads, writes and descriptor writes, and wait for each callback before starting the next.
9.3 iOS and macOS (Core Bluetooth)
- The operating system negotiates the MTU automatically;
maximumWriteValueLength(for:)shows the result. - The Bluetooth address is not exposed. Use the peripheral
identifier(stable on one device) or the serial number from the Device Information Service to tell machines apart. - Declare
NSBluetoothAlwaysUsageDescriptionin the app'sInfo.plist.
9.4 Desktop and scripts
On Windows, macOS and Linux, libraries such as Bleak (Python) let the operating system negotiate the MTU. Check that the Power Diagram arrives to confirm it is large enough.
10 Troubleshooting
| Symptom | Likely cause | What to do |
|---|---|---|
| The bike does not appear in the scan | It already holds two connections (another app, the Zycle app, a head unit) | Close one of the other connections. Check the scan is not filtered on 0x180A or the Zycle service UUID. |
Control point writes return 80 xx 05
|
Another app or an ANT+ device holds control | Disconnect that app or device. Keep reading data meanwhile. |
| No answer at all to control point writes | Indications on 0x2AD9 not enabled, or Write Without Response used |
Subscribe to indications first; write with response. |
Writes return 80 xx 02
|
Op code not implemented | Use only 0x00, 0x04, 0x05 and 0x11. |
| Data arrives but the Power Diagram or Command never do | MTU too small | Request an MTU of 185 or more before subscribing (section 3.3). |
| Values look absurd (for example 64 000 W instead of 250 W) | Wrong byte order | FTMS and CPS are little-endian; the Zycle service is big-endian. |
| Resistance looks ten times too small or too large | A library applied the FTMS 0.1 resolution | Use the raw integer 0–200 (section 4.4). |
| Gradient changes have no effect | Set Target Inclination used, or a partial simulation write | Use op code 0x11 with all six operand octets. |
Annex A — Test vectors
Every frame in this guide, collected for use as unit-test fixtures. The same vectors are available as a JSON file.
A.1 Fitness Machine Service
| Frame | Octets | Meaning |
|---|---|---|
| Fitness Machine Feature (read) | 86 50 00 00 0C 20 00 00 |
Fitness Machine Features 0x00005086, Target Setting Features 0x0000200C |
| Supported Resistance Level Range (read) | 00 00 C8 00 01 00 |
Minimum 0, maximum 200, increment 1 |
| Supported Power Range (read) | 00 00 A0 0F 01 00 |
Minimum 0 W, maximum 4000 W, increment 1 W |
| Indoor Bike Data (notification) | 74 08 A8 0C B4 00 95 40 00 54 00 FA 00 2D 07 |
32.40 km/h, 90 rpm, 16 533 m since power-on, resistance 84 of 200, 250 W, 1837 s elapsed |
| Control point, Request Control (write) | 00 |
|
| Control point, Request Control (indication) | 80 00 01 |
Success |
| Control point, Set Target Resistance Level (write) | 04 96 |
Resistance level 150 of 200 |
| Control point, Set Target Resistance Level (indication) | 80 04 01 |
Success |
| Control point, Set Target Power (write) | 05 2C 01 |
300 W, little-endian |
| Control point, Set Target Power (indication) | 80 05 01 |
Success |
| Control point, Set Indoor Bike Simulation Parameters (write) | 11 00 00 2C 01 33 33 |
Wind 0.000 m/s, grade +3.00 %, Crr 0.0051, Cw 0.51 kg/m |
| Control point, Set Indoor Bike Simulation Parameters (indication) | 80 11 01 |
Success |
| Control point, unimplemented procedure (write) | 07 |
Start or Resume, not implemented |
| Control point, Op Code not supported (indication) | 80 07 02 |
|
| Control point, Control not permitted (indication) | 80 05 05 |
Another client or an ANT+ peer holds control; the write was discarded |
| Fitness Machine Status, Target Resistance Level Changed | 07 96 |
Resistance level 150 of 200 |
| Fitness Machine Status, Target Power Changed | 08 2C 01 |
300 W, the value the machine applied |
| Fitness Machine Status, Indoor Bike Simulation Parameters Changed | 12 00 00 2C 01 33 33 |
The six operand octets of the accepted procedure, unchanged |
A.2 Cycling Power Service
| Frame | Octets | Meaning |
|---|---|---|
| Cycling Power Feature (read) | 0C 00 00 00 |
Wheel revolution data, crank revolution data |
| Cycling Power Measurement (notification) | 30 00 FA 00 DB 60 00 00 00 68 C3 0A 00 B4 |
250 W, 24 795 flywheel revolutions, 2755 crank revolutions |
| Cycling Power Measurement, two seconds later | 30 00 FA 00 F6 60 00 00 00 78 C6 0A 00 BC |
Three more crank revolutions in 2048 counts of 1/1024 s: 90 rpm |
A.3 Zycle service
| Frame | Octets | Meaning |
|---|---|---|
| Information, simulation mode | 00 FA 00 5A 00 12 D6 87 04 01 08 02 00 02 DE |
Chainring index 1, sprocket index 8 |
| Information, power mode | 00 FA 00 5A 00 12 D6 87 02 2C 01 02 00 02 DE |
Target 300 W, split across the parameters low octet first |
| Information, basic resistance mode | 00 FA 00 5A 00 12 D6 87 01 54 00 02 00 02 DE |
Applied resistance 84 of 200 |
| Command, Version (write) | 00 01 |
|
| Command, Version (notification) |
01 27 followed by 26 octets of 00
|
Firmware 1.39, zero-padded to 28 octets |
Annex B — Quick reference
B.1 Identifiers
| Item | Value |
|---|---|
| Advertised name | ZInfinity |
| Appearance (scan response) | 1156 — Cycling Power Sensor |
| Fitness Machine Service | 0x1826 |
| Cycling Power Service | 0x1818 |
| Device Information Service | 0x180A |
| Zycle service | db6dba4b-a18d-44ae-90f1-38d01dec8f51 |
| Zycle Information | db6d0001-a18d-44ae-90f1-38d01dec8f51 |
| Zycle Power Diagram | db6d0004-a18d-44ae-90f1-38d01dec8f51 |
| Zycle Command | db6d0005-a18d-44ae-90f1-38d01dec8f51 |
B.2 Limits and constants
| Item | Value |
|---|---|
| Advertising interval | 250 ms, continuous |
| Preferred connection interval | 80–100 ms |
| Preferred supervision timeout | 4000 ms |
| Maximum ATT MTU | 185 octets |
| Simultaneous connections | 2 |
| Notification rate | Once per second, every subscribed characteristic (except Control Point, Status and Power Diagram) |
| Target power range | 0–4000 W, increment 1 |
| Target resistance range | 0–200, increment 1 |
| Grade resolution | 0.01 %, signed |
| Power Diagram points | 24 at 15°, plus one padding pair |
| Balance and Smoothness range | 0–1000, units of 0.1 % |
| Lowest gear index | 1 (indices are numbered from one) |
Annex C — Revision history
| Edition | Date | Changes |
|---|---|---|
| 1.0 | 31 August 2026 | First publication (PDF). |
| 1.0 web edition | October 2026 | Published on the Zycle help center. Added Key rules, Platform notes (9) and Troubleshooting (10); clarified the integer resistance scale (4.4); section 8.3 now says to subscribe to the Power Diagram; full UUIDs of the Zycle characteristics added; support link updated. |