From 22cac3e51afbda9d414f0e96d498fd1deaa3627a Mon Sep 17 00:00:00 2001 From: miraicat <280425293+miraicat@users.noreply.github.com> Date: Wed, 29 Apr 2026 23:34:42 +0800 Subject: [PATCH 1/2] feat(ios): derive treadmill speed from Apple Watch cadence in Fake Treadmill mode Adds opt-in setting `applewatch_as_treadmill_speed` (default off) that derives Speed from Apple Watch step cadence inside cadenceFromAppleWatch() when fakedevice_treadmill is also enabled. Reuses cadence_sensor_speed_ratio ("Wheel Ratio") for the multiplier and the existing virtual treadmill / RSC broadcast pipeline. Useful for indoor walking and slow jogging with apps like Kinomap or Zwift when no physical treadmill is connected. Dual-gated behind both new toggles so normal treadmill users are unaffected. When cadence reports exactly zero (user stopped), Speed is also zeroed; a negative cadence (no fresh sample) leaves Speed unchanged. Settings appended at the end of qzsettings and settings.qml per src/CLAUDE.md. --- src/devices/treadmill.cpp | 20 ++++++++++++++++++++ src/qzsettings.cpp | 4 +++- src/qzsettings.h | 9 +++++++++ src/settings.qml | 29 +++++++++++++++++++++++++++++ 4 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/devices/treadmill.cpp b/src/devices/treadmill.cpp index e89c4fc053..65cb493789 100644 --- a/src/devices/treadmill.cpp +++ b/src/devices/treadmill.cpp @@ -612,11 +612,31 @@ bool treadmill::cadenceFromAppleWatch() { .startsWith(QStringLiteral("Disabled"))) { long appleWatchCadence = h.stepCadence(); qDebug() << QStringLiteral("Current Cadence: ") << QString::number(appleWatchCadence); + // Slow-jogging mode: derive Speed from Apple Watch cadence when running on Fake + // Treadmill. Gated on both toggles so this only runs in the explicit Fake Treadmill + // use case and does not affect normal treadmill users by default. + const bool appleWatchTreadmillSpeed = + settings.value(QZSettings::applewatch_as_treadmill_speed, + QZSettings::default_applewatch_as_treadmill_speed).toBool() && + settings.value(QZSettings::fakedevice_treadmill, + QZSettings::default_fakedevice_treadmill).toBool(); if (appleWatchCadence > 0) { evaluateStepCount(); Cadence = appleWatchCadence; + if (appleWatchTreadmillSpeed) { + const double ratio = + settings.value(QZSettings::cadence_sensor_speed_ratio, + QZSettings::default_cadence_sensor_speed_ratio).toDouble(); + Speed = appleWatchCadence * (ratio > 0.0 ? ratio : 0.0); + } return true; } + if (appleWatchCadence == 0 && appleWatchTreadmillSpeed) { + // Cadence reported as exactly zero (user stopped); zero Speed too so the virtual + // treadmill broadcast doesn't ghost the previous value. Negative values are + // treated as "no fresh sample" and leave Speed unchanged. + Speed = 0; + } return false; } #endif diff --git a/src/qzsettings.cpp b/src/qzsettings.cpp index 5a80444d10..9b38d18884 100644 --- a/src/qzsettings.cpp +++ b/src/qzsettings.cpp @@ -1081,9 +1081,10 @@ const QString QZSettings::step_gain = QStringLiteral("step_gain"); const QString QZSettings::proform_carbon_tlx_treadmill = QStringLiteral("proform_carbon_tlx_treadmill"); const QString QZSettings::proform_carbon_tlx_v84_314_treadmill = QStringLiteral("proform_carbon_tlx_v84_314_treadmill"); const QString QZSettings::proform_carbon_tl_PFTL59723_6 = QStringLiteral("proform_carbon_tl_PFTL59723_6"); +const QString QZSettings::applewatch_as_treadmill_speed = QStringLiteral("applewatch_as_treadmill_speed"); -const uint32_t allSettingsCount = 882; +const uint32_t allSettingsCount = 883; QVariant allSettings[allSettingsCount][2] = { {QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles}, @@ -1989,6 +1990,7 @@ QVariant allSettings[allSettingsCount][2] = { {QZSettings::proform_carbon_tlx_treadmill, QZSettings::default_proform_carbon_tlx_treadmill}, {QZSettings::proform_carbon_tlx_v84_314_treadmill, QZSettings::default_proform_carbon_tlx_v84_314_treadmill}, {QZSettings::proform_carbon_tl_PFTL59723_6, QZSettings::default_proform_carbon_tl_PFTL59723_6}, + {QZSettings::applewatch_as_treadmill_speed, QZSettings::default_applewatch_as_treadmill_speed}, }; void QZSettings::qDebugAllSettings(bool showDefaults) { diff --git a/src/qzsettings.h b/src/qzsettings.h index ff4d15a86e..da8b98958b 100644 --- a/src/qzsettings.h +++ b/src/qzsettings.h @@ -2974,6 +2974,15 @@ class QZSettings { static const QString proform_carbon_tl_PFTL59723_6; static constexpr bool default_proform_carbon_tl_PFTL59723_6 = false; + /** + * @brief When enabled together with fakedevice_treadmill, derives treadmill Speed from + * Apple Watch step cadence using cadence_sensor_speed_ratio. Lets users do indoor walking + * or slow jogging without a physical treadmill, broadcasting cadence-driven speed via + * the existing virtual treadmill / RSC pipeline. Default off; iOS-only effect. + */ + static const QString applewatch_as_treadmill_speed; + static constexpr bool default_applewatch_as_treadmill_speed = false; + /** * @brief Write the QSettings values using the constants from this namespace. * @param showDefaults Optionally indicates if the default should be shown with the key. diff --git a/src/settings.qml b/src/settings.qml index ad1c339124..dab4f578bb 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -1313,6 +1313,7 @@ import Qt.labs.platform 1.1 property double power_sensor_speed_inclination_coeff_a: 0.0 property double power_sensor_speed_inclination_coeff_b: 0.0 property bool proform_carbon_tlx_v84_314_treadmill: false + property bool applewatch_as_treadmill_speed: false } @@ -14497,6 +14498,34 @@ import Qt.labs.platform 1.1 color: Material.color(Material.Lime) } + IndicatorOnlySwitch { + id: appleWatchAsTreadmillSpeedDelegate + text: qsTr("Use Apple Watch Cadence for Fake Treadmill Speed") + spacing: 0 + bottomPadding: 0 + topPadding: 0 + rightPadding: 0 + leftPadding: 0 + clip: false + checked: settings.applewatch_as_treadmill_speed + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + onClicked: { settings.applewatch_as_treadmill_speed = checked; window.settings_restart_to_apply = true; } + } + + Label { + text: qsTr("iOS only. For Fake Treadmill mode: when no physical treadmill is connected, derives Speed from Apple Watch step cadence using the Wheel Ratio under Accessories > Cadence Sensor Options. For walking or slow jogging, set Wheel Ratio much lower than the cycling default, usually around 0.025-0.04. Useful with apps like Kinomap or Zwift. Default is off.") + font.bold: true + font.italic: true + font.pixelSize: Qt.application.font.pixelSize - 2 + textFormat: Text.PlainText + wrapMode: Text.WordWrap + verticalAlignment: Text.AlignVCenter + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.fillWidth: true + color: Material.color(Material.Lime) + } + IndicatorOnlySwitch { id: fakeEllipticalDelegate text: qsTr("Fake Elliptical") From 545f56e0a403242500f47591529c25ff026717d5 Mon Sep 17 00:00:00 2001 From: miraicat <280425293+miraicat@users.noreply.github.com> Date: Thu, 30 Apr 2026 19:53:37 +0800 Subject: [PATCH 2/2] docs(ios): correct wheel ratio range in applewatch_as_treadmill_speed help text Real-world testing showed the original 0.025-0.04 range was below typical slow jog speeds at 180 spm cadence (max ~3.6 km/h). Updated to 0.04-0.15 which covers walking through running. --- src/settings.qml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/settings.qml b/src/settings.qml index bbb6a37f9f..18b2a9afd5 100644 --- a/src/settings.qml +++ b/src/settings.qml @@ -14651,7 +14651,7 @@ import Qt.labs.platform 1.1 } Label { - text: qsTr("iOS only. For Fake Treadmill mode: when no physical treadmill is connected, derives Speed from Apple Watch step cadence using the Wheel Ratio under Accessories > Cadence Sensor Options. For walking or slow jogging, set Wheel Ratio much lower than the cycling default, usually around 0.025-0.04. Useful with apps like Kinomap or Zwift. Default is off.") + text: qsTr("iOS only. For Fake Treadmill mode: when no physical treadmill is connected, derives Speed from Apple Watch step cadence using the Wheel Ratio under Accessories > Cadence Sensor Options. The cycling default is far too high for running - try 0.04-0.15 depending on pace, from walking to running, and tune to taste. Useful with apps like Kinomap or Zwift. Default is off.") font.bold: true font.italic: true font.pixelSize: Qt.application.font.pixelSize - 2