Skip to content
Go back

I Put an ESP32 in My Stationary Bike

Published:  at  07:00 PM

I often go to great lengths to ensure the hardware I buy doesn’t lock me into proprietary apps. Last year for example, I reverse engineered the Bluetooth protocol for a ham radio I got and wrote an open source library for controlling it.

So when I got myself a stationary bike, I specifically got a “dumb” one. All it has for electronics is a little battery-powered LCD that can cycle between time, RPM, distance, etc. I really didn’t want a fancy bluetooth-enabled thing that would force me to download another bloated app that would require creating a free account on a crappy service they’d constantly try to upsell me on, and then would promptly go out of date in a couple of years.

But after a while, I started wishing the bike could collect analytics so I could track my usage over time. Sigh

My solution? Open up the bike and wire in an ESP32.

Put a bird on it from Portlandia, except with an ESP32

I wired the microcontroller to the bike’s reed switch sensor (which triggers once per wheel rotation to track cadence), designed and 3D-printed an enclosure for the electronics, wrote MicroPython firmware to log rides, and built an Android app to sync my workout data to Google Health Connect. Now I have a smart bike that I can customize to my heart’s content!

In this post, I’ll talk about the first part of this project: adding the ESP32 to the bike. In a future post, I’ll talk about how I put the firmware and app together.

Adding the ESP32

The stationary bike in question is a Flexispot from Amazon (~$230 if you catch it on sale). It has a desk so it’s a nice way to keep moving while working, and has a magnetic resistance system so it runs very quiet.

Here’s what it looks like all opened up with the modifications I made.

Open bike with modifications labeled

In order, we have 1. The ESP32, 2. A USB-C Power Connector, 3. A Patch Into The Reed Switch, and 4. An Indicator LED.

1. The ESP32

I used a (~$5) ESP32 devboard I got on Amazon: ESP32-S3-DevKitC-1-N16R8.

To house it, I used YAPP_Box to design and 3D print a little enclosure for it:

ESP32 enclosure (open)
ESP32 enclosure (closed)

The RCA connectors and circuit on the protoboard are there to connect to the reed switch on the bike and the indicator LED I installed. (See sections 3 and 4 below). RCA connectors were just what I had lying around, I think on future projects I will prefer JST connectors.

2. A USB-C Power Connector

To relay USB-C power to the dev board, I got one of these panel mount USB-C extension cords ($5 on Amazon). I installed it at the base of the bike:

USB-C panel mount extension

The connector works, but the plastic chassis of the bike was a little too thick for it, so it doesn’t work with wider USB-C connectors. For other projects I’ll probably look for other ways to panel-mount USB-C input.

3. A Patch Into The Reed Switch

The line for the reed switch was easy to locate, and I spliced my own cable into it and then heat shrinked everything together. I kept it connected to the built-in LCD indicator on the bike, so that cadence and other info could still be displayed there. I handled level shifting with a few diodes / resistors that hooked up on the proto board in the microcontroller chassis.

Reed switch patch

4. An Indicator LED

Finally, I drilled into the side of the bike to install a little indicator LED I could address in my code to indicate status or error conditions.

Indicator LED

Putting It All Together

Before reassembling the bike, I flashed the ESP32 with the latest version of MicroPython and wrote a simple program to toggle the LED every time the reed switch was triggered:

from machine import Pin, Timer

led = Pin(4, Pin.OUT)
reed = Pin(5, Pin.IN, Pin.PULL_UP)

debounce_timer = Timer(0)
debouncing = False

def debounce_callback(t):
    global debouncing
    debouncing = False

def reed_irq_handler(pin):
    global debouncing
    if not debouncing:
        debouncing = True
        debounce_timer.init(mode=Timer.ONE_SHOT, period=50, callback=debounce_callback)
        led.value(not led.value())

reed.irq(trigger=Pin.IRQ_FALLING, handler=reed_irq_handler)
Putting it all together

…it worked! Not bad for a little weekend project! Now to write a firmware to log my activity sessions, and an app to sync with Google Health Connect. I’ll talk about this in a future post.


Suggest Changes

Previous Post
AI-Coding a Mobile App and Firmware for My Stationary Bike
Next Post
PSA: R Graphics Devices Can Break Snapshot Tests