Wahoo Kickr Bike - External Gear Display

Yep… that’s possible - just add time for creating the code :grinning:

BòóX

1 Like

Hi all - I came across this thread, and immediately ordered the T-Display S3. Thanks to the projects already loaded up to git, I’ve been successful in tailoring my own version of the display with gear indicator, grade and toggle between Watts and Watts/KG.

Now I’d like to add cadence. It must be in there, but I can’t find any code examples of anyone grabbing it. Does any know how to set up a callback for it?

If someone can let me know the service and characteristic to extend my callback constants, I should be able to figure it out from there. Here is what I currently have:

/* BLE Services --------------------------------------------------------------*/
// Gearing
static BLEUUID
serviceGearingUUID(“a026ee0d-0a7d-4ab3-97fa-f1500f9feb8b”);
static BLEUUID
charGearingUUID(“a026e03a-0a7d-4ab3-97fa-f1500f9feb8b”);

//for grade

static BLEUUID
serviceGradeUUID(“a026ee0b-0a7d-4ab3-97fa-f1500f9feb8b”);
static BLEUUID
charGradeUUID(“a026e037-0a7d-4ab3-97fa-f1500f9feb8b”);

// Power Measurement
static BLEUUID
servicePowerUUID(“00001818-0000-1000-8000-00805f9b34fb”);
static BLEUUID
charPowerUUID(“00002a63-0000-1000-8000-00805f9b34fb”);

Cycling Power Measurement (Power & Cadence)
As mentioned in the GATT Specification Supplement document the Service 0x1818 holds the characteristic 0x2A63. The first UINT16 (unsigned 16-bits integer) holds the FLAGS that describe the characteristic. In our Case “3C00” (as little-endian) and so we get “00000000 00111100”. Section “3.59.2.1 Flags field” in the document tells us which values are submitted:

Pedal Power Balance Present: False
Pedal Power Balance Reference: Unknown
Accumulated Torque Present: True
Accumulated Torque Source: Crank based
Wheel Revolution Data Present: True
Crank Revolution Data Present: True
treme Force Magnitudes Present: False
Extreme Torque Magnitudes Present: False
Extreme Angles Present: False
Top Dead Spot Angle Present: False
Bottom Dead Spot Angle Present: False
Accumulated Energy Present: False
Offset Compensation Indicator : False

So the values in the characteristic are:

From here you can work with the values:

There are some things to consider like cadence drops due to high reading frequency but slow revolutions ect.

I’ve already implemented this to my solution (among other things) and will upload it to my GIT in a couple of days.

1 Like

Thanks so much, I will start playing with this info. Look forward to seeing your code when you post it.

Hi Jay, Can you provide your version?

I’ve updated the GIT. This version is not fully layouted so expect no eye candy …

Code is written for LILYGO T-Display-S3 ESP32-S3 or LILYGO TTGO T-Display ESP32 and displays the following Bluetooth Low Energy (BLE) data:

  • Gearing ratio (as set in the wahoo app) and what gears you are currently in. Information (text and graphic) will update when shifting.
  • Cyclist weight (as set in the wahoo app) - not displayed but used for W7Kg calculation
  • Cycling power (Watt or watts per kilogram). Displayed information can be changed via a button (BOOT) on the device.
  • Cycling cadence (RPM - revolutions per minute)

IMPORTANT: Set the Display Resolution “170 x 320” for LILYGO T-Display-S3 ESP32-S3 or “135 x 240” for LILYGO TTGO T-Display ESP32 via the variables or the graphics will not scale accordingly.

h**ps://github.com/stareat/wahoo-kickr-bike-shift-display

Sure, initial version can be found at:

github . com / ejwheeler3 / WahooKickrDisplayDingus

Remove, the spaces from the URL to access.

Just one file, no graphics or other .h files required. Known bugs and to do list at the top of the file. Displays gear, grade, power, power zone and % of ftp, up to 150%. Uses a sprite to redraw the screen, which eliminates flicker.

A couple of fuzzy photos showing the display dingus in W/KG mode and in Watts mode.


I haven’t tackled cadence yet, will likely borrow your code for that.

Next thing I plan to tackle is power smoothing - likely will use the top button to toggle instant, 1s, 3s average.

1 Like

Hi,

I ported the code to an ESP32 Wemos d1 mini and Wahoo Kickr 5 to use the following services. The basis for this is the code from Stareat. I replaced gear with grades.

// Grade
static BLEUUID serviceUUID1("a026ee0b-0a7d-4ab3-97fa-f1500f9feb8b");
static BLEUUID charUUID11("a026e037-0a7d-4ab3-97fa-f1500f9feb8b");

// Cycling Power Measurement
static BLEUUID serviceUUID2("00001818-0000-1000-8000-00805f9b34fb");
static BLEUUID charUUID21("00002a63-0000-1000-8000-00805f9b34fb");

// Weigt Measurement
static BLEUUID serviceUUID3("0000181c-0000-1000-8000-00805f9b34fb");
static BLEUUID charUUID31("00002a98-0000-1000-8000-00805f9b34fb");

Basically found all 3 services. The weight is determined at the start. When Zwift is active and pedaling, the watt values ​​are displayed. However, the grade values ​​do not come. Apparently only transferred to the Kickr Bike. A test with a BLE scanner also shows no data transfer.

Do you have an idea how I can get grade values? Is there a Wahoo Kickr 5 update? Activation of the service? UDP? Any Idea?

Regards ZwiftRider

1 Like

Hi,

is there documentation for SmartTrainers like the Wahoo Kickr 5 which BLE services are supported and how the received data must be interpreted?

Regards ZwiftRider

1 Like

I think the grade value is the tilt angle of the KICKR BIKE, not the grade that Zwift is simulating.
I believe the device is intercepting the BLE signal from the Wahoo side of things, it would not know what Zwift is sending.

2 Likes

Yes, I believe the data from these BLE services is as the wahoo device sends it to the computer/app. At least with the Kickr bike. With the hubs that you mate to your road bike, I wouldn’t expect grade to be sent, since there is no physical tilt. But that is just a guess.

If you have Zwift set to 100%, I find that the Kickr bike grade and Zwift display agree pretty closely. Rouvy on the other hand seems to display a greater incline than it asks the bike to use when the grade gets to be above 4%. The steeper the grade, the larger the divergence. I need to look more closely at that, but assuming it is true I may add a Rouvy correction scaling, perhaps enabled by the top button.

1 Like

Minor updates to my code pushed to github, mostly around removing serial outputs, which apparently really bog down the performance of the code as the cpu waits on the uart chip.

Also added some dividers and moved the gear indicator to the upper left in prep for future fields on the lower left.

updated code: ht**s://github.com/ejwheeler3/WahooKickrDisplayDingus

3 Likes

For me, it takes several restarts of the T-Display S3 to get it to change from the initial loading screen.
I use a KICKR BIKE SHIFT without the tilt / grade.
Would the device be looking for that signal to determine if it is connected and then move on from the initial loading screen?

Are you using your own code or any of the other implementations?

Amazing work @Jay_Wheeler , thank you so much!
I did some research but unfortunately with the holidays didn’t have more time to investigate further but through the same Tilt advertisement we can get button presses here:

// Top right
if (pData[0] == 0x00 && pData[1] == 0x01) {
Serial.println(“Top Right Pressed”);
}
// Bottom right
if (pData[0] == 0x80 && pData[1] == 0x00) {
Serial.println(“Bottom Right Pressed”);
}
// Side right
if (pData[0] == 0x00 && pData[1] == 0x08) {
Serial.println(“Side Right Pressed”);
}
// Side left
if (pData[0] == 0x20 && pData[1] == 0x00) {
Serial.println(“Side Left Pressed”);
}

Now just need to map it through the Keyboard library to assign Keys.
My idea is to have Previous Song, NEXT Song, Mute sound and a key for Discord chat

Using yours or @Jay_Wheeler’s code.
The result is the same.
I have a smaller LillyGo display board, as seen in post #47 that shows the data right away, every time.
I have to unplug the T-Display S3 several times, letting it sit for 15-20 seconds each time while pedaling and shifting to make sure data is going out, and often it will never show the data.
But when it does, it’s great.

I decided to steal the idea for this display - I like the Kickr bike, but this really improves it. I didn’t really need power, I have other ways of seeing that, so decided to show gearing and then make gradient more visible with an actual hill, that gets sized and colored based on the gradient. Will probably be tweaking a bit more, but today will be about designing and 3D-printing a mount for it :slight_smile:
I based mine off of Jay Wheelers code (but there’s probably not much of it left, I have been an embedded SW developer for more than 25 years, so I like things done my own way :smiley: )

5 Likes

This looks really nice :+1:

BòóX

Very cool, also look forward to seeing your custom case. The lilygo case is kinda pudgy, i suppose they are leaving room for a second circuit board.

I spent a couple hours yesterday and today implementing a side view gear display w basic chain and jockey ring animation. Fun to play w code while cycling, lol.

I’ll update git w this code in the next day or two, have some other minor changes to make first.

Ride on!




2 Likes

I just updated my code in git at this url:

ht tps://github.com/ejwheeler3/WahooKickrDisplayDingus

You can view the code running in “demo” mode on youtube at this url:

ht tps://youtu.be/NAgRoNUSRXU

The new version brings in:

  • side view graphic of gearing, with animation of gear shifts and color coding as you run out of gears
  • ability to toggle between instant, 3 second and 10 second power using the top button
  • ability to toggle between Watts and W/KG using the bottom button
  • ability to change and permanently save your FTP and Weight directly thru the device. Press both buttons at once to enter the weight / ftp input screens. Values entered are stored directly in the device eeprom, along with the settings you have in effect for instant/3s/10s power, and watts vs W/KG.
  • grade readout is now color coded - you’ll need to climb the radio tower or equivalent to see the full range of colors, lol.

3 Likes