Twenty-three bytes. That was the ATT MTU one Bluetooth hub negotiated with a chest strap on an internal tool we built for our own desks. The strap sends motion frames of roughly 226 bytes. With a 23-byte MTU, heart rate kept arriving on time and the accelerometer data never arrived at all. Nothing crashed and nothing logged an error. Half the product had quietly gone missing.
We learned the rest of this post from that tool. It is not a product and it is not medical. It is a background service that holds a BLE link to a chest-strap heart-rate sensor through the working day and turns every heartbeat into numbers we can look at later. Building it taught us something founders building health wearables tend to find out too late. The sensor is the easy part. Whether you can trust the number depends on everything after the sensor.
What HRV actually punishes
Heart rate is forgiving. Average sixty beats and one bad one barely moves the result. HRV is the opposite. RMSSD is built from the differences between consecutive beat intervals, so a single missed beat, where the strap reports one 1600 ms interval instead of two 800 ms ones, produces two huge successive differences. Squared. One glitch in a minute of clean data can make a calm person look like they just ran up the stairs.
So the first thing we wrote was not the chart. It was the rejection logic. Any interval outside 300 to 2000 ms is dropped outright, because no resting adult produces those. Then every interval that survives is compared against a rolling mean of the last 20 accepted beats, and anything too far from that mean is thrown away as well. Only then does a minute get rolled up into RMSSD, SDNN, pNN50 and mean heart rate. Everything downstream reads those minute summaries and never touches raw beats.
This is where we would push back on a lot of wearable firmware. The instinct is to interpolate a missing beat and keep the stream looking smooth. We would rather drop it. A gap is honest and a guessed beat is not, and a smoothed HRV number looks trustworthy right up until someone compares it with a clinical reference.
Track the rejection rate like a vital sign
Once you reject beats, you have a new number worth watching: how many you rejected. On our strap, a rejection rate above about 5% almost always meant the electrodes had dried out. It was not the algorithm and it was not the firmware. It was skin contact.
That changes where you debug. Check contact before you touch the code. A product that shows the user a readiness score without knowing its own rejection rate is asking to be trusted blind. We would put that rate in the device logs from the first prototype, next to the UART debug output we already insist on building in early. If a wrist-worn optical sensor is involved instead of chest ECG, expect the rate to be worse and to swing with motion, which is one more reason to have the accelerometer data actually arrive.
Nobody is average
Population ranges for resting heart rate and HRV are wide enough to be close to useless for one person. A trained cyclist and an office worker can both be perfectly healthy and still sit at opposite ends of the chart.
Our tool scores readiness against the wearer, not against a table. The resting heart rate and RMSSD baselines are medians over that person's calmest recorded minutes, refreshed every hour. Medians, not means, because the calm minutes still contain the odd bad minute and a mean lets it in. If your wearable ships with a hard-coded "good HRV" threshold, it will be wrong for most of your users on day one and they will notice by week two.
The radio link is part of the measurement
Back to the MTU. When a reading goes missing, founders assume the sensor failed or the app has a bug. In our case both of the worst faults were in the link layer and both looked like application bugs.
The first was the MTU problem above. The second was bonding. The strap keeps a short list of bonded devices and evicts the oldest when a new one pairs. When a second computer paired with it, the first one's connection started dropping 0.2 seconds after every connect. From the application it looked like a flaky reconnect loop. In fact it was the strap politely forgetting us.
Neither shows up in a datasheet summary. You find them by logging what the link actually negotiated: MTU, connection interval, bond state, disconnect reason codes. We would log all of it on every connect in development builds. A wearable that reconnects with backoff but never records why it lost the link will pass every demo and fail in someone's pocket.
One more link-layer surprise, if a phone app is on your roadmap. Our phone view talks to the strap over Web Bluetooth, and that API only exists in a secure context. On a plain http:// address, even on our own wifi, the page simply had no bluetooth object. We had to serve it over HTTPS on the LAN. It cost an afternoon. Budget for it.
What we would build first
If we were starting a heart-rate or HRV wearable tomorrow, the first milestone would not be a pretty trend graph. It would be four boring things working together. Beat-level rejection with a visible rejection rate. Minute-level rollups so nothing downstream scans raw data. Baselines per wearer. And a BLE layer that logs what it negotiated and reconnects with backoff when the link drops. The firmware for those gets written alongside the board, not after it, because the sensor placement and the electrode contact decide how much rejection you will be doing.
A raw ECG stream helps too. Ours comes off the strap at 130 Hz and we draw it on an ECG-paper grid. When the numbers look odd, looking at the waveform settles the argument in seconds.
Get the sensor wrong and you swap a part. Get the trust wrong and your users stop believing the device, and no firmware update brings that back.
If you want a second opinion on your wearable's sensing and radio design, send it to us and we will tell you what we would check first.

