Concept2 on Zwift via Raspberry Pi

Concept2 Rowing on Zwift

N.B. What is written here is for information only. As each and every installation is quite different it will be impossible to provide support. However, if you are interested to take on the challenge this should give you an insight in what is required.

Things you will learn here: Zwift, Zwift mobile, wifi, Bluetooth, Concept2, Linux, Raspberry Pi, Raspbian, Python, PyRow, USB, NodeJS, github, Unix sockets etc.


Zwift is an on-line cycling community. The Zwift software runs on your PC/Mac as you cycle using an indoor trainer and a power meter. The power meter broadcasts your power and cadence and Zwift on the PC/Mac captures this, processes it, and puts you ‘on the road’ with hundreds of other cyclists from around the world. It certainly livens up indoor turbo sessions once you’ve watched all the NetFlix back catalogue.


Your Concept2 rower accurately measures your power output, and each completed rowing stroke gives a strokes per minute measure – which is your cadence.

The Big Idea

Use Concept2 to emulate a bicycle power meter and to broadcast data to Zwift. Zwift will then put you on the virtual road as just another cyclist with, albeit, a very low cadence. Those long hard rowing sessions are no more, instead you have other athletes to compete with, hills to climb and targets / times to chase.

The Implementation

So I did some research and found that this had already been done. Excellent news. I would be up and running (or rowing) in no time. Take a look at Bruno's journal entry on rowing with Waterrower in Zwift.

Interesting stuff, uh? At last, a proper reason to get a Raspberry Pi.

Raspberry Pi

First up I would need a Raspberry Pi. Cheap, powerful computing in a tiny box at a tiny price. I ordered a starter kit and was ready to go. Except that I needed a keyboard, and a mouse. And THEN I was ready to go.

OK, it’s cheap and easy ‘fun’. But with a steep learning curve. The world of Raspbian (Linux) is a whole new world and only by judicious Google searching, and lots of trial and error can you even begin to understand what is going on, how things hang together, and what those cryptic commands and equally cryptic error messages mean.

Rowing with Waterrower in Zwift

Following the excellent notes from the Waterrower implementation I was on my way. The Waterrower is simply another rowing machine so it can’t be too difficult to get things up and running and simply rewrite the Waterrower code to be driven by Concept2 events. Can it?

It can.

It didn't work.

At all.

Back To Basics

Lets have a look at what is required here. At the most basic level the Concept2 with PM5 has Bluetooth capability. It uses that to broadcast data during a session to the Concept2 ErgData app on your iPhone or Android device. Let’s capture that information on the PC/Mac running Zwift and it is done. Simple.

But as with many things in life, it isn't.

The Concept2 PM5 uses some protocol to broadcast the rowing information via Bluetooth. I say ‘some protocol’ because I haven’t looked into what it is. What Zwift is expecting is a device that acts like with a Cycle Power Meter.

What this means is that a Bluetooth device can broadcast to the world informing the world what it is and what it does. Zwift is only interested in devices that state ‘I’m a Cycling Power Meter’. If you want to know more on how this works there is a whole Bluetooth specification on the Cycle Power Profile for you to read.

So, something like this had to be done...

Concept2 and Raspberry Pi emulate a Stages power meter

We need the Concept2 rowing machine plus a Raspberry Pi to pretend to be something like a Stages power meter.

We know that the Concept2 has a USB output which can send live rowing data to a PC running the appropriate software, and we know that the Raspberry Pi has many USB ports. We also know that the Raspberry Pi has a Bluetooth adapter and that Zwift is more than happy to get acquainted with Cycle Power Meters which broadcast over Bluetooth.

The Architecture

Breaking things down into component parts we need to build something that will bring everything together: the Concept2 will send out data via USB. There will be a Raspbian USB driver which understands the low level bits and bytes. That will pass on the data to something which understands what the Concept2 is saying and makes sense of it.

Concept2 Raspberry Pi Zwift architecture

Then we have to ‘do some stuff’: extract the information we need and hand it on to something which understands how to create the right kind of Bluetooth information. There is also a Bluetooth (BLE – Bluetooth Low Energy) Raspbian driver which knows how to get the adapter to broadcast the information packet. Finally Zwift gets the info and you are rowing (riding) along the virtual road.

Concept2 And USB

Looking at the code that had been produced for the Waterrower it was going to be some effort to convert that to be able to make sense of Concept2 data, for three reasons:

- I had no idea of how the Waterrower operated, and wasn’t inclined to learn

- I didn’t (yet) understand how and what Concept2 would send via USB

- I have no experience of NodeJS – the language that the Waterrower code is written in

And then quite by accident I came across PyRow. A vertiable antique in Internet years which has been sitting there, patiently waiting to be used.


PyRow was an amazing find. Like the best of the Internet, someone has already done all the hard work and made it freely available. They have written a suite of code which communicates with USB drivers on Raspbian (Linux) and not only that it specifically understands all the messages that the Concept2 rower sends via USB to whoever might listen.

This is big news. This is a great leap forward.

Except that it is written in Python. I’ve never seen, never mind written, Python before. Time for more learning…

Getting The Concept2 Data

I need two things from Concept2: the power and the stroke count. I’m working on the premise here that a regular cycle power meter broadcasts the power and the stroke count on each full rotation of the chain ring. And then waits until another rotation is complete and broadcasts once more. I’m happy to corrected on this, but it is a fair assumption.

Firing up PyRow and taking a look at how it works it is soon quite obvious how to get the information I need. The trick is to understand when a convenient part of the Concept2 stroke cycle occurs and at that point capture the power and increment the stroke point.

The phases of the Concept2 stroke cycle are:

Wait for min speed -> Wait for acceleration -> Drive -> Dwelling -> Recovery

Concept2 fires out data at a rate of knots (i.e. many times per second) so it is trivial to monitor the current state and do nothing until the state changes to ‘Dwelling’. At that point take the power reading, notch up another stroke and send both pieces of information to the Bluetooth adapter.

Broadcasting The Concept2 Data

It would now be easy to format the information, pass it to the Bluetooth adapter and get it to Zwift.

Except there is one small problem: Python doesn’t do Bluetooth at all well on Linux.

So close, yet so far.

I went back to look at the Waterrower code and saw that Bruno had also done some excellent stand alone work emulating a Bluetooth cycling power device. It can be found here, take a look:

Now this really is something.

Alas, I also don’t know NodeJS, so it was back to school once more to quickly get to grips with the syntax and the structure. But it wasn’t too difficult to get up to speed.

Those paying close attention will see that with the ble-cycling-power files there is a test.js script. And a peek inside there shows a very simple script which creates a Bluetooth device called ‘TestDevice’ and every 249ms it broadcasts a random power value and an incrementing stroke count.

This is broadcasted to anyone who would care to listen. Maybe even Zwift on my PC. Let's run it and see...

From Raspberry Pi To Zwift In A Few Awkward Steps.

When you read Bruno’s article on Waterrower and Zwift - you DID read it, didn't you? - you will know that the Raspberry Pi Bluetooth cannot be monitored by the PC running Zwift. That just doesn’t seem to make any obvious sense but it is what it is.

But when has that stopped anyone from trying?

Fire up the test.js script, and see that it is indeed working and not crashing and burning on the Raspberry pi, and then look to connect to it from the Zwift PC.

The PC scans for Bluetooth devices and does indeed find 'TestDevice'. However when I try to connect nothing happens, and after a few seconds the notification comes that that connection (or pairing) failed.

The same happens on my phone. I see the device but when I attempt to connect it fails.

OK, this might be because the phone and PC are expecting to pair with the device – using a shared PIN. But I can’t do this from the Raspberry Pi as there is no way for it to display a PIN. And we know that it doesn’t work like this for regular bike sensors and all that other fitness kit you’ve got.

More research and I find that there is a Bluetooth connecting option known as 'Just Works' which might involve setting a default known PIN of 000000 within the Bluetooth device and then any device which attempts to connect tries that and auto-connects.

Not as easy as it seems. Where might that 000000 'Just Works' PIN go? I don’t know. Various sites had various suggestions. But nothing worked.

And then one of the serendipitous moments occured.

I had Zwift running on the PC. I was also trying to connect to the Raspberry Pi on my phone. And in the corner of my eye I noticed that the Zwift PC was notifying me that it had found the TestDevice and did I want to connect to it? You bet I did.

It would appear that something like this is going on:

Concept2 to Zwift via Raspberry Pi USB Bluetooth WiFi

The connection from the Raspberry Pi is forwarded seamlessly to the PC, via Bluetooth to the phone and then forwarded again to the PC via WiFi. There is no need to explicitly connect the phone to the Raspberry Pi, it 'Just Works'. It is as though the phone isn’t doing anything, although it is a critical component.

Things are looking up.

From Python To NodeJS

At this point we have good solid communication from the Concept2 to the Raspberry Pi via USB. The Python code will sit there and monitor the state of the Concept2 machine forever, or at least until the Raspberry Pi is switched off.

On the otherside of the Raspberry Pi we have the NodeJS script running which is relentlessly pumping out fake data to Zwift via Bluetooth.

The phone in the middle is keeping quiet, but doing a sterling job.

And the PC is running Zwift, albeit with random data coming in from a ‘bike power meter’.

The last piece of the jigsaw is to get the data from the Python script to the NodeJS script and all would be fine in the world.

Referring to the earlier diagram this is where we have to ‘Do some stuff here’.


The stuff we will do will be sockets.

A socket is simply a mechanism on the Raspberry Pi which enables two processes to communicate. In simplest terms I open a socket on the NodeJS (Bluetooth side) and get the process to sit there and listen.

On the Concept2 / USB side I start up the Python script and get it to connect to the socket that has just been created. Once it has connected it then has one job, and only one job to do:

Get data from Concept2 via USB -> send the data into the socket.

That’s it, and it was pretty simple to add that to the existing code.

On the NodeJS side the script also has one* job, and only one job to do:

Get data from the socket -> send the data (in the correct format) via a Bluetooth broadcast.

And that is it.

Start the NodeJS and Python processes, then start rowing, and before you know it you are mixing it with the cyclists on the virtual tarmac that is Zwift!

*The NodeJS side actually has one other small job to do… if nothing comes in on the socket for four seconds we assume that the Concept2 rowing has stopped, but to keep the Zwift session alive we continue to pump out messages indicating 0W power and the current stroke count.

Concept2 on Zwift via Raspberry Pi

The Beginning, Not The End

That was a not-so-quick hack to get things working. It took many hours of tinkering and frustration to get to this point and it has been worth the blood, sweat and tears.

There are still many things to do to refine the solution:

    - Document the build process from scratch

    - Make the code more user friendly / tidy, and maybe even with error handling!

    - Publish the scripts I’ve written

    - Get the processes to run in user mode – currently I’m running Python and NodeJS as SUDO commands because of ‘access rights’ to USB and Bluetooth adapters. Tsk.

    - Make the whole process more elegant, e.g. start up the scripts as services at boot time so the Raspberry Pi is always ready to go.

    - Shut down the services gracefully – rather than the brutal ctrl-z I’m currently using.

    - Maybe abandon the USB interface and use Bluetooth to receive from PM5 and then broadcast to Zwift.

    - Do a LOT MORE rowing and accumulating those Zwift kilometres.

Comments? Thoughts? Please get in touch via


Stainland Lions @ ChaseTheLadderStainland Lions @ ChaseTheLadder