@@ -98,11 +98,37 @@ void ZVS::serverVSSYNCRXOnWrite(NimBLECharacteristic* pCharacteristic, NimBLECon
9898
9999void ZVS::decodeLegacyZVSRequest (const std::vector<uint8_t >& data) {
100100 // Basic sanity check
101- if (data.empty () || data[0 ] != 0x3F ) {
102- return ; // Not a Zwift VS Legacy request
101+ if (data.size () < 5 ) {
102+ return ; // Not a Zwift VS Legacy request to decode
103+ }
104+ // Accept either Wahoo Kickr or Zwift Hub legacy request
105+ if (data[0 ] != 0x3F && data[0 ] != 0x04 ) {
106+ return ;
107+ }
108+
109+ // Zwift Hub: Set Simulation Parameter
110+ if (data[0 ] == 0x04 && data[1 ] == 0x22 && data[3 ] == 0x10 ) {
111+ float zwiftGrade = 0 .0f ;
112+ // 8-bit grade [04 22 02 10 69]
113+ if (data[2 ] == 0x02 && data.size () >= 5 ) {
114+ uint8_t value = data[4 ];
115+ zwiftGrade = (float )value * 0 .025f ;
116+ }
117+ // 16-bit grade [04 22 03 10 97 01]
118+ if (data[2 ] == 0x03 && data.size () >= 6 ) {
119+ uint16_t value = data[4 ] | (data[5 ] << 8 );
120+ zwiftGrade = (float )value * 0 .0025f ;
121+ }
122+ LOG (" -> Zwift Road Grade: %.2f" , zwiftGrade);
123+ operations->setNewGrade (zwiftGrade); // grade in percentage resolution 0.01
124+ return ;
103125 }
126+ // Zwift Hub done!
127+
128+ // Restrict further decoding to ID (0x3F)
129+ if (data[0 ] != 0x3F ) return ;
130+ // Wahoo Kickr a.o.: Set Simulation Parameter and Set Gear Ratio
104131 size_t offset = 1 ; // Skip request ID (0x3F)
105-
106132 while (offset < data.size ()) {
107133 uint8_t tag = data[offset++];
108134 // Defensive bounds check
0 commit comments