Hacking a Proform exercise bike. Additional information

Some additional information about the protocol for the Proform exercise bike.
Bytes that change the incline on the proform bike. All incline change commands have a common number of bytes. Only bytes 12-15 change.
Common command:
BA, B4, B1, B0, B6, B0, B0, B0, B1, B0, B0, Byte12, Byte13, Byte14, Byte15, 8D, 8A


Byte12​
Byte13​
Byte14​
Byte15​
Absolute Incline​
B0B0C2B8-15
B0B1C2B7-14.5
B0B2C2B6-14
B0B3C2B5-13.5
B0B4C2B4-13
B0B5C2B3-12.5
B0B6C2B2-12
B0B7C2B1-11.5
B0B8C2B0-11
B0B9C1C6-10.5
B0C1C1C5-10
B0C2C1C4-9.5
B0C3C1C3-9
B0C4C1C2-8.5
B0C5C1C1-8
B0C6C1B9-7.5
B1B0C1B8-7
B1B1C1B7-6.5
B1B2C1B6-6
B1B3C1B5-5.5
B1B4C1B4-5
B1B5C1B3-4.5
B1B6C1B2-4
B1B7C1B1-3.5
B1B8C1B0-3
B1B9B9C6-2.5
B1C1B9C5-2
B1C2B9C4-1.5
B1C3B9C3-1
B1C4B9C2-0.5
B1C5B9C1ZERO LEVEL
B1C6B9B90.5
B2B0B9B81
B2B1B9B71.5
B2B2B9B62
B2B3B9B52.5
B2B4B9B43
B2B5B9B33.5
B2B6B9B24
B2B7B9B14.5
B2B8B9B05
B2B9B8C65.5
B2C1B8C56
B2C2B8C46.5
B2C3B8C37
B2C4B8C27.5
B2C5B8C18
B2C6B8B98.5
B3B0B8B89
B3B1B8B79.5
B3B2B8B610
B3B3B8B510.5
B3B4B8B411
B3B5B8B311.5
B3B6B8B212
B4B7B8B112.5
B5B8B8B013
B6B9B8C613.5
B7C1B7C514
B8C2B7C414.5
B3C3B7C315


Bytes that change the gear on the proform bike. All gear change commands have a common number of bytes. Only bytes 12-15 change.
Common command:
BA, B6, B1, B0, B6, B0, B0, B0, B5, B0, B0, Byte12, Byte13, Byte14, Byte15, 8D, 8A


Byte12Byte13Byte14Byte15Gear
B0C6B8B51
B1B1B8B32
B1B4B8B03
B1B7B7C44
B1C1B7C15
B1C4B7B76
B2B0B7B47
B2B3B7B18
B2B6B6C59
B2B8B6C310
B2C2B6B911
B2C5B6B612
B3B1B6B313
B3B4B6B014
B3B7B5C415
B3C1B5C116
B3C4B5B717
B3C6B5B518
B4B2B5B219
B4B5B4C620
B4B8B4C321
B4C2B4B922

There seem to be some gaps between bytes. In fact, we can fill in the pattern and end up with approximately 60 different gears/resistance levels. I feel that the upper end of the gears could be extended too, although I haven't tried to go beyond the measured pulses.
Here are the filled in gear codes for bytes 12-15.

Byte12 Byte13 Byte14 Byte15 Gear
B0 C6 B8 B5 1
B1 B0 B8 B4 1.5
B1 B1 B8 B3 2
B1 B2 B8 B2 2.3
B1 B3 B8 B1 2.6
B1 B4 B8 B0 3
B1 B5 B7 C6 3.3
B1 B6 B7 C5 3.6
B1 B7 B7 C4 4
B1 B8 B7 C3 4.3
B1 B9 B7 C2 4.6
B1 C1 B7 C1 5
B1 C2 B7 B9 5.3
B1 C3 B7 B8 5.6
B1 C4 B7 B7 6
B1 C5 B7 B6 6.3
B1 C6 B7 B5 6.6
B2 B0 B7 B4 7
B2 B1 B7 B3 7.3
B2 B2 B7 B2 7.6
B2 B3 B7 B1 8
B2 B3 B7 B0 8.3
B2 B4 B6 C6 8.6
B2 B6 B6 C5 9
B2 B7 B6 C4 9.5
B2 B8 B6 C3 10
B2 B9 B6 C2 10.3
B2 C1 B6 C1 10.6
B2 C2 B6 B9 11
B2 C3 B6 B8 11.3
B2 C4 B6 B7 11.6
B2 C5 B6 B6 12
B2 C6 B6 B5 12.3
B3 B0 B6 B4 12.6
B3 B1 B6 B3 13
B3 B2 B6 B2 13.3
B3 B3 B6 B1 13.6
B3 B4 B6 B0 14
B3 B5 B5 C6 14.3
B3 B6 B5 C5 14.6
B3 B7 B5 C4 15
B3 B8 B5 C3 15.3
B3 B9 B5 C2 15.6
B3 C1 B5 C1 16
B3 C2 B5 B9 16.3
B3 C3 B5 B8 16.6
B3 C4 B5 B7 17
B3 C5 B5 B6 17.5
B3 C6 B5 B5 18
B4 B0 B5 B4 18.3
B4 B1 B5 B3 18.6
B4 B2 B5 B2 19
B4 B3 B5 B1 19.3
B4 B4 B5 B0 19.6
B4 B5 B4 C6 20
B4 B6 B4 C5 20.3
B4 B7 B4 C4 20.6
B4 B8 B4 C3 21
B4 B9 B4 C2 21.3
B4 C1 B4 C1 21.6
B4 C2 B4 B9 22

Here is a Fritzing schematic of the Arduino circuit I made to control the bike.
proform_sketch_bb.jpg

View attachment 277452


And the code for the Arduino:

Code:
int incline = 30; //(levels from -15 to +15 by half increments. Index for zero is 30)
int gear = 0;

//expanded gear ranges. Can the resistance go higher?
//is the resistance increase at higher incline due to mechanical adjustment or due to new codes sent.
//                       1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61
byte gearCommand1[] ={0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4};
byte gearCommand2[] ={0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2};
byte gearCommand3[] ={0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4, 0xB4};
byte gearCommand4[] ={0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9};

/*
//these are the bytes for gear changes.
//bytes 12 to 15.  There could be more levels in between these, note the pattern of bytes for incline
//                       1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,   16,   17,   18,   19,   20,   21,   22
byte gearCommand1[] ={0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB4, 0xB4, 0xB4, 0xB4};
byte gearCommand2[] ={0xC6, 0xB1, 0xB4, 0xB7, 0xC1, 0xC4, 0xB0, 0xB3, 0xB6, 0xB8, 0xC2, 0xC5, 0xB1, 0xB4, 0xB7, 0xC1, 0xC4, 0xC6, 0xB2, 0xB5, 0xB8, 0xC2};
byte gearCommand3[] ={0xB8, 0xB8, 0xB8, 0xB7, 0xB7, 0xB7, 0xB7, 0xB7, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB6, 0xB5, 0xB5, 0xB5, 0xB5, 0xB5, 0xB4, 0xB4, 0xB4};
byte gearCommand4[] ={0xB5, 0xB3, 0xB0, 0xC4, 0xC1, 0xB7, 0xB4, 0xB1, 0xC5, 0xC3, 0xB9, 0xB6, 0xB3, 0xB0, 0xC4, 0xC1, 0xB7, 0xB5, 0xB2, 0xC6, 0xC3, 0xB9};
*/
//these are the bytes for incline changes
//note that "0" is number 31 below and changes are 0.5 degree increments on the bike
//                           1,    2,    3,    4,    5,    6,    7,    8,    9,   10,   11,   12,   13,   14,   15,   16,   17,   18,   19,   20,   21,   22,   23,   24,   25,   26,   27,   28,   29,   30,   31,   32,   33,   34,   35,   36,   37,   38,   39,   40,   41,   42,   43,   44,   45,   46,   47,   48,   49,   50,   51,   52,   53,   54,   55,   56,   57,   58,   59,   60,   61
byte inclineCommand1[] = {0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB0, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB1, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB2, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3, 0xB3};
byte inclineCommand2[] = {0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xC1, 0xC2};
byte inclineCommand3[] = {0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC2, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xC1, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB9, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB8, 0xB7, 0xB7, 0xB7};
byte inclineCommand4[] = {0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4, 0xC3, 0xC2, 0xC1, 0xB9, 0xB8, 0xB7, 0xB6, 0xB5, 0xB4, 0xB3, 0xB2, 0xB1, 0xB0, 0xC6, 0xC5, 0xC4};

byte inclineDownButton = 10;
byte inclineUpButton = 11;
byte gearDownButton = 9;
byte gearUpButton =8;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(38400);

  pinMode(inclineDownButton, INPUT_PULLUP); //
  pinMode(inclineUpButton, INPUT_PULLUP); //
  pinMode(gearDownButton, INPUT_PULLUP); //
  pinMode(gearUpButton, INPUT_PULLUP); //
}

void loop() {
  bool gearDown = digitalRead(gearDownButton);
  bool gearUp = digitalRead(gearUpButton);
  bool inclineDown = digitalRead(inclineDownButton);
  bool inclineUp = digitalRead(inclineUpButton);

  if (gearDown==LOW){
    gear = gear - 1;
    if (gear < 0){ gear = 0;}
    Serial.write(0xBA);
    Serial.write(0xB6);
    Serial.write(0xB1);
    Serial.write(0xB0);
    Serial.write(0xB6);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB5);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(gearCommand1[gear]);
    Serial.write(gearCommand2[gear]);
    Serial.write(gearCommand3[gear]);
    Serial.write(gearCommand4[gear]);
    Serial.write(0x8D);
    Serial.write(0x8A);
    delay(500);
  }
  if (gearUp==LOW){
    gear = gear + 1;
    if (gear > 21){ gear = 21;}
    Serial.write(0xBA);
    Serial.write(0xB6);
    Serial.write(0xB1);
    Serial.write(0xB0);
    Serial.write(0xB6);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB5);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(gearCommand1[gear]);
    Serial.write(gearCommand2[gear]);
    Serial.write(gearCommand3[gear]);
    Serial.write(gearCommand4[gear]);
    Serial.write(0x8D);
    Serial.write(0x8A);
    delay(500);
  }
  if (inclineDown==LOW){
    incline = incline - 1;
    if (incline < 0){ incline = 0;}
    Serial.write(0xBA);
    Serial.write(0xB4);
    Serial.write(0xB1);
    Serial.write(0xB0);
    Serial.write(0xB6);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB1);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(inclineCommand1[incline]);
    Serial.write(inclineCommand2[incline]);
    Serial.write(inclineCommand3[incline]);
    Serial.write(inclineCommand4[incline]);
    Serial.write(0x8D);
    Serial.write(0x8A);
    delay(500);
  }
  if (inclineUp==LOW){
    incline = incline + 1;
    if (incline > 60){ incline = 60;} //max 60 entries in the incline array
    Serial.write(0xBA);
    Serial.write(0xB4);
    Serial.write(0xB1);
    Serial.write(0xB0);
    Serial.write(0xB6);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(0xB1);
    Serial.write(0xB0);
    Serial.write(0xB0);
    Serial.write(inclineCommand1[incline]);
    Serial.write(inclineCommand2[incline]);
    Serial.write(inclineCommand3[incline]);
    Serial.write(inclineCommand4[incline]);
    Serial.write(0x8D);
    Serial.write(0x8A);
    delay(500);
  }
}
  • Like
Reactions: SunnyWideSky

Blog entry information

Author
kilomotor
Views
605
Last update

More entries in General

Share this entry

Top