Zwift Interface for Dumb Turbo Trainer




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.

Be the First to Share


    • Halloween Contest

      Halloween Contest
    • Crayons Challenge

      Crayons Challenge
    • Cheese Challenge

      Cheese Challenge



    5 months ago on Step 3

    Thanks a lot for posting this! I actually got this same idea a while back but I would not have had it in me to figure out the bluetooth stuff. I made a budget version from this, without a screen, cheap aliexpress linear actuator / DC motor and switched the 3d prints for a box I had lying around. Works great, really jazzed about this project!


    8 months ago on Introduction

    Thank you so much Pete for sharing this wonderful project. I used as a base to develope the ble connection of my direct drive smart trainer. Many thanks also to Old Tekkie for discovering the cause of the strange behaviour of Zwift, after the last update... I've been struggling with that the last few days without finding a solution. Again, thank you.


    8 months ago

    Very grateful to Peter for this code to help me connect my exercise bike to Zwift and permit incline control. However, I experienced issues with the Zwift update of December 2021 that killed the incline control. I found the problem is that Zwift now requires a "reset" acknowledgement after the "control" request and prior to the "start/resume" request in the control point. This is easily resolved by adding another test condition to the ESP32 code in the switch statement testing for Op Code Value 0x01 as follows:

    case 0x01: //reset
    //reply with 0x80, 0x01, 0x01 to say OK
    replyDs[1] = rxValue[0];
    pControlPoint->setValue(replyDs, 3);
    Hope this helps anyone else who is surprised by Zwift suddenly acting funny.


    2 years ago

    Nice!!! I've been waiting this for more than a year, since I bought my dumb turbo trainer. What trainer do you use? I have an Elite Power Mag with Misuro B+ to ride with Zwift. I think this could integrate the magnets and put it next to the flywheel to geit it more integrated.


    Reply 2 years ago

    The lenght of my cable is 43mm, I supposed that I have to put the limit switches in this lenght and change in the code 100mm for 43mm. Will this adapt the total gradient to this lenght? I mean, if 1186 steps are 0,1mm of travel and, imagine that it is a 1% slope, in my case should be 4,3%. Anything more to change?

    I'll try it in a few days when I`ll get all the materials.


    Reply 1 year ago

    Did you get around to trying this? Sounds like a great idea!


    Reply 2 years ago

    I originally built it for a Crivit turbo trainer (very cheap and not very good), but then got an Elite Volare trainer which I use it with now.

    Directly turning the magnets would be neater, however there were two problems I came across when trying it:
    * It requires a lot of torque to turn the magnets directly. You might need a very low geared stepper motor with a larger diameter shaft than the one I used
    * This magnetic field means that you need to hold them in position so you would need to have the stepper motor constantly on which might make it hot. On my controller the lead screw stops the magnets turning backwards when the power to the motor is turned off.

    What you might need to do is use a lead screw to turn a gear wheel attached to the magnets. This would give you gearing (for the torque) and hold the magnets in position.


    Reply 2 years ago

    Hi, thanks for your answer, I think is easier than that. I mean to put a similar structure as you design with a carriage in parallel to the flywheel supporting the magnets pretty close to it. See this video ( ) of a Bkool Smart trainer and see the very small stepper who moves theese two big magnets. Is the similar idea that you use, but attaching to the trainer with some 3d printed structured.


    Question 1 year ago

    I made your project and it's wonderful !!!
    I just ask you if you can help me on a small change, I would like to send the cadence data (with relative time 1024/60) but I don't know where to insert them .. I imagine on array "bikeData [19]" where positions 14 and 15 are used for send the power ... Do you have any idea?
    Do we also need to vary something on the feature?
    Thank you so much for your help and for your project


    1 year ago

    Peter - this is fantastic! Thanks so much, I have a fluid trainer, so am working on modifying the code and hardware to move a magnet as MarioS103 suggested. Really excited by this, COVID winter just got a little bit better - thank you!