The staircase.
A bench that tells the truth, and the ladder it drew.

Four little dev boards on one breadboard: one playing the rider's legs and the drivetrain, two pretending to be bicycle sensors, and one watching the whole thing over Bluetooth. By the small hours it was printing a clean gear ladder — first through seventh and back down — off two independent radios, exactly the way the real bike will. Here's the rig, the three dead ends I walked into, and the moment it worked.

4
dev boards on the bench
7
gears resolved, cleanly
1–2
samples to lock each gear
0
dropped pulses, end to end

The rig

Yesterday's post ended with three rules: two clocks, feed pulses, match the product. Here is what they built.

Ride Generator

One ESP32 plays rider and drivetrain together. A little scriptable physics engine — cadence, gear, shifts, coasting, even a slow rider surge — turns a scripted ride into two streams of pulses: one edge per crank revolution, one edge per wheel revolution.

Sensors ×2

Two identical boards. A single strap pin to ground picks the role: speed or cadence. Each does exactly what a real sensor does and nothing more — timestamp the pulses on its own pin, on its own clock, and advertise a single-function Bluetooth CSC profile.

And the head unit: an ESP32-C6 as the GearSense Central, connecting to both sensors at once and fusing them into a gear.

Three ESP32 boards on a shared breadboard — two LOLIN32 sensor simulators on the left, the ESP32-C6 head unit with display on the right

The rig: two simulator boards and the C6 head unit, all USB-powered from the same bench.

Where the chain lives

The neat part — and it took me a moment to see it — is that the two sensors are never wired to each other. They don't need to be. Real sensors aren't either; the drivetrain is what couples them. So I put the drivetrain in the only place it belongs: the generator's maths. Wheel pulses are simply crank pulses times the current gear ratio. Each wire leaving the generator is just the mechanical input one sensor happens to see — a string of edges, exactly like a magnet sweeping past a reed switch.

the chain, in software
wheel_pulses = crank_pulses × gear_ratio
The coupling lives in the generator. The two sensors stay blissfully ignorant of each other, time their own edges, and the Central rediscovers the gear from nothing but two independent frequencies arriving over the air.

Bring-up, and three honest dead ends

Nothing worked the first time. In order:

The lesson, again Same lesson as yesterday in a different costume: the device's own log is the witness that doesn't lie. I burned an hour on theories a one-minute read of the log would have thrown out. Trust the instrument.

The staircase

And then it just... worked.

# both sensors discovered — connecting both
# sensor 0 subscribed
# sensor 1 subscribed
wheel=2.57Hz crank=1.28Hz  ratio=2.012  ->  G2
wheel=2.82Hz crank=1.29Hz  ratio=2.178  ->  G3
wheel=3.31Hz crank=1.38Hz  ratio=2.403  ->  G4
wheel=3.39Hz crank=1.34Hz  ratio=2.522  ->  G4  (between gears)
wheel=3.69Hz crank=1.41Hz  ratio=2.626  ->  G5
wheel=4.10Hz crank=1.37Hz  ratio=2.990  ->  G6
wheel=4.94Hz crank=1.48Hz  ratio=3.345  ->  G7

The fused ratio walks straight up the cassette — 2.0, 2.18, 2.4, 2.67, 3.0, 3.43 — tracking the generator's climb step for step, and back down again. Two independent Bluetooth sensors, two independent clocks, fused on a third radio, and it simply knows what gear you're in.

The lines I love most are the ones that say (between gears). Every one of them lands on a shift — a ratio caught halfway between two gears, 2.52 sitting between 4th and 5th — and the classifier correctly refuses to commit, holding the last real gear until the ratio settles. That's the hysteresis earning its keep against honest, jittery, one-sample-at-a-time data.

What this proves A gear resolves in a notification or two. The thirty-second problem from yesterday's post is simply gone — not patched, gone — because the bench finally carries the timing signal a real sensor does. The method was never the problem. The mirror was.

On how this gets made

A note worth saying plainly, because theIdea.club runs in the open: the firmware across this whole stretch was written in a string of late-night sessions with Claude. I bring the bike, the instincts, and a healthy supply of bad ideas; it holds the NimBLE API and a steadier memory than mine, and it pushes back when I'm about to do something daft. I couldn't have written this code alone — and it wouldn't exist without the bike and the stubbornness either. That's the collaboration, and I'd rather be honest about it than pretend otherwise.

What comes next

The rig is a real instrument now, and the real sensors are in the mail.

  1. Swap in the Magene S3+ and CYCPLUS C3 sensors as they arrive — two vendors, a free comparison
  2. Stress the detector: coast segments, rapid shifts, the EMI mess of a motor under load
  3. Fold the frequency-ratio classifier into the on-bike Mark III
  4. Watch the staircase appear on a real ride

Comments

Loading…

Leave a comment