Introduction: Zwift Interface for Dumb Turbo Trainer

Like many people, while we were effectively banned from cycling outside during lockdown I dug out my old turbo trainer and gave Zwift a go. The turbo trainer was a relatively obscure make and there was no virtual power profile in Zwift for it so I also ended up getting a power meter. I then found riding at a fixed(ish) resistance unrealistic and difficult and found I was turning up the resistance on climbs so I could pedal harder and turning it down again on the flat.

Then I decided to automate the process and convert my dumb turbo trainer into a slightly less dumb one....

You can use this without a power meter but it will make it very hard work in the game - the resistance will increase as you go uphill but your power will not and you will slow right down!


Main parts

Small parts

  • Stripboard (or perfboard)
  • R-78E3.3-0.5 buck converter (
  • IRLB8721PBF N channel MOSFET (
  • 3x 3mm LEDs (1 red, 2 green)
  • 2x 10uF 25V electrolytic capacitors
  • 3x 10KΩ resistors 0.125W or 0.25W
  • 1x 56Ω resistor 0.125W or 0.25W
  • 2x 12x12 tactile switches (
  • DC barrel jack socket to match the plug on your power supply
  • 2x Microswitches
  • 1x Slide switch
  • 0.1 inch pin sockets (I used DIP sockets cut in half because they have a lower profile than Arduino style sockets)
  • 0.1 inch pin header
  • 1x 5 way DuPont connector housing
  • 1x 9 way DuPont connector housing (or 1x 2 way + 1x 6way)
  • 1x11 way DuPont connector housing (or 1x 3 way + 1x 4way)
  • DuPont crimp pins
  • 5 way multicore cable (6 way is easier to get hold of and can be used)
  • Ribbon cable
  • M2 self tapping screws
  • M4 x 25mm machine screws
  • M3 x ?? machine screws
  • 3mm nuts


  • Screwdrivers or allen keys to disassemble the adjuster on the turbo trainer
  • Soldering iron and solder
  • Crimp tool for DuPont connectors
  • Arduino IDE and a USB cable for programming
  • Optional: multimeter

Step 1: Make a Linear Actuator

The resistance of a turbo trainer varies depending on how much the cable is pulled: the more cable that is pulled the easier the resistance. The linear actuator allows this to be done using a stepper motor which can be controlled precisely.

You will need a stepper motor with a gearbox attached to it because it takes a lot of force to pull the cable. The motor that I used has a 1:27 gear ratio which seems to work well and means that the motor doesn't need to draw too much current.

The frame and the slider for the linear actuator are 3D printed. I made mine in PLA which works fine. The STL files are attached at the bottom of this page. They were modelled in Sketchup which doesn't export to STL as well as other software so you may get some errors in your slicer software about the model not being manifold which you should be able to ignore, but check the GCode looks like the STL model before printing.

The STL files can be downloaded here:

You will need the frame and the slider as a minimum. You may also need some spacers for the motor depending on how far you need to pull the cable on the trainer. On my Ender 3, these all printed fine without supports.

There is also an STL for a plastic case to house the controller box. I printed this with supports for the holes because some were quite long, but you shouldn't need supports for the slide switch holder.


  1. Attach the shaft coupler to the stepper motor
  2. Attach the K08 bearing to one end of the frame using the short 4 screws that come with it (or use your own)
  3. Attach the flanged nut onto the slider using the M3 screws
  4. Screw the lead screw through the flanged nut and the slider
  5. Install the lead screw in the bearing, but don't tighten the set screws
  6. Place any spacers on the face of the motor and attach the lead screw to the shaft coupler but don't tighten the set screws
  7. Bolt the motor to the slider using the M3 machine screws. 4 holes are provided, but you might only be able to get 2 of them in which is fine.
  8. Tighten the set screws in the bearing and on the shaft coupler
  9. Screw the microswitches in place. You might want to drill a pilot hole for the screws in the plastic to get them started

If you have a geared motor, it will be very difficult to turn the lead screw by hand. You can either grip one end with some pliers or grips, or use an allen key in the set screw of the shaft coupler as a lever.

The cable from the turbo trainer the then hooked into the slider, and the outer of the cable is hooked into the holder on the end.

Step 2: Make the Circuit Board and the Cabling

The circuit is based on an ESP32 which includes WiFi, Bluetooth and lots of IO pins. We don't need the WiFi, only the Bluetooth and the IO.

The circuit is built on stripboard/Veroboard but could be built on perfboard if you make the connections yourself. There is a stripboard layout drawing at the top of this page (and schematic) and a guide on working with stripboard here:

Drilling the board

Stripboard requires breaks in the tracks to stop current flowing where it shouldn't, so this is the first thing to do along with drilling the holes for bolts and for the power socket (which has elongated pins). To make the breaks, use a 4 mm drill bit and drill the copper track on the bottom of the board just enough to break it at the locations marked with a X. You don't need to go all the way through.

After making all the breaks, I use a multimeter to check there's no continuity across the break as trying to track down this sort of problem later is a nightmare. I also check that there's no bridging between adjacent tracks (e.g. from any shards of copper) for the same reason.

The DC jack socket has wide pins, so while you have the drill handy use a small drill bit (1.5 mm is about right) to lengthen the holes slightly so the socket fits. The MOSFET (Q1) also has slightly larger legs than the holes allow for, so it's worth drilling these holes slightly bigger too to make it easier to install.

The three mounting holes require a 3 mm drill bit and drill all the way through the board for these if you want to mount it in the case.


You can assemble the board in any order, my suggested sequence is:

  • Install the wire links
  • Install the sockets for the ESP32/A4988 as these act as reference points for everything else. Note that pins 3 and 4 of the ESP32 should not be connected so either don't provide a socket here, or pop the contact out if you're using IC sockets
  • Install the resistors, capacitors and the LED
  • Install the MOSFET and bend it down flat against the board (it doesn't require thermal paste or bolting)
  • Install the R78E33 buck regulator
  • Install the DC jack socket
  • Install the pin headers along the edge of the board

Buttons and microswitches

Repeat the process for the up/down pushbuttons and the small boards for the microswitches.


I used DuPont connectors but you can use Molex or any other 0.1 inch connector the for cables. You will need 4 cables with the pinouts in the PDF at the top of the page. Cable 4 needs to be quite short to fit inside the case (approx 30 mm long).

Your stepper motor may already come with a plug on the end of the cable. Check that the coil arrangements match the details below otherwise your motor will not turn, or turn the wrong way.

Powering up/testing

Before installing the A4988 and ESP32 modules, test the board to make sure it powers up. Connect the power switch to J11, plug in a 12V/1A power supply (with centre pin positive) into the DC jack socket, turn on the switch and the LED on the board should light up.

If something isn't right the most common problems I've found are that a wire link has been missed out or one of the solder connections on the components has been missed. There is a diagram at the top of the page which gives the expected voltages at various pins (all require the power switch to be on except the one marked 12V*).

Step 3: Software and Setup

The ESP32 is programmed using the Arduino IDE. You need to have the ESP32 toolchain installed - instructions are here: You will also need the Neil Kolban's ESP32 BLE Arduino Library and Bodmer's TFT_eSPI library which can be installed from the Arduino IDE.

To upload the code plug the ESP32 into a PC using the USB connection, select the board and virtual COM port in Arduino and click compile/upload. The code is available on Sourceforge here:

I generally prefer using PlatformIO for ESP32 development, but I could not get my code to compile properly in it (there is some issue with the TFT library).

Bluetooth FTMS

Zwift can connect to devices (Heart rate monitors, power meters, cadence sensors etc.) using either Bluetooth or ANT+. The ESP32 only has Bluetooth so I'm using that. Smart trainers (or "controllables" in Zwift) use the Fitness Machine Service (FTMS) over Bluetooth which is similar to the FE-C protocol for ANT+ but it is not particularly well documented. The flowchart explains how FTMS is used in this project with the terrain simulation data from Zwift being used to adjust the trainer resistance.

When testing, I found that Zwift never changed the wind speed or the wind resistance coefficient and the rolling resistance was almost always zero except for a few short sections of mud/sand on the road where it would jump to 255.

I haven't tested what happens when you do a workout and if it tries to automatically enter some sort of ERG mode - probably not with my code because I've only said the resistance can be changed and not set a target power.

Cable installation and limit settings

Before using the controller, remove the cable from your turbo controller and clip it into the slider on the actuator. Using a pair of pliers or grips, turn the lead screw to pull the cable as tight as it will go (you can add spacers to the motor if you need more length) and screw in the adjuster screw on the top of the slider so that it activates the microswitch. It's best to leave a small safety margin because the backlash in the system means the position varies very slightly as it moves back and forth.

Then do the same for the other end of the actuator.

You can set the limit screws however you want if you want to avoid maximum/minimum resistance on the trainer.

Once this is done, plug in the stepper motor and the control buttons.

Startup Sequence

As the linear actuator is quite powerful it includes limit switches to stop the lead screw breaking the frame. These are tested when the controller is powered on:

  • First, the limit switches need to be pressed to check they are plugged in and working (this can be skipped by shorting out jumper JP1 on the main circuit board)
  • Second, the actuator is homed to determine the upper and lower limits of travel

Once this is completed, the controller waits for a connection from Zwift. Instead of connecting to Zwift, you can use the up/down buttons to manually adjust the trainer.

Connecting to Zwift

For some reason in Zwift the controller is initially detected as a power meter not a controllable so to connect, follow these steps:

  • Log in to Zwift
  • Make sure Bluetooth is turned on
  • If your power meter is already detected, click the power meter button to disconnect it
  • Click the power meter button to start searching for devices and choose "ESP32" when it appears in the list
  • This will normally automatically connect the controller as a controllable but if it doesn't then click on "Controllable" to search and choose "ESP32". The screen on the controller should now say that it is connected and start displaying data fro Zwift.
  • Click on the power meter button to disconnect the ESP32 as a power meter, then click it again to search and add your actual power meter
  • Add any other sensors then click "Lets go!"

Once you start a route, the gradient on the controller will automatically adjust to match the terrain in the game and the actuator will adjust the resistance on the trainer. You can adjust how much the resistance changes using the "resistance difficulty" setting in Zwift, at maximum difficulty the gradient on the trainer is approximately the same as in the game although how it "feels" depends on your trainer.

For some reason, the data sent by Zwift is slightly ahead of the gradient displayed in the game - I guess this is because some trainers take a second or two to react to changes.

No data?

Sometimes no data is received by the controller from Zwift, this often happens when you finish a ride and try to start a new one straight away. Turning the controller on and off, and closing Zwift and restarting it (or turning Bluetooth off then on again) usually resolves it.