Compare commits

...

37 Commits

Author SHA1 Message Date
Roberto Viola
a9cfe24358 Update homeform.h 2025-07-11 12:09:08 +02:00
Roberto Viola
8cc1982dd4 Update homeform.cpp 2025-07-11 09:39:32 +02:00
Roberto Viola
f82e099795 Add GPIO gear control support for Raspberry Pi
Introduces GPIO-based gear shifting functionality using GPIO 17 (up) and GPIO 27 (down) for Raspberry Pi. Adds a worker thread to monitor GPIO pins, emits signals for gear changes, and integrates with the homeform class. Includes new settings key and UI option to enable or disable this feature.
2025-07-11 08:58:47 +02:00
Roberto Viola
705eb57414 SOLE LCR Bike #3226 2025-07-10 08:27:32 +02:00
Roberto Viola
9c446bcaf6 fixing CI 2025-07-10 06:46:20 +02:00
Roberto Viola
86118c04e2 2.20.0 2025-07-09 15:17:26 +02:00
Roberto Viola
081d9d4e24 Peloton vs QZ Rower Pace Targets (Issue #3533) 2025-07-09 11:24:18 +02:00
Roberto Viola
4ffc0867e3 fixing CI 2025-07-09 06:31:57 +02:00
Roberto Viola
3bfecadd1f fixing CI 2025-07-09 06:21:34 +02:00
Roberto Viola
06aa01d755 Update project.pbxproj 2025-07-08 15:38:50 +02:00
Roberto Viola
e432df9f6b Add nordictrack-build CI target (#3531)
* Add nordictrack-build CI target

Added new CI job 'nordictrack-build' that builds Android APK from refs/pull/3478/head branch.
This target uses the same build structure as the existing android-build job but checks out
the Nordic Track gRPC implementation PR instead of master branch.

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

Co-Authored-By: Claude <noreply@anthropic.com>

* Update main.yml

* Update main.yml

---------

Co-authored-by: Claude <noreply@anthropic.com>
2025-07-08 12:21:22 +02:00
Roberto Viola
e3f4384014 Unify inclination step setting for treadmill and bike
Updated the inclination step adjustment in homeform.cpp to use the treadmill_step_incline setting for both treadmills and bikes. Moved the inclination step setting UI in settings.qml to a more general location and clarified its effect on both device types.
2025-07-08 09:42:40 +02:00
Roberto Viola
563ced3de1 2.19.3 2025-07-08 06:06:34 +02:00
Roberto Viola
e48c6525ea Update project.pbxproj 2025-07-08 05:04:28 +02:00
Roberto Viola
ca34e99277 PELOTON fixing pop classes 2025-07-08 05:03:56 +02:00
Roberto Viola
446f5200ba ASCEND S2 BIKE + QDOYMOS / Virtual machine (Issue #3419) 2025-07-06 15:53:11 +02:00
Roberto Viola
edcb7ab359 SPEEDMAGPRO distance fix
Email from Patrick L. 5/7/2025
2025-07-06 15:46:07 +02:00
Roberto Viola
3844808b60 adding SPEEDMAGPRO 2025-07-06 15:31:08 +02:00
Roberto Viola
8e1ddc502f bike losing connection #3528 2025-07-06 15:29:14 +02:00
Roberto Viola
e633f0f671 Nautilus 616
Mail from Leanne 5/7/2025
2025-07-05 21:59:28 +02:00
Roberto Viola
93a38a7b79 Improve token debug messages for Strava, Peloton, Zwift
Replaced raw token output in debug logs with clearer, non-sensitive success messages for Strava, Peloton, and Zwift authentication flows. This enhances log readability and security by avoiding direct token exposure in debug output.
2025-07-04 10:34:18 +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 756 additions and 213 deletions

View File

@@ -647,11 +647,56 @@ jobs:
uses: actions/upload-artifact@v4
with:
name: fdroid-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug.apk
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:
@@ -1469,11 +1532,114 @@ jobs:
name: windows-msvc2022-binary-no-python
path: windows-msvc2022-binary-no-python.zip
if: ${{ ! matrix.config.python }}
nordictrack-build:
# The type of runner that the job will run on
runs-on: ubuntu-22.04
if: github.event_name == 'schedule'
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
- name: Xvfb install and run
run: |
sudo apt-get install -y xvfb
Xvfb -ac ${{ env.DISPLAY }} -screen 0 1280x780x24 &
- name: Checkout PR code
uses: actions/checkout@v3
with:
ref: refs/pull/3478/head
token: ${{ secrets.GITHUB_TOKEN }}
submodules: 'false' # Prima disattiva il checkout automatico dei submodule
- name: Checkout submodules with specific branches
run: |
git submodule init
git submodule update --init --recursive
- name: Fix qmdnsengine submodule
run: |
cd src/qmdnsengine
git fetch
git checkout 602da51dc43c55bd9aa8a83c47ea3594a9b01b98
- name: Install packages required to run QZ inside workflow
run: sudo apt update -y && sudo apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 libqt5networkauth5-dev libqt5websockets5* libxcb-randr0-dev libxcb-xtest0-dev libxcb-xinerama0-dev libxcb-shape0-dev libxcb-xkb-dev
- name: Install Qt Android
uses: jdpurcell/install-qt-action@v5
with:
version: '5.15.0'
host: 'linux'
target: 'android'
arch: 'android'
modules: 'qtcharts qtnetworkauth'
dir: '${{ github.workspace }}/output/android/'
cache: 'true'
cache-key-prefix: 'install-qt-action-android'
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: 'temurin' # See 'Supported distributions' for available options
java-version: '11.0.23+9'
- name: patching qt for bluetooth
run: cp qt-patches/android/5.15.0/jar/*.* ${{ github.workspace }}/output/android/Qt/5.15.0/android/jar/
- name: download 3rd party files for qthttpserver
run: cp qHttpServerBin/5.15.2/headers/* src/qthttpserver/src/3rdparty/http-parser/
- name: Set Android NDK 21 && build
run: |
# Install NDK 21 after GitHub update
# https://github.com/actions/virtual-environments/issues/5595
ANDROID_ROOT="/usr/local/lib/android"
ANDROID_SDK_ROOT="${ANDROID_ROOT}/sdk"
SDKMANAGER="${ANDROID_SDK_ROOT}/cmdline-tools/latest/bin/sdkmanager"
echo "y" | $SDKMANAGER "ndk;21.4.7075529"
export ANDROID_NDK="${ANDROID_SDK_ROOT}/ndk-bundle"
export ANDROID_NDK_ROOT="${ANDROID_NDK}"
cd src
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" >> secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
echo "${{ secrets.cesiumkey }}" >> inner_templates/googlemaps/cesium-key.js
echo "#define LICENSE" >> secret.h
cd ..
ln -sfn $ANDROID_SDK_ROOT/ndk/21.4.7075529 $ANDROID_NDK
rm -rf /usr/local/lib/android/sdk/ndk/25.1.8937393
# QTHTTPSERVER must use the same NDK
cd src/qthttpserver
qmake
make -j8
make install
cd ../..
qmake -spec android-clang 'ANDROID_ABIS=armeabi-v7a arm64-v8a x86 x86_64' 'ANDROID_NDK_ROOT=/usr/local/lib/android/sdk/ndk/21.4.7075529' && make -j4 && make INSTALL_ROOT=${{ github.workspace }}/output/android/ install
cp src/android-qdomyos-zwift-deployment-settings.json src/android-qdomyos-zwift-nordictrack-deployment-settings.json
sed -i '1s|{|{\n "android-extra-libs": "${{ github.workspace }}/android_openssl/no-asm/latest/arm/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/arm64/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86/libssl_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libcrypto_1_1.so,${{ github.workspace }}/android_openssl/no-asm/latest/x86_64/libssl_1_1.so",|' src/android-qdomyos-zwift-nordictrack-deployment-settings.json
sed -i 's/"android-debug"/"android-nordictrack"/g' src/android-qdomyos-zwift-nordictrack-deployment-settings.json
sed -i 's/android-debug\.apk/android-debug-nordictrack.apk/g' src/android-qdomyos-zwift-nordictrack-deployment-settings.json
cat src/android-qdomyos-zwift-nordictrack-deployment-settings.json
- name: Build APK (not usable for production due to unpatched QT library)
run: cd src; androiddeployqt --input android-qdomyos-zwift-nordictrack-deployment-settings.json --output ${{ github.workspace }}/output/android/ --android-platform android-31 --gradle --aab
- name: Archive nordictrack binary
uses: actions/upload-artifact@v4
with:
name: nordictrack-android-trial
path: ${{ github.workspace }}/output/android/build/outputs/apk/debug/android-debug-nordictrack.apk
upload_to_release:
permissions: write-all
runs-on: ubuntu-22.04
if: github.event_name == 'schedule'
#if: github.event_name == 'schedule'
needs: [linux-x86-build, window-msvc2019-build, window-msvc2022-build, ios-build, window-build, android-build, raspberry-pi-build]
steps:
- name: Checkout code
@@ -1506,6 +1672,7 @@ jobs:
## Other Platforms:
- **fdroid-android-trial**: Android build
- **nordictrack-android-trial**: Nordictrack build for iFIT2 Tablets
- **raspberry-pi-binary**: Raspberry Pi build
__Please help us improve QZ by reporting any issues you encounter!__ :wink:
@@ -1517,6 +1684,7 @@ jobs:
windows-msvc2019-ai-server-binary/*
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
fdroid-android-trial/android-debug.apk
nordictrack-android-trial/android-debug-nordictrack.apk
raspberry-pi-binary/qdomyos-zwift-32bit
raspberry-pi-binary/qdomyos-zwift-64bit

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 = 1121;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4463,7 +4463,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.20;
OTHER_CFLAGS = (
"-pipe",
"-g",
@@ -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 = 1121;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4659,7 +4659,7 @@
/Users/cagnulein/Qt/5.15.2/ios/plugins/audio,
"/Users/cagnulein/qdomyos-zwift/src/ios/adb",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.20;
ONLY_ACTIVE_ARCH = YES;
OTHER_CFLAGS = (
"-pipe",
@@ -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 = 1121;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4830,7 +4830,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.20;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -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 = 1121;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4922,7 +4922,7 @@
"@executable_path/../Frameworks",
"@loader_path/../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.20;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -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 = 1121;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -5038,7 +5038,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.20;
MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";
@@ -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 = 1121;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -5150,7 +5150,7 @@
"@executable_path/Frameworks",
"@executable_path/../../Frameworks",
);
MARKETING_VERSION = 2.19;
MARKETING_VERSION = 2.20;
MTL_ENABLE_DEBUG_INFO = NO;
MTL_FAST_MATH = YES;
OTHER_CODE_SIGN_FLAGS = "--generate-entitlement-der";

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.20.0" android:versionCode="1121" 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

@@ -63,6 +63,12 @@ int CharacteristicWriteProcessor2AD9::writeProcess(quint16 uuid, const QByteArra
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)FTMS_START_RESUME);
reply.append((quint8)FTMS_SUCCESS);
} else if (cmd == FTMS_STOP_PAUSE) {
qDebug() << QStringLiteral("stop/pause simulation! ignoring it");
reply.append((quint8)FTMS_RESPONSE_CODE);
reply.append((quint8)FTMS_STOP_PAUSE);
reply.append((quint8)FTMS_SUCCESS);
} else if (cmd == FTMS_REQUEST_CONTROL) {
qDebug() << QStringLiteral("control requested");

View File

@@ -1049,7 +1049,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
}
this->signalBluetoothDeviceConnected(ypooElliptical);
} else if ((b.name().toUpper().startsWith(QStringLiteral("NAUTILUS E")) ||
b.name().toUpper().startsWith(QStringLiteral("NAUTILUS M"))) &&
b.name().toUpper().startsWith(QStringLiteral("NAUTILUS M")) ||
b.name().toUpper().startsWith(QStringLiteral("NAUTILUS 616"))) && // actually this is a bike that uses the same Bluetooth characteristics of the elliptical
!nautilusElliptical && // NAUTILUS E616
filter) {
this->setLastBluetoothDevice(b);
@@ -1698,10 +1699,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("LYDSTO")) ||
(b.name().toUpper().startsWith("CYCLO_")) ||
(b.name().toUpper().startsWith("SL010-")) ||
(b.name().toUpper().startsWith("EXPERT-SX9")) ||
(b.name().toUpper().startsWith("LCR")) ||
(b.name().toUpper().startsWith("EXPERT-SX9")) ||
(b.name().toUpper().startsWith("MRK-S26S-")) ||
(b.name().toUpper().startsWith("ROBX")) ||
(b.name().toUpper().startsWith("SPEEDMAGPRO")) ||
(b.name().toUpper().startsWith("XCX-")) ||
(b.name().toUpper().startsWith("NEO BIKE PLUS ")) ||
(b.name().toUpper().startsWith(QStringLiteral("PM5")) && !b.name().toUpper().endsWith(QStringLiteral("SKI")) && !b.name().toUpper().endsWith(QStringLiteral("ROW"))) ||
@@ -2462,6 +2463,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
keepBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(keepBike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("LCB")) ||
b.name().toUpper().startsWith("LCR") ||
b.name().toUpper().startsWith(QStringLiteral("R92"))) &&
!soleBike && filter) {
this->setLastBluetoothDevice(b);

View File

@@ -980,8 +980,11 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
1000.0;
index += 3;
} else {
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged2ACE.msecsTo(now)));
// Only calculate distance if 2AD2 hasn't already done it recently (within 2000ms)
if (lastRefreshCharacteristicChanged2AD2.msecsTo(now) > 2000) {
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged2ACE.msecsTo(now)));
}
}
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
@@ -1585,6 +1588,12 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if(device.name().toUpper().startsWith(QStringLiteral("THINK X")) || device.name().toUpper().startsWith(QStringLiteral("THINK-"))) {
THINK_X = true;
qDebug() << "THINK X workaround enabled!";
} else if(device.name().toUpper().startsWith(QStringLiteral("WLT8828"))) {
qDebug() << QStringLiteral("WLT8828 found");
WLT8828 = true;
max_resistance = 32;
resistance_lvl_mode = true;
ergModeSupported = false; // this bike doesn't have ERG mode natively
}
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {

View File

@@ -150,6 +150,7 @@ class ftmsbike : public bike {
bool EXPERT_SX9 = false;
bool PM5 = false;
bool THINK_X = false;
bool WLT8828 = false;
int16_t T2_lastGear = 0;

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

@@ -781,9 +781,35 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
QtAndroid::androidContext().object());
#endif
// Initialize GPIO gear worker if enabled
gpioGearsEnabled = settings.value(QZSettings::gpio_gears_enabled, QZSettings::default_gpio_gears_enabled).toBool();
if (gpioGearsEnabled) {
gpioGearWorker = new GPIOGearWorkerThread(this);
connect(gpioGearWorker, &GPIOGearWorkerThread::gearUpPressed, this, &homeform::onGPIOGearUpPressed);
connect(gpioGearWorker, &GPIOGearWorkerThread::gearDownPressed, this, &homeform::onGPIOGearDownPressed);
gpioGearWorker->start();
qDebug() << "GPIO Gear Worker initialized for Raspberry Pi";
}
bluetoothManager->homeformLoaded = true;
}
homeform::~homeform() {
gpx_save_clicked();
fit_save_clicked();
// Cleanup GPIO gear worker
if (gpioGearWorker) {
gpioGearWorker->stop();
gpioGearWorker->quit();
gpioGearWorker->wait(1000);
delete gpioGearWorker;
gpioGearWorker = nullptr;
qDebug() << "GPIO Gear Worker cleaned up";
}
}
#ifdef Q_OS_ANDROID
extern "C" {
JNIEXPORT void JNICALL
@@ -1141,11 +1167,6 @@ void homeform::refresh_bluetooth_devices_clicked() {
bluetoothManager->restart();
}
homeform::~homeform() {
gpx_save_clicked();
fit_save_clicked();
}
void homeform::aboutToQuit() {
qDebug() << "homeform::aboutToQuit()";
@@ -4162,9 +4183,12 @@ void homeform::Plus(const QString &name) {
double perc = ((elliptical *)bluetoothManager->device())->currentInclination().value() + step;
((elliptical *)bluetoothManager->device())->changeInclination(perc, perc);
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
double step =
settings.value(QZSettings::treadmill_step_incline, QZSettings::default_treadmill_step_incline)
.toDouble();
((bike *)bluetoothManager->device())
->changeInclination(((bike *)bluetoothManager->device())->currentInclination().value() + 0.5,
((bike *)bluetoothManager->device())->currentInclination().value() + 0.5);
->changeInclination(((bike *)bluetoothManager->device())->currentInclination().value() + step,
((bike *)bluetoothManager->device())->currentInclination().value() + step);
}
}
} else if (name.contains(QStringLiteral("pid_hr"))) {
@@ -4424,9 +4448,12 @@ void homeform::Minus(const QString &name) {
double perc = ((elliptical *)bluetoothManager->device())->currentInclination().value() - step;
((elliptical *)bluetoothManager->device())->changeInclination(perc, perc);
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
double step =
settings.value(QZSettings::treadmill_step_incline, QZSettings::default_treadmill_step_incline)
.toDouble();
((bike *)bluetoothManager->device())
->changeInclination(((bike *)bluetoothManager->device())->currentInclination().value() - 0.5,
((bike *)bluetoothManager->device())->currentInclination().value() - 0.5);
->changeInclination(((bike *)bluetoothManager->device())->currentInclination().value() - step,
((bike *)bluetoothManager->device())->currentInclination().value() - step);
}
}
} else if (name.contains(QStringLiteral("pid_hr"))) {
@@ -7589,7 +7616,7 @@ void homeform::onStravaGranted() {
settings.setValue(QZSettings::strava_accesstoken, strava->token());
settings.setValue(QZSettings::strava_refreshtoken, strava->refreshToken());
settings.setValue(QZSettings::strava_lastrefresh, QDateTime::currentDateTime());
qDebug() << QStringLiteral("strava authenticathed") << strava->token() << strava->refreshToken();
qDebug() << QStringLiteral("strava authenticated successfully");
strava_refreshtoken();
setGeneralPopupVisible(true);
}
@@ -7627,8 +7654,7 @@ void homeform::replyDataReceived(const QByteArray &v) {
settings.setValue(QZSettings::strava_refreshtoken, jsonResponse[QStringLiteral("refresh_token")]);
settings.setValue(QZSettings::strava_expires, jsonResponse[QStringLiteral("expires_at")]);
qDebug() << jsonResponse[QStringLiteral("access_token")] << jsonResponse[QStringLiteral("refresh_token")]
<< jsonResponse[QStringLiteral("expires_at")];
qDebug() << "Strava tokens received successfully, expires at:" << jsonResponse[QStringLiteral("expires_at")];
QString urlstr = QStringLiteral("https://www.strava.com/oauth/token?");
QUrlQuery params;
@@ -7691,7 +7717,7 @@ void homeform::networkRequestFinished(QNetworkReply *reply) {
settings.setValue(QZSettings::strava_refreshtoken, refresh_token);
settings.setValue(QZSettings::strava_lastrefresh, QDateTime::currentDateTime());
qDebug() << access_token << refresh_token;
qDebug() << "Strava tokens refreshed successfully";
} else {
@@ -8563,3 +8589,99 @@ extern "C" {
}
}
#endif
// GPIO Gear Worker Thread Implementation
GPIOGearWorkerThread::GPIOGearWorkerThread(QObject *parent)
: QThread(parent), m_running(false) {
#if __has_include(<wiringPi.h>)
if (wiringPiSetup() == -1) {
qDebug() << "wiringPiSetup failed for GPIO Gear Worker";
return;
}
// Setup GPIO pins as inputs with pull-up resistors
pinMode(GPIO_GEAR_UP, INPUT);
pinMode(GPIO_GEAR_DOWN, INPUT);
pullUpDnControl(GPIO_GEAR_UP, PUD_UP);
pullUpDnControl(GPIO_GEAR_DOWN, PUD_UP);
qDebug() << "GPIO Gear Worker: Pins" << GPIO_GEAR_UP << "and" << GPIO_GEAR_DOWN << "initialized";
#endif
}
GPIOGearWorkerThread::~GPIOGearWorkerThread() {
stop();
if (isRunning()) {
quit();
wait(1000);
}
}
void GPIOGearWorkerThread::run() {
m_running = true;
m_lastUpState = HIGH;
m_lastDownState = HIGH;
m_lastTriggerTime = QDateTime::currentDateTime().addMSecs(-GPIO_DEBOUNCE_MS);
qDebug() << "GPIO Gear Worker thread started";
while (m_running) {
#if __has_include(<wiringPi.h>)
int upState = digitalRead(GPIO_GEAR_UP);
int downState = digitalRead(GPIO_GEAR_DOWN);
QDateTime currentTime = QDateTime::currentDateTime();
// Check for gear up (GPIO 17) - falling edge (button pressed)
if (m_lastUpState == HIGH && upState == LOW) {
if (m_lastTriggerTime.msecsTo(currentTime) >= GPIO_DEBOUNCE_MS) {
emit gearUpPressed();
m_lastTriggerTime = currentTime;
qDebug() << "GPIO Gear Up pressed";
}
}
// Check for gear down (GPIO 27) - falling edge (button pressed)
if (m_lastDownState == HIGH && downState == LOW) {
if (m_lastTriggerTime.msecsTo(currentTime) >= GPIO_DEBOUNCE_MS) {
emit gearDownPressed();
m_lastTriggerTime = currentTime;
qDebug() << "GPIO Gear Down pressed";
}
}
m_lastUpState = upState;
m_lastDownState = downState;
#endif
msleep(GPIO_POLL_MS);
}
qDebug() << "GPIO Gear Worker thread stopped";
}
void GPIOGearWorkerThread::stop() {
m_running = false;
}
// GPIO Gear Control Methods in homeform
void homeform::onGPIOGearUpPressed() {
QDateTime currentTime = QDateTime::currentDateTime();
if (lastGearGpioTime.msecsTo(currentTime) < 100) {
return; // Additional debouncing protection
}
lastGearGpioTime = currentTime;
qDebug() << "GPIO Gear Up processed";
Plus(QStringLiteral("gears"));
}
void homeform::onGPIOGearDownPressed() {
QDateTime currentTime = QDateTime::currentDateTime();
if (lastGearGpioTime.msecsTo(currentTime) < 100) {
return; // Additional debouncing protection
}
lastGearGpioTime = currentTime;
qDebug() << "GPIO Gear Down processed";
Minus(QStringLiteral("gears"));
}

View File

@@ -24,6 +24,21 @@
#include <QQuickItem>
#include <QQuickItemGrabResult>
#include <QTextToSpeech>
#include <QThread>
#include <QDateTime>
#if __has_include(<wiringPi.h>)
#include <wiringPi.h>
#else
#define INPUT 0
#define HIGH 1
#define LOW 0
#define PUD_UP 2
inline int digitalRead(int pin) { Q_UNUSED(pin); return HIGH; }
inline void pinMode(int pin, int mode) { Q_UNUSED(pin); Q_UNUSED(mode); }
inline void pullUpDnControl(int pin, int pud) { Q_UNUSED(pin); Q_UNUSED(pud); }
inline int wiringPiSetup() { return 0; }
#endif
#ifdef Q_OS_ANDROID
@@ -42,6 +57,31 @@
#endif
#endif
class GPIOGearWorkerThread : public QThread {
Q_OBJECT
public:
explicit GPIOGearWorkerThread(QObject *parent = nullptr);
~GPIOGearWorkerThread();
void run() override;
void stop();
signals:
void gearUpPressed();
void gearDownPressed();
private:
static const uint8_t GPIO_GEAR_UP = 17; // GPIO 17 (Physical pin 11)
static const uint8_t GPIO_GEAR_DOWN = 27; // GPIO 27 (Physical pin 13)
static const uint16_t GPIO_DEBOUNCE_MS = 100;
static const uint16_t GPIO_POLL_MS = 50;
bool m_running = false;
int m_lastUpState = HIGH;
int m_lastDownState = HIGH;
QDateTime m_lastTriggerTime;
};
class DataObject : public QObject {
Q_OBJECT
@@ -709,6 +749,9 @@ class homeform : public QObject {
static homeform *m_singleton;
TemplateInfoSenderBuilder *userTemplateManager = nullptr;
TemplateInfoSenderBuilder *innerTemplateManager = nullptr;
GPIOGearWorkerThread *gpioGearWorker = nullptr;
QDateTime lastGearGpioTime;
bool gpioGearsEnabled = false;
QList<QObject *> dataList;
QList<SessionLine> Session;
QQmlApplicationEngine *engine;
@@ -888,6 +931,8 @@ class homeform : public QObject {
void sortTilesTimeout();
void gearUp();
void gearDown();
void onGPIOGearUpPressed();
void onGPIOGearDownPressed();
void changeTimestamp(QTime source, QTime actual);
void pelotonOffset_Plus();
void pelotonOffset_Minus();

View File

@@ -844,7 +844,7 @@ ApplicationWindow {
}
ItemDelegate {
text: "version 2.19.2"
text: "version 2.20.0"
width: parent.width
}

View File

@@ -1025,14 +1025,10 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
}
qDebug() << row.duration << "power" << row.power << row.rampDuration << row.rampElapsed;
trainrows.append(row);
atLeastOnePower = true;
}
} else {
r.duration = QTime(0, len / 60, len % 60, 0);
r.power = -1;
if (r.power != -1) {
atLeastOnePower = true;
}
trainrows.append(r);
}
} else if (!zone.toUpper().compare(QStringLiteral("DESCENDING RECOVERY"))) {
@@ -1054,38 +1050,25 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
}
qDebug() << row.duration << "power" << row.power << row.rampDuration << row.rampElapsed;
trainrows.append(row);
atLeastOnePower = true;
}
} else if (!zone.toUpper().compare(QStringLiteral("RECOVERY"))) {
r.duration = QTime(0, len / 60, len % 60, 0);
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.45;
if (r.power != -1) {
atLeastOnePower = true;
}
trainrows.append(r);
qDebug() << r.duration << "power" << r.power;
} else if (!zone.toUpper().compare(QStringLiteral("FLAT ROAD"))) {
r.duration = QTime(0, len / 60, len % 60, 0);
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.50;
if (r.power != -1) {
atLeastOnePower = true;
}
trainrows.append(r);
qDebug() << r.duration << "power" << r.power;
} else if (!zone.toUpper().compare(QStringLiteral("SWEET SPOT"))) {
r.duration = QTime(0, len / 60, len % 60, 0);
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.91;
if (r.power != -1) {
atLeastOnePower = true;
}
trainrows.append(r);
qDebug() << r.duration << "power" << r.power;
} else if (!zone.toUpper().compare(QStringLiteral("INTERVALS"))) {
r.duration = QTime(0, len / 60, len % 60, 0);
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.75;
if (r.power != -1) {
atLeastOnePower = true;
}
trainrows.append(r);
qDebug() << r.duration << "power" << r.power;
} else if (!zone.toUpper().compare(QStringLiteral("ZONE 1"))) {
@@ -1148,9 +1131,6 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
if(len > 0 && atLeastOnePower) {
r.duration = QTime(0, len / 60, len % 60, 0);
r.power = -1;
if (r.power != -1) {
atLeastOnePower = true;
}
qDebug() << "ERROR not handled!" << zone;
trainrows.append(r);
}
@@ -1167,7 +1147,7 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
QJsonArray pace_intensities_list = target_metrics_data_list[QStringLiteral("pace_intensities")].toArray();
int pace_count = 0;
rower_pace_offset = 0;
rower_pace_offset = -1;
foreach (QJsonValue o, pace_intensities_list) {
if(o["value"].toInt() < 0) {
@@ -2203,8 +2183,7 @@ void peloton::replyDataReceived(const QByteArray &v) {
tempAccessToken = jsonResponse[QStringLiteral("access_token")].toString();
tempRefreshToken = jsonResponse[QStringLiteral("refresh_token")].toString();
qDebug() << jsonResponse[QStringLiteral("access_token")] << jsonResponse[QStringLiteral("refresh_token")]
<< jsonResponse[QStringLiteral("expires_at")];
qDebug() << "Peloton tokens received successfully, expires at:" << jsonResponse[QStringLiteral("expires_at")];
QString urlstr = QStringLiteral("https://www.peloton.com/oauth/token?");
QUrlQuery params;
@@ -2268,7 +2247,7 @@ void peloton::networkRequestFinished(QNetworkReply *reply) {
tempRefreshToken = refresh_token;
tempExpiresAt = QDateTime::currentDateTime();
qDebug() << access_token << refresh_token;
qDebug() << "Peloton tokens refreshed successfully";
} else {

View File

@@ -980,4 +980,4 @@ INCLUDEPATH += purchasing/inapp
WINRT_MANIFEST = AppxManifest.xml
VERSION = 2.19.2
VERSION = 2.20.0

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");
@@ -942,9 +943,10 @@ const QString QZSettings::inclinationResistancePoints = QStringLiteral("inclinat
const QString QZSettings::default_inclinationResistancePoints = QStringLiteral("");
const QString QZSettings::floatingwindow_type = QStringLiteral("floatingwindow_type");
const QString QZSettings::gpio_gears_enabled = QStringLiteral("gpio_gears_enabled");
const uint32_t allSettingsCount = 771;
const uint32_t allSettingsCount = 772;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1736,6 +1738,7 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::nordictrack_elite_800, QZSettings::default_nordictrack_elite_800},
{QZSettings::inclinationResistancePoints, QZSettings::default_inclinationResistancePoints},
{QZSettings::floatingwindow_type, QZSettings::default_floatingwindow_type},
{QZSettings::gpio_gears_enabled, QZSettings::default_gpio_gears_enabled},
{QZSettings::rogue_echo_bike, QZSettings::default_rogue_echo_bike},
};

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;
@@ -2513,6 +2516,9 @@ class QZSettings {
static const QString floatingwindow_type;
static constexpr int default_floatingwindow_type = 0;
static const QString gpio_gears_enabled;
static constexpr bool default_gpio_gears_enabled = false;
/**
* @brief Write the QSettings values using the constants from this namespace.
* @param showDefaults Optionally indicates if the default should be shown with the key.

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,16 @@ 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
property bool gpio_gears_enabled: false
}
function paddingZeros(text, limit) {
@@ -5353,7 +5359,7 @@ import Qt.labs.platform 1.1
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
}
IndicatorOnlySwitch {
text: qsTr("Zwift Treadmill Auto Inclination")
@@ -6368,43 +6374,6 @@ import Qt.labs.platform 1.1
color: Material.color(Material.Lime)
}
RowLayout {
spacing: 10
Label {
id: labelTreadmillStepInclination
text: qsTr("Inclination Step:")
Layout.fillWidth: true
}
TextField {
id: treadmillInclinationStepTextField
text: settings.treadmill_step_incline
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
//inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.treadmill_step_incline = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
id: okTreadmillInclinationStepButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.treadmill_step_incline = treadmillInclinationStepTextField.text; toast.show("Setting saved!"); }
}
}
Label {
text: qsTr("(Incline Tile) This controls the amount of the increase or decrease in the inclination when you press the plus or minus button in the Incline Tile. Default is 0.5.")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
RowLayout {
spacing: 10
@@ -9327,6 +9296,43 @@ import Qt.labs.platform 1.1
color: Material.color(Material.Lime)
}
RowLayout {
spacing: 10
Label {
id: labelInclinationStep
text: qsTr("Inclination Step:")
Layout.fillWidth: true
}
TextField {
id: inclinationStepTextField
text: settings.treadmill_step_incline
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
//inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.treadmill_step_incline = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
id: okInclinationStepButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.treadmill_step_incline = inclinationStepTextField.text; toast.show("Setting saved!"); }
}
}
Label {
text: qsTr("(Incline Tile) This controls the amount of the increase or decrease in the inclination when you press the plus or minus button in the Incline Tile for both treadmills and bikes. Default is 0.5.")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("Send real inclination to virtual bridge")
@@ -12054,6 +12060,45 @@ import Qt.labs.platform 1.1
}
}
}
AccordionElement {
id: hardwareControlAccordion
title: qsTr("Hardware Control")
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Yellow)
color: Material.backgroundColor
accordionContent: ColumnLayout {
spacing: 0
IndicatorOnlySwitch {
id: gpioGearsEnabledDelegate
text: qsTr("GPIO Gears Control")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.gpio_gears_enabled
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.gpio_gears_enabled = checked; window.settings_restart_to_apply = true; }
}
Label {
text: qsTr("Enable GPIO-based gear shifting on Raspberry Pi (GPIO 17 for up, GPIO 27 for down)")
font.bold: true
font.italic: true
font.pixelSize: Qt.application.font.pixelSize - 2
textFormat: Text.PlainText
wrapMode: Text.WordWrap
verticalAlignment: Text.AlignVCenter
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
}
}
}
/*##^##
Designer {

View File

@@ -100,7 +100,7 @@ private slots:
access_token_expiration = now.toMSecsSinceEpoch() + (expires_in - 5) * 1000;
refresh_token_expiration = now.toMSecsSinceEpoch() + (refresh_expires_in - 5) * 1000;
qDebug() << "Access Token: " << access_token;
qDebug() << "Access Token received successfully";
} else {
qDebug() << "Error fetching token: " << reply->errorString();
}