Compare commits

...

107 Commits

Author SHA1 Message Date
Roberto Viola
04d37e0a68 Merge remote-tracking branch 'origin/master' into fixing_trainprogram_jitter 2025-02-23 16:30:47 +00:00
Roberto Viola
ae280e170a SOLE LCR Bike #3226 2025-02-22 07:08:53 +01:00
Roberto Viola
d2dfb16033 Cyclotronics Smart Trainer #3223 2025-02-21 15:58:11 +01:00
Roberto Viola
64d99748c7 Proform XBike #3214 2025-02-21 14:16:51 +01:00
Roberto Viola
16d90a010b Proform XBike #3214 2025-02-21 13:02:36 +01:00
Roberto Viola
32baab9072 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-02-21 08:25:31 +01:00
Roberto Viola
31dd125263 Peloton API Login Issues (Issue #3217) 2025-02-21 08:25:24 +01:00
Roberto Viola
483fd87ee5 Update main.cpp 2025-02-20 20:03:09 +01:00
Roberto Viola
254786ea5d Gears don't work for mid-work free ride segment (Issue #2897)
https://github.com/cagnulein/qdomyos-zwift/issues/2897#issuecomment-2671383599
2025-02-20 14:52:59 +01:00
Roberto Viola
c06a439c0c Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-02-20 11:48:20 +01:00
Roberto Viola
7b76999c0d Fixing ios bluetooth crash (#3213)
* adding files

* fixing build

* Update project.pbxproj
2025-02-20 11:48:06 +01:00
Roberto Viola
5c0190dffe Titan 7000 #3218 2025-02-20 11:47:29 +01:00
Roberto Viola
aabd2824d3 “Lydsto S1” spinning bike 2025-02-20 10:11:03 +01:00
Roberto Viola
2f7033cd6d Support device JTX Cyclo 5 (FAL-SPORTS0234) (Issue #3208) 2025-02-20 08:24:59 +01:00
Roberto Viola
756fe823f8 “Lydsto S1” spinning bike 2025-02-20 08:16:35 +01:00
Roberto Viola
ed0d163944 Proform XBike #3214 2025-02-19 15:35:29 +01:00
Roberto Viola
b7ee025a6f SPAX-BK ERG not working #3200 2025-02-19 10:44:48 +01:00
Roberto Viola
23dca8ec93 added PELOTON_SECRET_KEY on github secrets 2025-02-19 09:07:33 +01:00
Roberto Viola
fe05cb613f Peloton oauth (#2632)
* starting

* builds?

* Update peloton.cpp

* Update settings.qml

* Update peloton.cpp

* Update peloton.cpp

* trying to login

* Update peloton.cpp

* Update peloton.cpp

* Update peloton.cpp

* workout api returns always void

* fixing auth header on workout

* Update peloton.cpp

* handling new cases

* Update peloton.cpp

* Update peloton.cpp

* Update peloton.cpp

* Update qzsettings.cpp

* adding the peloton connect button

* adding popup to switch to the new api and removed credentials from the settings

* fixing peloton popup

* Update main.qml

* everything should be fine now

* added peloton button on the settings page too

* Update project.pbxproj

* new kingsmith variant treadmill

* Cannot set Virtufit Etappe 2 auto resistance (Issue #3130)

* CycleOps Phantom 5 (Issue #3004)

* Nordictrack commercial 1750 incline calibration incorrect (Issue #3118)

* Update qzsettings.cpp

* airdate and current_pedaling_duration fixed

* fixing spinups in powerzone classes

* Update project.pbxproj

* Update project.pbxproj

* getting wattage and cadence directly from the zwift hub riding data if available

* Update project.pbxproj

* Update project.pbxproj

* Update project.pbxproj

* 2.18.19

* Update project.pbxproj

* Update project.pbxproj

* Update project.pbxproj

* Update peloton.cpp

* Update project.pbxproj

* Row and Tread Target Pace Issues from 2.18.19 Update #3206
2025-02-19 09:03:02 +01:00
Roberto Viola
c56c6fe5e4 Issue with Elite Direto XR, zwift cog, zwift play, mywhoosh and zwift combo #3191 2025-02-19 08:54:03 +01:00
Roberto Viola
8f7fafa4f2 DIRETO XR limited at 600W #3193 2025-02-19 08:48:08 +01:00
Roberto Viola
ef9ca0bfc8 DIRETO XR limited at 600W #3193 2025-02-19 08:46:34 +01:00
Roberto Viola
34635114df QZ CRUSH AT CONNECTION INITIAL 100 DOMYOS (Issue #3197) 2025-02-19 08:30:07 +01:00
Roberto Viola
3e29dd63df Update proformbike.cpp 2025-02-18 21:21:15 +01:00
Roberto Viola
c575159616 Pro-Form, model PFTL99015.0 #3209 2025-02-18 16:52:35 +01:00
Roberto Viola
ddebfc7e75 Support device JTX Cyclo 5 (FAL-SPORTS0234) #3208 2025-02-18 15:47:41 +01:00
Roberto Viola
2b51e5982a Kettler tour 600 #3207 2025-02-18 14:47:36 +01:00
Roberto Viola
00b616f4f8 QZ Companion on NordicTrack T8.5S is unable to communicate with QZ Fitness #3187 2025-02-18 08:26:59 +01:00
Roberto Viola
38274e1056 QZ CRUSH AT CONNECTION INITIAL 100 DOMYOS (Issue #3197) 2025-02-18 08:16:18 +01:00
Roberto Viola
cf1397cb81 Connecting to Horizon 7.0 #3201 2025-02-17 15:25:40 +01:00
Roberto Viola
b23c1b46ab Log on Thread (#3189) 2025-02-17 15:11:21 +01:00
Roberto Viola
4750ee9214 No heart rate and negative calories when using Apple Watch & Elliptical Skandika Carbon P23 #3198 2025-02-17 12:14:33 +01:00
Roberto Viola
05b39acb3e Christopeit TM3000S treadmill 2025-02-17 10:47:42 +01:00
Roberto Viola
26d2a59ad5 QZ Connects to Merach R11 rower on iOS but no metrics/data are being captured (Issue #3190) 2025-02-17 10:22:47 +01:00
Roberto Viola
1ed382faef QZ CRUSH AT CONNECTION INITIAL 100 DOMYOS (Issue #3197) 2025-02-17 10:06:28 +01:00
Roberto Viola
ebbbd4febb Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-02-17 08:53:31 +01:00
Roberto Viola
95014c3863 No heart rate and negative calories when using Apple Watch & Elliptical Skandika Carbon P23 (Issue #3198) 2025-02-17 08:53:25 +01:00
Roberto Viola
b61f5752d2 Smart bike BH BR9110 #3192 2025-02-16 15:52:44 +01:00
Roberto Viola
4b7533d721 VANRYSEL-HT ftms bike 2025-02-16 15:49:23 +01:00
Roberto Viola
6519e9ae86 avoiding crash on tacxneo2 2025-02-16 09:21:48 +01:00
Roberto Viola
cbc3b9d292 MSVC Print Stack Trace (#3185) 2025-02-15 19:32:00 +01:00
Roberto Viola
1dde627a4c Sportstech sBike Lite 2025-02-14 17:34:16 +01:00
Roberto Viola
06727f23e4 Proform Bike PFEVEX71316.0 #3157 2025-02-14 14:30:55 +01:00
Roberto Viola
1c8279d2fc Treadmill and Power Sensor Speed Forcing (Issue #3152) 2025-02-13 10:19:24 +01:00
Roberto Viola
aa0193b41e Pafer treadmill #2985 2025-02-13 10:01:26 +01:00
Roberto Viola
fcf6a8b586 fixing 0 metrics to apple watch to ipad bridge 2025-02-13 08:55:33 +01:00
Roberto Viola
574c51bcec Does it support Crosstrainer Skandika Carbon P23? #3175 2025-02-13 08:03:07 +01:00
Roberto Viola
01fa8602a0 Trying to fix android crash 2025-02-12 20:44:19 +01:00
Roberto Viola
b006e8cc2b Update proformbike.cpp 2025-02-12 13:36:20 +01:00
Roberto Viola
e8486364a3 adding help to CLI 2025-02-12 12:15:02 +01:00
sirfergy
f72b6b04ce Enable setting power sensor via command line (#3106)
* Enable setting power sensor via command line

* Remove weird whitespace

* Update main.cpp

---------

Co-authored-by: Roberto Viola <Cagnulein@gmail.com>
2025-02-12 12:05:17 +01:00
Roberto Viola
b5890ea818 Update trainprogram.cpp 2025-02-12 11:24:12 +01:00
Roberto Viola
83627f5397 Merge branch 'master' into fixing_trainprogram_jitter 2025-02-12 10:07:42 +01:00
Roberto Viola
1152b4d9b2 Proform Bike PFEVEX71316.0 #3157 2025-02-12 09:46:29 +01:00
Roberto Viola
219c4e2491 Treadmill and Power Sensor Speed Forcing (Issue #3152) 2025-02-12 08:39:41 +01:00
Roberto Viola
47d78f4464 Yesoul Treadmill support #3174 2025-02-12 08:33:12 +01:00
Roberto Viola
bebfd03ae9 Treadmill and Power Sensor Speed Forcing (Issue #3152) 2025-02-11 15:20:31 +01:00
Roberto Viola
0bf98491cb getting wattage and cadence directly from the zwift hub riding data if available 2025-02-11 15:05:28 +01:00
Roberto Viola
63e4c627b3 Proform Bike PFEVEX71316.0 #3157 2025-02-11 14:54:49 +01:00
Roberto Viola
a37e3c8287 getting wattage and cadence directly from the zwift hub riding data if available 2025-02-11 09:09:27 +00:00
Roberto Viola
45ab560d08 adding more info about upload on strava failed 2025-02-11 09:01:12 +01:00
Roberto Viola
3d1846cbe8 Proform Bike PFEVEX71316.0 #3157 2025-02-11 08:52:18 +01:00
Roberto Viola
936bbe2372 Treadmill and Power Sensor Speed Forcing #3152 2025-02-10 17:09:33 +01:00
Tomáš Janoušek
963a3fbb97 skillbike: allow 3-digit bikes (#3165)
My TechnoGym Skillbike is called "BIKE 861" and QZ wouldn't detect it
previously because it assumed all such bikes have 4-digit names.

The fix is to relax requirement and detect any /BIKE \d+/ as
technogymBike.
2025-02-10 16:39:28 +01:00
Roberto Viola
b4161da81a Run Elevation Written By Zwift Elevation Data Instead Of Treadmill Data #3160 2025-02-10 16:33:58 +01:00
Roberto Viola
be65d915e3 Proform Bike PFEVEX71316.0 #3157 2025-02-10 10:08:00 +01:00
Roberto Viola
22fb9df723 Treadmill and Power Sensor Usage #3152 2025-02-10 09:28:28 +01:00
Roberto Viola
8709f81b16 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-02-09 18:28:46 +01:00
Roberto Viola
347ede62b2 adding debug on the ipad bridge 2025-02-09 18:28:25 +01:00
Roberto Viola
c4a913d317 Yoroto rower 2025-02-09 15:42:56 +01:00
Roberto Viola
2b8af5d777 Update trainprogram.cpp 2025-02-09 09:39:52 +01:00
Roberto Viola
1706fde7ab No power when pedalling with Van Rysel D500 turbo trainer. #3065 (#3083) 2025-02-08 17:54:21 +01:00
Roberto Viola
60e8d37624 fixing-raspberry-64bit-segfault-build (#3084)
* Update main.yml

* Update main.yml

* Update main.yml

https://github.com/docker/setup-qemu-action/issues/188

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml

* Update main.yml
2025-02-08 11:33:05 +01:00
Roberto Viola
519bc38fb6 sole f85 doesn't handle 0.5 inclination step 2025-02-07 21:22:08 +01:00
Roberto Viola
375d223f66 fixed domyos bike settings 2025-02-07 20:37:33 +01:00
Roberto Viola
f16e0649cb added a setting to enable the ignore ftms setting for domyos bikes 2025-02-07 18:47:47 +01:00
Roberto Viola
9e685cde37 Proform Bike PFEVEX71316.0 #3157 2025-02-07 10:31:48 +01:00
Roberto Viola
d32d7cd802 Rouvy: Virtual shifting with Zwift gearing, noticeably harder than physical gearing #3031
https://github.com/cagnulein/qdomyos-zwift/issues/3031#issuecomment-2642230942
2025-02-07 09:24:29 +01:00
Roberto Viola
8961ca860a adding preset buttons for power zones 2025-02-06 09:55:07 +01:00
Roberto Viola
9a3fa7c82f fix crash on iOS 2025-02-05 15:44:46 +01:00
Roberto Viola
183f36bf40 Polar OH1 not connecting consistently with Android tablet / Qdomyos-swift app #3139 (#3144) 2025-02-05 13:08:23 +01:00
Roberto Viola
bac91d14c6 YESOUL G1M Max bike #3149 2025-02-05 10:27:09 +01:00
Roberto Viola
7455729225 Elite Drivo II 2025-02-05 08:56:44 +01:00
Roberto Viola
223b6b7a0e manually adjusting the resistance, incorrect behavior! (Issue #3145) 2025-02-04 16:35:47 +01:00
Roberto Viola
825555a34f Upload Strava from WIndows crash (#3143)
* Update homeform.cpp

* aggiunta /RTC1

* Update qdomyos-zwift.pri
2025-02-04 12:47:28 +01:00
Roberto Viola
aeead83510 Nordictrack commercial 1750 incline calibration incorrect (Issue #3118) 2025-02-04 08:34:25 +00:00
Roberto Viola
78a8981006 CycleOps Phantom 5 (Issue #3004) 2025-02-04 07:29:54 +00:00
Roberto Viola
e9bb6bc73b Cannot set Virtufit Etappe 2 auto resistance (Issue #3130) 2025-02-04 07:23:49 +00:00
Roberto Viola
d2354074f8 new kingsmith variant treadmill 2025-02-04 07:21:02 +00:00
Roberto Viola
78ee43cb7d commenting 64bit build image for now 2025-02-03 08:37:11 +01:00
Roberto Viola
952fc914fb QZ crushes right after connection to MERACH S01 (Issue #3136) 2025-02-03 08:35:01 +01:00
Roberto Viola
bc54203cdf Elito Avanti added 2025-02-02 16:01:53 +01:00
Gerd Naschenweng
854846585a Update 10_Installation.md (#3129) 2025-02-01 09:38:38 +01:00
Roberto Viola
309bfb623b preparing for #2632 2025-01-31 16:35:31 +01:00
Roberto Viola
9a99740701 BT Log share for LifeSpan-TM-2000 #3021 2025-01-31 10:47:54 +01:00
Roberto Viola
1f299a1ff1 BT Log share for LifeSpan-TM-2000 #3021 2025-01-31 10:33:52 +01:00
Roberto Viola
8bce3d0541 version 2.18.18 2025-01-31 09:36:34 +01:00
Roberto Viola
06d033d13c adding debug to connection to apple watch 2025-01-31 07:41:05 +01:00
Roberto Viola
654b070c7e Update ftmsbike.cpp
clean time in case for a long period we don't receive values
2025-01-30 16:04:27 +01:00
Roberto Viola
a159c8f072 Technogym Elliptical #3121 2025-01-30 14:54:52 +01:00
Roberto Viola
3846d974af Inclination stops updating in app after about 30 minutes (but treadmill adjustment still works) (Issue #2992) 2025-01-30 11:27:09 +01:00
Roberto Viola
7e5fcfb881 adding some logs on the homeform update to understand the lag on some androids 2025-01-30 10:46:54 +01:00
Roberto Viola
3f6b284468 Merge branch 'master' into fixing_trainprogram_jitter 2025-01-30 10:05:48 +01:00
Roberto Viola
4e9cafcd5e Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-01-29 21:18:13 +01:00
Roberto Viola
d343c1c98c Tacx Neo 3M #3062 2025-01-29 11:34:23 +01:00
Roberto Viola
1be9e2620d Update trainprogram.cpp 2023-12-01 15:08:14 +01:00
Roberto Viola
805981df4d fixing 2023-12-01 14:53:33 +01:00
68 changed files with 4297 additions and 371 deletions

View File

@@ -128,6 +128,7 @@ jobs:
run: |
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
@@ -304,6 +305,7 @@ jobs:
# qmake
# 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
@@ -606,6 +608,7 @@ jobs:
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
@@ -709,6 +712,7 @@ jobs:
run: |
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
@@ -819,6 +823,7 @@ jobs:
run: |
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
@@ -1006,6 +1011,7 @@ jobs:
run: |
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
@@ -1100,6 +1106,7 @@ jobs:
run: |
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
@@ -1136,7 +1143,7 @@ jobs:
path: src/qdomyos-zwift-32bit
raspberry-pi-build-and-image-64bit:
runs-on: ubuntu-24.04
runs-on: ubuntu-22.04
steps:
- name: Checkout code
@@ -1144,16 +1151,21 @@ jobs:
with:
submodules: recursive
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Setup QEMU
uses: docker/setup-qemu-action@v3
with:
image: tonistiigi/binfmt:qemu-v7.0.0
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
version: v0.19.3
- name: Secrets
run: |
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
@@ -1182,94 +1194,12 @@ jobs:
- name: Rename binary
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
- name: Archive Raspberry Pi binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-binary
path: src/qdomyos-zwift-64bit
- name: Download and expand Raspberry Pi OS image
run: |
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz
xz -d 2024-10-22-raspios-bookworm-arm64-lite.img.xz
ORIGINAL_SIZE=$(stat -c %s 2024-10-22-raspios-bookworm-arm64-lite.img)
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
truncate -s $NEW_SIZE 2024-10-22-raspios-bookworm-arm64-lite.img
sudo apt-get update
sudo apt-get install -y parted
sudo parted 2024-10-22-raspios-bookworm-arm64-lite.img resizepart 2 100%
- name: Mount Raspberry Pi image
run: |
sudo apt-get install -y kpartx qemu-user-static
LOOP_DEVICE=$(sudo losetup -f --show 2024-10-22-raspios-bookworm-arm64-lite.img)
echo "Loop device is $LOOP_DEVICE"
sudo kpartx -av $LOOP_DEVICE
sudo mkdir -p /mnt/raspbian
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p2 /mnt/raspbian
sudo resize2fs /dev/mapper/$(basename $LOOP_DEVICE)p2
echo "LOOP_DEVICE=$LOOP_DEVICE" >> $GITHUB_ENV
sudo cp /usr/bin/qemu-aarch64-static /mnt/raspbian/usr/bin/
sudo mkdir -p /mnt/raspbian_p1
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p1 /mnt/raspbian_p1
- name: Install Qt and dependencies on Raspberry Pi image
run: |
sudo chroot /mnt/raspbian qemu-aarch64-static /bin/bash << EOF
apt-get update
apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2
EOF
- name: Copy binary to Raspberry Pi image
run: |
sudo cp src/qdomyos-zwift-64bit /mnt/raspbian/home/pi/qdomyos-zwift
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
- name: Setup auto-start for qdomyos-zwift
run: |
echo '[Unit]
Description=QDomyos-Zwift
After=multi-user.target
[Service]
ExecStart=/home/pi/qdomyos-zwift
User=pi
Environment=DISPLAY=:0
[Install]
WantedBy=multi-user.target' | sudo tee /mnt/raspbian/etc/systemd/system/qdomyos-zwift.service
sudo chroot /mnt/raspbian systemctl enable qdomyos-zwift.service
- name: Modify boot config to enable bluetooth
run: |
# The following line makes it specific for Raspberry Pi Zero 2W.
# though I expect it is needed for Raspberry Pi 3B and maybe others as well
echo "[pi02]" | sudo tee -a /mnt/raspbian_p1/config.txt
echo "dtoverlay=miniuart-bt" | sudo tee -a /mnt/raspbian_p1/config.txt
- name: Unmount Raspberry Pi image
run: |
sudo umount /mnt/raspbian
sudo umount /mnt/raspbian_p1
sudo kpartx -d ${{ env.LOOP_DEVICE }}
sudo losetup -d ${{ env.LOOP_DEVICE }}
- name: Compress modified Raspberry Pi image
run: |
xz -z 2024-10-22-raspios-bookworm-arm64-lite.img
- name: Upload Raspberry Pi image as artifact
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-image
path: 2024-10-22-raspios-bookworm-arm64-lite.img.xz
upload_to_release:
permissions: write-all
runs-on: ubuntu-20.04
if: github.event_name == 'schedule'
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build, raspberry-pi-build-and-image-64bit, raspberry-pi-build] # Specify the job dependencies
needs: [linux-x86-build, window-msvc2019-build, ios-build, window-build, android-build, raspberry-pi-build] # Specify the job dependencies
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
@@ -1299,5 +1229,5 @@ jobs:
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/qdomyos-zwift-32bit
raspberry-pi-64bit-binary/qdomyos-zwift-64bit
2024-10-22-raspios-bookworm-arm64-lite.img.xz
#raspberry-pi-64bit-binary/qdomyos-zwift-64bit
#2024-10-22-raspios-bookworm-arm64-lite.img.xz

2
.vscode/launch.json vendored
View File

@@ -8,7 +8,7 @@
"program": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.exe",
"symbolSearchPath": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/qdomyos-zwift.pdb",
"sourceFileMap": {
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/"
"d:/a/qdomyos-zwift/qdomyos-zwift": "c:/work/qdomyos-zwift/",
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
}
}

View File

@@ -413,6 +413,7 @@
87958F1B27628D5400124B24 /* moc_elitesterzosmart.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87958F1A27628D5400124B24 /* moc_elitesterzosmart.cpp */; };
8798C8872733E103003148B3 /* strydrunpowersensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798C8862733E103003148B3 /* strydrunpowersensor.cpp */; };
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798C8882733E10E003148B3 /* moc_strydrunpowersensor.cpp */; };
8798FDC52D66075B00CF8EE8 /* OSXBtManagerInternal.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8798FDC32D66075B00CF8EE8 /* OSXBtManagerInternal.mm */; };
879A38C8281BD83300F78B2A /* characteristicnotifier2ad9.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879A38C7281BD83300F78B2A /* characteristicnotifier2ad9.cpp */; };
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */; };
879E5AAA289C05A500FEA38A /* moc_proformwifitreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 879E5AA9289C05A500FEA38A /* moc_proformwifitreadmill.cpp */; };
@@ -435,6 +436,8 @@
87A18F072660D5C1002D7C96 /* ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F052660D5C0002D7C96 /* ftmsrower.cpp */; };
87A18F092660D5D9002D7C96 /* moc_ftmsrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */; };
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A2E0212B2B053E00E6168F /* swiftDebug.mm */; };
87A33F1A2D611D8400BFFF29 /* moc_logwriter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A33F192D611D8400BFFF29 /* moc_logwriter.cpp */; };
87A33F1D2D611D9500BFFF29 /* logwriter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A33F1C2D611D9500BFFF29 /* logwriter.cpp */; };
87A3BC222656429600D302E3 /* rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC1F2656429400D302E3 /* rower.cpp */; };
87A3BC232656429600D302E3 /* echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC202656429400D302E3 /* echelonrower.cpp */; };
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC24265642A200D302E3 /* moc_rower.cpp */; };
@@ -1375,6 +1378,11 @@
8798C8852733E103003148B3 /* strydrunpowersensor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = strydrunpowersensor.h; path = ../src/devices/strydrunpowersensor/strydrunpowersensor.h; sourceTree = "<group>"; };
8798C8862733E103003148B3 /* strydrunpowersensor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = strydrunpowersensor.cpp; path = ../src/devices/strydrunpowersensor/strydrunpowersensor.cpp; sourceTree = "<group>"; };
8798C8882733E10E003148B3 /* moc_strydrunpowersensor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_strydrunpowersensor.cpp; sourceTree = "<group>"; };
8798FDC02D66075B00CF8EE8 /* osxbluetooth_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbluetooth_p.h; path = ../src/ios/BluetoothPatch/osxbluetooth_p.h; sourceTree = SOURCE_ROOT; };
8798FDC12D66075B00CF8EE8 /* osxbtcentralmanager_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbtcentralmanager_p.h; path = ../src/ios/BluetoothPatch/osxbtcentralmanager_p.h; sourceTree = SOURCE_ROOT; };
8798FDC22D66075B00CF8EE8 /* osxbtgcdtimer_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbtgcdtimer_p.h; path = ../src/ios/BluetoothPatch/osxbtgcdtimer_p.h; sourceTree = SOURCE_ROOT; };
8798FDC32D66075B00CF8EE8 /* OSXBtManagerInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; name = OSXBtManagerInternal.mm; path = ../src/ios/BluetoothPatch/OSXBtManagerInternal.mm; sourceTree = SOURCE_ROOT; };
8798FDC42D66075B00CF8EE8 /* osxbtutility_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osxbtutility_p.h; path = ../src/ios/BluetoothPatch/osxbtutility_p.h; sourceTree = SOURCE_ROOT; };
879A38C7281BD83300F78B2A /* characteristicnotifier2ad9.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = characteristicnotifier2ad9.cpp; path = ../src/characteristics/characteristicnotifier2ad9.cpp; sourceTree = "<group>"; };
879E5AA6289C057E00FEA38A /* proformwifitreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformwifitreadmill.cpp; path = ../src/devices/proformwifitreadmill/proformwifitreadmill.cpp; sourceTree = "<group>"; };
879E5AA7289C057E00FEA38A /* proformwifitreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformwifitreadmill.h; path = ../src/devices/proformwifitreadmill/proformwifitreadmill.h; sourceTree = "<group>"; };
@@ -1407,6 +1415,9 @@
87A18F082660D5D9002D7C96 /* moc_ftmsrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ftmsrower.cpp; sourceTree = "<group>"; };
87A2E0202B2B024200E6168F /* swiftDebug.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = swiftDebug.h; path = ../src/ios/swiftDebug.h; sourceTree = "<group>"; };
87A2E0212B2B053E00E6168F /* swiftDebug.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = swiftDebug.mm; path = ../src/ios/swiftDebug.mm; sourceTree = "<group>"; };
87A33F192D611D8400BFFF29 /* moc_logwriter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_logwriter.cpp; sourceTree = "<group>"; };
87A33F1B2D611D9500BFFF29 /* logwriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = logwriter.h; path = ../src/logwriter.h; sourceTree = SOURCE_ROOT; };
87A33F1C2D611D9500BFFF29 /* logwriter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = logwriter.cpp; path = ../src/logwriter.cpp; sourceTree = SOURCE_ROOT; };
87A3BC1E2656429300D302E3 /* echelonrower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = echelonrower.h; path = ../src/devices/echelonrower/echelonrower.h; sourceTree = "<group>"; };
87A3BC1F2656429400D302E3 /* rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = rower.cpp; path = ../src/devices/rower.cpp; sourceTree = "<group>"; };
87A3BC202656429400D302E3 /* echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = echelonrower.cpp; path = ../src/devices/echelonrower/echelonrower.cpp; sourceTree = "<group>"; };
@@ -2168,6 +2179,14 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
8798FDC02D66075B00CF8EE8 /* osxbluetooth_p.h */,
8798FDC12D66075B00CF8EE8 /* osxbtcentralmanager_p.h */,
8798FDC22D66075B00CF8EE8 /* osxbtgcdtimer_p.h */,
8798FDC32D66075B00CF8EE8 /* OSXBtManagerInternal.mm */,
8798FDC42D66075B00CF8EE8 /* osxbtutility_p.h */,
87A33F1B2D611D9500BFFF29 /* logwriter.h */,
87A33F1C2D611D9500BFFF29 /* logwriter.cpp */,
87A33F192D611D8400BFFF29 /* moc_logwriter.cpp */,
87A3DD982D3413790060BAEB /* lifespantreadmill.h */,
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */,
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */,
@@ -3626,6 +3645,7 @@
87C7074327E4CF5900E79C46 /* keepbike.cpp in Compile Sources */,
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */,
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */,
87A33F1D2D611D9500BFFF29 /* logwriter.cpp in Compile Sources */,
8798C8892733E10E003148B3 /* moc_strydrunpowersensor.cpp in Compile Sources */,
8781907E2615089D0085E656 /* peloton.cpp in Compile Sources */,
2B800DC34C91D8B080DEFBE8 /* fit_mesg_with_event_broadcaster.cpp in Compile Sources */,
@@ -3749,6 +3769,7 @@
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */,
873824E327E647A8004F1B46 /* bitmap.cpp in Compile Sources */,
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */,
8798FDC52D66075B00CF8EE8 /* OSXBtManagerInternal.mm in Compile Sources */,
873824E827E647A8004F1B46 /* provider.cpp in Compile Sources */,
8791A8AA25C8603F003B50B2 /* moc_inspirebike.cpp in Compile Sources */,
03F49BBCF19B73B18385B13D /* FitMesgDefinition.mm in Compile Sources */,
@@ -3800,6 +3821,7 @@
E62DA5FF2436135448C94671 /* moc_toorxtreadmill.cpp in Compile Sources */,
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */,
87CC3BA325A0885F001EC5A8 /* domyoselliptical.cpp in Compile Sources */,
87A33F1A2D611D8400BFFF29 /* moc_logwriter.cpp in Compile Sources */,
87D105542909971100B3935B /* moc_mepanelbike.cpp in Compile Sources */,
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */,
87DA8467284933DE00B550E9 /* moc_fakeelliptical.cpp in Compile Sources */,
@@ -4233,7 +4255,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1011;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4427,7 +4449,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1011;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4657,7 +4679,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1011;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4753,7 +4775,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1011;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4845,7 +4867,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1011;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4961,7 +4983,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1011;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -197,6 +197,8 @@ LOG_FILE="/var/log/qz-treadmill-monitor.log"
TARGET_DEVICE="I_TL"
SCAN_INTERVAL=30 # Time in seconds between checks
SERVICE_NAME="qz"
DEBUG_LOG_DIR="/var/log" # Directory where QZ debug logs are stored
ERROR_MESSAGE="BTLE stateChanged InvalidService"
log() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> "$LOG_FILE"
@@ -209,7 +211,7 @@ is_service_running() {
scan_for_device() {
log "Starting Bluetooth scan for $TARGET_DEVICE..."
# Run bluetoothctl scan in the background and capture output
bluetoothctl scan on &>/dev/null &
SCAN_PID=$!
@@ -234,18 +236,43 @@ scan_for_device() {
fi
}
restart_qz_on_error() {
# Get the current date
CURRENT_DATE=$(date '+%a_%b_%d')
# Find the latest QZ debug log file for today
LATEST_LOG=$(ls -t "$DEBUG_LOG_DIR"/debug-"$CURRENT_DATE"_*.log 2>/dev/null | head -n 1)
if [ -z "$LATEST_LOG" ]; then
log "No QZ debug log found for today."
return 0
fi
log "Checking latest log file: $LATEST_LOG for errors..."
# Search the latest log for the error message
if grep -q "$ERROR_MESSAGE" "$LATEST_LOG"; then
log "***** Error detected in QZ log: $ERROR_MESSAGE *****"
log "Restarting QZ service..."
systemctl restart "$SERVICE_NAME"
else
log "No errors detected in $LATEST_LOG."
fi
}
manage_service() {
local device_found=$1
if $device_found; then
if ! is_service_running; then
log "Starting QZ service..."
log "***** Starting QZ service... *****"
systemctl start "$SERVICE_NAME"
else
log "QZ service is already running."
restart_qz_on_error # Check the log for errors when QZ is already running
fi
else
if is_service_running; then
log "Stopping QZ service..."
log "***** Stopping QZ service... *****"
systemctl stop "$SERVICE_NAME"
else
log "QZ service is already stopped."

26
src/OAuth2.h Normal file
View File

@@ -0,0 +1,26 @@
#ifndef OAUTH2_H
#define OAUTH2_H
#include <QString>
#include <QTextStream>
struct OAuth2Parameter {
QString responseType = QStringLiteral("code");
QString approval_prompt = QStringLiteral("force");
inline bool isEmpty() const { return responseType.isEmpty() && approval_prompt.isEmpty(); }
QString toString() const {
QString msg;
QTextStream out(&msg);
out << QStringLiteral("OAuth2Parameter{\n") << QStringLiteral("responseType: ") << this->responseType
<< QStringLiteral("\n") << QStringLiteral("approval_prompt: ") << this->approval_prompt
<< QStringLiteral("\n");
return msg;
}
};
#define _STR(x) #x
#define STRINGIFY(x) _STR(x)
#endif // OAUTH2_H

58
src/WebPelotonAuth.qml Normal file
View File

@@ -0,0 +1,58 @@
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Controls.Material 2.12
import QtQuick.Dialogs 1.0
import QtGraphicalEffects 1.12
import Qt.labs.settings 1.0
import QtMultimedia 5.15
import QtQuick.Layouts 1.3
import QtWebView 1.1
Item {
anchors.fill: parent
height: parent.height
width: parent.width
visible: true
WebView {
anchors.fill: parent
height: parent.height
width: parent.width
visible: !rootItem.pelotonPopupVisible
url: rootItem.getPelotonAuthUrl
}
Popup {
id: popupPelotonConnectedWeb
parent: Overlay.overlay
enabled: rootItem.pelotonPopupVisible
onEnabledChanged: { if(rootItem.pelotonPopupVisible) popupPelotonConnectedWeb.open() }
onClosed: { rootItem.pelotonPopupVisible = false; }
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
width: 380
height: 120
modal: true
focus: true
palette.text: "white"
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
enter: Transition
{
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
}
exit: Transition
{
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
Label {
anchors.horizontalCenter: parent.horizontalCenter
width: 370
height: 120
text: qsTr("Your Peloton account is now connected!<br><br>Restart the app to apply this!")
}
}
}
}

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.18.17" android:versionCode="999" 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.18.20" android:versionCode="1020" 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 -->

View File

@@ -31,8 +31,21 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
uint16_t normalizeIncline = 0;
QSettings settings;
bool real_inclination_to_virtual_treamill_bridge = settings.value(QZSettings::real_inclination_to_virtual_treamill_bridge, QZSettings::default_real_inclination_to_virtual_treamill_bridge).toBool();
double inclination = ((treadmill *)Bike)->currentInclination().value();
if(real_inclination_to_virtual_treamill_bridge) {
double offset = settings.value(QZSettings::zwift_inclination_offset,
QZSettings::default_zwift_inclination_offset).toDouble();
double gain = settings.value(QZSettings::zwift_inclination_gain,
QZSettings::default_zwift_inclination_gain).toDouble();
inclination -= offset;
inclination /= gain;
}
if (dt == bluetoothdevice::TREADMILL)
normalizeIncline = (uint32_t)qRound(((treadmill *)Bike)->currentInclination().value() * 10);
normalizeIncline = (uint32_t)qRound(inclination * 10);
a = (normalizeIncline >> 8) & 0XFF;
b = normalizeIncline & 0XFF;
QByteArray inclineBytes;
@@ -40,7 +53,7 @@ int CharacteristicNotifier2ACD::notify(QByteArray &value) {
inclineBytes.append(a);
double ramp = 0;
if (dt == bluetoothdevice::TREADMILL)
ramp = qRadiansToDegrees(qAtan(((treadmill *)Bike)->currentInclination().value() / 100));
ramp = qRadiansToDegrees(qAtan(inclination / 100));
int16_t normalizeRamp = (int32_t)qRound(ramp * 10);
a = (normalizeRamp >> 8) & 0XFF;
b = normalizeRamp & 0XFF;

View File

@@ -972,7 +972,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(trxappgateusbRower);
} else if ((b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) ||
} else if (((b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) && !toorx_bike) ||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_elliptical)) &&
!trxappgateusbElliptical && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
this->setLastBluetoothDevice(b);
@@ -990,7 +990,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(trxappgateusbElliptical);
} else if (b.name().startsWith(QStringLiteral("Domyos-EL")) &&
} else if (b.name().startsWith(QStringLiteral("Domyos-EL")) && !settings.value(QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts).toBool() &&
!b.name().startsWith(QStringLiteral("DomyosBridge")) && !domyosElliptical && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1010,6 +1010,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
(b.name().startsWith(QStringLiteral("Domyos-EL")) && settings.value(QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts).toBool()) ||
(b.name().toUpper().startsWith("SF-") && b.name().midRef(3).toInt() > 0) ||
b.name().toUpper().startsWith(QStringLiteral("MYELLIPTICAL ")) ||
b.name().toUpper().startsWith(QStringLiteral("CARDIOPOWER EEGO")) ||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical)) && !ypooElliptical && filter) {
@@ -1390,17 +1393,17 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("T118_")) ||
b.name().toUpper().startsWith(QStringLiteral("TM4500")) ||
b.name().toUpper().startsWith(QStringLiteral("RUNN ")) ||
b.name().toUpper().startsWith(QStringLiteral("YS_T1MPLUST")) ||
b.name().toUpper().startsWith(QStringLiteral("YPOO-MINI PRO-")) ||
b.name().toUpper().startsWith(QStringLiteral("BFX_T9_")) ||
b.name().toUpper().startsWith(QStringLiteral("AB300S-")) ||
b.name().toUpper().startsWith(QStringLiteral("TF04-")) || // Sport Synology Z5 Treadmill #2415
b.name().toUpper().startsWith(QStringLiteral("FIT-")) || // FIT-1596
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && !b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) || // FIT-1596 and sports tech f37s treadmill #2412
b.name().toUpper().startsWith(QStringLiteral("LJJ-")) || // LJJ-02351A
b.name().toUpper().startsWith(QStringLiteral("WLT-EP-")) || // Flow elliptical
(b.name().toUpper().startsWith("SCHWINN 810")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-MC")) ||
(b.name().toUpper().startsWith(QStringLiteral("KS-HD-Z1D"))) || // Kingsmith WalkingPad Z1
(b.name().toUpper().startsWith(QStringLiteral("FIT-")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // sports tech f37s treadmill #2412
(b.name().toUpper().startsWith(QStringLiteral("NOBLEPRO CONNECT")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("TT8")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("ST90")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
@@ -1511,12 +1514,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(technogymmyrunrfcommTreadmill);
}
#endif
} else if ((b.name().toUpper().startsWith("TACX ") ||
b.name().toUpper().startsWith("NEO 3M ") ||
} else if ((b.name().toUpper().startsWith("TACX ") ||
b.name().toUpper().startsWith(QStringLiteral("THINK X")) ||
(b.name().toUpper().startsWith("VANRYSEL-HT")) ||
b.address() == QBluetoothAddress("C1:14:D9:9C:FB:01") || // specific TACX NEO 2 #1707
(b.name().toUpper().startsWith("TACX SMART BIKE"))) &&
!b.name().toUpper().startsWith("TACX SATORI") &&
ftms_bike.contains(QZSettings::default_ftms_bike) &&
!tacxneo2Bike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1572,6 +1576,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("YS_C1_")) || // Yesoul C1H
(b.name().toUpper().startsWith("YS_G1_")) || // Yesoul S3
(b.name().toUpper().startsWith("YS_G1MPLUS")) || // Yesoul G1M Plus
(b.name().toUpper().startsWith("YS_G1MMAX")) || // Yesoul G1M Max
(b.name().toUpper().startsWith("DS25-")) || // Bodytone DS25
(b.name().toUpper().startsWith("SCHWINN 510T")) ||
(b.name().toUpper().startsWith("3G CARDIO ")) ||
@@ -1615,27 +1620,35 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("CSRB") && b.name().length() == 11) ||
(b.name().toUpper().startsWith("DU30-")) || // BodyTone du30
(b.name().toUpper().startsWith("BIKZU_")) ||
(b.name().toUpper().startsWith("WLT8828")) ||
(b.name().toUpper().startsWith("VANRYSEL-HT")) ||
(b.name().toUpper().startsWith("WLT8828")) ||
(b.name().toUpper().startsWith("HARISON-X15")) ||
(b.name().toUpper().startsWith("FEIVON V2")) ||
(b.name().toUpper().startsWith("FELVON V2")) ||
(b.name().toUpper().startsWith("JUSTO")) ||
(b.name().toUpper().startsWith("MYCYCLE ")) ||
(b.name().toUpper().startsWith("T2 ")) ||
(b.name().toUpper().startsWith("DR") && b.name().length() == 2) ||
(b.name().toUpper().startsWith("RC-MAX-")) ||
(b.name().toUpper().startsWith("TPS-SPBIKE-2.0")) ||
(b.name().toUpper().startsWith("NEO BIKE SMART")) ||
(b.name().toUpper().startsWith("ZDRIVE")) ||
(b.name().toUpper().startsWith("TUNTURI E60-")) ||
(b.name().toUpper().startsWith("JFBK5.0")) ||
(b.name().toUpper().startsWith("NEO 3M ")) ||
(b.name().toUpper().startsWith("JFBK7.0")) ||
(b.name().toUpper().startsWith("SPEEDRACEX")) ||
(b.name().toUpper().startsWith("POOBOO")) ||
(b.name().toUpper().startsWith("ZYCLE ZPRO")) ||
(b.name().toUpper().startsWith("SM720I")) ||
(b.name().toUpper().startsWith("AVANTI")) ||
(b.name().toUpper().startsWith("T300P_")) ||
(b.name().toUpper().startsWith("T200_")) ||
(b.name().toUpper().startsWith("BZ9110 ")) ||
(b.name().toUpper().startsWith("TITAN 7000")) ||
(b.name().toUpper().startsWith("LYDSTO")) ||
(b.name().toUpper().startsWith("CYCLO_")) ||
(b.name().toUpper().startsWith("LCR")) ||
(b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) ||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
@@ -1673,7 +1686,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(wahooKickrSnapBike, &wahookickrsnapbike::debug, this, &bluetooth::debug);
wahooKickrSnapBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(wahooKickrSnapBike);
} else if (b.name().toUpper().startsWith("BIKE ") && b.name().length() == 9 &&
} else if (b.name().toUpper().startsWith("BIKE ") && b.name().midRef(5).toInt() > 0 &&
!technogymBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1719,6 +1732,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("RACER S")) ||
((b.name().toUpper().startsWith("KU")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith("ELITETRAINER")) ||
(b.name().toUpper().startsWith("TOUR 600")) ||
(b.name().toUpper().startsWith(QStringLiteral("QD")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith(QStringLiteral("DFC")) && b.name().length() == 3) ||
(b.name().toUpper().startsWith(QStringLiteral("ASSIOMA")) &&
@@ -1774,6 +1788,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("S4 COMMS")) ||
b.name().toUpper().startsWith(QStringLiteral("KS-WLT")) || // KS-WLT-W1
b.name().toUpper().startsWith(QStringLiteral("I-ROWER")) ||
b.name().toUpper().startsWith(QStringLiteral("YOROTO-RW-")) ||
b.name().toUpper().startsWith(QStringLiteral("SF-RW")) ||
b.name().toUpper().startsWith(QStringLiteral("DFIT-L-R")) ||
!b.name().compare(ftms_rower, Qt::CaseInsensitive) ||
@@ -2070,7 +2085,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(proformBike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
proformBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(proformBike);
} else if ((b.name().startsWith(QStringLiteral("I_TL")) || b.name().startsWith(QStringLiteral("I_IT"))) &&
} else if ((b.name().startsWith(QStringLiteral("I_TL")) || b.name().startsWith(QStringLiteral("I_IT")) ||
b.name().toUpper().contains(QStringLiteral("_IFIT TREADMILL"))) &&
!proformTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2214,6 +2230,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().startsWith(QStringLiteral("TRX ROUTE KEY")) ||
b.name().toUpper().startsWith(QStringLiteral("MASTERT40-")) ||
b.name().toUpper().startsWith(QStringLiteral("BH DUALKIT TREAD")) ||
b.name().toUpper().startsWith(QStringLiteral("TF-T")) ||
b.name().toUpper().startsWith(QStringLiteral("BH-TR-"))) && !toorx && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2312,6 +2329,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("VIFHTR2.1")) ||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK"))) ||
b.name().toUpper().contains(QStringLiteral("CR011R")) ||
(b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) && toorx_bike) ||
b.name().toUpper().startsWith(QStringLiteral("DKN MOTION"))) &&
(toorx_bike))) &&
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical && !iconsole_rower) {
@@ -2401,7 +2419,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
pafersBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(pafersBike);
} else if (((b.name().startsWith(QStringLiteral("FS-")) && snode_bike) ||
(b.name().toUpper().startsWith(QStringLiteral("TF-")) &&
(b.name().toUpper().startsWith(QStringLiteral("TF-")) && !b.name().toUpper().startsWith(QStringLiteral("TF-T")) &&
!horizon_treadmill_force_ftms)) && // TF-769DF2
!snodeBike &&
!ftmsBike && !fitPlusBike && filter) {
@@ -2755,6 +2773,8 @@ void bluetooth::connectedAndDiscovered() {
&bluetoothdevice::cadenceSensor);
connect(powerSensorRun, &bluetoothdevice::speedChanged, this->device(),
&bluetoothdevice::speedSensor);
connect(powerSensorRun, &bluetoothdevice::inclinationChanged, this->device(),
&bluetoothdevice::inclinationSensor);
connect(powerSensorRun, &bluetoothdevice::instantaneousStrideLengthChanged, this->device(),
&bluetoothdevice::instantaneousStrideLengthSensor);
connect(powerSensorRun, &bluetoothdevice::groundContactChanged, this->device(),

View File

@@ -149,6 +149,7 @@ double bluetoothdevice::inclinationDifficultOffset() { return m_inclination_diff
void bluetoothdevice::cadenceSensor(uint8_t cadence) { Q_UNUSED(cadence) }
void bluetoothdevice::powerSensor(uint16_t power) { Q_UNUSED(power) }
void bluetoothdevice::speedSensor(double speed) { Q_UNUSED(speed) }
void bluetoothdevice::inclinationSensor(double grade, double inclination) { Q_UNUSED(grade); Q_UNUSED(inclination) }
void bluetoothdevice::instantaneousStrideLengthSensor(double length) { Q_UNUSED(length); }
void bluetoothdevice::groundContactSensor(double groundContact) { Q_UNUSED(groundContact); }
void bluetoothdevice::verticalOscillationSensor(double verticalOscillation) { Q_UNUSED(verticalOscillation); }

View File

@@ -445,6 +445,7 @@ class bluetoothdevice : public QObject {
virtual void cadenceSensor(uint8_t cadence);
virtual void powerSensor(uint16_t power);
virtual void speedSensor(double speed);
virtual void inclinationSensor(double grade, double inclination);
virtual void changeResistance(resistance_t res);
virtual void changePower(int32_t power);
virtual void changeInclination(double grade, double percentage);

View File

@@ -89,7 +89,12 @@ void cycleopsphantombike::forceInclination(double inclination) {
QSettings settings;
// weight = kg * 100, grade = % * 10
setControlMode(ControlMode::ManualSlope, settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 100.0, inclination * 10.0);
// the OP says that doesn't work, let's use wattage instead
//setControlMode(ControlMode::ManualSlope, settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 100.0, inclination * 10.0);
double bikeResistanceOffset = settings.value(QZSettings::bike_resistance_offset, QZSettings::default_bike_resistance_offset).toInt();
double bikeResistanceGain = settings.value(QZSettings::bike_resistance_gain_f, QZSettings::default_bike_resistance_gain_f).toDouble();
double power = 100.0 + (inclination * 10.0 * bikeResistanceGain) + bikeResistanceOffset;
setControlMode(ControlMode::ManualPower, power);
}
void cycleopsphantombike::update() {

View File

@@ -2,6 +2,7 @@
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "homeform.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
@@ -513,6 +514,14 @@ void domyoselliptical::serviceScanDone(void) {
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &domyoselliptical::stateChanged);
if(!gattCommunicationChannelService) {
QSettings settings;
settings.setValue(QZSettings::domyos_elliptical_fmts, true);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Domyos Elliptial it's a FTMS. Restart QZ to apply the fix, thanks.");
return;
}
gattCommunicationChannelService->discoverDetails();
}

View File

@@ -52,6 +52,8 @@ void faketreadmill::update() {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
requestInclination = -100;
}
StepCount = StepCount.value() + 0.5;
_ergTable.collectTreadmillData(Speed.value(), _watts, Inclination.value());

View File

@@ -1,4 +1,5 @@
#include "fitplusbike.h"
#include "homeform.h"
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
@@ -639,7 +640,11 @@ void fitplusbike::characteristicChanged(const QLowEnergyCharacteristic &characte
.toString()
.startsWith(QStringLiteral("Disabled")))
Cadence = ((uint8_t)newValue.at(6));
m_watt = (double)((((uint8_t)newValue.at(10)) << 8) | ((uint8_t)newValue.at(9))) / 10.0;
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
m_watt = (double)((((uint8_t)newValue.at(10)) << 8) | ((uint8_t)newValue.at(9))) / 10.0;
}
/*if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool())
Speed = (double)((((uint8_t)newValue.at(4)) << 10) | ((uint8_t)newValue.at(9))) / 100.0;
@@ -950,16 +955,28 @@ void fitplusbike::serviceScanDone(void) {
QBluetoothUuid _gattCommunicationChannelServiceId((quint16)0xfff0);
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &fitplusbike::stateChanged);
gattCommunicationChannelService->discoverDetails();
if(gattCommunicationChannelService) {
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &fitplusbike::stateChanged);
gattCommunicationChannelService->discoverDetails();
if (sportstech_sx600) {
if (sportstech_sx600) {
gattCommunicationChannelServiceFTMS = m_control->createServiceObject(QBluetoothUuid((quint16)0x1826));
if (gattCommunicationChannelServiceFTMS) {
qDebug() << "FTMS found!";
connect(gattCommunicationChannelServiceFTMS, &QLowEnergyService::stateChanged, this,
&fitplusbike::stateChanged);
gattCommunicationChannelServiceFTMS->discoverDetails();
}
}
} else {
qDebug() << _gattCommunicationChannelServiceId << "not found!";
gattCommunicationChannelServiceFTMS = m_control->createServiceObject(QBluetoothUuid((quint16)0x1826));
if (gattCommunicationChannelServiceFTMS) {
qDebug() << "FTMS found!";
connect(gattCommunicationChannelServiceFTMS, &QLowEnergyService::stateChanged, this,
&fitplusbike::stateChanged);
gattCommunicationChannelServiceFTMS->discoverDetails();
if(gattCommunicationChannelServiceFTMS) {
QSettings settings;
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
qDebug() << "forcing FTMS bike since it has FTMS";
if(homeform::singleton())
homeform::singleton()->setToastRequested("FTMS bike found, restart the app to apply the change!");
}
}
}

View File

@@ -131,8 +131,12 @@ void ftmsbike::init() {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
bool ret = writeCharacteristic(write, sizeof(write), "requestControl", false, true);
write[0] = {FTMS_START_RESUME};
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
if(resistance_lvl_mode && DIRETO_XR) {
setWheelDiameter(2070.0);
} else {
write[0] = {FTMS_START_RESUME};
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
}
if(ret) {
initDone = true;
@@ -140,6 +144,13 @@ void ftmsbike::init() {
}
}
ftmsbike::~ftmsbike() {
// Set wheel circumference back to 2070 when object is destroyed
if (DIRETO_XR) {
setWheelDiameter(2070.0);
}
}
void ftmsbike::zwiftPlayInit() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
@@ -240,7 +251,7 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
} else {
if(JFBK5_0) {
if(JFBK5_0 || DIRETO_XR) {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00, 0x00};
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
write[2] = ((uint16_t)requestResistance * 10) >> 8;
@@ -310,8 +321,11 @@ void ftmsbike::update() {
if(DIRETO_XR && gears_zwift_ratio)
setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears()));
else
else {
forceResistance(requestResistance + (gears() * 5));
if(DIRETO_XR)
Resistance = requestResistance;
}
}
}
requestResistance = -1;
@@ -420,6 +434,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
bool heart = false;
bool watt_ignore_builtin =
settings.value(QZSettings::watt_ignore_builtin, QZSettings::default_watt_ignore_builtin).toBool();
qDebug() << characteristic.uuid() << newValue.length() << QStringLiteral(" << ") << newValue.toHex(' ');
@@ -443,6 +459,19 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
return;
}
if(characteristic.uuid() == QBluetoothUuid(QStringLiteral("00000002-19ca-4651-86e5-fa29dcdd09d1")) && newValue.at(0) == 0x03) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
m_watt = lockscreen::zwift_hub_getPowerFromBuffer(newValue.mid(1));
qDebug() << "Current power: " << m_watt.value();
Cadence = lockscreen::zwift_hub_getCadenceFromBuffer(newValue.mid(1));
qDebug() << "Current cadence: " << Cadence.value();
#endif
#endif
return;
}
// Wattbike Atom First Generation - Display Gears
if(WATTBIKE && characteristic.uuid() == QBluetoothUuid(QStringLiteral("b4cc1224-bc02-4cae-adb9-1217ad2860d1")) &&
@@ -475,6 +504,12 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
uint16_t word_flags;
};
// clean time in case for a long period we don't receive values
if(lastRefreshCharacteristicChanged2AD2.secsTo(now) > 5) {
qDebug() << "clearing lastRefreshCharacteristicChanged2AD2" << lastRefreshCharacteristicChanged2AD2 << now;
lastRefreshCharacteristicChanged2AD2 = now;
}
flags Flags;
int index = 0;
Flags.word_flags = (newValue.at(1) << 8) | newValue.at(0);
@@ -546,6 +581,8 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
(uint16_t)((uint8_t)newValue.at(index))));
index += 2;
if(Resistance.value() > 0) {
if(BIKE_)
d = d / 10.0;
Resistance = d;
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit resistanceRead(Resistance.value());
@@ -581,6 +618,10 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
// power table from an user
if(DU30_bike) {
m_watt = wattsFromResistance(Resistance.value());
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
} else if (LYDSTO && watt_ignore_builtin) {
m_watt = wattFromHR(true);
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
} else if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled")))
@@ -914,7 +955,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || ICSE || SCH_190U || DOMYOS) {
if (settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || ICSE || SCH_190U || DOMYOS || SMB1) {
QBluetoothUuid ftmsService((quint16)0x1826);
if (s->serviceUuid() != ftmsService) {
qDebug() << QStringLiteral("hammer racer bike wants to be subscribed only to FTMS service in order "
@@ -990,7 +1031,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
}
if (gattFTMSService && gattWriteCharControlPointId.isValid() &&
settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool()) {
(settings.value(QZSettings::hammer_racer_s, QZSettings::default_hammer_racer_s).toBool() || SMB1)) {
init();
}
@@ -1060,20 +1101,19 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
lastPacketFromFTMS.append(b.at(i));
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
if(!DIRETO_XR) {
if (gears() != 0) {
slope += (gears() * 50);
}
if(min_inclination > (((double)slope) / 100.0)) {
slope = min_inclination * 100;
qDebug() << "grade override due to min_inclination " << min_inclination;
}
slope *= gain;
slope += (offset * 100);
if (gears() != 0) {
slope += (gears() * 50);
}
if(min_inclination > (((double)slope) / 100.0)) {
slope = min_inclination * 100;
qDebug() << "grade override due to min_inclination " << min_inclination;
}
slope *= gain;
slope += (offset * 100);
b[3] = slope & 0xFF;
b[4] = slope >> 8;
@@ -1249,6 +1289,18 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("JFBK5.0 found");
resistance_lvl_mode = true;
JFBK5_0 = true;
} else if((bluetoothDevice.name().toUpper().startsWith("BIKE-"))) {
qDebug() << QStringLiteral("BIKE- found");
BIKE_ = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("SMB1"))) {
qDebug() << QStringLiteral("SMB1 found");
SMB1 = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("SPAX-BK-"))) {
qDebug() << QStringLiteral("SPAX-BK found");
resistance_lvl_mode = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("LYDSTO"))) {
qDebug() << QStringLiteral("LYDSTO found");
LYDSTO = true;
}
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {

View File

@@ -70,6 +70,7 @@ class ftmsbike : public bike {
Q_OBJECT
public:
ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
~ftmsbike();
bool connected() override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
@@ -133,6 +134,9 @@ class ftmsbike : public bike {
bool SS2K = false;
bool DIRETO_XR = false;
bool JFBK5_0 = false;
bool BIKE_ = false;
bool SMB1 = false;
bool LYDSTO = false;
uint8_t battery_level = 0;

View File

@@ -396,6 +396,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
}
}
notificationSubscribed = 0;
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
@@ -436,6 +437,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
descriptor.append((char)0x01);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
notificationSubscribed++;
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
@@ -451,6 +453,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
descriptor.append((char)0x02);
descriptor.append((char)0x00);
if (c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration).isValid()) {
notificationSubscribed++;
s->writeDescriptor(c.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
} else {
qDebug() << QStringLiteral("ClientCharacteristicConfiguration") << c.uuid()
@@ -529,8 +532,15 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
void ftmsrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
if (notificationSubscribed)
notificationSubscribed--;
qDebug() << "notificationSubscribed=" << notificationSubscribed;
if (!notificationSubscribed) {
initRequest = true;
emit connectedAndDiscovered();
}
}
void ftmsrower::descriptorRead(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {

View File

@@ -57,6 +57,7 @@ class ftmsrower : public rower {
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
volatile int notificationSubscribed = 0;
bool initDone = false;
bool initRequest = false;

View File

@@ -8,9 +8,47 @@
#include <QSettings>
#include <QThread>
heartratebelt::heartratebelt() {}
heartratebelt::heartratebelt() {
// Initialize the update timer to run every second
updateTimer = new QTimer(this);
updateTimer->setInterval(1000); // 1 second
connect(updateTimer, &QTimer::timeout, this, &heartratebelt::update);
updateTimer->start();
}
void heartratebelt::update() {}
heartratebelt::~heartratebelt() {
if (updateTimer) {
updateTimer->stop();
delete updateTimer;
}
}
void heartratebelt::update() {
QSettings settings;
// Check if we are in connecting state and more than 10 seconds have passed
if (m_control &&
m_control->state() == QLowEnergyController::ConnectingState &&
!connectingTime.isNull() &&
connectingTime.msecsTo(QDateTime::currentDateTime()) > CONNECTION_TIMEOUT
#ifdef Q_OS_IOS
&& !settings.value(QZSettings::ios_cache_heart_device, QZSettings::default_ios_cache_heart_device).toBool()
#endif
) {
emit debug(QStringLiteral("Connection timeout in ConnectingState - disconnecting and retrying..."));
disconnectBluetooth();
connectingTime = QDateTime(); // Reset the timestamp
// Reconnect after 1 second
QTimer::singleShot(1000, this, [this]() {
if (m_control) {
emit debug(QStringLiteral("Attempting to reconnect after timeout..."));
m_control->connectToDevice();
}
});
}
}
void heartratebelt::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
@@ -115,8 +153,7 @@ void heartratebelt::error(QLowEnergyController::Error err) {
void heartratebelt::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QSettings settings;
// QString heartRateBeltName = settings.value(QZSettings::heart_rate_belt_name),
// QStringLiteral("Disabled")).toString();//NOTE: clazy-unused-non-trivial-variable
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
@@ -168,6 +205,13 @@ bool heartratebelt::connected() {
void heartratebelt::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::ConnectingState) {
connectingTime = QDateTime::currentDateTime();
} else {
connectingTime = QDateTime(); // Reset timestamp for other states
}
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
m_control->connectToDevice();

View File

@@ -24,6 +24,7 @@
#include <QObject>
#include <QTime>
#include <QDateTime>
#include "treadmill.h"
@@ -31,11 +32,15 @@ class heartratebelt : public treadmill {
Q_OBJECT
public:
heartratebelt();
~heartratebelt();
bool connected() override;
private:
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattNotifyCharacteristic;
QDateTime connectingTime; // Timestamp when entering connecting state
static const int CONNECTION_TIMEOUT = 10000; // 10 seconds in milliseconds
QTimer* updateTimer; // Timer for periodic updates
signals:
void disconnected();
@@ -48,13 +53,11 @@ class heartratebelt : public treadmill {
void disconnectBluetooth();
private slots:
void characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue);
void stateChanged(QLowEnergyService::ServiceState state);
void controllerStateChanged(QLowEnergyController::ControllerState state);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
@@ -62,4 +65,4 @@ class heartratebelt : public treadmill {
void errorService(QLowEnergyService::ServiceError);
};
#endif // HEARTRATEBELT_H
#endif // HEARTRATEBELT_H

View File

@@ -182,6 +182,10 @@ void horizontreadmill::btinit() {
QStringLiteral("init"), false, true);
waitForAPacket();
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...0%");
}
init1:
initPacketRecv = false;
@@ -271,6 +275,10 @@ void horizontreadmill::btinit() {
goto init1;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...15%");
}
init2:
initPacketRecv = false;
@@ -357,6 +365,10 @@ void horizontreadmill::btinit() {
goto init2;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...25%");
}
init3:
initPacketRecv = false;
@@ -443,6 +455,10 @@ void horizontreadmill::btinit() {
goto init3;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...35%");
}
init4:
initPacketRecv = false;
@@ -529,6 +545,10 @@ void horizontreadmill::btinit() {
goto init4;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...50%");
}
init5:
initPacketRecv = false;
@@ -615,6 +635,10 @@ void horizontreadmill::btinit() {
goto init5;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...65%");
}
init6:
initPacketRecv = false;
@@ -701,6 +725,10 @@ void horizontreadmill::btinit() {
goto init6;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...80%");
}
init7:
initPacketRecv = false;
@@ -787,6 +815,10 @@ void horizontreadmill::btinit() {
goto init7;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...90%");
}
init8:
initPacketRecv = false;
@@ -812,6 +844,10 @@ void horizontreadmill::btinit() {
waitForAPacket();
goto init8;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization completed!");
}
}
messageID = 0x10;
}
@@ -1467,7 +1503,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
1.60934); // miles/h
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Inclination = treadmillInclinationOverride((double)((uint8_t)lastPacketComplete.at(30)) / 10.0);
parseInclination(treadmillInclinationOverride((double)((uint8_t)lastPacketComplete.at(30)) / 10.0));
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
if (firstDistanceCalculated && watts(weight))
@@ -1493,7 +1529,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
1.60934); // miles/h
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
Inclination = treadmillInclinationOverride((double)((uint8_t)newValue.at(63)) / 10.0);
parseInclination(treadmillInclinationOverride((double)((uint8_t)newValue.at(63)) / 10.0));
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
if (firstDistanceCalculated && watts(weight))
@@ -1777,13 +1813,13 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
if (Flags.inclination) {
if(!tunturi_t60_treadmill && !ICONCEPT_FTMS_treadmill)
Inclination = treadmillInclinationOverride((double)(
parseInclination(treadmillInclinationOverride((double)(
(int16_t)(
((int16_t)(int8_t)newValue.at(index + 1) << 8) |
(uint8_t)newValue.at(index)
)
) /
10.0);
10.0));
else if(ICONCEPT_FTMS_treadmill) {
uint8_t val1 = (uint8_t)newValue.at(index);
uint8_t val2 = (uint8_t)newValue.at(index + 1);
@@ -2384,6 +2420,7 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
QSettings settings;
bluetoothDevice = device;
if (device.name().toUpper().startsWith(QStringLiteral("MOBVOI TMP"))) {
@@ -2407,6 +2444,7 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("TUNTURI T60 TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("F85"))) {
sole_f85_treadmill = true;
settings.setValue(QZSettings::treadmill_step_incline, 1.0); // this treadmill doesn't handle 0.5 inclination
minInclination = -5.0;
qDebug() << QStringLiteral("SOLE F85 TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("F89"))) {
@@ -2448,6 +2486,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("MX-TM "))) {
qDebug() << QStringLiteral("MX-TM found");
MX_TM = true;
} else if (device.name().toUpper().startsWith(QStringLiteral("FIT-"))) {
qDebug() << QStringLiteral("FIT- found");
FIT = true;
}
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
@@ -3172,7 +3213,7 @@ void horizontreadmill::testProfileCRC() {
double horizontreadmill::minStepInclination() {
QSettings settings;
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill)
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL || sole_s77_treadmill || FIT)
return 1.0;
else
return 0.5;

View File

@@ -107,6 +107,7 @@ class horizontreadmill : public treadmill {
bool BOWFLEX_T9 = false;
bool YPOO_MINI_PRO = false;
bool MX_TM = false;
bool FIT = false;
void testProfileCRC();
void updateProfileCRC();

View File

@@ -446,7 +446,7 @@ void kingsmithr2treadmill::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xFED7);
QBluetoothUuid _gattNotifyCharacteristicId((quint16)0xFED8);
if (KS_NACH_X21C) {
if (KS_NACH_X21C || KS_NGCH_G1C_2) {
_gattWriteCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED7-0000-1000-8000-00805f9b34fb"));
_gattNotifyCharacteristicId = QBluetoothUuid(QStringLiteral("0002FED8-0000-1000-8000-00805f9b34fb"));
} else if (KS_NGCH_G1C || KS_NACH_MXG || KS_NACH_X21C_2) {
@@ -515,6 +515,12 @@ void kingsmithr2treadmill::serviceScanDone(void) {
qDebug() << "KS_NACH_X21C default service id not found";
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00011234-0000-1000-8000-00805f9b34fb"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
} else if(gattCommunicationChannelService == nullptr && KS_NGCH_G1C) {
KS_NGCH_G1C_2 = true;
KS_NGCH_G1C = false;
qDebug() << "KS_NGCH_G1C default service id not found";
_gattCommunicationChannelServiceId = QBluetoothUuid(QStringLiteral("00021234-0000-1000-8000-00805f9b34fb"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&kingsmithr2treadmill::stateChanged);

View File

@@ -99,6 +99,7 @@ class kingsmithr2treadmill : public treadmill {
bool KS_NACH_X21C = false;
bool KS_NACH_X21C_2 = false;
bool KS_NGCH_G1C = false;
bool KS_NGCH_G1C_2 = false;
bool KS_NACH_MXG = false;
#ifdef Q_OS_IOS

View File

@@ -142,7 +142,7 @@ void lifespantreadmill::changeInclinationRequested(double grade, double percenta
uint32_t lifespantreadmill::GetStepsFromPacket(const QByteArray& packet) {
if (packet.length() < 4) return 0;
return (packet[2] << 8) | packet[3];
return ((uint16_t)((uint8_t)packet[2]) << 8) | (uint16_t)((uint8_t)packet[3]);
}
void lifespantreadmill::update() {

View File

@@ -268,7 +268,7 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
requestInclination = -100;
}
int currentRequestInclination = requestInclination;
double currentRequestInclination = requestInclination;
// since the motor of the treadmill is slow, let's filter the inclination changes to more than 1 second
if (requestInclination != -100 && lastInclinationChanged.secsTo(QDateTime::currentDateTime()) > 1) {
@@ -286,6 +286,7 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
bool nordictrack_treadmill_t8_5s = settings.value(QZSettings::nordictrack_treadmill_t8_5s, QZSettings::default_nordictrack_treadmill_t8_5s).toBool();
bool nordictrack_treadmill_x14i = settings.value(QZSettings::nordictrack_treadmill_x14i, QZSettings::nordictrack_treadmill_x14i).toBool();
bool proform_treadmill_carbon_t7 = settings.value(QZSettings::proform_treadmill_carbon_t7, QZSettings::default_proform_treadmill_carbon_t7).toBool();
bool nordictrack_treadmill_1750_adb = settings.value(QZSettings::nordictrack_treadmill_1750_adb, QZSettings::default_nordictrack_treadmill_1750_adb).toBool();
if (requestSpeed != -1) {
int x1 = 1845;
@@ -305,6 +306,10 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
// 458 0 183 10 mph
y1Speed = (int) (458 - (27.5 * ((Speed.value() * 0.621371) - 1)));
y2 = y1Speed - (int)(((requestSpeed - Speed.value()) * 0.621371) * 27.5);
} else if(nordictrack_treadmill_1750_adb) {
x1 = 1206;
y1Speed = (int) (603 - (34.0 * ((Speed.value() * 0.621371) - 0.5)));
y2 = 603 - (int)(((requestSpeed * 0.621371) - 0.5) * 34.0);
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Speed) + " " +
@@ -347,6 +352,10 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
// 458 0 183 10%
y1Inclination = (int) (458 - (27.5 * (currentInclination().value())));
y2 = y1Inclination - (int)((requestInclination - currentInclination().value()) * 27.5);
} else if(nordictrack_treadmill_1750_adb) {
x1 = 75;
y1Inclination = (int) (603 - (21.72222222 * (currentInclination().value() + 3.0)));
y2 = 603 - (int)((requestInclination + 3.0) * 21.72222222);
}
lastCommand = "input swipe " + QString::number(x1) + " " + QString::number(y1Inclination) + " " +

View File

@@ -152,7 +152,28 @@ uint16_t proformbike::wattsFromResistance(resistance_t resistance) {
}
void proformbike::forceResistance(resistance_t requestResistance) {
if (proform_studio || proform_tdf_10) {
if(proform_bike_PFEVEX71316_0) {
// Value: 0012020402120812020400000004000000000700
// Value: ff040001002c0000000000000000000000000000
// Value: 0012020402120812020400000004000000001000
// Value: ff04000100350000000000000000000000000000
const uint8_t res1[] = {0xfe, 0x02, 0x16, 0x03};
uint8_t res2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x12, 0x08, 0x12, 0x02, 0x04,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00};
res2[18] = requestResistance;
uint8_t res3[] = {0xff, 0x04, 0x00, 0x10, 0x00, 0x35, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
res3[5] = 0x25 + requestResistance;
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, false);
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, false);
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
} else if (proform_studio || proform_tdf_10) {
const uint8_t res1[] = {0xfe, 0x02, 0x16, 0x03};
uint8_t res2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x12, 0x08, 0x12, 0x02, 0x04,
0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x06, 0x06};
@@ -167,6 +188,89 @@ void proformbike::forceResistance(resistance_t requestResistance) {
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, false);
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, false);
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
} else if (proform_xbike) {
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x6c, 0x07, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt994
const uint8_t res2[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x54, 0x0b, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1325
const uint8_t res3[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x3c, 0x0f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1636
const uint8_t res4[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x24, 0x13, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1786
const uint8_t res5[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x0c, 0x17, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt1905
const uint8_t res6[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xf4, 0x1a, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2015
const uint8_t res7[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xdc, 0x1e, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2134
const uint8_t res8[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xc4, 0x22, 0x00, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2209
const uint8_t res9[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xac, 0x26, 0x00, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt2293
const uint8_t res10[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xdc, 0x1e, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3007
const uint8_t res11[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x0c, 0x17, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3502
const uint8_t res12[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x24, 0x13, 0x00, 0x4e, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3578
const uint8_t res13[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x3c, 0x0f, 0x00, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3666
const uint8_t res14[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x54, 0x0b, 0x00, 0x76, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3740
const uint8_t res15[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x6c, 0x07, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3824
const uint8_t res16[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x84, 0x03, 0x00, 0x9e, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt3920
const uint8_t res17[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02, 0x00, 0x10, 0x04, 0x00, 0x28, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt4208
const uint8_t res18[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02, 0x00, 0x10, 0x01, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt4227
const uint8_t res19[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x02, 0x00, 0x10, 0x03, 0x00, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00}; // pkt4235
uint8_t noOpData7[] = {0xfe, 0x02, 0x0d, 0x02};
writeCharacteristic((uint8_t *)noOpData7, sizeof(noOpData7), QStringLiteral("resrequest"), false, false);
switch (requestResistance) {
case 1:
writeCharacteristic((uint8_t *)res1, sizeof(res1), QStringLiteral("resistance1"), false, true);
break;
case 2:
writeCharacteristic((uint8_t *)res2, sizeof(res2), QStringLiteral("resistance2"), false, true);
break;
case 3:
writeCharacteristic((uint8_t *)res3, sizeof(res3), QStringLiteral("resistance3"), false, true);
break;
case 4:
writeCharacteristic((uint8_t *)res4, sizeof(res4), QStringLiteral("resistance4"), false, true);
break;
case 5:
writeCharacteristic((uint8_t *)res5, sizeof(res5), QStringLiteral("resistance5"), false, true);
break;
case 6:
writeCharacteristic((uint8_t *)res6, sizeof(res6), QStringLiteral("resistance6"), false, true);
break;
case 7:
writeCharacteristic((uint8_t *)res7, sizeof(res7), QStringLiteral("resistance7"), false, true);
break;
case 8:
writeCharacteristic((uint8_t *)res8, sizeof(res8), QStringLiteral("resistance8"), false, true);
break;
case 9:
writeCharacteristic((uint8_t *)res9, sizeof(res9), QStringLiteral("resistance9"), false, true);
break;
case 10:
writeCharacteristic((uint8_t *)res10, sizeof(res10), QStringLiteral("resistance10"), false, true);
break;
case 11:
writeCharacteristic((uint8_t *)res11, sizeof(res11), QStringLiteral("resistance11"), false, true);
break;
case 12:
writeCharacteristic((uint8_t *)res12, sizeof(res12), QStringLiteral("resistance12"), false, true);
break;
case 13:
writeCharacteristic((uint8_t *)res13, sizeof(res13), QStringLiteral("resistance13"), false, true);
break;
case 14:
writeCharacteristic((uint8_t *)res14, sizeof(res14), QStringLiteral("resistance14"), false, true);
break;
case 15:
writeCharacteristic((uint8_t *)res15, sizeof(res15), QStringLiteral("resistance15"), false, true);
break;
case 16:
writeCharacteristic((uint8_t *)res16, sizeof(res16), QStringLiteral("resistance16"), false, true);
break;
case 17:
writeCharacteristic((uint8_t *)res17, sizeof(res17), QStringLiteral("resistance17"), false, true);
break;
case 18:
writeCharacteristic((uint8_t *)res18, sizeof(res18), QStringLiteral("resistance18"), false, true);
break;
case 19:
writeCharacteristic((uint8_t *)res19, sizeof(res19), QStringLiteral("resistance19"), false, true);
break;
}
} else if (proform_hybrid_trainer_PFEL03815 || proform_bike_sb) {
const uint8_t res1[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01,
0x04, 0x32, 0x02, 0x00, 0x4b, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -778,24 +882,56 @@ void proformbike::update() {
uint8_t noOpData6_proform_bike_PFEVEX71316_1[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x81, 0xab, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// proform_bike_PFEVEX71316_0
uint8_t noOpData1_proform_bike_PFEVEX71316_0[] = {0xfe, 0x02, 0x14, 0x03};
uint8_t noOpData2_proform_bike_PFEVEX71316_0[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x08, 0x10, 0x02, 0x00,
0x0a, 0x3e, 0x90, 0x30, 0x04, 0x00, 0x00, 0x50, 0x00, 0x00};
uint8_t noOpData3_proform_bike_PFEVEX71316_0[] = {0xff, 0x02, 0x18, 0x8e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4_proform_bike_PFEVEX71316_0[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData5_proform_bike_PFEVEX71316_0[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x08, 0x13, 0x02, 0x00,
0x0d, 0x81, 0x0e, 0x41, 0x00, 0x00, 0x40, 0x00, 0x00, 0x80};
uint8_t noOpData6_proform_bike_PFEVEX71316_0[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x80, 0x3a, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData2_proform_bike_325_csx[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00, 0x0d, 0x3c, 0x9e, 0x31, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80};
uint8_t noOpData3_proform_bike_325_csx[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x85, 0xb9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData5_proform_bike_325_csx[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x00, 0x03, 0x80, 0x00, 0x40, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00};
// proform_xbike
uint8_t noOpData2_proform_xbike[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00,
0x0d, 0x3c, 0x9c, 0x31, 0x00, 0x00, 0x40, 0x40, 0x00, 0x80};
uint8_t noOpData3_proform_xbike[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x81, 0xb3, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData5_proform_xbike[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x00,
0x03, 0x80, 0x00, 0x40, 0xd5, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx || proform_bike_325_csx) {
if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_225_csx || proform_bike_325_csx || proform_xbike) {
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData1_proform_bike_PFEVEX71316_0, sizeof(noOpData1_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
} else {
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
}
break;
case 1:
if (proform_studio || proform_tdf_10)
if (proform_xbike) {
writeCharacteristic(noOpData2_proform_xbike, sizeof(noOpData2_proform_xbike), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData2_proform_studio, sizeof(noOpData2_proform_studio), QStringLiteral("noOp"));
else if (proform_bike_325_csx) {
writeCharacteristic(noOpData2_proform_bike_325_csx, sizeof(noOpData2_proform_bike_325_csx),
QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData2_proform_bike_PFEVEX71316_0, sizeof(noOpData2_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
} else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
writeCharacteristic(noOpData2_nordictrack_gx_2_7, sizeof(noOpData2_nordictrack_gx_2_7),
QStringLiteral("noOp"));
@@ -821,7 +957,9 @@ void proformbike::update() {
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
if (proform_studio || proform_tdf_10)
if (proform_xbike) {
writeCharacteristic(noOpData3_proform_xbike, sizeof(noOpData3_proform_xbike), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData3_proform_studio, sizeof(noOpData3_proform_studio), QStringLiteral("noOp"));
else if (proform_bike_325_csx) {
writeCharacteristic(noOpData3_proform_bike_325_csx, sizeof(noOpData3_proform_bike_325_csx),
@@ -829,6 +967,8 @@ void proformbike::update() {
} else if (proform_tour_de_france_clc) {
writeCharacteristic(noOpData3_proform_tour_de_france_clc, sizeof(noOpData3_proform_tour_de_france_clc),
QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData3_proform_bike_PFEVEX71316_0, sizeof(noOpData3_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
} else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
writeCharacteristic(noOpData3_nordictrack_gx_2_7, sizeof(noOpData3_nordictrack_gx_2_7),
QStringLiteral("noOp"));
@@ -851,7 +991,10 @@ void proformbike::update() {
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
break;
case 3:
if (proform_studio || proform_tdf_10)
if (proform_xbike) {
innerWriteResistance();
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData4_proform_studio, sizeof(noOpData4_proform_studio), QStringLiteral("noOp"));
else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
innerWriteResistance();
@@ -860,6 +1003,8 @@ void proformbike::update() {
innerWriteResistance();
writeCharacteristic(noOpData4_proform_hybrid_trainer_PFEL03815,
sizeof(noOpData4_proform_hybrid_trainer_PFEL03815), QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData4_proform_bike_PFEVEX71316_0, sizeof(noOpData4_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
} else if (proform_bike_sb || proform_bike_325_csx) {
innerWriteResistance();
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
@@ -869,7 +1014,9 @@ void proformbike::update() {
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
if (proform_studio || proform_tdf_10)
if (proform_xbike) {
writeCharacteristic(noOpData5_proform_xbike, sizeof(noOpData5_proform_xbike), QStringLiteral("noOp"));
} else if (proform_studio || proform_tdf_10)
writeCharacteristic(noOpData5_proform_studio, sizeof(noOpData5_proform_studio), QStringLiteral("noOp"));
else if (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci) {
writeCharacteristic(noOpData5_nordictrack_gx_2_7, sizeof(noOpData5_nordictrack_gx_2_7),
@@ -877,6 +1024,8 @@ void proformbike::update() {
} else if (proform_hybrid_trainer_PFEL03815) {
writeCharacteristic(noOpData5_proform_hybrid_trainer_PFEL03815,
sizeof(noOpData5_proform_hybrid_trainer_PFEL03815), QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData5_proform_bike_PFEVEX71316_0, sizeof(noOpData5_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"));
} else if (proform_bike_325_csx) {
writeCharacteristic(noOpData5_proform_bike_325_csx, sizeof(noOpData5_proform_bike_325_csx),
QStringLiteral("noOp"));
@@ -898,6 +1047,21 @@ void proformbike::update() {
else if (proform_tour_de_france_clc) {
writeCharacteristic(noOpData6_proform_tour_de_france_clc, sizeof(noOpData6_proform_tour_de_france_clc),
QStringLiteral("noOp"));
} else if(proform_bike_PFEVEX71316_0) {
writeCharacteristic(noOpData6_proform_bike_PFEVEX71316_0, sizeof(noOpData6_proform_bike_PFEVEX71316_0), QStringLiteral("noOp"), false, true);
if (requestResistance != -1)
innerWriteResistance();
else if (requestInclination != -100) {
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("noOp"));
// only 0.5 steps ara available
double inc = qRound(requestInclination * 2.0) / 2.0;
if (inc != currentInclination().value()) {
emit debug(QStringLiteral("writing inclination ") + QString::number(requestInclination) +
" rounded " + QString::number(inc));
forceIncline(inc);
}
requestInclination = -100;
}
} else if (proform_bike_225_csx) {
writeCharacteristic(noOpData6_proform_bike_225_csx, sizeof(noOpData6_proform_bike_225_csx),
QStringLiteral("noOp"));
@@ -936,14 +1100,14 @@ void proformbike::update() {
counterPoll++;
if (counterPoll > 6) {
counterPoll = 0;
} else if(counterPoll == 6 && proform_bike_225_csx) {
} else if(counterPoll == 6 && (proform_bike_225_csx || proform_bike_PFEVEX71316_0)) {
counterPoll = 0;
} else if (counterPoll == 6 &&
(proform_tour_de_france_clc || proform_cycle_trainer_400 || proform_bike_PFEVEX71316_1) &&
requestResistance == -1) {
// this bike sends the frame noOpData7 only when it needs to change the resistance
counterPoll = 0;
} else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_325_csx)) {
} else if (counterPoll == 5 && (nordictrack_gx_2_7 || proform_cycle_trainer_300_ci || proform_hybrid_trainer_PFEL03815 || proform_bike_sb || proform_bike_325_csx || proform_xbike)) {
counterPoll = 0;
}
@@ -970,7 +1134,7 @@ void proformbike::update() {
}
bool proformbike::inclinationAvailableByHardware() {
if (proform_studio || proform_tdf_10)
if (proform_studio || proform_tdf_10 || proform_bike_PFEVEX71316_0)
return true;
else
return false;
@@ -1048,12 +1212,13 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
lastPacket = newValue;
if (proform_studio || proform_tdf_10) {
if (proform_studio || proform_tdf_10 || proform_bike_PFEVEX71316_0) {
if (newValue.length() != 20 ||
// frames with power
(newValue.at(0) != 0x00 && newValue.at(0) != 0x01) || newValue.at(1) != 0x12 ||
(newValue.at(0) == 0x00 &&
(newValue.at(2) != 0x01 || newValue.at(3) != 0x04 || newValue.at(4) != 0x02 || newValue.at(5) != 0x2c))) {
(newValue.at(2) != 0x01 || newValue.at(3) != 0x04 || newValue.at(4) != 0x02 || (proform_bike_PFEVEX71316_0 ? newValue.at(5) != 0x30 : newValue.at(5) != 0x2c))) ||
(proform_bike_PFEVEX71316_0 && (uint8_t)newValue.at(2) == 0xFF && (uint8_t)newValue.at(3) == 0xFF)) {
return;
}
@@ -1418,6 +1583,55 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
m_pelotonResistance = 100;
break;
}
} else if (proform_xbike) {
switch ((uint8_t)newValue.at(11)) {
case 0x07:
Resistance = 2;
m_pelotonResistance = 25;
break;
case 0x0b:
case 0x0c:
Resistance = 3;
m_pelotonResistance = 35;
break;
case 0x0f:
Resistance = 4;
m_pelotonResistance = 40;
break;
case 0x13:
Resistance = 5;
m_pelotonResistance = 50;
break;
case 0x17:
Resistance = 6;
m_pelotonResistance = 60;
break;
case 0x1a:
case 0x1b:
Resistance = 7;
m_pelotonResistance = 65;
break;
case 0x1f:
Resistance = 8;
m_pelotonResistance = 75;
break;
case 0x23:
case 0x24:
Resistance = 9;
m_pelotonResistance = 85;
break;
case 0x26:
case 0x27:
Resistance = 10;
m_pelotonResistance = 100;
break;
/* when the proform bike is changing the resistance, it sends some strange values, so i'm keeping
the last good one default: Resistance = 0; m_pelotonResistance = 0; break;
*/
default:
Resistance = 1;
m_pelotonResistance = 10;
}
} else if (!nordictrack_gx_2_7) {
switch ((uint8_t)newValue.at(11)) {
case 0x00:
@@ -1677,6 +1891,8 @@ void proformbike::btinit() {
proform_cycle_trainer_400 = settings.value(QZSettings::proform_cycle_trainer_400, QZSettings::default_proform_cycle_trainer_400).toBool();
proform_bike_PFEVEX71316_1 = settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1).toBool();
nordictrack_gx_44_pro = settings.value(QZSettings::nordictrack_gx_44_pro, QZSettings::default_nordictrack_gx_44_pro).toBool();
proform_bike_PFEVEX71316_0 = settings.value(QZSettings::proform_bike_PFEVEX71316_0, QZSettings::default_proform_bike_PFEVEX71316_0).toBool();
proform_xbike = settings.value(QZSettings::proform_xbike, QZSettings::default_proform_xbike).toBool();
if(nordictrack_GX4_5_bike)
max_resistance = 25;
@@ -1741,6 +1957,128 @@ void proformbike::btinit() {
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else if (settings.value(QZSettings::proform_xbike, QZSettings::default_proform_xbike).toBool()) {
max_resistance = 10;
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x80, 0x8b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x07, 0x04, 0x88, 0x93,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData6[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x07, 0x28, 0x90, 0x07, 0x01, 0xdb, 0x20, 0x77, 0xc4, 0x13, 0x60, 0xb7, 0x1c, 0x7b};
uint8_t initData11[] = {0x01, 0x12, 0xc0, 0x27, 0x84, 0xe3, 0x40, 0xa7, 0x0c, 0x9b, 0xe0, 0x77, 0xc4, 0x53, 0xa0, 0x37, 0xbc, 0x3b, 0x80, 0x07};
uint8_t initData12[] = {0xFF, 0x08, 0x84, 0x03, 0x80, 0x88, 0x02, 0x00, 0x00, 0xEE, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData13[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData15[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3d, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData16[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData17[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData18[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt582[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t xbike_pkt585[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x00, 0x0d, 0x00,
0x10, 0x00, 0xc0, 0x1c, 0x4c, 0x00, 0x00, 0xe0};
uint8_t xbike_pkt588[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x51, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt594[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t xbike_pkt599[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt602[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xa9, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt607[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t xbike_pkt610[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt613[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt618[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t xbike_pkt621[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x07, 0x13, 0x02, 0x0c, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t xbike_pkt624[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa8, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
// The initialization sequence requires specific timing between messages
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt582, sizeof(xbike_pkt582), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt585, sizeof(xbike_pkt585), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt588, sizeof(xbike_pkt588), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt594, sizeof(xbike_pkt594), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt599, sizeof(xbike_pkt599), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt602, sizeof(xbike_pkt602), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt607, sizeof(xbike_pkt607), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt610, sizeof(xbike_pkt610), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt613, sizeof(xbike_pkt613), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt618, sizeof(xbike_pkt618), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt621, sizeof(xbike_pkt621), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(xbike_pkt624, sizeof(xbike_pkt624), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else if (settings.value(QZSettings::nordictrack_gx_44_pro, QZSettings::default_nordictrack_gx_44_pro).toBool()) {
max_resistance = 25; // Most NordicTrack bikes use resistance range 1-25
@@ -1821,6 +2159,109 @@ void proformbike::btinit() {
QThread::msleep(400);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else if (settings.value(QZSettings::proform_bike_PFEVEX71316_0, QZSettings::default_proform_bike_PFEVEX71316_0)
.toBool()) {
max_resistance = 26;
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData3[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x08, 0x04, 0x80, 0x8c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x08, 0x04, 0x88, 0x94,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData6[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData8[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xfe, 0x02, 0x2c, 0x04};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x08, 0x28, 0x90, 0x04,
0x00, 0xc1, 0x58, 0xfd, 0x90, 0x31, 0xd0, 0x75, 0x28, 0xc1};
uint8_t initData11[] = {0x01, 0x12, 0x78, 0x2d, 0xc0, 0x71, 0x20, 0xf5, 0x88, 0x41,
0x18, 0xdd, 0x90, 0x51, 0x10, 0xd5, 0x88, 0x41, 0x38, 0xed};
uint8_t initData12[] = {0xff, 0x08, 0xa0, 0x91, 0x40, 0x78, 0x02, 0x00, 0x00, 0x11,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(400);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData13[] = {0xfe, 0x02, 0x17, 0x03};
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x08, 0x13, 0x02, 0x00,
0x0d, 0x00, 0x10, 0x00, 0xdc, 0x1c, 0x4c, 0x00, 0x00, 0xe0};
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData15[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x6e, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData16[] = {0xfe, 0x02, 0x17, 0x03};
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData17[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x08, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData18[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xaa, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData19[] = {0xfe, 0x02, 0x17, 0x03};
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData20[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x08, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
QThread::msleep(400);
uint8_t initData21[] = {0xff, 0x05, 0x00, 0x80, 0x01, 0x00, 0xaa, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
QThread::msleep(400);
} else if (settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool() ||
settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1)
.toBool()) {

View File

@@ -93,6 +93,8 @@ class proformbike : public bike {
bool proform_cycle_trainer_400 = false;
bool proform_bike_PFEVEX71316_1 = false;
bool nordictrack_gx_44_pro = false;
bool proform_bike_PFEVEX71316_0 = false;
bool proform_xbike = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -241,10 +241,17 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
((double)(((int16_t)((int8_t)newValue.at(index + 1)) << 8) | (int16_t)((uint8_t)newValue.at(index)))) /
10.0;
// steps of 0.5 only to send to the Inclination override function
inc = qRound(inc * 2.0) / 2.0;
Inclination = treadmillInclinationOverride(inc);
if(!areInclinationSettingsDefault()) {
inc = qRound(inc * 2.0) / 2.0;
Inclination = treadmillInclinationOverride(inc);
} else {
Inclination = inc;
}
index += 4; // the ramo value is useless
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
if(stryd_inclination_instead_treadmill)
emit inclinationChanged(Inclination.value(), Inclination.value());
}
if (Flags.elevation) {

View File

@@ -11,6 +11,7 @@
#include "keepawakehelper.h"
#include <QLowEnergyConnectionParameters>
#endif
#include "homeform.h"
#include <chrono>
@@ -28,6 +29,17 @@ tacxneo2::tacxneo2(bool noWriteResistance, bool noHeartService) {
void tacxneo2::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
if(!gattCustomService) {
qDebug() << "gattCustomService is null!";
QSettings settings;
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
qDebug() << "forcing FTMS bike since it has FTMS";
if(homeform::singleton())
homeform::singleton()->setToastRequested("FTMS bike found, restart the app to apply the change!");
return;
}
QEventLoop loop;
QTimer timeout;
if (wait_for_response) {

View File

@@ -17,8 +17,14 @@ void treadmill::changeSpeed(double speed) {
speed -= settings.value(QZSettings::speed_offset, QZSettings::default_speed_offset).toDouble();
if(stryd_speed_instead_treadmill && Speed.value() > 0) {
double delta = (Speed.value() - rawSpeed.value());
qDebug() << "stryd_speed_instead_treadmill so override speed by " << delta;
speed -= delta;
double maxAllowedDelta = speed * 0.20; // 20% of the speed request
if (std::abs(delta) <= maxAllowedDelta) {
qDebug() << "stryd_speed_instead_treadmill so override speed by " << delta;
speed -= delta;
} else {
qDebug() << "Delta" << delta << "exceeds 20% threshold of" << maxAllowedDelta << "- not applying correction";
}
}
qDebug() << "changeSpeed" << speed << autoResistanceEnable << m_difficult << m_difficult_offset << m_lastRawSpeedRequested;
RequestedSpeed = (speed * m_difficult) + m_difficult_offset;
@@ -30,6 +36,7 @@ void treadmill::changeInclination(double grade, double inclination) {
double treadmill_incline_min = settings.value(QZSettings::treadmill_incline_min, QZSettings::default_treadmill_incline_min).toDouble();
double treadmill_incline_max = settings.value(QZSettings::treadmill_incline_max, QZSettings::default_treadmill_incline_max).toDouble();
double step = settings.value(QZSettings::treadmill_step_incline, QZSettings::default_treadmill_step_incline).toDouble();
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
if(grade < treadmill_incline_min) {
grade = treadmill_incline_min;
@@ -39,6 +46,18 @@ void treadmill::changeInclination(double grade, double inclination) {
qDebug() << "grade override due to treadmill_incline_max" << grade;
}
if(stryd_inclination_instead_treadmill) {
double delta = (Inclination.value() - rawInclination.value());
double maxAllowedDelta = grade * 0.20; // 20% of the inclination request
if (std::abs(delta) <= maxAllowedDelta) {
qDebug() << "stryd_inclination_instead_treadmill so override inclination by " << delta;
grade -= delta;
} else {
qDebug() << "Delta" << delta << "exceeds 20% threshold of" << maxAllowedDelta << "- not applying correction";
}
}
m_lastRawInclinationRequested = grade;
Q_UNUSED(inclination);
qDebug() << "changeInclination" << grade << autoResistanceEnable << m_inclination_difficult
@@ -145,6 +164,7 @@ void treadmill::clearStats() {
elapsed.clear(true);
Speed.clear(false);
rawSpeed.clear(false);
rawInclination.clear(false);
KCal.clear(true);
Distance.clear(true);
Distance1s.clear(true);
@@ -169,6 +189,7 @@ void treadmill::setPaused(bool p) {
elapsed.setPaused(p);
Speed.setPaused(p);
rawSpeed.setPaused(p);
rawInclination.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
Distance1s.setPaused(p);
@@ -190,6 +211,7 @@ void treadmill::setLap() {
elapsed.setLap(true);
Speed.setLap(false);
rawSpeed.setLap(false);
rawInclination.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
Distance1s.setLap(true);
@@ -234,7 +256,17 @@ void treadmill::powerSensor(uint16_t power) {
}
m_watt.setValue(power + vwatts, false);
}
void treadmill::speedSensor(double speed) { Speed.setValue(speed); }
void treadmill::speedSensor(double speed) {
Speed.setValue(speed);
qDebug() << "Current speed: " << speed;
}
void treadmill::inclinationSensor(double grade, double inclination) {
Inclination.setValue(inclination);
qDebug() << "Current Inclination: " << inclination;
}
void treadmill::instantaneousStrideLengthSensor(double length) { InstantaneousStrideLengthCM.setValue(length); }
void treadmill::groundContactSensor(double groundContact) { GroundContactMS.setValue(groundContact); }
void treadmill::verticalOscillationSensor(double verticalOscillation) {
@@ -261,6 +293,69 @@ double treadmill::treadmillInclinationOverrideReverse(double Inclination) {
return treadmillInclinationOverride(15);
}
bool treadmill::areInclinationSettingsDefault() {
QSettings settings;
// Check gain and offset settings first
if (settings.value(QZSettings::treadmill_inclination_ovveride_gain).toDouble() !=
QZSettings::default_treadmill_inclination_ovveride_gain) {
return false;
}
if (settings.value(QZSettings::treadmill_inclination_ovveride_offset).toDouble() !=
QZSettings::default_treadmill_inclination_ovveride_offset) {
return false;
}
// Array of settings to check - pairs of setting key and its default value
const struct {
QString setting;
double defaultValue;
} checkPairs[] = {
{QZSettings::treadmill_inclination_override_0, QZSettings::default_treadmill_inclination_override_0},
{QZSettings::treadmill_inclination_override_05, QZSettings::default_treadmill_inclination_override_05},
{QZSettings::treadmill_inclination_override_10, QZSettings::default_treadmill_inclination_override_10},
{QZSettings::treadmill_inclination_override_15, QZSettings::default_treadmill_inclination_override_15},
{QZSettings::treadmill_inclination_override_20, QZSettings::default_treadmill_inclination_override_20},
{QZSettings::treadmill_inclination_override_25, QZSettings::default_treadmill_inclination_override_25},
{QZSettings::treadmill_inclination_override_30, QZSettings::default_treadmill_inclination_override_30},
{QZSettings::treadmill_inclination_override_35, QZSettings::default_treadmill_inclination_override_35},
{QZSettings::treadmill_inclination_override_40, QZSettings::default_treadmill_inclination_override_40},
{QZSettings::treadmill_inclination_override_45, QZSettings::default_treadmill_inclination_override_45},
{QZSettings::treadmill_inclination_override_50, QZSettings::default_treadmill_inclination_override_50},
{QZSettings::treadmill_inclination_override_55, QZSettings::default_treadmill_inclination_override_55},
{QZSettings::treadmill_inclination_override_60, QZSettings::default_treadmill_inclination_override_60},
{QZSettings::treadmill_inclination_override_65, QZSettings::default_treadmill_inclination_override_65},
{QZSettings::treadmill_inclination_override_70, QZSettings::default_treadmill_inclination_override_70},
{QZSettings::treadmill_inclination_override_75, QZSettings::default_treadmill_inclination_override_75},
{QZSettings::treadmill_inclination_override_80, QZSettings::default_treadmill_inclination_override_80},
{QZSettings::treadmill_inclination_override_85, QZSettings::default_treadmill_inclination_override_85},
{QZSettings::treadmill_inclination_override_90, QZSettings::default_treadmill_inclination_override_90},
{QZSettings::treadmill_inclination_override_95, QZSettings::default_treadmill_inclination_override_95},
{QZSettings::treadmill_inclination_override_100, QZSettings::default_treadmill_inclination_override_100},
{QZSettings::treadmill_inclination_override_105, QZSettings::default_treadmill_inclination_override_105},
{QZSettings::treadmill_inclination_override_110, QZSettings::default_treadmill_inclination_override_110},
{QZSettings::treadmill_inclination_override_115, QZSettings::default_treadmill_inclination_override_115},
{QZSettings::treadmill_inclination_override_120, QZSettings::default_treadmill_inclination_override_120},
{QZSettings::treadmill_inclination_override_125, QZSettings::default_treadmill_inclination_override_125},
{QZSettings::treadmill_inclination_override_130, QZSettings::default_treadmill_inclination_override_130},
{QZSettings::treadmill_inclination_override_135, QZSettings::default_treadmill_inclination_override_135},
{QZSettings::treadmill_inclination_override_140, QZSettings::default_treadmill_inclination_override_140},
{QZSettings::treadmill_inclination_override_145, QZSettings::default_treadmill_inclination_override_145},
{QZSettings::treadmill_inclination_override_150, QZSettings::default_treadmill_inclination_override_150}
};
// Check each setting against its default value
for (const auto& pair : checkPairs) {
if (settings.value(pair.setting).toDouble() != pair.defaultValue) {
return false;
}
}
// If we got here, all settings match their defaults
return true;
}
double treadmill::treadmillInclinationOverride(double Inclination) {
QSettings settings;
@@ -488,17 +583,20 @@ bool treadmill::followPowerBySpeed() {
.toBool();
double w = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
static double lastInclination = 0;
static double lastWattage = 0;
if (treadmill_follow_wattage) {
if (currentInclination().value() != lastInclination && lastWattage != 0) {
if (currentInclination().value() != lastInclination && wattsMetric().value() != 0) {
double newspeed = 0;
double bestSpeed = 0.1;
double bestDifference = fabs(wattsCalc(w, bestSpeed, currentInclination().value()) - lastWattage);
// don't read the wattage directly from the m_watt because if you were using a power sensor, the power calcuated in the for will not match it
double previousWatt = wattsCalc(w, currentSpeed().value(), lastInclination);
double bestDifference = fabs(wattsCalc(w, bestSpeed, currentInclination().value()) - previousWatt);
for (int speed = 1; speed <= 300; speed++) {
double s = ((double)speed) / 10.0;
double thisDifference = fabs(wattsCalc(w, s, currentInclination().value()) - lastWattage);
double thisDifference = fabs(wattsCalc(w, s, currentInclination().value()) - previousWatt);
if (thisDifference < bestDifference) {
bestDifference = thisDifference;
bestSpeed = s;
@@ -513,7 +611,6 @@ bool treadmill::followPowerBySpeed() {
}
lastInclination = currentInclination().value();
lastWattage = wattsMetric().value();
return r;
}
@@ -535,13 +632,24 @@ QTime treadmill::lastRequestedPace() {
}
}
void treadmill::parseInclination(double inclination) {
QSettings settings;
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
if(!stryd_inclination_instead_treadmill) {
Inclination = inclination;
} else {
qDebug() << "Inclination from the treadmill is discarded since we are using the one from the power sensor " << inclination;
}
rawInclination = inclination;
}
void treadmill::parseSpeed(double speed) {
QSettings settings;
bool stryd_speed_instead_treadmill = settings.value(QZSettings::stryd_speed_instead_treadmill, QZSettings::default_stryd_speed_instead_treadmill).toBool();
if(!stryd_speed_instead_treadmill) {
Speed = speed;
} else {
qDebug() << "speed from the treadmill is discarded since we are using the one from the power sensor";
qDebug() << "speed from the treadmill is discarded since we are using the one from the power sensor " << speed;
}
rawSpeed = speed;
}

View File

@@ -58,6 +58,7 @@ class treadmill : public bluetoothdevice {
void cadenceSensor(uint8_t cadence) override;
void powerSensor(uint16_t power) override;
void speedSensor(double speed) override;
void inclinationSensor(double grade, double inclination) override;
void instantaneousStrideLengthSensor(double length) override;
void groundContactSensor(double groundContact) override;
void verticalOscillationSensor(double verticalOscillation) override;
@@ -72,6 +73,7 @@ class treadmill : public bluetoothdevice {
double lastSpeed = 0.0;
double lastInclination = 0;
metric rawSpeed;
metric rawInclination;
metric RequestedSpeed;
metric RequestedInclination;
metric InstantaneousStrideLengthCM;
@@ -83,6 +85,8 @@ class treadmill : public bluetoothdevice {
treadmillErgTable _ergTable;
void parseSpeed(double speed);
void parseInclination(double speed);
bool areInclinationSettingsDefault();
private:
bool simulateInclinationWithSpeed();

View File

@@ -81,6 +81,8 @@ void trxappgateusbbike::forceResistance(resistance_t requestResistance) {
resistance[2] = 0x3f;
} else if (bike_type == TOORX_SRX_500) {
resistance[2] = 0x23;
} else if (bike_type == FAL_SPORTS) {
resistance[2] = 0x1b;
}
resistance[4] = requestResistance + 1;
@@ -138,6 +140,10 @@ void trxappgateusbbike::update() {
const uint8_t noOpData[] = {0xf0, 0xa2, 0x3b, 0x01, 0xce};
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
} else if (bike_type == TYPE::FAL_SPORTS) {
const uint8_t noOpData[] = {0xf0, 0xa2, 0x1b, 0x01, 0xae};
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
} else if (bike_type == TYPE::TUNTURI || bike_type == TYPE::TUNTURI_2) {
const uint8_t noOpData[] = {0xf0, 0xa2, 0x03, 0x01, 0x96};
@@ -219,7 +225,7 @@ void trxappgateusbbike::characteristicChanged(const QLowEnergyCharacteristic &ch
lastPacket = newValue;
if ((newValue.length() != 21 && (bike_type != JLL_IC400 && bike_type != ASVIVA && bike_type != FYTTER_RI08 &&
bike_type != TUNTURI && bike_type != TUNTURI_2 && bike_type != TOORX_SRX_500)) ||
bike_type != TUNTURI && bike_type != TUNTURI_2 && bike_type != TOORX_SRX_500 && bike_type != FAL_SPORTS)) ||
(newValue.length() != 19 && (bike_type == JLL_IC400 || bike_type == ASVIVA || bike_type == FYTTER_RI08 || bike_type == PASYOU)) ||
(newValue.length() != 20 && newValue.length() != 21 &&
(bike_type == TUNTURI || bike_type == TYPE::TUNTURI_2))) {
@@ -241,7 +247,7 @@ void trxappgateusbbike::characteristicChanged(const QLowEnergyCharacteristic &ch
(60000.0 / ((double)lastTimeCharChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) *
// body weight in kg * 3.5) / 200 ) / 60
} else if (bike_type == TUNTURI || bike_type == TUNTURI_2) {
} else if (bike_type == TUNTURI || bike_type == TUNTURI_2 || bike_type == FAL_SPORTS) {
speed = cadence * 0.37407407407407407407407407407407;
resistance = GetResistanceFromPacket(newValue);
watt = GetWattFromPacket(newValue);
@@ -746,6 +752,22 @@ void trxappgateusbbike::btinit(bool startTape) {
QThread::msleep(400);
writeCharacteristic((uint8_t *)initData4, sizeof(initData4), QStringLiteral("init"), false, true);
QThread::msleep(400);
} else if (bike_type == TYPE::FAL_SPORTS) {
const uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x01, 0x92};
const uint8_t initData2[] = {0xf0, 0xa0, 0x1b, 0x01, 0xac};
const uint8_t initData3[] = {0xf0, 0xa1, 0x1b, 0x01, 0xad};
const uint8_t initData4[] = {0xf0, 0xa3, 0x1b, 0x01, 0x01, 0xb0};
const uint8_t initData5[] = {0xf0, 0xa5, 0x1b, 0x01, 0x02, 0xb3};
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
QThread::msleep(400);
writeCharacteristic((uint8_t *)initData2, sizeof(initData2), QStringLiteral("init"), false, true);
QThread::msleep(400);
writeCharacteristic((uint8_t *)initData3, sizeof(initData3), QStringLiteral("init"), false, true);
QThread::msleep(400);
writeCharacteristic((uint8_t *)initData4, sizeof(initData4), QStringLiteral("init"), false, true);
QThread::msleep(400);
writeCharacteristic((uint8_t *)initData5, sizeof(initData5), QStringLiteral("init"), false, true);
} else if (bike_type == TYPE::FYTTER_RI08) {
const uint8_t initData1[] = {0xf0, 0xa0, 0x00, 0x00, 0x90};
const uint8_t initData2[] = {0xf0, 0xa0, 0x00, 0xc8, 0x58};
@@ -829,7 +851,8 @@ void trxappgateusbbike::stateChanged(QLowEnergyService::ServiceState state) {
if (bike_type == TYPE::IRUNNING || bike_type == TYPE::CHANGYOW || bike_type == TYPE::ICONSOLE ||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::DKN_MOTION_2 || bike_type == TYPE::FYTTER_RI08 ||
bike_type == TYPE::HERTZ_XR_770_2 || bike_type == TYPE::VIRTUFIT_2 || bike_type == TYPE::TUNTURI ||
bike_type == TYPE::FITHIWAY || bike_type == TYPE::ENERFIT_SPX_9500_2 || bike_type == TYPE::REEBOK_2) {
bike_type == TYPE::FITHIWAY || bike_type == TYPE::ENERFIT_SPX_9500_2 || bike_type == TYPE::REEBOK_2 ||
bike_type == TYPE::FAL_SPORTS) {
uuidWrite = QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3");
uuidNotify1 = QStringLiteral("49535343-1E4D-4BD9-BA61-23C647249616");
uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
@@ -920,7 +943,7 @@ void trxappgateusbbike::serviceScanDone(void) {
QString uuid3 = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
if (bike_type == TYPE::IRUNNING || bike_type == TYPE::CHANGYOW || bike_type == TYPE::ICONSOLE ||
bike_type == TYPE::JLL_IC400 || bike_type == TYPE::FYTTER_RI08 || bike_type == TYPE::TUNTURI ||
bike_type == TYPE::FITHIWAY) {
bike_type == TYPE::FITHIWAY || bike_type == TYPE::FAL_SPORTS) {
uuid = uuid2;
}
@@ -1162,6 +1185,10 @@ void trxappgateusbbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
bike_type = TYPE::PASYOU;
qDebug() << QStringLiteral("PASYOU bike found");
} else if (device.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS"))) {
bike_type = TYPE::FAL_SPORTS;
qDebug() << QStringLiteral("FAL-SPORTS bike found");
}
bluetoothDevice = device;

View File

@@ -114,6 +114,7 @@ class trxappgateusbbike : public bike {
TOORX_SRX_500 = 25,
IRUNNING_2 = 26,
PASYOU = 27,
FAL_SPORTS = 28,
} TYPE;
TYPE bike_type = TRXAPPGATE;

View File

@@ -79,7 +79,7 @@ void ypooelliptical::forceInclination(double inclination) {
void ypooelliptical::forceResistance(resistance_t requestResistance) {
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO) {
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS) {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
write[1] = ((uint16_t)requestResistance * 10) & 0xFF;
writeCharacteristic(&gattFTMSWriteCharControlPointId, gattFTMSService, write, sizeof(write),
@@ -108,7 +108,7 @@ void ypooelliptical::update() {
if (initRequest) {
initRequest = false;
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO) {
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(&gattFTMSWriteCharControlPointId, gattFTMSService, write, sizeof(write), "requestControl", false, true);
} else {
@@ -250,7 +250,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2ACE) && !iconsole_elliptical) {
if(E35 == false && SCH_590E == false && KETTLER == false && CARDIOPOWER_EEGO == false) {
if(E35 == false && SCH_590E == false && KETTLER == false && CARDIOPOWER_EEGO == false && MYELLIPTICAL == false && SKANDIKA == false && DOMYOS == false) {
if (newvalue.length() == 18) {
qDebug() << QStringLiteral("let's wait for the next piece of frame");
lastPacket = newvalue;
@@ -270,7 +270,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
index += 3;
if (!Flags.moreData) {
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO) {
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS) {
Speed = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint16_t)((uint8_t)lastPacket.at(index)))) /
100.0;
@@ -282,7 +282,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
// this particular device, seems to send the actual speed here
if (Flags.avgSpeed) {
// double avgSpeed;
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO) {
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL && !SKANDIKA && !DOMYOS) {
Speed = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint16_t)((uint8_t)lastPacket.at(index)))) /
100.0;
@@ -292,7 +292,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
}
if (Flags.totDistance) {
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO) {
if(!E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL && !SKANDIKA && !DOMYOS) {
Distance = ((double)((((uint32_t)((uint8_t)lastPacket.at(index + 2)) << 16) |
(uint32_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint32_t)((uint8_t)lastPacket.at(index)))) /
@@ -314,7 +314,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
.toString()
.startsWith(QStringLiteral("Disabled"))) {
double divisor = 1.0;
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO)
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS)
divisor = 2.0;
Cadence = (((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint16_t)((uint8_t)lastPacket.at(index))))) / divisor;
@@ -382,7 +382,7 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
.startsWith(QStringLiteral("Disabled"))) {
double divisor = 100.0; // i added this because this device seems to send it multiplied by 100
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO)
if(E35 || SCH_590E || KETTLER || CARDIOPOWER_EEGO || MYELLIPTICAL || SKANDIKA || DOMYOS)
divisor = 1.0;
m_watt = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
@@ -391,9 +391,12 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
}
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
index += 2;
} else if(DOMYOS) {
m_watt = elliptical::watts();
emit debug(QStringLiteral("Current Watt: ") + QString::number(m_watt.value()));
}
if (Flags.avgPower && lastPacket.length() > index + 1 && !E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO) { // E35 has a bug about this
if (Flags.avgPower && lastPacket.length() > index + 1 && !E35 && !SCH_590E && !KETTLER && !CARDIOPOWER_EEGO && !MYELLIPTICAL && !SKANDIKA && !DOMYOS) { // E35 has a bug about this
double avgPower;
avgPower = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint16_t)((uint8_t)lastPacket.at(index))));
@@ -402,8 +405,8 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
}
if (Flags.expEnergy && lastPacket.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint16_t)((uint8_t)lastPacket.at(index))));
/*KCal = ((double)(((uint16_t)((uint8_t)lastPacket.at(index + 1)) << 8) |
(uint16_t)((uint8_t)lastPacket.at(index))));*/
index += 2;
// energy per hour
@@ -411,16 +414,16 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
// energy per minute
index += 1;
} else {
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
}
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 /
((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in
// kg * 3.5) / 200 ) / 60
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
@@ -483,15 +486,15 @@ void ypooelliptical::characteristicChanged(const QLowEnergyCharacteristic &chara
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
emit debug(QStringLiteral("Current Heart: ") + QString::number(Heart.value()));
}
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
update_hr_from_external();
}
} else {
return;
}
if (heartRateBeltName.startsWith(QStringLiteral("Disabled")) &&
(!Flags.heartRate || Heart.value() == 0 || disable_hr_frommachinery)) {
update_hr_from_external();
}
lastRefreshCharacteristicChanged = now;
#ifdef Q_OS_IOS
@@ -538,7 +541,7 @@ void ypooelliptical::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << "skipping service" << s->serviceUuid();
continue;
}
else if(s->serviceUuid() != _gattFTMSService && SCH_590E) {
else if(s->serviceUuid() != _gattFTMSService && (SCH_590E || MYELLIPTICAL || SKANDIKA || DOMYOS)) {
qDebug() << "skipping service" << s->serviceUuid();
continue;
}
@@ -756,6 +759,15 @@ void ypooelliptical::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if(device.name().toUpper().startsWith(QStringLiteral("CARDIOPOWER EEGO"))) {
CARDIOPOWER_EEGO = true;
qDebug() << "CARDIOPOWER_EEGO workaround ON!";
} else if(device.name().toUpper().startsWith(QStringLiteral("MYELLIPTICAL "))) {
MYELLIPTICAL = true;
qDebug() << "MYELLIPTICAL workaround ON!";
} else if(device.name().toUpper().startsWith(QStringLiteral("SF-")) && device.name().midRef(3).toInt() > 0) {
SKANDIKA = true;
qDebug() << "SKANDIKA workaround ON!";
} else if(device.name().toUpper().startsWith(QStringLiteral("DOMYOS-EL"))) {
DOMYOS = true;
qDebug() << "DOMYOS workaround ON!";
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);

View File

@@ -80,6 +80,9 @@ class ypooelliptical : public elliptical {
bool E35 = false;
bool KETTLER = false;
bool CARDIOPOWER_EEGO = false;
bool MYELLIPTICAL = false;
bool SKANDIKA = false;
bool DOMYOS = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -51,4 +51,28 @@ import Foundation
return fullData
}
@objc public static func getPowerFromBuffer(buffer: Data) -> UInt32 {
var physical = BLEReceiver_Zwift_HubRidingData()
do {
try physical.merge(serializedData: buffer)
return physical.power
} catch {
var SwiftDebug = swiftDebug()
SwiftDebug.qtDebug("Error in getPowerFromBuffer: \(error)")
return 0
}
}
@objc public static func getCadenceFromBuffer(buffer: Data) -> UInt32 {
var physical = BLEReceiver_Zwift_HubRidingData()
do {
try physical.merge(serializedData: buffer)
return physical.cadence
} catch {
var SwiftDebug = swiftDebug()
SwiftDebug.qtDebug("Error in getCadenceFromBuffer: \(error)")
return 0
}
}
}

View File

@@ -51,8 +51,6 @@ using namespace std::chrono_literals;
#pragma message "DEFINE STRAVA_CLIENT_ID!!!"
#endif
#endif
#define _STR(x) #x
#define STRINGIFY(x) _STR(x)
#define STRAVA_CLIENT_ID_S STRINGIFY(STRAVA_CLIENT_ID)
DataObject::DataObject(const QString &name, const QString &icon, const QString &value, bool writable, const QString &id,
@@ -407,6 +405,47 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
.toString(),
settings.value(QZSettings::tile_preset_inclination_5_color, QZSettings::default_tile_preset_inclination_5_color)
.toString());
preset_powerzone_1 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_1"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_1_label, QZSettings::default_tile_preset_powerzone_1_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_1_color, QZSettings::default_tile_preset_powerzone_1_color).toString());
preset_powerzone_2 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_2"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_2_label, QZSettings::default_tile_preset_powerzone_2_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_2_color, QZSettings::default_tile_preset_powerzone_2_color).toString());
preset_powerzone_3 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_3"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_3_label, QZSettings::default_tile_preset_powerzone_3_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_3_color, QZSettings::default_tile_preset_powerzone_3_color).toString());
preset_powerzone_4 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_4"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_4_label, QZSettings::default_tile_preset_powerzone_4_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_4_color, QZSettings::default_tile_preset_powerzone_4_color).toString());
preset_powerzone_5 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_5"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_5_label, QZSettings::default_tile_preset_powerzone_5_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_5_color, QZSettings::default_tile_preset_powerzone_5_color).toString());
preset_powerzone_6 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_6"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_6_label, QZSettings::default_tile_preset_powerzone_6_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_6_color, QZSettings::default_tile_preset_powerzone_6_color).toString());
preset_powerzone_7 = new DataObject(
"", "", "", false, QStringLiteral("preset_powerzone_7"), 48, labelFontSize, QStringLiteral("white"),
QLatin1String(""), 0, true,
settings.value(QZSettings::tile_preset_powerzone_7_label, QZSettings::default_tile_preset_powerzone_7_label).toString(),
settings.value(QZSettings::tile_preset_powerzone_7_color, QZSettings::default_tile_preset_powerzone_7_color).toString());
if (!settings.value(QZSettings::top_bar_enabled, QZSettings::default_top_bar_enabled).toBool()) {
@@ -550,6 +589,9 @@ homeform::homeform(QQmlApplicationEngine *engine, bluetooth *bl) {
connect(pelotonHandler, &peloton::workoutChanged, this, &homeform::pelotonWorkoutChanged);
connect(pelotonHandler, &peloton::loginState, this, &homeform::pelotonLoginState);
connect(pelotonHandler, &peloton::pzpLoginState, this, &homeform::pzpLoginState);
connect(pelotonHandler, &peloton::pelotonAuthUrlChanged, this, &homeform::pelotonAuthUrlChanged);
connect(pelotonHandler, &peloton::pelotonWebVisibleChanged, this, &homeform::pelotonWebVisibleChanged);
connect(stack, SIGNAL(peloton_connect_clicked()), pelotonHandler, SLOT(peloton_connect_clicked()));
// copying bundles zwo files in the right path if necessary
QDirIterator itZwo(":/zwo/");
@@ -1914,6 +1956,48 @@ void homeform::sortTiles() {
biggearsMinus->setGridId(i);
dataList.append(biggearsMinus);
}
if (settings.value(QZSettings::tile_preset_powerzone_1_enabled, QZSettings::default_tile_preset_powerzone_1_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_1_order, QZSettings::default_tile_preset_powerzone_1_order).toInt() == i) {
preset_powerzone_1->setGridId(i);
dataList.append(preset_powerzone_1);
}
if (settings.value(QZSettings::tile_preset_powerzone_2_enabled, QZSettings::default_tile_preset_powerzone_2_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_2_order, QZSettings::default_tile_preset_powerzone_2_order).toInt() == i) {
preset_powerzone_2->setGridId(i);
dataList.append(preset_powerzone_2);
}
if (settings.value(QZSettings::tile_preset_powerzone_3_enabled, QZSettings::default_tile_preset_powerzone_3_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_3_order, QZSettings::default_tile_preset_powerzone_3_order).toInt() == i) {
preset_powerzone_3->setGridId(i);
dataList.append(preset_powerzone_3);
}
if (settings.value(QZSettings::tile_preset_powerzone_4_enabled, QZSettings::default_tile_preset_powerzone_4_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_4_order, QZSettings::default_tile_preset_powerzone_4_order).toInt() == i) {
preset_powerzone_4->setGridId(i);
dataList.append(preset_powerzone_4);
}
if (settings.value(QZSettings::tile_preset_powerzone_5_enabled, QZSettings::default_tile_preset_powerzone_5_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_5_order, QZSettings::default_tile_preset_powerzone_5_order).toInt() == i) {
preset_powerzone_5->setGridId(i);
dataList.append(preset_powerzone_5);
}
if (settings.value(QZSettings::tile_preset_powerzone_6_enabled, QZSettings::default_tile_preset_powerzone_6_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_6_order, QZSettings::default_tile_preset_powerzone_6_order).toInt() == i) {
preset_powerzone_6->setGridId(i);
dataList.append(preset_powerzone_6);
}
if (settings.value(QZSettings::tile_preset_powerzone_7_enabled, QZSettings::default_tile_preset_powerzone_7_enabled).toBool() &&
settings.value(QZSettings::tile_preset_powerzone_7_order, QZSettings::default_tile_preset_powerzone_7_order).toInt() == i) {
preset_powerzone_7->setGridId(i);
dataList.append(preset_powerzone_7);
}
}
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
for (int i = 0; i < 100; i++) {
@@ -2975,7 +3059,32 @@ void homeform::LargeButton(const QString &name) {
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE ||
bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL ||
bluetoothManager->device()->deviceType() == bluetoothdevice::ROWING) {
if (name.contains(QStringLiteral("erg_mode"))) {
if (name.startsWith(QStringLiteral("preset_powerzone_"))) {
double ftp = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
int zoneNum = name.right(1).toInt(); // Gets last digit from preset_powerzone_X
QString zoneSetting = QString("tile_preset_powerzone_%1_value").arg(zoneNum);
double zoneValue = settings.value(zoneSetting, zoneNum).toDouble();
// Calculate target watts based on FTP and zone value
// Each zone represents a percentage of FTP
double targetWatts;
if (zoneValue <= 1.9) {
targetWatts = ftp * 0.55 * (zoneValue);
} else if (zoneValue <= 2.9) {
targetWatts = ftp * 0.75 * (zoneValue - 1);
} else if (zoneValue <= 3.9) {
targetWatts = ftp * 0.90 * (zoneValue - 2);
} else if (zoneValue <= 4.9) {
targetWatts = ftp * 1.05 * (zoneValue - 3);
} else if (zoneValue <= 5.9) {
targetWatts = ftp * 1.20 * (zoneValue - 4);
} else if (zoneValue <= 6.9) {
targetWatts = ftp * 1.50 * (zoneValue - 5);
} else {
targetWatts = ftp * 1.70 * (zoneValue - 6);
}
bluetoothManager->device()->changePower(targetWatts);
} else if (name.contains(QStringLiteral("erg_mode"))) {
settings.setValue(QZSettings::zwift_erg, !settings.value(QZSettings::zwift_erg, QZSettings::default_zwift_erg).toBool());
} else if (name.contains(QStringLiteral("preset_resistance_1"))) {
bluetoothManager->device()->changeResistance(settings
@@ -3989,6 +4098,8 @@ void homeform::update() {
double currentHRZone = 1;
double ftpZone = 1;
qDebug() << "homeform::update fired!";
if (settings.status() != QSettings::NoError) {
qDebug() << "!!!!QSETTINGS ERROR!" << settings.status();
}
@@ -4496,7 +4607,7 @@ void homeform::update() {
QString::number(
bluetoothManager->device()->difficult() *
settings.value(QZSettings::bike_resistance_gain_f, QZSettings::default_bike_resistance_gain_f)
.toDouble() *
.toDouble() +
settings.value(QZSettings::bike_resistance_offset, QZSettings::default_bike_resistance_offset)
.toDouble(),
'f', 0));
@@ -5140,6 +5251,8 @@ void homeform::update() {
}
*/
qDebug() << "homeform::update tiles updated!";
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_cadence, QZSettings::default_ant_cadence).toBool() &&
KeepAwakeHelper::antObject(false)) {
@@ -6131,6 +6244,7 @@ void homeform::fit_save_clicked() {
}
void homeform::strava_upload_file_prepare() {
qDebug() << lastFitFileSaved;
QFile f(lastFitFileSaved);
f.open(QFile::OpenModeFlag::ReadOnly);
QByteArray fitfile = f.readAll();
@@ -6250,22 +6364,6 @@ QStringList homeform::bluetoothDevices() {
QStringList homeform::metrics() { return bluetoothdevice::metrics(); }
struct OAuth2Parameter {
QString responseType = QStringLiteral("code");
QString approval_prompt = QStringLiteral("force");
inline bool isEmpty() const { return responseType.isEmpty() && approval_prompt.isEmpty(); }
QString toString() const {
QString msg;
QTextStream out(&msg);
out << QStringLiteral("OAuth2Parameter{\n") << QStringLiteral("responseType: ") << this->responseType
<< QStringLiteral("\n") << QStringLiteral("approval_prompt: ") << this->approval_prompt
<< QStringLiteral("\n");
return msg;
}
};
QAbstractOAuth::ModifyParametersFunction
homeform::buildModifyParametersFunction(const QUrl &clientIdentifier, const QUrl &clientIdentifierSharedKey) {
return [clientIdentifier, clientIdentifierSharedKey](QAbstractOAuth::Stage stage, QVariantMap *parameters) {
@@ -6361,6 +6459,9 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
QSettings settings;
QString token = settings.value(QZSettings::strava_accesstoken).toString();
qDebug() << "File size to upload:" << data.size() << "bytes";
qDebug() << "Remote filename:" << remotename;
// The V3 API doc said "https://api.strava.com" but it is not working yet
QUrl url = QUrl(QStringLiteral("https://www.strava.com/api/v3/uploads"));
QNetworkRequest request = QNetworkRequest(url);
@@ -6458,6 +6559,11 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
manager = new QNetworkAccessManager(this);
replyStrava = manager->post(request, multiPart);
connect(replyStrava, &QNetworkReply::uploadProgress,
[](qint64 bytesSent, qint64 bytesTotal) {
qDebug() << "Upload progress:" << bytesSent << "/" << bytesTotal;
});
// catch finished signal
connect(replyStrava, &QNetworkReply::finished, this, &homeform::writeFileCompleted);
#if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
@@ -6467,8 +6573,24 @@ bool homeform::strava_upload_file(const QByteArray &data, const QString &remoten
}
void homeform::errorOccurredUploadStrava(QNetworkReply::NetworkError code) {
qDebug() << QStringLiteral("strava upload error!") << code;
setToastRequested("Strava Upload Failed!");
qDebug() << "Strava upload error details:";
qDebug() << "Error code:" << code;
if(replyStrava) {
qDebug() << "Error string:" << replyStrava->errorString();
qDebug() << "HTTP status code:" << replyStrava->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
QByteArray errorData = replyStrava->readAll();
qDebug() << "Error response body:" << QString(errorData);
QJsonDocument jsonResponse = QJsonDocument::fromJson(errorData);
if (!jsonResponse.isNull()) {
qDebug() << "JSON error message:" << jsonResponse.toJson();
}
setToastRequested("Strava Upload Failed: " + replyStrava->errorString());
} else {
setToastRequested("Strava Upload Failed");
}
}
void homeform::writeFileCompleted() {
@@ -6678,6 +6800,14 @@ void homeform::setGeneralPopupVisible(bool value) {
emit generalPopupVisibleChanged(m_generalPopupVisible);
}
bool homeform::pelotonPopupVisible() { return m_pelotonPopupVisible; }
void homeform::setPelotonPopupVisible(bool value) {
m_pelotonPopupVisible = value;
emit pelotonPopupVisibleChanged(m_pelotonPopupVisible);
}
bool homeform::licensePopupVisible() { return m_LicensePopupVisible; }
void homeform::setLicensePopupVisible(bool value) {

View File

@@ -5,6 +5,7 @@
#include "bluetooth.h"
#include "fit_profile.hpp"
#include "gpx.h"
#include "OAuth2.h"
#include "peloton.h"
#include "qmdnsengine/browser.h"
#include "qmdnsengine/cache.h"
@@ -150,6 +151,8 @@ class homeform : public QObject {
Q_PROPERTY(QStringList tile_order READ tile_order NOTIFY tile_orderChanged)
Q_PROPERTY(bool generalPopupVisible READ generalPopupVisible NOTIFY generalPopupVisibleChanged WRITE
setGeneralPopupVisible)
Q_PROPERTY(bool pelotonPopupVisible READ pelotonPopupVisible NOTIFY pelotonPopupVisibleChanged WRITE
setPelotonPopupVisible)
Q_PROPERTY(bool licensePopupVisible READ licensePopupVisible NOTIFY licensePopupVisibleChanged WRITE
setLicensePopupVisible)
Q_PROPERTY(bool mapsVisible READ mapsVisible NOTIFY mapsVisibleChanged WRITE setMapsVisible)
@@ -193,6 +196,10 @@ class homeform : public QObject {
Q_PROPERTY(QString getStravaAuthUrl READ getStravaAuthUrl NOTIFY stravaAuthUrlChanged)
Q_PROPERTY(bool stravaWebVisible READ stravaWebVisible NOTIFY stravaWebVisibleChanged)
QString getPelotonAuthUrl() { if(!pelotonHandler) return ""; return pelotonHandler->pelotonAuthUrl; }
bool pelotonWebVisible() { if(!pelotonHandler) return false; return pelotonHandler->pelotonAuthWebVisible; }
Q_PROPERTY(QString getPelotonAuthUrl READ getPelotonAuthUrl NOTIFY pelotonAuthUrlChanged)
Q_PROPERTY(bool pelotonWebVisible READ pelotonWebVisible NOTIFY pelotonWebVisibleChanged)
public:
static homeform *singleton() { return m_singleton; }
@@ -446,6 +453,7 @@ class homeform : public QObject {
bool stravaUploadRequested() { return m_stravaUploadRequested; }
void setPelotonProvider(const QString &value) { m_pelotonProvider = value; }
bool generalPopupVisible();
bool pelotonPopupVisible();
bool licensePopupVisible();
bool mapsVisible();
bool videoIconVisible();
@@ -501,6 +509,7 @@ class homeform : public QObject {
m_stravaUploadRequested = value;
}
void setGeneralPopupVisible(bool value);
void setPelotonPopupVisible(bool value);
int workout_sample_points() { return Session.count(); }
int preview_workout_points();
@@ -678,6 +687,13 @@ class homeform : public QObject {
DataObject *stepCount;
DataObject *ergMode;
DataObject *rss;
DataObject *preset_powerzone_1;
DataObject *preset_powerzone_2;
DataObject *preset_powerzone_3;
DataObject *preset_powerzone_4;
DataObject *preset_powerzone_5;
DataObject *preset_powerzone_6;
DataObject *preset_powerzone_7;
private:
static homeform *m_singleton;
@@ -697,6 +713,7 @@ class homeform : public QObject {
QString m_info = QStringLiteral("Connecting...");
bool m_labelHelp = true;
bool m_generalPopupVisible = false;
bool m_pelotonPopupVisible = false;
bool m_LicensePopupVisible = false;
bool m_MapsVisible = false;
bool m_VideoIconVisible = false;
@@ -898,6 +915,7 @@ class homeform : public QObject {
void toastRequestedChanged(QString value);
void stravaUploadRequestedChanged(bool value);
void generalPopupVisibleChanged(bool value);
void pelotonPopupVisibleChanged(bool value);
void licensePopupVisibleChanged(bool value);
void videoIconVisibleChanged(bool value);
void videoVisibleChanged(bool value);
@@ -923,6 +941,8 @@ class homeform : public QObject {
void previewWorkoutTagsChanged(QString value);
void stravaAuthUrlChanged(QString value);
void stravaWebVisibleChanged(bool value);
void pelotonAuthUrlChanged(QString value);
void pelotonWebVisibleChanged(bool value);
void workoutEventStateChanged(bluetoothdevice::WORKOUT_EVENT_STATE state);

View File

@@ -33,5 +33,6 @@
<file>icons/video.png</file>
<file>icons/mini-display.png</file>
<file>icons/btn_strava_connectwith_orange.png</file>
<file>icons/Button_Connect_Rect_DarkMode.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -21,10 +21,11 @@ extension String {
class Connection {
let connection: NWConnection
let SwiftDebug = swiftDebug()
// outgoing connection
init(endpoint: NWEndpoint) {
print("PeerConnection outgoing endpoint: \(endpoint)")
SwiftDebug.qtDebug("PeerConnection outgoing endpoint: \(endpoint)")
let tcpOptions = NWProtocolTCP.Options()
tcpOptions.enableKeepalive = true
tcpOptions.keepaliveIdle = 2
@@ -38,41 +39,41 @@ class Connection {
// incoming connection
init(connection: NWConnection) {
print("PeerConnection incoming connection: \(connection)")
SwiftDebug.qtDebug("PeerConnection incoming connection: \(connection)")
self.connection = connection
start()
}
func start() {
connection.stateUpdateHandler = { newState in
print("connection.stateUpdateHandler \(newState)")
self.SwiftDebug.qtDebug("connection.stateUpdateHandler \(newState)")
switch newState {
case .ready:
self.receiveMessage()
case .failed(let error):
self.connection.stateUpdateHandler = nil
self.connection.cancel()
print("Server error\(error)")
self.SwiftDebug.qtDebug("Server error\(error)")
case .setup:
print("Server setup.")
self.SwiftDebug.qtDebug("Server setup.")
case .waiting(_):
print("Server waiting.")
self.SwiftDebug.qtDebug("Server waiting.")
case .preparing:
print("Server preparing.")
self.SwiftDebug.qtDebug("Server preparing.")
case .cancelled:
print("Server cancelled.")
self.SwiftDebug.qtDebug("Server cancelled.")
@unknown default:
print("Server DEFAULT.")
self.SwiftDebug.qtDebug("Server DEFAULT.")
}
}
connection.start(queue: .main)
}
func send(_ message: String) {
print("sending \(message)")
self.SwiftDebug.qtDebug("sending \(message)")
connection.send(content: message.data(using: .utf8), contentContext: .defaultMessage, isComplete: true, completion: .contentProcessed({ error in
if error != nil {
print("Connection send error: \(String(describing: error))")
self.SwiftDebug.qtDebug("Connection send error: \(String(describing: error))")
}
}))
}
@@ -81,36 +82,36 @@ class Connection {
connection.receive(minimumIncompleteLength: 1, maximumLength: 100) { data, _, _, _ in
if let data = data,
let message = String(data: data, encoding: .utf8) {
print("Connection receiveMessage message: \(message)")
self.SwiftDebug.qtDebug("Connection receiveMessage message: \(message)")
if message.contains("SENDER=") {
let sender = message.slice(from: "SENDER=", to: "#")
if sender?.contains("PHONE") ?? false && message.contains("HR=") {
let hr : String = message.slice(from: "HR=", to: "#") ?? ""
WatchKitConnection.currentHeartRate = (Int(hr) ?? 0)
WatchKitConnection.currentHeartRate = (Int(hr) ?? WatchKitConnection.currentHeartRate)
}
if sender?.contains("PHONE") ?? false && message.contains("CAD=") {
let cad : String = message.slice(from: "CAD=", to: "#") ?? ""
WatchKitConnection.stepCadence = (Int(cad) ?? 0)
WatchKitConnection.stepCadence = (Int(cad) ?? WatchKitConnection.stepCadence)
}
if sender?.contains("PAD") ?? false && message.contains("KCAL=") {
let kcal : String = message.slice(from: "KCAL=", to: "#") ?? ""
WatchKitConnection.kcal = (Double(kcal) ?? 0)
WatchKitConnection.kcal = (Double(kcal) ?? WatchKitConnection.kcal)
}
if sender?.contains("PAD") ?? false && message.contains("ODO=") {
let odo : String = message.slice(from: "ODO=", to: "#") ?? ""
WatchKitConnection.distance = (Double(odo) ?? 0)
WatchKitConnection.distance = (Double(odo) ?? WatchKitConnection.distance)
}
if sender?.contains("PAD") ?? false && message.contains("BCAD=") {
let cad : String = message.slice(from: "BCAD=", to: "#") ?? ""
WatchKitConnection.cadence = (Double(cad) ?? 0)
WatchKitConnection.cadence = (Double(cad) ?? WatchKitConnection.cadence)
}
if sender?.contains("PAD") ?? false && message.contains("SPD=") {
let spd : String = message.slice(from: "SPD=", to: "#") ?? ""
WatchKitConnection.speed = (Double(spd) ?? 0)
WatchKitConnection.speed = (Double(spd) ?? WatchKitConnection.speed)
}
if sender?.contains("PAD") ?? false && message.contains("PWR=") {
let pwr : String = message.slice(from: "PWR=", to: "#") ?? ""
WatchKitConnection.power = (Double(pwr) ?? 0)
WatchKitConnection.power = (Double(pwr) ?? WatchKitConnection.power)
}
}
}

View File

@@ -0,0 +1,80 @@
#define QT_IOS_BLUETOOTH
#import "osxbtcentralmanager_p.h"
#import <objc/runtime.h>
QT_USE_NAMESPACE
// Definizione della struttura dei membri privati
@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) () {
@package // Rende i membri accessibili alla categoria
OSXBluetooth::CharHash charMap;
OSXBluetooth::DescHash descMap;
OSXBluetooth::ValueHash valuesToWrite;
}
@end
@implementation QT_MANGLE_NAMESPACE(OSXBTCentralManager) (SafeCache)
- (bool)cacheWriteValue:(const QByteArray &)value for:(NSObject *)obj
{
@try {
// Accesso alle variabili private tramite self
OSXBluetooth::CharHash &localCharMap = ((QT_MANGLE_NAMESPACE(OSXBTCentralManager) *)self)->charMap;
OSXBluetooth::DescHash &localDescMap = ((QT_MANGLE_NAMESPACE(OSXBTCentralManager) *)self)->descMap;
OSXBluetooth::ValueHash &localValuesToWrite = ((QT_MANGLE_NAMESPACE(OSXBTCentralManager) *)self)->valuesToWrite;
// Verifica validità oggetto
if (!obj) {
qDebug() << "Error: Invalid object (nil)";
return false;
}
// Verifica tipo oggetto
if ([obj isKindOfClass:[CBCharacteristic class]]) {
@try {
CBCharacteristic *const ch = (CBCharacteristic *)obj;
if (!localCharMap.key(ch)) {
qDebug() << "Error: Unexpected characteristic, no handle found";
return false;
}
} @catch (NSException *e) {
qDebug() << "Exception handling characteristic:" << e.reason;
return false;
}
} else if ([obj isKindOfClass:[CBDescriptor class]]) {
@try {
CBDescriptor *const d = (CBDescriptor *)obj;
if (!localDescMap.key(d)) {
qDebug() << "Error: Unexpected descriptor, no handle found";
return false;
}
} @catch (NSException *e) {
qDebug() << "Exception handling descriptor:" << e.reason;
return false;
}
} else {
qDebug() << "Error: Invalid object type, characteristic or descriptor required";
return false;
}
// Gestione cache esistente
@try {
if (localValuesToWrite.contains(obj)) {
qDebug() << "Warning: Already has a cached value for this object, the value will be replaced";
}
localValuesToWrite[obj] = value;
} @catch (NSException *e) {
qDebug() << "Exception during cache operation:" << e.reason;
return false;
}
return true;
} @catch (NSException *e) {
qDebug() << "Unexpected exception in cacheWriteValue:" << e.reason;
return false;
}
}
@end

View File

@@ -0,0 +1,63 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef OSXBLUETOOTH_P_H
#define OSXBLUETOOTH_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/qglobal.h>
#include <CoreBluetooth/CoreBluetooth.h>
#ifdef Q_OS_MACOS
#include <IOBluetooth/IOBluetooth.h>
#endif
#endif // OSXBLUETOOTH_P_H

View File

@@ -0,0 +1,176 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef OSXBTCENTRALMANAGER_P_H
#define OSXBTCENTRALMANAGER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "qlowenergycontroller.h"
#include "qlowenergyservice.h"
#include "osxbtgcdtimer_p.h"
#include "qbluetoothuuid.h"
#include "osxbtutility_p.h"
#include "osxbluetooth_p.h"
#include <QtCore/qbytearray.h>
#include <QtCore/qglobal.h>
#include <QtCore/qqueue.h>
#include <QtCore/qhash.h>
#include <Foundation/Foundation.h>
@class QT_MANGLE_NAMESPACE(OSXBTCentralManager);
QT_BEGIN_NAMESPACE
class QLowEnergyServicePrivate;
namespace OSXBluetooth {
class LECBManagerNotifier;
enum CentralManagerState
{
// QLowEnergyController already has some of these states,
// but it's not enough and we need more special states here.
CentralManagerIdle,
// Required by CBCentralManager to avoid problems with dangled pointers.
CentralManagerUpdating,
CentralManagerConnecting,
CentralManagerDiscovering,
CentralManagerDisconnecting
};
// In Qt we work with handles and UUIDs. Core Bluetooth
// has NSArrays (and nested NSArrays inside servces/characteristics).
// To simplify a navigation, I need a simple way to map from a handle
// to a Core Bluetooth object. These are weak pointers,
// will probably require '__weak' with ARC.
typedef QHash<QLowEnergyHandle, CBService *> ServiceHash;
typedef QHash<QLowEnergyHandle, CBCharacteristic *> CharHash;
typedef QHash<QLowEnergyHandle, CBDescriptor *> DescHash;
// Descriptor/charactesirstic read/write requests
// - we have to serialize 'concurrent' requests.
struct LERequest {
enum RequestType {
CharRead,
CharWrite,
DescRead,
DescWrite,
ClientConfiguration,
Invalid
};
LERequest() : type(Invalid),
withResponse(false),
handle(0)
{}
RequestType type;
bool withResponse;
QLowEnergyHandle handle;
QByteArray value;
};
typedef QQueue<LERequest> RequestQueue;
// Core Bluetooth's write confirmation does not provide
// the updated value (characteristic or descriptor).
// But the Qt's Bluetooth API ('write with response')
// expects this updated value as a response, so we have
// to cache this write value and report it back.
// 'NSObject *' will require '__weak' with ARC.
typedef QHash<NSObject *, QByteArray> ValueHash;
}
QT_END_NAMESPACE
@interface QT_MANGLE_NAMESPACE(OSXBTCentralManager) : NSObject<CBCentralManagerDelegate,
CBPeripheralDelegate,
QT_MANGLE_NAMESPACE(GCDTimerDelegate)>
- (id)initWith:(QT_PREPEND_NAMESPACE(OSXBluetooth)::LECBManagerNotifier *)notifier;
- (void)dealloc;
- (CBPeripheral *)peripheral;
// IMPORTANT: _all_ these methods are to be executed on qt_LE_queue,
// when passing parameters - C++ objects _must_ be copied (see the controller's code).
- (void)connectToDevice:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)aDeviceUuid;
- (void)disconnectFromDevice;
- (void)discoverServices;
- (void)discoverServiceDetails:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
- (void)setNotifyValue:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
forCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
- (void)readCharacteristic:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
- (void)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
charHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))charHandle
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid
withResponse:(bool)writeWithResponse;
- (void)readDescriptor:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
- (void)write:(const QT_PREPEND_NAMESPACE(QByteArray) &)value
descHandle:(QT_PREPEND_NAMESPACE(QLowEnergyHandle))descHandle
onService:(const QT_PREPEND_NAMESPACE(QBluetoothUuid) &)serviceUuid;
- (void)detach;
@end
#endif

View File

@@ -0,0 +1,108 @@
/****************************************************************************
**
** Copyright (C) 2018 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef OSXBTGCDTIMER_P_H
#define OSXBTGCDTIMER_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "osxbtutility_p.h"
#include <QtCore/qelapsedtimer.h>
#include <QtCore/qglobal.h>
#include <Foundation/Foundation.h>
QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
enum class OperationTimeout
{
none,
serviceDiscovery,
includedServicesDiscovery,
characteristicsDiscovery,
characteristicRead,
descriptorsDiscovery,
descriptorRead,
characteristicWrite
};
} // namespace OSXBluetooth
QT_END_NAMESPACE
@protocol QT_MANGLE_NAMESPACE(GCDTimerDelegate)
@required
- (void)timeout:(id)sender;
@end
@interface QT_MANGLE_NAMESPACE(OSXBTGCDTimer) : NSObject
- (instancetype)initWithDelegate:(id<QT_MANGLE_NAMESPACE(GCDTimerDelegate)>)delegate;
- (void)watchAfter:(id)object withTimeoutType:(QT_PREPEND_NAMESPACE(OSXBluetooth)::OperationTimeout)type;
- (void)startWithTimeout:(qint64)ms step:(qint64)stepMS;
- (void)handleTimeout;
- (void)cancelTimer;
- (id)objectUnderWatch;
- (QT_PREPEND_NAMESPACE(OSXBluetooth)::OperationTimeout)timeoutType;
@end
QT_BEGIN_NAMESPACE
namespace OSXBluetooth {
using GCDTimerObjC = QT_MANGLE_NAMESPACE(OSXBTGCDTimer);
using GCDTimer = ObjCStrongReference<GCDTimerObjC>;
} // namespace OSXBluetooth
QT_END_NAMESPACE
#endif // OSXBTGCDTIMER_P_H

View File

@@ -0,0 +1,336 @@
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtBluetooth module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:LGPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 3 as published by the Free Software
** Foundation and appearing in the file LICENSE.LGPL3 included in the
** packaging of this file. Please review the following information to
** ensure the GNU Lesser General Public License version 3 requirements
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 2.0 or (at your option) the GNU General
** Public license version 3 or any later version approved by the KDE Free
** Qt Foundation. The licenses are as published by the Free Software
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
** https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef OSXBTUTILITY_P_H
#define OSXBTUTILITY_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include "osxbluetooth_p.h"
#include <QtCore/qloggingcategory.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qglobal.h>
#include <Foundation/Foundation.h>
QT_BEGIN_NAMESPACE
class QLowEnergyCharacteristicData;
class QBluetoothAddress;
class QBluetoothUuid;
namespace OSXBluetooth {
struct NSObjectDeleter {
static void cleanup(NSObject *obj)
{
[obj release];
}
};
template<class T>
class ObjCScopedPointer : public QScopedPointer<NSObject, NSObjectDeleter>
{
public:
// TODO: remove default argument, add 'retain' parameter,
// add a default ctor??? This will make the semantics more
// transparent + will simplify the future transition to ARC
// (if it will ever happen).
explicit ObjCScopedPointer(T *ptr = nullptr) : QScopedPointer(ptr){}
operator T*() const
{
return data();
}
T *data()const
{
return static_cast<T *>(QScopedPointer::data());
}
T *take()
{
return static_cast<T *>(QScopedPointer::take());
}
};
#define QT_BT_MAC_AUTORELEASEPOOL const QMacAutoReleasePool pool;
template<class T>
class ObjCStrongReference {
public:
ObjCStrongReference()
: m_ptr(nil)
{
}
ObjCStrongReference(T *obj, bool retain)
{
if (retain)
m_ptr = [obj retain];
else
m_ptr = obj; // For example, created with initWithXXXX.
}
ObjCStrongReference(const ObjCStrongReference &rhs)
{
m_ptr = [rhs.m_ptr retain];
}
ObjCStrongReference &operator = (const ObjCStrongReference &rhs)
{
// "Old-style" implementation:
if (this != &rhs && m_ptr != rhs.m_ptr) {
[m_ptr release];
m_ptr = [rhs.m_ptr retain];
}
return *this;
}
#ifdef Q_COMPILER_RVALUE_REFS
ObjCStrongReference(ObjCStrongReference &&xval)
{
m_ptr = xval.m_ptr;
xval.m_ptr = nil;
}
ObjCStrongReference &operator = (ObjCStrongReference &&xval)
{
m_ptr = xval.m_ptr;
xval.m_ptr = nil;
return *this;
}
#endif
~ObjCStrongReference()
{
[m_ptr release];
}
void reset(T *newVal)
{
if (m_ptr != newVal) {
[m_ptr release];
m_ptr = [newVal retain];
}
}
void resetWithoutRetain(T *newVal)
{
if (m_ptr != newVal) {
[m_ptr release];
m_ptr = newVal;
}
}
operator T *() const
{
return m_ptr;
}
T *data() const
{
return m_ptr;
}
T *take()
{
T * p = m_ptr;
m_ptr = nil;
return p;
}
private:
T *m_ptr;
};
// The type 'T' is some XXXRef from CoreFoundation and co.
// In principle, we can do a trick removing a pointer from a type
// when template is instantiated, but it's quite a lot of ugly pp-tokens
// like OSXBluetooth::CFStrongReference<OSXBluetooth::remove_pointer<CFUUIDRref> > strongReference;
// so instead we use 'T' everywhere, not 'T *' as can expected
// from a smart pointer.
template<class T>
class CFStrongReference {
public:
CFStrongReference()
: m_ptr(nullptr)
{
}
CFStrongReference(T obj, bool retain)
: m_ptr(obj)
{
if (m_ptr && retain)
CFRetain(m_ptr);
}
CFStrongReference(const CFStrongReference &rhs)
{
if ((m_ptr = rhs.m_ptr))
CFRetain(m_ptr);
}
CFStrongReference &operator = (const CFStrongReference &rhs)
{
// "Old-style" implementation:
if (this != &rhs && m_ptr != rhs.m_ptr) {
if (m_ptr)
CFRelease(m_ptr);
if ((m_ptr = rhs.m_ptr))
CFRetain(m_ptr);
}
return *this;
}
#ifdef Q_COMPILER_RVALUE_REFS
CFStrongReference(CFStrongReference &&xval)
{
m_ptr = xval.m_ptr;
xval.m_ptr = nullptr;
}
CFStrongReference &operator = (CFStrongReference &&xval)
{
m_ptr = xval.m_ptr;
xval.m_ptr = nullptr;
return *this;
}
#endif
~CFStrongReference()
{
if (m_ptr)
CFRelease(m_ptr);
}
void reset(T newVal)
{
if (m_ptr != newVal) {
if (m_ptr)
CFRelease(m_ptr);
if ((m_ptr = newVal))
CFRetain(m_ptr);
}
}
operator T() const
{
return m_ptr;
}
T data() const
{
return m_ptr;
}
T take()
{
T p = m_ptr;
m_ptr = nullptr;
return p;
}
private:
T m_ptr;
};
QString qt_address(NSString *address);
#ifndef QT_IOS_BLUETOOTH
QBluetoothAddress qt_address(const BluetoothDeviceAddress *address);
BluetoothDeviceAddress iobluetooth_address(const QBluetoothAddress &address);
ObjCStrongReference<IOBluetoothSDPUUID> iobluetooth_uuid(const QBluetoothUuid &uuid);
QBluetoothUuid qt_uuid(IOBluetoothSDPUUID *uuid);
QString qt_error_string(IOReturn errorCode);
void qt_test_iobluetooth_runloop();
#endif // !QT_IOS_BLUETOOTH
QBluetoothUuid qt_uuid(CBUUID *uuid);
CFStrongReference<CFUUIDRef> cf_uuid(const QBluetoothUuid &qtUuid);
ObjCStrongReference<CBUUID> cb_uuid(const QBluetoothUuid &qtUuid);
bool equal_uuids(const QBluetoothUuid &qtUuid, CBUUID *cbUuid);
bool equal_uuids(CBUUID *cbUuid, const QBluetoothUuid &qtUuid);
QByteArray qt_bytearray(NSData *data);
QByteArray qt_bytearray(NSObject *data);
ObjCStrongReference<NSData> data_from_bytearray(const QByteArray &qtData);
ObjCStrongReference<NSMutableData> mutable_data_from_bytearray(const QByteArray &qtData);
dispatch_queue_t qt_LE_queue();
extern const int defaultLEScanTimeoutMS;
extern const int maxValueLength;
} // namespace OSXBluetooth
// Logging category for both OS X and iOS.
Q_DECLARE_LOGGING_CATEGORY(QT_BT_OSX)
QT_END_NAMESPACE
#if QT_MACOS_PLATFORM_SDK_EQUAL_OR_ABOVE(101300) && QT_MACOS_DEPLOYMENT_TARGET_BELOW(101300)
// In the macOS 10.13 SDK, the identifier property was moved from the CBPeripheral
// and CBCentral classes to a new CBPeer base class. Because CBPeer is only available
// on macOS 10.13 and above, the same is true for -[CBPeer identifier]. However,
// since we know that the derived classes have always had this property,
// we'll explicitly mark its availability here. This will not adversely affect
// using the identifier through the CBPeer base class, which will still require macOS 10.13.
@interface CBPeripheral (UnguardedWorkaround)
@property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_7, 5_0);
@end
@interface CBCentral (UnguardedWorkaround)
@property (readonly, nonatomic) NSUUID *identifier NS_AVAILABLE(10_7, 5_0);
@end
#endif
#endif

View File

@@ -19,6 +19,7 @@ protocol WatchKitConnectionProtocol {
}
class WatchKitConnection: NSObject {
let SwiftDebug = swiftDebug()
static let shared = WatchKitConnection()
weak var delegate: WatchKitConnectionDelegate?
static var currentHeartRate = 0
@@ -144,6 +145,8 @@ extension WatchKitConnection: WCSessionDelegate {
replyValues["speed"] = WatchKitConnection.speed
replyValues["steps"] = Double(WatchKitConnection.steps)
SwiftDebug.qtDebug(replyValues.debugDescription)
replyHandler(replyValues)
//LocalNotificationHelper.fireHeartRate(heartReateDouble)

View File

@@ -84,6 +84,8 @@ class lockscreen {
// Zwift Hub Protobuf
static QByteArray zwift_hub_inclinationCommand(double inclination);
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);
// quick actions
static void set_action_profile(const char* profile);

View File

@@ -374,4 +374,20 @@ QByteArray lockscreen::zwift_hub_setGearsCommand(unsigned int gears) {
return QByteArray(bytes, length);
}
}
// Function to get power value from buffer data
uint32_t lockscreen::zwift_hub_getPowerFromBuffer(const QByteArray& buffer) {
NSData *data = [NSData dataWithBytes:buffer.constData() length:buffer.length()];
uint32_t power = [ZwiftHubBike getPowerFromBufferWithBuffer:data];
return power;
}
// Function to get cadence value from buffer data
uint32_t lockscreen::zwift_hub_getCadenceFromBuffer(const QByteArray& buffer) {
NSData *data = [NSData dataWithBytes:buffer.constData() length:buffer.length()];
uint32_t cadence = [ZwiftHubBike getCadenceFromBufferWithBuffer:data];
return cadence;
}
#endif

View File

@@ -846,7 +846,8 @@ class BLEPeripheralManagerZwift: NSObject, CBPeripheralManagerDelegate {
} else if(self.serviceToggle == 3) {
if(watt_bike_emulator) {
WattBikeSequence = (WattBikeSequence + 1) % 255
let WattBikeArray : [UInt8] = [ WattBikeSequence, 0x03, 0xB6, (UInt8)(self.CurrentGears) ]
let gearValue = max(0, min(self.CurrentGears, 255))
let WattBikeArray: [UInt8] = [ WattBikeSequence, 0x03, 0xB6, UInt8(gearValue) ]
let WattBikeData = Data(bytes: WattBikeArray, count: 4)
let ok = self.peripheralManager.updateValue(WattBikeData, for: self.WattBikeReadCharacteristic, onSubscribedCentrals: nil)
if(ok) {

15
src/logwriter.cpp Normal file
View File

@@ -0,0 +1,15 @@
#include "logwriter.h"
LogWriter::LogWriter(QObject *parent) : QObject(parent) {
}
LogWriter::~LogWriter() {
}
void LogWriter::writeLog(const QString &path, const QString &txt) {
QFile outFile(path);
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream ts(&outFile);
ts << txt;
fprintf(stderr, "%s", txt.toLocal8Bit().constData());
}

19
src/logwriter.h Normal file
View File

@@ -0,0 +1,19 @@
// logwriter.h
#ifndef LOGWRITER_H
#define LOGWRITER_H
#include <QObject>
#include <QFile>
#include <QTextStream>
class LogWriter : public QObject {
Q_OBJECT
public:
explicit LogWriter(QObject *parent = nullptr);
virtual ~LogWriter();
public slots:
void writeLog(const QString &path, const QString &txt);
};
#endif // LOGWRITER_H

View File

@@ -9,7 +9,7 @@
#endif
#endif
#include <QQmlContext>
#include "logwriter.h"
#include "bluetooth.h"
#include "devices/domyostreadmill/domyostreadmill.h"
#include "homeform.h"
@@ -84,6 +84,8 @@ QString deviceName = QLatin1String("");
uint32_t pollDeviceTime = 200;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
QString power_sensor_name = QStringLiteral("Disabled");
bool power_sensor_as_treadmill = false;
QString logfilename = QStringLiteral("debug-") +
QDateTime::currentDateTime()
.toString()
@@ -94,12 +96,150 @@ QString logfilename = QStringLiteral("debug-") +
QUrl profileToLoad;
static const QtMessageHandler QT_DEFAULT_MESSAGE_HANDLER = qInstallMessageHandler(0);
// Function to display help information and exit
void displayHelp() {
printf("qDomyos-Zwift Usage:\n");
printf("General options:\n");
printf(" -h, --help Display this help message and exit\n");
printf(" -no-gui Run in non-GUI mode\n");
printf(" -qml Force QML mode\n");
printf(" -noqml Disable QML mode\n");
printf(" -miles Use miles instead of kilometers\n");
printf(" -no-console Disable console output\n");
printf(" -no-log Disable logging\n");
printf(" -profile <name> Load specific profile\n");
printf("\nDevice configuration:\n");
printf(" -name <device_name> Set device name\n");
printf(" -poll-device-time <ms> Set device polling time in milliseconds\n");
printf(" -no-write-resistance Disable resistance writing\n");
printf(" -no-heart-service Disable heart rate service\n");
printf(" -heart-service Enable heart rate service\n");
printf(" -no-virtual-device-bluetooth Disable virtual device bluetooth\n");
printf("\nBike specific options:\n");
printf(" -only-virtualbike Run only virtual bike mode\n");
printf(" -bike-resistance-gain <value> Set bike resistance gain\n");
printf(" -bike-resistance-offset <value> Set bike resistance offset\n");
printf(" -bike-cadence-sensor Enable bike cadence sensor\n");
printf(" -bike-power-sensor Enable bike power sensor\n");
printf(" -bike-wheel-revs Enable bike wheel revolution tracking\n");
printf(" -power-sensor-name <name> Set power sensor name\n");
printf(" -power-sensor-as-treadmill Use power sensor as treadmill\n");
printf("\nTreadmill specific options:\n");
printf(" -only-virtualtreadmill Run only virtual treadmill mode\n");
printf(" -run-cadence-sensor Enable run cadence sensor\n");
printf(" -horizon-treadmill-7-8 Enable Horizon 7.8 treadmill support\n");
printf(" -horizon-treadmill-force-ftms Force FTMS for Horizon treadmill\n");
printf(" -nordictrack-10-treadmill Enable NordicTrack 10 treadmill support\n");
printf(" -reebok_fr30_treadmill Enable Reebok FR30 treadmill support\n");
printf("\nBluetooth options:\n");
printf(" -no-reconnection Disable bluetooth reconnection\n");
printf(" -bluetooth_relaxed Enable relaxed bluetooth mode\n");
printf(" -battery-service Enable battery service\n");
printf(" -service-changed Enable service changed notifications\n");
printf(" -bluetooth-event-gear-device <device> Set bluetooth event gear device\n");
printf("\nIntegration options:\n");
printf(" -zwift_play Enable Zwift Play\n");
printf(" -zwift_click Enable Zwift Click\n");
printf(" -zwift_play_emulator Enable Zwift Play emulator\n");
printf(" -test-peloton Enable Peloton test mode\n");
printf(" -test-hfb Enable Home Fitness Buddy test mode\n");
printf(" -test-pzp Enable Power Zone Pack test mode\n");
printf(" -train <program> Specify training program\n");
printf("\nPeloton options:\n");
printf(" -peloton-username <username> Set Peloton username\n");
printf(" -peloton-password <password> Set Peloton password\n");
printf("\nPower Zone Pack options:\n");
printf(" -pzp-username <username> Set Power Zone Pack username\n");
printf(" -pzp-password <password> Set Power Zone Pack password\n");
printf("\nOther options:\n");
printf(" -test-resistance Enable resistance testing\n");
printf(" -fit-file-saved-on-quit Save FIT file on application quit\n");
exit(0);
}
#ifdef Q_CC_MSVC
#include <windows.h>
#include <dbghelp.h>
#include <rtcapi.h>
#include <cstdio>
void PrintStack() {
CONTEXT context = {};
RtlCaptureContext(&context);
STACKFRAME64 stackFrame = {};
stackFrame.AddrPC.Offset = context.Rip; // Per x64, usa Rip
stackFrame.AddrPC.Mode = AddrModeFlat;
stackFrame.AddrFrame.Offset = context.Rbp;
stackFrame.AddrFrame.Mode = AddrModeFlat;
stackFrame.AddrStack.Offset = context.Rsp;
stackFrame.AddrStack.Mode = AddrModeFlat;
HANDLE process = GetCurrentProcess();
HANDLE thread = GetCurrentThread();
SymInitialize(process, NULL, TRUE);
while (StackWalk64(
IMAGE_FILE_MACHINE_AMD64, process, thread, &stackFrame, &context,
NULL, SymFunctionTableAccess64, SymGetModuleBase64, NULL)) {
printf("Address: 0x%llx\n", stackFrame.AddrPC.Offset);
}
SymCleanup(process);
}
int __cdecl CustomRTCErrorHandler(int errorType, const wchar_t* filename, int linenumber,
const wchar_t* moduleName, const wchar_t* format, ...)
{
// Buffer for the formatted error message
wchar_t errorMessage[512];
va_list args;
// Format the error message using varargs
va_start(args, format);
vswprintf(errorMessage, sizeof(errorMessage)/sizeof(wchar_t), format, args);
va_end(args);
// Print complete error information
fwprintf(stderr, L"Runtime Error Check Failed!\n");
fwprintf(stderr, L"Error Type: %d\n", errorType);
fwprintf(stderr, L"File: %ls\n", filename ? filename : L"Unknown");
fwprintf(stderr, L"Line: %d\n", linenumber);
fwprintf(stderr, L"Module: %ls\n", moduleName ? moduleName : L"Unknown");
fwprintf(stderr, L"Error Message: %ls\n", errorMessage);
fwprintf(stderr, L"----------------------------------------\n");
#ifdef _DEBUG
__debugbreak(); // Break into debugger in debug builds
#endif
PrintStack();
return 1; // Return non-zero to indicate error was handled
}
#endif
QCoreApplication *createApplication(int &argc, char *argv[]) {
QSettings settings;
bool nogui = false;
for (int i = 1; i < argc; ++i) {
if (!qstrcmp(argv[i], "-h") || !qstrcmp(argv[i], "--help")) {
displayHelp();
// displayHelp() will exit the program
}
if (!qstrcmp(argv[i], "-no-gui")) {
nogui = true;
forceQml = false;
@@ -215,6 +355,12 @@ QCoreApplication *createApplication(int &argc, char *argv[]) {
qDebug() << homeform::getProfileDir() + "/" + profileName << "not found!";
}
}
if (!qstrcmp(argv[i], "-power-sensor-name")) {
power_sensor_name = argv[++i];
}
if (!qstrcmp(argv[i], "-power-sensor-as-treadmill")) {
power_sensor_as_treadmill = true;
}
}
if (nogui) {
@@ -260,6 +406,19 @@ QCoreApplication *createApplication(int &argc, char *argv[]) {
}
}
// Global thread and writer instance
static QThread *logThread = nullptr;
static LogWriter *logWriter = nullptr;
void initializeLogThread() {
if (!logThread) {
logThread = new QThread();
logWriter = new LogWriter();
logWriter->moveToThread(logThread);
logThread->start();
}
}
void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QString &msg) {
QSettings settings;
@@ -298,13 +457,15 @@ void myMessageOutput(QtMsgType type, const QMessageLogContext &context, const QS
QString path = homeform::getWritableAppDir();
// Linux log files are generated on binary location
// Ensure thread is initialized
initializeLogThread();
// Write log in the worker thread
QMetaObject::invokeMethod(logWriter, "writeLog",
Qt::QueuedConnection,
Q_ARG(QString, path + logfilename),
Q_ARG(QString, txt));
QFile outFile(path + logfilename);
outFile.open(QIODevice::WriteOnly | QIODevice::Append);
QTextStream ts(&outFile);
ts << txt;
fprintf(stderr, "%s", txt.toLocal8Bit().constData());
}
(*QT_DEFAULT_MESSAGE_HANDLER)(type, context, msg);
}
@@ -314,6 +475,10 @@ int main(int argc, char *argv[]) {
qputenv("QT_MULTIMEDIA_PREFERRED_PLUGINS", "windowsmediafoundation");
#endif
#ifdef Q_CC_MSVC
_RTC_SetErrorFuncW(CustomRTCErrorHandler);
#endif
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
QScopedPointer<QCoreApplication> app(createApplication(argc, argv));
#else
@@ -420,6 +585,8 @@ int main(int argc, char *argv[]) {
settings.setValue(QZSettings::zwift_play, zwift_play);
settings.setValue(QZSettings::zwift_play_emulator, zwift_play_emulator);
settings.setValue(QZSettings::virtual_device_bluetooth, virtual_device_bluetooth);
settings.setValue(QZSettings::power_sensor_name, power_sensor_name);
settings.setValue(QZSettings::power_sensor_as_treadmill, power_sensor_as_treadmill);
}
#endif
@@ -433,7 +600,7 @@ int main(int argc, char *argv[]) {
qInstallMessageHandler(myMessageOutput);
qDebug() << QStringLiteral("version ") << app->applicationVersion();
foreach (QString s, settings.allKeys()) {
if (!s.contains(QStringLiteral("password")) && !s.contains("user_email") && !s.contains("username")) {
if (!s.contains(QStringLiteral("password")) && !s.contains("user_email") && !s.contains("username") && !s.contains("token")) {
qDebug() << s << settings.value(s);
}

View File

@@ -30,6 +30,7 @@ ApplicationWindow {
signal fit_save_clicked()
signal refresh_bluetooth_devices_clicked()
signal strava_connect_clicked()
signal peloton_connect_clicked()
signal loadSettings(url name)
signal saveSettings(url name)
signal deleteSettings(url name)
@@ -52,6 +53,8 @@ ApplicationWindow {
property string profile_name: "default"
property string theme_status_bar_background_color: "#800080"
property bool volume_change_gears: false
property string peloton_username: "username"
property string peloton_password: "password"
}
Store {
@@ -185,6 +188,33 @@ ApplicationWindow {
}
}
MessageDialog {
id: popupPelotonAuth
text: "Peloton Authentication Change"
informativeText: "Peloton has moved to a new authentication system. Username and password are no longer required.\n\nWould you like to switch to the new authentication method now?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {
settings.peloton_username = "username"
settings.peloton_password = "password"
stackView.push("WebPelotonAuth.qml")
peloton_connect_clicked()
}
onNoClicked: this.visible = false
visible: false
}
Timer {
id: pelotonAuthCheck
interval: 1000 // 1 second delay after startup
running: true
repeat: false
onTriggered: {
if (settings.peloton_password !== "password") {
popupPelotonAuth.visible = true
}
}
}
Popup {
id: popupClassificaHelper
parent: Overlay.overlay
@@ -345,6 +375,40 @@ ApplicationWindow {
}
}
Popup {
id: popupPelotonConnected
parent: Overlay.overlay
enabled: rootItem.pelotonPopupVisible
onEnabledChanged: { if(rootItem.pelotonPopupVisible) popupPelotonConnected.open() }
onClosed: { rootItem.pelotonPopupVisible = false; }
x: Math.round((parent.width - width) / 2)
y: Math.round((parent.height - height) / 2)
width: 380
height: 120
modal: true
focus: true
palette.text: "white"
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutside
enter: Transition
{
NumberAnimation { property: "opacity"; from: 0.0; to: 1.0 }
}
exit: Transition
{
NumberAnimation { property: "opacity"; from: 1.0; to: 0.0 }
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
Label {
anchors.horizontalCenter: parent.horizontalCenter
width: 370
height: 120
text: qsTr("Your Peloton account is now connected!<br><br>Restart the app to apply this change!")
}
}
}
Timer {
id: popupLicenseAutoClose
interval: 120000; running: rootItem.licensePopupVisible; repeat: false
@@ -645,6 +709,9 @@ ApplicationWindow {
toolButtonLoadSettings.visible = true;
toolButtonSaveSettings.visible = true;
stackView.push("settings.qml")
stackView.currentItem.peloton_connect_clicked.connect(function() {
peloton_connect_clicked()
});
drawer.close()
}
}
@@ -777,7 +844,7 @@ ApplicationWindow {
}
ItemDelegate {
text: "version 2.18.17"
text: "version 2.18.20"
width: parent.width
}
@@ -799,6 +866,23 @@ ApplicationWindow {
}
}
ItemDelegate {
Image {
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter
source: "icons/icons/Button_Connect_Rect_DarkMode.png"
fillMode: Image.PreserveAspectFit
visible: true
width: parent.width
}
width: parent.width
onClicked: {
stackView.push("WebPelotonAuth.qml")
peloton_connect_clicked()
drawer.close()
}
}
FileDialog {
id: fileDialogGPX
title: "Please choose a file"

View File

@@ -1,3 +1,13 @@
#if __has_include("secret.h")
#include "secret.h"
#else
#if defined(WIN32)
#pragma message("PELOTON API WILL NOT WORK!!!")
#else
#warning "PELOTON API WILL NOT WORK!!!"
#endif
#endif
#include "homeform.h"
#include "peloton.h"
#include <chrono>
@@ -5,6 +15,8 @@ using namespace std::chrono_literals;
const bool log_request = true;
#define RAWHEADER request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));request.setRawHeader(QByteArray("Authorization"), QByteArray("Bearer ") + settings.value(QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken).toString().toUtf8());
peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
QSettings settings;
@@ -12,6 +24,9 @@ peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
mgr = new QNetworkAccessManager(this);
timer = new QTimer(this);
//peloton_connect_clicked();
peloton_refreshtoken();
// only for test purpose
/*
current_image_downloaded =
@@ -19,9 +34,9 @@ peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
"img_1646099287_a620f71b3d6740718457b21769a7ed46.png"));
*/
if (!settings.value(QZSettings::peloton_username, QZSettings::default_peloton_username)
if (!settings.value(QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken)
.toString()
.compare(QStringLiteral("username"))) {
.length()) {
qDebug() << QStringLiteral("invalid peloton credentials");
return;
}
@@ -627,21 +642,16 @@ void peloton::startEngine() {
QSettings settings;
timer->stop();
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::login_onfinish);
QUrl url(QStringLiteral("https://api.onepeloton.com/auth/login"));
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/me"));
qDebug() << "peloton::getMe" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
QJsonObject obj;
obj[QStringLiteral("username_or_email")] =
settings.value(QZSettings::peloton_username, QZSettings::default_peloton_username).toString();
obj[QStringLiteral("password")] =
settings.value(QZSettings::peloton_password, QZSettings::default_peloton_password).toString();
QJsonDocument doc(obj);
QByteArray data = doc.toJson();
mgr->post(request, data);
RAWHEADER
qDebug() << settings.value(QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken).toString().toLatin1() << request.rawHeader(QByteArray("authorization"));
mgr->get(request);
}
void peloton::login_onfinish(QNetworkReply *reply) {
@@ -663,13 +673,16 @@ void peloton::login_onfinish(QNetworkReply *reply) {
peloton_credentials_wrong = true;
qDebug() << QStringLiteral("invalid peloton credentials during login ") << status;
homeform::singleton()->setToastRequested("Peloton Auth Failed!");
emit loginState(false);
return;
}
user_id = document[QStringLiteral("user_id")].toString();
total_workout = document[QStringLiteral("user_data")][QStringLiteral("total_workouts")].toInt();
user_id = document[QStringLiteral("id")].toString();
total_workout = document[QStringLiteral("total_workouts")].toInt();
qDebug() << "user_id" << user_id << "total workout" << total_workout;
emit loginState(!user_id.isEmpty());
getWorkoutList(1);
@@ -805,12 +818,12 @@ void peloton::workout_onfinish(QNetworkReply *reply) {
current_instructor_id = ride[QStringLiteral("instructor_id")].toString();
current_ride_id = ride[QStringLiteral("id")].toString();
current_workout_type = ride[QStringLiteral("fitness_discipline")].toString();
current_pedaling_duration = ride[QStringLiteral("pedaling_duration")].toInt();
current_pedaling_duration = ride[QStringLiteral("duration")].toInt();
current_image_url = ride[QStringLiteral("image_url")].toString();
qint64 time = ride[QStringLiteral("original_air_time")].toInt();
qDebug() << QStringLiteral("original_air_time") << time;
qDebug() << QStringLiteral("current_pedaling_duration") << current_pedaling_duration;
qint64 time = ride[QStringLiteral("scheduled_start_time")].toInt();
qDebug() << QStringLiteral("scheduled_start_time") << time;
qDebug() << QStringLiteral("duration") << current_pedaling_duration;
current_original_air_time = QDateTime::fromSecsSinceEpoch(time, Qt::UTC);
@@ -1391,7 +1404,191 @@ void peloton::performance_onfinish(QNetworkReply *reply) {
QJsonArray segment_list = json[QStringLiteral("segment_list")].toArray();
trainrows.clear();
if (!target_metrics_performance_data.isEmpty() && bluetoothManager->device() &&
if(!target_metrics_performance_data.isEmpty() && bluetoothManager->device() &&
bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
QJsonArray targetMetrics = target_metrics_performance_data[QStringLiteral("target_metrics")].toArray();
if (targetMetrics.count() > 0)
trainrows.reserve(targetMetrics.count());
QSettings settings;
QString difficulty =
settings.value(QZSettings::peloton_difficulty, QZSettings::default_peloton_difficulty).toString();
bool powerZoneFound = false;
for (int i = 0; i < targetMetrics.count(); i++) {
QJsonObject targetMetric = targetMetrics.at(i).toObject();
QJsonObject offsets = targetMetric[QStringLiteral("offsets")].toObject();
QJsonArray metrics = targetMetric[QStringLiteral("metrics")].toArray();
// Find resistance and cadence metrics
int lowerResistance = 0, upperResistance = 0, lowerCadence = 0, upperCadence = 0;
for (QJsonValue metricValue : metrics) {
QJsonObject metric = metricValue.toObject();
QString name = metric[QStringLiteral("name")].toString();
if (name == QStringLiteral("resistance")) {
lowerResistance = metric[QStringLiteral("lower")].toInt();
upperResistance = metric[QStringLiteral("upper")].toInt();
} else if (name == QStringLiteral("cadence")) {
lowerCadence = metric[QStringLiteral("lower")].toInt();
upperCadence = metric[QStringLiteral("upper")].toInt();
} else if (name == QStringLiteral("power_zone")) {
powerZoneFound = true;
break;
}
}
if(powerZoneFound == true)
break;
trainrow r;
int duration = offsets[QStringLiteral("end")].toInt() - offsets[QStringLiteral("start")].toInt();
if (i != 0) {
// offsets have a 1s gap
duration++;
}
r.lower_requested_peloton_resistance = lowerResistance;
r.upper_requested_peloton_resistance = upperResistance;
r.lower_cadence = lowerCadence;
r.upper_cadence = upperCadence;
r.average_requested_peloton_resistance =
(r.lower_requested_peloton_resistance + r.upper_requested_peloton_resistance) / 2;
r.average_cadence = (r.lower_cadence + r.upper_cadence) / 2;
if (bluetoothManager && bluetoothManager->device()) {
if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
r.lower_resistance = ((bike *)bluetoothManager->device())
->pelotonToBikeResistance(lowerResistance);
r.upper_resistance = ((bike *)bluetoothManager->device())
->pelotonToBikeResistance(upperResistance);
r.average_resistance = ((bike *)bluetoothManager->device())
->pelotonToBikeResistance(r.average_requested_peloton_resistance);
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::ELLIPTICAL) {
r.lower_resistance =
((elliptical *)bluetoothManager->device())
->pelotonToEllipticalResistance(lowerResistance);
r.upper_resistance =
((elliptical *)bluetoothManager->device())
->pelotonToEllipticalResistance(upperResistance);
r.average_resistance = ((elliptical *)bluetoothManager->device())
->pelotonToEllipticalResistance(r.average_requested_peloton_resistance);
}
}
// Set for compatibility
if (difficulty == QStringLiteral("average")) {
r.resistance = r.average_resistance;
r.requested_peloton_resistance = r.average_requested_peloton_resistance;
r.cadence = r.average_cadence;
} else if (difficulty == QStringLiteral("upper")) {
r.resistance = r.upper_resistance;
r.requested_peloton_resistance = r.upper_requested_peloton_resistance;
r.cadence = r.upper_cadence;
} else { // lower
r.resistance = r.lower_resistance;
r.requested_peloton_resistance = r.lower_requested_peloton_resistance;
r.cadence = r.lower_cadence;
}
// Compact rows in the training program
if (i == 0 ||
(r.lower_requested_peloton_resistance != trainrows.last().lower_requested_peloton_resistance ||
r.upper_requested_peloton_resistance != trainrows.last().upper_requested_peloton_resistance ||
r.lower_cadence != trainrows.last().lower_cadence ||
r.upper_cadence != trainrows.last().upper_cadence)) {
r.duration = QTime(0, 0, 0).addSecs(duration);
trainrows.append(r);
} else {
trainrows.last().duration = trainrows.last().duration.addSecs(duration);
}
}
foreach(trainrow r, trainrows) {
qDebug() << r.duration << r.average_cadence << r.average_resistance;
}
QJsonArray targetMetricsList = target_metrics_performance_data[QStringLiteral("target_metrics")].toArray();
bool atLeastOnePower = false;
if (trainrows.empty() && !targetMetricsList.isEmpty() &&
bluetoothManager->device()->deviceType() != bluetoothdevice::ROWING &&
bluetoothManager->device()->deviceType() != bluetoothdevice::TREADMILL) {
int lastEnd = 60;
for (QJsonValue metric : targetMetricsList) {
QJsonObject metricObj = metric.toObject();
QJsonObject offsets = metricObj[QStringLiteral("offsets")].toObject();
int start = offsets[QStringLiteral("start")].toInt();
int end = offsets[QStringLiteral("end")].toInt();
int len = end - start + 1;
// Check if there's a gap from previous segment
if (!trainrows.isEmpty()) {
int prevEnd = start - 1; // Expected previous end
if (lastEnd < prevEnd) {
// Add gap row
trainrow gapRow;
gapRow.duration = QTime(0, (prevEnd - lastEnd + 1) / 60, (prevEnd - lastEnd + 1) % 60, 0);
gapRow.power = -1;
qDebug() << "adding a gap row of " << gapRow.duration << " seconds because start was " << start << " and end " << lastEnd;
trainrows.append(gapRow);
}
}
lastEnd = end;
QJsonArray metricsArray = metricObj[QStringLiteral("metrics")].toArray();
if (!metricsArray.isEmpty()) {
QJsonObject powerMetric = metricsArray[0].toObject();
int zone = powerMetric[QStringLiteral("lower")].toInt();
trainrow r;
r.duration = QTime(0, len / 60, len % 60, 0);
switch(zone) {
case 1: // Zone 1 / Recovery
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.50;
break;
case 2: // Zone 2
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.66;
break;
case 3: // Zone 3
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.83;
break;
case 4: // Zone 4 / Sweet Spot
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 0.98;
break;
case 5: // Zone 5
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 1.13;
break;
case 6: // Zone 6
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 1.35;
break;
case 7: // Zone 7
r.power = settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble() * 1.5;
break;
default:
r.power = -1;
break;
}
if (r.power != -1) {
atLeastOnePower = true;
}
trainrows.append(r);
qDebug() << r.duration << "power" << r.power << "zone" << zone;
}
}
// If this list doesn't have anything useful for this session
if (!atLeastOnePower) {
trainrows.clear();
}
}
} else if (!target_metrics_performance_data.isEmpty() && bluetoothManager->device() &&
bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
double miles = 1;
bool treadmill_force_speed =
@@ -1437,10 +1634,12 @@ void peloton::performance_onfinish(QNetworkReply *reply) {
paceintensity_lower = oo[QStringLiteral("lower")].toInt();
paceintensity_upper = oo[QStringLiteral("upper")].toInt();
paceintensity_avg = ((paceintensity_upper - paceintensity_lower) / 2.0) + paceintensity_lower;
if(paceintensity_lower < 7)
speed_lower = treadmill_pace[paceintensity_lower].levels[peloton_treadmill_level].speed;
speed_upper = treadmill_pace[paceintensity_upper].levels[peloton_treadmill_level].speed;
speed_average = (((speed_upper - speed_lower) / 2.0) + speed_lower) * miles;
if(paceintensity_lower < 7) {
speed_lower = treadmill_pace[paceintensity_lower].levels[peloton_treadmill_level].slow_pace;
speed_upper = treadmill_pace[paceintensity_upper].levels[peloton_treadmill_level].fast_pace;
speed_average = (((speed_upper - speed_lower) / 2.0) + speed_lower) * miles;
miles = 1; // the pace intensity are always in km/h
}
}
}
int offset_start = offset[QStringLiteral("start")].toInt();
@@ -1647,74 +1846,75 @@ double peloton::rowerpaceToSpeed(double pace) {
}
void peloton::getInstructor(const QString &instructor_id) {
QSettings settings;
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::instructor_onfinish);
QUrl url(QStringLiteral("https://api.onepeloton.com/api/instructor/") + instructor_id);
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/instructor/") + instructor_id);
qDebug() << "peloton::getInstructor" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
RAWHEADER
mgr->get(request);
}
void peloton::getRide(const QString &ride_id) {
QSettings settings;
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::ride_onfinish);
QUrl url(QStringLiteral("https://api.onepeloton.com/api/ride/") + ride_id +
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/ride/") + ride_id +
QStringLiteral("/details?stream_source=multichannel"));
qDebug() << "peloton::getRide" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
RAWHEADER
mgr->get(request);
}
void peloton::getPerformance(const QString &workout) {
QSettings settings;
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::performance_onfinish);
QUrl url(QStringLiteral("https://api.onepeloton.com/api/workout/") + workout +
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/workout/") + workout +
QStringLiteral("/performance_graph?every_n=") + QString::number(peloton_workout_second_resolution));
qDebug() << "peloton::getPerformance" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
RAWHEADER
mgr->get(request);
}
void peloton::getWorkout(const QString &workout) {
QSettings settings;
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::workout_onfinish);
QUrl url(QStringLiteral("https://api.onepeloton.com/api/workout/") + workout);
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/workout/") + workout);
qDebug() << "peloton::getWorkout" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
RAWHEADER
mgr->get(request);
}
void peloton::getSummary(const QString &workout) {
QSettings settings;
connect(mgr, &QNetworkAccessManager::finished, this, &peloton::summary_onfinish);
QUrl url(QStringLiteral("https://api.onepeloton.com/api/workout/") + workout + QStringLiteral("/summary"));
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/workout/") + workout + QStringLiteral("/summary"));
qDebug() << "peloton::getSummary" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
RAWHEADER
mgr->get(request);
}
void peloton::getWorkoutList(int num) {
Q_UNUSED(num)
QSettings settings;
// if (num == 0) { //NOTE: clang-analyzer-deadcode.DeadStores
// num = this->total_workout;
// }
@@ -1727,16 +1927,276 @@ void peloton::getWorkoutList(int num) {
int current_page = 0;
QUrl url(QStringLiteral("https://api.onepeloton.com/api/user/") + user_id +
QUrl url(QStringLiteral("https://api-3p.onepeloton.com/api/v1/user") +
QStringLiteral("/workouts?sort_by=-created&page=") + QString::number(current_page) +
QStringLiteral("&limit=") + QString::number(limit));
qDebug() << "peloton::getWorkoutList" << url;
QNetworkRequest request(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/json"));
request.setHeader(QNetworkRequest::UserAgentHeader, QStringLiteral("qdomyos-zwift"));
RAWHEADER
mgr->get(request);
}
void peloton::setTestMode(bool test) { testMode = test; }
void peloton::onPelotonGranted() {
pelotonAuthWebVisible = false;
emit pelotonWebVisibleChanged(pelotonAuthWebVisible);
QSettings settings;
settings.setValue(QZSettings::peloton_accesstoken, pelotonOAuth->token());
settings.setValue(QZSettings::peloton_refreshtoken, pelotonOAuth->refreshToken());
settings.setValue(QZSettings::peloton_lastrefresh, QDateTime::currentDateTime());
qDebug() << QStringLiteral("peloton authenticathed") << pelotonOAuth->token() << pelotonOAuth->refreshToken();
peloton_refreshtoken();
if(homeform::singleton())
homeform::singleton()->setPelotonPopupVisible(true);
}
void peloton::onPelotonAuthorizeWithBrowser(const QUrl &url) {
// ui->textBrowser->append(tr("Open with browser:") + url.toString());
QSettings settings;
bool strava_auth_external_webbrowser =
settings.value(QZSettings::strava_auth_external_webbrowser, QZSettings::default_strava_auth_external_webbrowser)
.toBool();
#if defined(Q_OS_WIN) || (defined(Q_OS_MAC) && !defined(Q_OS_IOS))
strava_auth_external_webbrowser = true;
#endif
pelotonAuthUrl = url.toString();
emit pelotonAuthUrlChanged(pelotonAuthUrl);
if (strava_auth_external_webbrowser)
QDesktopServices::openUrl(url);
else {
pelotonAuthWebVisible = true;
emit pelotonWebVisibleChanged(pelotonAuthWebVisible);
}
}
void peloton::replyDataReceived(const QByteArray &v) {
qDebug() << v;
QByteArray data;
QSettings settings;
QString s(v);
QJsonDocument jsonResponse = QJsonDocument::fromJson(s.toUtf8());
settings.setValue(QZSettings::peloton_accesstoken, jsonResponse[QStringLiteral("access_token")]);
settings.setValue(QZSettings::peloton_refreshtoken, jsonResponse[QStringLiteral("refresh_token")]);
settings.setValue(QZSettings::peloton_expires, jsonResponse[QStringLiteral("expires_at")]);
qDebug() << jsonResponse[QStringLiteral("access_token")] << jsonResponse[QStringLiteral("refresh_token")]
<< jsonResponse[QStringLiteral("expires_at")];
QString urlstr = QStringLiteral("https://www.peloton.com/oauth/token?");
QUrlQuery params;
params.addQueryItem(QStringLiteral("client_id"), QStringLiteral(PELOTON_CLIENT_ID_S));
#ifdef PELOTON_SECRET_KEY
#define _STR(x) #x
#define STRINGIFY(x) _STR(x)
params.addQueryItem("client_secret", STRINGIFY(PELOTON_SECRET_KEY));
#endif
params.addQueryItem(QStringLiteral("code"), peloton_code);
data.append(params.query(QUrl::FullyEncoded).toUtf8());
// trade-in the temporary access code retrieved by the Call-Back URL for the finale token
QUrl url = QUrl(urlstr);
QNetworkRequest request = QNetworkRequest(url);
request.setHeader(QNetworkRequest::ContentTypeHeader, QStringLiteral("application/x-www-form-urlencoded"));
// now get the final token - but ignore errors
if (manager) {
delete manager;
manager = 0;
}
manager = new QNetworkAccessManager(this);
// connect(manager, SIGNAL(sslErrors(QNetworkReply*, const QList<QSslError> & )), this,
// SLOT(onSslErrors(QNetworkReply*, const QList<QSslError> & ))); connect(manager,
// SIGNAL(finished(QNetworkReply*)), this, SLOT(networkRequestFinished(QNetworkReply*)));
manager->post(request, data);
}
void peloton::onSslErrors(QNetworkReply *reply, const QList<QSslError> &error) {
reply->ignoreSslErrors();
qDebug() << QStringLiteral("peloton::onSslErrors") << error;
}
void peloton::networkRequestFinished(QNetworkReply *reply) {
QSettings settings;
// we can handle SSL handshake errors, if we got here then some kind of protocol was agreed
if (reply->error() == QNetworkReply::NoError || reply->error() == QNetworkReply::SslHandshakeFailedError) {
QByteArray payload = reply->readAll(); // JSON
QString refresh_token;
QString access_token;
// parse the response and extract the tokens, pretty much the same for all services
// although polar choose to also pass a user id, which is needed for future calls
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(payload, &parseError);
if (parseError.error == QJsonParseError::NoError) {
refresh_token = document[QStringLiteral("refresh_token")].toString();
access_token = document[QStringLiteral("access_token")].toString();
}
settings.setValue(QZSettings::peloton_accesstoken, access_token);
settings.setValue(QZSettings::peloton_refreshtoken, refresh_token);
settings.setValue(QZSettings::peloton_lastrefresh, QDateTime::currentDateTime());
qDebug() << access_token << refresh_token;
} else {
// general error getting response
QString error =
QString(tr("Error retrieving access token, %1 (%2)")).arg(reply->errorString()).arg(reply->error());
qDebug() << error << reply->url() << reply->readAll();
}
}
void peloton::callbackReceived(const QVariantMap &values) {
qDebug() << QStringLiteral("peloton::callbackReceived") << values;
if (!values.value(QZSettings::peloton_code).toString().isEmpty()) {
peloton_code = values.value(QZSettings::peloton_code).toString();
qDebug() << peloton_code;
}
}
QOAuth2AuthorizationCodeFlow *peloton::peloton_connect() {
if (manager) {
delete manager;
manager = nullptr;
}
if (pelotonOAuth) {
delete pelotonOAuth;
pelotonOAuth = nullptr;
}
if (pelotonReplyHandler) {
delete pelotonReplyHandler;
pelotonReplyHandler = nullptr;
}
manager = new QNetworkAccessManager(this);
OAuth2Parameter parameter;
pelotonOAuth = new QOAuth2AuthorizationCodeFlow(manager, this);
pelotonOAuth->setScope(QStringLiteral("openid offline_access 3p.profile:r 3p.workout:r"));
pelotonOAuth->setClientIdentifier(QStringLiteral(PELOTON_CLIENT_ID_S));
pelotonOAuth->setAuthorizationUrl(QUrl(QStringLiteral("https://auth.onepeloton.com/oauth/authorize")));
pelotonOAuth->setAccessTokenUrl(QUrl(QStringLiteral("https://auth.onepeloton.com/oauth/token")));
pelotonOAuth->setModifyParametersFunction(
buildModifyParametersFunction(QUrl(QLatin1String("")), QUrl(QLatin1String(""))));
pelotonReplyHandler = new QOAuthHttpServerReplyHandler(QHostAddress(QStringLiteral("127.0.0.1")), 18080, this);
connect(pelotonReplyHandler, &QOAuthHttpServerReplyHandler::replyDataReceived, this, &peloton::replyDataReceived);
connect(pelotonReplyHandler, &QOAuthHttpServerReplyHandler::callbackReceived, this, &peloton::callbackReceived);
pelotonOAuth->setReplyHandler(pelotonReplyHandler);
return pelotonOAuth;
}
void peloton::peloton_connect_clicked() {
QLoggingCategory::setFilterRules(QStringLiteral("qt.networkauth.*=true"));
peloton_connect();
connect(pelotonOAuth, &QOAuth2AuthorizationCodeFlow::authorizeWithBrowser, this, &peloton::onPelotonAuthorizeWithBrowser);
connect(pelotonOAuth, &QOAuth2AuthorizationCodeFlow::granted, this, &peloton::onPelotonGranted);
pelotonOAuth->grant();
// qDebug() <<
// QAbstractOAuth2::post("https://www.peloton.com/oauth/authorize?client_id=7976&scope=activity:read_all,activity:write&redirect_uri=http://127.0.0.1&response_type=code&approval_prompt=force");
}
QAbstractOAuth::ModifyParametersFunction peloton::buildModifyParametersFunction(const QUrl &clientIdentifier, const QUrl &clientIdentifierSharedKey) {
return [clientIdentifier, clientIdentifierSharedKey](QAbstractOAuth::Stage stage, QVariantMap *parameters) {
if (stage == QAbstractOAuth::Stage::RequestingAuthorization) {
parameters->insert(QStringLiteral("audience"), QStringLiteral("https://api-3p.onepeloton.com/"));
parameters->insert(QStringLiteral("responseType"), QStringLiteral("code")); /* Request refresh token*/
parameters->insert(QStringLiteral("approval_prompt"),
QStringLiteral("force")); /* force user check scope again */
QByteArray code = parameters->value(QStringLiteral("code")).toByteArray();
// DON'T TOUCH THIS LINE, THANKS Roberto Viola
(*parameters)[QStringLiteral("code")] = QUrl::fromPercentEncoding(code); // NOTE: Old code replaced by
}
if (stage == QAbstractOAuth::Stage::RefreshingAccessToken) {
parameters->insert(QStringLiteral("client_id"), clientIdentifier);
parameters->insert(QStringLiteral("client_secret"), clientIdentifierSharedKey);
}
};
}
void peloton::peloton_refreshtoken() {
QSettings settings;
// QUrlQuery params; //NOTE: clazy-unuse-non-tirial-variable
if (settings.value(QZSettings::peloton_refreshtoken).toString().isEmpty()) {
peloton_connect();
return;
}
QNetworkRequest request(QUrl(QStringLiteral("https://auth.onepeloton.com/oauth/token?")));
request.setRawHeader("Content-Type", "application/x-www-form-urlencoded");
// set params
QString data;
data += QStringLiteral("client_id=" PELOTON_CLIENT_ID_S);
data += QStringLiteral("&refresh_token=") + settings.value(QZSettings::peloton_refreshtoken).toString();
data += QStringLiteral("&grant_type=refresh_token");
// make request
if (manager) {
delete manager;
manager = nullptr;
}
manager = new QNetworkAccessManager(this);
QNetworkReply *reply = manager->post(request, data.toLatin1());
// blocking request
QEventLoop loop;
connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
loop.exec();
int statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute).toInt();
qDebug() << QStringLiteral("HTTP response code: ") << statusCode;
// oops, no dice
if (reply->error() != 0) {
qDebug() << QStringLiteral("Got error") << reply->errorString().toStdString().c_str();
return;
}
// lets extract the access token, and possibly a new refresh token
QByteArray r = reply->readAll();
qDebug() << QStringLiteral("Got response:") << r.data();
QJsonParseError parseError;
QJsonDocument document = QJsonDocument::fromJson(r, &parseError);
// failed to parse result !?
if (parseError.error != QJsonParseError::NoError) {
qDebug() << tr("JSON parser error") << parseError.errorString();
}
QString access_token = document[QStringLiteral("access_token")].toString();
QString refresh_token = document[QStringLiteral("refresh_token")].toString();
settings.setValue(QZSettings::peloton_accesstoken, access_token);
settings.setValue(QZSettings::peloton_refreshtoken, refresh_token);
settings.setValue(QZSettings::peloton_lastrefresh, QDateTime::currentDateTime());
homeform::singleton()->setToastRequested("Peloton Login OK!");
}

View File

@@ -2,6 +2,7 @@
#define PELOTON_H
#include "bluetooth.h"
#include "OAuth2.h"
#include "powerzonepack.h"
#include "trainprogram.h"
#include <QAbstractOAuth2>
@@ -24,6 +25,14 @@
#include "filedownloader.h"
#include "homefitnessbuddy.h"
#if defined(WIN32)
#pragma message("DEFINE PELOTON_SECRET_KEY!!!")
#else
#warning "DEFINE PELOTON_SECRET_KEY!!!"
#endif
#define PELOTON_CLIENT_ID_S STRINGIFY(PELOTON_SECRET_KEY)
class peloton : public QObject {
Q_OBJECT
@@ -50,6 +59,10 @@ class peloton : public QObject {
int current_pedaling_duration = 0;
qint64 start_time = 0;
// OAuth
QString pelotonAuthUrl;
bool pelotonAuthWebVisible;
void setTestMode(bool test);
bool isWorkoutInProgress() {
@@ -58,7 +71,7 @@ class peloton : public QObject {
private:
_PELOTON_API current_api = peloton_api;
const int peloton_workout_second_resolution = 10;
const int peloton_workout_second_resolution = 1;
bool peloton_credentials_wrong = false;
QNetworkAccessManager *mgr = nullptr;
@@ -84,6 +97,17 @@ class peloton : public QObject {
bool testMode = false;
//OAuth
QOAuth2AuthorizationCodeFlow *pelotonOAuth = nullptr;
QNetworkAccessManager *manager = nullptr;
QOAuthHttpServerReplyHandler *pelotonReplyHandler = nullptr;
QString peloton_code;
QOAuth2AuthorizationCodeFlow *peloton_connect();
void peloton_refreshtoken();
QNetworkReply *replyPeloton;
QAbstractOAuth::ModifyParametersFunction buildModifyParametersFunction(const QUrl &clientIdentifier,
const QUrl &clientIdentifierSharedKey);
// rowers
double rowerpaceToSpeed(double pace);
typedef struct _peloton_rower_pace_intensities_level {
@@ -118,6 +142,9 @@ class peloton : public QObject {
_peloton_treadmill_pace_intensities treadmill_pace[7];
public slots:
void peloton_connect_clicked();
private slots:
void login_onfinish(QNetworkReply *reply);
void workoutlist_onfinish(QNetworkReply *reply);
@@ -130,6 +157,14 @@ class peloton : public QObject {
void hfb_trainrows(QList<trainrow> *list);
void pzp_loginState(bool ok);
// OAuth
void onPelotonGranted();
void onPelotonAuthorizeWithBrowser(const QUrl &url);
void replyDataReceived(const QByteArray &v);
void onSslErrors(QNetworkReply *reply, const QList<QSslError> &error);
void networkRequestFinished(QNetworkReply *reply);
void callbackReceived(const QVariantMap &values);
void startEngine();
signals:
@@ -137,6 +172,8 @@ class peloton : public QObject {
void pzpLoginState(bool ok);
void workoutStarted(QString name, QString instructor);
void workoutChanged(QString name, QString instructor);
void pelotonAuthUrlChanged(QString value);
void pelotonWebVisibleChanged(bool value);
};
#endif // PELOTON_H

View File

@@ -31,7 +31,7 @@ CONFIG += qmltypes
#unix:!android: CONFIG += webengine
win32:DEFINES += _ITERATOR_DEBUG_LEVEL=0
win32:!mingw:LIBS += -llibprotobuf -llibprotoc -labseil_dll -llibprotobuf-lite -L$$PWD
win32:!mingw:LIBS += -llibprotobuf -llibprotoc -labseil_dll -llibprotobuf-lite -ldbghelp -L$$PWD
QML_IMPORT_NAME = org.cagnulein.qdomyoszwift
QML_IMPORT_MAJOR_VERSION = 1
@@ -46,6 +46,9 @@ win32:QMAKE_LFLAGS_RELEASE += -static-libstdc++ -static-libgcc -llibcrypto-1_1-x
QMAKE_LFLAGS_RELEASE += -s
QMAKE_CXXFLAGS += -fno-sized-deallocation
msvc {
win32:QMAKE_CXXFLAGS_DEBUG += /RTC1
}
unix:android: {
CONFIG -= optimize_size
QMAKE_CFLAGS_OPTIMIZE_FULL -= -Oz
@@ -92,6 +95,7 @@ SOURCES += \
$$PWD/devices/technogymbike/technogymbike.cpp \
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.cpp \
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.cpp \
$$PWD/logwriter.cpp \
$$PWD/mqtt/qmqttauthenticationproperties.cpp \
$$PWD/mqtt/qmqttclient.cpp \
$$PWD/mqtt/qmqttconnection.cpp \
@@ -331,6 +335,7 @@ INCLUDEPATH += fit-sdk/ devices/
HEADERS += \
$$PWD/EventHandler.h \
$$PWD/OAuth2.h \
$$PWD/devices/antbike/antbike.h \
$$PWD/devices/crossrope/crossrope.h \
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.h \
@@ -349,6 +354,7 @@ HEADERS += \
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.h \
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.h \
$$PWD/ergtable.h \
$$PWD/logwriter.h \
$$PWD/osc.h \
$$PWD/oscpp/client.hpp \
$$PWD/oscpp/detail/endian.hpp \
@@ -949,4 +955,4 @@ INCLUDEPATH += purchasing/inapp
WINRT_MANIFEST = AppxManifest.xml
VERSION = 2.18.17
VERSION = 2.18.20

View File

@@ -114,5 +114,6 @@
<file>StaticAccordionElement.qml</file>
<file>inner_templates/chartjs/dotreadmillchartlive.js</file>
<file>inner_templates/chartjs/treadmillchartlive.htm</file>
<file>WebPelotonAuth.qml</file>
</qresource>
</RCC>

View File

@@ -764,6 +764,16 @@ const QString QZSettings::zwiftplay_swap = QStringLiteral("zwiftplay_swap");
const QString QZSettings::gears_zwift_ratio = QStringLiteral("gears_zwift_ratio");
const QString QZSettings::domyos_bike_500_profile_v2 = QStringLiteral("domyos_bike_500_profile_v2");
const QString QZSettings::gears_offset = QStringLiteral("gears_offset");
const QString QZSettings::peloton_accesstoken = QStringLiteral("peloton_accesstoken");
const QString QZSettings::default_peloton_accesstoken = QStringLiteral("");
const QString QZSettings::peloton_refreshtoken = QStringLiteral("peloton_refreshtoken");
const QString QZSettings::default_peloton_refreshtoken = QStringLiteral("");
const QString QZSettings::peloton_lastrefresh = QStringLiteral("peloton_lastrefresh");
const QString QZSettings::default_peloton_lastrefresh = QStringLiteral("");
const QString QZSettings::peloton_expires = QStringLiteral("peloton_expires");
const QString QZSettings::default_peloton_expires = QStringLiteral("");
const QString QZSettings::peloton_code = QStringLiteral("peloton_code");
const QString QZSettings::default_peloton_code = QStringLiteral("");
const QString QZSettings::proform_carbon_tl_PFTL59720 = QStringLiteral("proform_carbon_tl_PFTL59720");
const QString QZSettings::proform_treadmill_sport_70 = QStringLiteral("proform_treadmill_sport_70");
const QString QZSettings::peloton_date_format = QStringLiteral("peloton_date_format");
@@ -813,8 +823,72 @@ const QString QZSettings::tile_biggears_swap = QStringLiteral("tile_biggears_swa
const QString QZSettings::treadmill_follow_wattage = QStringLiteral("treadmill_follow_wattage");
const QString QZSettings::fit_file_garmin_device_training_effect = QStringLiteral("fit_file_garmin_device_training_effect");
const QString QZSettings::proform_treadmill_705_cst_V80_44 = QStringLiteral("proform_treadmill_705_cst_V80_44");
const QString QZSettings::nordictrack_treadmill_1750_adb = QStringLiteral("nordictrack_treadmill_1750_adb");
const uint32_t allSettingsCount = 687;
const QString QZSettings::tile_preset_powerzone_1_enabled = QStringLiteral("tile_preset_powerzone_1_enabled");
const QString QZSettings::tile_preset_powerzone_1_order = QStringLiteral("tile_preset_powerzone_1_order");
const QString QZSettings::tile_preset_powerzone_1_value = QStringLiteral("tile_preset_powerzone_1_value");
const QString QZSettings::tile_preset_powerzone_1_label = QStringLiteral("tile_preset_powerzone_1_label");
const QString QZSettings::default_tile_preset_powerzone_1_label = QStringLiteral("Zone 1");
const QString QZSettings::tile_preset_powerzone_1_color = QStringLiteral("tile_preset_powerzone_1_color");
const QString QZSettings::default_tile_preset_powerzone_1_color = QStringLiteral("white");
const QString QZSettings::tile_preset_powerzone_2_enabled = QStringLiteral("tile_preset_powerzone_2_enabled");
const QString QZSettings::tile_preset_powerzone_2_order = QStringLiteral("tile_preset_powerzone_2_order");
const QString QZSettings::tile_preset_powerzone_2_value = QStringLiteral("tile_preset_powerzone_2_value");
const QString QZSettings::tile_preset_powerzone_2_label = QStringLiteral("tile_preset_powerzone_2_label");
const QString QZSettings::default_tile_preset_powerzone_2_label = QStringLiteral("Zone 2");
const QString QZSettings::tile_preset_powerzone_2_color = QStringLiteral("tile_preset_powerzone_2_color");
const QString QZSettings::default_tile_preset_powerzone_2_color = QStringLiteral("limegreen");
const QString QZSettings::tile_preset_powerzone_3_enabled = QStringLiteral("tile_preset_powerzone_3_enabled");
const QString QZSettings::tile_preset_powerzone_3_order = QStringLiteral("tile_preset_powerzone_3_order");
const QString QZSettings::tile_preset_powerzone_3_value = QStringLiteral("tile_preset_powerzone_3_value");
const QString QZSettings::tile_preset_powerzone_3_label = QStringLiteral("tile_preset_powerzone_3_label");
const QString QZSettings::default_tile_preset_powerzone_3_label = QStringLiteral("Zone 3");
const QString QZSettings::tile_preset_powerzone_3_color = QStringLiteral("tile_preset_powerzone_3_color");
const QString QZSettings::default_tile_preset_powerzone_3_color = QStringLiteral("gold");
const QString QZSettings::tile_preset_powerzone_4_enabled = QStringLiteral("tile_preset_powerzone_4_enabled");
const QString QZSettings::tile_preset_powerzone_4_order = QStringLiteral("tile_preset_powerzone_4_order");
const QString QZSettings::tile_preset_powerzone_4_value = QStringLiteral("tile_preset_powerzone_4_value");
const QString QZSettings::tile_preset_powerzone_4_label = QStringLiteral("tile_preset_powerzone_4_label");
const QString QZSettings::default_tile_preset_powerzone_4_label = QStringLiteral("Zone 4");
const QString QZSettings::tile_preset_powerzone_4_color = QStringLiteral("tile_preset_powerzone_4_color");
const QString QZSettings::default_tile_preset_powerzone_4_color = QStringLiteral("orange");
const QString QZSettings::tile_preset_powerzone_5_enabled = QStringLiteral("tile_preset_powerzone_5_enabled");
const QString QZSettings::tile_preset_powerzone_5_order = QStringLiteral("tile_preset_powerzone_5_order");
const QString QZSettings::tile_preset_powerzone_5_value = QStringLiteral("tile_preset_powerzone_5_value");
const QString QZSettings::tile_preset_powerzone_5_label = QStringLiteral("tile_preset_powerzone_5_label");
const QString QZSettings::default_tile_preset_powerzone_5_label = QStringLiteral("Zone 5");
const QString QZSettings::tile_preset_powerzone_5_color = QStringLiteral("tile_preset_powerzone_5_color");
const QString QZSettings::default_tile_preset_powerzone_5_color = QStringLiteral("darkorange");
const QString QZSettings::tile_preset_powerzone_6_enabled = QStringLiteral("tile_preset_powerzone_6_enabled");
const QString QZSettings::tile_preset_powerzone_6_order = QStringLiteral("tile_preset_powerzone_6_order");
const QString QZSettings::tile_preset_powerzone_6_value = QStringLiteral("tile_preset_powerzone_6_value");
const QString QZSettings::tile_preset_powerzone_6_label = QStringLiteral("tile_preset_powerzone_6_label");
const QString QZSettings::default_tile_preset_powerzone_6_label = QStringLiteral("Zone 6");
const QString QZSettings::tile_preset_powerzone_6_color = QStringLiteral("tile_preset_powerzone_6_color");
const QString QZSettings::default_tile_preset_powerzone_6_color = QStringLiteral("orangered");
const QString QZSettings::tile_preset_powerzone_7_enabled = QStringLiteral("tile_preset_powerzone_7_enabled");
const QString QZSettings::tile_preset_powerzone_7_order = QStringLiteral("tile_preset_powerzone_7_order");
const QString QZSettings::tile_preset_powerzone_7_value = QStringLiteral("tile_preset_powerzone_7_value");
const QString QZSettings::tile_preset_powerzone_7_label = QStringLiteral("tile_preset_powerzone_7_label");
const QString QZSettings::default_tile_preset_powerzone_7_label = QStringLiteral("Zone 7");
const QString QZSettings::tile_preset_powerzone_7_color = QStringLiteral("tile_preset_powerzone_7_color");
const QString QZSettings::default_tile_preset_powerzone_7_color = QStringLiteral("red");
const QString QZSettings::proform_bike_PFEVEX71316_0 = QStringLiteral("proform_bike_PFEVEX71316_0");
const QString QZSettings::real_inclination_to_virtual_treamill_bridge = QStringLiteral("real_inclination_to_virtual_treamill_bridge");
const QString QZSettings::stryd_inclination_instead_treadmill = QStringLiteral("stryd_inclination_instead_treadmill");
const QString QZSettings::domyos_elliptical_fmts = QStringLiteral("domyos_elliptical_fmts");
const QString QZSettings::proform_xbike = QStringLiteral("proform_xbike");
const uint32_t allSettingsCount = 733;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1467,6 +1541,11 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio},
{QZSettings::domyos_bike_500_profile_v2, QZSettings::default_domyos_bike_500_profile_v2},
{QZSettings::gears_offset, QZSettings::default_gears_offset},
{QZSettings::peloton_accesstoken, QZSettings::default_peloton_accesstoken},
{QZSettings::peloton_refreshtoken, QZSettings::default_peloton_refreshtoken},
{QZSettings::peloton_lastrefresh, QZSettings::default_peloton_lastrefresh},
{QZSettings::peloton_expires, QZSettings::default_peloton_expires},
{QZSettings::peloton_code, QZSettings::default_peloton_code},
{QZSettings::proform_carbon_tl_PFTL59720, QZSettings::default_proform_carbon_tl_PFTL59720},
{QZSettings::proform_treadmill_sport_70, QZSettings::default_proform_treadmill_sport_70},
{QZSettings::peloton_date_format, QZSettings::default_peloton_date_format},
@@ -1508,6 +1587,55 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::treadmill_follow_wattage, QZSettings::default_treadmill_follow_wattage},
{QZSettings::fit_file_garmin_device_training_effect, QZSettings::default_fit_file_garmin_device_training_effect},
{QZSettings::proform_treadmill_705_cst_V80_44, QZSettings::default_proform_treadmill_705_cst_V80_44},
{QZSettings::nordictrack_treadmill_1750_adb, QZSettings::default_nordictrack_treadmill_1750_adb},
{QZSettings::tile_preset_powerzone_1_enabled, QZSettings::default_tile_preset_powerzone_1_enabled},
{QZSettings::tile_preset_powerzone_1_order, QZSettings::default_tile_preset_powerzone_1_order},
{QZSettings::tile_preset_powerzone_1_value, QZSettings::default_tile_preset_powerzone_1_value},
{QZSettings::tile_preset_powerzone_1_label, QZSettings::default_tile_preset_powerzone_1_label},
{QZSettings::tile_preset_powerzone_1_color, QZSettings::default_tile_preset_powerzone_1_color},
{QZSettings::tile_preset_powerzone_2_enabled, QZSettings::default_tile_preset_powerzone_2_enabled},
{QZSettings::tile_preset_powerzone_2_order, QZSettings::default_tile_preset_powerzone_2_order},
{QZSettings::tile_preset_powerzone_2_value, QZSettings::default_tile_preset_powerzone_2_value},
{QZSettings::tile_preset_powerzone_2_label, QZSettings::default_tile_preset_powerzone_2_label},
{QZSettings::tile_preset_powerzone_2_color, QZSettings::default_tile_preset_powerzone_2_color},
{QZSettings::tile_preset_powerzone_3_enabled, QZSettings::default_tile_preset_powerzone_3_enabled},
{QZSettings::tile_preset_powerzone_3_order, QZSettings::default_tile_preset_powerzone_3_order},
{QZSettings::tile_preset_powerzone_3_value, QZSettings::default_tile_preset_powerzone_3_value},
{QZSettings::tile_preset_powerzone_3_label, QZSettings::default_tile_preset_powerzone_3_label},
{QZSettings::tile_preset_powerzone_3_color, QZSettings::default_tile_preset_powerzone_3_color},
{QZSettings::tile_preset_powerzone_4_enabled, QZSettings::default_tile_preset_powerzone_4_enabled},
{QZSettings::tile_preset_powerzone_4_order, QZSettings::default_tile_preset_powerzone_4_order},
{QZSettings::tile_preset_powerzone_4_value, QZSettings::default_tile_preset_powerzone_4_value},
{QZSettings::tile_preset_powerzone_4_label, QZSettings::default_tile_preset_powerzone_4_label},
{QZSettings::tile_preset_powerzone_4_color, QZSettings::default_tile_preset_powerzone_4_color},
{QZSettings::tile_preset_powerzone_5_enabled, QZSettings::default_tile_preset_powerzone_5_enabled},
{QZSettings::tile_preset_powerzone_5_order, QZSettings::default_tile_preset_powerzone_5_order},
{QZSettings::tile_preset_powerzone_5_value, QZSettings::default_tile_preset_powerzone_5_value},
{QZSettings::tile_preset_powerzone_5_label, QZSettings::default_tile_preset_powerzone_5_label},
{QZSettings::tile_preset_powerzone_5_color, QZSettings::default_tile_preset_powerzone_5_color},
{QZSettings::tile_preset_powerzone_6_enabled, QZSettings::default_tile_preset_powerzone_6_enabled},
{QZSettings::tile_preset_powerzone_6_order, QZSettings::default_tile_preset_powerzone_6_order},
{QZSettings::tile_preset_powerzone_6_value, QZSettings::default_tile_preset_powerzone_6_value},
{QZSettings::tile_preset_powerzone_6_label, QZSettings::default_tile_preset_powerzone_6_label},
{QZSettings::tile_preset_powerzone_6_color, QZSettings::default_tile_preset_powerzone_6_color},
{QZSettings::tile_preset_powerzone_7_enabled, QZSettings::default_tile_preset_powerzone_7_enabled},
{QZSettings::tile_preset_powerzone_7_order, QZSettings::default_tile_preset_powerzone_7_order},
{QZSettings::tile_preset_powerzone_7_value, QZSettings::default_tile_preset_powerzone_7_value},
{QZSettings::tile_preset_powerzone_7_label, QZSettings::default_tile_preset_powerzone_7_label},
{QZSettings::tile_preset_powerzone_7_color, QZSettings::default_tile_preset_powerzone_7_color},
{QZSettings::proform_bike_PFEVEX71316_0, QZSettings::default_proform_bike_PFEVEX71316_0},
{QZSettings::real_inclination_to_virtual_treamill_bridge, QZSettings::default_real_inclination_to_virtual_treamill_bridge},
{QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill},
{QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts},
{QZSettings::proform_xbike, QZSettings::default_proform_xbike},
};
void QZSettings::qDebugAllSettings(bool showDefaults) {

View File

@@ -2138,6 +2138,21 @@ class QZSettings {
static const QString gears_offset;
static constexpr double default_gears_offset = 0.0;
static const QString peloton_accesstoken;
static const QString default_peloton_accesstoken;
static const QString peloton_refreshtoken;
static const QString default_peloton_refreshtoken;
static const QString peloton_lastrefresh;
static const QString default_peloton_lastrefresh;
static const QString peloton_expires;
static const QString default_peloton_expires;
static const QString peloton_code;
static const QString default_peloton_code;
static const QString proform_carbon_tl_PFTL59720;
static constexpr bool default_proform_carbon_tl_PFTL59720 = false;
@@ -2261,6 +2276,129 @@ class QZSettings {
static const QString proform_treadmill_705_cst_V80_44;
static constexpr bool default_proform_treadmill_705_cst_V80_44 = false;
static const QString nordictrack_treadmill_1750_adb;
static constexpr bool default_nordictrack_treadmill_1750_adb = false;
static const QString tile_preset_powerzone_1_enabled;
static constexpr bool default_tile_preset_powerzone_1_enabled = false;
static const QString tile_preset_powerzone_1_order;
static constexpr int default_tile_preset_powerzone_1_order = 55;
static const QString tile_preset_powerzone_1_value;
static constexpr double default_tile_preset_powerzone_1_value = 1.0;
static const QString tile_preset_powerzone_1_label;
static const QString default_tile_preset_powerzone_1_label;
static const QString tile_preset_powerzone_1_color;
static const QString default_tile_preset_powerzone_1_color;
static const QString tile_preset_powerzone_2_enabled;
static constexpr bool default_tile_preset_powerzone_2_enabled = false;
static const QString tile_preset_powerzone_2_order;
static constexpr int default_tile_preset_powerzone_2_order = 56;
static const QString tile_preset_powerzone_2_value;
static constexpr double default_tile_preset_powerzone_2_value = 2.0;
static const QString tile_preset_powerzone_2_label;
static const QString default_tile_preset_powerzone_2_label;
static const QString tile_preset_powerzone_2_color;
static const QString default_tile_preset_powerzone_2_color;
static const QString tile_preset_powerzone_3_enabled;
static constexpr bool default_tile_preset_powerzone_3_enabled = false;
static const QString tile_preset_powerzone_3_order;
static constexpr int default_tile_preset_powerzone_3_order = 57;
static const QString tile_preset_powerzone_3_value;
static constexpr double default_tile_preset_powerzone_3_value = 3.0;
static const QString tile_preset_powerzone_3_label;
static const QString default_tile_preset_powerzone_3_label;
static const QString tile_preset_powerzone_3_color;
static const QString default_tile_preset_powerzone_3_color;
static const QString tile_preset_powerzone_4_enabled;
static constexpr bool default_tile_preset_powerzone_4_enabled = false;
static const QString tile_preset_powerzone_4_order;
static constexpr int default_tile_preset_powerzone_4_order = 58;
static const QString tile_preset_powerzone_4_value;
static constexpr double default_tile_preset_powerzone_4_value = 4.0;
static const QString tile_preset_powerzone_4_label;
static const QString default_tile_preset_powerzone_4_label;
static const QString tile_preset_powerzone_4_color;
static const QString default_tile_preset_powerzone_4_color;
static const QString tile_preset_powerzone_5_enabled;
static constexpr bool default_tile_preset_powerzone_5_enabled = false;
static const QString tile_preset_powerzone_5_order;
static constexpr int default_tile_preset_powerzone_5_order = 59;
static const QString tile_preset_powerzone_5_value;
static constexpr double default_tile_preset_powerzone_5_value = 5.0;
static const QString tile_preset_powerzone_5_label;
static const QString default_tile_preset_powerzone_5_label;
static const QString tile_preset_powerzone_5_color;
static const QString default_tile_preset_powerzone_5_color;
static const QString tile_preset_powerzone_6_enabled;
static constexpr bool default_tile_preset_powerzone_6_enabled = false;
static const QString tile_preset_powerzone_6_order;
static constexpr int default_tile_preset_powerzone_6_order = 60;
static const QString tile_preset_powerzone_6_value;
static constexpr double default_tile_preset_powerzone_6_value = 6.0;
static const QString tile_preset_powerzone_6_label;
static const QString default_tile_preset_powerzone_6_label;
static const QString tile_preset_powerzone_6_color;
static const QString default_tile_preset_powerzone_6_color;
static const QString tile_preset_powerzone_7_enabled;
static constexpr bool default_tile_preset_powerzone_7_enabled = false;
static const QString tile_preset_powerzone_7_order;
static constexpr int default_tile_preset_powerzone_7_order = 61;
static const QString tile_preset_powerzone_7_value;
static constexpr double default_tile_preset_powerzone_7_value = 7.0;
static const QString tile_preset_powerzone_7_label;
static const QString default_tile_preset_powerzone_7_label;
static const QString tile_preset_powerzone_7_color;
static const QString default_tile_preset_powerzone_7_color;
static const QString proform_bike_PFEVEX71316_0;
static constexpr bool default_proform_bike_PFEVEX71316_0 = false;
static const QString real_inclination_to_virtual_treamill_bridge;
static constexpr bool default_real_inclination_to_virtual_treamill_bridge = false;
static const QString stryd_inclination_instead_treadmill;
static constexpr bool default_stryd_inclination_instead_treadmill = false;
static const QString domyos_elliptical_fmts;
static constexpr bool default_domyos_elliptical_fmts = false;
static const QString proform_xbike;
static constexpr bool default_proform_xbike = 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

@@ -196,6 +196,48 @@ ScrollView {
property bool tile_biggears_enabled: false
property int tile_biggears_order: 54
property bool tile_biggears_swap: false
property bool tile_preset_powerzone_1_enabled: false
property int tile_preset_powerzone_1_order: 55
property real tile_preset_powerzone_1_value: 1.0
property string tile_preset_powerzone_1_label: "Zone 1"
property string tile_preset_powerzone_1_color: "white"
property bool tile_preset_powerzone_2_enabled: false
property int tile_preset_powerzone_2_order: 56
property real tile_preset_powerzone_2_value: 2.0
property string tile_preset_powerzone_2_label: "Zone 2"
property string tile_preset_powerzone_2_color: "limegreen"
property bool tile_preset_powerzone_3_enabled: false
property int tile_preset_powerzone_3_order: 57
property real tile_preset_powerzone_3_value: 3.0
property string tile_preset_powerzone_3_label: "Zone 3"
property string tile_preset_powerzone_3_color: "gold"
property bool tile_preset_powerzone_4_enabled: false
property int tile_preset_powerzone_4_order: 58
property real tile_preset_powerzone_4_value: 4.0
property string tile_preset_powerzone_4_label: "Zone 4"
property string tile_preset_powerzone_4_color: "orange"
property bool tile_preset_powerzone_5_enabled: false
property int tile_preset_powerzone_5_order: 59
property real tile_preset_powerzone_5_value: 5.0
property string tile_preset_powerzone_5_label: "Zone 5"
property string tile_preset_powerzone_5_color: "darkorange"
property bool tile_preset_powerzone_6_enabled: false
property int tile_preset_powerzone_6_order: 60
property real tile_preset_powerzone_6_value: 6.0
property string tile_preset_powerzone_6_label: "Zone 6"
property string tile_preset_powerzone_6_color: "orangered"
property bool tile_preset_powerzone_7_enabled: false
property int tile_preset_powerzone_7_order: 61
property real tile_preset_powerzone_7_value: 7.0
property string tile_preset_powerzone_7_label: "Zone 7"
property string tile_preset_powerzone_7_color: "red"
}
@@ -4009,6 +4051,725 @@ ScrollView {
}
}
}
}
AccordionCheckElement {
id: presetPowerZone1EnabledAccordion
title: qsTr("Preset Power Zone 1")
linkedBoolSetting: "tile_preset_powerzone_1_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone1OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_1_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone1OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_1_order = presetPowerZone1OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone1Value
text: settings.tile_preset_powerzone_1_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_1_value = parseFloat(presetPowerZone1Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone1Label
text: settings.tile_preset_powerzone_1_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_1_label = presetPowerZone1Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone1Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone1
title: "Please choose a color"
onAccepted: {
presetPowerzone1ColorTextField.text = colorPresetPowerzone1.color
}
onRejected: {}
}
TextField {
id: presetPowerzone1ColorTextField
text: settings.tile_preset_powerzone_1_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone1.visible = true
}
}
Button {
id: okPresetPowerzone1ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_1_color = presetPowerzone1ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
AccordionCheckElement {
id: presetPowerZone2EnabledAccordion
title: qsTr("Preset Power Zone 2")
linkedBoolSetting: "tile_preset_powerzone_2_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone2OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_2_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone2OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_2_order = presetPowerZone2OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone2Value
text: settings.tile_preset_powerzone_2_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_2_value = parseFloat(presetPowerZone2Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone2Label
text: settings.tile_preset_powerzone_2_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_2_label = presetPowerZone2Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone2Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone2
title: "Please choose a color"
onAccepted: {
presetPowerzone2ColorTextField.text = colorPresetPowerzone2.color
}
onRejected: {}
}
TextField {
id: presetPowerzone2ColorTextField
text: settings.tile_preset_powerzone_2_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone2.visible = true
}
}
Button {
id: okPresetPowerzone2ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_2_color = presetPowerzone2ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
AccordionCheckElement {
id: presetPowerZone3EnabledAccordion
title: qsTr("Preset Power Zone 3")
linkedBoolSetting: "tile_preset_powerzone_3_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone3OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_3_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone3OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_3_order = presetPowerZone3OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone3Value
text: settings.tile_preset_powerzone_3_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_3_value = parseFloat(presetPowerZone3Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone3Label
text: settings.tile_preset_powerzone_3_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_3_label = presetPowerZone3Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone3Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone3
title: "Please choose a color"
onAccepted: {
presetPowerzone3ColorTextField.text = colorPresetPowerzone3.color
}
onRejected: {}
}
TextField {
id: presetPowerzone3ColorTextField
text: settings.tile_preset_powerzone_3_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone3.visible = true
}
}
Button {
id: okPresetPowerzone3ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_3_color = presetPowerzone3ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
AccordionCheckElement {
id: presetPowerZone4EnabledAccordion
title: qsTr("Preset Power Zone 4")
linkedBoolSetting: "tile_preset_powerzone_4_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone4OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_4_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone4OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_4_order = presetPowerZone4OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone4Value
text: settings.tile_preset_powerzone_4_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_4_value = parseFloat(presetPowerZone4Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone4Label
text: settings.tile_preset_powerzone_4_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_4_label = presetPowerZone4Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone4Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone4
title: "Please choose a color"
onAccepted: {
presetPowerzone4ColorTextField.text = colorPresetPowerzone4.color
}
onRejected: {}
}
TextField {
id: presetPowerzone4ColorTextField
text: settings.tile_preset_powerzone_4_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone4.visible = true
}
}
Button {
id: okPresetPowerzone4ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_4_color = presetPowerzone4ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
AccordionCheckElement {
id: presetPowerZone5EnabledAccordion
title: qsTr("Preset Power Zone 5")
linkedBoolSetting: "tile_preset_powerzone_5_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone5OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_5_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone5OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_5_order = presetPowerZone5OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone5Value
text: settings.tile_preset_powerzone_5_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_5_value = parseFloat(presetPowerZone5Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone5Label
text: settings.tile_preset_powerzone_5_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_5_label = presetPowerZone5Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone5Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone5
title: "Please choose a color"
onAccepted: {
presetPowerzone5ColorTextField.text = colorPresetPowerzone5.color
}
onRejected: {}
}
TextField {
id: presetPowerzone5ColorTextField
text: settings.tile_preset_powerzone_5_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone5.visible = true
}
}
Button {
id: okPresetPowerzone5ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_5_color = presetPowerzone5ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
AccordionCheckElement {
id: presetPowerZone6EnabledAccordion
title: qsTr("Preset Power Zone 6")
linkedBoolSetting: "tile_preset_powerzone_6_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone6OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_6_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone6OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_6_order = presetPowerZone6OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone6Value
text: settings.tile_preset_powerzone_6_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_6_value = parseFloat(presetPowerZone6Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone6Label
text: settings.tile_preset_powerzone_6_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_6_label = presetPowerZone6Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone6Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone6
title: "Please choose a color"
onAccepted: {
presetPowerzone6ColorTextField.text = colorPresetPowerzone6.color
}
onRejected: {}
}
TextField {
id: presetPowerzone6ColorTextField
text: settings.tile_preset_powerzone_6_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone6.visible = true
}
}
Button {
id: okPresetPowerzone6ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_6_color = presetPowerzone6ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
AccordionCheckElement {
id: presetPowerZone7EnabledAccordion
title: qsTr("Preset Power Zone 7")
linkedBoolSetting: "tile_preset_powerzone_7_enabled"
settings: settings
accordionContent: ColumnLayout {
spacing: 10
RowLayout {
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: presetPowerZone7OrderTextField
model: rootItem.tile_order
displayText: settings.tile_preset_powerzone_7_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = presetPowerZone7OrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_7_order = presetPowerZone7OrderTextField.displayText; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("zone value:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone7Value
text: settings.tile_preset_powerzone_7_value
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
validator: DoubleValidator {bottom: 1; top: 7;}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_7_value = parseFloat(presetPowerZone7Value.text); toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
text: qsTr("label:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
TextField {
id: presetPowerZone7Label
text: settings.tile_preset_powerzone_7_label
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_7_label = presetPowerZone7Label.text; toast.show("Setting saved!"); }
}
}
RowLayout {
Label {
id: labelPresetPowerzone7Color
text: qsTr("color:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ColorDialog {
id: colorPresetPowerzone7
title: "Please choose a color"
onAccepted: {
presetPowerzone7ColorTextField.text = colorPresetPowerzone7.color
}
onRejected: {}
}
TextField {
id: presetPowerzone7ColorTextField
text: settings.tile_preset_powerzone_7_color
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
onPressed: {
if(OS_VERSION !== "Android") colorPresetPowerzone7.visible = true
}
}
Button {
id: okPresetPowerzone7ColorButton
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_preset_powerzone_7_color = presetPowerzone7ColorTextField.displayText; toast.show("Setting saved!"); }
}
}
}
}
Label {
text: qsTr("Power zone presets allow quick access to specific training zones with customizable labels and values.")
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)
}
}
}

View File

@@ -4,6 +4,7 @@ import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.0
import Qt.labs.platform 1.1
//Page {
ScrollView {
@@ -15,6 +16,8 @@ import QtQuick.Dialogs 1.0
//anchors.bottomMargin: footerSettings.height + 10
id: settingsPane
signal peloton_connect_clicked()
Settings {
id: settings
property real ui_zoom: 100.0
@@ -977,6 +980,7 @@ import QtQuick.Dialogs 1.0
property bool gears_zwift_ratio: false
property bool domyos_bike_500_profile_v2: false
property double gears_offset: 0.0
property bool proform_carbon_tl_PFTL59720: false
// from version 2.16.71
@@ -1049,6 +1053,65 @@ import QtQuick.Dialogs 1.0
property bool treadmill_follow_wattage: false
property bool fit_file_garmin_device_training_effect: false
property bool proform_treadmill_705_cst_V80_44: false
// from version 2.18.19
property string peloton_accesstoken: ""
property string peloton_refreshtoken: ""
property string peloton_lastrefresh: ""
property string peloton_expires: ""
property string peloton_code: ""
property bool nordictrack_treadmill_1750_adb: false
property bool tile_preset_powerzone_1_enabled: false
property int tile_preset_powerzone_1_order: 55
property real tile_preset_powerzone_1_value: 1.0
property string tile_preset_powerzone_1_label: "Zone 1"
property string tile_preset_powerzone_1_color: "white"
property bool tile_preset_powerzone_2_enabled: false
property int tile_preset_powerzone_2_order: 56
property real tile_preset_powerzone_2_value: 2.0
property string tile_preset_powerzone_2_label: "Zone 2"
property string tile_preset_powerzone_2_color: "limegreen"
property bool tile_preset_powerzone_3_enabled: false
property int tile_preset_powerzone_3_order: 57
property real tile_preset_powerzone_3_value: 3.0
property string tile_preset_powerzone_3_label: "Zone 3"
property string tile_preset_powerzone_3_color: "gold"
property bool tile_preset_powerzone_4_enabled: false
property int tile_preset_powerzone_4_order: 58
property real tile_preset_powerzone_4_value: 4.0
property string tile_preset_powerzone_4_label: "Zone 4"
property string tile_preset_powerzone_4_color: "orange"
property bool tile_preset_powerzone_5_enabled: false
property int tile_preset_powerzone_5_order: 59
property real tile_preset_powerzone_5_value: 5.0
property string tile_preset_powerzone_5_label: "Zone 5"
property string tile_preset_powerzone_5_color: "darkorange"
property bool tile_preset_powerzone_6_enabled: false
property int tile_preset_powerzone_6_order: 60
property real tile_preset_powerzone_6_value: 6.0
property string tile_preset_powerzone_6_label: "Zone 6"
property string tile_preset_powerzone_6_color: "orangered"
property bool tile_preset_powerzone_7_enabled: false
property int tile_preset_powerzone_7_order: 61
property real tile_preset_powerzone_7_value: 7.0
property string tile_preset_powerzone_7_label: "Zone 7"
property string tile_preset_powerzone_7_color: "red"
property bool proform_bike_PFEVEX71316_0: false
property bool real_inclination_to_virtual_treamill_bridge: false
property bool stryd_inclination_instead_treadmill: false
// 2.18.20
property bool domyos_elliptical_fmts: false
property bool proform_xbike: false
}
function paddingZeros(text, limit) {
@@ -3138,69 +3201,85 @@ import QtQuick.Dialogs 1.0
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Yellow)
color: Material.backgroundColor
accordionContent: RowLayout {
spacing: 10
Label {
id: labelDomyosBikeCadenceFilter
text: qsTr("Cadence Filter:")
accordionContent: ColumnLayout {
spacing: 0
RowLayout {
spacing: 10
Label {
id: labelDomyosBikeCadenceFilter
text: qsTr("Cadence Filter:")
Layout.fillWidth: true
}
TextField {
id: domyosBikeCadenceFilterTextField
text: settings.domyos_bike_cadence_filter
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.domyos_bike_cadence_filter = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
}
Button {
id: okDomyosBikeCadenceFilter
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.domyos_bike_cadence_filter = domyosBikeCadenceFilterTextField.text; toast.show("Setting saved!"); }
}
}
IndicatorOnlySwitch {
text: qsTr("Ignore FTMS")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyosbike_notfmts
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.domyosbike_notfmts = checked
}
TextField {
id: domyosBikeCadenceFilterTextField
text: settings.domyos_bike_cadence_filter
horizontalAlignment: Text.AlignRight
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
inputMethodHints: Qt.ImhDigitsOnly
onAccepted: settings.domyos_bike_cadence_filter = text
onActiveFocusChanged: if(this.focus) this.cursorPosition = this.text.length
IndicatorOnlySwitch {
text: qsTr("Fix Calories/Km to Console")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyos_bike_display_calories
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.domyos_bike_display_calories = checked
}
Button {
id: okDomyosBikeCadenceFilter
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: { settings.domyos_bike_cadence_filter = domyosBikeCadenceFilterTextField.text; toast.show("Setting saved!"); }
IndicatorOnlySwitch {
text: qsTr("Bike 500 wattage profile")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyos_bike_500_profile_v1
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.domyos_bike_500_profile_v1 = checked; settings.domyos_bike_500_profile_v2 = false; }
}
IndicatorOnlySwitch {
text: qsTr("Bike 500 wattage profile v2")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyos_bike_500_profile_v2
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.domyos_bike_500_profile_v2 = checked; settings.domyos_bike_500_profile_v1 = false; }
}
}
IndicatorOnlySwitch {
text: qsTr("Fix Calories/Km to Console")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyos_bike_display_calories
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.domyos_bike_display_calories = checked
}
IndicatorOnlySwitch {
text: qsTr("Bike 500 wattage profile")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyos_bike_500_profile_v1
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.domyos_bike_500_profile_v1 = checked; settings.domyos_bike_500_profile_v2 = false; }
}
IndicatorOnlySwitch {
text: qsTr("Bike 500 wattage profile v2")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.domyos_bike_500_profile_v2
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.domyos_bike_500_profile_v2 = checked; settings.domyos_bike_500_profile_v1 = false; }
}
}
AccordionElement {
title: qsTr("Tacx Neo Options")
@@ -3279,7 +3358,9 @@ import QtQuick.Dialogs 1.0
"Proform 225 CSX",
"Proform 325 CSX / Healthrider H30X",
"Proform SB",
"Nordictrack GX 4.4 Pro"
"Nordictrack GX 4.4 Pro",
"TDF 1.0 PFEVEX71316.0",
"Proform XBike"
]
// Initialize when the accordion content becomes visible
@@ -3311,7 +3392,9 @@ import QtQuick.Dialogs 1.0
settings.proform_bike_225_csx ? 12 :
settings.proform_bike_325_csx ? 13 :
settings.proform_bike_sb ? 14 :
settings.nordictrack_gx_44_pro ? 15 : 0;
settings.nordictrack_gx_44_pro ? 15 :
settings.proform_bike_PFEVEX71316_0 ? 16 :
settings.proform_xbike ? 17 : 0;
console.log("bikeModelComboBox selected model: " + selectedModel);
if (selectedModel >= 0) {
@@ -3341,6 +3424,8 @@ import QtQuick.Dialogs 1.0
settings.proform_bike_325_csx = false;
settings.proform_bike_sb = false;
settings.nordictrack_gx_44_pro = false;
settings.proform_bike_PFEVEX71316_0 = false;
settings.proform_xbike = false;
// Set corresponding setting for selected model
switch (currentIndex) {
@@ -3359,6 +3444,8 @@ import QtQuick.Dialogs 1.0
case 13: settings.proform_bike_325_csx = true; break;
case 14: settings.proform_bike_sb = true; break;
case 15: settings.nordictrack_gx_44_pro = true; break;
case 16: settings.proform_bike_PFEVEX71316_0 = true; break;
case 17: settings.proform_xbike = true; break;
}
window.settings_restart_to_apply = true;
@@ -4177,7 +4264,7 @@ import QtQuick.Dialogs 1.0
color: Material.backgroundColor
accordionContent: ColumnLayout {
spacing: 0
/*
RowLayout {
spacing: 10
Label {
@@ -4254,6 +4341,23 @@ import QtQuick.Dialogs 1.0
Layout.fillWidth: true
color: Material.color(Material.Lime)
}
*/
ItemDelegate {
Image {
anchors.left: parent.left;
anchors.verticalCenter: parent.verticalCenter
source: "icons/icons/Button_Connect_Rect_DarkMode.png"
fillMode: Image.PreserveAspectFit
visible: true
width: parent.width
}
Layout.fillWidth: true
onClicked: {
stackView.push("WebPelotonAuth.qml")
peloton_connect_clicked()
}
}
RowLayout {
spacing: 10
@@ -4963,6 +5067,20 @@ import QtQuick.Dialogs 1.0
color: Material.color(Material.Lime)
}
MessageDialog {
id: zwiftPlaySettingsDialog
text: "Zwift Play & Click Settings"
informativeText: "Would you like to disable Zwift Play and Zwift Click settings? Having them enabled together with 'Get gears from Zwift' may cause conflicts."
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {
settings.zwift_play = false;
settings.zwift_click = false;
settings.zwift_play_emulator = true;
window.settings_restart_to_apply = true;
}
visible: false
}
IndicatorOnlySwitch {
text: qsTr("Get Gears from Zwift")
spacing: 0
@@ -4974,7 +5092,16 @@ import QtQuick.Dialogs 1.0
checked: settings.zwift_play_emulator
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: { settings.zwift_play_emulator = checked; if(checked) { settings.watt_bike_emulator = false; } window.settings_restart_to_apply = true; }
onClicked: {
if (checked && !settings.zwift_play_emulator) { // Only show dialog when enabling
if (settings.zwift_play || settings.zwift_click) {
zwiftPlaySettingsDialog.visible = true;
}
settings.watt_bike_emulator = false;
}
window.settings_restart_to_apply = true;
settings.zwift_play_emulator = checked;
}
}
Label {
@@ -6315,6 +6442,7 @@ import QtQuick.Dialogs 1.0
"Proform 505 CST v.80.44",
"Proform Trainer 8.0",
"Proform 705 CST v.80.44",
"Nordictrack 1750",
]
// Initialize when the accordion content becomes visible
@@ -6376,7 +6504,8 @@ import QtQuick.Dialogs 1.0
settings.proform_treadmill_1500_pro ? 42 :
settings.proform_505_cst_80_44 ? 43 :
settings.proform_trainer_8_0 ? 44 :
settings.proform_treadmill_705_cst_V80_44 ? 45 : 0;
settings.proform_treadmill_705_cst_V80_44 ? 45 :
settings.nordictrack_treadmill_1750_adb ? 46 : 0;
console.log("treadmillModelComboBox selected model: " + selectedModel);
if (selectedModel >= 0) {
@@ -6436,6 +6565,7 @@ import QtQuick.Dialogs 1.0
settings.proform_505_cst_80_44 = false;
settings.proform_trainer_8_0 = false;
settings.proform_treadmill_705_cst_V80_44 = false;
settings.nordictrack_treadmill_1750_adb = false;
// Set new setting based on selection
switch (currentIndex) {
@@ -6484,6 +6614,7 @@ import QtQuick.Dialogs 1.0
case 43: settings.proform_505_cst_80_44 = true; break;
case 44: settings.proform_trainer_8_0 = true; break;
case 45: settings.proform_treadmill_705_cst_V80_44 = true; break;
case 46: settings.nordictrack_treadmill_1750_adb = true; break;
}
window.settings_restart_to_apply = true;
@@ -8837,6 +8968,35 @@ import QtQuick.Dialogs 1.0
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("Send real inclination to virtual bridge")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.real_inclination_to_virtual_treamill_bridge
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.real_inclination_to_virtual_treamill_bridge = checked
}
Label {
text: qsTr("By default QZ sends to the virtual bluetooth/dircon bridge the current inclination of the treadmill. Enabling this, it will send instead the one wihtout considering inclination gain or offset. Default: False.")
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("Disable Wattage from Machinery")
spacing: 0
@@ -9259,6 +9419,33 @@ import QtQuick.Dialogs 1.0
color: Material.color(Material.Lime)
}
IndicatorOnlySwitch {
text: qsTr("Use inclination from the power sensor")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.stryd_inclination_instead_treadmill
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
onClicked: settings.stryd_inclination_instead_treadmill = checked
}
Label {
text: qsTr("If you have a bluetooth treadmill and also a Runn device connected to QZ and you want to use the inclination from the RUNN instead of the inclination of the treadmill, enable this. Default: disabled.")
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("Add inclination gain factor to the power")
spacing: 0

View File

@@ -1145,6 +1145,8 @@ void TemplateInfoSenderBuilder::buildContext(bool forceReinit) {
obj.setProperty(QStringLiteral("verticaloscillation"),
(dep = ((treadmill *)device)->currentVerticalOscillation()).value());
} else if (tp == bluetoothdevice::ELLIPTICAL) {
obj.setProperty(QStringLiteral("resistance"), (dep = ((elliptical *)device)->currentResistance()).value());
obj.setProperty(QStringLiteral("resistance_avg"), dep.average());
obj.setProperty(QStringLiteral("cadence"), (dep = ((elliptical *)device)->currentCadence()).value());
obj.setProperty(QStringLiteral("cadence_color"), homeform::singleton()->cadence->valueFontColor());
obj.setProperty(QStringLiteral("cadence_avg"), dep.average());

View File

@@ -586,6 +586,17 @@ void trainprogram::scheduler() {
QMutexLocker(&this->schedulerMutex);
QSettings settings;
QDateTime now = QDateTime::currentDateTime();
qint64 msecsElapsed = lastSchedulerCall.msecsTo(now);
// Reset jitter if it's getting too large
if (qAbs(currentTimerJitter) > 5000) {
currentTimerJitter = 0;
}
currentTimerJitter += msecsElapsed - 1000;
lastSchedulerCall = now;
// outside the if case about a valid train program because the information for the floating window url should be
// sent anyway
if (settings.value(QZSettings::peloton_companion_workout_ocr, QZSettings::default_companion_peloton_workout_ocr)
@@ -811,6 +822,7 @@ void trainprogram::scheduler() {
#endif
}
currentTimerJitter = 0;
return;
}
@@ -832,6 +844,20 @@ void trainprogram::scheduler() {
#endif
ticks++;
qDebug() << QStringLiteral("trainprogram ticks") << ticks << QStringLiteral("currentTimerJitter") << currentTimerJitter;
if(qAbs(currentTimerJitter) > 1000) {
// we are late...
if (currentTimerJitter > 1000) {
int seconds = currentTimerJitter / 1000;
ticks += seconds;
currentTimerJitter -= (seconds * 1000);
qDebug() << QStringLiteral("fixing jitter!") << seconds << ticks << currentTimerJitter;
} else {
// negative jitter, reset the counter without touching the ticks
currentTimerJitter = 0;
}
}
double odometerFromTheDevice = bluetoothManager->device()->odometer();
@@ -1270,6 +1296,7 @@ void trainprogram::restart() {
ticks = 0;
offset = 0;
currentStep = 0;
currentTimerJitter = 0;
started = true;
}

View File

@@ -168,6 +168,9 @@ private slots:
int lastStepTimestampChanged = 0;
double lastCurrentStepDistance = 0.0;
QTime lastCurrentStepTime = QTime(0, 0, 0);
int64_t currentTimerJitter = 0;
QDateTime lastSchedulerCall = QDateTime::currentDateTime();
QUdpSocket* pelotonOCRsocket = nullptr;
void pelotonOCRcomputeTime(QString t);

View File

@@ -531,12 +531,22 @@ void virtualtreadmill::treadmillProvider() {
cadence_multiplier = 1.0;
if (h) {
bool real_inclination_to_virtual_treamill_bridge = settings.value(QZSettings::real_inclination_to_virtual_treamill_bridge, QZSettings::default_real_inclination_to_virtual_treamill_bridge).toBool();
double inclination = treadMill->currentInclination().value();
if(real_inclination_to_virtual_treamill_bridge) {
double offset = settings.value(QZSettings::zwift_inclination_offset,
QZSettings::default_zwift_inclination_offset).toDouble();
double gain = settings.value(QZSettings::zwift_inclination_gain,
QZSettings::default_zwift_inclination_gain).toDouble();
inclination -= offset;
inclination /= gain;
}
uint16_t normalizeSpeed = (uint16_t)qRound(treadMill->currentSpeed().value() * 100);
// really connected to a device
if (h->virtualtreadmill_updateFTMS(
normalizeSpeed, 0, (uint16_t)((treadmill *)treadMill)->currentCadence().value() * cadence_multiplier,
(uint16_t)((treadmill *)treadMill)->wattsMetric().value(),
treadMill->currentInclination().value() * 10, (uint64_t)(((treadmill *)treadMill)->odometer() * 1000.0))) {
inclination * 10, (uint64_t)(((treadmill *)treadMill)->odometer() * 1000.0))) {
h->virtualtreadmill_setHeartRate(((treadmill *)treadMill)->currentHeart().value());
lastSlopeChanged = h->virtualtreadmill_lastChangeCurrentSlope();
if ((uint64_t)QDateTime::currentSecsSinceEpoch() < lastSlopeChanged + slopeTimeoutSecs)