AI-generated brief below

Though the experiment is mine, the summary of my activities below was prepared by Claude, and it attempted (poorly) to write this in my voice. I have supplied this information as proper disclosure of AI usage.

I wanted to know if a $20 smart ring could measure the one signal my mental-health work depends on: the state of the autonomic nervous system, the involuntary system that runs you between calm and stressed. The first version of this note ended with “the ring can’t, it’s the wrong instrument.” That was too quick, and wrong in an instructive way. The ring can’t do it on the firmware it ships with — but the reason is that the firmware throws the data away before it reaches you, not that the hardware is incapable. That distinction reopens a door I had closed. This note is the corrected map.

Why I was after HRV

The thesis behind the venture is that activity signals — steps, screen time — track mental state poorly, so I want something closer to the body’s internal state. Heart rate variability (HRV) is the most validated cheap window onto it. A healthy, calm nervous system varies the gap between heartbeats moment to moment; under stress those gaps stiffen toward regularity. The clinical standard is the 1996 Task Force report (Circulation 93:1043–1065). The metric most people mean is RMSSD, the beat-to-beat measure, which is an accepted index of vagal (“rest”) tone (Laborde et al., 2017). The thing to hold onto: every HRV metric is computed from the millisecond gaps between individual heartbeats. No gaps, no HRV.

What the ring actually gives, and the wall it hits

The ring speaks a documented Bluetooth protocol with no pairing (tahnok/colmi_r02_client). Its “real-time heart rate” command returns a handful of smoothed beats-per-minute integers with no gaps between beats. There is a deeper raw mode that streams the optical pulse sensor (edgeimpulse). I wrote a probe to enable it and time every sample. On my unit, stock firmware, the answer was flat: one sample per second, all sensor channels locked to 1 Hz (my measurement).

That is fatal to HRV by simple sampling math. To see the wobble between beats you must sample the pulse many times per second; the practical floor is around 25 Hz. At 1 Hz you are not under-sampling the heartbeat, you are missing it. This is the Nyquist limit, not an engineering nuisance: a 1 Hz sampler cannot represent anything above 0.5 Hz, and a resting heartbeat sits near 1.2 Hz. At this rate the information is gone at the sensor.

The correction: 1 Hz is a decision, not a limit

Here is what I got wrong the first time. The 1 Hz is set by the firmware, not the silicon. Two pieces of evidence.

First, the well-known “faster values” modified firmware. I pulled the stock and modified images and diffed them byte for byte: they differ in five bytes, of which exactly one is a functional change — a timing constant dropped from 125 to 32 (my analysis). The Gadgetbridge maintainers, who reversed the same file, describe it as lowering the raw refresh timeout and nothing else (PR #3896). A single software constant moves the rate from ~1 Hz to ~4 Hz. A physical ceiling cannot be moved by a constant.

Second, and this is reasoning rather than a looked-up fact: the sensor must already sample far faster than 1 Hz internally, because computing a heart rate at all requires resolving the shape of each pulse. The fast samples exist inside the chip. The firmware reads them, averages them down to one number per second, and transmits only that. The raw high-rate stream I want is being generated and then discarded one step before Bluetooth.

I then confirmed the part that makes this actionable. The firmware dump in the community repo contains the symbol spo2_vc30f_int_FV003 (my finding, from the binary). That names the init routine for the ring’s pulse sensor, the VCare VC30F. In plain terms: the working configuration sequence for the sensor is sitting inside the firmware image we already have. So even without the sensor’s datasheet — which I could not find — the high-rate configuration is recoverable by disassembly.

The honest conclusion is therefore narrower and more useful than “impossible.” HRV is unreachable on any available firmware. Reaching it means writing your own, and the materials to attempt that are unusually complete.

What the custom-firmware path would take

This is feasible at proof-of-concept scale and the repo gives a strong start: full firmware dumps, the bootloader, the BlueX SDK, an over-the-air flasher, and the modified image as a worked example of binary patching (atc1441/ATC_RF03_Ring). The core work is disassembly (the dump is mostly stripped, ~1,500 strings, so this is real Ghidra labour on an ARM Cortex-M0): find the VC30F init, stop the decimation, raise the rate, stream each sample. Binary-patching the existing image is more tractable than rebuilding from the SDK, which I could not confirm is buildable as shipped.

Three limits survive even a perfect firmware, and only the first is soft:

  • Light versus speed versus power. Sampling faster means less light collected per sample unless you drive the LED harder, which costs power. Real, but commercial wearables run pulse sensors at 25–128 Hz routinely, so this is a tuning problem, not a wall.

  • Battery. The ring carries a 17 mAh cell (RF03 datasheet, in the repo). High-rate sensing plus constant streaming drains that fast. My expectation, unverified, is that you get bursts of HRV-grade capture measured in minutes, not always-on recording.

  • The sensor’s own maximum rate. The VC30F datasheet isn’t in hand, so its hard ceiling is unknown to me. As a heart-rate part it near-certainly supports 25 Hz or more internally, but I am flagging this as unverified rather than asserting it.

The ground truth is now in hand

To judge any ring result you need a reference that is already correct. The Polar H10 chest strap detects heartbeats from ECG on-device and reports the inter-beat intervals over the standard Bluetooth Heart Rate Service, in units of 1/1024 second (Polar developer docs). Against a real ECG its intervals show bias under 1 ms at rest (Gilgen-Ammann et al., 2019). It is gold-standard-equivalent for the linear metrics I care about.

I captured a five-minute resting window and computed the reference (my measurement): RMSSD 36 ms, SDNN 55 ms, mean HR 81 bpm. Those are ordinary resting values; nothing alarming, and more to the point the chain works end to end — strap to intervals to HRV. To use this as a ruler I also built a harness that records the ring and the H10 at the same time on one clock, so the two streams can be aligned and compared. The H10 is only ever the ruler. The consumer device is the ring.

Warnings and gotchas

  • The consumer problem is larger than the reverse-engineering. Everything above produces a proof of concept on rings I flash myself. A shippable product means custom firmware on hardware I do not control, and the OEM already varies components silently across batches (R02, R03, RY02, RY03, with no cross-compatible firmware). At scale this is fatal unless I either secure an OEM relationship with a fixed bill-of-materials and factory flashing, or design my own ring around a pulse sensor with a documented high-rate output. This is the real project. The bytes are the easy part. (This is my assessment, not a sourced claim.)

  • The breathing fallback was weak. Before reopening the firmware path I tried to pull respiration from the 1 Hz pulse stream, since breathing is slow enough to survive 1 Hz sampling and is itself a coarse autonomic signal. Free-breathing gave a weak ~13 breaths/min peak; a paced test produced a smeared, preprocessing-sensitive result, not a clean measurement (my analysis). Two reasons it was always going to be hard: the spontaneous blood-pressure rhythm called a Mayer wave sits at ~0.1 Hz, the same frequency I paced my breath to, so the two are inseparable there; and the low-frequency band runs 0.04–0.15 Hz with thermoregulatory drift below it, which swamps the signal until filtered out (Gevirtz review, 2019).

  • One thing I cannot test on the ring’s raw stream at all. Respiratory sinus arrhythmia — the heart speeding and slowing with the breath — needs a heart-rate series, which 1 Hz pulse data cannot produce. The breathing capture could only ever test slow pulse-baseline drift, never the heart coupling.

  • The reference capture wasn’t pristine. The artifact filter removed 5.2% of beats (clean recordings run 1–3%), and HR 81 with a high LF/HF balance suggests I wasn’t fully settled. Usable, but a wetter strap and a stiller, later capture would give a cleaner baseline.

Where this leaves the venture

Two phases, and the second is the hard one. Phase one: prove HRV-grade capture on rings I flash myself, validated beat-for-beat against the H10. The materials make this feasible, and the firmware-symbol find removes the worst blocker. Phase two: turn “rings I can flash” into “rings I can ship,” which is a hardware-supply and regulatory problem, not a signal-processing one.

The night cost a ring, an evening, and the embarrassment of a wrong first conclusion. What it bought is worth more: I now know, on my own hardware, exactly where the wall is — and that it is made of firmware and supply chains, not physics.