# A Smart Ring for Zwift

> A reproducible firmware patch that lets a COLMI R12 expose standard Bluetooth heart-rate data to fitness software.

Canonical page: [A Smart Ring for Zwift](<https://devanmetz.com/portfolio/colmi-r12-zwift/>)

Project date: Aug 2026

Updated: 2026-10-14

Category: Firmware & interoperability

Status: Hardware-tested experiment

Tags: Python, ARM assembly, Bluetooth LE, Reverse engineering

- **My work:** Firmware patch, packager, and BLE test tools
- **Target:** COLMI R12 · RT11CR_V1.0
- **Compatibility:** Stock firmware RT11CR_1.00.09_260424

- [View the project](<https://github.com/DevanMetz/colmi-r12-zwift>)

## In short

I patched a COLMI R12 smart ring so fitness software such as Zwift can receive its heart-rate data over standard Bluetooth. I also built packaging, monitoring, and recovery tools for the specific hardware and firmware combination I tested.

![A black COLMI R12 smart ring with its illuminated display.](<https://devanmetz.com/_astro/colmi-r12.B-OjV_HP_6YYAu.webp>)

COLMI R12 product image from COLMI Store. My contribution is the independent firmware patch.

## Overview 

I adapted a COLMI R12 smart ring to software that expects the standard Bluetooth Heart Rate Service (a shared format for sending heart-rate data). The patch makes the ring advertise that service so a fitness application such as Zwift can use its heart-rate notifications.

It is a narrowly targeted interoperability experiment. The supported combination is hardware `RT11CR_V1.0` with stock firmware (the original device software) `RT11CR_1.00.09_260424`. Vendor firmware is not distributed with the project.

## What the patch changes 

The patched device:

-   Exposes Heart Rate Service `0x180D` (its Bluetooth service identifier).
-   Keeps heart-rate measurement running after the display sleeps.
-   Resends the last valid BPM (beats per minute) every two seconds between new sensor results.

That resend keeps a standard notification stream available between measurements. It does not create a new sensor reading every two seconds; the most recent valid result can appear more than once.

I wrote ARM Thumb patches (changes to the processor instructions) and Python tools for packaging the firmware, communicating over Bluetooth, monitoring readings, and recovering the stock image.

## Reproducible packaging 

### Input checks 

The packager checks the exact input image before changing it. It verifies the expected original instruction ranges, preserves image length, and updates the firmware metadata (information describing the image) and checksums (values used to check data integrity).

### Recovery image 

The process produces both a patched image and a stock-recovery image. The recovery image uses a higher boot counter (the update version counter) so it can restore the original code through the device’s update mechanism.

These checks are important because fixed-address binary patches (changes to compiled code at specific locations) depend on a specific firmware layout. A similarly named device or a different stock release is not automatically a compatible target.

## Using it with Zwift 

After following the supported-image preparation and flashing (writing firmware to the device) instructions in the repository:

1.  Disconnect other tools or phone apps that are connected to the ring.
2.  Put the ring on and open its heart-rate page.
3.  Pair the ring as a **Heart Rate** device in Zwift.
4.  Leave the ring on that page during the session.

### Checking the Bluetooth stream 

A standalone BLE (Bluetooth Low Energy) monitor can inspect standard notifications before involving Zwift. The tools can select a specific device if more than one compatible ring is nearby.

## Known limitation and recovery 

### Sensor and display behavior 

Leaving the heart-rate page stops the sensor and its updates. Keeping the display asleep is supported; navigating to another page is a different action. A tap-lock experiment was excluded after it made wake behavior unreliable.

### Restoring the stock firmware 

The repository documents how to prepare and retain a recovery image. Its setup flow verifies the stock image and requires an explicit confirmation before starting an over-the-air flash (a wireless firmware update).

I tested the patch on `RT11CR_V1.0` hardware with `RT11CR_1.00.09_260424` firmware. Other R12 variants are untested.

## Files and credits 

The [public repository](<https://github.com/DevanMetz/colmi-r12-zwift>) contains the patch assembly, Python tools, supported-image hashes, monitor, and recovery instructions. Use that versioned reference for the exact flashing procedure.

The cover is a [COLMI Store product image](<https://colmi.store/products/r12-smart-ring>). COLMI designed the hardware; my contribution is the independent firmware patch and the tooling around it.

## FAQ

### Which COLMI R12 version supports the patch?

The tested combination is RT11CR_V1.0 hardware with stock firmware RT11CR_1.00.09_260424. Other R12 variants are untested, and vendor firmware is not distributed.

### Does the ring take a new reading every two seconds?

No. The patch resends the last valid BPM every two seconds between new sensor results. The same result can appear more than once.

### How do I use the patched ring with Zwift?

After preparing and flashing the supported image using the repository instructions, disconnect other tools or phone apps, put the ring on, open its heart-rate page, and pair it as a Heart Rate device in Zwift. Leave it on that page during the session.

### Can I restore the stock firmware?

The packaging process produces a stock-recovery image with a higher boot counter so the original code can be restored through the device's update mechanism. The repository documents its preparation and recovery procedure.

## Related reading

- [OpenFloat Archery](<https://devanmetz.com/portfolio/openfloat-archery/index.md>)
- [Solar Cellular Trail Camera](<https://devanmetz.com/portfolio/solar-trail-camera/index.md>)
- [nRF54L15 Support](<https://devanmetz.com/portfolio/nrf54l15-support/index.md>)
