Skip to content

FEC over BLE

Dimitar Marinov edited this page Feb 5, 2021 · 2 revisions

Message structure

  0  |        1       |   2  |    3    |     4     |     5-11     |  12 |
Sync | Message Length | Type | Channel | Data Page | message data | CRC | 

Example:

//               0 1  2  3   4  5  6   7  8 9  10 11   12   message index
let message = [164,9,78, 5, 25,16,15,103,73,18,96,51, 143],
//                           0  1  2   3  4  5  6  7        payload index
0   Sync                     , 164, 0xA4, MSB first
1   Length                   ,   9,     , the message length is 9 counted from index 3
2   Type                     ,  78, 0x4E, data type is Broadcast
3   Channel                  ,   5,     , ANT+ channel number 5
4   Data page number         ,  25, 0x19, Data Page 25: Specific Trainer/Stationary Bike Data
5   Update event count       ,  16,
6   Instantaneous cadence    ,  15, cadence is 15 rpm
7   Accumulated power LSB    , 103,
8   Accumulated power MSB    ,  73,
9   Instantaneous power LSB  ,  18, 0b00010010, power is 18 Watts      
10  Instantaneous power MSN  ,  96, 0b0000, 0  
10  Trainer status bit field ,  96, 0b0110, flag 1 and 2 are on 
11  Flags Bit Field          ,  51,
11  FE State Bit Field       ,  51,
12  CRC                      , 143, XOR of everything before that  

What comes over FEC2?

By spec we should have:

  • Data page 16 at 2Hz,
  • Data page 25 at 0.8Hz (and minimum once every 5 messages).
  • Data page 80 and 81 every 132 messages

Recorded a 60s session and this is a list with the unique messages that came over.

let messages = [
    [164,9,78,5,25,248,0,19,70,0,96,32,18],  // Data Page 25: Specific Trainer Data
    [164,9,78,5,16,25,216,5,0,0,255,36,233], // Data Page 16: General FE Data

    [164,9,78,5,81,255,3,40,139,182,0,0,94], // Data Page 81: Product Information
    [164,9,78,5,240,0,0,0,0,0,0,0,22],       // Data Pages 240-255: are Manufacturer specific data
    [164,9,78,5,249,0,0,0,218,178,4,0,115],  // Data Pages 240-255: are Manufacturer specific data
];

Most of the session consists of the two Data Pages 25 and 16 repeating as a pair. What we need is the messages with Data Page 25. They contain the power readings. Page 16 has speed and distance the last of which is implemented with 255 rollover (are 32bit values too much?).

How to handle power and status field in data page 25 (using JS)?

Messages with Data Page 25 contain the most important bit of information that the trainer is sending, the power measurement. But it comes coupled with unrelated trainer status field. What we have is 8 bits at index 9 of the message representing the power LSB and bit 0-3 from the 8 bits at the next index 10 representing power MSB with the rest 4 bits being the status flags.

 let dataview = new DataView(new Uint8Array([164,9,78,5,25,12,15,108,72,90,96,51,209]).buffer);

 const powerLSB = dataview.getUint8(9);  // 8bit power LSB
 const powerMSB = dataview.getUint8(10); // 0-3 bit is power MSB, 4-7 bit is status flags

here dataview contains the message. In this case at index 9 we have the value 90 which is the LSB. The next index has the value 96 which is 0b01100000 in binary and has the status flags 0b0110 and the power MSB 0b0000. The full 12 bit value for power is 0b000001011010 resulting in 90 Watts.

function decodePower(powerMSB, powerLSB) {
    return ((powerMSB & 0b00001111) << 8) + (powerLSB);
}

(decodePower(0b01100001, 0b00101100) === 300)
(decodePower(0b01100001, 0b00000000) === 256)
(decodePower(0b01100000, 0b11111111) === 255)
(decodePower(0b01100000, 0b01111000) === 120)
(decodePower(0b01100000, 0b00000001) === 1)

The function decodePower uses a mask 0b00001111 to extract just the power MSB. Next since they are the 4 most significant in a 12 bit value they need to be shifted 8 places. And finally we just add the value of the LSB.

function decoupleStatus(powerMSB) {
    return powerMSB >> 4;
}

(decoupleStatus(0b01100000) === 6)
(decoupleStatus(0b01100001) === 6)
(decoupleStatus(0b01101111) === 6)
(decoupleStatus(0b11100000) === 14)

The function decoupleStatus shifts right 4 positions 0b0110000 to get the 4 bits of the flags in this case 0b0110.

Proposal: 40 bits for Power, Cadence, Speed, Distance

...