Compare commits

...

23 Commits

Author SHA1 Message Date
Roberto Viola
4d0c5a5cb8 Update project.pbxproj 2025-07-04 12:03:24 +02:00
Roberto Viola
f4d161867b Add inclination support to lockscreen and Bluetooth device
Introduces setInclination method to the lockscreen class and updates bluetoothdevice to set inclination from external sources. This enhances the data synchronization by including inclination values alongside speed, power, cadence, and steps.
2025-07-04 11:59:08 +02:00
Roberto Viola
a3654095f7 Refactor speed and inclination variables for TCP data
Introduces tcpSpeed and tcpInclination static variables in WatchKitConnection to distinguish TCP-received values from local ones. Updates references in AppDelegate and Connection to use the new variables for TCP data handling.
2025-07-04 11:43:37 +02:00
Roberto Viola
f2bfb0e7b6 Add device UUID to treadmill data sync protocol
Introduces a unique device UUID to messages exchanged between devices to prevent processing echoed treadmill data. Updates both C++ and Swift code to include and check the UUID in message payloads, ensuring that treadmill speed and inclination updates are only processed if the message originates from a different device.
2025-07-04 11:19:49 +02:00
Roberto Viola
dbb4b57ee9 Update faketreadmill.cpp 2025-07-04 11:10:33 +02:00
Roberto Viola
05e64940c3 Add TCP speed and inclination integration for iOS
Introduces methods to access TCP speed and inclination data from iOS via lockscreen and WatchKitConnection. Updates faketreadmill to use these values if available, allowing for dynamic updates from external sources.
2025-07-04 11:03:43 +02:00
Roberto Viola
c4c380bc4a Add treadmill inclination sync between iOS and desktop
Introduces support for sending and receiving treadmill inclination data between the iOS app and the desktop application. Adds an 'inclination' property to WatchKitConnection, updates message formats to include inclination, and refactors TCP message handling to process and apply inclination updates for fake treadmill devices. Also adds code style rules to documentation.
2025-07-04 10:24:28 +02:00
Roberto Viola
d2f8ed8c01 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-07-04 09:49:36 +02:00
Roberto Viola
60a9d7cb0f fixing description in the stagesbike log 2025-07-04 09:49:10 +02:00
Roberto Viola
4c0793c785 Reverting wahoo protocol to 2.17, creating a setting to revert this (#3489)
* reverting to eb540dc579/src/devices/wahookickrsnapbike/wahookickrsnapbike.cpp

* Update wahookickrsnapbike.h

* Update homeform.cpp

* fixing build

* trying to get the right issue

* trying to restore thing

* Update project.pbxproj

* adding the settngs, but need to use the new setting in the wahookickrsnapbike.cpp

* watt gain issue!

* Update wahookickrsnapbike.h

* Update project.pbxproj

* using the new settings (not tested, just to compare on github web)

* trying to improve readability

* cleaning

* splitting the 2 logic in the update. not tested yet

* trying to align the logic

* fixing description

* Update project.pbxproj
2025-07-04 09:39:55 +02:00
Roberto Viola
5fc377f648 Update main.yml 2025-07-03 15:28:29 +02:00
Roberto Viola
0d6f207991 Update main.yml 2025-07-03 14:35:35 +02:00
Roberto Viola
051f296913 Unify app startup wait time in CI workflow
Replaces conditional sleep based on Android API level with a fixed 60-second wait after starting the app. Simplifies the workflow and ensures consistent wait time across all API levels.
2025-07-03 13:02:45 +02:00
Roberto Viola
45a4d6d0b1 Fix shell script syntax in workflow app start step
Replaces multi-line if-else block with single-line commands using backslashes to ensure correct execution in the GitHub Actions workflow when waiting for the app to start based on Android API level.
2025-07-03 12:28:33 +02:00
Roberto Viola
d2612ad03f Improve Android CI app launch and debugging steps
Enhanced the workflow to use a longer wait time for older Android API levels, added more robust process detection for the app, and included additional debugging output such as logcat and package info. Also, logcat outputs are now saved as artifacts for easier analysis.
2025-07-03 11:51:20 +02:00
Roberto Viola
6bb4d99f29 Improve app process check for Android versions
Updated the workflow to use a fallback ps command for compatibility with different Android versions when verifying if the app is running.
2025-07-03 10:15:50 +02:00
Roberto Viola
c3dbce9ea8 Add matrix strategy for Android emulator tests
Introduces a matrix build to run emulator tests across multiple Android API levels and architectures. Updates emulator configuration and artifact naming to reflect the tested Android version, improving test coverage and traceability.
2025-07-03 09:02:40 +02:00
Roberto Viola
989315fb5e fixing android emulator test 2025-07-03 08:59:09 +02:00
Roberto Viola
ce3782f80b Elite Rampa + MyWoosh compatibility
email from M.Carter from 02/07/2025
2025-07-03 08:51:02 +02:00
Roberto Viola
4ee77b392e Update AndroidManifest.xml 2025-07-02 22:01:28 +02:00
Roberto Viola
03896d7384 Update InAppPurchase.java (#3523) 2025-07-02 22:00:23 +02:00
Roberto Viola
9258bf6af2 Update AndroidManifest.xml 2025-07-02 20:45:26 +02:00
Roberto Viola
7a0a990eb8 Add Android 16 API 36 compatibility with WindowInsetsController
- Create CustomQtActivity extending QtActivity for Android 16 support
- Replace deprecated setSystemUiVisibility with WindowInsetsController
- Maintain backward compatibility with older Android versions
- Fix header toolbar visibility issues on Android 16

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-02 20:44:17 +02:00
24 changed files with 520 additions and 146 deletions

View File

@@ -652,6 +652,51 @@ jobs:
android-emulator-test:
runs-on: ubuntu-latest
needs: android-build
strategy:
fail-fast: false
matrix:
api-level: [24, 26, 28, 29, 30, 31, 33, 34, 35, 36]
include:
- api-level: 24
target: default
arch: x86
android-version: "Android 7.0"
- api-level: 26
target: default
arch: x86
android-version: "Android 8.0"
- api-level: 28
target: default
arch: x86
android-version: "Android 9.0"
- api-level: 29
target: default
arch: x86
android-version: "Android 10"
- api-level: 30
target: google_apis
arch: x86
android-version: "Android 11"
- api-level: 31
target: google_apis
arch: x86_64
android-version: "Android 12"
- api-level: 33
target: google_apis
arch: x86_64
android-version: "Android 13"
- api-level: 34
target: google_apis
arch: x86_64
android-version: "Android 14"
- api-level: 35
target: google_apis
arch: x86_64
android-version: "Android 15"
- api-level: 36
target: google_apis
arch: x86_64
android-version: "Android 16"
steps:
- name: Checkout code
@@ -677,12 +722,12 @@ jobs:
java-version: '17'
# Use a smaller emulator configuration
- name: Run tests on emulator
- name: Run tests on emulator (${{ matrix.android-version }})
uses: ReactiveCircus/android-emulator-runner@v2
with:
target: default # Use default instead of Google APIs
arch: x86
api-level: 29
target: ${{ matrix.target }}
arch: ${{ matrix.arch }}
api-level: ${{ matrix.api-level }}
profile: Nexus 6
disable-animations: true
script: |
@@ -705,15 +750,29 @@ jobs:
adb shell pm grant org.cagnulen.qdomyoszwift android.permission.WRITE_EXTERNAL_STORAGE || true
# Start the main activity
adb shell am start -n org.cagnulen.qdomyoszwift/org.qtproject.qt5.android.bindings.QtActivity
adb shell am start -n org.cagnulen.qdomyoszwift/org.cagnulen.qdomyoszwift.CustomQtActivity
# Wait for app to start
sleep 40
sleep 60
# Verify the app is running
echo "Checking if app is running..."
adb shell "ps -A" > process_list.txt
# Use different ps commands for different Android versions
adb shell "ps -A 2>/dev/null || ps" > process_list.txt
# Debug: show all processes to understand the format
echo "=== All running processes ==="
cat process_list.txt | head -20
echo "=== Looking for our app ==="
grep -q "qdomyos" process_list.txt || (echo "App process not found in process list" && echo "TEST FAILED: App process not running" && exit 1)
adb shell pm list packages | grep org.cagnulen.qdomyoszwift
echo "=== Checking app info ==="
adb shell dumpsys package org.cagnulen.qdomyoszwift | grep -A5 -B5 "state"
echo "=== Logcat output for debugging ==="
adb logcat -d | grep -i "qdomyos\|crash\|error\|exception\|fatal" | tail -n 50
echo "=== Full recent logcat ==="
adb logcat -d | tail -n 100
echo "App is running successfully"
# Take a screenshot for verification
@@ -723,17 +782,21 @@ jobs:
# Check if the package is installed
adb shell pm list packages | grep org.cagnulen.qdomyoszwift
# Display logcat output for debugging (just the last 100 lines)
adb logcat -d | grep -i qdomyos | tail -n 100
# Save logcat for debugging
echo "Saving logcat for analysis..."
adb logcat -d > full_logcat.txt
adb logcat -d | grep -i qdomyos > qdomyos_logcat.txt
- name: Upload test evidence
if: always()
uses: actions/upload-artifact@v4
with:
name: android-emulator-test-evidence
name: android-emulator-test-evidence-api${{ matrix.api-level }}
path: |
screenshot.png
process_list.txt
full_logcat.txt
qdomyos_logcat.txt
if-no-files-found: warn
ios-build:

View File

@@ -371,4 +371,9 @@ The ProForm 995i implementation serves as the reference example:
## Additional Memories
- When adding a new setting in QML (setting-tiles.qml), you must:
* Add the property at the END of the properties list
* Add the property at the END of the properties list
## Code Style Rules
- All comments MUST be written in English only
- Avoid code duplication - unify repeated logic into shared functions or sections when possible

View File

@@ -4381,7 +4381,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1110;
CURRENT_PROJECT_VERSION = 1118;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4575,7 +4575,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1110;
CURRENT_PROJECT_VERSION = 1118;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4805,7 +4805,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1110;
CURRENT_PROJECT_VERSION = 1118;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4901,7 +4901,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1110;
CURRENT_PROJECT_VERSION = 1118;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4993,7 +4993,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1110;
CURRENT_PROJECT_VERSION = 1118;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5109,7 +5109,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1110;
CURRENT_PROJECT_VERSION = 1118;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

4
src/CLAUDE.md Normal file
View File

@@ -0,0 +1,4 @@
when you add a setting remember:
- you have to add always as the last settings declared in the settings.qml
- if you have to add a setting also on another qml file, you need also to declare it there always putting as the last one
- in the qzsettings.cpp there is a allsettingscount that must be updated if you add a setting

View File

@@ -1,5 +1,5 @@
<?xml version="1.0"?>
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.19.2" android:versionCode="1114" android:installLocation="auto">
<manifest package="org.cagnulen.qdomyoszwift" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:versionName="2.19.2" android:versionCode="1116" android:installLocation="auto">
<!-- The following comment will be replaced upon deployment with default permissions based on the dependencies of the application.
Remove the comment if you do not require these default permissions. -->
<!-- %%INSERT_PERMISSIONS -->
@@ -10,7 +10,7 @@
<supports-screens android:largeScreens="true" android:normalScreens="true" android:anyDensity="true" android:smallScreens="true"/>
<application android:hardwareAccelerated="true" android:debuggable="false" android:name="org.qtproject.qt5.android.bindings.QtApplication" android:label="qdomyos-zwift" android:extractNativeLibs="true" android:icon="@drawable/icon" android:usesCleartextTraffic="true">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.qtproject.qt5.android.bindings.QtActivity" android:label="QZ" android:launchMode="singleTop">
<activity android:theme="@style/Theme.AppCompat" android:exported="true" android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|layoutDirection|locale|fontScale|keyboard|keyboardHidden|navigation|mcc|mnc|density" android:name="org.cagnulen.qdomyoszwift.CustomQtActivity" android:label="QZ" android:launchMode="singleTop">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>

View File

@@ -0,0 +1,34 @@
package org.cagnulen.qdomyoszwift;
import android.os.Build;
import android.os.Bundle;
import android.view.View;
import android.view.WindowInsets;
import android.view.WindowInsetsController;
import org.qtproject.qt5.android.bindings.QtActivity;
public class CustomQtActivity extends QtActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// Handle Android 16 API 36 WindowInsetsController for fullscreen support
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
// Android 11 (API 30) and above - use WindowInsetsController
getWindow().setDecorFitsSystemWindows(false);
WindowInsetsController controller = getWindow().getDecorView().getWindowInsetsController();
if (controller != null) {
controller.hide(WindowInsets.Type.statusBars() | WindowInsets.Type.navigationBars());
controller.setSystemBarsBehavior(WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
}
} else {
// Fallback for older Android versions (API < 30)
getWindow().getDecorView().setSystemUiVisibility(
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_FULLSCREEN |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
);
}
}
}

View File

@@ -82,7 +82,7 @@ import com.android.billingclient.api.QueryProductDetailsResult;
** Add Dependencies below to build.gradle file:
dependencies {
def billing_version = "4.0.0"
def billing_version = "8.0.0"
implementation "com.android.billingclient:billing:$billing_version"
}
@@ -152,18 +152,23 @@ public class InAppPurchase implements PurchasesUpdatedListener
public void onPurchasesUpdated(BillingResult billingResult, List<Purchase> purchases) {
int responseCode = billingResult.getResponseCode();
QLog.d(TAG, "onPurchasesUpdated called. Response code: " + responseCode + ", Debug message: " + billingResult.getDebugMessage());
if (purchases == null) {
QLog.e(TAG, "Purchase failed: Data missing from result (purchases is null)");
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, "Data missing from result");
return;
}
if (billingResult.getResponseCode() == RESULT_OK) {
QLog.d(TAG, "Purchase successful, handling " + purchases.size() + " purchases");
handlePurchase(purchases);
} else if (responseCode == RESULT_USER_CANCELED) {
QLog.d(TAG, "Purchase cancelled by user");
purchaseFailed(purchaseRequestCode, FAILUREREASON_USERCANCELED, "");
} else {
String errorString = getErrorString(responseCode);
QLog.e(TAG, "Purchase failed with error: " + errorString + " (code: " + responseCode + ")");
purchaseFailed(purchaseRequestCode, FAILUREREASON_ERROR, errorString);
}
}
@@ -287,7 +292,7 @@ public class InAppPurchase implements PurchasesUpdatedListener
List<ProductDetails> productDetailsList = productDetailsResult.getProductDetailsList();
if (billingResult.getResponseCode() != RESULT_OK) {
QLog.e(TAG, "Unable to launch Google Play purchase screen");
QLog.e(TAG, "Unable to launch Google Play purchase screen. Response code: " + billingResult.getResponseCode() + ", Debug message: " + billingResult.getDebugMessage());
String errorString = getErrorString(requestCode);
purchaseFailed(requestCode, FAILUREREASON_ERROR, errorString);
return;
@@ -298,9 +303,19 @@ public class InAppPurchase implements PurchasesUpdatedListener
}
ProductDetails productDetails = productDetailsList.get(0);
BillingFlowParams.ProductDetailsParams productDetailsParams = BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails)
.build();
BillingFlowParams.ProductDetailsParams.Builder productDetailsParamsBuilder = BillingFlowParams.ProductDetailsParams.newBuilder()
.setProductDetails(productDetails);
// For subscriptions, we need to set the offer token
if (productDetails.getSubscriptionOfferDetails() != null && !productDetails.getSubscriptionOfferDetails().isEmpty()) {
String offerToken = productDetails.getSubscriptionOfferDetails().get(0).getOfferToken();
QLog.d(TAG, "Setting offer token for subscription: " + offerToken);
productDetailsParamsBuilder.setOfferToken(offerToken);
} else {
QLog.w(TAG, "No subscription offer details found for product: " + identifier);
}
BillingFlowParams.ProductDetailsParams productDetailsParams = productDetailsParamsBuilder.build();
BillingFlowParams purchaseParams = BillingFlowParams.newBuilder()
.setProductDetailsParamsList(java.util.Arrays.asList(productDetailsParams))

View File

@@ -260,6 +260,7 @@ void bluetoothdevice::update_hr_from_external() {
h.setPower(m_watt.value());
h.setCadence(Cadence.value());
h.setSteps(StepCount.value());
h.setInclination(Inclination.value());
Heart = appleWatchHeartRate;
qDebug() << "Current Heart from Apple Watch: " << QString::number(appleWatchHeartRate);
#endif

View File

@@ -11,6 +11,9 @@
#include "keepawakehelper.h"
#include <QLowEnergyConnectionParameters>
#endif
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
#include <chrono>
@@ -85,6 +88,15 @@ void faketreadmill::update() {
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
// Initialize lockscreen for iOS TCP data access
if (!h) {
h = new lockscreen();
h->virtualtreadmill_zwift_ios(false);
}
#endif
#endif
if (!firstStateChanged)
emit connectedAndDiscovered();
firstStateChanged = 1;
@@ -112,6 +124,19 @@ void faketreadmill::update() {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
// Update from TCP data if available
if (h && firstStateChanged) {
double tcpSpeed = h->getTcpSpeed();
double tcpInclination = h->getTcpInclination();
if (tcpSpeed > -100) {
Speed = tcpSpeed;
}
if (tcpInclination > -100) {
Inclination = tcpInclination;
}
}
#endif
#endif
}

View File

@@ -102,6 +102,22 @@ void stagesbike::update() {
// updateDisplay(elapsed);
}
if (requestInclination != -100 || ((VirtualBike() && VirtualBike()->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100)) {
qDebug() << QStringLiteral("writing inclination ") << requestInclination << lastRawRequestedInclinationValue << gears();
if(eliteService != nullptr) {
QByteArray a = setSimulationMode(
lastRawRequestedInclinationValue + gears(), 0.005, 0.5, 0.0, 1.0); // since this bike doesn't have the concept of resistance,
// i'm using the gears in the inclination
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(eliteService, eliteWriteCharacteristic, b, a.length(), "forceInclination", false, false);
requestInclination = -100; // reset the requestInclination to -100 so that it doesn't get written again
requestResistance = -1; // reset the requestResistance so that it doesn't get written again
}
}
if (requestResistance != -1) {
if (requestResistance > 100) {
requestResistance = 100;
@@ -109,21 +125,6 @@ void stagesbike::update() {
requestResistance = 1;
}
if (requestInclination != -100 || ((VirtualBike() && VirtualBike()->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100)) {
qDebug() << QStringLiteral("writing inclination ") << requestInclination << lastRawRequestedInclinationValue << gears();
if(eliteService != nullptr) {
QByteArray a = setSimulationMode(
lastRawRequestedInclinationValue + gears(), 0.005, 0.5, 0.0, 1.0); // since this bike doesn't have the concept of resistance,
// i'm using the gears in the inclination
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(eliteService, eliteWriteCharacteristic, b, a.length(), "forcePower", false, false);
requestInclination = -100; // reset the requestInclination to -100 so that it doesn't get written again
requestResistance = -1; // reset the requestResistance so that it doesn't get written again
}
}
if (requestResistance != currentResistance().value() || lastGearValue != gears()) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
if(eliteService != nullptr) {
@@ -566,6 +567,10 @@ void stagesbike::stateChanged(QLowEnergyService::ServiceState state) {
// ********************************************************************************************************
}
void stagesbike::inclinationChanged(double grade, double percentage) {
changeInclination(grade, percentage);
}
void stagesbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));

View File

@@ -104,6 +104,8 @@ class stagesbike : public bike {
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void inclinationChanged(double grade, double percentage);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();

View File

@@ -199,9 +199,11 @@ void wahookickrsnapbike::update() {
}
#endif
QSettings settings;
bool wahooWithoutWheelDiameter = settings.value(QZSettings::wahoo_without_wheel_diameter, QZSettings::default_wahoo_without_wheel_diameter).toBool();
if (initRequest) {
lastCommandErgMode = false;
QSettings settings;
QByteArray a = unlockCommand();
uint8_t b[20];
memcpy(b, a.constData(), a.length());
@@ -218,16 +220,18 @@ void wahookickrsnapbike::update() {
}
QThread::msleep(700);
QByteArray d = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears()));
uint8_t e[20];
setGears(settings.value(QZSettings::gears_current_value, QZSettings::default_gears_current_value).toDouble());
memcpy(e, d.constData(), d.length());
writeCharacteristic(e, d.length(), "setWheelCircumference", false, true);
if (!wahooWithoutWheelDiameter) {
QByteArray d = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears()));
uint8_t e[20];
setGears(settings.value(QZSettings::gears_current_value, QZSettings::default_gears_current_value).toDouble());
memcpy(e, d.constData(), d.length());
writeCharacteristic(e, d.length(), "setWheelCircumference", false, true);
}
// required to the SS2K only one time
Resistance = 0;
emit resistanceRead(Resistance.value());
initRequest = false;
initRequest = false;
} else if (
#ifndef Q_OS_IOS
bluetoothDevice.isValid() &&
@@ -264,54 +268,93 @@ void wahookickrsnapbike::update() {
requestResistance = -1;
}
if (KICKR_BIKE) {
if(requestInclination != -100) {
debug("writing inclination request " + QString::number(requestInclination));
inclinationChanged(requestInclination, requestInclination);
Inclination = requestInclination; // the bike is not sending back the inclination?
requestInclination = -100;
}
} else if (requestResistance != -1 && KICKR_BIKE == false) {
if (requestResistance > 100) {
requestResistance = 100;
} else if (requestResistance == 0) {
requestResistance = 1;
if (!wahooWithoutWheelDiameter) {
if (KICKR_BIKE) {
if(requestInclination != -100) {
debug("writing inclination request " + QString::number(requestInclination));
inclinationChanged(requestInclination, requestInclination);
Inclination = requestInclination; // the bike is not sending back the inclination?
requestInclination = -100;
}
} else if (requestResistance != -1 && KICKR_BIKE == false) {
if (requestResistance > 100) {
requestResistance = 100;
} else if (requestResistance == 0) {
requestResistance = 1;
}
auto virtualBike = this->VirtualBike();
if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
lastForcedResistance = requestResistance;
QByteArray a = setResistanceMode(((double)requestResistance) / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
} else if (requestResistance != currentResistance().value() && ((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(lastForcedResistance));
QByteArray a = setResistanceMode(((double)lastForcedResistance) / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
}
requestResistance = -1;
}
auto virtualBike = this->VirtualBike();
if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
lastForcedResistance = requestResistance;
QByteArray a = setResistanceMode(((double)requestResistance) / 100.0);
if (lastGearValue != gears()) {
if(KICKR_SNAP) {
inclinationChanged(lastGrade, lastGrade);
} else {
QByteArray a = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears()));
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setWheelCircumference", false, false);
lastGrade = 999; // to force a change
}
}
}
else {
if (KICKR_BIKE) {
if(requestInclination != -100) {
debug("writing inclination request " + QString::number(requestInclination));
inclinationChanged(requestInclination, requestInclination);
Inclination = requestInclination; // the bike is not sending back the inclination?
requestInclination = -100;
} else if (lastGearValue != gears()) {
inclinationChanged(lastGrade, lastGrade);
}
} else if (requestResistance != -1 && KICKR_BIKE == false) {
if (requestResistance > 100) {
requestResistance = 100;
} else if (requestResistance == 0) {
requestResistance = 1;
}
auto virtualBike = this->VirtualBike();
if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
lastForcedResistance = requestResistance;
QByteArray a = setResistanceMode(((double)requestResistance) / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
} else if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) && lastGearValue != gears()) {
emit debug(QStringLiteral("writing resistance due to gears changed ") + QString::number(lastForcedResistance));
QByteArray a = setResistanceMode(((double)lastForcedResistance + (gears() - lastGearValue)) / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
} else if (requestResistance != currentResistance().value() &&
((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike)) {
emit debug(QStringLiteral("writing resistance ") + QString::number(lastForcedResistance));
QByteArray a = setResistanceMode(((double)lastForcedResistance) / 100.0);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setResistance", false, false);
}
requestResistance = -1;
}
if (lastGearValue != gears()) {
if(KICKR_SNAP) {
inclinationChanged(lastGrade, lastGrade);
} else {
QByteArray a = setWheelCircumference(wheelCircumference::gearsToWheelDiameter(gears()));
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setWheelCircumference", false, false);
lastGrade = 999; // to force a change
} else if (virtualBike && virtualBike->ftmsDeviceConnected() && lastGearValue != gears()) {
inclinationChanged(lastGrade, lastGrade);
}
requestResistance = -1;
}
}
lastGearValue = gears();
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
@@ -802,7 +845,7 @@ void wahookickrsnapbike::serviceScanDone(void) {
QSettings settings;
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
settings.sync();
if(homeform::singleton())
if(homeform::singleton())
homeform::singleton()->setToastRequested("Zwift Hub device found, please restart the app to enjoy virtual gearing!");
return;
}
@@ -823,13 +866,13 @@ void wahookickrsnapbike::error(QLowEnergyController::Error err) {
void wahookickrsnapbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
if (device.name().toUpper().startsWith("WAHOO KICKR")) {
WAHOO_KICKR = true;
qDebug() << "WAHOO KICKR workaround activated";
} else if(device.name().toUpper().startsWith("KICKR BIKE")) {
KICKR_BIKE = true;
qDebug() << "KICKR BIKE workaround activated";
if (device.name().toUpper().startsWith("WAHOO KICKR")) {
WAHOO_KICKR = true;
qDebug() << "WAHOO KICKR workaround activated";
} else if(device.name().toUpper().startsWith("KICKR BIKE")) {
KICKR_BIKE = true;
qDebug() << "KICKR BIKE workaround activated";
} else if(device.name().toUpper().startsWith("KICKR SNAP")) {
KICKR_SNAP = true;
qDebug() << "KICKR SNAP workaround activated";
@@ -915,30 +958,42 @@ void wahookickrsnapbike::controllerStateChanged(QLowEnergyController::Controller
void wahookickrsnapbike::inclinationChanged(double grade, double percentage) {
Q_UNUSED(percentage);
if(lastCommandErgMode) {
lastGrade = grade + 1; // to force a refresh
initRequest = true;
qDebug() << "avoid sending this command, since I have first to restore the setSimGrade";
return;
}
if(lastGrade == grade) {
qDebug() << "grade is already set to " << grade << "skipping";
return;
}
lastGrade = grade;
Inclination = grade;
emit debug(QStringLiteral("writing inclination ") + QString::number(grade));
QSettings settings;
double g = grade;
if(KICKR_SNAP) {
g += gears() * 0.5;
qDebug() << "adding gear offset so " << g;
if (settings.value(QZSettings::wahoo_without_wheel_diameter, QZSettings::default_wahoo_without_wheel_diameter).toBool()) {
lastGrade = grade;
emit debug(QStringLiteral("writing inclination ") + QString::number(grade));
double g = grade;
g += gears();
QByteArray a = setSimGrade(g);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setSimGrade", false, false);
} else {
if(lastCommandErgMode) {
lastGrade = grade + 1; // to force a refresh
initRequest = true;
qDebug() << "avoid sending this command, since I have first to restore the setSimGrade";
return;
}
if(lastGrade == grade) {
qDebug() << "grade is already set to " << grade << "skipping";
return;
}
lastGrade = grade;
Inclination = grade;
emit debug(QStringLiteral("writing inclination ") + QString::number(grade));
double g = grade;
if(KICKR_SNAP) {
g += gears() * 0.5;
qDebug() << "adding gear offset so " << g;
}
QByteArray a = setSimGrade(g);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setSimGrade", false, false);
lastCommandErgMode = false;
}
QByteArray a = setSimGrade(g);
uint8_t b[20];
memcpy(b, a.constData(), a.length());
writeCharacteristic(b, a.length(), "setSimGrade", false, false);
lastCommandErgMode = false;
}
bool wahookickrsnapbike::inclinationAvailableByHardware() {
@@ -946,10 +1001,18 @@ bool wahookickrsnapbike::inclinationAvailableByHardware() {
}
double wahookickrsnapbike::maxGears() {
QSettings settings;
if (settings.value(QZSettings::wahoo_without_wheel_diameter, QZSettings::default_wahoo_without_wheel_diameter).toBool()) {
return bike::maxGears(); // Use base class behavior
}
wheelCircumference::GearTable g;
return g.maxGears;
return g.maxGears; // Use gear table when wheel diameter mode is disabled
}
double wahookickrsnapbike::minGears() {
return 1;
}
QSettings settings;
if (settings.value(QZSettings::wahoo_without_wheel_diameter, QZSettings::default_wahoo_without_wheel_diameter).toBool()) {
return bike::minGears(); // Use base class behavior
}
return 1; // Use gear minimum when wheel diameter mode is disabled
}

View File

@@ -46,6 +46,7 @@ class wahookickrsnapbike : public bike {
double maxGears() override;
double minGears() override;
enum OperationCode : uint8_t {
_unlock = 32,
_setResistanceMode = 64,
@@ -62,7 +63,7 @@ class wahookickrsnapbike : public bike {
// Variabili per iOS (pubbliche per permettere all'implementazione iOS di impostarle)
bool zwift_found = false;
bool wahoo_found = false;
// Wrapper per characteristicChanged che accetta direttamente QBluetoothUuid
void handleCharacteristicValueChanged(const QBluetoothUuid &uuid, const QByteArray &newValue);

View File

@@ -21,6 +21,7 @@ ScrollView {
property int gear_cog_size: 14
property string gear_wheel_size: "700 x 18C"
property real gear_circumference: 2070
property bool wahoo_without_wheel_diameter: false
}
property int selectedCranksetSize: settings.gear_crankset_size
@@ -323,6 +324,38 @@ ScrollView {
spacing: 20
id: chainringColumn
// Wahoo Options
GroupBox {
title: "Wahoo Options"
Layout.fillWidth: true
ColumnLayout {
IndicatorOnlySwitch {
id: wahooWithoutWheelDiameterDelegate
text: qsTr("Without Wheel Diameter Protocol")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.wahoo_without_wheel_diameter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.wahoo_without_wheel_diameter = checked
}
Label {
text: qsTr("Enable this for simplified Wahoo protocol that adds gears directly to grade instead of using wheel diameter changes. Default is false.")
wrapMode: Text.WordWrap
Layout.fillWidth: true
Layout.maximumWidth: chainringColumn.width - 20
font.pixelSize: Qt.application.font.pixelSize - 2
color: Material.accent
}
}
}
// Crankset Size
GroupBox {
title: "Chainring Size"

View File

@@ -38,6 +38,7 @@
#include <QStandardPaths>
#include <QTime>
#include <QUrlQuery>
#include <QUuid>
#include <chrono>
homeform *homeform::m_singleton = 0;
@@ -682,6 +683,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
}
#ifndef Q_OS_IOS
deviceUUID = QUuid::createUuid().toString();
iphone_browser = new QMdnsEngine::Browser(&iphone_server, "_qz_iphone._tcp.local.", &iphone_cache);
QObject::connect(iphone_browser, &QMdnsEngine::Browser::serviceAdded, [](const QMdnsEngine::Service &service) {
@@ -705,16 +707,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
[]() { qDebug() << "iphone_socket connected!"; });
QObject::connect(homeform::singleton()->iphone_socket, &QTcpSocket::readyRead, []() {
QString rec = homeform::singleton()->iphone_socket->readAll();
qDebug() << "iphone_socket received << " << rec;
QStringList fields = rec.split("#");
foreach (QString f, fields) {
if (f.contains("HR")) {
QStringList values = f.split("=");
if (values.length() > 1) {
emit homeform::singleton()->heartRate(values[1].toDouble());
}
}
}
homeform::singleton()->processTcpMessage(rec);
});
homeform::singleton()->iphone_address = address;
@@ -745,16 +738,7 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
[]() { qDebug() << "iphone_socket connected!"; });
QObject::connect(homeform::singleton()->iphone_socket, &QTcpSocket::readyRead, []() {
QString rec = homeform::singleton()->iphone_socket->readAll();
qDebug() << "iphone_socket received << " << rec;
QStringList fields = rec.split("#");
foreach (QString f, fields) {
if (f.contains("HR")) {
QStringList values = f.split("=");
if (values.length() > 1) {
emit homeform::singleton()->heartRate(values[1].toDouble());
}
}
}
homeform::singleton()->processTcpMessage(rec);
});
homeform::singleton()->iphone_address = address;
homeform::singleton()->iphone_socket->connectToHost(
@@ -6967,13 +6951,24 @@ void homeform::update() {
#ifndef Q_OS_IOS
if (iphone_socket && iphone_socket->state() == QAbstractSocket::ConnectedState) {
QString toSend =
"SENDER=PAD#HR=" + QString::number(bluetoothManager->device()->currentHeart().value()) +
"SENDER=PAD#UUID=" + deviceUUID +
"#HR=" + QString::number(bluetoothManager->device()->currentHeart().value()) +
"#KCAL=" + QString::number(bluetoothManager->device()->calories().value()) +
"#BCAD=" + QString::number(bluetoothManager->device()->currentCadence().value()) +
"#SPD=" + QString::number(bluetoothManager->device()->currentSpeed().value()) +
"#PWR=" + QString::number(bluetoothManager->device()->wattsMetric().value()) +
"#CAD=" + QString::number(bluetoothManager->device()->currentCadence().value()) +
"#ODO=" + QString::number(bluetoothManager->device()->odometer()) + "#";
"#ODO=" + QString::number(bluetoothManager->device()->odometer());
// Aggiungi inclinazione se il device connesso è un treadmill
if (bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
treadmill* t = qobject_cast<treadmill*>(bluetoothManager->device());
if (t) {
toSend += "#INCL=" + QString::number(t->currentInclination().value());
}
}
toSend += "#";
int write = iphone_socket->write(toSend.toLocal8Bit(), toSend.length());
qDebug() << "iphone_socket send " << write << toSend;
}
@@ -6998,6 +6993,59 @@ bool homeform::getDevice() {
return this->bluetoothManager->device()->connected();
}
#ifndef Q_OS_IOS
void homeform::processTcpMessage(const QString& message) {
qDebug() << "iphone_socket received << " << message;
QStringList fields = message.split("#");
bool hasTreadmillData = false;
double speed = 0.0;
double incline = 0.0;
QString remoteUUID;
foreach (QString f, fields) {
if (f.contains("HR")) {
QStringList values = f.split("=");
if (values.length() > 1) {
emit heartRate(values[1].toDouble());
}
} else if (f.contains("UUID=")) {
QStringList values = f.split("=");
if (values.length() > 1) {
remoteUUID = values[1];
}
} else if (f.contains("SPD=")) {
QStringList values = f.split("=");
if (values.length() > 1) {
speed = values[1].toDouble();
}
} else if (f.contains("INCL=")) {
QStringList values = f.split("=");
if (values.length() > 1) {
incline = values[1].toDouble();
hasTreadmillData = true;
}
}
}
// Process treadmill data only if UUID is different (avoid echo)
if (hasTreadmillData && !remoteUUID.isEmpty() && remoteUUID != deviceUUID && bluetoothManager) {
bluetooth* bt = bluetoothManager;
if (bt->device()) {
faketreadmill* ft = qobject_cast<faketreadmill*>(bt->device());
if (ft) {
// Update speed and incline of fake treadmill
if (speed > -100) {
ft->changeSpeed(speed);
}
if (incline > -100) {
ft->changeInclination(incline, incline);
}
}
}
}
}
#endif
bool homeform::getLap() {
if (!this->bluetoothManager->device()) {

View File

@@ -823,6 +823,8 @@ class homeform : public QObject {
QTcpSocket *iphone_socket = nullptr;
QMdnsEngine::Service iphone_service;
QHostAddress iphone_address;
void processTcpMessage(const QString& message);
QString deviceUUID;
#endif
public slots:

View File

@@ -55,6 +55,16 @@ var pedometer = CMPedometer()
{
return WatchKitConnection.stepCadence;
}
@objc public func getTcpSpeed() -> Double
{
return WatchKitConnection.tcpSpeed;
}
@objc public func getTcpInclination() -> Double
{
return WatchKitConnection.tcpInclination;
}
@objc public func setSteps(steps: Int) -> Void
{
@@ -128,8 +138,20 @@ var pedometer = CMPedometer()
Server.server?.send(createString(sender: sender))
}
@objc public func setInclination(inclination: Double) -> Void
{
var sender: String
if UIDevice.current.userInterfaceIdiom == .pad {
sender = "PAD"
} else {
sender = "PHONE"
}
WatchKitConnection.inclination = inclination;
Server.server?.send(createString(sender: sender))
}
func createString(sender: String) -> String {
return "SENDER=\(sender)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(WatchKitConnection.kcal)#BCAD=\(WatchKitConnection.cadence)#SPD=\(WatchKitConnection.speed)#PWR=\(WatchKitConnection.power)#CAD=\(WatchKitConnection.stepCadence)#ODO=\(WatchKitConnection.distance)#";
return "SENDER=\(sender)#UUID=\(WatchKitConnection.deviceUUID)#HR=\(WatchKitConnection.currentHeartRate)#KCAL=\(WatchKitConnection.kcal)#BCAD=\(WatchKitConnection.cadence)#SPD=\(WatchKitConnection.speed)#PWR=\(WatchKitConnection.power)#CAD=\(WatchKitConnection.stepCadence)#ODO=\(WatchKitConnection.distance)#INCL=\(WatchKitConnection.inclination)#";
}
@objc func updateHeartRate() {

View File

@@ -112,6 +112,27 @@ class Connection {
if sender?.contains("PAD") ?? false && message.contains("PWR=") {
let pwr : String = message.slice(from: "PWR=", to: "#") ?? ""
WatchKitConnection.power = (Double(pwr) ?? WatchKitConnection.power)
}
if sender?.contains("PAD") ?? false && message.contains("INCL=") {
let incl : String = message.slice(from: "INCL=", to: "#") ?? ""
WatchKitConnection.inclination = (Double(incl) ?? WatchKitConnection.inclination)
}
// Handle treadmill data from any sender except self (avoid echo)
if message.contains("UUID=") {
let remoteUUID = message.slice(from: "UUID=", to: "#") ?? ""
if remoteUUID != WatchKitConnection.deviceUUID {
// Process speed data from remote device
if message.contains("SPD=") {
let spd : String = message.slice(from: "SPD=", to: "#") ?? ""
WatchKitConnection.tcpSpeed = (Double(spd) ?? WatchKitConnection.tcpSpeed)
}
// Process inclination data from remote device
if message.contains("INCL=") {
let incl : String = message.slice(from: "INCL=", to: "#") ?? ""
WatchKitConnection.tcpInclination = (Double(incl) ?? WatchKitConnection.tcpInclination)
}
}
}
}
}

View File

@@ -26,10 +26,14 @@ class WatchKitConnection: NSObject {
static var distance = 0.0
static var stepCadence = 0
static var kcal = 0.0
static var speed = 0.0
static var speed = -100.0
static var power = 0.0
static var cadence = 0.0
static var steps = 0
static var inclination = -100.0
static var deviceUUID = UUID().uuidString
static var tcpSpeed = -100.0
static var tcpInclination = -100.0
private override init() {
super.init()

View File

@@ -15,6 +15,7 @@ class lockscreen {
void setSpeed(double speed);
void setPower(double power);
void setCadence(double cadence);
void setInclination(double inclination);
// virtualbike
void virtualbike_ios();
@@ -97,6 +98,10 @@ class lockscreen {
// Zwift Hub Protobuf
static QByteArray zwift_hub_inclinationCommand(double inclination);
// TCP Data Access
double getTcpSpeed();
double getTcpInclination();
static QByteArray zwift_hub_setGearsCommand(unsigned int gears);
static uint32_t zwift_hub_getPowerFromBuffer(const QByteArray& buffer);
static uint32_t zwift_hub_getCadenceFromBuffer(const QByteArray& buffer);

View File

@@ -89,6 +89,10 @@ void lockscreen::setSpeed(double speed)
{
[h setSpeedWithSpeed:speed];
}
void lockscreen::setInclination(double inclination)
{
[h setInclinationWithInclination:inclination];
}
void lockscreen::virtualbike_ios()
@@ -385,6 +389,14 @@ QByteArray lockscreen::zwift_hub_inclinationCommand(double inclination) {
}
}
double lockscreen::getTcpSpeed() {
return [h getTcpSpeed];
}
double lockscreen::getTcpInclination() {
return [h getTcpInclination];
}
QByteArray lockscreen::zwift_hub_setGearsCommand(unsigned int gears) {
NSError *error = nil;
NSData *command = [ZwiftHubBike setGearCommandWithGears:gears error:&error];

View File

@@ -442,6 +442,7 @@ const QString QZSettings::default_horizon_treadmill_profile_user5 = QStringLiter
const QString QZSettings::nordictrack_gx_2_7 = QStringLiteral("nordictrack_gx_2_7");
const QString QZSettings::rolling_resistance = QStringLiteral("rolling_resistance");
const QString QZSettings::wahoo_rgt_dircon = QStringLiteral("wahoo_rgt_dircon");
const QString QZSettings::wahoo_without_wheel_diameter = QStringLiteral("wahoo_without_wheel_diameter");
const QString QZSettings::tts_description_enabled = QStringLiteral("tts_description_enabled");
const QString QZSettings::tile_preset_resistance_1_enabled = QStringLiteral("tile_preset_resistance_1_enabled");
const QString QZSettings::tile_preset_resistance_1_order = QStringLiteral("tile_preset_resistance_1_order");
@@ -944,7 +945,7 @@ const QString QZSettings::default_inclinationResistancePoints = QStringLiteral("
const QString QZSettings::floatingwindow_type = QStringLiteral("floatingwindow_type");
const uint32_t allSettingsCount = 771;
const uint32_t allSettingsCount = 772;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},

View File

@@ -1343,6 +1343,9 @@ class QZSettings {
static const QString wahoo_rgt_dircon;
static constexpr bool default_wahoo_rgt_dircon = false;
static const QString wahoo_without_wheel_diameter;
static constexpr bool default_wahoo_without_wheel_diameter = false;
static const QString tts_description_enabled;
static constexpr bool default_tts_description_enabled = true;

View File

@@ -489,7 +489,7 @@ import Qt.labs.platform 1.1
property bool eslinker_ypoo: false
// from version 2.11.69
property bool wahoo_rgt_dircon: false
property bool wahoo_rgt_dircon: false
// from version 2.11.73
property bool tts_description_enabled: true
@@ -1158,10 +1158,15 @@ import Qt.labs.platform 1.1
property int tile_heat_time_in_zone_4_order: 71
property bool proform_treadmill_carbon_tls: false
// 2.19.1
property bool proform_treadmill_995i: false
property bool rogue_echo_bike: false
property int fit_file_garmin_device_training_effect_device: 3122
property int fit_file_garmin_device_training_effect_device: 3122
// 2.19.2
property bool tile_hr_time_in_zone_individual_mode: false
property bool wahoo_without_wheel_diameter: false
}
function paddingZeros(text, limit) {