Skip to content

Commit 0f9684b

Browse files
cagnuleinclaude
andauthored
fix: send MOK Fitness S10 Ultra resistance commands to 0xFFF2 instead of FTMS control point (#4853)
The bike NAKs FTMS_SET_TARGET_RESISTANCE_LEVEL sent over the standard FTMS control point (0x2AD9), so auto-resistance never took effect. Its own app writes resistance changes as a single raw command (AF 05 04 <level> AA) to a proprietary characteristic (0xFFF2) instead, confirmed via BLE packet capture and NRF Connect testing (reported in #4844). Co-authored-by: Claude <noreply@anthropic.com>
1 parent a9374b1 commit 0f9684b

2 files changed

Lines changed: 40 additions & 0 deletions

File tree

src/devices/ftmsbike/ftmsbike.cpp

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -304,6 +304,31 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
304304
}
305305
enableManualResistancePowerAdjustment(requestResistance);
306306

307+
if (MOK_FITNESS) {
308+
// The MOK Fitness S10 Ultra NAKs the FTMS control point (0x2AD9) for resistance changes.
309+
// Its own app changes resistance with a single write to 0xFFF2: AF 05 04 <level> AA.
310+
if (requestResistance < 0) {
311+
qDebug() << "Negative resistance detected:" << requestResistance << "using fallback value 1";
312+
requestResistance = 1;
313+
}
314+
if (max_resistance > 0 && requestResistance > max_resistance) {
315+
qDebug() << "Resistance" << requestResistance << "exceeds max_resistance" << max_resistance << "- clamping";
316+
requestResistance = max_resistance;
317+
}
318+
319+
Resistance = requestResistance;
320+
321+
if (gattMokFitnessService && gattWriteCharMokFitnessId.isValid()) {
322+
uint8_t write[] = {0xAF, 0x05, 0x04, (uint8_t)requestResistance, 0xAA};
323+
enqueueWrite(gattMokFitnessService, gattWriteCharMokFitnessId, write, sizeof(write),
324+
QStringLiteral("forceResistance MOK ") + QString::number(requestResistance), false, false,
325+
gattWriteCharMokFitnessId.properties() & QLowEnergyCharacteristic::WriteNoResponse);
326+
} else {
327+
qDebug() << QStringLiteral("MOK Fitness resistance characteristic (0xFFF2) not found");
328+
}
329+
return;
330+
}
331+
307332
QSettings settings;
308333
bool ergModeNotSupported = (requestPower > 0 && !ergModeSupported);
309334
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
@@ -1720,6 +1745,16 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
17201745
zwiftPlayWriteChar = c;
17211746
zwiftPlayService = s;
17221747
}
1748+
1749+
QBluetoothUuid _mokFitnessWriteCharId((quint16)0xFFF2);
1750+
if (MOK_FITNESS &&
1751+
(c.properties() & QLowEnergyCharacteristic::Write ||
1752+
c.properties() & QLowEnergyCharacteristic::WriteNoResponse) &&
1753+
c.uuid() == _mokFitnessWriteCharId) {
1754+
qDebug() << QStringLiteral("MOK Fitness resistance control characteristic found");
1755+
gattWriteCharMokFitnessId = c;
1756+
gattMokFitnessService = s;
1757+
}
17231758
}
17241759
}
17251760
}

src/devices/ftmsbike/ftmsbike.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,11 @@ class ftmsbike : public bike {
131131
QLowEnergyCharacteristic zwiftPlayWriteChar;
132132
QLowEnergyService *zwiftPlayService = nullptr;
133133

134+
// MOK Fitness bikes don't use the FTMS control point (0x2AD9) to change resistance,
135+
// they need a raw command written to a proprietary characteristic (0xFFF2) instead.
136+
QLowEnergyCharacteristic gattWriteCharMokFitnessId;
137+
QLowEnergyService *gattMokFitnessService = nullptr;
138+
134139
uint8_t sec1Update = 0;
135140
QByteArray lastPacket;
136141
QByteArray lastPacketFromFTMS;

0 commit comments

Comments
 (0)