Ant+ to Bluetooth Low Energy Bridge in Go

As a regular Zwifter, I use a Life Fitness IC5 spinning bike (2020) that broadcasts power and cadence over ANT+.

The Problem

Modern devices only support Bluetooth, requiring an ANT+ bridge such as the North Pole Engineering CABLE or the Viiiiva Heart Rate Strap. Unfortunately, both are discontinued. Also, the European Radio Equipment Directive requires encryption for wireless transmission of personal data and is helping to end ANT+. The CABLE replacements WYUR and CORD from North Pole Engineering are also unavailable.

The Solution

Rather than wait for my CABLE to fail, and having to replace my perfectly working spinning bike because of this, I wrote my own ANT+ to Bluetooth bridge in Go. This runs on Debian with a USB ANT+ dongle. The architecture is straightforward. An Ant module receives ANT+ events and puts them on a Channel from where the Bluetooth module reads them:

PlantUML Syntax:<br />
hide footbox<br />
skinparam sequenceArrowThickness 2<br />
skinparam actorStyle hollow</p>
<p>skinparam arrow {<br />
  FontSize 10<br />
}<br />
skinparam note {<br />
  FontSize 10<br />
}</p>
<p>actor “Trainer” as Trainer<br />
participant “ANT+\nModule” as ANT<br />
participant “Event\nChannel” as Channel<br />
participant “BLE\nModule” as BLE<br />
actor “Zwift” as Zwift</p>
<p>activate ANT<br />
activate Channel<br />
activate BLE</p>
<p>Trainer -> ANT : broadcast\nANT+ raw data<br />
note over ANT: Parse ANT message,\nextract payload</p>
<p>ANT -> Channel : write\n(RPM, Power, Time)<br />
Channel <- BLE : read\n(RPM, Power, Time)<br />
BLE -> BLE: process to\n(NREV, TREV, Power)<br />
BLE -> Zwift: send\n(NREV, TREV,\nPower)<br />

The key challenge: ANT+ broadcasts instantaneous RPM, while Bluetooth requires cumulative revolution counts and a last-revolution timestamp.

Technical Challenges

Three issues required custom solutions:

  • USB reliability: The ANT+ library’s USB handling wasn’t robust enough, so I wrote my own to support hot-plugging the dongle.
  • Recovery from bicycle pauzes: When the bike stops transmitting (e.g. during a break), the bridge now detects 10 seconds of inactivity and reinitializes automatically. Without this it did not recover.
  • Restarts or crashes of the bridge: When the bridge restarts it resets the bluetooth interface. This causes the bike to automatically reconnect. There must be a proper way to do it but life is too short to find out.
  • Crash robustness: A script runs the bridge and simply restarts it when it crashes. Crashes still can occur occasionally although haven’t seen them while cycling.
  • Sleep prevention: Wrapped the bridge in systemd-inhibit to prevent the PC from sleeping mid-ride:
    systemd-inhibit --what=idle:sleep --why="Do not interrupt cycling" $( dirname $0 )/bin/bridge "$@"

Physical challenges

During initial development I was a lot on the bike trying to get the ant+ data to show. After development I was still a lot on the bike.

Conclusion

The bridge has been running reliably across sessions from 30 minutes to over 2 hours. The code is available here and supports ANT+ dongles that identify as “Dynastream Innovations”. It’s not perfect, but it works well — pull requests welcome.

This entry was posted in Biking, Devops/Linux. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *