🚀 MY PROJECTS HAVE A NEW HOME!
eladorbach.com

My latest engineering projects, experiments and articles are now published on my new website.

VISIT MY NEW WEBSITE →

You're currently viewing my original blog, which remains available as an archive of earlier work.

Saturday, October 3, 2026

Life Fitness IC6 Indoor Cycle

How i pwn3d my sp1n b1k3 part1 the HW:

I bought a used Life Fitness / ICG IC6 because I liked the bike (and the price), but I didn't like how closed the console was.

Life Fitness ICG IC6 indoor cycle during bench work with an Arduino Leonardo connected
The used IC6 during bench work, with the Arduino Leonardo already earning its keep.

Before opening anything I did what I usually do with unfamiliar hardware: I searched for every useful piece of information I could find. Service and maintenance manuals, FCC filings, regulatory documents, photos, old product pages and anything else that could help me understand what was hiding between the pedals and the display.

By the time I opened my own console I already had a rough picture of the system. The teardown was not a random fishing expedition anymore. I knew what I was looking at and what questions I still needed to answer.

Inside the console

The console is built around a TI AM335x, the same processor family used by the BeagleBone Black, and it boots from an SD card.

IC6 main console PCB showing the SD-card slot
Main console PCB — component side.
IC6 main console PCB component side
Main console PCB — SD card side.

That was good news. It meant this was not some completely mysterious one-off computer. It was an embedded Linux system built around hardware with plenty of public documentation.

Inside the front housing of the IC6 console
Original display assembly and front electronics.
Back of the original IC6 display assembly and front electronics
Inside the front console housing.

The bigger question was still outside the console: where were cadence, resistance and the rest of the bike data actually coming from?

Following the four wires

The bike connects to the console through only four populated wires, so that became the next place to look.

Four-wire harness connecting the IC6 bike to the console
The bike side DB9 connector.
IC6 bike-side DB9 connector
The four wire bike to console harness.

The IC6 is self powered. Turning the pedals wakes the electronics and brings up the power going to the console. For bench measurements I simply had to keep the bike awake by turning the pedals when needed.

Probing the four wires quickly separated them into two groups. One line was the bike-side power supply, one was ground, and the remaining two behaved like a digital two-wire bus.

Multimeter measuring IC6 VIN at about 5.02 volts
SCL — ~4.97 V idle
Multimeter measuring IC6 SDA idling at about 4.96 volts
SDA — ~4.96 V idle
Multimeter measuring IC6 SCL idling at about 4.97 volts
VIN — ~5.02 V

Both communication lines sat close to 5 V when idle. That looked very much like an I2C bus with pull-ups.

I did not need to spend much time worrying about which of the two signal wires was SDA and which was SCL. There were only two possibilities. If they were crossed there was no valid I2C communication; swap them and the bus worked.

At this point the picture was much simpler than when I started:

pedaling
   |
   v
bike electronics wake up
   |
   +---- power ----> console
   |
   +---- I2C  -----> console

Using a Leonardo as the bridge

I wanted a simple way to talk to the bus without involving the original console, so I used the Arduino Leonardo you can see in the opening photo.

The Leonardo was a convenient choice because the ATmega32U4 has native USB and the board uses 5 V logic. I powered the Leonardo from the computer over USB and connected only ground, SDA and SCL to the bike. I did not connect the Leonardo 5 V output to the bike power line.

I also disconnected the original console while doing this. I wanted one master talking to the bike, not two devices trying to control the same I2C bus.

The Leonardo can be programmed as a USB to I2C adapter. One useful project for the ATmega32U4 is I2C-Mega-USB, which lets Linux see the board as an I2C adapter.

That made the next step pleasantly boring.

i2cdetect -l

lists the I2C adapters that Linux can see. After finding the bus number assigned to the Leonardo, I could scan it with:

i2cdetect -y BUS_NUMBER

The bike answered at two addresses:

0x40
0x48

That was the point where the four mystery wires stopped being mystery wires. I now had a powered digital bus, two responding devices and a normal computer interface I could experiment with.

A very small scanner

A full Linux USB-I2C bridge is convenient, but it is not required just to see which addresses answer. The Leonardo can do that by itself and print the result over its normal USB serial connection.

This is a minimal version of that test. It scans once at 50 kHz and reports only the addresses that acknowledge.

#include <Wire.h>

void setup() {
  Serial.begin(115200);

  unsigned long start = millis();
  while (!Serial && millis() - start < 3000) {
  }

  Wire.begin();
  Wire.setClock(50000);

  Serial.println(F("IC6 I2C scan"));

  for (uint8_t address = 0x08; address <= 0x77; address++) {
    Wire.beginTransmission(address);
    uint8_t result = Wire.endTransmission();

    if (result == 0) {
      Serial.print(F("device at 0x"));
      if (address < 0x10) {
        Serial.print('0');
      }
      Serial.println(address, HEX);
    }
  }

  Serial.println(F("done"));
}

void loop() {
}

On this bike the interesting result is:

device at 0x40
device at 0x48

From addresses to useful bike data

Finding 0x40 and 0x48 did not magically tell me what every byte meant. It just gave me two doors to knock on.

From there I could start making controlled reads, change one thing on the bike at a time and watch what changed. Pedal faster. Pedal slower. Move the resistance knob. Let the bike stop. Wake it again. Compare the responses.

That is how the useful pieces started separating from each other: cadence, brake position, power-module state and the other information needed by a replacement console.

I kept the early work read-only wherever possible. There is no reason to start writing calibration or service values while you are still learning what the normal ride data looks like.

The part that belongs on GitHub

This post is the story of how I got from a closed console to a usable bike interface. I do not want to maintain another copy of the complete protocol here.

The current wiring, packet documentation and example readers live in the GitHub repository:

Life Fitness / ICG IC6 I2C interface

That includes the wiring reference, the protocol notes and the current examples.

The original goal has not changed much. I wanted the good mechanical part of the IC6 without being locked into the original console. Once the bike data is available through a simple interface, the display can be an Arduino experiment, an ESP32, a Raspberry Pi, a Jetson or something that does not exist yet.

This is an independent interoperability project and is not affiliated with, authorized by, sponsored by or endorsed by Life Fitness or Indoor Cycling Group. Product and company names are used only to identify compatible hardware.

No comments:

Post a Comment