mirror of
https://github.com/cagnulein/qdomyos-zwift.git
synced 2026-02-18 00:17:41 +01:00
Compare commits
23 Commits
android36
...
build-1118
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4d0c5a5cb8 | ||
|
|
f4d161867b | ||
|
|
a3654095f7 | ||
|
|
f2bfb0e7b6 | ||
|
|
dbb4b57ee9 | ||
|
|
05e64940c3 | ||
|
|
c4c380bc4a | ||
|
|
d2f8ed8c01 | ||
|
|
60a9d7cb0f | ||
|
|
4c0793c785 | ||
|
|
5fc377f648 | ||
|
|
0d6f207991 | ||
|
|
051f296913 | ||
|
|
45a4d6d0b1 | ||
|
|
d2612ad03f | ||
|
|
6bb4d99f29 | ||
|
|
c3dbce9ea8 | ||
|
|
989315fb5e | ||
|
|
ce3782f80b | ||
|
|
4ee77b392e | ||
|
|
03896d7384 | ||
|
|
9258bf6af2 | ||
|
|
7a0a990eb8 |
83
.github/workflows/main.yml
vendored
83
.github/workflows/main.yml
vendored
@@ -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:
|
||||
|
||||
@@ -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
|
||||
@@ -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
4
src/CLAUDE.md
Normal 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
|
||||
@@ -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"/>
|
||||
|
||||
34
src/android/src/CustomQtActivity.java
Normal file
34
src/android/src/CustomQtActivity.java
Normal 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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(' '));
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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];
|
||||
|
||||
@@ -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},
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user