Nerding out on my home gym
The goal is a workout log I never have to type. Not coaching — yet — just an honest record of what I actually did, produced without touching a phone between sets.
I have tracked workouts for years, through a succession of iPhone apps. They all worked. The data was fine. What wore me down was the tapping: pick the exercise, enter the weight, enter the reps, do it again, forty times a morning, while trying to rest. Tracking was never the problem. The interface was.
Then I bought a Beyond Power Voltra.
The machine that already knows
The Voltra is a portable cable trainer that sets its resistance electronically rather than by hanging weight on a stack. It is a genuinely clever bit of kit — it can hold a constant load, add resistance only on the eccentric, and change the weight mid-rep. But the part that got me thinking was quieter than any of that: because the resistance is computed rather than stacked, the machine already knows what I am doing. Position, force, where one rep ends and the next begins. It is not inferring any of that. It is the thing generating it.
Which means the Voltra could log itself perfectly, and never bother me again.
And then I looked at the rest of the garage. A rack. A barbell. Bumper plates. Adjustable dumbbells. A Nordic bench. Every one of them is dumb steel that will never have a sensor in it, and collectively they are most of my training. No firmware update is coming for a 45 lb plate.
So the question got more interesting than “log my workouts”:
Can cameras, audio and a bit of AI give my analog equipment the same treatment the Voltra gives itself?
If the answer is yes, then the future is not a garage full of instrumented machines at four figures apiece. It is a room that pays attention. You would not need smart equipment — you would need a smart room, and the steel stays exactly as dumb and as cheap and as unbreakable as it already is.
That is the bet this whole build is testing. This phase asks the narrow version of it: can a couple of cheap cameras and a microphone tell what is happening in the room at all, well enough that the tapping goes away? Mostly yes. And the failures along the way were far more instructive than the successes.
I am not sold yet. I am watching to see whether this holds up over real sessions or quietly becomes another thing I stop using. But it is showing enough promise to keep going.

The hardware, such as it is
Two Reolink Wi-Fi cameras on the NVR that already watches the house. One is a 180° fisheye dome, now on the garage ceiling. The other is a 5 MP PTZ on the wall. Nothing was bought for this except, eventually, a microphone.
Everything — pose estimation, speech recognition, language understanding — runs on a MacBook in the garage. No cloud, no subscription, no audio leaving the house. That last part matters when the thing is listening in your home every morning.
The first surprise was that the NVR hands out the fisheye raw: the full hemisphere as a circle inscribed in a 2560×2560 square, completely un-rectified. That is the good outcome. It means the dewarping is mine to do, and one dome can be unrolled into several flat virtual cameras aimed wherever I want.


The projection is equidistant — radius on the sensor is proportional to angle off the optical axis — which holds far better at the rim than the perspective model does. The one number the whole thing depends on is the radius of that image circle, because it sets the angle-to-pixel scale. Get it wrong by 5% and every rectified line bends by 5%.
My first circle-finder thresholded the frame and took the lit extent. It measured
the same lens at r=1161 on one exposure and r=1222 on another. Rewriting it
to walk 720 rays outward looking for the intensity cliff, then least-squares fit
a circle to those edge points, brought the two into agreement:
| Frame | Threshold method | Ray fit |
|---|---|---|
| Test room, RTSP | 1161 | 1208.0 |
| Test room, snapshot | 1222 | 1210.6 |
| Garage, mounted | — | 1220.4 ±0.57px |
Working out what is in the room
Before tracking anything, it had to know what it was looking at. I described the room out loud and had it identify the equipment from a single frame.

Orange is training equipment; grey is not. The rack and the Freak Athlete bench were readable from their own branding. Two calls were wrong: an orange object on the rack upright turned out to be a slap collar, not the cable machine, and the whole left third of the frame is bins and a scooter — dead space that later shaped where the fisheye got cropped.
Which camera should do the counting
The obvious guess is the ceiling fisheye: it sees the whole room, it is dead centre, nothing occludes it. The obvious guess is wrong, and one measurement settles it.
| PTZ | Fisheye, dewarped | |
|---|---|---|
| Detection confidence | 0.92 | 0.92 |
| Keypoints resolved | 11/17 | 12/17 |
| Person height in frame | 696 px | 117 px |
The fisheye finds me just as reliably. But at 117 pixels tall, with keypoint error roughly constant in pixels, the measurement noise relative to the movement is about six times worse. You would still get a rep count. You would not get the thing that makes the count interesting — a set slowing from 1.16 to 1.43 seconds per rep as it goes on.
So the two cameras do different jobs. The PTZ counts reps. The fisheye answers cheaper questions: which station am I at, am I working or resting, where did I go when I left the other camera’s frame.
Counting reps without knowing the exercise
There are no per-exercise angle thresholds anywhere in this. One counter serves every movement: normalise the skeleton by torso length so camera distance stops mattering, take the dominant motion component across the limbs, and require a genuine autocorrelation peak at a plausible rep period. A set repeats. Fidgeting does not.
That last gate exists because the first version counted me sitting at a laptop as four reps at 5.4 seconds each.
Then three bugs, each individually sufficient to break rep counting completely, and each only visible because the intermediate numbers were on screen:
| Locomotion measured wrong. Normalising hip position by torso length and then differencing it. With hips at x = 1200 px and the torso estimate flickering between 170 and 190, that "moves" while I stand still. | 0.96 vs 0.055 limit |
| Periodicity on uneven samples. Pose frames do not arrive on a clock, but autocorrelation indexes lags in samples and assumes they do. Uneven spacing smeared the rep peak away entirely. | 0.0–0.15 vs 0.30 needed |
| Wrong array after that fix. Resampling onto a uniform grid made peak indices address 120 samples while the timestamps still held 53. | crash |
With all three fixed, a set of cable curls came through cleanly: 11 reps at 1.30 seconds, opening and closing on its own, tempo drifting from 1.16 to 1.43 seconds across the set. Sets are closed by rest, not by a spoken cue — nobody announces the end of a set, and making me do it would put the work back on me, which is the entire point of removing it.
The microphone was the whole problem
I wanted to talk to it rather than tap at it, so it transcribes continuously with Whisper. The first three attempts at a voice-activity gate all failed, and they all failed the same way: with the fan running, the camera microphone had about 8 dB between noise floor and speech. Every gate I built on that measurement fed back on itself — speech raised the floor, the raised floor rejected speech, and quiet speech that failed to trigger got recorded as silence, raising the floor further. The gate closed precisely when I talked.
| Approach | Result, replayed against audio that scores 38/38 offline |
|---|---|
| Rolling-percentile gate | 13 of 19 windows skipped, 25 garbled words |
| Energy-triggered utterances | 3 utterances, 5 words |
| No gate at all | 27 words, continuous |
The offline path was accurate because it gated nothing. So now nothing is gated, and hallucination is handled where it can actually be judged: per-word confidence, a repetition-tail cut, and a stoplist of Whisper’s stock inventions. A bare “you” is what it emits when you hand it a fan.
Which microphone, though
Both cameras have microphones. The fisheye sits dead centre in the room with nothing blowing on it; the PTZ is closer to me but in the fan’s draught. I was fairly confident the fisheye would win. I counted out loud and scored both against the known sequence.
| Fisheye | PTZ | |
|---|---|---|
| Signal-to-noise | 9.8 dB | 8.0 dB |
| Numbers transcribed | 7–31 | 5–42 |
| Correct | 25/39 (64%) | 38/38 (100%) |
The fisheye had better signal-to-noise and worse transcription. It ran clean from 7 to 31 and then collapsed into nonsense. The reason is reverberation: a ceiling microphone in a sealed concrete box hears my voice arrive several times off hard surfaces. Reverb raises the measured speech level, which is why the SNR looked good, while smearing the consonants that carry the words.
Signal-to-noise was the wrong metric. The microphone that wins is the one nearest you, not the one in the best position. Which pointed at the real fix — a USB conference microphone on the laptop, with no camera and no network in the audio path:
| Camera mic | USB mic | |
|---|---|---|
| Noise floor | −37.5 dB | −67.4 dB |
| Speech peak | −29.5 dB | −29.3 dB |
| Usable range | 8.0 dB | 38.1 dB |
Thirty decibels of noise removed for the same speech level. Nearly every audio failure of the morning traces back to that one number, and no amount of clever gating would have substituted for it.
Two models, because one cannot do both
Transcription arriving in twelve-second blocks meant nothing on screen while I was still talking. But the accurate model needs 3.9 seconds to process a 6-second clip — run it on a short step and it falls permanently behind. A smaller model does the same clip in 1.0 second and hears less well.
So both run. The fast one puts words on screen about a second behind my voice;
the accurate one re-transcribes the same audio in longer segments for the text
that actually gets logged. They are deliberately never merged — patching a rough
transcript into an accurate one in place is what produced "33 three" in an
earlier design.
Understanding, not keyword matching
The transcript feeds a 4B model running locally through Ollama. The regex it replaced could match a fixed list of exercise names and pull out bare integers; it could not tell “log triceps pushdown, forty” from “that took about forty seconds”.
Two things the prompt could not be trusted to do, so the code does them. The
model invented an RPE of 5 for a sentence that never mentioned effort, so rpe,
sets and unit now survive only if the utterance actually mentions them — a
prompt rule is a request, this is a guarantee. And it labelled “that was cable
curls at thirty five, got ten” as chatter while returning a full log payload, so
a movement plus a number is now a log regardless of what it calls itself.
Naming my actual equipment in the prompt is what lets “switching to the freak athlete” and “moving over to the Voltra” resolve to real stations instead of guesses.
What this does not do yet
There is no ground truth. Not one rep this system has counted has been verified against a human count. Every number above is a plausible reading, not a measured fact. Until sets are scored against something that actually knows, the accuracy of this thing is unknown — and “it looked right on the screen” is not evidence.
It also cannot name an exercise it was not told about, cannot read load at all, and has not been tested with more than one person in the room. The correction buttons on the dashboard are the first move toward fixing the first problem: every time I tap −1 or +1, I am labelling data, and normal use slowly builds the scored dataset that sitting down to write a session by hand never would.
Next
Ground truth over Bluetooth. This is the one that matters, and it closes the loop on the whole premise. The Voltra already knows position, force and rep boundaries. If I can read that over BLE, then every cable rep I do is simultaneously logged by the machine and watched by the cameras — the same rep, recorded two ways, one of which is right by construction.
That turns the cable machine into a calibration rig. It scores the cameras, continuously, without me writing anything down. And if the cameras can be shown to agree with the Voltra on the movement they can both see, that is the evidence that they can be trusted on the barbell and the dumbbells, which no machine will ever report. The smart machine teaches the room how to watch the dumb ones.
It also solves load outright. No camera will ever read the weight on a bar; the Voltra just knows.
Stations and real counting. Use the fisheye to identify which station I am standing at, hand that to the counter as context, and score rep counts properly per movement instead of trusting one clean set of curls.
Something that talks back. Once the record is trustworthy, the same signals support coaching: tempo drifting inside a set, a session running short against the last one, a movement I have quietly stopped doing. That is where it earns its keep — but it has to be right before it is allowed to have opinions.