Skip to content

Commit 3490d6f

Browse files
cagnuleinclaude
andcommitted
feat: add Wahoo KickRun treadmill driver (wahookickruntreadmill)
Implements the proprietary Wahoo KickRun BLE protocol reverse-engineered from HCI snoop traces: - Speed command: 02 [uint24-LE μm/s] 00 [flag] on the write+notify char - Challenge-response: device sends FD E0 01 [code] after paddle push, driver echoes E0 [code] immediately without waiting - Init sequence: capability queries 01 10 12 18 80 86 f0; 86 response carries a 4-byte device ID that is echoed back via cmd 87 - Workout unlock: 19 00 sent once before the first speed command - Telemetry: FF 01 [tgt μm/s LE24] 00 [cur μm/s LE24] ... parsed for speed/distance/kcal Removes KICKR RUN from the generic horizonTreadmill branch so the device gets its own driver instead of falling back to FTMS-only control. NOTE: the physical paddle push on the treadmill is still required for each speed change - it is a hardware safety mechanism that generates the BLE challenge code. QZ responds to the challenge automatically, eliminating the ~6 s delay introduced by the Wahoo app. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 96acc90 commit 3490d6f

5 files changed

Lines changed: 547 additions & 1 deletion

File tree

src/devices/bluetooth.cpp

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1624,6 +1624,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
16241624
emit searchingStop();
16251625
}
16261626
this->signalBluetoothDeviceConnected(lifefitnessTreadmill);
1627+
} else if (b.name().toUpper().startsWith(QStringLiteral("KICKR RUN")) &&
1628+
!wahooKickRunTreadmill && filter) {
1629+
this->setLastBluetoothDevice(b);
1630+
this->stopDiscovery();
1631+
wahooKickRunTreadmill = new wahookickruntreadmill(noWriteResistance, noHeartService);
1632+
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
1633+
stateFileRead();
1634+
#endif
1635+
emit deviceConnected(b);
1636+
connect(wahooKickRunTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
1637+
&bluetooth::connectedAndDiscovered);
1638+
connect(wahooKickRunTreadmill, &wahookickruntreadmill::debug, this, &bluetooth::debug);
1639+
wahooKickRunTreadmill->deviceDiscovered(b);
1640+
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
1641+
emit searchingStop();
1642+
}
1643+
this->signalBluetoothDeviceConnected(wahooKickRunTreadmill);
16271644
} else if ((b.name().toUpper().startsWith(QStringLiteral("HORIZON")) ||
16281645
b.name().toUpper().startsWith(QStringLiteral("HZ_T101-")) ||
16291646
b.name().toUpper().startsWith(QStringLiteral("HZ_7.0AT-")) ||
@@ -1685,7 +1702,6 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
16851702
b.name().toUpper().startsWith(QStringLiteral("FOCUS M3")) ||
16861703
b.name().toUpper().startsWith(QStringLiteral("ANPIUS-")) ||
16871704
b.name().toUpper().startsWith(QStringLiteral("SWALK LITE-")) ||
1688-
b.name().toUpper().startsWith(QStringLiteral("KICKR RUN")) ||
16891705
b.name().toUpper().startsWith(QStringLiteral("SPERAX_RM-01")) ||
16901706
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) ||
16911707
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1

src/devices/bluetooth.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,7 @@
154154
#include "devices/ultrasportbike/ultrasportbike.h"
155155
#include "devices/wahookickrheadwind/wahookickrheadwind.h"
156156
#include "devices/wahookickrsnapbike/wahookickrsnapbike.h"
157+
#include "devices/wahookickruntreadmill/wahookickruntreadmill.h"
157158
#include "devices/yesoulbike/yesoulbike.h"
158159
#include "devices/ypooelliptical/ypooelliptical.h"
159160
#include "devices/ziprotreadmill/ziprotreadmill.h"
@@ -305,6 +306,7 @@ class bluetooth : public QObject, public SignalHandler {
305306
stagesbike *powerBike = nullptr;
306307
ultrasportbike *ultraSportBike = nullptr;
307308
wahookickrsnapbike *wahooKickrSnapBike = nullptr;
309+
wahookickruntreadmill *wahooKickRunTreadmill = nullptr;
308310
ypooelliptical *ypooElliptical = nullptr;
309311
ziprotreadmill *ziproTreadmill = nullptr;
310312
kineticinroadbike *kineticInroadBike = nullptr;

0 commit comments

Comments
 (0)