-
-
Notifications
You must be signed in to change notification settings - Fork 93
FEC over BLE
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
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?).
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
.
...