Compare commits

...

517 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
15f4fc51ce adding watchos symbols 2025-01-29 21:17:55 +01:00
Roberto Viola
d343c1c98c Tacx Neo 3M #3062 2025-01-29 11:34:23 +01:00
Roberto Viola
4a17a8474a CycleOps Phantom 5 #3004 2025-01-29 06:40:31 +01:00
Roberto Viola
2bb1ff0b09 BT Log share for LifeSpan-TM-2000 #3021 2025-01-28 16:39:21 +01:00
Roberto Viola
dec5bd6603 Update bluetooth.cpp 2025-01-28 16:00:18 +01:00
Roberto Viola
282f01b55d Possible bug with NT C2950 IP UDP metrics? (Issue #3079) 2025-01-28 14:42:03 +01:00
Roberto Viola
349be00771 BT Log share for LifeSpan-TM-2000 (Issue #3021) 2025-01-28 11:06:45 +01:00
Roberto Viola
adc47fd19c Update project.pbxproj 2025-01-28 08:45:09 +01:00
Roberto Viola
e876ef97cd CycleOps Phantom 5 #3004 2025-01-28 08:36:28 +01:00
Roberto Viola
903409d962 Senator iPlus treadmill (Issue #3114) 2025-01-28 08:28:08 +01:00
Roberto Viola
9295554195 Tunturi E60 Signature bike #3115 2025-01-28 08:25:59 +01:00
Roberto Viola
8d6cfe03ac Virtufit etappe setting restored 2025-01-27 20:30:42 +01:00
Roberto Viola
70d5051a6f Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-01-26 16:42:36 +01:00
Roberto Viola
124e4ec561 adding in also on WIN32 on qfit 2025-01-26 16:42:34 +01:00
Roberto Viola
036db83321 Fixing crash on windows about fit file writing (#3095)
* Update qfit.cpp

* Update qfit.cpp
2025-01-26 06:15:48 +01:00
Roberto Viola
23dfa67fe5 On iOS devices, the Domyos elliptical bike reads astronomical values above 11km/h. (Issue #3099) 2025-01-25 20:12:09 +01:00
Roberto Viola
79d7a09203 Technogym Skillrun #3097 2025-01-25 14:59:59 +01:00
Roberto Viola
96d9fb485b Create qdomyos-zwift.code-workspace 2025-01-24 13:30:52 +01:00
Roberto Viola
68c4d954ef Create launch.json 2025-01-24 13:28:49 +01:00
Roberto Viola
ef7bedacb8 adding the pdb to win msvc 2025-01-24 12:33:30 +01:00
Roberto Viola
e7a1373305 Update proformtreadmill.cpp 2025-01-24 10:58:28 +01:00
Roberto Viola
cfd06df25e Solution to get Octane Zero Runner ZR8 Elliptical working with Zwift #1338 2025-01-24 08:28:56 +01:00
Roberto Viola
89bc6d0529 Support for Proform 705 CST treadmill (Issue #3072) 2025-01-23 14:49:34 +01:00
Roberto Viola
0446000270 BT Log share for LifeSpan-TM-2000 (Issue #3021) 2025-01-23 14:09:30 +01:00
Roberto Viola
9908e8ca98 Update project.pbxproj 2025-01-23 09:23:11 +01:00
Roberto Viola
326f09c903 BT Log share for LifeSpan-TM-2000 (Issue #3021) 2025-01-22 17:00:07 +01:00
Roberto Viola
2b52206795 improving training effect on garmin
https://github.com/dvmarinoff/Auuki/issues/231#issuecomment-2606479981
2025-01-22 12:07:46 +01:00
Roberto Viola
4cadcddac1 Inclination stops updating in app after about 30 minutes (but treadmill adjustment still works) #2992 2025-01-22 11:52:55 +01:00
Roberto Viola
8910b8bf28 Update main.yml 2025-01-22 10:58:07 +01:00
Roberto Viola
fc3287758e Please add Treadmill: LifeSmart TM4500 #3074 2025-01-22 09:30:36 +01:00
Roberto Viola
c94a03bb23 Update main.yml 2025-01-22 09:24:55 +01:00
Roberto Viola
2c5ba21b99 Update main.yml 2025-01-21 14:14:15 +01:00
Roberto Viola
4f00550400 Update cycleopsphantombike.cpp 2025-01-21 10:56:47 +01:00
Roberto Viola
dfd622c948 Inclination stops updating in app after about 30 minutes (but treadmill adjustment still works) #2992 2025-01-21 08:39:57 +01:00
Roberto Viola
a7d66727f3 Update project.pbxproj 2025-01-20 15:19:39 +01:00
Roberto Viola
06a5c412bd XT385: giving the possibility to use FTMS 2025-01-20 15:14:22 +01:00
Roberto Viola
0a3616ec0e Inclination stops updating in app after about 30 minutes (but treadmill adjustment still works) (Issue #2992) 2025-01-20 09:50:08 +01:00
Roberto Viola
b4226306b0 Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2025-01-20 09:21:14 +01:00
Roberto Viola
4f3353303a Tacx Neo 3M #3062 2025-01-19 16:31:05 +01:00
Roberto Viola
81b832071a Recent compile on master broadcasts device on RaspberryPI as "Pixel 6a" (Issue #3063) 2025-01-19 10:51:49 +01:00
Roberto Viola
d1966df73c Tacx Neo 3m #3062 2025-01-18 21:03:12 +01:00
Roberto Viola
e194291efb Lifespan Fitness SM-720i #3061 2025-01-18 16:23:14 +01:00
Roberto Viola
af88f6cd0d Update project.pbxproj 2025-01-18 08:24:55 +01:00
Roberto Viola
62838da761 Update cycleopsphantombike.cpp 2025-01-18 08:18:58 +01:00
David Mason
7236608f59 Tests for Cyclops Phantom and Pitpat Bikes (#3056) 2025-01-18 08:01:36 +01:00
David Mason
2570f2843c Test timeout exploration (#3048) 2025-01-18 08:00:53 +01:00
Roberto Viola
a9fe9bebaf Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2025-01-17 13:57:21 +01:00
Roberto Viola
15a7c3abd0 BT Log share for LifeSpan-TM-2000 #3021 2025-01-17 13:57:15 +01:00
Roberto Viola
7872950f65 Update project.pbxproj 2025-01-17 12:00:59 +01:00
Roberto Viola
4a711368e3 Inclination stops updating in app after about 30 minutes (but treadmill adjustment still works) (Issue #2992) 2025-01-17 11:51:45 +01:00
Roberto Viola
fbcc7e4478 BT Log share for LifeSpan-TM-2000 #3021 2025-01-17 09:12:03 +01:00
Roberto Viola
e23af2e5f5 Windows: FIT FILE Save Issue (#3053)
* trying fix

* useless?

* temp file?

* Update qfit.cpp

* Update qfit.cpp

* Revert "trying fix"

This reverts commit 82c752d26f.

* Update qfit.cpp
2025-01-16 10:24:42 +01:00
Roberto Viola
9c6fed4d48 Update project.pbxproj 2025-01-15 22:50:03 +01:00
Roberto Viola
26ac25d3ba Read cadence from Garmin when in -run-cadence-sensor mode for FTMS treadmills (Issue #3042) 2025-01-15 22:49:04 +01:00
Roberto Viola
19beae66bb Update project.pbxproj 2025-01-15 08:38:09 +01:00
Roberto Viola
037f660825 Virtual shifting overshoots/undershoots target resistance, then bounces to expected resistance (Issue #3051) #3031 2025-01-15 08:08:52 +01:00
Roberto Viola
47e719bff0 Commenting temporary Linux tests 2025-01-15 06:25:05 +01:00
Roberto Viola
020f30d8df Option to swap the virtual gear shift buttons in the UI #3049 2025-01-14 13:01:34 +01:00
Roberto Viola
7078508ba9 2.18.17 2025-01-14 10:42:30 +01:00
Roberto Viola
eb002332ed Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2025-01-14 10:28:46 +01:00
Roberto Viola
ea1da07e71 Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2025-01-14 10:06:14 +01:00
Roberto Viola
e1d32cd747 Profiles can't be selected (Issue #3045) 2025-01-14 08:20:42 +01:00
Roberto Viola
1bb3450512 2.18.16 2025-01-13 14:22:55 +01:00
Roberto Viola
ce1a78156e QZ iOS beta app closes and shuts down (Issue #3013) 2025-01-13 11:23:14 +01:00
Roberto Viola
9d95e52d12 Garmin discovers but can't connect to cadence sensor (Issue #3023) (#3038) 2025-01-12 19:40:29 +01:00
Roberto Viola
73c072583a fixing build 2025-01-12 18:12:25 +01:00
Roberto Viola
253e2b7eab Update project.pbxproj 2025-01-12 18:07:38 +01:00
Roberto Viola
650c6de692 Rouvy: Virtual shifting with Zwift gearing, noticeably harder than physical gearing (Issue #3031) 2025-01-12 18:04:51 +01:00
Roberto Viola
1ac4e20efb Update project.pbxproj 2025-01-12 17:58:11 +01:00
Roberto Viola
f08ea4346e BT Log share for LifeSpan-TM-2000 #3021 2025-01-12 16:13:14 +01:00
Roberto Viola
c206886639 BT Log share for LifeSpan-TM-2000 (Issue #3021) 2025-01-12 16:11:25 +01:00
Roberto Viola
61bf953b1a Update project.pbxproj 2025-01-12 14:55:14 +01:00
Roberto Viola
1dcd35e825 Watts Stuck at Max…not going down to 0 (Issue #3025) 2025-01-12 14:46:13 +01:00
Roberto Viola
5a7bb8b103 Horizon cycle 7.0IC-02 no resistance adjustment in Kinomap via QZ app #3035 2025-01-12 14:43:40 +01:00
Roberto Viola
c4be4f068f fixing virtual device menu not loaded 2025-01-12 14:42:23 +01:00
Roberto Viola
4534c334bc Create build-qrc-qml.sh 2025-01-12 09:07:08 +01:00
Roberto Viola
9fa6d6d8b1 auto set speed to 3km/h when using auto inclination (Issue #3034) 2025-01-11 19:24:47 +01:00
sirfergy
a5b34161c1 Missed a character! (#3027) 2025-01-10 22:41:53 +01:00
sirfergy
bf2c6929e1 Add two options to set horizon treadmill settings (#3026) 2025-01-10 21:35:49 +01:00
Roberto Viola
2a451c3120 Proform Trainer 8.0 No Bluetooth control (Issue #3017) 2025-01-10 19:30:22 +01:00
Roberto Viola
1169714908 Resistance scaling kickr core 4303 (Peloton App) (Issue #3020) 2025-01-10 19:03:35 +01:00
Roberto Viola
14de4e4760 Proform Trainer 8.0 No Bluetooth control (Issue #3017) 2025-01-10 13:47:12 +01:00
Roberto Viola
712f527ce0 Update project.pbxproj 2025-01-09 12:40:56 +01:00
Roberto Viola
0631c64ba5 Update bluetooth.cpp 2025-01-09 12:37:32 +01:00
Roberto Viola
85c43db53e cscbike: speed based on power setting enable 2025-01-09 12:37:03 +01:00
Roberto Viola
8394bf3f19 Yesoul v1 FMTS bike #2186 2025-01-09 12:21:37 +01:00
Roberto Viola
bd1f25f016 2.18.15 2025-01-08 14:25:13 +01:00
Roberto Viola
95f340063a Cant control ProForm 505 CST through QZ app or Zwift #3005 2025-01-08 11:11:37 +01:00
Roberto Viola
2be1d82e8d Update project.pbxproj 2025-01-08 09:58:16 +01:00
Roberto Viola
501af18298 CycleOps Phantom 5 #3004 2025-01-08 08:30:25 +01:00
Roberto Viola
724292bd34 Hammer Speed Race X resistance change not working correctly #3002 2025-01-08 08:10:27 +01:00
Roberto Viola
cbbdebdf84 Inclination stops updating in app after about 30 minutes (but treadmill adjustment still works) #2992 2025-01-08 08:01:13 +01:00
Roberto Viola
02c17dcf55 starting CycleOps Phantom 5 #3004 2025-01-07 17:41:22 +01:00
Roberto Viola
23d1f9d8c0 No data from Domyos Training Bike 900 #2973 2025-01-07 15:03:26 +01:00
Roberto Viola
f4e0d3596d Horizon 5.0 Bike Compatibility #3001 2025-01-07 11:20:25 +01:00
Roberto Viola
3b012bc946 Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2025-01-07 09:21:23 +01:00
Roberto Viola
33a5a2c80f Horizon 5.0 Bike Compatibility #3001 2025-01-07 09:06:53 +01:00
Roberto Viola
e8b481d517 S22i connects and reports info to QZ, but Auto-Resistance is not being controlled by QZ App (Issue #2909) 2025-01-05 09:21:19 +01:00
Roberto Viola
dcfa58b3a9 Horizon 5.0u bike #2984 2025-01-04 20:04:24 +01:00
Roberto Viola
fd4106cf00 No data from Domyos Training Bike 900 #2973 2025-01-04 05:43:31 +01:00
Roberto Viola
87dddac5f4 added garmin_bluetooth_compatibility for treadmills 2025-01-04 05:26:51 +01:00
Roberto Viola
5488af7e35 DeerRun S500 Bike Integration #2932 2025-01-02 14:48:03 +01:00
Roberto Viola
0a3bd56f15 Care Fitness Rowing no data in QZ (Issue #2874) 2025-01-02 14:37:21 +01:00
Roberto Viola
a5ae8f994b Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2025-01-02 14:32:12 +01:00
Roberto Viola
6a0b3e7fc4 DeerRun S500 Bike Integration #2932 2025-01-01 14:32:15 +01:00
Gerd Naschenweng
a7620c38d0 Included QZ service monitoring (#2960) 2024-12-30 11:23:09 +01:00
Roberto Viola
0060e316dc Update project.pbxproj 2024-12-29 10:59:31 +01:00
Roberto Viola
b71321f301 Treadmill Live Charts #2955 2024-12-29 10:57:20 +01:00
Roberto Viola
c99ef80d78 Speed up Settings page (#2951)
* it works, but i need to check all the accordionelement

* fixing layouts

* Update settings.qml
2024-12-28 22:17:49 +01:00
Roberto Viola
2adf3fe27b SwitchDeletages in the settings now are changing only if the user clicks on the indicator (ios default behaviour) 2024-12-28 18:51:24 +01:00
Roberto Viola
ade033eb59 Tactile Feedback for Zwift Play controllers (and Ride?) #2752 2024-12-28 18:22:11 +01:00
Roberto Viola
42666cf1e9 Update project.pbxproj 2024-12-28 14:39:17 +01:00
Roberto Viola
530f11f67c Tactile Feedback for Zwift Play controllers (and Ride?) (Issue #2752) 2024-12-28 13:48:57 +01:00
Roberto Viola
0391db60aa Raspberry kickr run (#2941) 2024-12-28 13:03:57 +01:00
Roberto Viola
486c90a112 Tactile Feedback for Zwift Play controllers (and Ride?) (Issue #2752) (#2753)
* Tactile Feedback for Zwift Play controllers (and Ride?) (Issue #2752)

* it works!

* vibrate on the right controller
2024-12-28 11:45:13 +01:00
Roberto Viola
d1767797d7 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-12-27 17:41:25 +01:00
Roberto Viola
fbe03d23f3 Smart Trainer Magene T200 #2947 2024-12-27 17:39:53 +01:00
Roberto Viola
361280c131 Issue with QZ 2.18 (982) and Zwift Play in SIM Mode (Issue #2944) 2024-12-27 17:35:43 +01:00
Roberto Viola
04e0fc6e7c Update README.md 2024-12-27 14:25:24 +01:00
Roberto Viola
ab52eee127 Issue with QZ 2.18 (982) and Zwift Play in SIM Mode #2944 2024-12-27 13:50:15 +01:00
Roberto Viola
94825252f7 Proform 1500 Pro Treadmill #2943 2024-12-27 10:23:14 +01:00
Roberto Viola
93f13817be Proform 1500 Pro Treadmill #2943 2024-12-27 10:14:41 +01:00
Roberto Viola
739ea4e841 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-12-26 14:44:12 +01:00
Roberto Viola
fe3ad9ffb4 Open Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2024-12-26 14:25:58 +01:00
Roberto Viola
8fce809ee9 Update project.pbxproj 2024-12-26 14:10:21 +01:00
Roberto Viola
c156cbff99 DeerRun S500 Bike Integration (Issue #2932) 2024-12-26 14:01:39 +01:00
Roberto Viola
268be8e0f5 Delay in Strava Upload and Treadmill Pace Coloring Off #2933 2024-12-26 13:11:32 +01:00
Roberto Viola
5581e1c0e1 Update project.pbxproj 2024-12-25 11:19:23 +01:00
Roberto Viola
7fea2d442f Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-12-25 11:17:52 +01:00
Roberto Viola
74276764a6 2.18.12 2024-12-25 10:45:10 +01:00
Roberto Viola
a3e54782bb Peloton Treadmill Pace Levels #2469 2024-12-25 10:41:11 +01:00
Roberto Viola
b7bc80b2a3 Update project.pbxproj 2024-12-25 10:34:45 +01:00
Roberto Viola
b869a41f3d fixing build 2024-12-25 10:28:52 +01:00
Roberto Viola
9c7954945f Update kineticinroadbike.cpp 2024-12-24 12:30:50 +01:00
Roberto Viola
13cd666718 fixing msvc 2024-12-24 12:23:59 +01:00
Roberto Viola
c3e627e85b Update SmartControl.cpp 2024-12-24 12:02:13 +01:00
Roberto Viola
f23c24ae9b fixing build 2024-12-24 11:53:47 +01:00
Roberto Viola
d27da35beb fixing build 2024-12-24 11:49:40 +01:00
Roberto Viola
6457b205e4 fixing build 2024-12-24 11:42:56 +01:00
Roberto Viola
bea7b61dcc adding the original kineticinroad bike sdk 2024-12-24 11:16:38 +01:00
Roberto Viola
2cc8d51a6c Update project.pbxproj 2024-12-24 10:19:26 +01:00
Roberto Viola
5410b806bb Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-12-24 10:16:15 +01:00
Roberto Viola
b937d8bd71 Pooboo Bike #2935 2024-12-24 09:50:50 +01:00
Roberto Viola
cd25cfab8e 2.18.11 2024-12-23 18:54:53 +01:00
Roberto Viola
229e6ad461 Fixing build 2024-12-23 11:02:33 +01:00
Roberto Viola
977cae1cbd Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541) 2024-12-23 10:09:49 +01:00
Roberto Viola
c8a9be2ca6 Revert "using variables instead of timer"
This reverts commit 445646fe02.
2024-12-23 10:04:05 +01:00
Roberto Viola
c3acf82a9b Revert "everal Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541)"
This reverts commit ddfc60bbf5.
2024-12-23 10:03:57 +01:00
Roberto Viola
ddfc60bbf5 everal Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541)
https://github.com/cagnulein/qdomyos-zwift/issues/2541#issuecomment-2557707654
2024-12-21 16:24:52 +01:00
Roberto Viola
445646fe02 using variables instead of timer
Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541)
2024-12-21 16:09:48 +01:00
Roberto Viola
3dd3c8fb40 Update project.pbxproj 2024-12-21 16:00:45 +01:00
Roberto Viola
fb390b3618 Revert "Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541)"
This reverts commit f6f9a95f06.
2024-12-21 15:12:59 +01:00
Roberto Viola
ca5fb75f3a Revert "handling ERG mode for VFSPINBIKE"
This reverts commit e881ce5f0f.
2024-12-20 14:30:26 +01:00
Roberto Viola
e881ce5f0f handling ERG mode for VFSPINBIKE 2024-12-20 12:21:22 +01:00
Roberto Viola
8002e47551 VFSPINBIKE model workaround for ERG mode started 2024-12-20 08:43:53 +01:00
Roberto Viola
5b66b5705d Support for iConsole based rowing machines, e.g. the "Baltic Rower Pro" (Issue #2901) 2024-12-19 15:17:43 +01:00
Roberto Viola
d1c5521d2a setting to disable the treadmill tag to have the inclination on strava 2024-12-19 10:14:29 +01:00
Roberto Viola
74151edfb3 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-12-19 10:00:10 +01:00
Roberto Viola
00f6747d7d Toputure TP1 treadmill #2918 2024-12-19 10:00:05 +01:00
Roberto Viola
0101955ad3 updating usb serial for android
first build on mac pro for android
2024-12-19 09:58:34 +01:00
Roberto Viola
f6f9a95f06 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541)
https://github.com/cagnulein/qdomyos-zwift/issues/2541#issuecomment-2551961488
2024-12-18 21:39:29 +01:00
Roberto Viola
3d82b89db0 Sole S77 Support #1283 2024-12-18 21:04:54 +01:00
Roberto Viola
8c7b549a45 Sole S77 Support #1283 2024-12-18 19:02:56 +01:00
Roberto Viola
3ad4dc1cfe Sole S77 Support #1283 2024-12-18 18:59:42 +01:00
Roberto Viola
7524314f74 version 2.18.10 2024-12-18 15:17:29 +01:00
Roberto Viola
94545e8958 Update project.pbxproj 2024-12-18 15:15:29 +01:00
Roberto Viola
2c74b2d2e2 nextrow for power training
https://github.com/cagnulein/qdomyos-zwift/issues/2915#issuecomment-2551377819
2024-12-18 14:59:01 +01:00
Roberto Viola
108c190254 S22i connects and reports info to QZ, but Auto-Resistance is not being controlled by QZ App (Issue #2909) 2024-12-18 14:55:26 +01:00
Roberto Viola
466209307e heart rate and wahoo kickr fan toast when connected 2024-12-18 14:34:33 +01:00
Roberto Viola
acccba59dc fixed overlapping lines in the nextrow
https://github.com/cagnulein/qdomyos-zwift/issues/2915#issuecomment-2551013411
2024-12-18 12:12:07 +01:00
Roberto Viola
9325e2f9d1 handling distance for next rows
https://github.com/cagnulein/qdomyos-zwift/issues/2915#issuecomment-2550839841
2024-12-18 10:42:48 +01:00
Roberto Viola
36ebff2667 handling repeat tag in the xml
tunturi t90 inclination not working
#2915
2024-12-18 10:04:26 +01:00
Roberto Viola
6d0d08b5fb OSC: Open Sound Control (#2449)
* starting

* hardcoding ip

* adding fields

* adding setting

* added /QZ/Resistance on write

* finalizing!

* Update osc.cpp

* Update homeform.h

* Update osc.h

* fixing settings

* port added

* Update osc.cpp
2024-12-18 08:41:27 +01:00
Roberto Viola
e695a1e291 tunturi t90 inclination not working #2915 2024-12-17 19:00:59 +01:00
Roberto Viola
133488221b Update kineticinroadbike.cpp 2024-12-17 09:48:57 +01:00
Roberto Viola
b186b672ea kineticinroadbike 2024-12-14 14:04:19 +01:00
Roberto Viola
2badef3daf YPOO-mini pro treadmill #2905 2024-12-14 09:11:27 +01:00
Roberto Viola
f8700296fb Update kineticinroadbike.cpp 2024-12-13 21:32:41 +01:00
Roberto Viola
0f79fb56c7 Peloton Treadmill Pace Levels #2469 2024-12-13 20:44:22 +01:00
Roberto Viola
d8412c95d4 only if a resistance value is greater than 0 will set the resistance received in the ftms bike 2024-12-13 17:23:44 +01:00
Roberto Viola
469c239eed typo on the settings 2024-12-13 17:12:56 +01:00
Roberto Viola
7fad542553 YPOO-mini pro treadmill #2905 2024-12-13 15:21:03 +01:00
Roberto Viola
d0c0aeab84 Update project.pbxproj 2024-12-13 14:55:01 +01:00
Roberto Viola
9fd7123649 Peloton Treadmill Pace Levels #2469 2024-12-13 14:28:32 +01:00
Roberto Viola
5b922043ec YPOO-mini pro treadmill #2905 2024-12-13 13:36:09 +01:00
Roberto Viola
2953589ece fixing build 2024-12-13 12:02:39 +01:00
Roberto Viola
5836990903 Update kineticinroadbike.cpp 2024-12-13 10:44:50 +01:00
Roberto Viola
acd7e24382 kinetic inroad bike
https://github.com/kinetic-fit/kinetic-sdk-java/blob/master/com/kinetic/sdk/inride/InRide.java
2024-12-13 10:11:57 +01:00
Roberto Viola
71827e0546 Update project.pbxproj 2024-12-11 12:11:44 +01:00
Roberto Viola
7e8139e5a5 Treadmill incline multiplied on QZ output [BUG] #2511
ProAction Nydo (BH Fitness) threadmill doesent incline
2024-12-11 09:54:26 +01:00
Roberto Viola
20d2b6ec9e Zycle ZPro #2899 2024-12-11 09:30:32 +01:00
Roberto Viola
be7d0e58a7 Care Fitness Rowing no data in QZ (Issue #2874) 2024-12-11 08:49:23 +01:00
nix155
f20c449279 Fixed VNC server functionality (#2898)
Co-authored-by: Andrey Zotov <azotov@teko.io>
2024-12-10 17:04:08 +01:00
Roberto Viola
bf059715ec Care Fitness Rowing no data in QZ (Issue #2874) 2024-12-10 11:17:53 +01:00
Roberto Viola
98cd3f22a2 unable to connect to NordicTrack 7i #2838 2024-12-09 10:32:51 +01:00
Roberto Viola
bad290d104 Data fields not updating Trojan Pro Spin Bike 2.0, iConsole #2884 2024-12-09 10:24:04 +01:00
Roberto Viola
3c55d025ce Update ftmsbike.cpp (#2888) 2024-12-09 07:32:03 +01:00
nix155
5c775ac5b4 Added build with qtwebglplugin (#2879)
Co-authored-by: Andrey Zotov <azotov@teko.io>
2024-12-05 12:26:56 +01:00
Roberto Viola
9295aa58a7 Care Fitness Rowing no data in QZ #2874 2024-12-05 12:20:58 +01:00
Roberto Viola
96d68bbd39 Update project.pbxproj 2024-12-05 10:29:00 +01:00
Roberto Viola
7ddb6bc1ca Bowflex T9 Not Starting When Connected #2866 2024-12-05 08:56:24 +01:00
Roberto Viola
a0145793a2 Question about treadmill powerzones (Discussion #2873) 2024-12-04 15:45:16 +01:00
Roberto Viola
ecb37d67cc Revert "Question about treadmill powerzones (Discussion #2873)"
This reverts commit 3ae203d7ad.
2024-12-04 14:33:31 +01:00
Roberto Viola
969476f368 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-12-04 13:32:17 +01:00
Roberto Viola
3ae203d7ad Question about treadmill powerzones (Discussion #2873) 2024-12-04 13:32:12 +01:00
Roberto Viola
e979b5aebe Update project.pbxproj 2024-12-04 09:01:46 +01:00
Roberto Viola
d568bccc28 Bowflex T9 Not Starting When Connected #2866 2024-12-04 08:59:51 +01:00
nix155
301429182d Added multi-stage build, unnecessary files removed from the image (#2870) 2024-12-04 06:39:18 +01:00
nix155
8df78b9387 Added files for building and running in Docker with GUI (#2868)
Co-authored-by: Andrey Zotov <azotov@teko.io>
2024-12-03 18:19:55 +01:00
Roberto Viola
ba57309bcd Bowflex T9 Not Starting When Connected #2866 2024-12-03 18:12:54 +01:00
Roberto Viola
8d573b3ee6 Incline from GPX will be rounded to inclination step
Fitness Master T25 incline doesn't work [BUG] #2820
2024-12-03 17:09:23 +01:00
Marcel
ba43ba8c21 Add Life Fitness 95 CSAFE Ellipical (#2863)
* Add Life Fitness 95 CSAFE Ellipical

* Change bautrate type

* Fix data baudrate once more

* alternative way of setting level

* fix windows serial

* fix u_int16_t

* reorder header files

* Fix header setup

* multiple command refresh

* increment allSettingsCount

* Fix android build

* update kalman filter parameters

* formatting fixes

* More formatting fixes

* add setting version
2024-12-03 14:48:28 +01:00
Roberto Viola
47a3c24b03 2.18.9 2024-12-03 14:23:06 +01:00
Roberto Viola
40579fd376 improving safety on mediabuttonreceiver 2024-12-03 14:22:11 +01:00
Roberto Viola
bb17c1cc1a Inclination Gain implementation in QZ for Elite Suito/Rouvy #2850 (#2858) 2024-12-03 11:21:51 +01:00
Roberto Viola
1cc8862a04 proform carbon TL PFTL59722c.0 (Issue #2806) 2024-12-03 10:32:23 +01:00
Roberto Viola
ff4606caa4 Yosuda RC-Max #2861 2024-12-03 08:58:37 +01:00
Roberto Viola
aff12a0462 Bowflex T9 #2860 2024-12-03 08:56:55 +01:00
Roberto Viola
3c5054acbd App not displaying metrics #2835 2024-12-02 14:37:18 +01:00
Roberto Viola
9a854f7810 Buggy behaviour on Raspberry pi 4 and Zwift #2810 2024-12-02 11:39:04 +01:00
Roberto Viola
1e731f7cbe Zwo warmup not fully elapsed in terms of time in running workouts #2847 2024-12-02 08:36:23 +01:00
Roberto Viola
c0cd6234f3 Android 6 crash (#2852)
* Android 6 crash

* Revert "Android 6 crash"

This reverts commit 6d4bfab1c9.

* Update MediaButtonReceiver.java

* Update MediaButtonReceiver.java

* Update MediaButtonReceiver.java
2024-12-01 15:14:59 +01:00
Roberto Viola
9cc79ab33a Update project.pbxproj 2024-12-01 08:09:17 +01:00
Roberto Viola
181de73a13 Revert "Zwo warmup not fully elapsed in terms of time in running workouts (Issue #2847)"
This reverts commit 36f6fa7feb.
2024-12-01 08:08:41 +01:00
Roberto Viola
57e03c39f1 Virtual activity tag in Strava not applicable when sport type is set to walking #2844 2024-12-01 08:07:23 +01:00
Roberto Viola
ff8a89d688 Revert "Virtual activity tag in Strava not applicable when sport type is set to walking (Issue #2844)"
This reverts commit 2b568c6260.
2024-12-01 08:06:18 +01:00
Roberto Viola
36f6fa7feb Zwo warmup not fully elapsed in terms of time in running workouts (Issue #2847) 2024-11-30 14:50:03 +01:00
Roberto Viola
7d7e9cc79d Update project.pbxproj 2024-11-30 14:21:06 +01:00
Roberto Viola
2616ebe229 Fitness Master T25 incline doesn't work [BUG] #2820
always using kcal from QZ to avoid treadmill madness about KCAL
2024-11-30 14:17:18 +01:00
Roberto Viola
2b568c6260 Virtual activity tag in Strava not applicable when sport type is set to walking (Issue #2844) 2024-11-30 14:13:24 +01:00
Roberto Viola
f49f539e71 Update project.pbxproj 2024-11-28 18:02:03 +01:00
Roberto Viola
1845f3a5ae Incorrect Avg watts displayed on power badge (Issue #2843) 2024-11-28 17:57:17 +01:00
Roberto Viola
9aa337cd47 Update truetreadmill.cpp 2024-11-28 16:30:44 +01:00
Roberto Viola
9b12c5c4bf trying to get app to work with Wahoo Gymconnect to control treadmill (Issue #2840) 2024-11-28 16:00:34 +01:00
Roberto Viola
1d12f7e475 Multi-shift for Zwift Click and Play (and Ride?) (Issue #2751) 2024-11-28 13:33:41 +01:00
Roberto Viola
e463ab9aae reverting 438bd2c195 2024-11-28 13:19:02 +01:00
Roberto Viola
baa9de9059 Update project.pbxproj 2024-11-28 12:27:39 +01:00
Roberto Viola
e3b706f537 Kickr Move Bluetooth Not found (Issue #2827) 2024-11-28 12:26:52 +01:00
Roberto Viola
438bd2c195 Multi-shift for Zwift Click and Play (and Ride?) (Issue #2751) 2024-11-28 11:48:18 +01:00
Roberto Viola
5b546911ff NordicTrack GX 4.4 Pro with QZ on Samsung S7 (Android 8) (Discussion #2836) 2024-11-28 10:45:00 +01:00
Roberto Viola
b544e325ce Update project.pbxproj 2024-11-28 08:46:57 +01:00
Roberto Viola
c01fad2e29 Wattbike Atom First Generation - Display Gears #2829 2024-11-28 08:28:37 +01:00
Roberto Viola
a4d2f53207 Fitness Master T25 incline doesn't work [BUG] #2820 2024-11-28 08:21:44 +01:00
Roberto Viola
a8b3fc3129 Update project.pbxproj 2024-11-27 16:56:14 +01:00
Roberto Viola
b7dec6d223 Wattbike Atom First Generation - Display Gears #2829 2024-11-27 16:55:17 +01:00
Roberto Viola
2d76ea554d Fitness Master T25 incline doesn't work [BUG] #2820 2024-11-27 16:51:56 +01:00
Roberto Viola
b0f03dbe0a Update project.pbxproj 2024-11-27 16:12:38 +01:00
Roberto Viola
46429e04b4 using combobox for the nordictrack bikes in the settings 2024-11-27 16:05:49 +01:00
Roberto Viola
7d479b7d88 secondline for gear tile 2024-11-27 14:11:32 +01:00
Roberto Viola
c86651cdf6 proform carbon TL PFTL59722c.0 #2806 2024-11-27 11:18:02 +01:00
Roberto Viola
63bfdba992 Fitness Master T25 incline doesn't work [BUG] #2820 2024-11-27 10:51:50 +01:00
Roberto Viola
6767c42b14 Fitness Master T25 incline doesn't work [BUG] #2820 2024-11-27 10:50:42 +01:00
Roberto Viola
70f2f2ecb5 NEO BIKE SMART added 2024-11-27 09:23:44 +01:00
Roberto Viola
691ec420b0 Wattbike Atom First Generation - Display Gears #2829 2024-11-27 08:58:16 +01:00
Roberto Viola
278f130906 Update project.pbxproj 2024-11-27 07:14:25 +01:00
Roberto Viola
531a88e326 2.18.8 2024-11-27 07:08:58 +01:00
Roberto Viola
d8e3e193b8 fixing zwift pool time 2024-11-26 18:34:32 +01:00
Roberto Viola
cda99d6f21 ZDRIVE trainer added 2024-11-26 16:52:36 +01:00
Roberto Viola
3cf3ae9135 Kickr Climb not move #2823
forcing inclination after a gear change
2024-11-26 16:11:59 +01:00
Roberto Viola
7333361190 Kickr Climb not move #2823 2024-11-26 12:03:27 +01:00
Roberto Viola
f75ebbb47f Update project.pbxproj 2024-11-26 08:06:07 +01:00
Roberto Viola
ce35fad608 Wattbike Atom First Generation - Display Gears #2829 2024-11-26 08:03:17 +01:00
Roberto Viola
8160d711a1 Fitness Master T25 incline doesn't work [BUG] #2820 2024-11-26 07:55:17 +01:00
Roberto Viola
2a595ced57 Update project.pbxproj 2024-11-25 16:30:43 +01:00
Roberto Viola
9cb14b91ba Compatibility with Technogym Skillbike #2805 2024-11-25 16:29:33 +01:00
Roberto Viola
92e008b3c8 don't add value to erg table if the cadence is 0 2024-11-25 16:00:59 +01:00
Roberto Viola
f6d8924e18 Continuos beeping when speed is modified by the %T. speed tile (Issue #2825) 2024-11-25 13:38:47 +01:00
Roberto Viola
ef955a03bc Kickr Move Bluetooth Not found #2827 2024-11-25 12:10:08 +01:00
Roberto Viola
67c12f7342 Continuos beeping when speed is modified by the %T. speed tile (Issue #2825) 2024-11-25 11:35:12 +01:00
Roberto Viola
5cfbc6855b Continuos beeping when speed is modified by the %T. speed tile (Issue #2825) 2024-11-25 10:51:15 +01:00
Roberto Viola
aa9e9e20fe Change speed or PID when a workouts is load an is running (Issue #2813) 2024-11-25 08:36:29 +01:00
Roberto Viola
417f9c370d Compatibility with Technogym Skillbike #2805 2024-11-25 08:26:32 +01:00
Roberto Viola
3f3bdbb83e forcing ftms default value cause zwift ride don't like 0 2024-11-25 08:21:30 +01:00
Roberto Viola
c1c58a7a4d Update project.pbxproj 2024-11-22 15:05:42 +01:00
Roberto Viola
a6a3dd4c28 Compatibility with Technogym Skillbike #2805 2024-11-22 13:21:45 +01:00
Marcel
55c3dbe3b6 Move csafe lib to separate folder and add commands (#2811) 2024-11-22 13:00:31 +01:00
Roberto Viola
50e0c5aab9 Update project.pbxproj 2024-11-22 10:36:59 +01:00
Roberto Viola
fde0566abf version 2.18.7 2024-11-22 10:34:20 +01:00
Roberto Viola
be83fcbddb Compatibility with Technogym Skillbike #2805 2024-11-22 10:32:23 +01:00
Roberto Viola
a7ccc1997a Compatibility with Technogym Skillbike #2805 2024-11-22 10:03:38 +01:00
Roberto Viola
f6339a9f70 proform carbon TL PFTL59722c.0 #2806 2024-11-22 08:18:43 +01:00
Roberto Viola
6d15d61f68 Update project.pbxproj 2024-11-21 16:59:32 +01:00
Roberto Viola
7b0b81694c Compatibility with Technogym Skillbike #2805 2024-11-21 16:50:28 +01:00
Roberto Viola
e9ab643aab proform carbon TL PFTL59722c.0 #2806 2024-11-21 15:45:19 +01:00
Roberto Viola
2e1ef17861 watt averaging also for charts 2024-11-21 15:31:44 +01:00
Roberto Viola
2527a3d303 adding watts calculated from the homeform in the log 2024-11-21 13:48:00 +01:00
Roberto Viola
62973d0564 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-11-21 10:44:04 +01:00
Roberto Viola
59c428a14b Update project.pbxproj 2024-11-21 10:43:39 +01:00
Roberto Viola
064a4de214 https://github.com/cagnulein/qdomyos-zwift/issues/2541#issuecomment-2489558440 2024-11-21 10:43:20 +01:00
Roberto Viola
998d95f6b2 Nordictrack T series 5 treadmill #2795 2024-11-21 10:21:37 +01:00
Roberto Viola
e609d9ea93 cscbike battery level implemented 2024-11-21 10:16:39 +01:00
Roberto Viola
0d191e6f02 D2RIDE battery ignored 2024-11-21 10:16:24 +01:00
Roberto Viola
3cb3aa4d1c Update project.pbxproj 2024-11-20 15:50:35 +01:00
Roberto Viola
f580e98d26 fixing live power zone chart 2024-11-20 15:50:11 +01:00
Roberto Viola
e1ad8aab73 restoring gears_zwift_ratio variable in the ftms bike
Zwift hub gear custom
#2757
2024-11-20 15:29:00 +01:00
Roberto Viola
340cdd9323 Program doesnt work with CardioPower Ergo 5 (Issue #2804) 2024-11-20 14:25:12 +01:00
Roberto Viola
82ba5debcd fixing workout live chart for powerzones 2024-11-19 16:11:12 +01:00
Roberto Viola
d9a677b4ca Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-11-19 15:20:36 +01:00
Roberto Viola
cd56463286 Cadence Sensor As A Treadmill #2789 2024-11-19 15:20:32 +01:00
Roberto Viola
3faa726fcd Update mqttpublisher.cpp 2024-11-19 12:52:37 +01:00
Roberto Viola
ef938df79f Time shift in PZ rides (Issue #2690) 2024-11-19 12:24:41 +01:00
Roberto Viola
470ca0a98e Update stagesbike.cpp 2024-11-19 11:12:50 +01:00
Roberto Viola
fe9bb7e26a fixing wrong cadence value for cadence in the power pedal sensor 2024-11-19 09:49:04 +01:00
Roberto Viola
a30b1f5298 2.18.6 2024-11-19 09:37:56 +01:00
Marcel
e7196d3033 Fix port on csafe rower (#2794) 2024-11-18 14:46:33 +01:00
Roberto Viola
09977ac703 MQTT Support for Homeassistant (Issue #2759) 2024-11-18 14:28:09 +01:00
Roberto Viola
ea9ed85bf7 Master T40 treadmill #2640 2024-11-18 13:46:11 +01:00
Roberto Viola
1f3d819b24 Update project.pbxproj 2024-11-18 12:28:31 +01:00
Roberto Viola
096a025b79 MQTT Support for Homeassistant (Issue #2759) 2024-11-18 12:25:50 +01:00
Roberto Viola
0926f0b484 MQTT Support for Homeassistant (Issue #2759) 2024-11-18 12:14:35 +01:00
Roberto Viola
b6f6641204 Peloton Auto Start! (#2784) 2024-11-18 11:49:10 +01:00
Roberto Viola
65418c6e9a Peloton Auto Start! (#2784) 2024-11-18 11:46:45 +01:00
Roberto Viola
6abd0e677b Magene T300 Plus bike trainer #2793 2024-11-18 10:27:38 +01:00
Roberto Viola
ecfae5f416 Update 10_Installation.md 2024-11-18 08:49:37 +01:00
Roberto Viola
e2f4d4e376 Technogym Cycle #2788 2024-11-16 14:45:03 +01:00
Roberto Viola
fb8de4606a Sram disabled from the settings due to crypto 2024-11-15 21:23:05 +01:00
Roberto Viola
09af8f98b3 MQTT Support for Homeassistant (Issue #2759) 2024-11-15 16:57:34 +01:00
Roberto Viola
70abcee27d MQTT Support for Homeassistant (Issue #2759) 2024-11-15 16:42:57 +01:00
Roberto Viola
d0deb6bee5 Peloton Auto Start! (#2784)
* it works!

* done!
2024-11-15 14:41:27 +01:00
Roberto Viola
3494349961 MQTT Support for Homeassistant (Issue #2759) (#2766)
* adding qmqtt libraries

* Update qmqttsubscription_p.h

* Update qmqttclient_p.h

* Revert "Update qmqttclient_p.h"

This reverts commit 7629972927.

* Revert "Update qmqttsubscription_p.h"

This reverts commit 9c52f7363e.

* Update qdomyos-zwift.pri

* adding class for mqtt

* Update mqttpublisher.cpp

* Update mqttpublisher.cpp

* fixing build

* adding settings and also works on raspberry

* working also on ios!

* done!

* trying to fix windows build

* Update qmqttmessage.h

* fixing windows build

* Update qmqttglobal.h
2024-11-15 12:29:03 +01:00
Roberto Viola
5abc9ac9c0 fixing get gears from zwift for new firmware version of zwift ride 2024-11-15 09:01:47 +01:00
Roberto Viola
d0360ea87b Fix Protobuf Windows on Github Actions (#2780) 2024-11-14 17:16:50 +01:00
Roberto Viola
d31c5c4d53 Pause button on qz won't reset the speed tile (Issue #2781) 2024-11-14 16:39:09 +01:00
Roberto Viola
f2f49464fc trying to fix win build 2024-11-14 15:10:22 +01:00
Marcel
f6d0e068f6 Fix bluetooth nor working in RPI zero 2w (#2778) 2024-11-14 11:33:04 +01:00
Roberto Viola
17f699234c 2.18.5 2024-11-13 12:40:17 +01:00
Roberto Viola
bf4c07aba4 ftms bike automatically if it has a zwift play service 2024-11-13 12:34:21 +01:00
Roberto Viola
a7fadf55aa Update project.pbxproj 2024-11-13 11:20:24 +01:00
Roberto Viola
4912807ae4 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-11-13 11:13:54 +01:00
Roberto Viola
4a61c34d58 NordicTrack Treadmill C960i #2770 2024-11-13 11:13:45 +01:00
Roberto Viola
109dc901e9 Zwift hub gear custom (#2757)
* first raw version

* Update project.pbxproj

* Update virtualbike_zwift.swift

* fixing formula

* fixing casting to double

* need to center the values in the table

* Update gears.qml

* Revert "Update gears.qml"

This reverts commit 0f149448b3.

* Update gears.qml

* i need to save the first 3 static objects and use it in the wahoo module

* qml finally saves the settings correctly

* completed?

* Update project.pbxproj

* fixing gear conversion

* adding max and minGears

* fixing UI and settings

* kickr core to wahookickr class

* ftms wheel circumference for gears

* implementing

* Update wahookickrsnapbike.cpp

* Update ftmsbike.cpp

* first custom gear test

* adding inclination custom message too

* Update ftmsbike.cpp

* implemented protobuf

* protobuf also for the gears

* Update ftmsbike.cpp

* Update project.pbxproj

* reverting tacxneo wheel diameter and ftms standard wheel diamater in order to merge it

* fixing mingears and maxgears

* adding android part

* Update main.cpp

* Update main.cpp

* Update main.cpp

* Update project.pbxproj

* fixing android build

* fixing build

* fixing android build

* Update main.cpp

* removed debug
2024-11-13 11:13:16 +01:00
Roberto Viola
9b8b6643f8 RPM from Magene Speed Sensor not reading properly (Issue #2769) 2024-11-13 10:05:41 +01:00
Roberto Viola
a26e79dac7 Raspberry PI Build Fix (#2771)
* Update main.yml

* Update main.yml
2024-11-13 09:33:42 +01:00
Roberto Viola
291c5d68f2 SRAM AXS Controller for Virtual Gearing #2768 2024-11-12 16:29:33 +01:00
Roberto Viola
c85e838e33 Proform Treadmill C700 #2762 2024-11-12 10:36:50 +01:00
Roberto Viola
94975e0117 Proform Performance 400i #2741
speed in kph?
2024-11-12 09:53:59 +01:00
Roberto Viola
eebb9359a6 Proform 575i (Issue #2652) 2024-11-12 09:43:14 +01:00
Roberto Viola
40e6609297 Several Issues Using QZ with Rouvy and Zwift Play Controllers (Issue #2541)
https://github.com/cagnulein/qdomyos-zwift/issues/2541#issuecomment-2468340941
2024-11-11 15:57:39 +01:00
Roberto Viola
af028504eb Proform 575i #2652 2024-11-11 11:24:07 +01:00
Roberto Viola
bfe4564659 Update proformtreadmill.cpp 2024-11-11 10:08:36 +01:00
Roberto Viola
85cdcaa457 Master T40 treadmill #2640 2024-11-11 09:42:01 +01:00
Roberto Viola
cd5c10835c Proform Performance 400i #2741
stop better handling?
2024-11-11 09:22:06 +01:00
Roberto Viola
ce97c92645 Custom gearing ranges/ratios #2671
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11190939
2024-11-09 16:08:23 +01:00
Roberto Viola
d27b3b9ba0 fixing build 2024-11-09 12:46:01 +01:00
Roberto Viola
0f96923006 Update project.pbxproj 2024-11-09 12:38:28 +01:00
Roberto Viola
bc7ac73de9 Custom gearing ranges/ratios #2671
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11190939
2024-11-09 12:37:21 +01:00
Roberto Viola
8d2fe9dd25 trxappgateusbtreadmill wrong kcal 2024-11-09 12:18:03 +01:00
Roberto Viola
2d99106254 Proform Performance 400i #2741 2024-11-08 16:23:03 +01:00
Roberto Viola
04d01efd0f Update project.pbxproj 2024-11-08 15:00:27 +01:00
Roberto Viola
da10fff966 Proform Performance 400i #2741 2024-11-08 14:58:33 +01:00
Roberto Viola
991ee8674a Update project.pbxproj 2024-11-07 20:23:20 +01:00
Roberto Viola
7f24950498 wizard control for wifi devices added 2024-11-07 14:34:14 +01:00
Roberto Viola
405fb415d4 wizard control for wifi devices added 2024-11-07 13:59:58 +01:00
Roberto Viola
0aa4c27e02 Custom gearing ranges/ratios #2671
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11176772
2024-11-07 12:53:14 +01:00
Roberto Viola
add12366a0 Custom gearing ranges/ratios #2671
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11176772
2024-11-07 12:34:52 +01:00
Roberto Viola
2c496f6fe2 Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-11-07 11:49:51 +01:00
Roberto Viola
1381c19723 2.18.3 2024-11-07 11:49:29 +01:00
Roberto Viola
a8ddfc8b70 Garmin HR over ANT+ not collected in QZ (Issue #2740) (#2747)
* Garmin HR over ANT+ not collected in QZ (Issue #2740)

* Update HeartChannelController.java

* Update build.gradle

* Update HeartChannelController.java

* Update build.gradle

* Create android-antplus-plugin-lib-release_3.9.0.aar

* Update HeartChannelController.java

* Update HeartChannelController.java

* Update HeartChannelController.java
2024-11-07 11:43:57 +01:00
Roberto Viola
6680ff1cd9 Echelon Rower Sport 2, not displaying stats (Issue #2749) 2024-11-06 21:04:43 +01:00
Roberto Viola
863dc6378c Update project.pbxproj 2024-11-06 18:46:09 +01:00
Roberto Viola
be0465d094 Capitol Sports Infinity Pro 4 treadmill (Issue #2748) 2024-11-06 18:41:41 +01:00
Roberto Viola
42d2dcef7e Capitol Sports Infinity Pro 4 treadmill 2024-11-06 16:36:10 +01:00
Roberto Viola
c70fd8608d Master T40 treadmill (Issue #2640) 2024-11-06 16:30:25 +01:00
Roberto Viola
e217f929e8 Zwift gear 4 does not change QZ gear or resistance (Issue #2746) 2024-11-06 14:43:05 +01:00
Roberto Viola
b95c51a2be Custom gearing ranges/ratios (Discussion #2671) 2024-11-06 14:02:10 +01:00
Roberto Viola
e3b635d107 Life fitness ic7 spin bike #2745 2024-11-06 12:07:47 +01:00
Roberto Viola
ae1642d052 relaxing bluetooth name on android 2024-11-06 11:46:16 +01:00
Roberto Viola
13e9f49a8b improving speed for gears for wahoo devices 2024-11-06 09:31:47 +01:00
Roberto Viola
13f174fb1e relaxing android bluetooth name control 2024-11-06 09:03:27 +01:00
Roberto Viola
3ef9e6304d adding some debug to virtualbike_zwift.swift 2024-11-06 08:47:53 +01:00
Roberto Viola
398909b809 Update project.pbxproj 2024-11-06 08:13:31 +01:00
Roberto Viola
dc140a3dd5 Proform Performance 400i #2741 2024-11-06 08:13:09 +01:00
Roberto Viola
b2fe23c32e Update project.pbxproj 2024-11-06 06:52:40 +01:00
Roberto Viola
46f0761f50 Improvements made for Proform 705 CST support (Issue #2719) (#2723)
* Improvements made for Proform 705 CST support (Issue #2719)

* comment removed

* Update proformtreadmill.cpp

* Update proformtreadmill.cpp
2024-11-06 06:45:52 +01:00
MRKrinetic
63258f2029 Revised README Layout and Links for Improved Readability (#2742)
* Update README.md

readme updated in a readble format Tested on, Reference, Blog.

* Update README.md

updated readme in readable format

* Update README.md

updated readme file in readble format
2024-11-05 16:49:42 +01:00
Roberto Viola
eeaf8fbe19 Proform Performance 400i #2741 2024-11-05 16:38:32 +01:00
Roberto Viola
934e6dfa57 bug unevenness not fair gpx #2733 2024-11-05 12:40:16 +01:00
Roberto Viola
a9dd04f4fa 2.18.2 2024-11-05 12:07:12 +01:00
Roberto Viola
34df54b96c Merge branch 'master' of https://github.com/cagnulein/qdomyos-zwift 2024-11-05 11:27:37 +01:00
Roberto Viola
e5ebd3c925 Bowflex M9 #2739 2024-11-05 11:27:32 +01:00
Roberto Viola
e99660ce40 Elite Tuo 2024-11-04 21:03:43 +01:00
Roberto Viola
d5357ed1c3 Technogym Myrun Treadmill rfcomm (#2732) 2024-11-04 20:26:22 +01:00
Roberto Viola
5a357c43e0 switching to the newest macos 2024-11-04 15:53:02 +01:00
Roberto Viola
287ef5bdc7 Proform 575i #2652 2024-11-04 15:51:51 +01:00
Roberto Viola
89db56ae58 Revert "Renpho smart bike r-q002 n (Issue #2401) (#2409)"
This reverts commit 4a33008c61.
2024-11-04 15:42:01 +01:00
Roberto Viola
8f8aa888ca Wahoo Kickr RUN on Android (#2718)
* it doesn't show as controllable yet

* Update virtualtreadmill_zwift.swift

* trying on android

* should be ok on android so

* Revert "should be ok on android so"

This reverts commit 638c99ba83.

* adding inclination parsing

* Revert "Update virtualtreadmill_zwift.swift"

This reverts commit b3a388199e.

* Revert "it doesn't show as controllable yet"

This reverts commit 370ea4e62f.
2024-11-04 15:33:02 +01:00
Roberto Viola
f9321a7bde SHAWN PAVLIN ASCEND S2 Erg Mode (Issue #2660) 2024-11-04 14:20:09 +01:00
Roberto Viola
4c59d2e2cb PID Heart function parameters (Discussion #2731) 2024-11-04 13:41:00 +01:00
Roberto Viola
26d346bdf1 don't ask me again location services (#2716)
* don't ask me again location services

* Update homeform.cpp

* Update Home.qml
2024-11-04 13:21:37 +01:00
Roberto Viola
ba741ada31 QZPI as virtual bike for linux
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11131220
2024-11-04 09:45:46 +01:00
Roberto Viola
2e8c4ebf9a I'm unable to connect my EXERCYCLE H9365R V2 BH correctly. (Issue #2727) 2024-11-02 19:45:38 +01:00
Roberto Viola
579e30683a Update gears.qml 2024-11-02 06:28:31 +01:00
David Mason
b1f893e944 Test updates 2024-10-31 (#2720) 2024-11-01 07:29:09 +01:00
Roberto Viola
415e305415 Custom gearing ranges/ratios (Discussion #2671)
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11113470
2024-10-31 16:53:09 +01:00
Roberto Viola
adb6928772 specific gear value at startup #2671 2024-10-31 16:47:21 +01:00
Roberto Viola
dc70bd1513 adding (but not using) the setuserconfiguration for the tacx 2024-10-31 15:47:37 +01:00
Roberto Viola
57f6a7d1a5 Update project.pbxproj 2024-10-31 13:21:49 +01:00
Roberto Viola
deafbd45d0 Custom gearing ranges/ratios (Discussion #2671)
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11110674
2024-10-31 13:20:45 +01:00
Roberto Viola
b2fa338e03 fixing preset gears
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11109001
2024-10-31 10:01:28 +01:00
Roberto Viola
4e6a98e789 fixing tests 2024-10-31 09:20:23 +01:00
Roberto Viola
72ca19e3e7 Wellfit treadmill support (issue #2659) 2024-10-31 09:19:29 +01:00
Roberto Viola
2c7dac1837 CYCPLUS FTMS bike
https://github.com/cagnulein/qdomyos-zwift/discussions/2671#discussioncomment-11108283
2024-10-31 08:31:21 +01:00
Roberto Viola
de22d58e75 fixing tests 2024-10-31 08:17:28 +01:00
Roberto Viola
2f6d5415cc Update devicetestdataindex.cpp 2024-10-31 07:04:38 +01:00
Roberto Viola
b8101ffa76 Gear UI on zwift #2391 (#2524)
* trying on ios

* Update virtualbike_zwift.swift

* dynamic gears

* trying also on dircon but it doesn't work

* adding the android part

* gears works on android too!

* Update virtualbike.cpp

* setting added

* char 1224 removed

* Update project.pbxproj

* Update dirconmanager.cpp

* Update virtualbike.cpp

* Update virtualbike.cpp
2024-10-31 06:25:55 +01:00
Roberto Viola
281590cf63 Wahoo Custom gearing ranges/ratios (#2682)
* first raw version

* Update project.pbxproj

* Update virtualbike_zwift.swift

* fixing formula

* fixing casting to double

* need to center the values in the table

* Update gears.qml

* Revert "Update gears.qml"

This reverts commit 0f149448b3.

* Update gears.qml

* i need to save the first 3 static objects and use it in the wahoo module

* qml finally saves the settings correctly

* completed?

* Update project.pbxproj

* fixing gear conversion

* adding max and minGears

* fixing UI and settings

* kickr core to wahookickr class
2024-10-31 05:21:43 +01:00
Roberto Viola
6759fb9ec0 I_ROWER fix #842 2024-10-30 14:21:51 +01:00
Roberto Viola
c7dad4f1ad Keiser M3i Heart Rate is Erratic (Issue #2711) 2024-10-30 09:38:00 +01:00
Roberto Viola
d29726632b handling resistance_lvl_mode on ftmsbike with zwift for don't affect wahoo climb 2024-10-29 14:15:26 +01:00
Roberto Viola
95e5c58a92 I_ROWER fix #842 2024-10-29 13:56:34 +01:00
Roberto Viola
8c5a3693c8 trying handling workout state for PM4 (#2398) 2024-10-28 20:21:51 +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
233 changed files with 36096 additions and 5546 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
@@ -141,7 +142,7 @@ jobs:
cd src/debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
cp qdomyos-zwift.* output/
cd output
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp "C:/mingw64/bin/libwinpthread-1.dll" .
@@ -168,7 +169,7 @@ jobs:
cd src/debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
cp qdomyos-zwift.* output/
cd output
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp "C:/mingw64/bin/libwinpthread-1.dll" .
@@ -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
@@ -338,7 +340,7 @@ jobs:
# This workflow contains a single job called "build"
linux-x86-build:
# The type of runner that the job will run on
runs-on: ubuntu-20.04
runs-on: ubuntu-24.04
# Steps represent a sequence of tasks that will be executed as part of the job
steps:
@@ -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
@@ -652,7 +655,7 @@ jobs:
ios-build:
# The type of runner that the job will run on
runs-on: macos-12
runs-on: macos-latest
permissions:
contents: write
@@ -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
@@ -833,9 +838,23 @@ jobs:
run: .\vcpkg\bootstrap-vcpkg.bat
working-directory: ${{ runner.workspace }}
- name: Create vcpkg.json
working-directory: ${{ runner.workspace }}
run: |
echo '{
"name": "qdomyos-zwift",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"dependencies": [
"protobuf",
"protobuf-c",
"abseil"
],
"builtin-baseline": "8c2fcacefba009d63672f9d137f192765e632c9f"
}' > vcpkg.json
- name: Install dependencies
run: |
.\vcpkg\vcpkg install protobuf protobuf-c abseil
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
@@ -848,7 +867,7 @@ jobs:
cd src/debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
cp qdomyos-zwift.* output/
cd output
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp ../../../icons/iOS/iTunesArtwork@2x.png .
@@ -876,7 +895,7 @@ jobs:
cd src/debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
cp qdomyos-zwift.* output/
cd output
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp "C:/mingw64/bin/libwinpthread-1.dll" .
@@ -992,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
@@ -1006,9 +1026,23 @@ jobs:
run: .\vcpkg\bootstrap-vcpkg.bat
working-directory: ${{ runner.workspace }}
- name: Create vcpkg.json
working-directory: ${{ runner.workspace }}
run: |
echo '{
"name": "qdomyos-zwift",
"$schema": "https://raw.githubusercontent.com/microsoft/vcpkg-tool/main/docs/vcpkg.schema.json",
"dependencies": [
"protobuf",
"protobuf-c",
"abseil"
],
"builtin-baseline": "8c2fcacefba009d63672f9d137f192765e632c9f"
}' > vcpkg.json
- name: Install dependencies
run: |
.\vcpkg\vcpkg install protobuf protobuf-c abseil
.\vcpkg\vcpkg install --triplet x64-windows --x-install-root=D:\a\qdomyos-zwift\vcpkg\installed
working-directory: ${{ runner.workspace }}
- name: Build
@@ -1024,7 +1058,7 @@ jobs:
cd src/debug
mkdir output
mkdir appx
cp qdomyos-zwift.exe output/
cp qdomyos-zwift.* output/
cd output
windeployqt --qmldir ../../ qdomyos-zwift.exe
cp ../../../icons/iOS/iTunesArtwork@2x.png .
@@ -1072,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
@@ -1080,13 +1115,13 @@ jobs:
cd ..
- name: Build for Raspberry Pi
uses: docker://arm32v7/debian:bullseye
uses: docker://arm32v7/debian:bullseye-20241016
with:
args: >
bash -c "
set -ex &&
apt-get update &&
apt-get install -y build-essential git cmake 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 &&
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-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 &&
export QT_SELECT=qt5 &&
export PATH=/usr/lib/qt5/bin:$PATH &&
cd /github/workspace &&
@@ -1098,14 +1133,17 @@ jobs:
make -j$(nproc)
"
- name: Rename binary
run: mv src/qdomyos-zwift src/qdomyos-zwift-32bit
- name: Archive Raspberry Pi binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-binary
path: src/qdomyos-zwift
path: src/qdomyos-zwift-32bit
raspberry-pi-build-and-image-64bit:
runs-on: ubuntu-latest
runs-on: ubuntu-22.04
steps:
- name: Checkout code
@@ -1113,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
@@ -1131,13 +1174,13 @@ jobs:
cd ..
- name: Build for Raspberry Pi 64-bit
uses: docker://arm64v8/debian:bullseye
uses: docker://arm64v8/debian:bullseye-20241016
with:
args: >
bash -c "
set -ex &&
apt-get update &&
apt-get install -y build-essential git cmake 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 &&
apt-get install -y build-essential git cmake qtbase5-dev qtbase5-private-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 &&
export QT_SELECT=qt5 &&
export PATH=/usr/lib/qt5/bin:$PATH &&
cd /github/workspace &&
@@ -1149,83 +1192,14 @@ jobs:
make -j$(nproc)
"
- name: Archive Raspberry Pi binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-binary
path: src/qdomyos-zwift
- name: Download and expand Raspberry Pi OS image
run: |
wget https://downloads.raspberrypi.org/raspios_lite_arm64/images/raspios_lite_arm64-2023-05-03/2023-05-03-raspios-bullseye-arm64-lite.img.xz
xz -d 2023-05-03-raspios-bullseye-arm64-lite.img.xz
ORIGINAL_SIZE=$(stat -c %s 2023-05-03-raspios-bullseye-arm64-lite.img)
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
truncate -s $NEW_SIZE 2023-05-03-raspios-bullseye-arm64-lite.img
sudo apt-get update
sudo apt-get install -y parted
sudo parted 2023-05-03-raspios-bullseye-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 2023-05-03-raspios-bullseye-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/
- 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 /mnt/raspbian/home/pi/
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: Unmount Raspberry Pi image
run: |
sudo umount /mnt/raspbian
sudo kpartx -d ${{ env.LOOP_DEVICE }}
sudo losetup -d ${{ env.LOOP_DEVICE }}
- name: Compress modified Raspberry Pi image
run: |
xz -z 2023-05-03-raspios-bullseye-arm64-lite.img
- name: Upload Raspberry Pi image as artifact
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-image
path: 2023-05-03-raspios-bullseye-arm64-lite.img.xz
- name: Rename binary
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
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] # 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
@@ -1254,9 +1228,6 @@ jobs:
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/*
raspberry-pi-64bit-binary/*
2023-05-03-raspios-bullseye-arm64-lite.img.xz
raspberry-pi-binary/qdomyos-zwift-32bit
#raspberry-pi-64bit-binary/qdomyos-zwift-64bit
#2024-10-22-raspios-bookworm-arm64-lite.img.xz

16
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,16 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "(Windows) Launch",
"type": "cppvsdbg",
"request": "launch",
"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/",
"compiled_source_path": "C://Users//violarob//Downloads//windows-msvc2019-binary-no-python (1)//output/"
}
}
]
}

View File

@@ -96,34 +96,36 @@ Zwift bridge for Treadmills and Bike!
|:---|:---:|:---:|:---:|:---:|---:|
|Resistance shifting with bluetooth remote|X||X|||
|TTS support|X|X|X|X||
|Zwift Play & Click support|X|||||
|MQTT integration|X|X|X|X||
|OpenSoundControl integration|X|X|X|X||
### Installation
You can install it on multiple platforms.
Read the [installation procedure](docs/10_Installation.md)
You can install it on multiple platforms.
Read the [installation procedure](docs/10_Installation.md)
### Tested on
You can run the app on [Macintosh or Linux devices](docs/10_Installation.md). IOS and Android are also supported.
QDomyos-Zwift works on every [FTMS-compatible application](docs/20_supported_devices_and_applications.md), and virtually any [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
The QDomyos-Zwift application can run on [Macintosh or Linux devices](docs/10_Installation.md) iOS, and Android.
It supports any [FTMS-compatible application](docs/20_supported_devices_and_applications.md) software and most [bluetooth enabled device](docs/20_supported_devices_and_applications.md).
### No GUI version
run as
$ sudo ./qdomyos-zwift -no-gui
$ sudo ./qdomyos-zwift -no-gui
### Reference
https://github.com/ProH4Ck/treadmill-bridge
=> GitHub Repository: [QDomyos-Zwift on GitHub](https://github.com/ProH4Ck/treadmill-bridge)
https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/
=> Treadmill Incline Reference: [What Is 10 Degrees in Incline on a Treadmill?](https://www.livestrong.com/article/422012-what-is-10-degrees-in-incline-on-a-treadmill/)
Icons used in this documentation come from [flaticon.com](https://www.flaticon.com)
=> Icon Attribution: Icons used in this documentation are from [Flaticon.com](https://www.flaticon.com)
### Blog
https://robertoviola.cloud
=> Related Blog: [Roberto Viola's Blog](https://robertoviola.cloud)

View File

@@ -6,21 +6,6 @@
objectVersion = 54;
objects = {
/* Begin PBXAggregateTarget section */
E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */ = {
isa = PBXAggregateTarget;
buildConfigurationList = 6CC3B5D2136C7CD6A5CF5A59 /* Build configuration list for PBXNativeTarget "qdomyoszwift" */;
buildPhases = (
5E618435888B9D49F8540165 /* Qt Qmake */,
7EF0942E79C014DCEC8976BC /* Qt Preprocessors */,
);
dependencies = (
);
name = "Qt Preprocess";
productName = "Qt Preprocess";
};
/* End PBXAggregateTarget section */
/* Begin PBXBuildFile section */
023642106C14651D2E1F4D5D /* dialogplugin in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = B9CCC4658EA620ABBD832E71 /* dialogplugin */; };
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 3088C65BF5992B009FFC93B5 /* virtualtreadmill.cpp */; settings = {ATTRIBUTES = (); }; };
@@ -143,6 +128,8 @@
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87083D9526678EFA0072410D /* zwiftworkout.cpp */; };
87097D2F275EA9A30020EE6F /* sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */; };
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */; };
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */; };
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 870A5DB42CEF8FD200839641 /* technogymbike.cpp */; };
8710706C29C48AEA0094D0F3 /* handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706B29C48AEA0094D0F3 /* handleurl.cpp */; };
8710706E29C48AF30094D0F3 /* moc_handleurl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */; };
8710707329C4A5E70094D0F3 /* GarminConnect.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8710707229C4A5E70094D0F3 /* GarminConnect.swift */; };
@@ -168,6 +155,24 @@
871B9FD2265E6A8800DB41F4 /* powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */; };
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */; };
871E4CD125A6FB5A00E18D6D /* BLEPeripheralManager.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */; };
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */; };
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */; };
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */; };
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */; };
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */; };
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */; };
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */; };
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F52CE65451008C2C17 /* qmqttconnection.cpp */; };
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089042CE65451008C2C17 /* qmqttsubscription.cpp */; };
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F32CE65451008C2C17 /* qmqttclient.cpp */; };
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */; };
872089142CE65451008C2C17 /* qmqtttype.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720890D2CE65451008C2C17 /* qmqtttype.cpp */; };
872089152CE65451008C2C17 /* qmqttcontrolpacket.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */; };
872089162CE65451008C2C17 /* qmqttmessage.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088FE2CE65451008C2C17 /* qmqttmessage.cpp */; };
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */; };
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */; };
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */; };
8720891B2CE6567D008C2C17 /* mqttpublisher.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */; };
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */; };
872261F0289EA887006A6F75 /* moc_nordictrackelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */; };
8727A47727849EA600019B5D /* paferstreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8727A47627849EA600019B5D /* paferstreadmill.cpp */; };
@@ -283,6 +288,10 @@
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8754D24B27F786F0003D7054 /* virtualrower.swift */; };
87586A4125B8340E00A243C4 /* proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4025B8340E00A243C4 /* proformbike.cpp */; };
87586A4325B8341B00A243C4 /* moc_proformbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87586A4225B8341B00A243C4 /* moc_proformbike.cpp */; };
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */; };
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */; };
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA94B2D130F8100667EE6 /* moc_osc.cpp */; };
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875CA9502D130FBC00667EE6 /* osc.cpp */; };
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69B826342E8D0009FD78 /* spirittreadmill.cpp */; };
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */; };
8762D50F2601F7EA00F6F049 /* M3iNS.mm in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8762D50B2601F7EA00F6F049 /* M3iNS.mm */; };
@@ -358,6 +367,7 @@
8772A0E825E43AE70080718C /* moc_trxappgateusbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772A0E725E43AE70080718C /* moc_trxappgateusbbike.cpp */; };
8772B7F42CB55E80004AB8E9 /* moc_deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */; };
8772B7F72CB55E98004AB8E9 /* deerruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */; };
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877350F62D1C08E50070CBD8 /* SmartControl.cpp */; };
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008129E876F7008E48B7 /* iconceptelliptical.cpp */; };
8775008529E87713008E48B7 /* moc_iconceptelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */; };
877758B32C98627300BB1697 /* moc_sportstechelliptical.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 877758B22C98627300BB1697 /* moc_sportstechelliptical.cpp */; };
@@ -380,6 +390,8 @@
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878531672711A3EB004B153D /* moc_fakebike.cpp */; };
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */; };
8785D5442B3DD105005A2EB7 /* moc_zwift_client_auth.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */; };
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */; };
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */; };
878A331A25AB4FF800BD13E1 /* yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331725AB4FF800BD13E1 /* yesoulbike.cpp */; };
878A331D25AB50C300BD13E1 /* moc_yesoulbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878A331B25AB50C200BD13E1 /* moc_yesoulbike.cpp */; };
878C9E6928B77E7C00669129 /* nordictrackifitadbbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 878C9E6828B77E7B00669129 /* nordictrackifitadbbike.cpp */; };
@@ -401,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 */; };
@@ -423,11 +436,17 @@
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 */; };
87A3BC27265642A300D302E3 /* moc_echelonrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */; };
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */; };
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */; };
87A4B76125AF27CB0027EF3C /* metric.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A4B75F25AF27CB0027EF3C /* metric.cpp */; };
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */; };
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */; };
87ADD2BB27634C1500B7A0AB /* technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */; };
87ADD2BD27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */; };
87AE0CB227760DCB00E547E9 /* virtualtreadmill_zwift.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87AE0CB127760DCB00E547E9 /* virtualtreadmill_zwift.swift */; };
@@ -439,6 +458,8 @@
87B617F225F260150094A1CB /* moc_fitshowtreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */; };
87B617F325F260150094A1CB /* moc_snodebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F025F260140094A1CB /* moc_snodebike.cpp */; };
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B617F125F260150094A1CB /* moc_screencapture.cpp */; };
87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */; };
87B871932CE1E94D009B06CA /* zwifthubbike.swift in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87B871922CE1E94D009B06CA /* zwifthubbike.swift */; };
87BAC3BF2BA497160003E925 /* PrivacyInfo.xcprivacy in Copy Bundle Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */; };
87BAC3C12BA497350003E925 /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */; };
87BAFE482B8CA7AA00065FCD /* moc_focustreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */; };
@@ -528,6 +549,8 @@
87E5D2C825E69F4700BDBE6C /* moc_horizontreadmill.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E5D2C725E69F4700BDBE6C /* moc_horizontreadmill.cpp */; };
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */; };
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87E6A85925B5C8B900371D28 /* flywheelbike.cpp */; };
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */; };
87EAC3D62D1D8D34004FE975 /* pitpatbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */; };
87EB917627EE5FB3002535E1 /* nautilusbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917427EE5FB3002535E1 /* nautilusbike.cpp */; };
87EB918227EE5FE7002535E1 /* moc_inapptransaction.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917727EE5FE7002535E1 /* moc_inapptransaction.cpp */; };
87EB918327EE5FE7002535E1 /* moc_inappstore.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87EB917827EE5FE7002535E1 /* moc_inappstore.cpp */; };
@@ -552,6 +575,10 @@
87FA11AB27C5ECD1008AC5D1 /* ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AA27C5ECD1008AC5D1 /* ultrasportbike.cpp */; };
87FA11AD27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FA11AC27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp */; };
87FA94672B6B89FD00B6AB9A /* SwiftUI.framework in Link Binary With Libraries */ = {isa = PBXBuildFile; fileRef = 87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
87FC40BC2D2E74F9008BA736 /* cycleopsphantombike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */; };
87FC40BD2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */; };
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */; };
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */; };
87FE5BAF2692F3130056EFC8 /* tacxneo2.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE5BAD2692F3130056EFC8 /* tacxneo2.cpp */; };
87FE5BB12692F31E0056EFC8 /* moc_tacxneo2.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FE5BB02692F31E0056EFC8 /* moc_tacxneo2.cpp */; };
87FFA13727BBE3FF00924E4E /* solebike.cpp in Compile Sources */ = {isa = PBXBuildFile; fileRef = 87FFA13527BBE3FE00924E4E /* solebike.cpp */; };
@@ -624,13 +651,6 @@
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
876E4E0E2594739400BD5714 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
proxyType = 1;
remoteGlobalIDString = E9F0AFC024A6F2D65CE84E08;
remoteInfo = "Qt Preprocess";
};
876E4E1C2594748000BD5714 /* PBXContainerItemProxy */ = {
isa = PBXContainerItemProxy;
containerPortal = 6DB9C3763D02B1415CD9D565 /* Project object */;
@@ -913,6 +933,8 @@
87097D2D275EA9A20020EE6F /* sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = sportsplusbike.cpp; path = ../src/devices/sportsplusbike/sportsplusbike.cpp; sourceTree = "<group>"; };
87097D2E275EA9A20020EE6F /* sportsplusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = sportsplusbike.h; path = ../src/devices/sportsplusbike/sportsplusbike.h; sourceTree = "<group>"; };
87097D30275EA9AE0020EE6F /* moc_sportsplusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusbike.cpp; sourceTree = "<group>"; };
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymbike.cpp; sourceTree = "<group>"; };
870A5DB42CEF8FD200839641 /* technogymbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = technogymbike.cpp; path = ../src/devices/technogymbike/technogymbike.cpp; sourceTree = SOURCE_ROOT; };
8710706A29C48AE90094D0F3 /* handleurl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = handleurl.h; path = ../src/handleurl.h; sourceTree = "<group>"; };
8710706B29C48AEA0094D0F3 /* handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = handleurl.cpp; path = ../src/handleurl.cpp; sourceTree = "<group>"; };
8710706D29C48AF30094D0F3 /* moc_handleurl.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_handleurl.cpp; sourceTree = "<group>"; };
@@ -949,6 +971,42 @@
871B9FD1265E6A8800DB41F4 /* powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = powerzonepack.cpp; path = ../src/powerzonepack.cpp; sourceTree = "<group>"; };
871B9FD3265E6A9A00DB41F4 /* moc_powerzonepack.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_powerzonepack.cpp; sourceTree = "<group>"; };
871E4CD025A6FB5A00E18D6D /* BLEPeripheralManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = BLEPeripheralManager.swift; path = ../src/ios/BLEPeripheralManager.swift; sourceTree = "<group>"; };
872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_mqttpublisher.cpp; sourceTree = "<group>"; };
872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttclient.cpp; sourceTree = "<group>"; };
872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttconnection_p.cpp; sourceTree = "<group>"; };
872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttmessage.cpp; sourceTree = "<group>"; };
872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_qmqttsubscription.cpp; sourceTree = "<group>"; };
872088F02CE65451008C2C17 /* qmqttauthenticationproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttauthenticationproperties.h; path = ../src/mqtt/qmqttauthenticationproperties.h; sourceTree = SOURCE_ROOT; };
872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttauthenticationproperties.cpp; path = ../src/mqtt/qmqttauthenticationproperties.cpp; sourceTree = SOURCE_ROOT; };
872088F22CE65451008C2C17 /* qmqttclient.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttclient.h; path = ../src/mqtt/qmqttclient.h; sourceTree = SOURCE_ROOT; };
872088F32CE65451008C2C17 /* qmqttclient.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttclient.cpp; path = ../src/mqtt/qmqttclient.cpp; sourceTree = SOURCE_ROOT; };
872088F42CE65451008C2C17 /* qmqttclient_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttclient_p.h; path = ../src/mqtt/qmqttclient_p.h; sourceTree = SOURCE_ROOT; };
872088F52CE65451008C2C17 /* qmqttconnection.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttconnection.cpp; path = ../src/mqtt/qmqttconnection.cpp; sourceTree = SOURCE_ROOT; };
872088F62CE65451008C2C17 /* qmqttconnection_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnection_p.h; path = ../src/mqtt/qmqttconnection_p.h; sourceTree = SOURCE_ROOT; };
872088F72CE65451008C2C17 /* qmqttconnectionproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnectionproperties.h; path = ../src/mqtt/qmqttconnectionproperties.h; sourceTree = SOURCE_ROOT; };
872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttconnectionproperties.cpp; path = ../src/mqtt/qmqttconnectionproperties.cpp; sourceTree = SOURCE_ROOT; };
872088F92CE65451008C2C17 /* qmqttconnectionproperties_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttconnectionproperties_p.h; path = ../src/mqtt/qmqttconnectionproperties_p.h; sourceTree = SOURCE_ROOT; };
872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttcontrolpacket.cpp; path = ../src/mqtt/qmqttcontrolpacket.cpp; sourceTree = SOURCE_ROOT; };
872088FB2CE65451008C2C17 /* qmqttcontrolpacket_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttcontrolpacket_p.h; path = ../src/mqtt/qmqttcontrolpacket_p.h; sourceTree = SOURCE_ROOT; };
872088FC2CE65451008C2C17 /* qmqttglobal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttglobal.h; path = ../src/mqtt/qmqttglobal.h; sourceTree = SOURCE_ROOT; };
872088FD2CE65451008C2C17 /* qmqttmessage.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttmessage.h; path = ../src/mqtt/qmqttmessage.h; sourceTree = SOURCE_ROOT; };
872088FE2CE65451008C2C17 /* qmqttmessage.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttmessage.cpp; path = ../src/mqtt/qmqttmessage.cpp; sourceTree = SOURCE_ROOT; };
872088FF2CE65451008C2C17 /* qmqttmessage_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttmessage_p.h; path = ../src/mqtt/qmqttmessage_p.h; sourceTree = SOURCE_ROOT; };
872089002CE65451008C2C17 /* qmqttpublishproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttpublishproperties.h; path = ../src/mqtt/qmqttpublishproperties.h; sourceTree = SOURCE_ROOT; };
872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttpublishproperties.cpp; path = ../src/mqtt/qmqttpublishproperties.cpp; sourceTree = SOURCE_ROOT; };
872089022CE65451008C2C17 /* qmqttpublishproperties_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttpublishproperties_p.h; path = ../src/mqtt/qmqttpublishproperties_p.h; sourceTree = SOURCE_ROOT; };
872089032CE65451008C2C17 /* qmqttsubscription.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscription.h; path = ../src/mqtt/qmqttsubscription.h; sourceTree = SOURCE_ROOT; };
872089042CE65451008C2C17 /* qmqttsubscription.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttsubscription.cpp; path = ../src/mqtt/qmqttsubscription.cpp; sourceTree = SOURCE_ROOT; };
872089052CE65451008C2C17 /* qmqttsubscription_p.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscription_p.h; path = ../src/mqtt/qmqttsubscription_p.h; sourceTree = SOURCE_ROOT; };
872089062CE65451008C2C17 /* qmqttsubscriptionproperties.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqttsubscriptionproperties.h; path = ../src/mqtt/qmqttsubscriptionproperties.h; sourceTree = SOURCE_ROOT; };
872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqttsubscriptionproperties.cpp; path = ../src/mqtt/qmqttsubscriptionproperties.cpp; sourceTree = SOURCE_ROOT; };
872089082CE65451008C2C17 /* qmqtttopicfilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttopicfilter.h; path = ../src/mqtt/qmqtttopicfilter.h; sourceTree = SOURCE_ROOT; };
872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttopicfilter.cpp; path = ../src/mqtt/qmqtttopicfilter.cpp; sourceTree = SOURCE_ROOT; };
8720890A2CE65451008C2C17 /* qmqtttopicname.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttopicname.h; path = ../src/mqtt/qmqtttopicname.h; sourceTree = SOURCE_ROOT; };
8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttopicname.cpp; path = ../src/mqtt/qmqtttopicname.cpp; sourceTree = SOURCE_ROOT; };
8720890C2CE65451008C2C17 /* qmqtttype.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = qmqtttype.h; path = ../src/mqtt/qmqtttype.h; sourceTree = SOURCE_ROOT; };
8720890D2CE65451008C2C17 /* qmqtttype.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = qmqtttype.cpp; path = ../src/mqtt/qmqtttype.cpp; sourceTree = SOURCE_ROOT; };
8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mqttpublisher.cpp; path = ../src/mqttpublisher.cpp; sourceTree = SOURCE_ROOT; };
872261EC289EA873006A6F75 /* nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nordictrackelliptical.cpp; path = ../src/devices/nordictrackelliptical/nordictrackelliptical.cpp; sourceTree = "<group>"; };
872261ED289EA873006A6F75 /* nordictrackelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nordictrackelliptical.h; path = ../src/devices/nordictrackelliptical/nordictrackelliptical.h; sourceTree = "<group>"; };
872261EF289EA887006A6F75 /* moc_nordictrackelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_nordictrackelliptical.cpp; sourceTree = "<group>"; };
@@ -1117,6 +1175,18 @@
87586A3F25B8340D00A243C4 /* proformbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = proformbike.h; path = ../src/devices/proformbike/proformbike.h; sourceTree = "<group>"; };
87586A4025B8340E00A243C4 /* proformbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = proformbike.cpp; path = ../src/devices/proformbike/proformbike.cpp; sourceTree = "<group>"; };
87586A4225B8341B00A243C4 /* moc_proformbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_proformbike.cpp; sourceTree = "<group>"; };
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_kineticinroadbike.cpp; sourceTree = "<group>"; };
875CA9472D0C742500667EE6 /* kineticinroadbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = kineticinroadbike.h; path = ../src/devices/kineticinroadbike/kineticinroadbike.h; sourceTree = SOURCE_ROOT; };
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = kineticinroadbike.cpp; path = ../src/devices/kineticinroadbike/kineticinroadbike.cpp; sourceTree = SOURCE_ROOT; };
875CA94B2D130F8100667EE6 /* moc_osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_osc.cpp; sourceTree = "<group>"; };
875CA94D2D130FBC00667EE6 /* client.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = client.hpp; path = ../src/oscpp/client.hpp; sourceTree = SOURCE_ROOT; };
875CA94E2D130FBC00667EE6 /* error.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = error.hpp; path = ../src/oscpp/error.hpp; sourceTree = SOURCE_ROOT; };
875CA94F2D130FBC00667EE6 /* osc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = osc.h; path = ../src/osc.h; sourceTree = SOURCE_ROOT; };
875CA9502D130FBC00667EE6 /* osc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = osc.cpp; path = ../src/osc.cpp; sourceTree = SOURCE_ROOT; };
875CA9512D130FBC00667EE6 /* print.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = print.hpp; path = ../src/oscpp/print.hpp; sourceTree = SOURCE_ROOT; };
875CA9522D130FBC00667EE6 /* server.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = server.hpp; path = ../src/oscpp/server.hpp; sourceTree = SOURCE_ROOT; };
875CA9532D130FBC00667EE6 /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = types.hpp; path = ../src/oscpp/types.hpp; sourceTree = SOURCE_ROOT; };
875CA9542D130FBC00667EE6 /* util.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; name = util.hpp; path = ../src/oscpp/util.hpp; sourceTree = SOURCE_ROOT; };
875F69B726342E8D0009FD78 /* spirittreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = spirittreadmill.h; path = ../src/devices/spirittreadmill/spirittreadmill.h; sourceTree = "<group>"; };
875F69B826342E8D0009FD78 /* spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = spirittreadmill.cpp; path = ../src/devices/spirittreadmill/spirittreadmill.cpp; sourceTree = "<group>"; };
875F69BA26342E9A0009FD78 /* moc_spirittreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_spirittreadmill.cpp; sourceTree = "<group>"; };
@@ -1239,6 +1309,8 @@
8772B7F32CB55E80004AB8E9 /* moc_deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_deerruntreadmill.cpp; sourceTree = "<group>"; };
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = deerruntreadmill.cpp; sourceTree = "<group>"; };
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = deerruntreadmill.h; path = ../src/devices/deeruntreadmill/deerruntreadmill.h; sourceTree = SOURCE_ROOT; };
877350F52D1C08E50070CBD8 /* SmartControl.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SmartControl.h; path = ../src/devices/kineticinroadbike/SmartControl.h; sourceTree = SOURCE_ROOT; };
877350F62D1C08E50070CBD8 /* SmartControl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SmartControl.cpp; path = ../src/devices/kineticinroadbike/SmartControl.cpp; sourceTree = SOURCE_ROOT; };
8775008129E876F7008E48B7 /* iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = iconceptelliptical.cpp; path = ../src/devices/iconceptelliptical/iconceptelliptical.cpp; sourceTree = "<group>"; };
8775008229E876F7008E48B7 /* iconceptelliptical.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = iconceptelliptical.h; path = ../src/devices/iconceptelliptical/iconceptelliptical.h; sourceTree = "<group>"; };
8775008429E87712008E48B7 /* moc_iconceptelliptical.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_iconceptelliptical.cpp; sourceTree = "<group>"; };
@@ -1272,6 +1344,9 @@
8785D5402B3DD0EC005A2EB7 /* PlayerStateWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = PlayerStateWrapper.h; path = "../src/zwift-api/PlayerStateWrapper.h"; sourceTree = "<group>"; };
8785D5412B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_PlayerStateWrapper.cpp; sourceTree = "<group>"; };
8785D5422B3DD105005A2EB7 /* moc_zwift_client_auth.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_zwift_client_auth.cpp; sourceTree = "<group>"; };
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = sportsplusrower.cpp; sourceTree = "<group>"; };
87873AF22D09AADF005F86B4 /* sportsplusrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportsplusrower.h; path = ../src/devices/sportsplusrower/sportsplusrower.h; sourceTree = SOURCE_ROOT; };
8789DCDB6A4F681A76DF3F92 /* Qt5Widgets */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = Qt5Widgets; path = "/Users/cagnulein/Qt/5.15.2/ios/lib/libQt5Widgets$(QT_LIBRARY_SUFFIX).a"; sourceTree = "<absolute>"; };
878A331725AB4FF800BD13E1 /* yesoulbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = yesoulbike.cpp; path = ../src/devices/yesoulbike/yesoulbike.cpp; sourceTree = "<group>"; };
878A331825AB4FF800BD13E1 /* yesoulbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = yesoulbike.h; path = ../src/devices/yesoulbike/yesoulbike.h; sourceTree = "<group>"; };
@@ -1303,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>"; };
@@ -1335,17 +1415,26 @@
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>"; };
87A3BC212656429400D302E3 /* rower.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = rower.h; path = ../src/devices/rower.h; sourceTree = "<group>"; };
87A3BC24265642A200D302E3 /* moc_rower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_rower.cpp; sourceTree = "<group>"; };
87A3BC25265642A200D302E3 /* moc_echelonrower.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_echelonrower.cpp; sourceTree = "<group>"; };
87A3DD982D3413790060BAEB /* lifespantreadmill.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = lifespantreadmill.h; path = ../src/devices/lifespantreadmill/lifespantreadmill.h; sourceTree = SOURCE_ROOT; };
87A3DD992D3413790060BAEB /* lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = lifespantreadmill.cpp; path = ../src/devices/lifespantreadmill/lifespantreadmill.cpp; sourceTree = SOURCE_ROOT; };
87A3DD9A2D3413790060BAEB /* moc_lifespantreadmill.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_lifespantreadmill.cpp; sourceTree = "<group>"; };
87A3EBB925D2CFED0040EB4C /* sportstechbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sportstechbike.h; path = ../src/devices/sportstechbike/sportstechbike.h; sourceTree = "<group>"; };
87A3EBBA25D2CFED0040EB4C /* sportstechbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sportstechbike.cpp; path = ../src/devices/sportstechbike/sportstechbike.cpp; sourceTree = "<group>"; };
87A4B75F25AF27CB0027EF3C /* metric.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = metric.cpp; path = ../src/metric.cpp; sourceTree = "<group>"; };
87A4B76025AF27CB0027EF3C /* metric.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = metric.h; path = ../src/metric.h; sourceTree = "<group>"; };
87A659DB8BE7DBAA7B395EF4 /* fit_monitoring_info_mesg_listener.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_monitoring_info_mesg_listener.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_monitoring_info_mesg_listener.hpp"; sourceTree = "<absolute>"; };
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_sramAXSController.cpp; sourceTree = "<group>"; };
87A6825B2CE3AB4000586A2A /* sramAXSController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = sramAXSController.h; path = ../src/devices/sramAXSController/sramAXSController.h; sourceTree = SOURCE_ROOT; };
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = sramAXSController.cpp; path = ../src/devices/sramAXSController/sramAXSController.cpp; sourceTree = SOURCE_ROOT; };
87ADD2B927634C1400B7A0AB /* technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = technogymmyruntreadmill.cpp; path = ../src/devices/technogymmyruntreadmill/technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
87ADD2BA27634C1400B7A0AB /* technogymmyruntreadmill.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = technogymmyruntreadmill.h; path = ../src/devices/technogymmyruntreadmill/technogymmyruntreadmill.h; sourceTree = "<group>"; };
87ADD2BC27634C2100B7A0AB /* moc_technogymmyruntreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_technogymmyruntreadmill.cpp; sourceTree = "<group>"; };
@@ -1362,6 +1451,8 @@
87B617EF25F260140094A1CB /* moc_fitshowtreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_fitshowtreadmill.cpp; sourceTree = "<group>"; };
87B617F025F260140094A1CB /* moc_snodebike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_snodebike.cpp; sourceTree = "<group>"; };
87B617F125F260150094A1CB /* moc_screencapture.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_screencapture.cpp; sourceTree = "<group>"; };
87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Zwift hub.pb.swift"; path = "../src/devices/zwifthubbike/Zwift hub.pb.swift"; sourceTree = SOURCE_ROOT; };
87B871922CE1E94D009B06CA /* zwifthubbike.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = zwifthubbike.swift; path = ../src/devices/zwifthubbike/zwifthubbike.swift; sourceTree = SOURCE_ROOT; };
87BAC3BE2BA497160003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = PrivacyInfo.xcprivacy; path = ../src/ios/PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
87BAC3C02BA497350003E925 /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
87BAFE472B8CA7AA00065FCD /* moc_focustreadmill.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_focustreadmill.cpp; sourceTree = "<group>"; };
@@ -1493,6 +1584,9 @@
87E6A85725B5C88E00371D28 /* moc_flywheelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_flywheelbike.cpp; sourceTree = "<group>"; };
87E6A85925B5C8B900371D28 /* flywheelbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = flywheelbike.cpp; path = ../src/devices/flywheelbike/flywheelbike.cpp; sourceTree = "<group>"; };
87E6A85A25B5C8B900371D28 /* flywheelbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = flywheelbike.h; path = ../src/devices/flywheelbike/flywheelbike.h; sourceTree = "<group>"; };
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_pitpatbike.cpp; sourceTree = "<group>"; };
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = pitpatbike.h; path = ../src/devices/pitpatbike/pitpatbike.h; sourceTree = SOURCE_ROOT; };
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pitpatbike.cpp; path = ../src/devices/pitpatbike/pitpatbike.cpp; sourceTree = SOURCE_ROOT; };
87EB917427EE5FB3002535E1 /* nautilusbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = nautilusbike.cpp; path = ../src/devices/nautilusbike/nautilusbike.cpp; sourceTree = "<group>"; };
87EB917527EE5FB3002535E1 /* nautilusbike.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = nautilusbike.h; path = ../src/devices/nautilusbike/nautilusbike.h; sourceTree = "<group>"; };
87EB917727EE5FE7002535E1 /* moc_inapptransaction.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_inapptransaction.cpp; sourceTree = "<group>"; };
@@ -1528,7 +1622,13 @@
87FA11AC27C5ECE4008AC5D1 /* moc_ultrasportbike.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_ultrasportbike.cpp; sourceTree = "<group>"; };
87FA94662B6B89FD00B6AB9A /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
87FA94682B6B8A5A00B6AB9A /* libSystem.B.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libSystem.B.tbd; path = usr/lib/libSystem.B.tbd; sourceTree = SDKROOT; };
87FC40B92D2E74F9008BA736 /* cycleopsphantombike.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = cycleopsphantombike.h; path = ../src/devices/cycleopsphantombike/cycleopsphantombike.h; sourceTree = SOURCE_ROOT; };
87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = cycleopsphantombike.cpp; path = ../src/devices/cycleopsphantombike/cycleopsphantombike.cpp; sourceTree = SOURCE_ROOT; };
87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_cycleopsphantombike.cpp; sourceTree = "<group>"; };
87FD05DDC378E9BAD82A818F /* fit_ohr_settings_mesg_listener.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = fit_ohr_settings_mesg_listener.hpp; path = "/Users/cagnulein/qdomyos-zwift/src/fit-sdk/fit_ohr_settings_mesg_listener.hpp"; sourceTree = "<absolute>"; };
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = moc_trxappgateusbrower.cpp; sourceTree = "<group>"; };
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = trxappgateusbrower.h; path = ../src/devices/trxappgateusbrower/trxappgateusbrower.h; sourceTree = SOURCE_ROOT; };
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = trxappgateusbrower.cpp; path = ../src/devices/trxappgateusbrower/trxappgateusbrower.cpp; sourceTree = SOURCE_ROOT; };
87FE5BAD2692F3130056EFC8 /* tacxneo2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = tacxneo2.cpp; path = ../src/devices/tacxneo2/tacxneo2.cpp; sourceTree = "<group>"; };
87FE5BAE2692F3130056EFC8 /* tacxneo2.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = tacxneo2.h; path = ../src/devices/tacxneo2/tacxneo2.h; sourceTree = "<group>"; };
87FE5BB02692F31E0056EFC8 /* moc_tacxneo2.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = moc_tacxneo2.cpp; sourceTree = "<group>"; };
@@ -2079,6 +2179,82 @@
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 */,
87FC40B92D2E74F9008BA736 /* cycleopsphantombike.h */,
87FC40BA2D2E74F9008BA736 /* cycleopsphantombike.cpp */,
87FC40BB2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp */,
87EAC3D42D1D8D34004FE975 /* pitpatbike.h */,
87EAC3D52D1D8D34004FE975 /* pitpatbike.cpp */,
87EAC3D22D1D8D1D004FE975 /* moc_pitpatbike.cpp */,
877350F52D1C08E50070CBD8 /* SmartControl.h */,
877350F62D1C08E50070CBD8 /* SmartControl.cpp */,
875CA94D2D130FBC00667EE6 /* client.hpp */,
875CA94E2D130FBC00667EE6 /* error.hpp */,
875CA94F2D130FBC00667EE6 /* osc.h */,
875CA9502D130FBC00667EE6 /* osc.cpp */,
875CA9512D130FBC00667EE6 /* print.hpp */,
875CA9522D130FBC00667EE6 /* server.hpp */,
875CA9532D130FBC00667EE6 /* types.hpp */,
875CA9542D130FBC00667EE6 /* util.hpp */,
875CA94B2D130F8100667EE6 /* moc_osc.cpp */,
875CA9472D0C742500667EE6 /* kineticinroadbike.h */,
875CA9482D0C742500667EE6 /* kineticinroadbike.cpp */,
875CA9452D0C740000667EE6 /* moc_kineticinroadbike.cpp */,
87873AF22D09AADF005F86B4 /* sportsplusrower.h */,
87873AF02D09A8CE005F86B4 /* sportsplusrower.cpp */,
87873AED2D09A8AA005F86B4 /* moc_sportsplusrower.cpp */,
870A5DB42CEF8FD200839641 /* technogymbike.cpp */,
870A5DB22CEF8FB100839641 /* moc_technogymbike.cpp */,
8720891A2CE6567D008C2C17 /* mqttpublisher.cpp */,
872088F02CE65451008C2C17 /* qmqttauthenticationproperties.h */,
872088F12CE65451008C2C17 /* qmqttauthenticationproperties.cpp */,
872088F22CE65451008C2C17 /* qmqttclient.h */,
872088F32CE65451008C2C17 /* qmqttclient.cpp */,
872088F42CE65451008C2C17 /* qmqttclient_p.h */,
872088F52CE65451008C2C17 /* qmqttconnection.cpp */,
872088F62CE65451008C2C17 /* qmqttconnection_p.h */,
872088F72CE65451008C2C17 /* qmqttconnectionproperties.h */,
872088F82CE65451008C2C17 /* qmqttconnectionproperties.cpp */,
872088F92CE65451008C2C17 /* qmqttconnectionproperties_p.h */,
872088FA2CE65451008C2C17 /* qmqttcontrolpacket.cpp */,
872088FB2CE65451008C2C17 /* qmqttcontrolpacket_p.h */,
872088FC2CE65451008C2C17 /* qmqttglobal.h */,
872088FD2CE65451008C2C17 /* qmqttmessage.h */,
872088FE2CE65451008C2C17 /* qmqttmessage.cpp */,
872088FF2CE65451008C2C17 /* qmqttmessage_p.h */,
872089002CE65451008C2C17 /* qmqttpublishproperties.h */,
872089012CE65451008C2C17 /* qmqttpublishproperties.cpp */,
872089022CE65451008C2C17 /* qmqttpublishproperties_p.h */,
872089032CE65451008C2C17 /* qmqttsubscription.h */,
872089042CE65451008C2C17 /* qmqttsubscription.cpp */,
872089052CE65451008C2C17 /* qmqttsubscription_p.h */,
872089062CE65451008C2C17 /* qmqttsubscriptionproperties.h */,
872089072CE65451008C2C17 /* qmqttsubscriptionproperties.cpp */,
872089082CE65451008C2C17 /* qmqtttopicfilter.h */,
872089092CE65451008C2C17 /* qmqtttopicfilter.cpp */,
8720890A2CE65451008C2C17 /* qmqtttopicname.h */,
8720890B2CE65451008C2C17 /* qmqtttopicname.cpp */,
8720890C2CE65451008C2C17 /* qmqtttype.h */,
8720890D2CE65451008C2C17 /* qmqtttype.cpp */,
872088E62CE6543C008C2C17 /* moc_mqttpublisher.cpp */,
872088E72CE6543C008C2C17 /* moc_qmqttclient.cpp */,
872088E82CE6543C008C2C17 /* moc_qmqttconnection_p.cpp */,
872088E92CE6543C008C2C17 /* moc_qmqttmessage.cpp */,
872088EA2CE6543C008C2C17 /* moc_qmqttsubscription.cpp */,
87B8718F2CE1E837009B06CA /* Zwift hub.pb.swift */,
87A6825B2CE3AB4000586A2A /* sramAXSController.h */,
87A6825C2CE3AB4000586A2A /* sramAXSController.cpp */,
87A682592CE3AB3100586A2A /* moc_sramAXSController.cpp */,
87A083062C73361C00567A4E /* characteristicnotifier2ad9.h */,
8772B7F92CB5603A004AB8E9 /* deerruntreadmill.h */,
8772B7F62CB55E98004AB8E9 /* deerruntreadmill.cpp */,
@@ -2494,6 +2670,10 @@
8710707229C4A5E70094D0F3 /* GarminConnect.swift */,
87A2E0202B2B024200E6168F /* swiftDebug.h */,
8730A3912B404159007E336D /* zwift_protobuf_layer.swift */,
87B871922CE1E94D009B06CA /* zwifthubbike.swift */,
87FE06822D170D5600CDAAF6 /* trxappgateusbrower.h */,
87FE06832D170D5600CDAAF6 /* trxappgateusbrower.cpp */,
87FE06802D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp */,
);
name = Sources;
sourceTree = "<group>";
@@ -3095,7 +3275,6 @@
buildRules = (
);
dependencies = (
F8E0E95C27758CAC67EF1CD4 /* PBXTargetDependency */,
876E4E312594748100BD5714 /* PBXTargetDependency */,
);
name = qdomyoszwift;
@@ -3165,9 +3344,6 @@
DevelopmentTeam = 6335M7T29D;
ProvisioningStyle = Automatic;
};
E9F0AFC024A6F2D65CE84E08 = {
DevelopmentTeam = 6335M7T29D;
};
};
};
buildConfigurationList = DAC4C1AA5EDEA1C85E9CA5E6 /* Build configuration list for PBXProject "qdomyoszwift" */;
@@ -3187,7 +3363,6 @@
projectRoot = "";
targets = (
799833E5566DEFFC37E4BF1E /* qdomyoszwift */,
E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */,
876E4E102594747F00BD5714 /* watchkit */,
876E4E192594748000BD5714 /* watchkit Extension */,
);
@@ -3238,31 +3413,6 @@
};
/* End PBXResourcesBuildPhase section */
/* Begin PBXShellScriptBuildPhase section */
5E618435888B9D49F8540165 /* Qt Qmake */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "Qt Qmake";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "make -C /Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug -f qdomyoszwift.xcodeproj/qt_makeqmake.mak";
showEnvVarsInLog = 0;
};
7EF0942E79C014DCEC8976BC /* Qt Preprocessors */ = {
isa = PBXShellScriptBuildPhase;
buildActionMask = 2147483647;
files = (
);
name = "Qt Preprocessors";
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "make -C /Users/cagnulein/qdomyos-zwift/build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug -f qdomyoszwift.xcodeproj/qt_preprocess.mak";
showEnvVarsInLog = 0;
};
/* End PBXShellScriptBuildPhase section */
/* Begin PBXSourcesBuildPhase section */
876E4E162594748000BD5714 /* Sources */ = {
isa = PBXSourcesBuildPhase;
@@ -3284,6 +3434,7 @@
files = (
8738249627E646E3004F1B46 /* characteristicnotifier2acd.cpp in Compile Sources */,
8738249127E646E3004F1B46 /* dirconpacket.cpp in Compile Sources */,
870A5DB52CEF8FD200839641 /* technogymbike.cpp in Compile Sources */,
87C5F0BB26285E5F0067A1B5 /* mimetext.cpp in Compile Sources */,
8732C17F27353464006DF424 /* iconceptbike.cpp in Compile Sources */,
87CF5169293C879800A7CABC /* characteristicwriteprocessore005.cpp in Compile Sources */,
@@ -3416,6 +3567,7 @@
87BE6FDE272D2A3E00C35795 /* moc_horizongr7bike.cpp in Compile Sources */,
A4BD6DF51CFFF867B7B5AED4 /* fit_developer_field_definition.cpp in Compile Sources */,
87EB918B27EE5FE7002535E1 /* moc_inappproductqmltype.cpp in Compile Sources */,
87873AF12D09A8CE005F86B4 /* sportsplusrower.cpp in Compile Sources */,
8762D5132601F89500F6F049 /* scanrecordresult.cpp in Compile Sources */,
3015F9B9FF4CA6D653D46CCA /* fit_developer_field_description.cpp in Compile Sources */,
87310B22266FBB78008BA0D6 /* moc_homefitnessbuddy.cpp in Compile Sources */,
@@ -3442,6 +3594,7 @@
87D269A325F535340076AA48 /* moc_skandikawiribike.cpp in Compile Sources */,
87F4FB5C29D550E00061BB4A /* moc_schwinn170bike.cpp in Compile Sources */,
87C5F0C226285E5F0067A1B5 /* emailaddress.cpp in Compile Sources */,
87B871902CE1E837009B06CA /* Zwift hub.pb.swift in Compile Sources */,
873824AF27E64706004F1B46 /* moc_characteristicwriteprocessor2ad9.cpp in Compile Sources */,
25F2400F80DAFBD41FE5CC75 /* fit_field.cpp in Compile Sources */,
873824E227E647A8004F1B46 /* dns.cpp in Compile Sources */,
@@ -3464,17 +3617,35 @@
87E6A85B25B5C8B900371D28 /* flywheelbike.cpp in Compile Sources */,
87182A09276BBAF600141463 /* virtualrower.cpp in Compile Sources */,
87C424262BC1294000503687 /* moc_treadmillErgTable.cpp in Compile Sources */,
877350F72D1C08E60070CBD8 /* SmartControl.cpp in Compile Sources */,
873824ED27E647A9004F1B46 /* resolver.cpp in Compile Sources */,
875CA9552D130FBC00667EE6 /* osc.cpp in Compile Sources */,
878531652711A3E1004B153D /* activiotreadmill.cpp in Compile Sources */,
87DAE16926E9FF5000B0527E /* moc_shuaa5treadmill.cpp in Compile Sources */,
48BA9CE9D6F256A15E8FB25D /* fit_mesg.cpp in Compile Sources */,
DD2E0091F3318F053D2995AA /* fit_mesg_broadcaster.cpp in Compile Sources */,
FE77C778768741F1A161682E /* fit_mesg_definition.cpp in Compile Sources */,
875F69B926342E8D0009FD78 /* spirittreadmill.cpp in Compile Sources */,
87A6825D2CE3AB4000586A2A /* sramAXSController.cpp in Compile Sources */,
87DF68B825E2673B00FCDA46 /* eslinkertreadmill.cpp in Compile Sources */,
8720890E2CE65451008C2C17 /* qmqttsubscriptionproperties.cpp in Compile Sources */,
8720890F2CE65451008C2C17 /* qmqttconnectionproperties.cpp in Compile Sources */,
872089102CE65451008C2C17 /* qmqttconnection.cpp in Compile Sources */,
872089112CE65451008C2C17 /* qmqttsubscription.cpp in Compile Sources */,
872089122CE65451008C2C17 /* qmqttclient.cpp in Compile Sources */,
872089132CE65451008C2C17 /* qmqtttopicfilter.cpp in Compile Sources */,
872089142CE65451008C2C17 /* qmqtttype.cpp in Compile Sources */,
872089152CE65451008C2C17 /* qmqttcontrolpacket.cpp in Compile Sources */,
872089162CE65451008C2C17 /* qmqttmessage.cpp in Compile Sources */,
872089172CE65451008C2C17 /* qmqtttopicname.cpp in Compile Sources */,
872089182CE65451008C2C17 /* qmqttpublishproperties.cpp in Compile Sources */,
872089192CE65451008C2C17 /* qmqttauthenticationproperties.cpp in Compile Sources */,
87FE06842D170D5600CDAAF6 /* trxappgateusbrower.cpp in Compile Sources */,
87BCE6BD29F28F72001F70EB /* ypooelliptical.cpp in Compile Sources */,
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 */,
@@ -3500,6 +3671,7 @@
7CF08714869DA569C2EA551C /* keepawakehelper.cpp in Compile Sources */,
87EB918727EE5FE7002535E1 /* moc_nautilusbike.cpp in Compile Sources */,
873824B827E64707004F1B46 /* moc_cache.cpp in Compile Sources */,
87873AEE2D09A8AA005F86B4 /* moc_sportsplusrower.cpp in Compile Sources */,
873824E427E647A8004F1B46 /* cache.cpp in Compile Sources */,
87A2E0222B2B053E00E6168F /* swiftDebug.mm in Compile Sources */,
87310B1E266FBB59008BA0D6 /* smartrowrower.cpp in Compile Sources */,
@@ -3552,6 +3724,7 @@
0317752B0C295CAB82D37E45 /* virtualtreadmill.cpp in Compile Sources */,
8742C2B427C92C48007D3FA0 /* moc_wahookickrsnapbike.cpp in Compile Sources */,
878531692711A3EC004B153D /* moc_fakebike.cpp in Compile Sources */,
870A5DB32CEF8FB100839641 /* moc_technogymbike.cpp in Compile Sources */,
873824B027E64706004F1B46 /* moc_cache_p.cpp in Compile Sources */,
87EB918627EE5FE7002535E1 /* moc_inapppurchasebackend.cpp in Compile Sources */,
87097D31275EA9AF0020EE6F /* moc_sportsplusbike.cpp in Compile Sources */,
@@ -3562,6 +3735,12 @@
87A0C4BB262329A600121A76 /* npecablebike.cpp in Compile Sources */,
873CD22D27EF8E4B000131BC /* iosinapppurchaseproduct.mm in Compile Sources */,
873CD20727EF8D8A000131BC /* inappproduct.cpp in Compile Sources */,
872088EB2CE6543C008C2C17 /* moc_mqttpublisher.cpp in Compile Sources */,
872088EC2CE6543C008C2C17 /* moc_qmqttclient.cpp in Compile Sources */,
875CA94C2D130F8100667EE6 /* moc_osc.cpp in Compile Sources */,
872088ED2CE6543C008C2C17 /* moc_qmqttmessage.cpp in Compile Sources */,
872088EE2CE6543C008C2C17 /* moc_qmqttsubscription.cpp in Compile Sources */,
872088EF2CE6543C008C2C17 /* moc_qmqttconnection_p.cpp in Compile Sources */,
87A3BC26265642A300D302E3 /* moc_rower.cpp in Compile Sources */,
87EFE45B27A51901006EA1C3 /* moc_nautiluselliptical.cpp in Compile Sources */,
872DCC392A18D4A800EC9F68 /* virtualdevice.cpp in Compile Sources */,
@@ -3586,9 +3765,11 @@
873824AE27E64706004F1B46 /* moc_browser.cpp in Compile Sources */,
8738249727E646E3004F1B46 /* characteristicnotifier2a53.cpp in Compile Sources */,
DF373364C5474D877506CB26 /* FitMesg.mm in Compile Sources */,
87FE06812D170D3C00CDAAF6 /* moc_trxappgateusbrower.cpp in Compile Sources */,
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 */,
@@ -3616,6 +3797,7 @@
8768C9062BBC12B80099DBE1 /* socket_local_client.c in Compile Sources */,
8727C7D42B3BF1E4005429EB /* moc_QTelnet.cpp in Compile Sources */,
C3D1FD2587BF6F15B58BA675 /* moc_bluetooth.cpp in Compile Sources */,
8720891B2CE6567D008C2C17 /* mqttpublisher.cpp in Compile Sources */,
87062648259480B700D06586 /* WorkoutTracking.swift in Compile Sources */,
8C3422A825EF7ECD78951307 /* moc_bluetoothdevice.cpp in Compile Sources */,
8785D5432B3DD105005A2EB7 /* moc_PlayerStateWrapper.cpp in Compile Sources */,
@@ -3639,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 */,
@@ -3648,6 +3831,7 @@
8762D5102601F7EA00F6F049 /* M3iNSQT.cpp in Compile Sources */,
872261EE289EA873006A6F75 /* nordictrackelliptical.cpp in Compile Sources */,
8718CBA3263063BD004BF4EE /* templateinfosender.cpp in Compile Sources */,
87EAC3D62D1D8D34004FE975 /* pitpatbike.cpp in Compile Sources */,
E8B499F921FB0AB55C7A8A8B /* moc_gpx.cpp in Compile Sources */,
87E6A85825B5C88E00371D28 /* moc_flywheelbike.cpp in Compile Sources */,
8754D24C27F786F0003D7054 /* virtualrower.swift in Compile Sources */,
@@ -3660,6 +3844,7 @@
879E5AA8289C057E00FEA38A /* proformwifitreadmill.cpp in Compile Sources */,
873824B227E64706004F1B46 /* moc_hostname.cpp in Compile Sources */,
873824EB27E647A8004F1B46 /* prober.cpp in Compile Sources */,
875CA9462D0C740000667EE6 /* moc_kineticinroadbike.cpp in Compile Sources */,
8767EF1E29448D6700810C0F /* characteristicwriteprocessor.cpp in Compile Sources */,
87B617F425F260150094A1CB /* moc_screencapture.cpp in Compile Sources */,
87B617ED25F25FED0094A1CB /* fitshowtreadmill.cpp in Compile Sources */,
@@ -3672,10 +3857,13 @@
8727C7D12B3BF1B8005429EB /* QTelnet.cpp in Compile Sources */,
8775008329E876F8008E48B7 /* iconceptelliptical.cpp in Compile Sources */,
87B187BD29B8C577007EEF9D /* moc_ziprotreadmill.cpp in Compile Sources */,
875CA9492D0C742500667EE6 /* kineticinroadbike.cpp in Compile Sources */,
877FBA2B276E684E00F6C0C9 /* moc_bowflextreadmill.cpp in Compile Sources */,
874D272229AFA13B0007C079 /* moc_apexbike.cpp in Compile Sources */,
8738248127E646C1004F1B46 /* characteristicnotifier2ad2.cpp in Compile Sources */,
87A6825A2CE3AB3100586A2A /* moc_sramAXSController.cpp in Compile Sources */,
87A0771029B641D600A368BF /* wahookickrheadwind.cpp in Compile Sources */,
87EAC3D32D1D8D1D004FE975 /* moc_pitpatbike.cpp in Compile Sources */,
8791A8AB25C861BD003B50B2 /* inspirebike.cpp in Compile Sources */,
876BFC9D27BE35C5001D7645 /* bowflext216treadmill.cpp in Compile Sources */,
871235C126B297720012D0F2 /* moc_kingsmithr1protreadmill.cpp in Compile Sources */,
@@ -3714,6 +3902,8 @@
87EB918827EE5FE7002535E1 /* moc_inappstoreqmltype.cpp in Compile Sources */,
87083D9626678EFA0072410D /* zwiftworkout.cpp in Compile Sources */,
87C5F0B826285E5F0067A1B5 /* stagesbike.cpp in Compile Sources */,
87FC40BC2D2E74F9008BA736 /* cycleopsphantombike.cpp in Compile Sources */,
87FC40BD2D2E74F9008BA736 /* moc_cycleopsphantombike.cpp in Compile Sources */,
87C5F0D526285E7E0067A1B5 /* moc_mimehtml.cpp in Compile Sources */,
871B9FD4265E6A9A00DB41F4 /* moc_powerzonepack.cpp in Compile Sources */,
87A0C4BF262329B500121A76 /* moc_cscbike.cpp in Compile Sources */,
@@ -3721,6 +3911,8 @@
8790FDE1277B0AC600247550 /* moc_nautilustreadmill.cpp in Compile Sources */,
8727A47927849EB200019B5D /* moc_paferstreadmill.cpp in Compile Sources */,
8783153B25E8D81E0007817C /* moc_sportstechbike.cpp in Compile Sources */,
87A3DD9B2D3413790060BAEB /* moc_lifespantreadmill.cpp in Compile Sources */,
87A3DD9C2D3413790060BAEB /* lifespantreadmill.cpp in Compile Sources */,
875F69BB26342E9A0009FD78 /* moc_spirittreadmill.cpp in Compile Sources */,
8768C8BF2BBC11C80099DBE1 /* transport.c in Compile Sources */,
);
@@ -3740,11 +3932,6 @@
target = 876E4E102594747F00BD5714 /* watchkit */;
targetProxy = 876E4E302594748100BD5714 /* PBXContainerItemProxy */;
};
F8E0E95C27758CAC67EF1CD4 /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
target = E9F0AFC024A6F2D65CE84E08 /* Qt Preprocess */;
targetProxy = 876E4E0E2594739400BD5714 /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -4068,7 +4255,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 919;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4101,6 +4288,9 @@
../../Qt/5.15.2/ios/include/QtMultimedia,
../src/devices,
../src/ios/adb/include,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -4259,7 +4449,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 919;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4294,6 +4484,9 @@
../../Qt/5.15.2/ios/include/QtMultimedia,
../src/devices,
../src/ios/adb/include,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/private,
../../Qt/5.15.2/ios/include/QtCore/5.15.2,
../../Qt/5.15.2/ios/include/QtCore/5.15.2/QtCore/,
);
LIBRARY_SEARCH_PATHS = (
/Users/cagnulein/Qt/5.15.2/ios/plugins/platforms,
@@ -4486,7 +4679,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 919;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4582,7 +4775,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 919;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4674,7 +4867,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 919;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4739,6 +4932,8 @@
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_VERSION = 5.0;
@@ -4788,7 +4983,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 919;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4849,6 +5044,8 @@
PRODUCT_NAME = "${TARGET_NAME}";
SDKROOT = watchos;
SKIP_INSTALL = YES;
STRIP_INSTALLED_PRODUCT = NO;
STRIP_SWIFT_SYMBOLS = NO;
SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_VERSION = 5.0;

View File

@@ -107,6 +107,7 @@ extension MainController: WorkoutTrackingDelegate {
WorkoutTracking.speed = WatchKitConnection.speed
WorkoutTracking.power = WatchKitConnection.power
WorkoutTracking.cadence = WatchKitConnection.cadence
WorkoutTracking.steps = WatchKitConnection.steps
if Locale.current.measurementSystem != "Metric" {
self.distanceLabel.setText("Distance \(String(format:"%.2f", WorkoutTracking.distance))")

View File

@@ -27,6 +27,7 @@ class WatchKitConnection: NSObject {
public static var speed = 0.0
public static var cadence = 0.0
public static var power = 0.0
public static var steps = 0
weak var delegate: WatchKitConnectionDelegate?
private override init() {
@@ -76,6 +77,10 @@ extension WatchKitConnection: WatchKitConnectionProtocol {
WatchKitConnection.power = dPower
let dCadence = Double(result["cadence"] as! Double)
WatchKitConnection.cadence = dCadence
if let stepsDouble = result["steps"] as? Double {
let iSteps = Int(stepsDouble)
WatchKitConnection.steps = iSteps
}
}, errorHandler: { (error) in
print(error)
})

View File

@@ -33,6 +33,7 @@ class WorkoutTracking: NSObject {
public static var cadenceSteps = 0
public static var speed = Double()
public static var power = Double()
public static var steps = Int()
public static var cadence = Double()
public static var lastDateMetric = Date()
var sport: Int = 0
@@ -269,6 +270,43 @@ extension WorkoutTracking: WorkoutTrackingProtocol {
}
} else {
// Guard to check if steps quantity type is available
guard let quantityTypeSteps = HKQuantityType.quantityType(
forIdentifier: .stepCount) else {
return
}
let stepsQuantity = HKQuantity(unit: HKUnit.count(), doubleValue: Double(WorkoutTracking.steps))
// Create a sample for total steps
let sampleSteps = HKCumulativeQuantitySeriesSample(
type: quantityTypeSteps,
quantity: stepsQuantity, // Use your steps quantity here
start: workoutSession.startDate!,
end: Date())
// Add the steps sample to workout builder
workoutBuilder.add([sampleSteps]) { (success, error) in
if let error = error {
print(error)
}
// End the data collection
self.workoutBuilder.endCollection(withEnd: Date()) { (success, error) in
if let error = error {
print(error)
}
// Finish the workout and save total steps
self.workoutBuilder.finishWorkout { (workout, error) in
if let error = error {
print(error)
}
workout?.setValue(stepsQuantity, forKey: "totalSteps")
}
}
}
guard let quantityTypeDistance = HKQuantityType.quantityType(
forIdentifier: .distanceWalkingRunning) else {
return
@@ -402,7 +440,7 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
// Fallback on earlier versions
}
} else if(sport == 1) {
if #available(watchOSApplicationExtension 10.0, *) {
if #available(watchOSApplicationExtension 10.0, *) {
let wattPerInterval = HKQuantity(unit: HKUnit.watt(),
doubleValue: WorkoutTracking.power)
@@ -445,7 +483,7 @@ extension WorkoutTracking: HKLiveWorkoutBuilderDelegate {
// Fallback on earlier versions
}
} else if(sport == 2) {
if #available(watchOSApplicationExtension 10.0, *) {
if #available(watchOSApplicationExtension 10.0, *) {
let speedPerInterval = HKQuantity(unit: HKUnit.meter().unitDivided(by: HKUnit.second()),
doubleValue: WorkoutTracking.speed * 0.277778)

View File

@@ -0,0 +1,96 @@
# Define build image
FROM ubuntu:latest AS build
# Install essential build dependencies
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y \
&& apt install --no-install-recommends -y \
git \
ca-certificates \
qtquickcontrols2-5-dev \
qtconnectivity5-dev \
qtbase5-private-dev \
qtpositioning5-dev \
libqt5charts5-dev \
libqt5networkauth5-dev \
libqt5websockets5-dev \
qml-module* \
libqt5texttospeech5-dev \
qtlocation5-dev \
qtmultimedia5-dev \
g++ \
make \
wget \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Define runtime image
FROM ubuntu:latest AS runtime
# Install essential runtime dependencies
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y \
&& apt install --no-install-recommends -y \
libqt5bluetooth5 \
libqt5widgets5 \
libqt5positioning5 \
libqt5xml5 \
libqt5charts5 \
qt5-assistant \
libqt5networkauth5 \
libqt5websockets5 \
qml-module* \
libqt5texttospeech5 \
libqt5location5-plugins \
libqt5multimediawidgets5 \
libqt5multimedia5-plugins \
libqt5multimedia5 \
qml-module-qtquick-controls2 \
libqt5location5 \
bluez \
dbus \
tigervnc-standalone-server \
tigervnc-tools \
libgl1-mesa-dri \
xfonts-base \
x11-xserver-utils \
tigervnc-common \
net-tools \
&& rm -rf /var/lib/apt/lists/*
# Stage 1: Build
FROM build AS builder
# Clone the project and build it
WORKDIR /usr/local/src
RUN git clone --recursive https://github.com/cagnulein/qdomyos-zwift.git
WORKDIR /usr/local/src/qdomyos-zwift
RUN git submodule update --init src/smtpclient/ \
&& git submodule update --init src/qmdnsengine/ \
&& git submodule update --init tst/googletest/
WORKDIR /usr/local/src/qdomyos-zwift/src
RUN qmake qdomyos-zwift.pro \
&& make -j4
# Stage 2: Runtime
FROM runtime
# Copy the built binary to /usr/local/bin
COPY --from=builder /usr/local/src/qdomyos-zwift/src/qdomyos-zwift /usr/local/bin/qdomyos-zwift
# VNC configuration
RUN mkdir -p ~/.vnc && \
echo "securepassword" | vncpasswd -f > ~/.vnc/passwd && \
chmod 600 ~/.vnc/passwd
# .Xauthority configuration
RUN touch /root/.Xauthority
ENV DISPLAY=:99
# Start VNC server with authentication
CMD vncserver :99 -depth 24 -localhost no -xstartup qdomyos-zwift && \
sleep infinity

2
docker/linux_gui_vnc/build.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
docker build -t qdomyos-zwift-vnc .

View File

@@ -0,0 +1,10 @@
services:
qdomyos-zwift-vnc:
image: qdomyos-zwift-vnc
container_name: qdomyos-zwift-vnc
privileged: true # Required for Bluetooth functionality
network_mode: "host" # Used to access host Bluetooth and D-Bus
volumes:
- /dev:/dev # Forward host devices (for Bluetooth)
- /run/dbus:/run/dbus # Forward D-Bus for Bluetooth interaction
restart: "no" # Do not restart the container automatically

View File

@@ -0,0 +1,95 @@
# Define build image
FROM ubuntu:latest AS build
# Install essential build dependencies
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y \
&& apt install --no-install-recommends -y \
git \
ca-certificates \
qtquickcontrols2-5-dev \
qtconnectivity5-dev \
qtbase5-private-dev \
qtpositioning5-dev \
libqt5charts5-dev \
libqt5networkauth5-dev \
libqt5websockets5-dev \
qml-module* \
libqt5texttospeech5-dev \
qtlocation5-dev \
qtmultimedia5-dev \
g++ \
make \
wget \
unzip \
&& rm -rf /var/lib/apt/lists/*
# Define runtime image
FROM ubuntu:latest AS runtime
# Install essential runtime dependencies
ARG DEBIAN_FRONTEND=noninteractive
RUN apt update && apt upgrade -y \
&& apt install --no-install-recommends -y \
libqt5bluetooth5 \
libqt5widgets5 \
libqt5positioning5 \
libqt5xml5 \
libqt5charts5 \
qt5-assistant \
libqt5networkauth5 \
libqt5websockets5 \
qml-module* \
libqt5texttospeech5 \
libqt5location5-plugins \
libqt5multimediawidgets5 \
libqt5multimedia5-plugins \
libqt5multimedia5 \
qml-module-qtquick-controls2 \
libqt5location5 \
bluez \
dbus \
&& rm -rf /var/lib/apt/lists/*
# Stage 1: Build
FROM build AS builder
# Define variables for Qt versions
ARG QT_VERSION=5.15
ARG QT_SUBVERSION=5.15.13
ARG QT_WEBPLUGIN_NAME=qtwebglplugin-everywhere-opensource-src
# Build WebGL plugin
WORKDIR /usr/local/src
RUN wget https://download.qt.io/official_releases/qt/${QT_VERSION}/${QT_SUBVERSION}/submodules/${QT_WEBPLUGIN_NAME}-${QT_SUBVERSION}.zip \
&& unzip ${QT_WEBPLUGIN_NAME}-${QT_SUBVERSION}.zip \
&& mv *-${QT_SUBVERSION} qtwebglplugin-everywhere \
&& cd qtwebglplugin-everywhere \
&& qmake \
&& make
# Clone the project and build it
WORKDIR /usr/local/src
RUN git clone --recursive https://github.com/cagnulein/qdomyos-zwift.git
WORKDIR /usr/local/src/qdomyos-zwift
RUN git submodule update --init src/smtpclient/ \
&& git submodule update --init src/qmdnsengine/ \
&& git submodule update --init tst/googletest/
WORKDIR /usr/local/src/qdomyos-zwift/src
RUN qmake qdomyos-zwift.pro \
&& make -j4
# Stage 2: Runtime
FROM runtime
# Copy the built binary to /usr/local/bin
COPY --from=builder /usr/local/src/qdomyos-zwift/src/qdomyos-zwift /usr/local/bin/qdomyos-zwift
# Copy WebGL plugin to the appropriate location
COPY --from=builder /usr/local/src/qtwebglplugin-everywhere/plugins/platforms/libqwebgl.so /usr/lib/x86_64-linux-gnu/qt5/plugins/platforms/libqwebgl.so
# Set the default command to run the application with WebGL
CMD ["qdomyos-zwift", "-qml", "-platform", "webgl:port=8080"]

2
docker/linux_webgl/build.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/bash
docker build -t qdomyos-zwift-webgl .

View File

@@ -0,0 +1,19 @@
services:
qdomyos-zwift-webgl:
image: qdomyos-zwift-webgl
container_name: qdomyos-zwift-webgl
privileged: true
network_mode: "host"
environment:
- DISPLAY=${DISPLAY}
volumes:
- /dev:/dev
- /run/dbus:/run/dbus
- ./.config:/root/.config
- /tmp/.X11-unix:/tmp/.X11-unix
stdin_open: true
tty: true
restart: "no"
# command: qdomyos-zwift -qml -platform webgl:port=8080
# command: ["qdomyos-zwift", "-no-gui"]

View File

@@ -10,7 +10,7 @@ These instructions build the app itself, not the test project.
```buildoutcfg
$ sudo apt update && sudo apt upgrade # this is very important on raspberry pi: you need the bluetooth firmware updated!
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
$ sudo apt install git qtquickcontrols2-5-dev libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qml-module* libqt5texttospeech5-dev libqt5texttospeech5 libqt5location5-plugins qtlocation5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 g++ make
$ git clone https://github.com/cagnulein/qdomyos-zwift.git
$ cd qdomyos-zwift
$ git submodule update --init src/smtpclient/
@@ -106,7 +106,7 @@ This operation takes a moment to complete.
#### Install qdomyos-zwift from sources
```bash
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
sudo apt install git libqt5bluetooth5 libqt5widgets5 libqt5positioning5 libqt5xml5 qtconnectivity5-dev qtbase5-private-dev qtpositioning5-dev libqt5charts5-dev libqt5charts5 qt5-assistant libqt5networkauth5-dev libqt5websockets5-dev qtmultimedia5-dev libqt5multimediawidgets5 libqt5multimedia5-plugins libqt5multimedia5 qtlocation5-dev qtquickcontrols2-5-dev libqt5texttospeech5-dev libqt5texttospeech5 g++ make
git clone https://github.com/cagnulein/qdomyos-zwift.git
cd qdomyos-zwift
git submodule update --init src/smtpclient/
@@ -117,6 +117,11 @@ qmake qdomyos-zwift.pro
make
```
If you need GUI also do a
```
apt install qml-module*
```
Please note :
- Don't build the application with `-j4` option (this will fail)
- Build operation is circa 45 minutes (subsequent builds are faster)
@@ -172,6 +177,151 @@ If everything is working as expected, **enable your service at boot time** :
Then reboot to check operations (`sudo reboot`)
### (optional) Treadmill Auto-Detection and Service Management
This section provides a reliable way to manage the QZ service based on the treadmill's power state. Using a `bluetoothctl`-based Bash script, this solution ensures the QZ service starts when the treadmill is detected and stops when it is not.
- **Bluetooth Discovery**: Monitors treadmill availability via `bluetoothctl`.
- **Service Control**: Automatically starts and stops the QZ service.
- **Logging**: Tracks treadmill status and actions in a log file.
**Notes:**
- Ensure `bluetoothctl` is installed and working on your system.
- Replace `I_TL` in the script with your treadmill's Bluetooth name. You can find your device name via `bluetoothctl scan on`
- Adjust the sleep interval (`sleep 30`) in the script as needed for your use case.
Step 1: Save the following script as `/root/qz-treadmill-monitor.sh`:
```bash
#!/bin/bash
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"
}
is_service_running() {
systemctl is-active --quiet "$SERVICE_NAME"
return $?
}
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=$!
# Allow some time for devices to appear
sleep 5
# Check if the target device appears in the list
bluetoothctl devices | grep -q "$TARGET_DEVICE"
DEVICE_FOUND=$?
# Stop scanning
kill "$SCAN_PID"
bluetoothctl scan off &>/dev/null
if [ $DEVICE_FOUND -eq 0 ]; then
log "Device '$TARGET_DEVICE' found."
return 0
else
log "Device '$TARGET_DEVICE' not found."
return 1
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... *****"
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... *****"
systemctl stop "$SERVICE_NAME"
else
log "QZ service is already stopped."
fi
fi
}
while true; do
log "Checking for treadmill status..."
if scan_for_device; then
manage_service true
else
manage_service false
fi
log "Waiting for $SCAN_INTERVAL seconds before next check..."
sleep "$SCAN_INTERVAL"
done
```
Step2: To ensure the script runs continuously, create a systemd service file at `/etc/systemd/system/qz-treadmill-monitor.service`
```bash
[Unit]
Description=QZ Treadmill Monitor Service
After=bluetooth.service
[Service]
Type=simple
ExecStart=/root/qz-treadmill-monitor.sh
Restart=always
RestartSec=10
User=root
[Install]
WantedBy=multi-user.target
```
Step 3: Enable and Start the Service
```bash
sudo systemctl daemon-reload
sudo systemctl enable qz-treadmill-monitor
sudo systemctl start qz-treadmill-monitor
```
Monitor logs are written to `/var/log/qz-treadmill-monitor.log`. Use the following command to check logs in real-time:
```bash
sudo tail -f /var/log/qz-treadmill-monitor.log
```
### (optional) Enable overlay FS

View File

@@ -0,0 +1,37 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"files.associations": {
"list": "cpp",
"chrono": "cpp",
"complex": "cpp",
"functional": "cpp",
"optional": "cpp",
"system_error": "cpp",
"type_traits": "cpp",
"xlocnum": "cpp",
"xtr1common": "cpp",
"qhttpserver": "cpp",
"array": "cpp",
"deque": "cpp",
"map": "cpp",
"unordered_map": "cpp",
"vector": "cpp",
"xstring": "cpp",
"algorithm": "cpp",
"xutility": "cpp",
"xlocale": "cpp",
"filesystem": "cpp",
"bitset": "cpp",
"iterator": "cpp",
"xhash": "cpp",
"xtree": "cpp",
"ostream": "cpp",
"locale": "cpp"
}
}
}

View File

@@ -10,59 +10,87 @@ ColumnLayout {
property alias textFont: accordionText.font.family
property alias textFontSize: accordionText.font.pixelSize
property alias indicatRectColor: indicatRect.color
default property alias accordionContent: contentPlaceholder.data
spacing: 0
default property alias accordionContent: contentLoader.sourceComponent
Layout.fillWidth: true;
// Signal emitted when content becomes visible
signal contentBecameVisible()
spacing: 0
Layout.fillWidth: true
Rectangle {
id: accordionHeader
color: "red"
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true;
Layout.fillWidth: true
height: 48
Rectangle{
id:indicatRect
x: 16; y: 20
width: 8; height: 8
radius: 8
color: "white"
Rectangle {
id: indicatRect
x: 16; y: 20
width: 8; height: 8
radius: 8
color: "white"
}
Text {
id: accordionText
x:34;y:13
x: 34; y: 13
color: "#FFFFFF"
text: rootElement.title
}
Image {
y:13
anchors.right: parent.right
y: 13
anchors.right: parent.right
anchors.rightMargin: 20
width: 30; height: 30
id: indicatImg
source: "qrc:/icons/arrow-collapse-vertical.png"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
rootElement.isOpen = !rootElement.isOpen
if(rootElement.isOpen)
{
if(rootElement.isOpen) {
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
}else{
} else {
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
}
}
}
}
// This will get filled with the content
ColumnLayout {
id: contentPlaceholder
visible: rootElement.isOpen
Layout.fillWidth: true;
// Loader with enhanced visibility handling
Loader {
id: contentLoader
active: rootElement.isOpen
visible: false // Start invisible
Layout.fillWidth: true
asynchronous: false
onLoaded: {
if (item) {
item.Layout.fillWidth = true
visible = true
rootElement.contentBecameVisible()
}
}
// Handle visibility changes
onVisibleChanged: {
if (visible && status === Loader.Ready) {
rootElement.contentBecameVisible()
}
}
}
// Handle accordion closing
onIsOpenChanged: {
if (!isOpen) {
contentLoader.visible = false
}
}
}

View File

@@ -1,61 +0,0 @@
#ifndef CRC16IBM_H
#define CRC16IBM_H
#include <QByteArray>
#include <QDebug>
class CRC16IBM {
public:
// Function to calculate CRC-16 (XMODEM) checksum
static quint16 calculateCRC(const QByteArray &data) {
quint16 crc = 0xFFFF; // Initial value
for (char byte : data) {
quint8 index = (crc >> 8) ^ static_cast<quint8>(byte);
crc = (crc << 8) ^ crc16Table[index];
}
return crc;
}
private:
// Precomputed CRC-16-IBM table
static constexpr quint16 crc16Table[256] = {
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7,
0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF,
0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6,
0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE,
0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485,
0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D,
0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4,
0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC,
0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823,
0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B,
0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12,
0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A,
0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41,
0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49,
0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70,
0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78,
0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F,
0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067,
0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E,
0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256,
0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D,
0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C,
0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634,
0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB,
0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3,
0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A,
0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92,
0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9,
0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1,
0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8,
0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0
};
};
#endif // CRC16IBM_H

View File

@@ -33,6 +33,7 @@ HomeForm {
property bool theme_tile_shadow_enabled: true
property string theme_tile_shadow_color: "#9C27B0"
property int theme_tile_secondline_textsize: 12
property bool skipLocationServicesDialog: false
}
MessageDialog {
@@ -86,14 +87,31 @@ HomeForm {
onTriggered: {if(rootItem.stopRequested) {rootItem.stopRequested = false; inner_stop(); }}
}
property var locationServiceRequsted: false
property bool locationServiceRequsted: false
MessageDialog {
id: locationServicesDialog
text: "Permissions Required"
informativeText: "QZ requires both Bluetooth and Location Services to be enabled.\nLocation Services are necessary on Android to allow the app to find Bluetooth devices.\nThe GPS will not be used.\n\nWould you like to enable them?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: {locationServiceRequsted = true; rootItem.enableLocationServices()}
visible: !rootItem.locationServices() && !locationServiceRequsted
onYesClicked: {
locationServiceRequsted = true
rootItem.enableLocationServices()
}
onNoClicked: remindLocationServicesDialog.visible = true
visible: !rootItem.locationServices() && !locationServiceRequsted && !settings.skipLocationServicesDialog
}
MessageDialog {
id: remindLocationServicesDialog
text: "Reminder Preference"
informativeText: "Would you like to be reminded about enabling Location Services next time?"
buttons: (MessageDialog.Yes | MessageDialog.No)
onYesClicked: settings.skipLocationServicesDialog = false
onNoClicked: settings.skipLocationServicesDialog = true
visible: false
}
MessageDialog {
text: "Restart the app"
informativeText: "To apply the changes, you need to restart the app.\nWould you like to do that now?"

View File

@@ -0,0 +1,20 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
import QtQuick.Controls 2.15
import QtQuick.Controls.Material 2.0
import Qt.labs.settings 1.0
import QtQuick.Dialogs 1.0
SwitchDelegate {
id: root
MouseArea {
anchors.fill: parent
onClicked: {
if (mouse.x > parent.width - parent.indicator.width) {
root.checked = !root.checked
root.clicked()
}
}
}
}

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

View File

@@ -22,7 +22,7 @@ ColumnLayout {
}
}
AccordionElement {
StaticAccordionElement {
title: qsTr("Settings folder")
indicatRectColor: Material.color(Material.Grey)
textColor: Material.color(Material.Grey)

View File

@@ -0,0 +1,68 @@
import QtQuick 2.7
import QtQuick.Layouts 1.3
ColumnLayout {
id: rootElement
property bool isOpen: false
property string title: ""
property alias color: accordionHeader.color
property alias textColor: accordionText.color
property alias textFont: accordionText.font.family
property alias textFontSize: accordionText.font.pixelSize
property alias indicatRectColor: indicatRect.color
default property alias accordionContent: contentPlaceholder.data
spacing: 0
Layout.fillWidth: true;
Rectangle {
id: accordionHeader
color: "red"
Layout.alignment: Qt.AlignTop
Layout.fillWidth: true;
height: 48
Rectangle{
id:indicatRect
x: 16; y: 20
width: 8; height: 8
radius: 8
color: "white"
}
Text {
id: accordionText
x:34;y:13
color: "#FFFFFF"
text: rootElement.title
}
Image {
y:13
anchors.right: parent.right
anchors.rightMargin: 20
width: 30; height: 30
id: indicatImg
source: "qrc:/icons/arrow-collapse-vertical.png"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
rootElement.isOpen = !rootElement.isOpen
if(rootElement.isOpen)
{
indicatImg.source = "qrc:/icons/arrow-expand-vertical.png"
}else{
indicatImg.source = "qrc:/icons/arrow-collapse-vertical.png"
}
}
}
}
// This will get filled with the content
ColumnLayout {
id: contentPlaceholder
visible: rootElement.isOpen
Layout.fillWidth: true;
}
}

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.1" android:versionCode="908" 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,7 +31,7 @@ dependencies {
implementation "androidx.core:core-ktx:1.12.0"
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.1.0"
implementation 'com.google.protobuf:protobuf-javalite:3.25.1'
if(amazon == "1") {
// amazon app store
implementation 'com.google.mlkit:text-recognition:16.0.0-beta6'
@@ -50,7 +50,7 @@ dependencies {
implementation "androidx.appcompat:appcompat:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$appcompat_version"
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.github.mik3y:usb-serial-for-android:v3.4.6'
implementation files('libs/usb-serial-for-android-3.8.1.aar')
androidTestImplementation "com.android.support:support-annotations:28.0.0"
implementation 'com.google.android.gms:play-services-wearable:+'

Binary file not shown.

View File

@@ -31,7 +31,7 @@ public class Ant {
private ChannelService.ChannelServiceComm mChannelService = null;
private boolean mChannelServiceBound = false;
private final String TAG = "Ant";
private Activity activity = null;
public static Activity activity = null;
static boolean speedRequest = false;
static boolean heartRequest = false;
static boolean garminKey = false;

View File

@@ -155,7 +155,7 @@ public class ChannelService extends Service {
public void openAllChannels() throws ChannelNotAvailableException {
if (Ant.heartRequest && heartChannelController == null)
heartChannelController = new HeartChannelController(acquireChannel());
heartChannelController = new HeartChannelController();
if (Ant.speedRequest) {
if(Ant.treadmill && sdmChannelController == null) {

View File

@@ -1,23 +1,21 @@
/*
* Copyright 2012 Dynastream Innovations Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package org.cagnulen.qdomyoszwift;
import android.os.RemoteException;
import android.content.Context;
import android.util.Log;
import android.app.Activity;
// ANT+ Plugin imports
import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc;
import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.DataState;
import com.dsi.ant.plugins.antplus.pcc.AntPlusHeartRatePcc.IHeartRateDataReceiver;
import com.dsi.ant.plugins.antplus.pcc.defines.DeviceState;
import com.dsi.ant.plugins.antplus.pcc.defines.EventFlag;
import com.dsi.ant.plugins.antplus.pcc.defines.RequestAccessResult;
import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IDeviceStateChangeReceiver;
import com.dsi.ant.plugins.antplus.pccbase.AntPluginPcc.IPluginAccessResultReceiver;
import com.dsi.ant.plugins.antplus.pccbase.PccReleaseHandle;
// Basic ANT imports for legacy support
import com.dsi.ant.channel.AntChannel;
import com.dsi.ant.channel.AntCommandFailedException;
import com.dsi.ant.channel.IAntChannelEventHandler;
@@ -30,220 +28,103 @@ import com.dsi.ant.message.fromant.ChannelEventMessage;
import com.dsi.ant.message.fromant.MessageFromAntType;
import com.dsi.ant.message.ipc.AntMessageParcel;
// Java imports
import java.math.BigDecimal;
import java.util.EnumSet;
import java.util.Random;
public class HeartChannelController {
// The device type and transmission type to be part of the channel ID message
private static final int CHANNEL_HEART_DEVICE_TYPE = 0x78;
private static final int CHANNEL_HEART_TRANSMISSION_TYPE = 1;
private static final String TAG = HeartChannelController.class.getSimpleName();
private Context context;
private AntPlusHeartRatePcc hrPcc = null;
private PccReleaseHandle<AntPlusHeartRatePcc> releaseHandle = null;
private boolean isConnected = false;
public int heart = 0; // Public to be accessible from ChannelService
// The period and frequency values the channel will be configured to
private static final int CHANNEL_HEART_PERIOD = 8118; // 1 Hz
private static final int CHANNEL_HEART_FREQUENCY = 57;
public HeartChannelController() {
this.context = Ant.activity;
openChannel();
}
private static final String TAG = HeartChannelController.class.getSimpleName();
public boolean openChannel() {
// Request access to first available heart rate device
releaseHandle = AntPlusHeartRatePcc.requestAccess((Activity)context, 0, 0, // 0 means first available device
new IPluginAccessResultReceiver<AntPlusHeartRatePcc>() {
@Override
public void onResultReceived(AntPlusHeartRatePcc result, RequestAccessResult resultCode, DeviceState initialDeviceState) {
switch(resultCode) {
case SUCCESS:
hrPcc = result;
isConnected = true;
Log.d(TAG, "Connected to heart rate monitor: " + result.getDeviceName());
subscribeToHrEvents();
break;
case CHANNEL_NOT_AVAILABLE:
Log.e(TAG, "Channel Not Available");
break;
case ADAPTER_NOT_DETECTED:
Log.e(TAG, "ANT Adapter Not Available");
break;
case BAD_PARAMS:
Log.e(TAG, "Bad request parameters");
break;
case OTHER_FAILURE:
Log.e(TAG, "RequestAccess failed");
break;
case DEPENDENCY_NOT_INSTALLED:
Log.e(TAG, "Dependency not installed");
break;
default:
Log.e(TAG, "Unrecognized result: " + resultCode);
break;
}
}
},
new IDeviceStateChangeReceiver() {
@Override
public void onDeviceStateChange(DeviceState newDeviceState) {
Log.d(TAG, "Device State Changed to: " + newDeviceState);
if (newDeviceState == DeviceState.DEAD) {
isConnected = false;
}
}
}
);
private static Random randGen = new Random();
return isConnected;
}
private AntChannel mAntChannel;
private void subscribeToHrEvents() {
if (hrPcc != null) {
hrPcc.subscribeHeartRateDataEvent(new IHeartRateDataReceiver() {
@Override
public void onNewHeartRateData(long estTimestamp, EnumSet<EventFlag> eventFlags,
int computedHeartRate, long heartBeatCount,
BigDecimal heartBeatEventTime, DataState dataState) {
heart = computedHeartRate;
Log.d(TAG, "Heart Rate: " + heart);
}
});
}
}
private ChannelEventCallback mChannelEventCallback = new ChannelEventCallback();
public void close() {
if (releaseHandle != null) {
releaseHandle.close();
releaseHandle = null;
}
hrPcc = null;
isConnected = false;
Log.d(TAG, "Channel Closed");
}
public int getHeartRate() {
return heart;
}
private boolean mIsOpen;
int heart = 0;
public HeartChannelController(AntChannel antChannel) {
mAntChannel = antChannel;
openChannel();
}
boolean openChannel() {
if (null != mAntChannel) {
if (mIsOpen) {
Log.w(TAG, "Channel was already open");
} else {
// Channel ID message contains device number, type and transmission type. In
// order for master (TX) channels and slave (RX) channels to connect, they
// must have the same channel ID, or wildcard (0) is used.
ChannelId channelId = new ChannelId(0,
CHANNEL_HEART_DEVICE_TYPE, CHANNEL_HEART_TRANSMISSION_TYPE);
try {
// Setting the channel event handler so that we can receive messages from ANT
mAntChannel.setChannelEventHandler(mChannelEventCallback);
// Performs channel assignment by assigning the type to the channel. Additional
// features (such as, background scanning and frequency agility) can be enabled
// by passing an ExtendedAssignment object to assign(ChannelType, ExtendedAssignment).
mAntChannel.assign(ChannelType.SLAVE_RECEIVE_ONLY);
/*
* Configures the channel ID, messaging period and rf frequency after assigning,
* then opening the channel.
*
* For any additional ANT features such as proximity search or background scanning, refer to
* the ANT Protocol Doc found at:
* http://www.thisisant.com/resources/ant-message-protocol-and-usage/
*/
mAntChannel.setChannelId(channelId);
mAntChannel.setPeriod(CHANNEL_HEART_PERIOD);
mAntChannel.setRfFrequency(CHANNEL_HEART_FREQUENCY);
mAntChannel.open();
mIsOpen = true;
Log.d(TAG, "Opened channel with device number");
} catch (RemoteException e) {
channelError(e);
} catch (AntCommandFailedException e) {
// This will release, and therefore unassign if required
channelError("Open failed", e);
}
}
} else {
Log.w(TAG, "No channel available");
}
return mIsOpen;
}
void channelError(RemoteException e) {
String logString = "Remote service communication failed.";
Log.e(TAG, logString);
}
void channelError(String error, AntCommandFailedException e) {
StringBuilder logString;
if (e.getResponseMessage() != null) {
String initiatingMessageId = "0x" + Integer.toHexString(
e.getResponseMessage().getInitiatingMessageId());
String rawResponseCode = "0x" + Integer.toHexString(
e.getResponseMessage().getRawResponseCode());
logString = new StringBuilder(error)
.append(". Command ")
.append(initiatingMessageId)
.append(" failed with code ")
.append(rawResponseCode);
} else {
String attemptedMessageId = "0x" + Integer.toHexString(
e.getAttemptedMessageType().getMessageId());
String failureReason = e.getFailureReason().toString();
logString = new StringBuilder(error)
.append(". Command ")
.append(attemptedMessageId)
.append(" failed with reason ")
.append(failureReason);
}
Log.e(TAG, logString.toString());
mAntChannel.release();
Log.e(TAG, "ANT Command Failed");
}
public void close() {
// TODO kill all our resources
if (null != mAntChannel) {
mIsOpen = false;
// Releasing the channel to make it available for others.
// After releasing, the AntChannel instance cannot be reused.
mAntChannel.release();
mAntChannel = null;
}
Log.e(TAG, "Channel Closed");
}
/**
* Implements the Channel Event Handler Interface so that messages can be
* received and channel death events can be handled.
*/
public class ChannelEventCallback implements IAntChannelEventHandler {
int revCounts = 0;
int ucMessageCount = 0;
byte ucPageChange = 0;
byte ucExtMesgType = 1;
long lastTime = 0;
double way;
int rev;
double remWay;
double wheel = 0.1;
@Override
public void onChannelDeath() {
// Display channel death message when channel dies
Log.e(TAG, "Channel Death");
}
@Override
public void onReceiveMessage(MessageFromAntType messageType, AntMessageParcel antParcel) {
Log.d(TAG, "Rx: " + antParcel);
Log.d(TAG, "Message Type: " + messageType);
// Switching on message type to handle different types of messages
switch (messageType) {
// If data message, construct from parcel and update channel data
case BROADCAST_DATA:
// Rx Data
//updateData(new BroadcastDataMessage(antParcel).getPayload());
BroadcastDataMessage m = new BroadcastDataMessage(antParcel);
Log.d(TAG, "BROADCAST_DATA: " + m.getPayload());
heart = m.getPayload()[7];
Log.d(TAG, "BROADCAST_DATA: " + heart);
break;
case ACKNOWLEDGED_DATA:
// Rx Data
//updateData(new AcknowledgedDataMessage(antParcel).getPayload());
Log.d(TAG, "ACKNOWLEDGED_DATA: " + new AcknowledgedDataMessage(antParcel).getPayload());
break;
case CHANNEL_EVENT:
// Constructing channel event message from parcel
ChannelEventMessage eventMessage = new ChannelEventMessage(antParcel);
EventCode code = eventMessage.getEventCode();
Log.d(TAG, "Event Code: " + code);
// Switching on event code to handle the different types of channel events
switch (code) {
case TX:
break;
case CHANNEL_COLLISION:
ucPageChange += 0x20;
ucPageChange &= 0xF0;
ucMessageCount += 1;
break;
case RX_SEARCH_TIMEOUT:
// TODO May want to keep searching
Log.e(TAG, "No Device Found");
break;
case CHANNEL_CLOSED:
case RX_FAIL:
case RX_FAIL_GO_TO_SEARCH:
case TRANSFER_RX_FAILED:
case TRANSFER_TX_COMPLETED:
case TRANSFER_TX_FAILED:
case TRANSFER_TX_START:
case UNKNOWN:
// TODO More complex communication will need to handle these events
break;
}
break;
case ANT_VERSION:
case BURST_TRANSFER_DATA:
case CAPABILITIES:
case CHANNEL_ID:
case CHANNEL_RESPONSE:
case CHANNEL_STATUS:
case SERIAL_NUMBER:
case OTHER:
// TODO More complex communication will need to handle these message types
break;
}
}
}
public boolean isConnected() {
return isConnected;
}
}

View File

@@ -6,6 +6,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.media.AudioManager;
import android.util.Log;
import android.os.Build;
public class MediaButtonReceiver extends BroadcastReceiver {
private static MediaButtonReceiver instance;
@@ -28,14 +29,39 @@ public class MediaButtonReceiver extends BroadcastReceiver {
private native void nativeOnMediaButtonEvent(int prev, int current, int max);
public static void registerReceiver(Context context) {
if (instance == null) {
instance = new MediaButtonReceiver();
try {
if (instance == null) {
instance = new MediaButtonReceiver();
}
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
if (context == null) {
Log.e("MediaButtonReceiver", "Context is null, cannot register receiver");
return;
}
if (Build.VERSION.SDK_INT >= 34) {
try {
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
} catch (SecurityException se) {
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
}
} else {
try {
context.registerReceiver(instance, filter);
} catch (SecurityException se) {
Log.e("MediaButtonReceiver", "Security exception while registering receiver: " + se.getMessage());
}
}
Log.d("MediaButtonReceiver", "Receiver registered successfully");
} catch (IllegalArgumentException e) {
Log.e("MediaButtonReceiver", "Invalid arguments for receiver registration: " + e.getMessage());
} catch (Exception e) {
Log.e("MediaButtonReceiver", "Unexpected error while registering receiver: " + e.getMessage());
}
IntentFilter filter = new IntentFilter("android.media.VOLUME_CHANGED_ACTION");
context.registerReceiver(instance, filter, Context.RECEIVER_EXPORTED);
Log.d("MediaButtonReceiver", "registerReceiver");
}
public static void unregisterReceiver(Context context) {
if (instance != null) {
context.unregisterReceiver(instance);

View File

@@ -0,0 +1,74 @@
package org.cagnulen.qdomyoszwift;
import android.app.ActivityManager;
import android.app.Activity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.provider.Settings;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import android.os.Looper;
import android.os.Handler;
import android.util.Log;
import com.garmin.android.connectiq.ConnectIQ;
import com.garmin.android.connectiq.ConnectIQAdbStrategy;
import com.garmin.android.connectiq.IQApp;
import com.garmin.android.connectiq.IQDevice;
import com.garmin.android.connectiq.exception.InvalidStateException;
import com.garmin.android.connectiq.exception.ServiceUnavailableException;
import android.content.BroadcastReceiver;
import android.content.ContextWrapper;
import android.content.IntentFilter;
import android.widget.Toast;
import org.jetbrains.annotations.Nullable;
import com.google.protobuf.InvalidProtocolBufferException;
import java.util.HashMap;
import java.util.List;
public class ZwiftHubBike {
private static Context context;
private static final String TAG = "ZwiftHubBike: ";
public static byte[] inclinationCommand(double inclination) throws InvalidProtocolBufferException {
ZwiftHub.SimulationParam.Builder simulation = ZwiftHub.SimulationParam.newBuilder();
simulation.setInclineX100((int)(inclination * 100.0));
ZwiftHub.HubCommand.Builder command = ZwiftHub.HubCommand.newBuilder();
command.setSimulation(simulation.build());
byte[] data = command.build().toByteArray();
byte[] fullData = new byte[data.length + 1];
fullData[0] = 0x04;
System.arraycopy(data, 0, fullData, 1, data.length);
return fullData;
}
public static byte[] setGearCommand(int gears) throws InvalidProtocolBufferException {
ZwiftHub.PhysicalParam.Builder physical = ZwiftHub.PhysicalParam.newBuilder();
physical.setGearRatioX10000(gears);
ZwiftHub.HubCommand.Builder command = ZwiftHub.HubCommand.newBuilder();
command.setPhysical(physical.build());
byte[] data = command.build().toByteArray();
byte[] fullData = new byte[data.length + 1];
fullData[0] = 0x04;
System.arraycopy(data, 0, fullData, 1, data.length);
return fullData;
}
}

View File

@@ -0,0 +1,162 @@
syntax = "proto2";
package org.cagnulen.qdomyoszwift;
//-------------- Zwift Hub messages
// The command code prepending this message is 0x00
// This message is sent always following the change of the gear ratio probably to verify it was received properly
message HubRequest {
optional uint32 DataId = 1; // Value observed 520 and 0, 0 requests general info, 1-7 are the fields# in DeviceInformationContent, 520 requests the gear ratio
// 512 to 534 responds unidentifiable data
}
// The command code prepending this message is 0x03
message HubRidingData {
optional uint32 Power = 1;
optional uint32 Cadence = 2;
optional uint32 SpeedX100 = 3;
optional uint32 HR = 4;
optional uint32 Unknown1 = 5; // Values observed 0 when stopped, 2864, 4060, 4636, 6803
optional uint32 Unknown2 = 6; // Values observed 25714, 30091 (constant during session)
}
message SimulationParam {
optional sint32 Wind = 1; // Wind in m/s * 100. In zwift there is no wind (0). Negative is backwind
optional sint32 InclineX100 = 2; // Incline value * 100
optional uint32 CWa = 3; // Wind coefficient CW * a * 10000. In zwift this is constant 0.51 (5100)
optional uint32 Crr = 4; // Rolling resistance Crr * 100000. In zwift this is constant 0.004 (400)
}
message PhysicalParam {
optional uint32 GearRatioX10000 = 2;
optional uint32 BikeWeightx100 = 4;
optional uint32 RiderWeightx100 = 5;
}
// The command code prepending this message is 0x04
message HubCommand {
optional uint32 PowerTarget = 3;
optional SimulationParam Simulation = 4;
optional PhysicalParam Physical = 5;
}
//---------------- Zwift Play messages
enum PlayButtonStatus {
ON = 0;
OFF = 1;
}
// The command code prepending this message is 0x07
message PlayKeyPadStatus {
optional PlayButtonStatus RightPad = 1;
optional PlayButtonStatus Button_Y_Up = 2;
optional PlayButtonStatus Button_Z_Left = 3;
optional PlayButtonStatus Button_A_Right = 4;
optional PlayButtonStatus Button_B_Down = 5;
optional PlayButtonStatus Button_On = 6;
optional PlayButtonStatus Button_Shift = 7;
optional sint32 Analog_LR = 8;
optional sint32 Analog_UD = 9;
}
message PlayCommandParameters {
optional uint32 param1 = 1;
optional uint32 param2 = 2;
optional uint32 HapticPattern = 3;
}
message PlayCommandContents {
optional PlayCommandParameters CommandParameters = 1;
}
// The command code prepending this message is 0x12
// This is sent to the control point to configure and make the controller vibrate
message PlayCommand {
optional PlayCommandContents CommandContents = 2;
}
// The command code prepending this message is 0x19
// This is sent periodically when there are no button presses
message Idle {
optional uint32 Unknown2 = 2;
}
//----------------- Zwift Ride messages
enum RideButtonMask {
LEFT_BTN = 0x00001;
UP_BTN = 0x00002;
RIGHT_BTN = 0x00004;
DOWN_BTN = 0x00008;
A_BTN = 0x00010;
B_BTN = 0x00020;
Y_BTN = 0x00040;
Z_BTN = 0x00100;
SHFT_UP_L_BTN = 0x00200;
SHFT_DN_L_BTN = 0x00400;
POWERUP_L_BTN = 0x00800;
ONOFF_L_BTN = 0x01000;
SHFT_UP_R_BTN = 0x02000;
SHFT_DN_R_BTN = 0x04000;
POWERUP_R_BTN = 0x10000;
ONOFF_R_BTN = 0x20000;
}
enum RideAnalogLocation {
LEFT = 0;
RIGHT = 1;
UP = 2;
DOWN = 3;
}
message RideAnalogKeyPress {
optional RideAnalogLocation Location = 1;
optional sint32 AnalogValue = 2;
}
message RideAnalogKeyGroup {
repeated RideAnalogKeyPress GroupStatus = 1;
}
// The command code prepending this message is 0x23
message RideKeyPadStatus {
optional uint32 ButtonMap = 1;
optional RideAnalogKeyGroup AnalogButtons = 2;
}
//------------------ Zwift Click messages
// The command code prepending this message is 0x37
message ClickKeyPadStatus {
optional PlayButtonStatus Button_Plus = 1;
optional PlayButtonStatus Button_Minus = 2;
}
//------------------ Device Information requested after connection
// The command code prepending this message is 0x3c
message DeviceInformationContent {
optional uint32 Unknown1 = 1;
repeated uint32 SoftwareVersion = 2;
optional string DeviceName = 3;
optional uint32 Unknown4 = 4;
optional uint32 Unknown5 =5;
optional string SerialNumber = 6;
optional string HardwareVersion = 7;
repeated uint32 ReplyData = 8;
optional uint32 Unknown9 = 9;
optional uint32 Unknown10 = 10;
optional uint32 Unknown13 = 13;
}
message SubContent {
optional DeviceInformationContent Content = 1;
optional uint32 Unknown2 = 2;
optional uint32 Unknown4 = 4;
optional uint32 Unknown5 = 5;
optional uint32 Unknown6 = 6;
}
message DeviceInformation {
optional uint32 InformationId = 1;
optional SubContent SubContent = 2;
}

1
src/build-qrc-qml.sh Executable file
View File

@@ -0,0 +1 @@
/Users/cagnulein/Qt/5.15.2/ios/bin/rcc qml.qrc -o ../build-qdomyos-zwift-Qt_5_15_2_for_iOS-Debug/qrc_qml.cpp

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

@@ -26,6 +26,7 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
double CRRGain = settings.value(QZSettings::CRRGain, QZSettings::default_CRRGain).toDouble();
double CWGain = settings.value(QZSettings::CWGain, QZSettings::default_CWGain).toDouble();
bool zwift_play_emulator = settings.value(QZSettings::zwift_play_emulator, QZSettings::default_zwift_play_emulator).toBool();
double min_inclination = settings.value(QZSettings::min_inclination, QZSettings::default_min_inclination).toDouble();
qDebug() << QStringLiteral("new requested resistance zwift erg grade ") + QString::number(iresistance) +
QStringLiteral(" enabled ") + force_resistance;
@@ -39,6 +40,11 @@ void CharacteristicWriteProcessor::changeSlope(int16_t iresistance, uint8_t crr,
percentage = (((qTan(qDegreesToRadians(iresistance / 100.0)) * 100.0) * 2.0) * gain) + offset;
}
if(min_inclination > grade) {
grade = min_inclination;
qDebug() << "grade override due to min_inclination " << min_inclination;
}
/*
Surface Road Crr MTB Crr Gravel Crr (Namebrand) Zwift Gravel Crr
Pavement .004 .01 .008 .008

View File

@@ -96,21 +96,51 @@ double bike::gears() {
}
return m_gears + gears_offset;
}
void bike::setGears(double gears) {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
double gears_offset = settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble();
gears -= gears_offset;
qDebug() << "setGears" << gears;
// Check for boundaries and emit failure signals
if(gears_zwift_ratio && (gears > 24 || gears < 1)) {
qDebug() << "new gear value ignored because of gears_zwift_ratio setting!";
if(gears > 24) {
emit gearFailedUp();
} else {
emit gearFailedDown();
}
return;
}
if(gears > maxGears()) {
qDebug() << "new gear value ignored because of maxGears" << maxGears();
emit gearFailedUp();
return;
}
if(gears < minGears()) {
qDebug() << "new gear value ignored because of minGears" << minGears();
emit gearFailedDown();
return;
}
if(m_gears > gears) {
emit gearOkDown();
} else {
emit gearOkUp();
}
m_gears = gears;
if(homeform::singleton()) {
homeform::singleton()->updateGearsValue();
}
settings.setValue(QZSettings::gears_current_value, m_gears);
if (settings.value(QZSettings::gears_restore_value, QZSettings::default_gears_restore_value).toBool())
settings.setValue(QZSettings::gears_current_value, m_gears);
if (lastRawRequestedResistanceValue != -1) {
changeResistance(lastRawRequestedResistanceValue);
}

View File

@@ -23,6 +23,9 @@ class bike : public bluetoothdevice {
double currentCrankRevolutions() override;
uint16_t lastCrankEventTime() override;
bool connected() override;
double defaultMaxGears() { return 9999.0; }
virtual double maxGears() { return defaultMaxGears(); }
virtual double minGears() { return -9999.0; }
virtual uint16_t watts();
virtual resistance_t pelotonToBikeResistance(int pelotonResistance);
virtual resistance_t resistanceFromPowerRequest(uint16_t power);
@@ -78,6 +81,10 @@ class bike : public bluetoothdevice {
void resistanceChanged(resistance_t resistance);
void resistanceRead(resistance_t resistance);
void steeringAngleChanged(double angle);
void gearOkUp(); // Signal when gear up succeeds
void gearOkDown(); // Signal when gear down succeeds
void gearFailedUp(); // Signal when gear up hits max
void gearFailedDown(); // Signal when gear down hits min
protected:
metric RequestedResistance;

View File

@@ -170,11 +170,14 @@ void bluetooth::finished() {
bool zwiftDeviceFound =
!settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool() && !settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool();
bool sramDeviceFound = !settings.value(QZSettings::sram_axs_controller, QZSettings::default_sram_axs_controller).toBool();
if ((!heartRateBeltFound && !heartRateBeltAvaiable()) || (!ftmsAccessoryFound && !ftmsAccessoryAvaiable()) ||
(!cscFound && !cscSensorAvaiable()) || (!powerSensorFound && !powerSensorAvaiable()) ||
(!eliteRizerFound && !eliteRizerAvaiable()) || (!eliteSterzoSmartFound && !eliteSterzoSmartAvaiable()) ||
(!fitmetriaFanfitFound && !fitmetriaFanfitAvaiable()) ||
(!zwiftDeviceFound && !zwiftDeviceAvaiable())) {
(!zwiftDeviceFound && !zwiftDeviceAvaiable()) ||
(!sramDeviceFound && !sramDeviceAvaiable())) {
// force heartRateBelt off
forceHeartBeltOffForTimeout = true;
@@ -294,6 +297,16 @@ bool bluetooth::zwiftDeviceAvaiable() {
return false;
}
bool bluetooth::sramDeviceAvaiable() {
Q_FOREACH (QBluetoothDeviceInfo b, devices) {
if (b.name().toUpper().startsWith("SRAM ")) {
return true;
}
}
return false;
}
bool bluetooth::powerSensorAvaiable() {
@@ -392,6 +405,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
settings.value(QZSettings::ftms_accessory_name, QZSettings::default_ftms_accessory_name).toString();
bool heartRateBeltFound = heartRateBeltName.startsWith(QStringLiteral("Disabled"));
bool ftmsAccessoryFound = ftmsAccessoryName.startsWith(QStringLiteral("Disabled"));
bool sramDeviceFound = !settings.value(QZSettings::sram_axs_controller, QZSettings::default_sram_axs_controller).toBool();
bool zwiftDeviceFound =
!settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool() && !settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool();
bool fitmetriaFanfitFound =
@@ -455,6 +469,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QString computrainerSerialPort =
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();
QString csaferowerSerialPort = settings.value(QZSettings::csafe_rower, QZSettings::default_csafe_rower).toString();
QString csafeellipticalSerialPort =
settings.value(QZSettings::csafe_elliptical_port, QZSettings::default_csafe_elliptical_port).toString();
bool manufacturerDeviceFound = false;
bool ss2k_peloton = settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool();
bool pafers_treadmill_bh_iboxster_plus =
@@ -475,6 +491,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QString ftms_treadmill = settings.value(QZSettings::ftms_treadmill, QZSettings::default_ftms_treadmill).toString();
bool saris_trainer = settings.value(QZSettings::saris_trainer, QZSettings::default_saris_trainer).toBool();
bool iconsole_elliptical = settings.value(QZSettings::iconsole_elliptical, QZSettings::default_iconsole_elliptical).toBool();
bool iconsole_rower = settings.value(QZSettings::iconsole_rower, QZSettings::default_iconsole_rower).toBool();
if (!heartRateBeltFound) {
@@ -488,6 +505,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
zwiftDeviceFound = zwiftDeviceAvaiable();
}
if(!sramDeviceFound) {
sramDeviceFound = sramDeviceAvaiable();
}
if (!ftmsAccessoryFound) {
ftmsAccessoryFound = ftmsAccessoryAvaiable();
@@ -765,6 +786,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(csafeRower);
} else if (!csafeellipticalSerialPort.isEmpty() && !csafeElliptical) {
this->stopDiscovery();
// csafeElliptical = new csafeelliptical(noWriteResistance, noHeartService, false);
csafeElliptical = new csafeelliptical(noWriteResistance, noHeartService, false,
bikeResistanceOffset, bikeResistanceGain);
emit deviceConnected(b);
connect(csafeElliptical, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
// connect(cscBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(csafeElliptical, &csafeelliptical::debug, this, &bluetooth::debug);
csafeElliptical->deviceDiscovered(b);
// connect(this, SIGNAL(searchingStop()), cscBike, SLOT(searchingStop())); //NOTE: Commented due to #358
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(csafeElliptical);
#endif
} else if (antbike_setting && !antBike) {
this->stopDiscovery();
@@ -917,7 +955,24 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(domyosBike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("FAL-SPORTS")) ||
} else if (b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_rower &&
!trxappgateusbRower && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
trxappgateusbRower = new trxappgateusbrower(noWriteResistance, noHeartService,
bikeResistanceOffset, bikeResistanceGain);
emit deviceConnected(b);
connect(trxappgateusbRower, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(domyosElliptical, SIGNAL(disconnected()), this, SLOT(restart()));
connect(trxappgateusbRower, &trxappgateusbrower::debug, this, &bluetooth::debug);
trxappgateusbRower->deviceDiscovered(b);
connect(this, &bluetooth::searchingStop, trxappgateusbRower, &trxappgateusbrower::searchingStop);
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(trxappgateusbRower);
} 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);
@@ -935,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();
@@ -955,6 +1010,10 @@ 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) {
this->setLastBluetoothDevice(b);
@@ -1066,7 +1125,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
if (this->discoveryAgent && !this->discoveryAgent->isActive())
emit searchingStop();
this->signalBluetoothDeviceConnected(proformRower);
} else if ((b.name().toUpper().startsWith(QStringLiteral("B01_"))) && !bhFitnessElliptical && filter) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("B01_"))) && ftms_bike.contains(QZSettings::default_ftms_bike) && !bhFitnessElliptical && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
bhFitnessElliptical = new bhfitnesselliptical(noWriteResistance, noHeartService, bikeResistanceOffset,
@@ -1250,7 +1309,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("F63")) ||
(b.name().toUpper().startsWith(QStringLiteral("ST90")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
b.name().toUpper().startsWith(QStringLiteral("TRX7.5")) ||
b.name().toUpper().startsWith(QStringLiteral("S77")) ||
(b.name().toUpper().startsWith(QStringLiteral("S77")) && sole_inclination) ||
(b.name().toUpper().startsWith(QStringLiteral("F85")) && sole_inclination)) &&
!soleF80 && filter) {
this->setLastBluetoothDevice(b);
@@ -1328,18 +1387,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith(QStringLiteral("DOMYOS-TC")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyostreadmill_notfmts, QZSettings::default_domyostreadmill_notfmts).toBool()) ||
b.name().toUpper().startsWith(QStringLiteral("XT685")) ||
b.name().toUpper().startsWith(QStringLiteral("XT285")) ||
b.name().toUpper().startsWith(QStringLiteral("FITNESS")) ||
b.name().toUpper().startsWith(QStringLiteral("WELLFIT TM")) ||
b.name().toUpper().startsWith(QStringLiteral("XTERRA TR")) ||
b.name().toUpper().startsWith(QStringLiteral("T118_")) ||
b.name().toUpper().startsWith(QStringLiteral("RUNN ")) ||
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))) ||
@@ -1347,11 +1411,14 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().startsWith(QStringLiteral("MOBVOI TM")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("LB600")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("TUNTURI T90-")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("KETTLER TREADMILL")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("ASSAULTRUNNER")) || // FTMS
b.name().toUpper().startsWith(QStringLiteral("CITYSPORTS-LINKER")) ||
(b.name().toUpper().startsWith(QStringLiteral("TP1")) && b.name().length() == 3) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("CTM")) && b.name().length() >= 15) || // FTMS
(b.name().toUpper().startsWith(QStringLiteral("F85")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("S77")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F89")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("F80")) && !sole_inclination) || // FMTS
(b.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) // FTMS
@@ -1447,11 +1514,13 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(technogymmyrunrfcommTreadmill);
}
#endif
} else if ((b.name().toUpper().startsWith("TACX ") ||
} 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();
@@ -1465,6 +1534,20 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(tacxneo2Bike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
tacxneo2Bike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(tacxneo2Bike);
} else if ((b.name().toUpper().startsWith("INDOORCYCLE")) &&
!cycleopsphantomBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
cycleopsphantomBike = new cycleopsphantombike(noWriteResistance, noHeartService);
// stateFileRead();
emit(deviceConnected(b));
connect(cycleopsphantomBike, SIGNAL(connectedAndDiscovered()), this, SLOT(connectedAndDiscovered()));
// connect(cycleopsphantomBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(cycleopsphantomBike, SIGNAL(debug(QString)), this, SLOT(debug(QString)));
// connect(cycleopsphantomBike, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
// connect(cycleopsphantomBike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
cycleopsphantomBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(cycleopsphantomBike);
} else if ((b.name().toUpper().startsWith(QStringLiteral(">CABLE")) ||
(b.name().toUpper().startsWith(QStringLiteral("MD")) && b.name().length() == 7) ||
// BIKE 1, BIKE 2, BIKE 3...
@@ -1493,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 ")) ||
@@ -1511,10 +1595,10 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith(ftmsAccessoryName.toUpper()) &&
settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton)
.toBool()) || // ss2k on a peloton bike
((b.name().toUpper().startsWith("KICKR CORE")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith("MERACH-MR667-")) ||
(b.name().toUpper().startsWith("DS60-")) ||
(b.name().toUpper().startsWith("BIKE-")) ||
(b.name().toUpper().startsWith("M9-")) ||
(b.name().toUpper().startsWith("SPAX-BK-")) ||
(b.name().toUpper().startsWith("YSV1")) ||
(b.name().toUpper().startsWith("VOLT") && b.name().length() == 4) ||
@@ -1531,16 +1615,40 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().startsWith(QStringLiteral("Domyos-Bike")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && !settings.value(QZSettings::domyosbike_notfmts, QZSettings::default_domyosbike_notfmts).toBool()) ||
(b.name().toUpper().startsWith("F") && b.name().toUpper().endsWith("ARROW")) || // FI9110 Arrow, https://www.fitnessdigital.it/bicicletta-smart-bike-ion-fitness-arrow-connect/p/10022863/ IO Fitness Arrow
(b.name().toUpper().startsWith("ICSE") && b.name().length() == 4) ||
(b.name().toUpper().startsWith("TUO") && b.name().length() == 3) ||
(b.name().toUpper().startsWith("FLX") && b.name().length() == 10) ||
(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
@@ -1562,6 +1670,8 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
this->signalBluetoothDeviceConnected(ftmsBike);
} else if ((b.name().toUpper().startsWith("KICKR SNAP") || b.name().toUpper().startsWith("KICKR BIKE") ||
b.name().toUpper().startsWith("KICKR ROLLR") ||
b.name().toUpper().startsWith("KICKR CORE") ||
(b.name().toUpper().startsWith("KICKR MOVE ")) ||
(b.name().toUpper().startsWith("HAMMER ") && saris_trainer) ||
(b.name().toUpper().startsWith("WAHOO KICKR"))) &&
!wahooKickrSnapBike && !ftmsBike && filter) {
@@ -1576,6 +1686,19 @@ 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().midRef(5).toInt() > 0 &&
!technogymBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
technogymBike =
new technogymbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
emit deviceConnected(b);
connect(technogymBike, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(wahooKickrSnapBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(technogymBike, &technogymbike::debug, this, &bluetooth::debug);
technogymBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(technogymBike);
} else if (((b.name().toUpper().startsWith("JFIC")) // HORIZON GR7
) &&
!horizonGr7Bike && filter) {
@@ -1590,12 +1713,26 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(horizonGr7Bike, &horizongr7bike::debug, this, &bluetooth::debug);
horizonGr7Bike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(horizonGr7Bike);
} else if (((b.name().toUpper().startsWith("SMART CONTROL"))
) &&
!kineticInroadBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
kineticInroadBike =
new kineticinroadbike(noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
emit deviceConnected(b);
connect(kineticInroadBike, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(trxappgateusb, SIGNAL(disconnected()), this, SLOT(restart()));
//connect(kineticInroadBike, &kineticinroadbike::debug, this, &bluetooth::debug);
kineticInroadBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(kineticInroadBike);
} else if ((b.name().toUpper().startsWith(QStringLiteral("STAGES ")) ||
(b.name().toUpper().startsWith("TACX SATORI")) ||
(b.name().toUpper().startsWith("RACER S")) ||
((b.name().toUpper().startsWith("KU")) && b.name().length() == 2) ||
(b.name().toUpper().startsWith("ELITETRAINER")) ||
((b.name().toUpper().startsWith("KICKR CORE")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826)) && deviceHasService(b, QBluetoothUuid((quint16)0x1818))) ||
(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")) &&
@@ -1651,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) ||
@@ -1733,8 +1871,22 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(ziproTreadmill, &ziprotreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
ziproTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(ziproTreadmill);
} else if ((b.name().toUpper().startsWith(QLatin1String("LIFESPAN-TM"))) && !lifespanTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
lifespanTreadmill = new lifespantreadmill(this->pollDeviceTime, noConsole, noHeartService);
// stateFileRead();
emit deviceConnected(b);
connect(lifespanTreadmill, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(ziproTreadmill, SIGNAL(disconnected()), this, SLOT(restart())); connect(echelonStride,
connect(lifespanTreadmill, &lifespantreadmill::debug, this, &bluetooth::debug);
connect(lifespanTreadmill, &lifespantreadmill::speedChanged, this, &bluetooth::speedChanged);
connect(lifespanTreadmill, &lifespantreadmill::inclinationChanged, this, &bluetooth::inclinationChanged);
lifespanTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(lifespanTreadmill);
} else if ((b.name().startsWith(QStringLiteral("ECH-ROW")) ||
b.name().toUpper().startsWith(QStringLiteral("ROWSPORT-")) ||
b.name().toUpper().startsWith(QStringLiteral("ROWSPORT")) ||
b.name().startsWith(QStringLiteral("ROW-S"))) &&
!echelonRower && filter) {
this->setLastBluetoothDevice(b);
@@ -1751,7 +1903,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// connect(echelonRower, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
echelonRower->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(echelonRower);
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride &&
} else if (b.name().startsWith(QStringLiteral("ECH")) && !echelonRower && !echelonStride && !ftmsBike &&
!echelonConnectSport && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1883,6 +2035,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
sportsPlusBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(sportsPlusBike);
} else if ((b.name().toUpper().contains(QStringLiteral("CARE")) &&
b.name().length() >= 13) // CARE968300122
&& !sportsPlusRower && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
sportsPlusRower = new sportsplusrower(noWriteResistance, noHeartService);
// stateFileRead();
emit deviceConnected(b);
connect(sportsPlusRower, &bluetoothdevice::connectedAndDiscovered, this,
&bluetooth::connectedAndDiscovered);
// connect(sportsPlusBike, SIGNAL(disconnected()), this, SLOT(restart()));
connect(sportsPlusRower, &sportsplusrower::debug, this, &bluetooth::debug);
// connect(sportsPlusBike, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
// connect(sportsPlusBike, SIGNAL(inclinationChanged(double)), this,
// SLOT(inclinationChanged(double)));
sportsPlusRower->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(sportsPlusRower);
} else if ((b.name().startsWith(yesoulbike::bluetoothName) ||
b.name().toUpper().startsWith("YS_G1M_")) && !yesoulBike && filter) {
this->setLastBluetoothDevice(b);
@@ -1916,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();
@@ -1948,7 +2118,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// SLOT(inclinationChanged(double)));
eslinkerTreadmill->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(eslinkerTreadmill);
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT")) && !deerrunTreadmill && filter) {
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT-T")) && !deerrunTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
deerrunTreadmill = new deerruntreadmill(this->pollDeviceTime, noConsole, noHeartService);
@@ -2060,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();
@@ -2100,7 +2271,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith(QStringLiteral("XT485")) && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
b.name().toUpper().startsWith(QStringLiteral("XT800")) ||
b.name().toUpper().startsWith(QStringLiteral("XT900"))) &&
!spiritTreadmill && filter) {
!spiritTreadmill && !horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
spiritTreadmill = new spirittreadmill();
@@ -2132,7 +2303,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith(QStringLiteral("DKN RUN"))) ||
(b.name().toUpper().startsWith(QStringLiteral("ADIDAS "))) ||
(b.name().toUpper().startsWith(QStringLiteral("REEBOK")))) &&
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill && !iconsole_elliptical &&
!trxappgateusb && !trxappgateusbBike && !toorx_bike && !toorx_ftms && !toorx_ftms_treadmill && !iconsole_elliptical && !iconsole_rower &&
filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -2158,9 +2329,10 @@ 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) {
!trxappgateusb && !toorx_ftms && !toorx_ftms_treadmill && !trxappgateusbBike && filter && !iconsole_elliptical && !iconsole_rower) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
trxappgateusbBike =
@@ -2247,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) {
@@ -2295,6 +2467,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().startsWith(QStringLiteral("SW")) && b.name().length() == 14 &&
!b.name().contains('(') && !b.name().contains(')') && !deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith(QStringLiteral("WINFITA"))) || // also FTMS
(b.name().toUpper().startsWith(QStringLiteral("SW-BLE"))) || // FTMS
(b.name().startsWith(QStringLiteral("BF70")))) &&
!fitshowTreadmill && !iconsole_elliptical && !horizonTreadmill && filter) {
this->setLastBluetoothDevice(b);
@@ -2353,6 +2526,28 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(chronoBike);
} else if (b.name().toUpper().startsWith(QStringLiteral("PITPAT-S")) && !pitpatBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
pitpatBike = new pitpatbike(noWriteResistance, noHeartService, bikeResistanceOffset,
bikeResistanceGain);
#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS)
stateFileRead();
#endif
emit deviceConnected(b);
connect(pitpatBike, &bluetoothdevice::connectedAndDiscovered, this, &bluetooth::connectedAndDiscovered);
//connect(pitpatBike, &pitpatbike::debug, this, &bluetooth::debug);
// NOTE: Commented due to #358
// connect(chronoBike, SIGNAL(speedChanged(double)), this, SLOT(speedChanged(double)));
// NOTE: Commented due to #358
// connect(chronoBike, SIGNAL(inclinationChanged(double)), this, SLOT(inclinationChanged(double)));
pitpatBike->deviceDiscovered(b);
// NOTE: Commented due to #358
// connect(this, SIGNAL(searchingStop()), chronoBike, SLOT(searchingStop()));
if (this->discoveryAgent && !this->discoveryAgent->isActive()) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(pitpatBike);
}
}
}
@@ -2578,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(),
@@ -2639,6 +2836,25 @@ void bluetooth::connectedAndDiscovered() {
}
}
if(settings.value(QZSettings::sram_axs_controller, QZSettings::default_sram_axs_controller).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("SRAM "))) && !sramAXSController && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
sramAXSController = new sramaxscontroller();
// connect(heartRateBelt, SIGNAL(disconnected()), this, SLOT(restart()));
connect(sramAXSController, &sramaxscontroller::debug, this, &bluetooth::debug);
connect(sramAXSController, &sramaxscontroller::plus, (bike*)this->device(), &bike::gearUp);
connect(sramAXSController, &sramaxscontroller::minus, (bike*)this->device(), &bike::gearDown);
sramAXSController->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested("SRAM Connected!");
break;
}
}
}
if(settings.value(QZSettings::zwift_click, QZSettings::default_zwift_click).toBool()) {
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if (((b.name().toUpper().startsWith("ZWIFT CLICK"))) && !zwiftClickRemote && this->device() &&
@@ -2665,6 +2881,7 @@ void bluetooth::connectedAndDiscovered() {
}
if(settings.value(QZSettings::zwift_play, QZSettings::default_zwift_play).toBool()) {
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
for (const QBluetoothDeviceInfo &b : qAsConst(devices)) {
if ((((b.name().toUpper().startsWith("ZWIFT PLAY"))) || b.name().toUpper().startsWith("ZWIFT RIDE") || b.name().toUpper().startsWith("ZWIFT SF2")) && zwiftPlayDevice.size() < 2 && this->device() &&
this->device()->deviceType() == bluetoothdevice::BIKE) {
@@ -2672,7 +2889,7 @@ void bluetooth::connectedAndDiscovered() {
if(b.manufacturerData(2378).size() > 0) {
qDebug() << "this should be 3 or 2. is it? " << int(b.manufacturerData(2378).at(0));
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
int(b.manufacturerData(2378).at(0)) == 3 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
int(b.manufacturerData(2378).at(0)) == 3 || int(b.manufacturerData(2378).at(0)) == 7 ? AbstractZapDevice::ZWIFT_PLAY_TYPE::LEFT : AbstractZapDevice::ZWIFT_PLAY_TYPE::RIGHT));
} else {
qDebug() << "manufacturer not found for ZWIFT CLICK";
zwiftPlayDevice.append(new zwiftclickremote(this->device(),
@@ -2684,6 +2901,14 @@ void bluetooth::connectedAndDiscovered() {
connect(zwiftPlayDevice.last(), &zwiftclickremote::debug, this, &bluetooth::debug);
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::plus, (bike*)this->device(), &bike::gearUp);
connect(zwiftPlayDevice.last()->playDevice, &ZwiftPlayDevice::minus, (bike*)this->device(), &bike::gearDown);
if((zwiftPlayDevice.last()->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) ||
(zwiftPlayDevice.last()->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) {
connect((bike*)this->device(), &bike::gearOkUp, this, &bluetooth::gearUp);
connect((bike*)this->device(), &bike::gearFailedUp, this, &bluetooth::gearFailedUp);
} else {
connect((bike*)this->device(), &bike::gearOkDown, this, &bluetooth::gearDown);
connect((bike*)this->device(), &bike::gearFailedDown, this, &bluetooth::gearFailedDown);
}
zwiftPlayDevice.last()->deviceDiscovered(b);
if(homeform::singleton())
homeform::singleton()->setToastRequested("Zwift Play/Ride Connected!");
@@ -2759,6 +2984,50 @@ void bluetooth::connectedAndDiscovered() {
firstConnected = false;
}
void bluetooth::gearUp() {
QSettings settings;
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
foreach(zwiftclickremote* p, zwiftPlayDevice) {
if((p->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) {
p->vibrate(0x20);
return;
}
}
}
void bluetooth::gearDown() {
QSettings settings;
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
foreach(zwiftclickremote* p, zwiftPlayDevice) {
if((p->typeZap == AbstractZapDevice::RIGHT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::LEFT && zwiftplay_swap)) {
p->vibrate(0x20);
return;
}
}
}
void bluetooth::gearFailedUp() {
QSettings settings;
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
foreach(zwiftclickremote* p, zwiftPlayDevice) {
if((p->typeZap == AbstractZapDevice::LEFT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::RIGHT && zwiftplay_swap)) {
p->vibrate(0x60);
return;
}
}
}
void bluetooth::gearFailedDown() {
QSettings settings;
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
foreach(zwiftclickremote* p, zwiftPlayDevice) {
if((p->typeZap == AbstractZapDevice::RIGHT && !zwiftplay_swap) || (p->typeZap == AbstractZapDevice::LEFT && zwiftplay_swap)) {
p->vibrate(0x60);
return;
}
}
}
void bluetooth::heartRate(uint8_t heart) { Q_UNUSED(heart) }
void bluetooth::restart() {
@@ -2975,6 +3244,11 @@ void bluetooth::restart() {
delete tacxneo2Bike;
tacxneo2Bike = nullptr;
}
if (cycleopsphantomBike) {
delete cycleopsphantomBike;
cycleopsphantomBike = nullptr;
}
if (stagesBike) {
delete stagesBike;
@@ -3021,6 +3295,11 @@ void bluetooth::restart() {
delete trxappgateusbElliptical;
trxappgateusbElliptical = nullptr;
}
if (trxappgateusbRower) {
delete trxappgateusbRower;
trxappgateusbRower = nullptr;
}
if (soleBike) {
delete soleBike;
@@ -3066,6 +3345,10 @@ void bluetooth::restart() {
delete ziproTreadmill;
ziproTreadmill = nullptr;
}
if (lifespanTreadmill) {
delete lifespanTreadmill;
lifespanTreadmill = nullptr;
}
if (octaneElliptical) {
delete octaneElliptical;
@@ -3191,6 +3474,11 @@ void bluetooth::restart() {
delete sportsPlusBike;
sportsPlusBike = nullptr;
}
if (sportsPlusRower) {
delete sportsPlusRower;
sportsPlusRower = nullptr;
}
if (pelotonBike) {
delete pelotonBike;
@@ -3212,12 +3500,22 @@ void bluetooth::restart() {
delete csafeRower;
csafeRower = nullptr;
}
if (csafeElliptical) {
delete csafeElliptical;
csafeElliptical= nullptr;
}
#endif
if (chronoBike) {
delete chronoBike;
chronoBike = nullptr;
}
if (pitpatBike) {
delete pitpatBike;
pitpatBike = nullptr;
}
if (snodeBike) {
delete snodeBike;
@@ -3233,11 +3531,21 @@ void bluetooth::restart() {
delete wahooKickrSnapBike;
wahooKickrSnapBike = nullptr;
}
if (technogymBike) {
delete technogymBike;
technogymBike = nullptr;
}
if (horizonGr7Bike) {
delete horizonGr7Bike;
horizonGr7Bike = nullptr;
}
if (kineticInroadBike) {
delete kineticInroadBike;
kineticInroadBike = nullptr;
}
if (renphoBike) {
delete renphoBike;
@@ -3383,6 +3691,8 @@ bluetoothdevice *bluetooth::device() {
return npeCableBike;
} else if (tacxneo2Bike) {
return tacxneo2Bike;
} else if (cycleopsphantomBike) {
return cycleopsphantomBike;
} else if (stagesBike) {
return stagesBike;
} else if (toorx) {
@@ -3401,6 +3711,8 @@ bluetoothdevice *bluetooth::device() {
return trxappgateusbBike;
} else if (trxappgateusbElliptical) {
return trxappgateusbElliptical;
} else if (trxappgateusbRower) {
return trxappgateusbRower;
} else if (soleBike) {
return soleBike;
} else if (keepBike) {
@@ -3443,6 +3755,8 @@ bluetoothdevice *bluetooth::device() {
return octaneTreadmill;
} else if (ziproTreadmill) {
return ziproTreadmill;
} else if (lifespanTreadmill) {
return lifespanTreadmill;
} else if (octaneElliptical) {
return octaneElliptical;
} else if (ftmsRower) {
@@ -3493,10 +3807,14 @@ bluetoothdevice *bluetooth::device() {
return sportsTechElliptical;
} else if (sportsPlusBike) {
return sportsPlusBike;
} else if (sportsPlusRower) {
return sportsPlusRower;
} else if (inspireBike) {
return inspireBike;
} else if (chronoBike) {
return chronoBike;
} else if (pitpatBike) {
return pitpatBike;
} else if (m3iBike) {
return m3iBike;
} else if (snodeBike) {
@@ -3505,8 +3823,12 @@ bluetoothdevice *bluetooth::device() {
return ftmsBike;
} else if (wahooKickrSnapBike) {
return wahooKickrSnapBike;
} else if (technogymBike) {
return technogymBike;
} else if (horizonGr7Bike) {
return horizonGr7Bike;
} else if (kineticInroadBike) {
return kineticInroadBike;
} else if (renphoBike) {
return renphoBike;
} else if (pafersBike) {
@@ -3522,6 +3844,8 @@ bluetoothdevice *bluetooth::device() {
return computrainerBike;
} else if (csafeRower) {
return csafeRower;
} else if (csafeElliptical) {
return csafeElliptical;
#endif
}
return nullptr;

View File

@@ -32,10 +32,12 @@
#ifndef Q_OS_IOS
#include "devices/computrainerbike/computrainerbike.h"
#include "devices/csaferower/csaferower.h"
#include "devices/csafeelliptical/csafeelliptical.h"
#endif
#include "devices/concept2skierg/concept2skierg.h"
#include "devices/crossrope/crossrope.h"
#include "devices/cscbike/cscbike.h"
#include "devices/cycleopsphantombike/cycleopsphantombike.h"
#include "devices/deeruntreadmill/deerruntreadmill.h"
#include "devices/domyosbike/domyosbike.h"
#include "devices/domyoselliptical/domyoselliptical.h"
@@ -67,9 +69,11 @@
#include "devices/iconceptelliptical/iconceptelliptical.h"
#include "devices/inspirebike/inspirebike.h"
#include "devices/keepbike/keepbike.h"
#include "devices/kineticinroadbike/kineticinroadbike.h"
#include "devices/kingsmithr1protreadmill/kingsmithr1protreadmill.h"
#include "devices/kingsmithr2treadmill/kingsmithr2treadmill.h"
#include "devices/lifefitnesstreadmill/lifefitnesstreadmill.h"
#include "devices/lifespantreadmill/lifespantreadmill.h"
#include "devices/m3ibike/m3ibike.h"
#include "devices/mcfbike/mcfbike.h"
#include "devices/mepanelbike/mepanelbike.h"
@@ -86,6 +90,7 @@
#include "devices/pafersbike/pafersbike.h"
#include "devices/paferstreadmill/paferstreadmill.h"
#include "devices/pelotonbike/pelotonbike.h"
#include "devices/pitpatbike/pitpatbike.h"
#include "devices/proformbike/proformbike.h"
#include "devices/proformelliptical/proformelliptical.h"
#include "devices/proformellipticaltrainer/proformellipticaltrainer.h"
@@ -110,8 +115,10 @@
#include "devices/spirittreadmill/spirittreadmill.h"
#include "devices/sportsplusbike/sportsplusbike.h"
#include "devices/sportsplusrower/sportsplusrower.h"
#include "devices/sportstechbike/sportstechbike.h"
#include "devices/sportstechelliptical/sportstechelliptical.h"
#include "devices/sramAXSController/sramAXSController.h"
#include "devices/stagesbike/stagesbike.h"
#include "devices/renphobike/renphobike.h"
@@ -122,11 +129,13 @@
#include "devices/echelonstride/echelonstride.h"
#include "templateinfosenderbuilder.h"
#include "technogymbike/technogymbike.h"
#include "devices/toorxtreadmill/toorxtreadmill.h"
#include "devices/treadmill.h"
#include "devices/truetreadmill/truetreadmill.h"
#include "devices/trxappgateusbbike/trxappgateusbbike.h"
#include "devices/trxappgateusbelliptical/trxappgateusbelliptical.h"
#include "devices/trxappgateusbrower/trxappgateusbrower.h"
#include "devices/trxappgateusbtreadmill/trxappgateusbtreadmill.h"
#include "devices/ultrasportbike/ultrasportbike.h"
#include "devices/wahookickrheadwind/wahookickrheadwind.h"
@@ -175,8 +184,10 @@ class bluetooth : public QObject, public SignalHandler {
#ifndef Q_OS_IOS
computrainerbike *computrainerBike = nullptr;
csaferower *csafeRower = nullptr;
csafeelliptical *csafeElliptical = nullptr;
#endif
concept2skierg *concept2Skierg = nullptr;
cycleopsphantombike *cycleopsphantomBike = nullptr;
deerruntreadmill *deerrunTreadmill = nullptr;
domyostreadmill *domyos = nullptr;
domyosbike *domyosBike = nullptr;
@@ -192,6 +203,7 @@ class bluetooth : public QObject, public SignalHandler {
nautiluselliptical *nautilusElliptical = nullptr;
nautilustreadmill *nautilusTreadmill = nullptr;
trxappgateusbbike *trxappgateusbBike = nullptr;
trxappgateusbrower *trxappgateusbRower = nullptr;
trxappgateusbelliptical *trxappgateusbElliptical = nullptr;
echelonconnectsport *echelonConnectSport = nullptr;
yesoulbike *yesoulBike = nullptr;
@@ -219,9 +231,11 @@ class bluetooth : public QObject, public SignalHandler {
truetreadmill *trueTreadmill = nullptr;
horizongr7bike *horizonGr7Bike = nullptr;
schwinnic4bike *schwinnIC4Bike = nullptr;
technogymbike* technogymBike = nullptr;
sportstechbike *sportsTechBike = nullptr;
sportstechelliptical *sportsTechElliptical = nullptr;
sportsplusbike *sportsPlusBike = nullptr;
sportsplusrower *sportsPlusRower = nullptr;
inspirebike *inspireBike = nullptr;
snodebike *snodeBike = nullptr;
eslinkertreadmill *eslinkerTreadmill = nullptr;
@@ -243,6 +257,7 @@ class bluetooth : public QObject, public SignalHandler {
smartrowrower *smartrowRower = nullptr;
echelonstride *echelonStride = nullptr;
lifefitnesstreadmill *lifefitnessTreadmill = nullptr;
lifespantreadmill *lifespanTreadmill = nullptr;
keepbike *keepBike = nullptr;
kingsmithr1protreadmill *kingsmithR1ProTreadmill = nullptr;
kingsmithr2treadmill *kingsmithR2Treadmill = nullptr;
@@ -250,6 +265,7 @@ class bluetooth : public QObject, public SignalHandler {
pafersbike *pafersBike = nullptr;
paferstreadmill *pafersTreadmill = nullptr;
tacxneo2 *tacxneo2Bike = nullptr;
pitpatbike *pitpatBike = nullptr;
renphobike *renphoBike = nullptr;
shuaa5treadmill *shuaA5Treadmill = nullptr;
heartratebelt *heartRateBelt = nullptr;
@@ -262,6 +278,7 @@ class bluetooth : public QObject, public SignalHandler {
wahookickrsnapbike *wahooKickrSnapBike = nullptr;
ypooelliptical *ypooElliptical = nullptr;
ziprotreadmill *ziproTreadmill = nullptr;
kineticinroadbike *kineticInroadBike = nullptr;
strydrunpowersensor *powerTreadmill = nullptr;
eliterizer *eliteRizer = nullptr;
elitesterzosmart *eliteSterzoSmart = nullptr;
@@ -274,6 +291,7 @@ class bluetooth : public QObject, public SignalHandler {
QList<eliteariafan *> eliteAriaFan;
QList<zwiftclickremote* > zwiftPlayDevice;
zwiftclickremote* zwiftClickRemote = nullptr;
sramaxscontroller* sramAXSController = nullptr;
QString filterDevice = QLatin1String("");
bool testResistance = false;
@@ -308,6 +326,7 @@ class bluetooth : public QObject, public SignalHandler {
bool eliteSterzoSmartAvaiable();
bool fitmetriaFanfitAvaiable();
bool zwiftDeviceAvaiable();
bool sramDeviceAvaiable();
bool fitmetria_fanfit_isconnected(QString name);
#ifdef Q_OS_WIN
@@ -346,6 +365,10 @@ class bluetooth : public QObject, public SignalHandler {
void speedChanged(double);
void inclinationChanged(double, double);
void connectedAndDiscovered();
void gearDown();
void gearUp();
void gearFailedDown();
void gearFailedUp();
signals:
};

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); }
@@ -254,6 +255,7 @@ void bluetoothdevice::update_hr_from_external() {
h.setSpeed(Speed.value());
h.setPower(m_watt.value());
h.setCadence(Cadence.value());
h.setSteps(StepCount.value());
Heart = appleWatchHeartRate;
qDebug() << "Current Heart from Apple Watch: " << QString::number(appleWatchHeartRate);
#endif

View File

@@ -216,6 +216,18 @@ class bluetoothdevice : public QObject {
*/
metric wattsMetric();
/**
* @brief wattsMetricforUi Show the wattage applying averaging in case the user requested this. Units: watts
*/
double wattsMetricforUI() {
QSettings settings;
bool power5s = settings.value(QZSettings::power_avg_5s, QZSettings::default_power_avg_5s).toBool();
if (power5s)
return wattsMetric().average5s();
else
return wattsMetric().value();
}
/**
* @brief changeFanSpeed Tries to change the fan speed.
* @param speed The requested fan speed. Units: depends on device
@@ -433,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);
@@ -664,6 +677,11 @@ class bluetoothdevice : public QObject {
* @brief _ergTable The current erg table
*/
ergTable _ergTable;
/**
* @brief StepCount A metric to get and set the step count. Unit: step
*/
metric StepCount;
/**
* @brief Collect the number of seconds in each zone for the current heart rate

View File

@@ -64,8 +64,24 @@ csafe::csafe() {
cmds["CSAFE_PM_GET_WORKTIME"] = populateCmd(0xa0, QList<int>(), 0x1a);
cmds["CSAFE_PM_GET_WORKDISTANCE"] = populateCmd(0xa3, QList<int>(), 0x1a);
// LIFE FITNESS specific commands
cmds["CSAFE_LF_GET_DETAIL"] = populateCmd(0xd0, QList<int>());
// Generic long commands
cmds["CSAFE_SETPROGRAM_CMD"] = populateCmd(0x24, QList<int>() << 1 << 1);
cmds["CSAFE_SETUSERINFO_CMD"] = populateCmd(0x2B, QList<int>() << 2 << 1 << 1 << 1);
cmds["CSAFE_SETLEVEL_CMD"] = populateCmd(0x2D, QList<int>() << 1);
// Generic Short Commands
cmds["CSAFE_GETSTATUS_CMD"] = populateCmd(0x80, QList<int>());
cmds["CSAFE_GOINUSE_CMD"] = populateCmd(0x85, QList<int>());
cmds["CSAFE_GETCALORIES_CMD"] = populateCmd(0xa3, QList<int>());
cmds["CSAFE_GETPROGRAM_CMD"] = populateCmd(0xA4, QList<int>());
cmds["CSAFE_GETPACE_CMD"] = populateCmd(0xa6, QList<int>());
cmds["CSAFE_GETCADENCE_CMD"] = populateCmd(0xa7, QList<int>());
cmds["CSAFE_GETHORIZONTAL_CMD"] = populateCmd(0xA1, QList<int>());
cmds["CSAFE_GETSPEED_CMD"] = populateCmd(0xA5, QList<int>());
cmds["CSAFE_GETUSERINFO_CMD"] = populateCmd(0xAB, QList<int>());
cmds["CSAFE_GETHRCUR_CMD"] = populateCmd(0xb0, QList<int>());
cmds["CSAFE_GETPOWER_CMD"] = populateCmd(0xb4, QList<int>());
@@ -87,7 +103,8 @@ csafe::csafe() {
resp[0xA0] = qMakePair(QString("CSAFE_GETTWORK_CMD"), QList<int>() << 1 << 1 << 1);
resp[0xA1] = qMakePair(QString("CSAFE_GETHORIZONTAL_CMD"), QList<int>() << 2 << 1);
resp[0xA3] = qMakePair(QString("CSAFE_GETCALORIES_CMD"), QList<int>() << 2);
resp[0xA4] = qMakePair(QString("CSAFE_GETPROGRAM_CMD"), QList<int>() << 1);
resp[0xA4] = qMakePair(QString("CSAFE_GETPROGRAM_CMD"), QList<int>() << 1 << 1);
resp[0xA5] = qMakePair(QString("CSAFE_GETSPEED_CMD"), QList<int>() << 2 << 1);
resp[0xA6] = qMakePair(QString("CSAFE_GETPACE_CMD"), QList<int>() << 2 << 1);
resp[0xA7] = qMakePair(QString("CSAFE_GETCADENCE_CMD"), QList<int>() << 2 << 1);
resp[0xAB] = qMakePair(QString("CSAFE_GETUSERINFO_CMD"), QList<int>() << 2 << 1 << 1 << 1);
@@ -105,6 +122,8 @@ csafe::csafe() {
resp[0x21] = qMakePair(QString("CSAFE_SETHORIZONTAL_CMD"), QList<int>() << 0);
resp[0x23] = qMakePair(QString("CSAFE_SETCALORIES_CMD"), QList<int>() << 0);
resp[0x24] = qMakePair(QString("CSAFE_SETPROGRAM_CMD"), QList<int>() << 0);
resp[0x2B] = qMakePair(QString("CSAFE_SETUSERINFO_CMD"), QList<int>() << 0);
resp[0x2D] = qMakePair(QString("CSAFE_SETLEVEL_CMD"), QList<int>() << 0);
resp[0x34] = qMakePair(QString("CSAFE_SETPOWER_CMD"), QList<int>() << 0);
resp[0x70] = qMakePair(QString("CSAFE_GETCAPS_CMD"), QList<int>() << 11);
@@ -129,6 +148,9 @@ csafe::csafe() {
resp[0x1A6C] =
qMakePair(QString("CSAFE_PM_GET_HEARTBEATDATA"),
QList<int>() << 1 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2 << 2);
// LIFE FITNESS specific response
resp[0xD0] = qMakePair(QString("CSAFE_LF_GET_DETAIL"), QList<int>() << 0x1c);
}
QList<QList<int>> csafe::populateCmd(int First, QList<int> Second, int Third) {
@@ -140,7 +162,9 @@ QList<QList<int>> csafe::populateCmd(int First, QList<int> Second, int Third) {
second.clear();
third.clear();
first.append(First);
foreach (int a, Second) { second.append(a); }
foreach (int a, Second) {
second.append(a);
}
third.append(Third);
ret.append(first);
ret.append(second);
@@ -155,7 +179,9 @@ QList<QList<int>> csafe::populateCmd(int First, QList<int> Second) {
first.clear();
second.clear();
first.append(First);
foreach (int a, Second) { second.append(a); }
foreach (int a, Second) {
second.append(a);
}
ret.append(first);
ret.append(second);
return ret;
@@ -195,7 +221,7 @@ QString csafe::bytes2ascii(const QVector<quint8> &raw_bytes) {
return word;
}
QByteArray csafe::write(const QStringList &arguments) {
QByteArray csafe::write(const QStringList &arguments, bool surround_msg) {
int i = 0;
QVector<quint8> message;
int wrapper = 0;
@@ -204,6 +230,10 @@ QByteArray csafe::write(const QStringList &arguments) {
while (i < arguments.size()) {
QString arg = arguments[i];
if (!cmds.contains(arg)) {
qWarning("CSAFE Command not implemented: %s", qPrintable(arg));
return QByteArray();
}
const auto &cmdprop = cmds[arg];
QVector<quint8> command;
@@ -290,27 +320,31 @@ QByteArray csafe::write(const QStringList &arguments) {
qWarning("Message is too long: %d", message.size());
}
int maxmessage = qMax(message.size() + 1, maxresponse); // report IDs
if (surround_msg) { // apply non-standard wrapping for PM3 rower
int maxmessage = qMax(message.size() + 1, maxresponse); // report IDs
if (maxmessage <= 21) {
message.prepend(0x01);
message.append(QVector<quint8>(21 - message.size()));
} else if (maxmessage <= 63) {
message.prepend(0x04);
message.append(QVector<quint8>(63 - message.size()));
} else if ((message.size() + 1) <= 121) {
message.prepend(0x02);
message.append(QVector<quint8>(121 - message.size()));
if (maxresponse > 121) {
qWarning("Response may be too long to receive. Max possible length: %d", maxresponse);
if (maxmessage <= 21) {
message.prepend(0x01);
message.append(QVector<quint8>(21 - message.size()));
} else if (maxmessage <= 63) {
message.prepend(0x04);
message.append(QVector<quint8>(63 - message.size()));
} else if ((message.size() + 1) <= 121) {
message.prepend(0x02);
message.append(QVector<quint8>(121 - message.size()));
if (maxresponse > 121) {
qWarning("Response may be too long to receive. Max possible length: %d", maxresponse);
}
} else {
qWarning("Message too long. Message length: %d", message.size());
message.clear();
}
} else {
qWarning("Message too long. Message length: %d", message.size());
message.clear();
}
QByteArray ret;
foreach (int a, message) { ret.append(a); }
foreach (int a, message) {
ret.append(a);
}
return ret;
}
@@ -325,9 +359,7 @@ QVector<quint8> csafe::check_message(QVector<quint8> message) {
quint8 stuffvalue = message.takeAt(i + 1);
message[i] = 0xF0 | stuffvalue;
}
checksum ^= message[i]; // calculate checksum
++i;
}
@@ -345,15 +377,21 @@ QVector<quint8> csafe::check_message(QVector<quint8> message) {
QVariantMap csafe::read(const QVector<quint8> &transmission) {
QVector<quint8> message;
bool stopfound = false;
int startflag = transmission[1];
int j = 0;
if (startflag == Extended_Frame_Start_Flag) {
j = 4;
} else if (startflag == Standard_Frame_Start_Flag) {
j = 2;
} else {
while (j < transmission.size()) {
int startflag = transmission[j];
if (startflag == Extended_Frame_Start_Flag) {
j = j + 3;
break;
} else if (startflag == Standard_Frame_Start_Flag) {
++j;
break;
} else {
++j;
}
}
if (j >= transmission.size()) {
qWarning("No Start Flag found.");
return QVariantMap();
}
@@ -371,7 +409,6 @@ QVariantMap csafe::read(const QVector<quint8> &transmission) {
qWarning("No Stop Flag found.");
return QVariantMap();
}
message = check_message(message);
int status = message.takeFirst();

View File

@@ -34,7 +34,7 @@ class csafe {
public:
csafe();
QByteArray write(const QStringList &arguments);
QByteArray write(const QStringList &arguments , bool surround_msg = false); //surround_msg is for wrapping the communication in CSAFE non-standard way for some devices like PM3
QVector<quint8> check_message(QVector<quint8> message);
QVariantMap read(const QVector<quint8> &transmission);
};

View File

@@ -0,0 +1,128 @@
#include "csaferunner.h"
CsafeRunnerThread::CsafeRunnerThread() {}
CsafeRunnerThread::CsafeRunnerThread(QString deviceFileName, int sleepTime) {
setDevice(deviceFileName);
setSleepTime(sleepTime);
}
void CsafeRunnerThread::setDevice(const QString &device) { deviceName = device; }
void CsafeRunnerThread::setBaudRate(uint32_t _baudRate) { baudRate = _baudRate; }
void CsafeRunnerThread::setSleepTime(int time) { sleepTime = time; }
void CsafeRunnerThread::addRefreshCommand(const QStringList &commands) {
mutex.lock();
refreshCommands.append(commands);
mutex.unlock();
}
void CsafeRunnerThread::sendCommand(const QStringList &commands) {
mutex.lock();
if (commandQueue.size() < MAX_QUEUE_SIZE) {
commandQueue.enqueue(commands);
mutex.unlock();
} else {
qDebug() << "CSAFE port commands QUEUE FULL. Dropping commands" << commands;
}
}
void CsafeRunnerThread::run() {
int rc = 0;
SerialHandler *serial = SerialHandler::create(deviceName, baudRate);
serial->setEndChar(0xf2); // end of frame for CSAFE
serial->setTimeout(1200); // CSAFE spec specifies 1s timeout
csafe *csafeInstance = new csafe();
int connectioncounter = 20; // counts timeouts. If 10 timeouts in a row, then the port is closed and reopened
int refresh_nr = -1;
QStringList refreshCommand = {};
while (1) {
if (connectioncounter > 10 || !serial->isOpen()) {
serial->closePort();
rc = serial->openPort();
if (rc != 0) {
emit portAvailable(false);
connectioncounter++;
qDebug() << "Error opening serial port " << deviceName << "rc=" << rc << " sleeping for "
<< "5s";
QThread::msleep(5000);
continue;
} else {
emit portAvailable(true);
connectioncounter = 0;
}
}
int elapsed = 0;
while (elapsed < sleepTime || sleepTime == -1) {
QThread::msleep(50);
elapsed += 50;
// TODO: does not seem to work with netsocket as intended. (no data available)
// Needs further testing, maybe because the port is already closed and needs to remain open.
// No issue for current implementations as they do not use unsolicited slave data / cmdAutoUpload .
if (serial->dataAvailable() > 0 || !commandQueue.isEmpty()) {
qDebug() << "CSAFE port data available. " << serial->dataAvailable() << " bytes"
<< "commands in queue: " << commandQueue.size();
break;
}
}
QByteArray ret;
mutex.lock();
if (!commandQueue.isEmpty()) {
ret = csafeInstance->write(commandQueue.dequeue());
qDebug() << "CSAFE port commands processed from queue. Remaining commands in queue: "
<< commandQueue.size();
} else {
if (!(elapsed < sleepTime) || !refreshCommands.isEmpty()) {
if (refreshCommands.length() > 0) {
refresh_nr++;
if (refresh_nr >= refreshCommands.length()) {
refresh_nr = 0;
}
QStringList refreshCommand = refreshCommands[refresh_nr];
ret = csafeInstance->write(refreshCommand);
}
}
}
mutex.unlock();
if (!ret.isEmpty()) { // we have commands to send
qDebug() << "CSAFE >> " << ret.toHex(' ');
rc = serial->rawWrite((uint8_t *)ret.data(), ret.length());
if (rc < 0) {
qDebug() << "Error writing serial port " << deviceName << "rc=" << rc;
connectioncounter++;
continue;
}
} else {
qDebug() << "CSAFE Slave unsolicited data present.";
}
static uint8_t rx[120];
rc = serial->rawRead(rx, 120, true);
if (rc > 0) {
qDebug() << "CSAFE << " << QByteArray::fromRawData((const char *)rx, rc).toHex(' ') << " (" << rc << ")";
connectioncounter = 0;
} else {
qDebug() << "Error reading serial port " << deviceName << " rc=" << rc;
connectioncounter++;
continue;
}
QVector<quint8> v;
for (int i = 0; i < rc; i++)
v.append(rx[i]);
QVariantMap frame = csafeInstance->read(v);
emit onCsafeFrame(frame);
memset(rx, 0x00, sizeof(rx));
}
serial->closePort();
}

View File

@@ -0,0 +1,44 @@
#include "csafe.h"
#include "qzsettings.h"
#include "serialhandler.h"
#include <QDebug>
#include <QMutex>
#include <QQueue>
#include <QSettings>
#include <QThread>
#include <QVariantMap>
#include <QVector>
#define MAX_QUEUE_SIZE 100
/**
* @brief The CsafeRunnerThread class is a thread that runs the CSAFE protocol interaction.
* It periodically sends the refresh commands processes the responses.
* It also allows sending additional commands to the device.
*/
class CsafeRunnerThread : public QThread {
Q_OBJECT
public:
explicit CsafeRunnerThread();
explicit CsafeRunnerThread(QString deviceFileName, int sleepTime = 200);
void setDevice(const QString &device);
void setBaudRate(uint32_t baudRate = 9600);
void setSleepTime(int time);
void addRefreshCommand(const QStringList &commands);
void run();
public slots:
void sendCommand(const QStringList &commands);
signals:
void onCsafeFrame(const QVariantMap &frame);
void portAvailable(bool available);
private:
QString deviceName;
uint32_t baudRate = 9600;
int sleepTime = 200;
QList<QStringList> refreshCommands;
QQueue<QStringList> commandQueue;
QMutex mutex;
};

View File

@@ -0,0 +1,101 @@
#include "csafeutility.h"
// Static data map initialization
const QMap<int, QPair<QString, double>> CSafeUtility::unitData = {{1, {"mile", 1609.34}},
{2, {"0.1 mile", 160.934}},
{3, {"0.01 mile", 16.0934}},
{4, {"0.001 mile", 1.60934}},
{5, {"ft", 0.3048}},
{6, {"inch", 0.0254}},
{7, {"lbs.", 0.453592}},
{8, {"0.1 lbs.", 0.0453592}},
{10, {"10 ft", 3.048}},
{16, {"mile/hour", 0.44704}},
{17, {"0.1 mile/hour", 0.044704}},
{18, {"0.01 mile/hour", 0.0044704}},
{19, {"ft/minute", 0.00508}},
{33, {"Km", 1000}},
{34, {"0.1 km", 100}},
{35, {"0.01 km", 10}},
{36, {"Meter", 1}},
{37, {"0.1 meter", 0.1}},
{38, {"Cm", 0.01}},
{39, {"Kg", 1}},
{40, {"0.1 kg", 0.1}},
{48, {"Km/hour", 0.277778}},
{49, {"0.1 Km/hour", 0.0277778}},
{50, {"0.01 Km/hour", 0.00277778}},
{51, {"Meter/minute", 0.0166667}},
{55, {"Minutes/mile", 1}},
{56, {"Minutes/km", 1}},
{57, {"Seconds/km", 1}},
{58, {"Seconds/mile", 1}},
{65, {"floors", 1}},
{66, {"0.1 floors", 0.1}},
{67, {"steps", 1}},
{68, {"revolutions", 1}},
{69, {"strides", 1}},
{70, {"strokes", 1}},
{71, {"beats", 1}},
{72, {"calories", 1}},
{73, {"Kp", 1}},
{74, {"% grade", 1}},
{75, {"0.01 % grade", 0.01}},
{76, {"0.1 % grade", 0.1}},
{79, {"0.1 floors/minute", 0.1}},
{80, {"floors/minute", 1}},
{81, {"steps/minute", 1}},
{82, {"revs/minute", 1}},
{83, {"strides/minute", 1}},
{84, {"stokes/minute", 1}},
{85, {"beats/minute", 1}},
{86, {"calories/minute", 1}},
{87, {"calories/hour", 1}},
{88, {"Watts", 1}},
{89, {"Kpm", 1}},
{90, {"Inch-Lb", 1}},
{91, {"Foot-Lb", 1}},
{92, {"Newton-Meters", 1}},
{97, {"Amperes", 1}},
{98, {"0.001 Amperes", 0.001}},
{99, {"Volts", 1}},
{100, {"0.001 Volts", 0.001}}};
QString CSafeUtility::getUnitName(int unitCode) {
if (unitData.contains(unitCode)) {
return unitData[unitCode].first; // Return the description
}
return "Unknown unit";
}
double CSafeUtility::convertToStandard(int unitCode, double value) {
if (unitData.contains(unitCode)) {
return value * unitData[unitCode].second; // Apply the conversion factor
}
return value; // Return the original value if no conversion factor is available
}
QString CSafeUtility::statusByteToText(int status) {
switch (status) {
case 0x00:
return "Error";
case 0x01:
return "Ready";
case 0x02:
return "Idle";
case 0x03:
return "Have ID";
case 0x05:
return "In Use";
case 0x06:
return "Pause";
case 0x07:
return "Finish";
case 0x08:
return "Manual";
case 0x09:
return "Off line";
default:
return "Unknown";
}
}

View File

@@ -0,0 +1,44 @@
/*
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* This emulates a serial port over a network connection.
* e.g. as created by ser2net or hardware serial to ethernet converters
*
*/
#ifndef CSAFEUTILITY_H
#define CSAFEUTILITY_H
#include <cstdint> // For uint8_t
#include <QString>
#include <QMap>
#include <QPair>
/**
* @brief This class contains some utility functions supporting the CSAFE protocol
*/
class CSafeUtility {
public:
static QString getUnitName(int unitCode);
static double convertToStandard(int unitCode, double value);
static QString statusByteToText(int statusByte);
private:
// Static map to hold unit data
static const QMap<int, QPair<QString, double>> unitData;
};
#endif // CSAFEUTILITY_H

View File

@@ -0,0 +1,28 @@
#include "kalmanfilter.h"
KalmanFilter::KalmanFilter(double measure_error, double estimate_error, double process_noise_q, double initial_Value) {
setMeasurementError(measure_error);
setEstimateError(estimate_error);
setProcessNoise(process_noise_q);
estimate = initial_Value;
}
KalmanFilter::~KalmanFilter() {};
double KalmanFilter::updateEstimate(double measurement) {
estimate_error += process_noise_q;
gain = estimate_error / (estimate_error + measure_error);
estimate += gain * (measurement - estimate);
estimate_error *= (1 - gain);
return estimate;
}
void KalmanFilter::setMeasurementError(double measure_err) { measure_error = measure_err; }
void KalmanFilter::setEstimateError(double estimate_err) { estimate_error = estimate_err; }
void KalmanFilter::setProcessNoise(double noise_q) { process_noise_q = noise_q; }
double KalmanFilter::getGain() { return gain; }
double KalmanFilter::getEstimateError() { return estimate_error; }

View File

@@ -0,0 +1,52 @@
/*
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* This emulates a serial port over a network connection.
* e.g. as created by ser2net or hardware serial to ethernet converters
*
*/
#ifndef KALMAN_FILTER_H
#define KALMAN_FILTER_H
#include <math.h>
/**
* @brief A simple Kalman filter for smoothing noisy data.
*/
class KalmanFilter {
public:
KalmanFilter(double measure_error, double estimate_error, double process_noise_q = 0, double initial_Value = 0);
~KalmanFilter();
double updateEstimate(double measurement);
void setMeasurementError(double measure_err);
void setEstimateError(double estimate_err);
void setProcessNoise(double noise_q);
double getGain();
double getEstimateError();
private:
double measure_error;
double estimate_error;
double process_noise_q;
double estimate = 0;
double gain = 0;
};
#endif

View File

@@ -0,0 +1,118 @@
#include "netserial.h"
NetSerial::NetSerial(QString deviceFilename) : socket(new QTcpSocket()), _timeout(1000), endChar('\n') {
setDevice(deviceFilename);
}
NetSerial::~NetSerial() {
closePort();
delete socket;
}
void NetSerial::setTimeout(int timeout) { this->_timeout = timeout; }
void NetSerial::setDevice(const QString &devname) {
if (!devname.isEmpty()) {
deviceFilename = devname;
parseDeviceFilename(devname);
}
}
void NetSerial::setEndChar(uint8_t endChar) { this->endChar = endChar; }
bool NetSerial::isOpen() const { return socket->state() == QAbstractSocket::ConnectedState; }
int NetSerial::openPort() {
if (serverAddress.isEmpty() || serverPort == 0) {
qDebug() << "Invalid server address or port";
return -1;
}
socket->connectToHost(serverAddress, serverPort);
if (!socket->waitForConnected(_timeout)) {
qDebug() << "Failed to connect to server:" << socket->errorString();
return -1;
}
return 0;
}
int NetSerial::closePort() {
if (isOpen()) {
socket->disconnectFromHost();
if (socket->state() != QAbstractSocket::UnconnectedState) {
socket->waitForDisconnected(_timeout);
}
}
return 0;
}
int NetSerial::dataAvailable() {
if (!isOpen()) {
qDebug() << "Socket not connected.";
return -1;
}
if (socket->bytesAvailable() > 0) {
qDebug() << "Socket data is available!!!!!!!!!!!!!!.";
}
return socket->bytesAvailable();
}
int NetSerial::rawWrite(uint8_t *bytes, int size) {
if (!isOpen()) {
qDebug() << "Socket not connected.";
return -1;
}
QByteArray data(reinterpret_cast<const char *>(bytes), size);
qint64 bytesWritten = socket->write(data);
if (bytesWritten == -1) {
qDebug() << "Failed to write to socket:" << socket->errorString();
return -1;
}
if (!socket->waitForBytesWritten(_timeout)) {
qDebug() << "Write operation timed out.";
return -1;
}
return static_cast<int>(bytesWritten);
}
int NetSerial::rawRead(uint8_t bytes[], int size, bool line) {
if (!isOpen()) {
qDebug() << "Socket not connected.";
return -1;
}
QByteArray buffer;
while (buffer.size() < size) {
if (!socket->waitForReadyRead(_timeout)) {
qDebug() << "Read operation timed out.";
return buffer.size() > 0 ? buffer.size() : -1;
}
buffer.append(socket->read(size - buffer.size()));
if (line && buffer.contains(static_cast<char>(endChar))) {
int index = buffer.indexOf(static_cast<char>(endChar)) + 1;
memcpy(bytes, buffer.data(), index);
return index;
}
}
memcpy(bytes, buffer.data(), buffer.size());
return buffer.size();
}
bool NetSerial::parseDeviceFilename(const QString &filename) {
// Format: "server:port", e.g., "127.0.0.1:12345"
QStringList parts = filename.split(':');
if (parts.size() == 2) {
serverAddress = parts[0];
serverPort = parts[1].toUShort();
return true;
} else {
qDebug() << "Invalid device filename format. Expected 'server:port'.";
return false;
}
}

View File

@@ -0,0 +1,61 @@
/*
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
*
*/
#ifndef NETSERIAL_H
#define NETSERIAL_H
#include "serialhandler.h"
#include <QString>
#include <QHostAddress>
#include <QTcpSocket>
#include <QDebug>
/**
* @brief This is a simple implementation of serial port emulation over TCP
* It emulates a serial port over a network connection.
* e.g. as created by ser2net or hardware serial to ethernet converters
*/
class NetSerial : public SerialHandler {
public:
NetSerial(QString deviceFilename);
~NetSerial() ;
int openPort() override;
int closePort() override;
int dataAvailable() override;
int rawWrite(uint8_t *bytes, int size) override;
int rawRead(uint8_t bytes[], int size, bool line = false) override;
bool isOpen() const override;
void setTimeout(int timeout) override;
void setEndChar(uint8_t endChar) override;
void setDevice(const QString &devname) override;
private:
QString deviceFilename;
QString serverAddress;
quint16 serverPort;
QTcpSocket *socket;
int _timeout = 1000; // Timeout in milliseconds
uint8_t endChar = '\n';
bool parseDeviceFilename(const QString &filename);
};
#endif // NETSERIAL_H

View File

@@ -0,0 +1,13 @@
#include "serialhandler.h"
#include "netserial.h"
#include "serialport.h"
SerialHandler *SerialHandler::create(const QString &deviceFilename, uint32_t baudRate) {
if (deviceFilename.contains(':')) {
qDebug() << "Using NetSerial for device:" << deviceFilename;
return new NetSerial(deviceFilename);
} else {
qDebug() << "Using Serialport for device:" << deviceFilename;
return new Serialport(deviceFilename, baudRate);
}
}

View File

@@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*
* This emulates a serial port over a network connection.
* e.g. as created by ser2net or hardware serial to ethernet converters
*
*/
#ifndef SERIALHANDLER_H
#define SERIALHANDLER_H
#include <sys/types.h>
#include <QString>
#include <QDebug>
/**
* @brief This is a parent class for serial port handlers
* It defines the common interface for serial operations over physical serial port or network
*/
class SerialHandler {
public:
virtual ~SerialHandler() = 0; // Pure virtual destructor
// Factory method to create the appropriate serial handler
static SerialHandler *create(const QString &deviceFilename, uint32_t baudRate);
// Abstract interface for serial operations
virtual int openPort() = 0;
virtual int closePort() = 0;
virtual int rawWrite(uint8_t *bytes, int size) = 0;
virtual int rawRead(uint8_t bytes[], int size, bool line = false) = 0;
virtual int dataAvailable() = 0;
virtual bool isOpen() const = 0;
// Common configuration methods
virtual void setDevice(const QString &devname) = 0;
virtual void setTimeout(int timeout) = 0;
virtual void setEndChar(uint8_t endChar) = 0;
protected:
// Protected constructor to prevent direct instantiation of this abstract class
SerialHandler() = default;
};
inline SerialHandler::~SerialHandler() {} // Definition of the pure virtual destructor
#endif // SERIALHANDLER_H

View File

@@ -0,0 +1,381 @@
#include "serialport.h"
/* ----------------------------------------------------------------------
* CONSTRUCTOR/DESTRUCTOR
* ---------------------------------------------------------------------- */
Serialport::Serialport(QString deviceFilename, uint32_t baudRate) {
setDevice(deviceFilename);
this->baudRate = baudRate;
}
Serialport::~Serialport() {}
void Serialport::setTimeout(int timeout) { this->_timeout = timeout; }
void Serialport::setDevice(const QString &devname) {
if (!devname.isEmpty()) {
deviceFilename = devname;
}
}
void Serialport::setEndChar(uint8_t endChar) { this->endChar = endChar; }
bool Serialport::isOpen() const {
#ifdef WIN32
return (devicePort != INVALID_HANDLE_VALUE); // Checks if the Windows handle is valid
#else
return (devicePort != -1); // Checks if the file descriptor is valid on Linux/macOS
#endif
}
int Serialport::closePort() {
#ifdef WIN32
return (int)!CloseHandle(devicePort);
#else
tcflush(devicePort, TCIOFLUSH); // Clear out the buffer
return close(devicePort);
#endif
}
int Serialport::openPort() {
#ifdef Q_OS_ANDROID
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/Usbserial", "open",
"(Landroid/content/Context;)V", QtAndroid::androidContext().object());
#elif !defined(WIN32)
// LINUX AND MAC USES TERMIO / IOCTL / STDIO
#if defined(Q_OS_MACX)
int ldisc = TTYDISC;
#else
int ldisc = N_TTY; // LINUX
#endif
if ((devicePort = open(deviceFilename.toLatin1(), O_RDWR | O_NOCTTY | O_NONBLOCK)) == -1)
return errno;
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
if (ioctl(devicePort, TIOCSETD, &ldisc) == -1)
return errno;
// get current settings for the port
tcgetattr(devicePort, &deviceSettings);
// set raw mode i.e. ignbrk, brkint, parmrk, istrip, inlcr, igncr, icrnl, ixon
// noopost, cs8, noecho, noechonl, noicanon, noisig, noiexn
cfmakeraw(&deviceSettings);
// set baud rate
switch (baudRate) {
case 2400:
cfsetispeed(&deviceSettings, B2400);
cfsetospeed(&deviceSettings, B2400);
break;
case 9600:
cfsetispeed(&deviceSettings, B9600);
cfsetospeed(&deviceSettings, B9600);
break;
case 19200:
cfsetispeed(&deviceSettings, B19200);
cfsetospeed(&deviceSettings, B19200);
break;
case 38400:
cfsetispeed(&deviceSettings, B38400);
cfsetospeed(&deviceSettings, B38400);
break;
case 57600:
cfsetispeed(&deviceSettings, B57600);
cfsetospeed(&deviceSettings, B57600);
break;
case 115200:
cfsetispeed(&deviceSettings, B115200);
cfsetospeed(&deviceSettings, B115200);
break;
default:
qWarning("Invalid baud rate, defaulting to 9600");
cfsetispeed(&deviceSettings, B9600);
cfsetospeed(&deviceSettings, B9600);
break;
}
// further attributes
deviceSettings.c_iflag &=
~(IGNBRK | BRKINT | ICRNL | INLCR | PARMRK | INPCK | ICANON | ISTRIP | IXON | IXOFF | IXANY);
deviceSettings.c_iflag |= IGNPAR;
deviceSettings.c_cflag &= (~CSIZE & ~CSTOPB);
deviceSettings.c_oflag = 0;
#if defined(Q_OS_MACX)
deviceSettings.c_cflag &= (~CCTS_OFLOW & ~CRTS_IFLOW); // no hardware flow control
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
#else
deviceSettings.c_cflag &= (~CRTSCTS); // no hardware flow control
deviceSettings.c_cflag |= (CS8 | CLOCAL | CREAD | HUPCL);
#endif
deviceSettings.c_lflag = 0;
deviceSettings.c_cc[VSTART] = 0x11;
deviceSettings.c_cc[VSTOP] = 0x13;
deviceSettings.c_cc[VEOF] = 0x20;
deviceSettings.c_cc[VMIN] = 0;
deviceSettings.c_cc[VTIME] = 0;
// set those attributes
if (tcsetattr(devicePort, TCSANOW, &deviceSettings) == -1)
return errno;
tcgetattr(devicePort, &deviceSettings);
tcflush(devicePort, TCIOFLUSH); // clear out the garbage
#else
// WINDOWS USES SET/GETCOMMSTATE AND READ/WRITEFILE
COMMTIMEOUTS timeouts; // timeout settings on serial ports
// if deviceFilename references a port above COM9
// then we need to open "\\.\COMX" not "COMX"
QString portSpec;
int portnum = deviceFilename.midRef(3).toString().toInt();
if (portnum < 10)
portSpec = deviceFilename;
else
portSpec = "\\\\.\\" + deviceFilename;
wchar_t deviceFilenameW[32]; // \\.\COM32 needs 9 characters, 32 should be enough?
MultiByteToWideChar(CP_ACP, 0, portSpec.toLatin1(), -1, (LPWSTR)deviceFilenameW, sizeof(deviceFilenameW));
// win32 commport API
devicePort = CreateFile(deviceFilenameW, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_DELETE | FILE_SHARE_WRITE | FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (devicePort == INVALID_HANDLE_VALUE)
return -1;
if (GetCommState(devicePort, &deviceSettings) == false)
return -1;
switch (baudRate) {
case 2400:
deviceSettings.BaudRate = CBR_2400;
break;
case 9600:
deviceSettings.BaudRate = CBR_9600;
break;
case 19200:
deviceSettings.BaudRate = CBR_19200;
break;
case 38400: // 38400
deviceSettings.BaudRate = CBR_38400;
break;
case 57600: // 57600
deviceSettings.BaudRate = CBR_57600;
break;
case 115200: // 115200
deviceSettings.BaudRate = CBR_115200;
break;
default:
qWarning("Invalid baud rate, defaulting to 9600");
deviceSettings.BaudRate = CBR_9600;
break;
}
// so we've opened the comm port lets set it up for
deviceSettings.fParity = NOPARITY;
deviceSettings.ByteSize = 8;
deviceSettings.StopBits = ONESTOPBIT;
deviceSettings.XonChar = 11;
deviceSettings.XoffChar = 13;
deviceSettings.EofChar = 0x0;
deviceSettings.ErrorChar = 0x0;
deviceSettings.EvtChar = 0x0;
deviceSettings.fBinary = true;
deviceSettings.fOutX = 0;
deviceSettings.fInX = 0;
deviceSettings.XonLim = 0;
deviceSettings.XoffLim = 0;
deviceSettings.fRtsControl = RTS_CONTROL_ENABLE;
deviceSettings.fDtrControl = DTR_CONTROL_ENABLE;
deviceSettings.fOutxCtsFlow = FALSE; // TRUE;
if (SetCommState(devicePort, &deviceSettings) == false) {
CloseHandle(devicePort);
return -1;
}
timeouts.ReadIntervalTimeout = 0;
timeouts.ReadTotalTimeoutConstant = 1000;
timeouts.ReadTotalTimeoutMultiplier = 50;
timeouts.WriteTotalTimeoutConstant = 2000;
timeouts.WriteTotalTimeoutMultiplier = 0;
SetCommTimeouts(devicePort, &timeouts);
#endif
// success
return 0;
}
int Serialport::dataAvailable() {
if (!isOpen()) {
return -1;
}
#ifdef Q_OS_ANDROID
jint len = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/Usbserial", "readLen", "()I");
static_cast<size_t>(len);
#elif defined(WIN32)
COMSTAT cs;
if (!ClearCommError(devicePort, NULL, &cs)) {
return -1;
}
return static_cast<size_t>(cs.cbInQue);
#else
int count = 0;
if (-1 == ioctl(devicePort, FIONREAD, &count)) {
return 0;
} else {
return static_cast<size_t>(count);
}
#endif
}
int Serialport::rawWrite(uint8_t *bytes, int size) {
qDebug() << "Writing data:" << QByteArray((const char *)bytes, size).toHex();
int rc = 0;
if (!isOpen()) {
qDebug() << "Port not open";
return -1;
}
#ifdef Q_OS_ANDROID
QAndroidJniEnvironment env;
jbyteArray d = env->NewByteArray(size);
jbyte *b = env->GetByteArrayElements(d, 0);
for (int i = 0; i < size; i++)
b[i] = bytes[i];
env->SetByteArrayRegion(d, 0, size, b);
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/Usbserial", "write", "([B)V", d);
#elif defined(WIN32)
DWORD cBytes;
rc = WriteFile(devicePort, bytes, size, &cBytes, NULL);
if (!rc)
return -1;
return rc;
#else
int ibytes;
ioctl(devicePort, FIONREAD, &ibytes);
// timeouts are less critical for writing, since vols are low
rc = write(devicePort, bytes, size);
// but it is good to avoid buffer overflow since the
// computrainer microcontroller has almost no RAM
if (rc != -1)
tcdrain(devicePort); // wait till its gone.
ioctl(devicePort, FIONREAD, &ibytes);
#endif
return rc;
}
int Serialport::rawRead(uint8_t bytes[], int size, bool line) {
int rc = 0;
#ifdef Q_OS_ANDROID
int fullLen = 0;
cleanFrame = false;
// previous buffer?
while (bufRX.count()) {
bytes[fullLen++] = bufRX.at(0);
bufRX.removeFirst();
qDebug() << "byte popped from rxBuf";
if (fullLen >= size) {
qDebug() << size << QByteArray((const char *)bytes, size).toHex(' ');
return size;
}
}
QAndroidJniEnvironment env;
while (fullLen < size) {
QAndroidJniObject dd =
QAndroidJniObject::callStaticObjectMethod("org/cagnulen/qdomyoszwift/Usbserial", "read", "()[B");
jint len = QAndroidJniObject::callStaticMethod<jint>("org/cagnulen/qdomyoszwift/Usbserial", "readLen", "()I");
jbyteArray d = dd.object<jbyteArray>();
jbyte *b = env->GetByteArrayElements(d, 0);
if (len + fullLen > size) {
QByteArray tmpDebug;
qDebug() << "buffer overflow! Truncate from" << len + fullLen << "requested" << size;
/*for(int i=0; i<len; i++) {
qDebug() << b[i];
}*/
for (int i = fullLen; i < size; i++) {
bytes[i] = b[i - fullLen];
}
for (int i = size; i < len + fullLen; i++) {
jbyte bb = b[i - fullLen];
bufRX.append(bb);
tmpDebug.append(bb);
}
qDebug() << len + fullLen - size << "bytes to the rxBuf" << tmpDebug.toHex(' ');
qDebug() << size << QByteArray((const char *)b, size).toHex(' ');
return size;
}
for (int i = fullLen; i < len + fullLen; i++) {
bytes[i] = b[i - fullLen];
}
qDebug() << len << QByteArray((const char *)b, len).toHex(' ');
fullLen += len;
}
qDebug() << "FULL BUFFER RX: << " << fullLen << QByteArray((const char *)bytes, size).toHex(' ');
cleanFrame = true;
return fullLen;
#elif defined(WIN32)
Q_UNUSED(size);
// Readfile deals with timeouts and readyread issues
DWORD cBytes;
rc = ReadFile(devicePort, bytes, 7, &cBytes, NULL);
if (rc)
return (int)cBytes;
else
return (-1);
#else
int timeout = 0, i = 0;
uint8_t byte;
// read one byte at a time sleeping when no data ready
// until we timeout waiting then return error
for (i = 0; i < size; i++) {
timeout = 0;
rc = 0;
while (rc == 0 && timeout < _timeout) {
rc = read(devicePort, &byte, 1);
if (rc == -1)
return -1;
else if (rc == 0) {
QThread::msleep(50); // sleep for 1/20th of a second
// std::this_thread::sleep_for(std::chrono::milliseconds(50));
timeout += 50;
} else {
bytes[i] = byte;
if (line && endChar == byte) {
return i + 1;
}
}
}
if (timeout >= _timeout)
return i > 0 ? i : -1;
}
qDebug() << i << QString::fromLocal8Bit((const char *)bytes, i);
return i;
#endif
}

View File

@@ -0,0 +1,98 @@
/*
* Copyright (c) 2009 Mark Liversedge (liversedge@gmail.com),
2024 Marcel Verpaalen
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef _SERIALPORT_h
#define _SERIALPORT_h
#include "serialhandler.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#include <QAndroidJniObject>
#endif
#include <QString>
#include <QThread>
#include <QFile>
#include <QMutex>
#include <QDebug>
#ifdef WIN32
#include <windows.h>
#include <winbase.h>
#else
#include <sys/ioctl.h>
#include <termios.h>
#include <unistd.h>
#ifndef N_TTY // for OpenBSD, this is a hack
#define N_TTY 0
#endif
#endif
#include <errno.h>
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
class Serialport : public SerialHandler {
public:
Serialport(QString deviceFilename, uint32_t baudRate);
~Serialport() override;
// Device management
void setDevice(const QString &devname) override;
void setTimeout(int timeout) override;
void setEndChar(uint8_t endChar) override;
// Port control
int openPort() override;
int dataAvailable() override;
int closePort() override;
// Data transfer
int rawWrite(uint8_t *bytes, int size) override;
int rawRead(uint8_t bytes[], int size, bool line = false) override;
bool isOpen() const override;
private:
uint32_t baudRate = 9600;
uint8_t endChar = 0x0D;
int _timeout = 1200;
QString deviceFilename;
// device port
#ifdef WIN32
HANDLE devicePort; // file descriptor for reading from com3
DCB deviceSettings; // serial port settings baud rate et al
#else
int devicePort; // unix!!
struct termios deviceSettings; // unix!!
#endif
#ifdef Q_OS_ANDROID
QList<jbyte> bufRX;
bool cleanFrame = false;
#endif
};
#endif // _SERIALPORT_h

View File

@@ -0,0 +1,331 @@
#include "csafeelliptical.h"
using namespace std::chrono_literals;
csafeelliptical::csafeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice,
int8_t bikeResistanceOffset, double bikeResistanceGain) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->noVirtualDevice = noVirtualDevice;
connect(refresh, &QTimer::timeout, this, &csafeelliptical::update);
refresh->start(200ms);
QString deviceFilename =
settings.value(QZSettings::csafe_elliptical_port, QZSettings::default_csafe_elliptical_port).toString();
CsafeRunnerThread *csafeRunner = new CsafeRunnerThread(deviceFilename);
setupCommands(csafeRunner);
connect(csafeRunner, &CsafeRunnerThread::portAvailable, this, &csafeelliptical::portAvailable);
connect(csafeRunner, &CsafeRunnerThread::onCsafeFrame, this, &csafeelliptical::onCsafeFrame);
connect(this, &csafeelliptical::sendCsafeCommand, csafeRunner, &CsafeRunnerThread::sendCommand,
Qt::QueuedConnection);
csafeRunner->start();
distanceReceived = 0;
kalman = new KalmanFilter(1, .01, .75, 0); // measure error, estimate error, process noise q, initial value
}
void csafeelliptical::setupCommands(CsafeRunnerThread *runner) {
QStringList command;
command << "CSAFE_GETPOWER_CMD";
command << "CSAFE_GETSPEED_CMD";
command << "CSAFE_GETCALORIES_CMD";
command << "CSAFE_GETHRCUR_CMD";
command << "CSAFE_GETHORIZONTAL_CMD";
runner->addRefreshCommand(command);
runner->addRefreshCommand(QStringList() << "CSAFE_LF_GET_DETAIL");
runner->addRefreshCommand(QStringList() << "CSAFE_GETPROGRAM_CMD");
}
void csafeelliptical::setupWorkout() {
emit sendCsafeCommand(QStringList() << "CSAFE_GETUSERINFO_CMD");
QStringList command = {"CSAFE_SETUSERINFO_CMD",
QString::number(settings.value(QZSettings::weight, QZSettings::default_weight).toInt()),
"39", QString::number(settings.value(QZSettings::age, QZSettings::default_age).toInt()),
"1"}; // weight,weight unit,age,gender
emit sendCsafeCommand(command);
emit sendCsafeCommand(QStringList() << "CSAFE_SETPROGRAM_CMD" << "4" << "5");
emit sendCsafeCommand(QStringList() << "CSAFE_GOINUSE_CMD");
}
void csafeelliptical::onCsafeFrame(const QVariantMap &csafeFrame) {
// qDebug() << "Current CSAFE frame received:" << csafeFrame;
if (csafeFrame["CSAFE_GETCADENCE_CMD"].isValid()) {
onCadence(csafeFrame["CSAFE_GETCADENCE_CMD"].value<QVariantList>()[0].toDouble());
}
if (csafeFrame["CSAFE_GETSPEED_CMD"].isValid()) {
double speed = csafeFrame["CSAFE_GETSPEED_CMD"].value<QVariantList>()[0].toDouble();
int unit = csafeFrame["CSAFE_GETSPEED_CMD"].value<QVariantList>()[1].toInt();
qDebug() << "Speed value:" << speed << "unit:" << CSafeUtility::getUnitName(unit) << "(" << unit << ")";
if (unit == 82) { // revs/minute
onCadence(speed);
} else {
onSpeed(CSafeUtility::convertToStandard(unit, speed));
}
}
if (csafeFrame["CSAFE_GETPOWER_CMD"].isValid()) {
onPower(csafeFrame["CSAFE_GETPOWER_CMD"].value<QVariantList>()[0].toDouble());
}
if (csafeFrame["CSAFE_GETHRCUR_CMD"].isValid()) {
onHeart(csafeFrame["CSAFE_GETHRCUR_CMD"].value<QVariantList>()[0].toDouble());
}
if (csafeFrame["CSAFE_GETCALORIES_CMD"].isValid()) {
onCalories(csafeFrame["CSAFE_GETCALORIES_CMD"].value<QVariantList>()[0].toDouble());
}
if (csafeFrame["CSAFE_GETHORIZONTAL_CMD"].isValid()) {
double distance = csafeFrame["CSAFE_GETHORIZONTAL_CMD"].value<QVariantList>()[0].toDouble();
int unit = csafeFrame["CSAFE_GETHORIZONTAL_CMD"].value<QVariantList>()[1].toInt();
qDebug() << "Distance value:" << distance << "unit:" << CSafeUtility::getUnitName(unit) << "(" << unit << ")"
<< CSafeUtility::convertToStandard(unit, distance);
onDistance(CSafeUtility::convertToStandard(unit, distance));
}
if (csafeFrame["CSAFE_GETPROGRAM_CMD"].isValid()) {
int resistance = csafeFrame["CSAFE_GETPROGRAM_CMD"].value<QVariantList>()[1].toUInt();
Resistance = resistance;
qDebug() << "Program:" << csafeFrame["CSAFE_GETPROGRAM_CMD"].value<QVariantList>()[0].toUInt()
<< "Current level received:" << resistance;
}
if (csafeFrame["CSAFE_GETSTATUS_CMD"].isValid()) {
uint16_t statusvalue = csafeFrame["CSAFE_GETSTATUS_CMD"].value<QVariantList>()[0].toUInt();
qDebug() << "Status value:" << statusvalue << " lastStatus:" << lastStatus
<< " Machine state from status:" << (statusvalue & 0x0f);
if (statusvalue != lastStatus) {
lastStatus = statusvalue;
char statusChar = static_cast<char>(statusvalue & 0x0f);
onStatus(statusChar);
}
}
}
void csafeelliptical::onSpeed(double speed) {
qDebug() << "Current Speed received:" << speed << " updated:" << distanceIsChanging;
if (distanceIsChanging)
Speed = speed;
}
void csafeelliptical::onPower(double power) {
qDebug() << "Current Power received:" << power << " updated:" << distanceIsChanging;
if (distanceIsChanging)
m_watt = power;
}
void csafeelliptical::onCadence(double cadence) {
qDebug() << "Current Cadence received:" << cadence << " updated:" << distanceIsChanging;
if (distanceIsChanging)
Cadence = cadence;
}
void csafeelliptical::onHeart(double hr) {
qDebug() << "Current Heart received:" << hr;
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool disable_hr_frommachinery =
settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool())
Heart = (uint8_t)KeepAwakeHelper::heart();
else
#endif
{
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
uint8_t heart = ((uint8_t)hr);
if (heart == 0 || disable_hr_frommachinery) {
update_hr_from_external();
} else
Heart = heart;
}
}
}
void csafeelliptical::onCalories(double calories) {
qDebug() << "Current Calories received:" << calories;
KCal = calories;
}
void csafeelliptical::onDistance(double distance) {
qDebug() << "Current Distance received:" << distance << " value tracker:" << distanceReceived.value();
Distance = distance / 1000.0;
if (distance != distanceReceived.value()) {
double calculated_speed = 3600 * (distance - distanceReceived.value()) /
abs(distanceReceived.lastChanged().msecsTo(QDateTime::currentDateTime()));
if (distanceIsChanging) { // skip the first distance or after pause otherwise you get enormous speed values
// Speed is calculated from the distance and is bit erratic. The kalman filter is used to smooth it out
Speed = kalman->updateEstimate(calculated_speed);
}
qDebug() << "Distance received:" << distance << " Previous:" << distanceReceived.value()
<< " time(ms):" << abs(distanceReceived.lastChanged().msecsTo(QDateTime::currentDateTime()))
<< " Calculated Speed:" << calculated_speed << " New speed:" << Speed.value()
<< " updated:" << distanceIsChanging;
distanceReceived = distance;
distanceIsChanging = true;
} else if (abs(distanceReceived.lastChanged().secsTo(QDateTime::currentDateTime())) > 15) {
distanceIsChanging = false;
m_watt = 0.0;
Cadence = 0.0;
Speed = 0.0;
}
}
void csafeelliptical::onStatus(char status) {
QString statusString = CSafeUtility::statusByteToText(status);
qDebug() << "Current Status code:" << status << " status: " << statusString;
if (status == 0x06) {
// pause
paused = true;
m_watt = 0.0;
Cadence = 0.0;
Speed = 0.0;
distanceIsChanging = false;
} else {
paused = false;
}
}
void csafeelliptical::portAvailable(bool available) {
if (available) {
qDebug() << "CSAFE port available";
_connected = true;
setupWorkout();
} else {
qDebug() << "CSAFE port not available";
_connected = false;
}
}
void csafeelliptical::update() {
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
update_metrics(true, watts());
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
// ******************************************* virtual treadmill init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && !virtual_device_rower) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
bool virtual_device_force_bike =
settings.value(QZSettings::virtual_device_force_bike, QZSettings::default_virtual_device_force_bike)
.toBool();
if (virtual_device_enabled) {
if (!virtual_device_force_bike) {
debug("creating virtual treadmill interface...");
auto virtualTreadmill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadmill, &virtualtreadmill::debug, this, &csafeelliptical::debug);
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
&csafeelliptical::changeInclinationRequested);
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
debug("creating virtual bike interface...");
auto virtualBike = new virtualbike(this);
// auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
connect(virtualBike, &virtualbike::changeInclination, this,
&csafeelliptical::changeInclinationRequested);
connect(virtualBike, &virtualbike::ftmsCharacteristicChanged, this,
&csafeelliptical::ftmsCharacteristicChanged);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
}
} else {
debug("not creating virtual interface... not enabled");
}
// ********************************************************************************************************
}
if (!firstStateChanged) {
emit connectedAndDiscovered();
}
firstStateChanged = 1;
// ********************************************************************************************************
if (!noVirtualDevice) {
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
Heart = (uint8_t)KeepAwakeHelper::heart();
debug("Current Heart: " + QString::number(Heart.value()));
}
#endif
if (heartRateBeltName.startsWith(QStringLiteral("Disabled"))) {
update_hr_from_external();
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
}
if (requestResistance != -1 && requestResistance != currentResistance().value()) {
if (!noWriteResistance) {
changeResistance(requestResistance);
}
}
}
void csafeelliptical::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
QByteArray b = newValue;
qDebug() << "Routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
}
void csafeelliptical::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
qDebug() << "Requested grade:" << grade << " percentage:" << percentage;
}
void csafeelliptical::changeResistance(resistance_t res) {
if (res < 0)
res = 0;
if (res > 25)
res = 25;
QStringList resistanceCommand = {"CSAFE_SETLEVEL", QString::number(res)};
emit sendCsafeCommand(resistanceCommand);
qDebug() << "Send resistance update to device. Requested resitance: " << res;
emit sendCsafeCommand(QStringList() << "CSAFE_GETPROGRAM_CMD");
}
bool csafeelliptical::connected() { return _connected; }
void csafeelliptical::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
}
uint16_t csafeelliptical::watts() { return m_watt.value(); }

View File

@@ -0,0 +1,142 @@
/*
* Copyright (c) 2024 Marcel Verpaalen (marcel@verpaalen.com)
* based on csaferower
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc., 51
* Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
#ifndef CSAFEELLIPTICAL_H
#define CSAFEELLIPTICAL_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#include <QAndroidJniObject>
#endif
#include "devices/csafe/csafe.h"
#include "devices/csafe/csaferunner.h"
#include "devices/csafe/csafeutility.h"
#include "devices/csafe/kalmanfilter.h"
#include "devices/csafe/serialhandler.h"
#include "devices/elliptical.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QDebug>
#include <QFile>
#include <QMutex>
#include <QSettings>
#include <QThread>
// #include <stdint.h>
// #include <stdio.h>
// #include <stdlib.h>
// #include <sys/types.h>
/**
* @brief This class is a CSAFE implementation for elliptical devices.
* Developed for Life Fitness 95x but most likely also working for other CSAFE devices.
*/
class csafeelliptical : public elliptical {
Q_OBJECT
public:
csafeelliptical(bool noWriteResistance, bool noHeartService, bool noVirtualDevice, int8_t bikeResistanceOffset,
double bikeResistanceGain);
bool connected() override;
private:
QTimer *refresh;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
int lastStatus = -1;
QSettings settings;
uint16_t watts() override;
void setupCommands(CsafeRunnerThread *runner);
void setupWorkout();
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
bool noVirtualDevice = false;
bool distanceIsChanging = false;
metric distanceReceived;
KalmanFilter *kalman;
KalmanFilter *kalman1;
KalmanFilter *kalman2;
KalmanFilter *kalman3;
KalmanFilter *kalman4;
bool _connected = true;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
signals:
void disconnected();
void debug(QString string);
void sendCsafeCommand(const QStringList &commands);
private slots:
void update();
void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void changeInclinationRequested(double grade, double percentage);
void changeResistance(resistance_t res) override;
void onPower(double power);
void onCadence(double cadence);
void onHeart(double hr);
void onCalories(double calories);
void onDistance(double distance);
void onStatus(char status);
void onSpeed(double speed);
void portAvailable(bool available);
void onCsafeFrame(const QVariantMap &frame);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
};
#endif // CSAFEELLIPTICAL_H

View File

@@ -18,20 +18,33 @@ csaferower::csaferower(bool noWriteResistance, bool noHeartService, bool noVirtu
connect(t, &csaferowerThread::onHeart, this, &csaferower::onHeart);
connect(t, &csaferowerThread::onCalories, this, &csaferower::onCalories);
connect(t, &csaferowerThread::onDistance, this, &csaferower::onDistance);
connect(t, &csaferowerThread::onPace, this, &csaferower::onPace);
connect(t, &csaferowerThread::onStatus, this, &csaferower::onStatus);
t->start();
}
void csaferower::onPower(double power) {
qDebug() << "Current Power received:" << power;
m_watt = power;
double pace = (pow((2.8 / power), (1. / 3))) * 1000; // pace to m/km put *500 instead to have a m/500m
Speed = (60.0 / (double)(pace)) * 30.0;
void csaferower::onPace(double pace) {
qDebug() << "Current Pace received:" << pace;
if(distanceIsChanging && pace > 0)
Speed = (60.0 / (double)(pace)) * 60.0;
else
Speed = 0;
qDebug() << "Current Speed calculated:" << Speed.value() << pace;
}
void csaferower::onCadence(double cadence) { qDebug() << "Current Cadence received:" << cadence; }
void csaferower::onPower(double power) {
qDebug() << "Current Power received:" << power;
if(distanceIsChanging)
m_watt = power;
}
void csaferower::onCadence(double cadence) {
qDebug() << "Current Cadence received:" << cadence;
if(distanceIsChanging)
Cadence = cadence;
}
void csaferower::onHeart(double hr) {
qDebug() << "Current Heart received:" << hr;
@@ -62,14 +75,30 @@ void csaferower::onCalories(double calories) {
KCal = calories;
}
void csaferower::onDistance(double distance) { qDebug() << "Current Distance received:" << distance / 1000.0; }
void csaferower::onDistance(double distance) {
qDebug() << "Current Distance received:" << distance / 1000.0;
if(distance != distanceReceived.value()) {
distanceIsChanging = true;
distanceReceived = distance;
} else if(abs(distanceReceived.lastChanged().secsTo(QDateTime::currentDateTime())) > 2) {
distanceIsChanging = false;
m_watt = 0;
Cadence = 0;
Speed = 0;
}
}
void csaferower::onStatus(char status) {
qDebug() << "Current Status received:" << status;
}
csaferowerThread::csaferowerThread() {}
void csaferowerThread::run() {
QSettings settings;
/*devicePort =
settings.value(QZSettings::computrainer_serialport, QZSettings::default_computrainer_serialport).toString();*/
deviceFilename = settings.value(QZSettings::csafe_rower, QZSettings::default_csafe_rower).toString();
openPort();
csafe *aa = new csafe();
@@ -81,7 +110,9 @@ void csaferowerThread::run() {
command << "CSAFE_GETPOWER_CMD";
command << "CSAFE_GETCALORIES_CMD";
command << "CSAFE_GETHRCUR_CMD";
QByteArray ret = aa->write(command);
command << "CSAFE_GETPACE_CMD";
command << "CSAFE_GETSTATUS_CMD";
QByteArray ret = aa->write(command,true);
qDebug() << " >> " << ret.toHex(' ');
rawWrite((uint8_t *)ret.data(), ret.length());
@@ -96,6 +127,9 @@ void csaferowerThread::run() {
if (f["CSAFE_GETCADENCE_CMD"].isValid()) {
emit onCadence(f["CSAFE_GETCADENCE_CMD"].value<QVariantList>()[0].toDouble());
}
if (f["CSAFE_GETPACE_CMD"].isValid()) {
emit onPace(f["CSAFE_GETPACE_CMD"].value<QVariantList>()[0].toDouble());
}
if (f["CSAFE_GETPOWER_CMD"].isValid()) {
emit onPower(f["CSAFE_GETPOWER_CMD"].value<QVariantList>()[0].toDouble());
}
@@ -108,6 +142,10 @@ void csaferowerThread::run() {
if (f["CSAFE_PM_GET_WORKDISTANCE"].isValid()) {
emit onDistance(f["CSAFE_PM_GET_WORKDISTANCE"].value<QVariantList>()[0].toDouble());
}
if (f["CSAFE_GETSTATUS_CMD"].isValid()) {
char statusChar = static_cast<char>(f["CSAFE_GETSTATUS_CMD"].value<QVariantList>()[0].toUInt() & 0x0f);
emit onStatus(statusChar);
}
memset(rx, 0x00, sizeof(rx));
QThread::msleep(50);
@@ -125,6 +163,9 @@ int csaferowerThread::closePort() {
}
int csaferowerThread::openPort() {
qDebug() << "Opening serial port " << deviceFilename.toLatin1();
#ifdef Q_OS_ANDROID
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/CSafeRowerUSBHID", "open",
"(Landroid/content/Context;)V", QtAndroid::androidContext().object());

View File

@@ -26,7 +26,7 @@
#include <QObject>
#include <QString>
#include "csafe.h"
#include "devices/csafe/csafe.h"
#include "devices/rower.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualrower.h"
@@ -86,6 +86,8 @@ class csaferowerThread : public QThread {
void onHeart(double hr);
void onCalories(double calories);
void onDistance(double distance);
void onPace(double pace);
void onStatus(char status);
private:
// Utility and BG Thread functions
@@ -141,6 +143,10 @@ class csaferower : public rower {
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;
bool distanceIsChanging = false;
metric distanceReceived;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
@@ -158,6 +164,8 @@ class csaferower : public rower {
void onHeart(double hr);
void onCalories(double calories);
void onDistance(double distance);
void onPace(double pace);
void onStatus(char status);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);

View File

@@ -12,6 +12,7 @@
#include <QLowEnergyConnectionParameters>
#endif
#include <chrono>
#include "homeform.h"
using namespace std::chrono_literals;
@@ -89,7 +90,7 @@ void cscbike::update() {
/*initDone*/) {
bool cadence_sensor_as_bike =
settings.value(QZSettings::cadence_sensor_as_bike, QZSettings::default_cadence_sensor_as_bike).toBool();
update_metrics(true, watts(), !cadence_sensor_as_bike);
update_metrics(false, watts(), !cadence_sensor_as_bike);
if(lastGoodCadence.secsTo(QDateTime::currentDateTime()) > 5 && !charNotified) {
readMethod = true;
@@ -141,7 +142,7 @@ void cscbike::serviceDiscovered(const QBluetoothUuid &gatt) {
void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
QDateTime now = QDateTime::currentDateTime();
qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
qDebug() << "characteristicChanged << " << characteristic.uuid() << newValue.toHex(' ') << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
// QString heartRateBeltName = //unused QString
@@ -157,6 +158,10 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) {
battery = newValue.at(0);
if(battery != battery_level)
if(homeform::singleton())
homeform::singleton()->setToastRequested(bluetoothDevice.name() + QStringLiteral(" Battery Level ") + QString::number(battery) + " %");
battery_level = battery;
qDebug() << QStringLiteral("battery: ") << battery;
return;
}
@@ -210,7 +215,7 @@ void cscbike::characteristicChanged(const QLowEnergyCharacteristic &characterist
if (CrankRevs != oldCrankRevs && deltaT) {
double cadence = ((CrankRevs - oldCrankRevs) / deltaT) * 1024 * 60;
if (cadence >= 0 && cadence < 256)
if ((cadence >= 0 && cadence < 256 && CrankPresent) || (!CrankPresent && WheelPresent))
Cadence = cadence;
lastGoodCadence = now;
} else if (lastGoodCadence.msecsTo(now) > 2000) {

View File

@@ -57,6 +57,8 @@ class cscbike : public bike {
uint8_t firstStateChanged = 0;
bool charNotified = false;
uint8_t battery_level = 0;
bool initDone = false;
bool initRequest = false;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,134 @@
#ifndef CYCLEOPSPHANTOMBIKE_H
#define CYCLEOPSPHANTOMBIKE_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "devices/bike.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class cycleopsphantombike : public bike {
Q_OBJECT
public:
cycleopsphantombike(bool noWriteResistance, bool noHeartService);
void changePower(int32_t power) override;
bool connected() override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
private:
enum class ControlMode : uint8_t {
Headless = 0x00,
ManualPower = 0x01,
ManualSlope = 0x02,
PowerRange = 0x03,
WarmUp = 0x04,
RollDown = 0x05
};
enum class ControlStatus : uint8_t {
SpeedOkay = 0x00,
SpeedUp = 0x01,
SpeedDown = 0x02,
RollDownInitializing = 0x03,
RollDownInProcess = 0x04,
RollDownPassed = 0x05,
RollDownFailed = 0x06
};
void setControlMode(ControlMode mode, int16_t parameter1 = 0, int16_t parameter2 = 0);
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
void forceInclination(double inclination);
uint16_t watts() override;
double bikeResistanceToPeloton(double resistance);
void setUserConfiguration(double wheelDiameter, double gearRatio);
QTimer *refresh;
const int max_resistance = 100;
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharControlPointId;
QLowEnergyCharacteristic gattWriteCharCustomId;
QLowEnergyService *gattPowerService = nullptr;
QLowEnergyService *gattCustomService = nullptr;
// QLowEnergyCharacteristic gattNotify1Characteristic;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged2A5B = QDateTime::currentDateTime();
QDateTime lastRefreshCharacteristicChanged2AD2 = QDateTime::currentDateTime();
QDateTime lastRefreshCharacteristicChangedPower = QDateTime::currentDateTime();
QDateTime lastGoodCadence = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;
uint16_t CrankRevsRead = 0;
double lastGearValue = -1;
bool resistance_received = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
signals:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
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 characteristicRead(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
void descriptorRead(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();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
void powerPacketReceived(const QByteArray &b);
};
#endif // CYCLEOPSPHANTOMBIKE_H

View File

@@ -368,6 +368,12 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
else
kcal = KCal.value();
}
if(!firstCharacteristicChanged) {
Distance += ((speed / (double)3600.0) /
((double)1000.0 / (double)(lastRefreshCharacteristicChanged.msecsTo(now))));
}
double distance = GetDistanceFromPacket(value);
double ucadence = ((uint8_t)value.at(9));
@@ -451,7 +457,7 @@ void domyosbike::characteristicChanged(const QLowEnergyCharacteristic &character
fabs(now.msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
KCal = kcal;
Distance = distance;
firstCharacteristicChanged = false;
}
double domyosbike::GetSpeedFromPacket(const QByteArray &packet) {

View File

@@ -80,6 +80,7 @@ class domyosbike : public bike {
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
bool firstCharacteristicChanged = true;
enum _BIKE_TYPE {
CHANG_YOW,

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>
@@ -367,7 +368,7 @@ void domyoselliptical::characteristicChanged(const QLowEnergyCharacteristic &cha
double domyoselliptical::GetSpeedFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(6) << 8) | packet.at(7);
uint16_t convertedData = (packet.at(6) << 8) | ((uint8_t)packet.at(7));
double data = (double)convertedData / 10.0f;
return data;
}
@@ -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

@@ -118,7 +118,8 @@ void elliptical::setGears(double gears) {
QSettings settings;
qDebug() << "setGears" << gears;
m_gears = gears;
settings.setValue(QZSettings::gears_current_value, m_gears);
if (settings.value(QZSettings::gears_restore_value, QZSettings::default_gears_restore_value).toBool())
settings.setValue(QZSettings::gears_current_value, m_gears);
if (lastRawRequestedResistanceValue != -1) {
changeResistance(lastRawRequestedResistanceValue);
}

View File

@@ -195,3 +195,11 @@ resistance_t fakebike::resistanceFromPowerRequest(uint16_t power) {
uint16_t fakebike::watts() { return m_watt.value(); }
bool fakebike::connected() { return true; }
double fakebike::maxGears() {
return 24;
}
double fakebike::minGears() {
return 1;
}

View File

@@ -42,6 +42,8 @@ class fakebike : public bike {
uint16_t watts() override;
resistance_t maxResistance() override { return 100; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
double maxGears() override;
double minGears() override;
private:
QTimer *refresh;

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

@@ -111,7 +111,7 @@ class fitshowtreadmill : public treadmill {
bool firstCharacteristicChanged = true;
int MAX_INCLINE = 30;
int COUNTDOWN_VALUE = 0;
int MAX_SPEED = 30;
int MAX_SPEED = 300;
int MIN_INCLINE = 0;
int MIN_SPEED = 0;
int UNIT = -100;

View File

@@ -21,7 +21,7 @@ focustreadmill::focustreadmill(uint32_t pollDeviceTime, bool noConsole, bool noH
double forceInitInclination) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
QZ_EnableDiscoveryCharsAndDescripttors = false;
#endif
m_watt.setType(metric::METRIC_WATT);

View File

@@ -15,6 +15,7 @@
#include "keepawakehelper.h"
#endif
#include <chrono>
#include "wheelcircumference.h"
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
@@ -35,14 +36,18 @@ ftmsbike::ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResis
initDone = false;
connect(refresh, &QTimer::timeout, this, &ftmsbike::update);
refresh->start(settings.value(QZSettings::poll_device_time, QZSettings::default_poll_device_time).toInt());
wheelCircumference::GearTable g;
g.printTable();
}
void ftmsbike::writeCharacteristicZwiftPlay(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(!zwiftPlayService) {
if(!zwiftPlayService || !gears_zwift_ratio) {
qDebug() << QStringLiteral("zwiftPlayService is null!");
return;
}
@@ -78,11 +83,18 @@ bool ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(!gattFTMSService) {
qDebug() << QStringLiteral("gattFTMSService is null!");
return false;
}
if(zwiftPlayService && gears_zwift_ratio) {
qDebug() << QStringLiteral("zwiftPlayService is present!");
return false;
}
if (wait_for_response) {
connect(gattFTMSService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
@@ -97,7 +109,7 @@ bool ftmsbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QStrin
}
writeBuffer = new QByteArray((const char *)data, data_len);
if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
if (gattWriteCharControlPointId.properties() & QLowEnergyCharacteristic::WriteNoResponse && !DOMYOS) {
gattFTMSService->writeCharacteristic(gattWriteCharControlPointId, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
} else {
@@ -119,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;
@@ -128,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();
@@ -216,8 +239,8 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
if (!settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool() &&
resistance_lvl_mode == false && _3G_Cardio_RB == false) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
resistance_lvl_mode == false && _3G_Cardio_RB == false && JFBK5_0 == false) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
double fr = (((double)requestResistance) * bikeResistanceGain) + ((double)bikeResistanceOffset);
requestResistance = fr;
@@ -228,12 +251,20 @@ void ftmsbike::forceResistance(resistance_t requestResistance) {
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
} else {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
if(_3G_Cardio_RB)
requestResistance = requestResistance * 10;
write[1] = ((uint8_t)(requestResistance));
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
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;
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
} else {
uint8_t write[] = {FTMS_SET_TARGET_RESISTANCE_LEVEL, 0x00};
if(_3G_Cardio_RB)
requestResistance = requestResistance * 10;
write[1] = ((uint8_t)(requestResistance));
writeCharacteristic(write, sizeof(write),
QStringLiteral("forceResistance ") + QString::number(requestResistance));
}
}
}
@@ -269,6 +300,8 @@ void ftmsbike::update() {
}
auto virtualBike = this->VirtualBike();
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if (requestResistance != -1 || lastGearValue != gears()) {
if (requestResistance > 100) {
@@ -282,116 +315,75 @@ void ftmsbike::update() {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
// if the FTMS is connected, the ftmsCharacteristicChanged event will do all the stuff because it's a
// FTMS bike. This condition handles the peloton requests
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike) &&
if (((virtualBike && !virtualBike->ftmsDeviceConnected()) || !virtualBike || resistance_lvl_mode) &&
(requestPower == 0 || requestPower == -1)) {
init();
forceResistance(requestResistance + (gears() * 5));
if(DIRETO_XR && gears_zwift_ratio)
setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears()));
else {
forceResistance(requestResistance + (gears() * 5));
if(DIRETO_XR)
Resistance = requestResistance;
}
}
}
requestResistance = -1;
}
if((virtualBike && virtualBike->ftmsDeviceConnected()) && lastGearValue != gears() && lastRawRequestedInclinationValue != -100 && lastPacketFromFTMS.length() >= 7) {
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
if(DIRETO_XR && gears_zwift_ratio) {
setWheelDiameter(wheelCircumference::gearsToWheelDiameter(gears()));
} else {
qDebug() << "injecting fake ftms frame in order to send the new gear value ASAP" << lastPacketFromFTMS.toHex(' ');
ftmsCharacteristicChanged(QLowEnergyCharacteristic(), lastPacketFromFTMS);
}
}
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(zwiftPlayService && gears_zwift_ratio && lastGearValue != gears()) {
uint8_t gear1[] = {0x04, 0x2a, 0x03, 0x10, 0xdc, 0xec};
uint8_t gear2[] = {0x04, 0x2a, 0x04, 0x10, 0xdc, 0xec, 0x01};
uint32_t gear_value = 0;
QSettings settings;
wheelCircumference::GearTable table;
wheelCircumference::GearTable::GearInfo g = table.getGear((int)gears());
double original_ratio = ((double)settings.value(QZSettings::gear_crankset_size, QZSettings::default_gear_crankset_size).toDouble()) /
((double)settings.value(QZSettings::gear_cog_size, QZSettings::default_gear_cog_size).toDouble());
double current_ratio = ((double)g.crankset / (double)g.rearCog);
uint32_t gear_value = static_cast<uint32_t>(10000.0 * (current_ratio/original_ratio) * (42.0/14.0));
qDebug() << "zwift hub gear current ratio" << current_ratio << g.crankset << g.rearCog << "gear_value" << gear_value << "original_ratio" << original_ratio;
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
QByteArray proto = lockscreen::zwift_hub_setGearsCommand(gear_value);
#else
QByteArray proto;
#endif
#elif defined Q_OS_ANDROID
QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/ZwiftHubBike",
"setGearCommand",
"(I)[B",
gear_value);
switch((int)gears()) {
case 1:
gear_value = 0x3acc;
break;
case 2:
gear_value = 0x43fc;
break;
case 3:
gear_value = 0x4dac;
break;
case 4:
gear_value = 0x56d5;
break;
case 5:
gear_value = 0x608c;
break;
case 6:
gear_value = 0x6be8;
break;
case 7:
gear_value = 0x77c4;
break;
case 8:
gear_value = 0x183a0;
break;
case 9:
gear_value = 0x191a8;
break;
case 10:
gear_value = 0x19fb0;
break;
case 11:
gear_value = 0x1adb8;
break;
case 12:
gear_value = 0x1bbc0;
break;
case 13:
gear_value = 0x1cbf3;
break;
case 14:
gear_value = 0x1dca8;
break;
case 15:
gear_value = 0x1ecdc;
break;
case 16:
gear_value = 0x1fd90;
break;
case 17:
gear_value = 0x290d4;
break;
case 18:
gear_value = 0x2a498;
break;
case 19:
gear_value = 0x2b7dc;
break;
case 20:
gear_value = 0x2cb9f;
break;
case 21:
gear_value = 0x2e2d8;
break;
case 22:
gear_value = 0x2fa90;
break;
case 23:
gear_value = 0x391c8;
break;
case 24:
gear_value = 0x3acf3;
break;
default:
// Gestione del caso di default
break;
if (!result.isValid()) {
qDebug() << "setGearCommand returned invalid value";
return;
}
gear_value = gear_value * settings.value(QZSettings::gears_gain, QZSettings::default_gears_gain).toDouble();
jbyteArray array = result.object<jbyteArray>();
QAndroidJniEnvironment env;
jbyte* bytes = env->GetByteArrayElements(array, nullptr);
jsize length = env->GetArrayLength(array);
if(gear_value < 0x10000) {
gear1[4] = gear_value & 0xFF;
gear1[5] = ((gear_value & 0xFF00) >> 8) & 0xFF;
writeCharacteristicZwiftPlay(gear1, sizeof(gear1), "gear", false, true);
} else {
gear2[4] = gear_value & 0xFF;
gear2[5] = ((gear_value & 0xFF00) >> 8) & 0xFF;
gear2[6] = ((gear_value & 0xFF0000) >> 16) & 0xFF;
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gear", false, true);
}
QByteArray proto((char*)bytes, length);
env->ReleaseByteArrayElements(array, bytes, JNI_ABORT);
#else
QByteArray proto;
qDebug() << "ERROR: gear message not handled!";
return;
#endif
writeCharacteristicZwiftPlay((uint8_t*)proto.data(), proto.length(), "gear", false, true);
uint8_t gearApply[] = {0x00, 0x08, 0x88, 0x04};
writeCharacteristicZwiftPlay(gearApply, sizeof(gearApply), "gearApply", false, true);
@@ -420,7 +412,7 @@ void ftmsbike::update() {
QSettings settings;
if (settings.value(QZSettings::ss2k_peloton, QZSettings::default_ss2k_peloton).toBool()) {
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t write[] = {FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS, 0x00, 0x00, 0x00, 0x00, 0x28, 0x19};
writeCharacteristic(write, sizeof(write), QStringLiteral("init SS2K"));
}
@@ -442,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(' ');
@@ -455,16 +449,37 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
return;
}
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19)) { // Battery Service
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A19) && !D2RIDE) { // Battery Service
if(newValue.length() > 0) {
uint8_t b = (uint8_t)newValue.at(0);
if(b != battery_level)
if(homeform::singleton())
homeform::singleton()->setToastRequested(QStringLiteral("Battery Level ") + QString::number(b) + " %");
homeform::singleton()->setToastRequested(bluetoothDevice.name() + QStringLiteral(" Battery Level ") + QString::number(b) + " %");
battery_level = b;
}
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")) &&
newValue.length() > 3 && newValue.at(1) == 0x03 && (uint8_t)newValue.at(2) == 0xb6) {
uint8_t gear = newValue.at(3);
qDebug() << "watt bike gears" << gear;
setGears(gear);
}
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2AD2)) {
@@ -489,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);
@@ -556,12 +577,17 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
emit debug(QStringLiteral("Current Distance: ") + QString::number(Distance.value()));
if (Flags.resistanceLvl) {
Resistance = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
emit resistanceRead(Resistance.value());
index += 2;
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
resistance_received = true;
double d = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(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());
resistance_received = true;
}
}
double ac = 0.01243107769;
double bc = 1.145964912;
@@ -592,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")))
@@ -925,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) {
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 "
@@ -985,7 +1015,7 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
}
QBluetoothUuid _zwiftPlayWriteCharControlPointId(QStringLiteral("00000003-19ca-4651-86e5-fa29dcdd09d1"));
if (c.uuid() == _zwiftPlayWriteCharControlPointId) {
if (c.uuid() == _zwiftPlayWriteCharControlPointId && !DIRETO_XR) {
qDebug() << QStringLiteral("Zwift Play service and Control Point found");
zwiftPlayWriteChar = c;
zwiftPlayService = s;
@@ -1001,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();
}
@@ -1045,8 +1075,8 @@ void ftmsbike::stateChanged(QLowEnergyService::ServiceState state) {
void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
if (!autoResistance()) {
qDebug() << "ignoring routing FTMS packet to the bike from virtualbike because of auto resistance OFF"
if (!autoResistance() || resistance_lvl_mode) {
qDebug() << "ignoring routing FTMS packet to the bike from virtualbike because of auto resistance OFF or resistance lvl mode is on"
<< characteristic.uuid() << newValue.toHex(' ');
return;
}
@@ -1059,28 +1089,75 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
qDebug() << "routing FTMS packet to the bike from virtualbike" << characteristic.uuid() << newValue.toHex(' ');
// handling gears
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && ((zwiftPlayService == nullptr && gears_zwift_ratio) || !gears_zwift_ratio)) {
if (b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && (zwiftPlayService == nullptr || !gears_zwift_ratio)) {
double min_inclination = settings.value(QZSettings::min_inclination, QZSettings::default_min_inclination).toDouble();
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();
lastPacketFromFTMS.clear();
for(int i=0; i<b.length(); i++)
lastPacketFromFTMS.append(b.at(i));
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
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;
qDebug() << "applying gears mod" << gears() << slope;
/*} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) {
} else if(b.at(0) == FTMS_SET_INDOOR_BIKE_SIMULATION_PARAMS && zwiftPlayService != nullptr && gears_zwift_ratio) {
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
uint8_t gear2[] = {0x04, 0x22, 0x02, 0x10, 0x00};
int g = (int)(((double)slope / 100.0) + settings.value(QZSettings::gears_offset, QZSettings::default_gears_offset).toDouble());
if(g < 0) {
g = 0;
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
QByteArray message = lockscreen::zwift_hub_inclinationCommand(((double)slope) / 100.0);
#else
QByteArray message;
#endif
#elif defined(Q_OS_ANDROID)
QAndroidJniObject result = QAndroidJniObject::callStaticObjectMethod(
"org/cagnulen/qdomyoszwift/ZwiftHubBike",
"inclinationCommand",
"(D)[B",
((double)slope) / 100.0);
if(!result.isValid()) {
qDebug() << "inclinationCommand returned invalid value";
return;
}
gear2[4] = g;
writeCharacteristicZwiftPlay(gear2, sizeof(gear2), "gearInclination", false, false);*/
jbyteArray array = result.object<jbyteArray>();
QAndroidJniEnvironment env;
jbyte* bytes = env->GetByteArrayElements(array, nullptr);
jsize length = env->GetArrayLength(array);
QByteArray message((char*)bytes, length);
env->ReleaseByteArrayElements(array, bytes, JNI_ABORT);
#else
QByteArray message;
qDebug() << "implement zwift hub protobuf!";
return;
#endif
writeCharacteristicZwiftPlay((uint8_t*)message.data(), message.length(), "gearInclination", false, false);
return;
} else if(b.at(0) == FTMS_SET_TARGET_POWER && zwiftPlayService != nullptr && gears_zwift_ratio) {
qDebug() << "discarding";
return;
} else if(b.at(0) == FTMS_SET_TARGET_POWER && b.length() > 2) {
lastPacketFromFTMS.clear();
for(int i=0; i<b.length(); i++)
@@ -1140,6 +1217,13 @@ void ftmsbike::serviceScanDone(void) {
connect(gattCommunicationChannelService.constLast(), &QLowEnergyService::stateChanged, this,
&ftmsbike::stateChanged);
gattCommunicationChannelService.constLast()->discoverDetails();
// watt bikes has the 6 as default gear value
if(s == QBluetoothUuid(QStringLiteral("b4cc1223-bc02-4cae-adb9-1217ad2860d1")) && SS2K == false) {
WATTBIKE = true;
qDebug() << QStringLiteral("restoring gear 6 to watt bikes");
setGears(6);
}
}
}
}
@@ -1181,6 +1265,7 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
ICSE = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("DOMYOS"))) {
qDebug() << QStringLiteral("DOMYOS found");
resistance_lvl_mode = true;
DOMYOS = true;
} else if ((bluetoothDevice.name().toUpper().startsWith("3G Cardio RB"))) {
qDebug() << QStringLiteral("_3G_Cardio_RB found");
@@ -1188,6 +1273,34 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if((bluetoothDevice.name().toUpper().startsWith("SCH_190U"))) {
qDebug() << QStringLiteral("SCH_190U found");
SCH_190U = true;
} else if(bluetoothDevice.name().toUpper().startsWith("D2RIDE")) {
qDebug() << QStringLiteral("D2RIDE found");
D2RIDE = true;
} else if(bluetoothDevice.name().toUpper().startsWith("VFSPINBIKE")) {
qDebug() << QStringLiteral("VFSPINBIKE found");
VFSPINBIKE = true;
} else if(bluetoothDevice.name().toUpper().startsWith("SMARTSPIN2K")) {
qDebug() << QStringLiteral("SS2K found");
SS2K = true;
} else if(bluetoothDevice.name().toUpper().startsWith("DIRETO XR")) {
qDebug() << QStringLiteral("DIRETO XR found");
DIRETO_XR = true;
} else if(bluetoothDevice.name().toUpper().startsWith("JFBK5.0") || bluetoothDevice.name().toUpper().startsWith("JFBK7.0")) {
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()) {
@@ -1234,6 +1347,17 @@ bool ftmsbike::connected() {
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void ftmsbike::setWheelDiameter(double diameter) {
uint8_t write[] = {FTMS_SET_WHEEL_CIRCUMFERENCE, 0x00, 0x00};
diameter = diameter * 10.0;
write[1] = ((uint16_t)diameter) & 0xFF;
write[2] = ((uint16_t)diameter) >> 8;
writeCharacteristic(write, sizeof(write), QStringLiteral("setWheelCircumference ") + QString::number(diameter));
}
uint16_t ftmsbike::watts() {
if (currentCadence().value() == 0) {
return 0;
@@ -1250,3 +1374,30 @@ void ftmsbike::controllerStateChanged(QLowEnergyController::ControllerState stat
m_control->connectToDevice();
}
}
double ftmsbike::maxGears() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio) {
wheelCircumference::GearTable g;
return g.maxGears;
} else if(WATTBIKE) {
return 22;
} else {
return 9999.0;
}
}
double ftmsbike::minGears() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio ) {
return 1;
} else if(WATTBIKE) {
return 1;
} else {
return -9999.0;
}
}

View File

@@ -26,6 +26,7 @@
#include <QObject>
#include <QString>
#include "wheelcircumference.h"
#include "devices/bike.h"
#ifdef Q_OS_IOS
@@ -69,10 +70,13 @@ 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; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
double maxGears() override;
double minGears() override;
private:
bool writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
@@ -81,6 +85,7 @@ class ftmsbike : public bike {
bool wait_for_response = false);
void zwiftPlayInit();
void startDiscover();
void setWheelDiameter(double diameter);
uint16_t watts() override;
void init();
void forceResistance(resistance_t requestResistance);
@@ -123,6 +128,15 @@ class ftmsbike : public bike {
bool DOMYOS = false;
bool _3G_Cardio_RB = false;
bool SCH_190U = false;
bool D2RIDE = false;
bool WATTBIKE = false;
bool VFSPINBIKE = false;
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

@@ -81,6 +81,9 @@ void ftmsrower::update() {
if(I_ROWER) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(write, sizeof(write), "start", false, true);
uint8_t write1[] = {FTMS_START_RESUME};
writeCharacteristic(write1, sizeof(write1), "start simulation", false, true);
} else {
uint8_t write[] = {FTMS_START_RESUME};
writeCharacteristic(write, sizeof(write), "start simulation", false, true);
@@ -393,6 +396,7 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
}
}
notificationSubscribed = 0;
qDebug() << QStringLiteral("all services discovered!");
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
@@ -408,6 +412,15 @@ void ftmsrower::stateChanged(QLowEnergyService::ServiceState state) {
connect(s, &QLowEnergyService::descriptorWritten, this, &ftmsrower::descriptorWritten);
connect(s, &QLowEnergyService::descriptorRead, this, &ftmsrower::descriptorRead);
if (I_ROWER) {
QBluetoothUuid ftmsService((quint16)0x1826);
if (s->serviceUuid() != ftmsService) {
qDebug() << QStringLiteral("I-ROWER wants to be subscribed only to FTMS service in order to send metrics")
<< s->serviceUuid();
continue;
}
}
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
auto characteristics_list = s->characteristics();
@@ -424,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()
@@ -439,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()
@@ -517,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

@@ -1,4 +1,5 @@
#include "heartratebelt.h"
#include "homeform.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
@@ -7,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());
@@ -114,10 +153,13 @@ 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() + ')');
if(homeform::singleton())
homeform::singleton()->setToastRequested(device.name() + QStringLiteral(" connected!"));
// if(device.name().startsWith(heartRateBeltName))
{
bluetoothDevice = device;
@@ -163,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

@@ -48,12 +48,18 @@ void horizontreadmill::writeCharacteristic(QLowEnergyService *service, QLowEnerg
return;
}
int timeA = 8000;
int timeB = 3000;
if (!gattCustomService) {
timeA = timeB = 300; // for the normal treadmills, the timeout must be small, otherwise we are blocking the update timer on iOS
}
if (wait_for_response) {
connect(this, &horizontreadmill::packetReceived, &loop, &QEventLoop::quit);
timeout.singleShot(8000, &loop, SLOT(quit())); // 6 seconds are important
timeout.singleShot(timeA, &loop, SLOT(quit())); // 6 seconds are important
} else {
connect(service, SIGNAL(characteristicWritten(QLowEnergyCharacteristic, QByteArray)), &loop, SLOT(quit()));
timeout.singleShot(3000, &loop, SLOT(quit()));
timeout.singleShot(timeB, &loop, SLOT(quit()));
}
if (writeBuffer) {
@@ -176,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;
@@ -257,11 +267,18 @@ void horizontreadmill::btinit() {
QStringLiteral("init"), false, true);
if (!initPacketRecv) {
if(gattFTMSService && homeform::singleton()) {
homeform::singleton()->setToastRequested("Enable the 'Force Using FTMS' setting under the Settings->Treadmill Options->Horizon Treadmill options and restart the app");
}
qDebug() << "init 1 not received";
waitForAPacket();
goto init1;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...15%");
}
init2:
initPacketRecv = false;
@@ -348,6 +365,10 @@ void horizontreadmill::btinit() {
goto init2;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...25%");
}
init3:
initPacketRecv = false;
@@ -434,6 +455,10 @@ void horizontreadmill::btinit() {
goto init3;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...35%");
}
init4:
initPacketRecv = false;
@@ -520,6 +545,10 @@ void horizontreadmill::btinit() {
goto init4;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...50%");
}
init5:
initPacketRecv = false;
@@ -606,6 +635,10 @@ void horizontreadmill::btinit() {
goto init5;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...65%");
}
init6:
initPacketRecv = false;
@@ -692,6 +725,10 @@ void horizontreadmill::btinit() {
goto init6;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...80%");
}
init7:
initPacketRecv = false;
@@ -778,6 +815,10 @@ void horizontreadmill::btinit() {
goto init7;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...90%");
}
init8:
initPacketRecv = false;
@@ -803,11 +844,21 @@ void horizontreadmill::btinit() {
waitForAPacket();
goto init8;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization completed!");
}
}
messageID = 0x10;
}
if(wellfit_treadmill) {
if(YPOO_MINI_PRO) {
uint8_t write[] = {0x01, 0x00, 0x00, 0x03, 0x08, 0x00, 0x02, 0x09};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointIdYpooMiniPro, write, sizeof(write), "requestControl", false, false);
QThread::msleep(500);
}
if(wellfit_treadmill || SW_TREADMILL || YPOO_MINI_PRO) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -882,10 +933,10 @@ void horizontreadmill::update() {
bool forceSpeedNeed = checkIfForceSpeedNeeding(requestSpeed);
qDebug() << "requestSpeed=" << requestSpeed << minSpeed << forceSpeedNeed
<< float_one_point_round(currentSpeed().value());
if (requestSpeed != currentSpeed().value() && minSpeed && requestSpeed >= 0 && requestSpeed <= 22 &&
if (float_one_point_round(requestSpeed) != float_one_point_round(currentSpeed().value()) && minSpeed && requestSpeed >= 0 && requestSpeed <= 22 &&
forceSpeedNeed) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
forceSpeed(float_one_point_round(requestSpeed));
}
requestSpeed = -1;
}
@@ -915,6 +966,11 @@ void horizontreadmill::update() {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
// this treadmill doesn't send the incline, so i'm forcing it manually
if(SW_TREADMILL) {
Inclination = requestInclination;
}
}
requestInclination = -100;
}
@@ -1064,6 +1120,10 @@ void horizontreadmill::update() {
}
}
if(SW_TREADMILL) {
Inclination = 0;
}
lastStop = QDateTime::currentMSecsSinceEpoch();
requestStop = -1;
@@ -1103,6 +1163,7 @@ bool horizontreadmill::checkIfForceSpeedNeeding(double requestSpeed) {
// example frame: 55aa320003050400532c00150000
void horizontreadmill::forceSpeed(double requestSpeed) {
QSettings settings;
const double miles_conversion = 0.621371;
bool horizon_paragon_x =
settings.value(QZSettings::horizon_paragon_x, QZSettings::default_horizon_paragon_x).toBool();
@@ -1155,7 +1216,7 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill) {
if(!anplus_treadmill && !trx3500_treadmill && !wellfit_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -1165,8 +1226,12 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
}
uint8_t writeS[] = {FTMS_SET_TARGET_SPEED, 0x00, 0x00};
writeS[1] = ((uint16_t)(requestSpeed * 100)) & 0xFF;
writeS[2] = ((uint16_t)(requestSpeed * 100)) >> 8;
if(BOWFLEX_T9) {
requestSpeed *= miles_conversion; // this treadmill wants the speed in miles, at least seems so!!
}
uint16_t speed_int = round(requestSpeed * 100);
writeS[1] = speed_int & 0xFF;
writeS[2] = speed_int >> 8;
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, writeS, sizeof(writeS),
QStringLiteral("forceSpeed"), false, false);
@@ -1221,7 +1286,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill) {
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_treadmill && !YPOO_MINI_PRO) {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
writeCharacteristic(gattFTMSService, gattWriteCharControlPointId, write, sizeof(write), "requestControl", false,
false);
@@ -1321,46 +1386,46 @@ void horizontreadmill::forceIncline(double requestIncline) {
if(requestInclination > 0 && requestInclination < 1) {
writeS[1] = 0x3C;
writeS[2] = 0x00;
} else if(requestInclination > 1 && requestInclination < 2) {
} else if(requestInclination >= 1 && requestInclination < 2) {
writeS[1] = 0x82;
writeS[2] = 0x00;
} else if(requestInclination > 2 && requestInclination < 3) {
} else if(requestInclination >= 2 && requestInclination < 3) {
writeS[1] = 0xC8;
writeS[2] = 0x00;
} else if(requestInclination > 3 && requestInclination < 4) {
} else if(requestInclination >= 3 && requestInclination < 4) {
writeS[1] = 0x04;
writeS[2] = 0x01;
} else if(requestInclination > 4 && requestInclination < 5) {
} else if(requestInclination >= 4 && requestInclination < 5) {
writeS[1] = 0x4A;
writeS[2] = 0x01;
} else if(requestInclination > 5 && requestInclination < 6) {
} else if(requestInclination >= 5 && requestInclination < 6) {
writeS[1] = 0x90;
writeS[2] = 0x01;
} else if(requestInclination > 6 && requestInclination < 7) {
} else if(requestInclination >= 6 && requestInclination < 7) {
writeS[1] = 0xCC;
writeS[2] = 0x01;
} else if(requestInclination > 7 && requestInclination < 8) {
} else if(requestInclination >= 7 && requestInclination < 8) {
writeS[1] = 0x12;
writeS[2] = 0x02;
} else if(requestInclination > 8 && requestInclination < 9) {
} else if(requestInclination >= 8 && requestInclination < 9) {
writeS[1] = 0x58;
writeS[2] = 0x02;
} else if(requestInclination > 9 && requestInclination < 10) {
} else if(requestInclination >= 9 && requestInclination < 10) {
writeS[1] = 0x94;
writeS[2] = 0x02;
} else if(requestInclination > 10 && requestInclination < 11) {
} else if(requestInclination >= 10 && requestInclination < 11) {
writeS[1] = 0xDA;
writeS[2] = 0x02;
} else if(requestInclination > 11 && requestInclination < 12) {
} else if(requestInclination >= 11 && requestInclination < 12) {
writeS[1] = 0x20;
writeS[2] = 0x03;
} else if(requestInclination > 12 && requestInclination < 13) {
} else if(requestInclination >= 12 && requestInclination < 13) {
writeS[1] = 0x5C;
writeS[2] = 0x03;
} else if(requestInclination > 13 && requestInclination < 14) {
} else if(requestInclination >= 13 && requestInclination < 14) {
writeS[1] = 0xA2;
writeS[2] = 0x03;
} else if(requestInclination > 14 && requestInclination < 15) {
} else if(requestInclination >= 14 && requestInclination < 15) {
writeS[1] = 0xE8;
writeS[2] = 0x03;
} else {
@@ -1438,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))
@@ -1464,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))
@@ -1625,8 +1690,8 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
if (Flags.expEnergy && newValue.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
/*KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));*/
index += 2;
// energy per hour
@@ -1634,17 +1699,17 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// energy per minute
index += 1;
} else {
if (watts(weight))
KCal += ((((0.048 * ((double)watts(weight)) + 1.19) *
weight * 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(weight))
KCal += ((((0.048 * ((double)watts(weight)) + 1.19) *
weight * 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()));
#ifdef Q_OS_ANDROID
@@ -1708,9 +1773,15 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
index += 2;
if (!Flags.moreData) {
parseSpeed(((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0);
double speed = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index)))) /
100.0;
if(BOWFLEX_T9) {
const double miles_conversion = 0.621371;
// this treadmill sends the speed in miles!
speed *= miles_conversion;
}
parseSpeed(speed);
index += 2;
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
}
@@ -1732,7 +1803,7 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
// else
{
if (firstDistanceCalculated)
if (firstDistanceCalculated && !isPaused())
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(now)));
distanceEval = true;
@@ -1742,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);
@@ -1803,8 +1874,8 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
if (Flags.expEnergy) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
/*KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));*/
index += 2;
// energy per hour
@@ -1812,21 +1883,20 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// energy per minute
index += 1;
} else {
if (firstDistanceCalculated &&
watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
KCal +=
((((0.048 *
((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
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
distanceEval = true;
}
if (firstDistanceCalculated &&
watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
KCal +=
((((0.048 *
((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
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
distanceEval = true;
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
@@ -1976,8 +2046,8 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
}
if (Flags.expEnergy && newValue.length() > index + 1) {
KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));
/*KCal = ((double)(((uint16_t)((uint8_t)newValue.at(index + 1)) << 8) |
(uint16_t)((uint8_t)newValue.at(index))));*/
index += 2;
// energy per hour
@@ -1985,22 +2055,22 @@ void horizontreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
// energy per minute
index += 1;
} else {
if (firstDistanceCalculated &&
watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
KCal +=
((((0.048 *
((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
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
distanceEval = true;
}
if (firstDistanceCalculated &&
watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()))
KCal +=
((((0.048 *
((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) +
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
distanceEval = true;
emit debug(QStringLiteral("Current KCal: ") + QString::number(KCal.value()));
#ifdef Q_OS_ANDROID
@@ -2104,6 +2174,7 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattCrossTrainerDataId((quint16)0x2ACE);
QBluetoothUuid _gattInclinationSupported((quint16)0x2AD5);
QBluetoothUuid _DomyosServiceId(QStringLiteral("49535343-fe7d-4ae5-8fa9-9fafd205e455"));
QBluetoothUuid _YpooMiniProCharId(QStringLiteral("d18d2c10-c44c-11e8-a355-529269fb1459"));
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
for (QLowEnergyService *s : qAsConst(gattCommunicationChannelService)) {
@@ -2156,12 +2227,16 @@ void horizontreadmill::stateChanged(QLowEnergyService::ServiceState state) {
// some treadmills doesn't have the control point and also are Cross Trainer devices so i need
// anyway to get the FTMS Service at least
gattFTMSService = s;
}/* else if (c.uuid() == _gattInclinationSupported) {
} else if(c.uuid() == _YpooMiniProCharId && YPOO_MINI_PRO) {
qDebug() << QStringLiteral("YPOO MINI PRO Control Point found");
gattWriteCharControlPointIdYpooMiniPro = c;
}
/* else if (c.uuid() == _gattInclinationSupported) {
s->readCharacteristic(c);
qDebug() << s->serviceUuid() << c.uuid() << "reading!";
}*/
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService &&
if (c.properties() & QLowEnergyCharacteristic::Write && c.uuid() == _gattWriteCharCustomService && !BOWFLEX_T9 && !MX_TM &&
!settings
.value(QZSettings::horizon_treadmill_force_ftms,
QZSettings::default_horizon_treadmill_force_ftms)
@@ -2345,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"))) {
@@ -2362,11 +2438,13 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if (device.name().toUpper().startsWith(QStringLiteral("ANPLUS-"))) {
anplus_treadmill = true;
qDebug() << QStringLiteral("ANPLUS TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-"))) {
} else if (device.name().toUpper().startsWith(QStringLiteral("TUNTURI T60-")) ||
device.name().toUpper().startsWith(QStringLiteral("TUNTURI T90-"))) {
tunturi_t60_treadmill = true;
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"))) {
@@ -2377,6 +2455,9 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
sole_tt8_treadmill = true;
minInclination = -6.0;
qDebug() << QStringLiteral("SOLE TT8 TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("S77"))) {
sole_s77_treadmill = true;
qDebug() << QStringLiteral("SOLE S77 TREADMILL workaround ON!");
} else if (device.name().toUpper().startsWith(QStringLiteral("SCHWINN 810"))) {
schwinn_810_treadmill = true;
qDebug() << QStringLiteral("Schwinn 810 TREADMILL workaround ON!");
@@ -2385,6 +2466,7 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("Technogym Run TREADMILL workaround ON!");
} else if(device.name().toUpper().startsWith(QStringLiteral("SW"))) {
qDebug() << QStringLiteral("SW TREADMILL workaround ON!");
SW_TREADMILL = true;
disableAutoPause = true;
} else if(device.name().toUpper().startsWith("HORIZON_7.8AT")) {
HORIZON_78AT_treadmill = true;
@@ -2395,6 +2477,18 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((device.name().toUpper().startsWith("DOMYOS"))) {
qDebug() << QStringLiteral("DOMYOS found");
DOMYOS = true;
} else if ((device.name().toUpper().startsWith(QStringLiteral("BFX_T9_")))) {
qDebug() << QStringLiteral("BOWFLEX T9 found");
BOWFLEX_T9 = true;
} else if ((device.name().toUpper().startsWith(QStringLiteral("YPOO-MINI PRO-")))) {
qDebug() << QStringLiteral("YPOO-MINI PRO found");
YPOO_MINI_PRO = true;
} 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"))) {
@@ -3119,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)
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

@@ -58,6 +58,7 @@ class horizontreadmill : public treadmill {
QList<QLowEnergyService *> gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharControlPointId;
QLowEnergyCharacteristic gattWriteCharControlPointIdYpooMiniPro;
QLowEnergyService *gattFTMSService = nullptr;
QLowEnergyCharacteristic gattWriteCharCustomService;
QLowEnergyService *gattCustomService = nullptr;
@@ -90,6 +91,7 @@ class horizontreadmill : public treadmill {
bool kettler_treadmill = false;
bool wellfit_treadmill = false;
bool sole_tt8_treadmill = false;
bool sole_s77_treadmill = false;
bool anplus_treadmill = false;
bool tunturi_t60_treadmill = false;
bool trx3500_treadmill = false;
@@ -101,6 +103,11 @@ class horizontreadmill : public treadmill {
bool HORIZON_78AT_treadmill = false;
bool ICONCEPT_FTMS_treadmill = false;
bool DOMYOS = false;
bool SW_TREADMILL = false;
bool BOWFLEX_T9 = false;
bool YPOO_MINI_PRO = false;
bool MX_TM = false;
bool FIT = false;
void testProfileCRC();
void updateProfileCRC();

View File

@@ -0,0 +1,363 @@
//
// SmartControl.c
//
// Copyright © 2017 Kinetic. All rights reserved.
//
#include "SmartControl.h"
#include <random>
#include <vector>
#define SensorHz 10000
typedef enum smart_control_command
{
SMART_CONTROL_COMMAND_SET_PERFORMANCE = 0x00,
SMART_CONTROL_COMMAND_SPINDOWN_CALIBRATION = 0x03
} smart_control_command;
uint32_t getrandom(uint32_t upper_bound) {
static std::random_device rd; // Used to initialize the random engine
static std::mt19937 gen(rd()); // Mersenne Twister engine
std::uniform_int_distribution<uint32_t> dis(0, upper_bound - 1);
return dis(gen);
}
uint8_t hash8WithSeed(uint8_t hash, const uint8_t *buffer, uint8_t length)
{
const uint8_t crc8_table[256] = {
0x00, 0x91, 0xe3, 0x72, 0x07, 0x96, 0xe4, 0x75,
0x0e, 0x9f, 0xed, 0x7c, 0x09, 0x98, 0xea, 0x7b,
0x1c, 0x8d, 0xff, 0x6e, 0x1b, 0x8a, 0xf8, 0x69,
0x12, 0x83, 0xf1, 0x60, 0x15, 0x84, 0xf6, 0x67,
0x38, 0xa9, 0xdb, 0x4a, 0x3f, 0xae, 0xdc, 0x4d,
0x36, 0xa7, 0xd5, 0x44, 0x31, 0xa0, 0xd2, 0x43,
0x24, 0xb5, 0xc7, 0x56, 0x23, 0xb2, 0xc0, 0x51,
0x2a, 0xbb, 0xc9, 0x58, 0x2d, 0xbc, 0xce, 0x5f,
0x70, 0xe1, 0x93, 0x02, 0x77, 0xe6, 0x94, 0x05,
0x7e, 0xef, 0x9d, 0x0c, 0x79, 0xe8, 0x9a, 0x0b,
0x6c, 0xfd, 0x8f, 0x1e, 0x6b, 0xfa, 0x88, 0x19,
0x62, 0xf3, 0x81, 0x10, 0x65, 0xf4, 0x86, 0x17,
0x48, 0xd9, 0xab, 0x3a, 0x4f, 0xde, 0xac, 0x3d,
0x46, 0xd7, 0xa5, 0x34, 0x41, 0xd0, 0xa2, 0x33,
0x54, 0xc5, 0xb7, 0x26, 0x53, 0xc2, 0xb0, 0x21,
0x5a, 0xcb, 0xb9, 0x28, 0x5d, 0xcc, 0xbe, 0x2f,
0xe0, 0x71, 0x03, 0x92, 0xe7, 0x76, 0x04, 0x95,
0xee, 0x7f, 0x0d, 0x9c, 0xe9, 0x78, 0x0a, 0x9b,
0xfc, 0x6d, 0x1f, 0x8e, 0xfb, 0x6a, 0x18, 0x89,
0xf2, 0x63, 0x11, 0x80, 0xf5, 0x64, 0x16, 0x87,
0xd8, 0x49, 0x3b, 0xaa, 0xdf, 0x4e, 0x3c, 0xad,
0xd6, 0x47, 0x35, 0xa4, 0xd1, 0x40, 0x32, 0xa3,
0xc4, 0x55, 0x27, 0xb6, 0xc3, 0x52, 0x20, 0xb1,
0xca, 0x5b, 0x29, 0xb8, 0xcd, 0x5c, 0x2e, 0xbf,
0x90, 0x01, 0x73, 0xe2, 0x97, 0x06, 0x74, 0xe5,
0x9e, 0x0f, 0x7d, 0xec, 0x99, 0x08, 0x7a, 0xeb,
0x8c, 0x1d, 0x6f, 0xfe, 0x8b, 0x1a, 0x68, 0xf9,
0x82, 0x13, 0x61, 0xf0, 0x85, 0x14, 0x66, 0xf7,
0xa8, 0x39, 0x4b, 0xda, 0xaf, 0x3e, 0x4c, 0xdd,
0xa6, 0x37, 0x45, 0xd4, 0xa1, 0x30, 0x42, 0xd3,
0xb4, 0x25, 0x57, 0xc6, 0xb3, 0x22, 0x50, 0xc1,
0xba, 0x2b, 0x59, 0xc8, 0xbd, 0x2c, 0x5e, 0xcf
};
for (uint8_t byte_index = 0; byte_index < length; byte_index++) {
hash = crc8_table[hash ^ buffer[byte_index]];
}
return hash;
}
double smart_control_speed_for_ticks(uint16_t ticks)
{
if (ticks == 0 || ticks == 65535) {
return 0;
}
return (6107.2561186) / ((double)ticks);
}
double smart_control_ticks_to_seconds(uint32_t ticks)
{
return (double)ticks / (double)SensorHz;
}
smart_control_power_data smart_control_process_power_data(uint8_t *data, size_t size)
{
uint8_t hashSeed = 0x42;
std::vector<uint8_t> inData(size); // Use vector instead of VLA
for (size_t i = 0; i < size; ++i) {
inData[i] = data[i];
}
uint8_t hash = hash8WithSeed(hashSeed, &inData[size - 1], 1);
for (size_t index = 0; index < size - 1; index++) {
inData[index] ^= hash;
hash = hash8WithSeed(hash, &inData[index], 1);
}
smart_control_power_data powerData;
if (size >= 14) {
powerData.mode = (smart_control_mode)inData[0];
powerData.targetResistance = ((uint16_t)inData[1] << 8) | (uint16_t)inData[2];
powerData.power = ((uint16_t)inData[3] << 8) | (uint16_t)inData[4];
powerData.cadenceRPM = inData[12];
if (size >= 18) {
uint32_t metersPerHour = ((uint32_t)inData[13] << 24) | ((uint32_t)inData[14] << 16) | ((uint32_t)inData[15] << 8) | (uint32_t)inData[16];
powerData.speedKPH = metersPerHour / 1000.0;
} else {
uint16_t rollerTicks = ((uint16_t)inData[5] << 8) | (uint16_t)inData[6];
powerData.speedKPH = smart_control_speed_for_ticks(rollerTicks);
}
} else {
powerData.mode = SMART_CONTROL_MODE_ERG;
powerData.targetResistance = 0;
powerData.cadenceRPM = 0;
powerData.power = 0;
powerData.speedKPH = 0;
}
return powerData;
}
smart_control_config_data smart_control_process_config_data(uint8_t *data, size_t size)
{
uint8_t hashSeed = 0x42;
std::vector<uint8_t> inData(size); // Use vector instead of VLA
for (size_t i = 0; i < size; ++i) {
inData[i] = data[i];
}
uint8_t hash = hash8WithSeed(hashSeed, &inData[size - 1], 1);
for (size_t index = 0; index < size - 1; index++) {
inData[index] ^= hash;
hash = hash8WithSeed(hash, &inData[index], 1);
}
smart_control_config_data configData;
if (size >= 5) {
configData.updateRate = inData[0];
configData.tickRate = ((uint32_t)inData[1] << 16) | ((uint32_t)inData[2] << 8) | (uint32_t)inData[3];
configData.firmwareUpdateState = inData[4];
if (size >= 13) {
configData.systemStatus = ((uint16_t)inData[5] << 8) | (uint16_t)inData[6];
configData.calibrationState = (smart_control_calibration_state)inData[7];
uint32_t spindownTicks = ((uint32_t)inData[8] << 24) | ((uint32_t)inData[9] << 16) | ((uint32_t)inData[10] << 8) | (uint32_t)inData[11];
configData.spindownTime = smart_control_ticks_to_seconds(spindownTicks);
}
if (size >= 15) {
uint16_t metersPerHour = ((uint16_t)inData[12] << 8) | (uint16_t)inData[13];
configData.calibrationThresholdKPH = metersPerHour / 1000.0;
} else {
configData.calibrationThresholdKPH = 33.8;
}
if (size >= 18) {
uint16_t metersPerHour = ((uint16_t)inData[14] << 8) | (uint16_t)inData[15];
configData.brakeCalibrationThresholdKPH = metersPerHour / 1000.0;
configData.brakeStrength = inData[16];
} else {
configData.brakeCalibrationThresholdKPH = 45;
configData.brakeStrength = 55;
}
if (size >= 19) {
configData.brakeOffset = inData[17];
} else {
configData.brakeOffset = 128;
}
if (size >= 20) {
configData.noiseFilter = inData[18];
} else {
configData.noiseFilter = 1;
}
} else {
configData.updateRate = 1;
configData.tickRate = 10000;
configData.firmwareUpdateState = 0;
configData.systemStatus = 0;
configData.calibrationState = SMART_CONTROL_CALIBRATION_STATE_NOT_PERFORMED;
configData.spindownTime = 0;
configData.calibrationThresholdKPH = 33.8;
configData.brakeCalibrationThresholdKPH = 45;
configData.brakeStrength = 55;
configData.brakeOffset = 128;
configData.noiseFilter = 1;
}
return configData;
}
#ifndef MIN
#define MIN(a,b) (((a)<(b))?(a):(b))
#define MAX(a,b) (((a)>(b))?(a):(b))
#endif
smart_control_set_mode_erg_data smart_control_set_mode_erg_command(uint16_t targetWatts)
{
smart_control_set_mode_erg_data data;
uint16_t clamped = MAX(0, MIN(0xFFFF, targetWatts));
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
data.bytes[1] = SMART_CONTROL_MODE_ERG;
data.bytes[2] = (uint16_t)clamped >> 8;
data.bytes[3] = (uint16_t)clamped;
data.bytes[4] = getrandom(0x100); // nonce
uint8_t dataLength = 5;
// Encode Packet
uint8_t hashSeed = 0x42;
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
for (unsigned index = 0; index < dataLength - 1; index++) {
uint8_t temp = data.bytes[index];
data.bytes[index] ^= hash;
hash = hash8WithSeed(hash, &temp, 1);
}
return data;
}
smart_control_set_mode_fluid_data smart_control_set_mode_fluid_command(uint8_t level)
{
smart_control_set_mode_fluid_data data;
uint8_t clamped = MAX(0, MIN(9, level));
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
data.bytes[1] = SMART_CONTROL_MODE_FLUID;
data.bytes[2] = clamped;
data.bytes[3] = getrandom(0x100); // nonce
uint8_t dataLength = 4;
// Encode Packet
uint8_t hashSeed = 0x42;
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
for (unsigned index = 0; index < dataLength - 1; index++) {
uint8_t temp = data.bytes[index];
data.bytes[index] ^= hash;
hash = hash8WithSeed(hash, &temp, 1);
}
return data;
}
smart_control_set_mode_brake_data smart_control_set_mode_brake_command(float percent)
{
smart_control_set_mode_brake_data data;
// normalize to 0-65535
float clamped = MAX(0, MIN(1, percent));
uint16_t normalized = (uint16_t) round(65535 * clamped);
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
data.bytes[1] = SMART_CONTROL_MODE_BRAKE;
data.bytes[2] = normalized >> 8;
data.bytes[3] = normalized;
data.bytes[4] = getrandom(0x100); // nonce
uint8_t dataLength = 5;
// Encode Packet
uint8_t hashSeed = 0x42;
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
for (unsigned index = 0; index < dataLength - 1; index++) {
uint8_t temp = data.bytes[index];
data.bytes[index] ^= hash;
hash = hash8WithSeed(hash, &temp, 1);
}
return data;
}
smart_control_set_mode_simulation_data smart_control_set_mode_simulation_command(float weightKG, float rollingCoeff, float windCoeff, float grade, float windSpeedMPS)
{
smart_control_set_mode_simulation_data data;
data.bytes[0] = SMART_CONTROL_COMMAND_SET_PERFORMANCE;
data.bytes[1] = SMART_CONTROL_MODE_SIMULATION;
// weight is in KGs ... multiply by 100 to get 2 points of precision
uint16_t weight100 = (uint16_t) roundf(MIN(655.36, weightKG) * 100);
data.bytes[2] = weight100 >> 8;
data.bytes[3] = weight100;
// Rolling coeff is < 1. multiply by 10,000 to get 5 points of precision
// coeff cannot be larger than 6.5536 otherwise it rolls over ...
uint16_t rr10000 = (uint16_t) roundf(MIN(6.5536, rollingCoeff) * 10000);
data.bytes[4] = rr10000 >> 8;
data.bytes[5] = rr10000;
// Wind coeff is typically < 1. multiply by 10,000 to get 5 points of precision
// coeff cannot be larger than 6.5536 otherwise it rolls over ...
uint16_t wr10000 = (uint16_t) roundf(MIN(6.5536, windCoeff) * 10000);
data.bytes[6] = wr10000 >> 8;
data.bytes[7] = wr10000;
// Grade is between -45.0 and 45.0
// Mulitply by 100 to get 2 points of precision
int16_t grade100 = (int16_t) roundf(MAX(-45, MIN(45, grade)) * 100);
data.bytes[8] = grade100 >> 8;
data.bytes[9] = grade100;
// windspeed is in meters / second. convert to CM / second
int16_t windSpeedCM = (int16_t) roundf(windSpeedMPS * 100);
data.bytes[10] = windSpeedCM >> 8;
data.bytes[11] = windSpeedCM;
data.bytes[12] = getrandom(0x100); // nonce
uint8_t dataLength = 13;
// Encode Packet
uint8_t hashSeed = 0x42;
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
for (unsigned index = 0; index < dataLength - 1; index++) {
uint8_t temp = data.bytes[index];
data.bytes[index] ^= hash;
hash = hash8WithSeed(hash, &temp, 1);
}
return data;
}
smart_control_calibration_command_data smart_control_start_calibration_command(bool brakeCalibration)
{
smart_control_calibration_command_data data;
data.bytes[0] = SMART_CONTROL_COMMAND_SPINDOWN_CALIBRATION;
data.bytes[1] = 0x01;
data.bytes[2] = brakeCalibration ? 0x01 : 0x00;
data.bytes[3] = getrandom(0x100); // nonce
uint8_t dataLength = 4;
// Encode Packet
uint8_t hashSeed = 0x42;
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
for (unsigned index = 0; index < dataLength - 1; index++) {
uint8_t temp = data.bytes[index];
data.bytes[index] ^= hash;
hash = hash8WithSeed(hash, &temp, 1);
}
return data;
}
smart_control_calibration_command_data smart_control_stop_calibration_command()
{
smart_control_calibration_command_data data;
data.bytes[0] = SMART_CONTROL_COMMAND_SPINDOWN_CALIBRATION;
data.bytes[1] = 0x00;
data.bytes[2] = 0x00;
data.bytes[3] = getrandom(0x100); // nonce
uint8_t dataLength = 4;
// Encode Packet
uint8_t hashSeed = 0x42;
uint8_t hash = hash8WithSeed(hashSeed, &data.bytes[dataLength - 1], 1);
for (unsigned index = 0; index < dataLength - 1; index++) {
uint8_t temp = data.bytes[index];
data.bytes[index] ^= hash;
hash = hash8WithSeed(hash, &temp, 1);
}
return data;
}

View File

@@ -0,0 +1,237 @@
//
// SmartControl.h
//
// Copyright © 2017 Kinetic. All rights reserved.
//
#ifndef SmartControl_h
#define SmartControl_h
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
static const char SMART_CONTROL_SERVICE_UUID[] = "E9410200-B434-446B-B5CC-36592FC4C724";
static const char SMART_CONTROL_SERVICE_POWER_UUID[] = "E9410201-B434-446B-B5CC-36592FC4C724";
static const char SMART_CONTROL_SERVICE_CONFIG_UUID[] = "E9410202-B434-446B-B5CC-36592FC4C724";
static const char SMART_CONTROL_SERVICE_CONTROL_UUID[] = "E9410203-B434-446B-B5CC-36592FC4C724";
/*! Smart Control Resistance Mode */
typedef enum smart_control_mode
{
SMART_CONTROL_MODE_ERG = 0x00,
SMART_CONTROL_MODE_FLUID = 0x01,
SMART_CONTROL_MODE_BRAKE = 0x02,
SMART_CONTROL_MODE_SIMULATION = 0x03
} smart_control_mode;
/*! Smart Control Power Data */
typedef struct smart_control_power_data
{
/*! Current Resistance Mode */
smart_control_mode mode;
/*! Current Power (Watts) */
uint16_t power;
/*! Current Speed (KPH) */
double speedKPH;
/*! Current Cadence (Virtual RPM) */
uint8_t cadenceRPM;
/*! Current wattage the RU is Targetting */
uint16_t targetResistance;
} smart_control_power_data;
/*!
Deserialize the raw power data (bytes) broadcast by Smart Control.
@param data The raw data broadcast from the [Power Service -> Power] Characteristic
@param size The size of the data array
@return Smart Control Power Data Struct
*/
smart_control_power_data smart_control_process_power_data(uint8_t *data, size_t size);
/*! Smart Control Calibration State */
typedef enum smart_control_calibration_state
{
SMART_CONTROL_CALIBRATION_STATE_NOT_PERFORMED = 0,
SMART_CONTROL_CALIBRATION_STATE_INITIALIZING = 1,
SMART_CONTROL_CALIBRATION_STATE_SPEED_UP = 2,
SMART_CONTROL_CALIBRATION_STATE_START_COASTING = 3,
SMART_CONTROL_CALIBRATION_STATE_COASTING = 4,
SMART_CONTROL_CALIBRATION_STATE_SPEED_UP_DETECTED = 5,
SMART_CONTROL_CALIBRATION_STATE_COMPLETE = 10
} smart_control_calibration_state;
/*! Smart Control Configuration Data */
typedef struct smart_control_config_data
{
/*! Power Data Update Rate (Hz) */
uint8_t updateRate;
/*! Current Calibration State of the RU */
smart_control_calibration_state calibrationState;
/*! Current Spindown Time being applied to the Power Data */
double spindownTime;
/*! Calibration Speed Threshold (KPH) */
double calibrationThresholdKPH;
/*! Brake Calibration Speed Threshold (KPH) */
double brakeCalibrationThresholdKPH;
/*! Clock Speed of Data Update (Hz) */
uint32_t tickRate;
/*! System Health Status (non-zero indicates problem) */
uint16_t systemStatus;
/*! Firmware Update State (Internal Use Only) */
uint8_t firmwareUpdateState;
/*! Normalized Brake Strength calculated by a Brake Calibration */
uint8_t brakeStrength;
/*! Normalized Brake Offset calculated by a Brake Calibration */
uint8_t brakeOffset;
/*! Noise Filter Strength */
uint8_t noiseFilter;
} smart_control_config_data;
/*!
Deserialize the raw config data (bytes) broadcast by Smart Control.
@param data The raw data broadcast from the [Power Service -> Config] Characteristic
@param size The size of the data array
@return Smart Control Config Data Struct
*/
smart_control_config_data smart_control_process_config_data(uint8_t *data, size_t size);
/*! Command Structs to write to the Control Point Characteristic */
#ifdef _MSC_VER
#pragma pack(push, 1)
#endif
typedef struct smart_control_set_mode_erg_data
{
uint8_t bytes[5];
}
#ifndef _MSC_VER
__attribute__((packed))
#endif
smart_control_set_mode_erg_data;
typedef struct smart_control_set_mode_fluid_data
{
uint8_t bytes[4];
}
#ifndef _MSC_VER
__attribute__((packed))
#endif
smart_control_set_mode_fluid_data;
typedef struct smart_control_set_mode_brake_data
{
uint8_t bytes[5];
}
#ifndef _MSC_VER
__attribute__((packed))
#endif
smart_control_set_mode_brake_data;
typedef struct smart_control_set_mode_simulation_data
{
uint8_t bytes[13];
}
#ifndef _MSC_VER
__attribute__((packed))
#endif
smart_control_set_mode_simulation_data;
typedef struct smart_control_calibration_command_data
{
uint8_t bytes[4];
}
#ifndef _MSC_VER
__attribute__((packed))
#endif
smart_control_calibration_command_data;
#ifdef _MSC_VER
#pragma pack(pop)
#endif
/*!
Creates the Command to put the Resistance Unit into ERG mode with a target wattage.
@param targetWatts The target wattage the RU should try to maintain by adjusting the brake position
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
*/
smart_control_set_mode_erg_data smart_control_set_mode_erg_command(uint16_t targetWatts);
/*!
Creates the Command to put the Resistance Unit into a "Fluid" mode, mimicking a fluid trainer.
This mode is a simplified interface for the Simulation Mode, where:
Rider + Bike weight is 85kg
Rolling Coeff is 0.004
Wind Resistance is 0.60
Grade is equal to the "level" parameter
Wind Speed is 0.0
@param level Difficulty level (0-9) the RU should apply (simulated grade %)
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
*/
smart_control_set_mode_fluid_data smart_control_set_mode_fluid_command(uint8_t level);
/*!
Creates the Command to put the Resistance Unit Brake at a specific position (as a percent).
@param percent Percent (0-1) of brake resistance to apply.
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
*/
smart_control_set_mode_brake_data smart_control_set_mode_brake_command(float percent);
/*!
Creates the Command to put the Resistance Unit into Simulation mode.
@param weightKG Weight of Rider and Bike in Kilograms (kg)
@param rollingCoeff Rolling Resistance Coefficient (0.004 for asphault)
@param windCoeff Wind Resistance Coeffienct (0.6 default)
@param grade Grade (-45 to 45) of simulated hill
@param windSpeedMPS Head or Tail wind speed (meters / second)
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
*/
smart_control_set_mode_simulation_data smart_control_set_mode_simulation_command(float weightKG, float rollingCoeff, float windCoeff, float grade, float windSpeedMPS);
/*!
Creates the Command to start the Calibration Process.
@param brakeCalibration Calibrates the brake (only needs to be done once, result is stored on unit)
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
*/
smart_control_calibration_command_data smart_control_start_calibration_command(bool brakeCalibration);
/*!
Creates the Command to stop the Calibration Process.
This is not necessary if the calibration process is allowed to complete.
@return Write the bytes of the struct to the Control Point Characteristic (w/ response)
*/
smart_control_calibration_command_data smart_control_stop_calibration_command(void);
#endif /* SmartControl_h */

View File

@@ -0,0 +1,528 @@
#include "kineticinroadbike.h"
#include "homeform.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
kineticinroadbike::kineticinroadbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &kineticinroadbike::update);
refresh->start(200ms);
}
void kineticinroadbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
// one for the resistance changed event (spontaneous), and one for the other ones.
if (wait_for_response) {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
return;
}
if (!gattWriteCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
QStringLiteral(" // ") + info;
}
loop.exec();
}
void kineticinroadbike::forceResistance(resistance_t requestResistance) {
/*uint8_t noOpData[] = {0xf0, 0xb1, 0x01, 0x00, 0x00};
noOpData[3] = requestResistance;
for (uint8_t i = 0; i < sizeof(noOpData) - 1; i++) {
noOpData[4] += noOpData[i]; // the last byte is a sort of a checksum
}
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("force resistance"), false, true);*/
}
void kineticinroadbike::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
btinit();
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() && initDone) {
update_metrics(true, watts());
// sending poll every 2 seconds
if (sec1Update++ >= (2000 / refresh->interval())) {
sec1Update = 0;
// updateDisplay(elapsed);
}
if (requestResistance != -1) {
if (requestResistance > max_resistance)
requestResistance = max_resistance;
else if (requestResistance <= 0)
requestResistance = 1;
if (requestResistance != currentResistance().value()) {
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
forceResistance(requestResistance);
}
requestResistance = -1;
}
if (requestStart != -1) {
qDebug() << QStringLiteral("starting...");
// btinit();
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
qDebug() << QStringLiteral("stopping...");
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
void kineticinroadbike::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
resistance_t kineticinroadbike::pelotonToBikeResistance(int pelotonResistance) {
for (resistance_t i = 1; i < max_resistance; i++) {
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) > pelotonResistance) {
return i;
}
}
if (pelotonResistance < bikeResistanceToPeloton(1))
return 1;
else
return max_resistance;
}
resistance_t kineticinroadbike::resistanceFromPowerRequest(uint16_t power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << Cadence.value();
return 1;
/*
if (Cadence.value() == 0)
return 1;
for (resistance_t i = 1; i < max_resistance; i++) {
if (wattsFromResistance(i) <= power && wattsFromResistance(i + 1) >= power) {
qDebug() << QStringLiteral("resistanceFromPowerRequest") << wattsFromResistance(i)
<< wattsFromResistance(i + 1) << power;
return i;
}
}
if (power < wattsFromResistance(1))
return 1;
else
return max_resistance;*/
}
double kineticinroadbike::bikeResistanceToPeloton(double resistance) {
QSettings settings;
// 0,0097x3 - 0,4972x2 + 10,126x - 37,08
double p = ((pow(resistance, 3) * 0.0097) - (0.4972 * pow(resistance, 2)) + (10.126 * resistance) - 37.08);
if (p < 0) {
p = 0;
}
return (p * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
}
void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
qDebug() << " << " + newValue.toHex(' ') << characteristic.uuid().toString();
lastPacket = newValue;
QByteArray encryptedData = newValue;
int dataSize = encryptedData.size();
if (dataSize < 14 || characteristic.uuid() != QBluetoothUuid(QStringLiteral("e9410201-b434-446b-b5cc-36592fc4c724"))) {
qDebug() << "Invalid data size";
return;
}
smart_control_power_data pD = smart_control_process_power_data((uint8_t *)newValue.data(), dataSize);
// Set the parsed values to the bike metrics
Resistance = pD.targetResistance;
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = pD.speedKPH;
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = pD.cadenceRPM;
}
m_watt = pD.power;
// Debug output
qDebug() << "Decrypted values:";
qDebug() << " Mode:" << pD.mode;
qDebug() << " Resistance:" << pD.targetResistance;
qDebug() << " Power:" << pD.power << "watts";
qDebug() << " Speed:" << pD.speedKPH << "km/h";
qDebug() << " Cadence:" << pD.cadenceRPM << "rpm";
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(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
Heart = (uint8_t)KeepAwakeHelper::heart();
} else
#endif
{
if (heartRateBeltName.startsWith(QLatin1String("Disabled"))) {
update_hr_from_external();
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
m_pelotonResistance = m_pelotonResistance.value();
qDebug() << QStringLiteral("Current Local elapsed: ") + GetElapsedFromPacket(newValue).toString();
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
qDebug() << QStringLiteral("Current Distance: ") + QString::number(Distance.value());
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
QTime kineticinroadbike::GetElapsedFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(3) << 8) | packet.at(4);
QTime t(0, convertedData / 60, convertedData % 60);
return t;
}
double kineticinroadbike::GetDistanceFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
double data = ((double)convertedData) / 100.0f;
return data;
}
void kineticinroadbike::btinit() {
uint8_t initData1[] = {0xf0, 0xa1, 0x00, 0x91};
uint8_t initData2[] = {0xf0, 0xa3, 0x00, 0x93};
uint8_t initData3[] = {0xf0, 0xb0, 0x01, 0x01, 0xa2};
// uint8_t initData4[] = { 0xf0, 0x60, 0x00, 0x50 }; // get sleep command
// useless i guess
// writeCharacteristic(initData4, sizeof(initData4), "get sleep", false, true);
// in the snoof log it repeats this frame 4 times, i will have to analyze the response to understand if 4 times are
// enough
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
initDone = true;
if (lastResistanceBeforeDisconnection != -1) {
qDebug() << QStringLiteral("forcing resistance to ") + QString::number(lastResistanceBeforeDisconnection) +
QStringLiteral(". It was the last value before the disconnection.");
forceResistance(lastResistanceBeforeDisconnection);
lastResistanceBeforeDisconnection = -1;
}
}
void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("e9410203-b434-446b-b5cc-36592fc4c724"));
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("e9410201-b434-446b-b5cc-36592fc4c724"));
QBluetoothUuid _gattNotify2CharacteristicId(QStringLiteral("e9410202-b434-446b-b5cc-36592fc4c724"));
QBluetoothUuid _gattNotify3CharacteristicId(QStringLiteral("e9410204-b434-446b-b5cc-36592fc4c724"));
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
if (gattCommunicationChannelService->state() == QLowEnergyService::ServiceDiscovered) {
// establish hook into notifications
auto characteristics_list = gattCommunicationChannelService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle() << c.properties();
}
}
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
gattNotify2Characteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
gattNotify3Characteristic = gattCommunicationChannelService->characteristic(_gattNotify3CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&kineticinroadbike::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&kineticinroadbike::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &kineticinroadbike::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&kineticinroadbike::descriptorWritten);
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_rower =
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
if (virtual_device_rower) {
qDebug() << QStringLiteral("creating virtual rower interface...");
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
} else {
qDebug() << QStringLiteral("creating virtual bike interface...");
auto virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,&kineticinroadbike::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &kineticinroadbike::changeInclination);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
}
firstStateChanged = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
gattCommunicationChannelService->writeDescriptor(
gattNotify2Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
gattCommunicationChannelService->writeDescriptor(
gattNotify3Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void kineticinroadbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' ');
initRequest = true;
emit connectedAndDiscovered();
}
void kineticinroadbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
}
void kineticinroadbike::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("e9410200-b434-446b-b5cc-36592fc4c724"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&kineticinroadbike::stateChanged);
if(gattCommunicationChannelService != nullptr) {
gattCommunicationChannelService->discoverDetails();
} else {
if(homeform::singleton())
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
m_control->disconnectFromDevice();
}
}
void kineticinroadbike::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("kineticinroadbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void kineticinroadbike::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << QStringLiteral("kineticinroadbike::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void kineticinroadbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')';
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &kineticinroadbike::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &kineticinroadbike::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &kineticinroadbike::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &kineticinroadbike::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
qDebug() << QStringLiteral("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
bool kineticinroadbike::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
uint16_t kineticinroadbike::watts() {
if (currentCadence().value() == 0) {
return 0;
}
return m_watt.value();
//return wattsFromResistance(Resistance.value());
}
void kineticinroadbike::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
lastResistanceBeforeDisconnection = Resistance.value();
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}

View File

@@ -0,0 +1,109 @@
#ifndef KINETICINROADBIKE_H
#define KINETICINROADBIKE_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "devices/bike.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualrower.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
#include "SmartControl.h"
class kineticinroadbike : public bike {
Q_OBJECT
public:
kineticinroadbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
resistance_t resistanceFromPowerRequest(uint16_t power) override;
bool connected() override;
private:
const resistance_t max_resistance = 32;
double bikeResistanceToPeloton(double resistance);
double GetDistanceFromPacket(const QByteArray &packet);
QTime GetElapsedFromPacket(const QByteArray &packet);
void btinit();
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
void forceResistance(resistance_t requestResistance);
uint16_t watts() override;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
QLowEnergyCharacteristic gattNotify3Characteristic;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t counterPoll = 1;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
resistance_t lastResistanceBeforeDisconnection = -1;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
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();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // KINETICINROADBIKE_H

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

@@ -0,0 +1,400 @@
#include "lifespantreadmill.h"
#include "keepawakehelper.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
using namespace std::chrono_literals;
lifespantreadmill::lifespantreadmill(uint32_t pollDeviceTime, bool noConsole, bool noHeartService,
double forceInitSpeed, double forceInitInclination) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
this->noConsole = noConsole;
this->noHeartService = noHeartService;
this->pollDeviceTime = pollDeviceTime;
if (forceInitSpeed > 0)
lastSpeed = forceInitSpeed;
if (forceInitInclination > 0)
lastInclination = forceInitInclination;
refresh = new QTimer(this);
connect(refresh, &QTimer::timeout, this, &lifespantreadmill::update);
refresh->start(500ms);
}
void lifespantreadmill::writeCharacteristic(uint8_t* data, uint8_t data_len, const QString& info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
QByteArray command((const char*)data, data_len);
lastPacket = command;
// Determine command type from packet
if (command.startsWith(QByteArray::fromHex("A182"))) {
currentCommand = CommandState::QuerySpeed;
} else if (command.startsWith(QByteArray::fromHex("A185"))) {
currentCommand = CommandState::QueryDistance;
} else if (command.startsWith(QByteArray::fromHex("A187"))) {
currentCommand = CommandState::QueryCalories;
} else if (command.startsWith(QByteArray::fromHex("A189"))) {
currentCommand = CommandState::QueryTime;
} else if (command.startsWith(QByteArray::fromHex("D0"))) {
currentCommand = CommandState::SetSpeed;
} else if (command.startsWith(QByteArray::fromHex("E1"))) {
currentCommand = CommandState::Start;
} else if (command.startsWith(QByteArray::fromHex("E0"))) {
currentCommand = CommandState::Stop;
} else if (command.startsWith(QByteArray::fromHex("A188"))) {
currentCommand = CommandState::QuerySteps;
}
if (wait_for_response) {
connect(this, &lifespantreadmill::packetReceived, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
if (gattWriteCharacteristic.properties() & QLowEnergyCharacteristic::WriteNoResponse) {
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer,
QLowEnergyService::WriteWithoutResponse);
} else {
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
}
if (!disable_log)
qDebug() << " >> " << writeBuffer->toHex(' ') << " // " << info;
loop.exec();
}
void lifespantreadmill::btinit(bool startTape) {
const QByteArray initSequence[] = {
QByteArray::fromHex("0200000000"), // Unknown
QByteArray::fromHex("C200000000"), // Firmware
QByteArray::fromHex("E9FF000000"), // Zeroes
QByteArray::fromHex("E400F40000") // Zeroes
};
for (const auto& cmd : initSequence) {
writeCharacteristic((uint8_t*)cmd.data(), cmd.size(), QStringLiteral("init"), false, true);
}
initDone = true;
}
double lifespantreadmill::GetSpeedFromPacket(const QByteArray& packet) {
if (packet.length() < 4) return 0.0;
return ((double)((uint16_t)((uint8_t)packet.at(2)) + ((uint16_t)((uint8_t)packet.at(3))) / 100.0));
}
double lifespantreadmill::GetInclinationFromPacket(const QByteArray& packet) {
if (packet.length() < 3) return 0.0;
return packet[2];
}
double lifespantreadmill::GetKcalFromPacket(const QByteArray& packet) {
if (packet.length() < 4) return 0.0;
return (packet[2] << 8) | packet[3];
}
double lifespantreadmill::GetDistanceFromPacket(const QByteArray& packet) {
if (packet.length() < 4) return 0.0;
double data = ((packet[2] << 8) | packet[3]) / 10.0;
return data;
}
void lifespantreadmill::forceSpeed(double requestSpeed) {
uint16_t speed_int = (uint16_t)(requestSpeed * 100);
uint8_t units = speed_int / 100;
uint8_t hundredths = speed_int % 100;
uint8_t cmd[] = {0xd0, units, hundredths, 0, 0};
writeCharacteristic(cmd, sizeof(cmd), QStringLiteral("set speed"), false, true);
}
void lifespantreadmill::forceIncline(double requestIncline) {
// Not implemented for this model
}
void lifespantreadmill::updateDisplay(uint16_t elapsed) {
// Not implemented for this model
}
void lifespantreadmill::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
uint32_t lifespantreadmill::GetStepsFromPacket(const QByteArray& packet) {
if (packet.length() < 4) return 0;
return ((uint16_t)((uint8_t)packet[2]) << 8) | (uint16_t)((uint8_t)packet[3]);
}
void lifespantreadmill::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
static uint8_t queue = 0;
qDebug() << m_control->state() << bluetoothDevice.isValid() << gattCommunicationChannelService
<< gattWriteCharacteristic.isValid() << initDone << requestSpeed << requestInclination;
if (initRequest) {
initRequest = false;
btinit((lastSpeed > 0 ? true : false));
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() && initDone) {
QSettings settings;
// ******************************************* virtual treadmill init *************************************
if (!firstInit && !this->hasVirtualDevice()) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
if (virtual_device_enabled) {
emit debug(QStringLiteral("creating virtual treadmill interface..."));
auto virtualTreadMill = new virtualtreadmill(this, noHeartService);
connect(virtualTreadMill, &virtualtreadmill::debug, this, &lifespantreadmill::debug);
connect(virtualTreadMill, &virtualtreadmill::changeInclination, this,
&lifespantreadmill::changeInclinationRequested);
this->setVirtualDevice(virtualTreadMill, VIRTUAL_DEVICE_MODE::PRIMARY);
firstInit = 1;
}
}
// ********************************************************************************************************
if(queue == 0) {
// Query metrics periodically
uint8_t speedQuery[] = {0xA1, 0x82, 0x00, 0x00, 0x00};
writeCharacteristic(speedQuery, sizeof(speedQuery), QStringLiteral("query speed"), false, true);
queue = 1;
} else {
uint8_t stepQuery[] = {0xA1, 0x88, 0x00, 0x00, 0x00};
writeCharacteristic(stepQuery, sizeof(stepQuery), QStringLiteral("query steps"), false, true);
queue = 0;
}
if (requestStart != -1) {
uint8_t start[] = {0xE1, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(start, sizeof(start), QStringLiteral("start"), false, true);
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1 || requestPause != -1) {
uint8_t stop[] = {0xE0, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(stop, sizeof(stop), QStringLiteral("stop"), false, true);
requestStop = -1;
requestPause = -1;
}
update_metrics(true, watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat()));
}
}
void lifespantreadmill::characteristicChanged(const QLowEnergyCharacteristic& characteristic,
const QByteArray& newValue) {
QSettings settings;
QByteArray value = newValue;
qDebug() << " << " << value.length() << value.toHex(' ') << (int)currentCommand;
double speed = 0.0;
switch(currentCommand) {
case CommandState::QuerySpeed:
speed = GetSpeedFromPacket(value);
if (Speed.value() != speed) {
emit speedChanged(speed);
}
emit debug(QStringLiteral("Current speed: ") + QString::number(speed));
Speed = speed;
if (speed > 0) {
lastSpeed = speed;
}
break;
case CommandState::QueryDistance:
Distance = GetDistanceFromPacket(value);
break;
case CommandState::QueryCalories:
KCal = GetKcalFromPacket(value);
break;
case CommandState::QuerySteps:
{
uint32_t newSteps = GetStepsFromPacket(value);
if (uint32_t(StepCount.value()) != newSteps) {
StepCount = newSteps;
emit debug(QStringLiteral("Current steps: ") + QString::number(StepCount.value()));
}
}
break;
default:
break;
}
if (!firstCharacteristicChanged) {
if (watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) {
KCal += ((((0.048 * ((double)watts(settings.value(QZSettings::weight, QZSettings::default_weight).toFloat())) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) / 200.0) /
(60000.0 / ((double)lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
}
Distance += ((Speed.value() / 3600.0) /
(1000.0 / (lastTimeCharacteristicChanged.msecsTo(QDateTime::currentDateTime()))));
}
update_hr_from_external();
cadenceFromAppleWatch();
lastTimeCharacteristicChanged = QDateTime::currentDateTime();
firstCharacteristicChanged = false;
currentCommand = CommandState::None;
emit packetReceived();
}
bool lifespantreadmill::connected() {
return initDone;
}
double lifespantreadmill::minStepInclination() {
return 1.0;
}
bool lifespantreadmill::autoPauseWhenSpeedIsZero() {
return lastStart == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStart + 10000);
}
bool lifespantreadmill::autoStartWhenSpeedIsGreaterThenZero() {
return (lastStop == 0 || QDateTime::currentMSecsSinceEpoch() > (lastStop + 25000)) && requestStop == -1;
}
// Direct copy of Bowflex Bluetooth compatibility functions
void lifespantreadmill::serviceDiscovered(const QBluetoothUuid& gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void lifespantreadmill::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
QBluetoothUuid _gattCommunicationChannelServiceId((uint16_t)0xfff0);
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
qDebug() << "WRONG SERVICE";
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&lifespantreadmill::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void lifespantreadmill::characteristicWritten(const QLowEnergyCharacteristic& characteristic,
const QByteArray& newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void lifespantreadmill::descriptorWritten(const QLowEnergyDescriptor& descriptor,
const QByteArray& newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + " " + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
}
void lifespantreadmill::stateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if (state == QLowEnergyService::ServiceDiscovered) {
QBluetoothUuid _gattWriteCharacteristicId((uint16_t)0xfff2);
QBluetoothUuid _gattNotifyCharacteristicId((uint16_t)0xfff1);
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotifyCharacteristicId);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&lifespantreadmill::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&lifespantreadmill::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &lifespantreadmill::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&lifespantreadmill::descriptorWritten);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void lifespantreadmill::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState) {
Speed = 0;
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
initDone = false;
}
}
void lifespantreadmill::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("errorService ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)));
}
void lifespantreadmill::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("error ") + QString::fromLocal8Bit(metaEnum.valueToKey(err)));
}
void lifespantreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &lifespantreadmill::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &lifespantreadmill::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &lifespantreadmill::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &lifespantreadmill::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
emit debug(QStringLiteral("Cannot connect to remote device."));
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("Controller connected. Search services..."));
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
emit debug(QStringLiteral("LowEnergy controller disconnected"));
emit disconnected();
});
m_control->connectToDevice();
return;
}
}

View File

@@ -0,0 +1,110 @@
#ifndef LIFESPANTREADMILL_H
#define LIFESPANTREADMILL_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include "treadmill.h"
class lifespantreadmill : public treadmill {
Q_OBJECT
public:
lifespantreadmill(uint32_t poolDeviceTime = 200, bool noConsole = false, bool noHeartService = false,
double forceInitSpeed = 0.0, double forceInitInclination = 0.0);
bool connected() override;
double minStepInclination() override;
bool autoPauseWhenSpeedIsZero() override;
bool autoStartWhenSpeedIsGreaterThenZero() override;
bool canHandleSpeedChange() override { return false; }
bool canHandleInclineChange() override { return false; }
private:
double GetSpeedFromPacket(const QByteArray &packet);
double GetInclinationFromPacket(const QByteArray &packet);
double GetKcalFromPacket(const QByteArray &packet);
double GetDistanceFromPacket(const QByteArray &packet);
void forceSpeed(double requestSpeed);
void forceIncline(double requestIncline);
void updateDisplay(uint16_t elapsed);
void btinit(bool startTape);
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
bool noConsole = false;
bool noHeartService = false;
uint32_t GetStepsFromPacket(const QByteArray& packet);
uint32_t pollDeviceTime = 200;
uint8_t sec1Update = 0;
uint8_t firstInit = 0;
QByteArray lastPacket;
QDateTime lastTimeCharacteristicChanged;
bool firstCharacteristicChanged = true;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
bool initDone = false;
bool initRequest = false;
enum class CommandState {
None,
QuerySpeed,
QueryDistance,
QueryCalories,
QueryTime,
QuerySteps,
SetSpeed,
Start,
Stop
};
CommandState currentCommand = CommandState::None;
Q_SIGNALS:
void disconnected();
void debug(QString string);
void speedChanged(double speed);
void packetReceived();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
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 changeInclinationRequested(double grade, double percentage);
void serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // LIFESPANTREADMILL_H

View File

@@ -720,6 +720,8 @@ void m3ibike::processAdvertising(const QByteArray &data) {
{
if (heartRateBeltDisabled && (k3.pulse == 0 || settings.value(QZSettings::heart_ignore_builtin, QZSettings::default_heart_ignore_builtin).toBool())) {
update_hr_from_external();
} else if(!heartRateBeltDisabled) {
// we don't have to anything in this case
} else {
Heart = k3.pulse;
}

View File

@@ -264,7 +264,7 @@ void nordictrackifitadbbike::processPendingDatagrams() {
if(freemotion_coachbike_b22_7)
m_pelotonResistance = (100 / 24) * resistance;
else
m_pelotonResistance = (100 / 32) * resistance;
m_pelotonResistance = bikeResistanceToPeloton(resistance);
qDebug() << QStringLiteral("Current Peloton Resistance: ") << m_pelotonResistance.value()
<< resistance;
if(!gearsAvailable && !nordictrackadbbike_resistance) {
@@ -497,56 +497,88 @@ void nordictrackifitadbbike::onHRM(int hrm) {
}
}
resistance_t nordictrackifitadbbike::pelotonToBikeResistance(int pelotonResistance) {
if (pelotonResistance <= 10) {
double nordictrackifitadbbike::bikeResistanceToPeloton(resistance_t bikeResistance) {
for (resistance_t i = 1; i < max_resistance; i++) {
if (pelotonToBikeResistance(i) <= bikeResistance && pelotonToBikeResistance(i + 1) > bikeResistance) {
return i;
}
}
if (bikeResistance < pelotonToBikeResistance(1))
return 1;
else
return 100;
}
resistance_t nordictrackifitadbbike::pelotonToBikeResistance(int pelotonResistance) {
QSettings settings;
int resistanceLevel;
if (pelotonResistance <= 5) {
resistanceLevel = 1;
}
if (pelotonResistance <= 20) {
return 2;
else if (pelotonResistance <= 7) {
resistanceLevel = 2;
}
if (pelotonResistance <= 25) {
return 3;
else if (pelotonResistance <= 9) {
resistanceLevel = 3;
}
if (pelotonResistance <= 30) {
return 4;
else if (pelotonResistance <= 10) {
resistanceLevel = 4;
}
if (pelotonResistance <= 35) {
return 5;
else if (pelotonResistance <= 15) {
resistanceLevel = 5;
}
if (pelotonResistance <= 40) {
return 6;
else if (pelotonResistance <= 25) {
resistanceLevel = 6;
}
if (pelotonResistance <= 45) {
return 7;
else if (pelotonResistance <= 30) {
resistanceLevel = 7;
}
if (pelotonResistance <= 50) {
return 8;
else if (pelotonResistance <= 35) {
resistanceLevel = 8;
}
if (pelotonResistance <= 55) {
return 9;
else if (pelotonResistance <= 40) {
resistanceLevel = 9;
}
if (pelotonResistance <= 60) {
return 10;
else if (pelotonResistance <= 45) {
resistanceLevel = 10;
}
if (pelotonResistance <= 65) {
return 11;
else if (pelotonResistance <= 50) {
resistanceLevel = 11;
}
if (pelotonResistance <= 70) {
return 12;
else if (pelotonResistance <= 55) {
resistanceLevel = 12;
}
if (pelotonResistance <= 75) {
return 13;
else if (pelotonResistance <= 60) {
resistanceLevel = 13;
}
if (pelotonResistance <= 80) {
return 14;
else if (pelotonResistance <= 65) {
resistanceLevel = 14;
}
if (pelotonResistance <= 85) {
return 15;
else if (pelotonResistance <= 70) {
resistanceLevel = 15;
}
if (pelotonResistance <= 100) {
return 16;
else if (pelotonResistance <= 75) {
resistanceLevel = 16;
}
return Resistance.value();
else if (pelotonResistance <= 80) {
resistanceLevel = 17;
}
else if (pelotonResistance <= 85) {
resistanceLevel = 18;
}
else if (pelotonResistance <= 95) {
resistanceLevel = 19;
}
else if (pelotonResistance <= 100) {
resistanceLevel = 20;
}
else {
return Resistance.value();
}
return (resistanceLevel * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
}
void nordictrackifitadbbike::forceResistance(double resistance) {}

View File

@@ -69,11 +69,12 @@ class nordictrackifitadbbike : public bike {
bool ifitCompatible() override;
private:
const resistance_t max_resistance = 17; // max inclination for s22i
const resistance_t max_resistance = 20; // max inclination for s22i
void forceResistance(double resistance);
uint16_t watts() override;
double getDouble(QString v);
uint16_t wattsFromResistance(double inclination, double cadence);
double bikeResistanceToPeloton(resistance_t resistance);
QTimer *refresh;

View File

@@ -12,6 +12,7 @@
#include <QThread>
#include <chrono>
#include <math.h>
#include <QRegularExpression>
using namespace std::chrono_literals;
@@ -139,6 +140,7 @@ nordictrackifitadbtreadmill::nordictrackifitadbtreadmill(bool noWriteResistance,
connect(socket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams()));
}
#ifdef Q_OS_WIN32
if (nordictrack_ifit_adb_remote)
{
logcatAdbThread = new nordictrackifitadbtreadmillLogcatAdbThread("logcatAdbThread");
connect(logcatAdbThread, &nordictrackifitadbtreadmillLogcatAdbThread::onSpeedInclination, this,
@@ -225,8 +227,13 @@ void nordictrackifitadbtreadmill::processPendingDatagrams() {
if (line.contains(QStringLiteral("Changed KPH"))) {
QStringList aValues = line.split(" ");
if (aValues.length()) {
speed = getDouble(aValues.last());
parseSpeed(speed);
QString numberStr = aValues.last();
// Regular expression to match numbers like X.X or XX.X
QRegularExpression regex(QStringLiteral("\\d+\\.\\d+"));
if (regex.match(numberStr).hasMatch()) {
speed = getDouble(numberStr);
parseSpeed(speed);
}
}
} else if (line.contains(QStringLiteral("Changed Grade"))) {
QStringList aValues = line.split(" ");
@@ -261,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) {
@@ -279,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;
@@ -298,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) + " " +
@@ -340,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

@@ -120,8 +120,14 @@ void npecablebike::characteristicChanged(const QLowEnergyCharacteristic &charact
qDebug() << QStringLiteral(" << char ") << characteristic.uuid();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A5B)) {
if(BIKE_DEVICE && characteristic.uuid() == QBluetoothUuid(QStringLiteral("6e400003-b5a3-f393-e0a9-e50e24dcca9e")) &&
newValue.length() == 20 && (uint8_t)newValue.at(0) == 0xFF && newValue.at(1) == 0x1F) {
Resistance = newValue.at(15);
emit resistanceRead(Resistance.value());
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
} else if (characteristic.uuid() == QBluetoothUuid((quint16)0x2A5B)) {
lastPacket = newValue;
uint8_t index = 1;
@@ -600,6 +606,10 @@ void npecablebike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
device.address().toString() + ')');
{
bluetoothDevice = device;
if(bluetoothDevice.name().toUpper().startsWith("BIKE ")) {
qDebug() << "BIKE workaround enabled";
BIKE_DEVICE = true;
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &npecablebike::serviceDiscovered);

View File

@@ -64,6 +64,8 @@ class npecablebike : public bike {
uint16_t oldLastCrankEventTime = 0;
uint16_t oldCrankRevs = 0;
bool BIKE_DEVICE = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -0,0 +1,464 @@
#include "pitpatbike.h"
#include "homeform.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
#ifdef Q_OS_IOS
extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
pitpatbike::pitpatbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain) {
#ifdef Q_OS_IOS
QZ_EnableDiscoveryCharsAndDescripttors = true;
#endif
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
this->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &pitpatbike::update);
refresh->start(200ms);
}
void pitpatbike::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
// if there are some crash here, maybe it's better to use 2 separate event for the characteristicChanged.
// one for the resistance changed event (spontaneous), and one for the other ones.
if (wait_for_response) {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
} else {
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, &loop, &QEventLoop::quit);
timeout.singleShot(300ms, &loop, &QEventLoop::quit);
}
if (gattCommunicationChannelService->state() != QLowEnergyService::ServiceState::ServiceDiscovered ||
m_control->state() == QLowEnergyController::UnconnectedState) {
qDebug() << QStringLiteral("writeCharacteristic error because the connection is closed");
return;
}
if (!gattWriteCharacteristic.isValid()) {
qDebug() << QStringLiteral("gattWriteCharacteristic is invalid");
return;
}
if (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') +
QStringLiteral(" // ") + info;
}
loop.exec();
}
void pitpatbike::forceResistance(resistance_t requestResistance) {
uint8_t noOpData[] = {0x6a, 0x06, 0x51, 0x82, 0x01, 0x01, 0xd5, 0x43};
noOpData[5] = requestResistance;
uint8_t crc = 0;
for(int i = 0; i < sizeof(noOpData) - 2; i++) {
crc ^= noOpData[i];
}
noOpData[6] = crc ^ 0x6A;
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("force resistance"), false, true);
}
void pitpatbike::sendPoll() {
uint8_t noOpData[] = {0x6a, 0x05, 0xfd, 0xf8, 0x43};
writeCharacteristic(noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
}
void pitpatbike::update() {
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
btinit();
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() && initDone) {
update_metrics(true, watts());
// sending poll every 2 seconds
if (sec1Update++ >= (1000 / refresh->interval())) {
sec1Update = 0;
sendPoll();
// updateDisplay(elapsed);
}
if (requestResistance != -1) {
if (requestResistance > max_resistance)
requestResistance = max_resistance;
else if (requestResistance <= 0)
requestResistance = 1;
if (requestResistance != currentResistance().value()) {
qDebug() << QStringLiteral("writing resistance ") + QString::number(requestResistance);
forceResistance(requestResistance);
}
requestResistance = -1;
}
if (requestStart != -1) {
qDebug() << QStringLiteral("starting...");
// btinit();
requestStart = -1;
emit bikeStarted();
}
if (requestStop != -1) {
qDebug() << QStringLiteral("stopping...");
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
void pitpatbike::serviceDiscovered(const QBluetoothUuid &gatt) {
qDebug() << QStringLiteral("serviceDiscovered ") + gatt.toString();
}
resistance_t pitpatbike::pelotonToBikeResistance(int pelotonResistance) {
for (resistance_t i = 1; i < max_resistance; i++) {
if (bikeResistanceToPeloton(i) <= pelotonResistance && bikeResistanceToPeloton(i + 1) > pelotonResistance) {
return i;
}
}
if (pelotonResistance < bikeResistanceToPeloton(1))
return 1;
else
return max_resistance;
}
double pitpatbike::bikeResistanceToPeloton(double resistance) {
QSettings settings;
// 0,0097x3 - 0,4972x2 + 10,126x - 37,08
double p = ((pow(resistance, 3) * 0.0097) - (0.4972 * pow(resistance, 2)) + (10.126 * resistance) - 37.08);
if (p < 0) {
p = 0;
}
return (p * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
}
void pitpatbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
qDebug() << " << " + newValue.toHex(' ');
lastPacket = newValue;
if (newValue.length() != 30) {
return;
}
/*if ((uint8_t)(newValue.at(0)) != 0xf0 && (uint8_t)(newValue.at(1)) != 0xd1)
return;*/
double distance = GetDistanceFromPacket(newValue);
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
Cadence = ((uint8_t)newValue.at(25));
}
if (!settings.value(QZSettings::speed_power_based, QZSettings::default_speed_power_based).toBool()) {
Speed = 0.37497622 * ((double)Cadence.value());
} else {
Speed = metric::calculateSpeedFromPower(
watts(), Inclination.value(), Speed.value(),
fabs(QDateTime::currentDateTime().msecsTo(Speed.lastChanged()) / 1000.0), this->speedLimit());
}
m_watt = (uint16_t)((uint8_t)newValue.at(24)) + ((uint16_t)((uint8_t)newValue.at(23)) << 8);
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(
QDateTime::currentDateTime())))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
if (Cadence.value() > 0) {
CrankRevs++;
LastCrankEventTime += (uint16_t)(1024.0 / (((double)(Cadence.value())) / 60.0));
}
lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
#ifdef Q_OS_ANDROID
if (settings.value(QZSettings::ant_heart, QZSettings::default_ant_heart).toBool()) {
Heart = (uint8_t)KeepAwakeHelper::heart();
} else
#endif
{
if (heartRateBeltName.startsWith(QLatin1String("Disabled"))) {
update_hr_from_external();
}
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence = settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence && h && firstStateChanged) {
h->virtualbike_setCadence(currentCrankRevolutions(), lastCrankEventTime());
h->virtualbike_setHeartRate((uint8_t)metrics_override_heartrate());
}
#endif
#endif
// these useless lines are needed to calculate the AVG resistance and AVG peloton resistance since
// echelon just send the resistance values when it changes
Resistance = newValue.at(5);
m_pelotonResistance = m_pelotonResistance.value();
qDebug() << QStringLiteral("Current Local elapsed: ") + GetElapsedFromPacket(newValue).toString();
qDebug() << QStringLiteral("Current Speed: ") + QString::number(Speed.value());
qDebug() << QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value());
qDebug() << QStringLiteral("Current Cadence: ") + QString::number(Cadence.value());
qDebug() << QStringLiteral("Current Distance: ") + QString::number(distance);
qDebug() << QStringLiteral("Current CrankRevs: ") + QString::number(CrankRevs);
qDebug() << QStringLiteral("Last CrankEventTime: ") + QString::number(LastCrankEventTime);
qDebug() << QStringLiteral("Current Watt: ") + QString::number(watts());
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
QTime pitpatbike::GetElapsedFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(3) << 8) | packet.at(4);
QTime t(0, convertedData / 60, convertedData % 60);
return t;
}
double pitpatbike::GetDistanceFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(7) << 8) | packet.at(8);
double data = ((double)convertedData) / 100.0f;
return data;
}
void pitpatbike::btinit() {
initDone = true;
}
void pitpatbike::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId((uint16_t)0xfbb1);
QBluetoothUuid _gattNotify1CharacteristicId((uint16_t)0xfbb2);
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
qDebug() << QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
Q_ASSERT(gattWriteCharacteristic.isValid());
Q_ASSERT(gattNotify1Characteristic.isValid());
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&pitpatbike::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&pitpatbike::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &pitpatbike::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&pitpatbike::descriptorWritten);
// ******************************************* virtual bike init *************************************
if (!firstStateChanged && !this->hasVirtualDevice()
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
&& !h
#endif
#endif
) {
QSettings settings;
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
bool virtual_device_rower =
settings.value(QZSettings::virtual_device_rower, QZSettings::default_virtual_device_rower).toBool();
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
bool cadence =
settings.value(QZSettings::bike_cadence_sensor, QZSettings::default_bike_cadence_sensor).toBool();
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
if (ios_peloton_workaround && cadence) {
qDebug() << "ios_peloton_workaround activated!";
h = new lockscreen();
h->virtualbike_ios();
} else
#endif
#endif
if (virtual_device_enabled) {
if (virtual_device_rower) {
qDebug() << QStringLiteral("creating virtual rower interface...");
auto virtualRower = new virtualrower(this, noWriteResistance, noHeartService);
// connect(virtualRower,&virtualrower::debug ,this,&echelonrower::debug);
this->setVirtualDevice(virtualRower, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
} else {
qDebug() << QStringLiteral("creating virtual bike interface...");
auto virtualBike =
new virtualbike(this, noWriteResistance, noHeartService, bikeResistanceOffset, bikeResistanceGain);
// connect(virtualBike,&virtualbike::debug ,this,&pitpatbike::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &pitpatbike::changeInclination);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
}
firstStateChanged = 1;
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void pitpatbike::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
qDebug() << QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' ');
initRequest = true;
emit connectedAndDiscovered();
}
void pitpatbike::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
qDebug() << QStringLiteral("characteristicWritten ") + newValue.toHex(' ');
}
void pitpatbike::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId((uint16_t)0xfbb0);
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,
&pitpatbike::stateChanged);
if(gattCommunicationChannelService != nullptr) {
gattCommunicationChannelService->discoverDetails();
} else {
if(homeform::singleton())
homeform::singleton()->setToastRequested("Bluetooth Service Error! Restart the bike!");
m_control->disconnectFromDevice();
}
}
void pitpatbike::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
qDebug() << QStringLiteral("pitpatbike::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void pitpatbike::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
qDebug() << QStringLiteral("pitpatbike::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString();
}
void pitpatbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
qDebug() << QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')';
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &pitpatbike::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &pitpatbike::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &pitpatbike::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &pitpatbike::controllerStateChanged);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, [this](QLowEnergyController::Error error) {
Q_UNUSED(error);
Q_UNUSED(this);
qDebug() << QStringLiteral("Cannot connect to remote device.");
emit disconnected();
});
connect(m_control, &QLowEnergyController::connected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("Controller connected. Search services...");
m_control->discoverServices();
});
connect(m_control, &QLowEnergyController::disconnected, this, [this]() {
Q_UNUSED(this);
qDebug() << QStringLiteral("LowEnergy controller disconnected");
emit disconnected();
});
// Connect
m_control->connectToDevice();
return;
}
bool pitpatbike::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
uint16_t pitpatbike::watts() {
if (currentCadence().value() == 0) {
return 0;
}
return m_watt.value();
}
void pitpatbike::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
lastResistanceBeforeDisconnection = Resistance.value();
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}

View File

@@ -0,0 +1,104 @@
#ifndef PITPATBIKE_H
#define PITPATBIKE_H
#include <QBluetoothDeviceDiscoveryAgent>
#include <QtBluetooth/qlowenergyadvertisingdata.h>
#include <QtBluetooth/qlowenergyadvertisingparameters.h>
#include <QtBluetooth/qlowenergycharacteristic.h>
#include <QtBluetooth/qlowenergycharacteristicdata.h>
#include <QtBluetooth/qlowenergycontroller.h>
#include <QtBluetooth/qlowenergydescriptordata.h>
#include <QtBluetooth/qlowenergyservice.h>
#include <QtBluetooth/qlowenergyservicedata.h>
#include <QtCore/qbytearray.h>
#ifndef Q_OS_ANDROID
#include <QtCore/qcoreapplication.h>
#else
#include <QtGui/qguiapplication.h>
#endif
#include <QtCore/qlist.h>
#include <QtCore/qmutex.h>
#include <QtCore/qscopedpointer.h>
#include <QtCore/qtimer.h>
#include <QDateTime>
#include <QObject>
#include <QString>
#include "devices/bike.h"
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualrower.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class pitpatbike : public bike {
Q_OBJECT
public:
pitpatbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset,
double bikeResistanceGain);
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
bool connected() override;
private:
const resistance_t max_resistance = 32;
double bikeResistanceToPeloton(double resistance);
double GetDistanceFromPacket(const QByteArray &packet);
QTime GetElapsedFromPacket(const QByteArray &packet);
void btinit();
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
void forceResistance(resistance_t requestResistance);
void sendPoll();
uint16_t watts() override;
QTimer *refresh;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
resistance_t lastResistanceBeforeDisconnection = -1;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
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();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // PITPATBIKE_H

View File

@@ -152,25 +152,28 @@ uint16_t proformbike::wattsFromResistance(resistance_t resistance) {
}
void proformbike::forceResistance(resistance_t requestResistance) {
QSettings settings;
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
bool nordictrack_GX4_5_bike =
settings.value(QZSettings::nordictrack_GX4_5_bike, QZSettings::default_nordictrack_GX4_5_bike).toBool();
bool nordictrack_gx_2_7 =
settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
bool proform_hybrid_trainer_PFEL03815 =
settings
.value(QZSettings::proform_hybrid_trainer_PFEL03815, QZSettings::default_proform_hybrid_trainer_PFEL03815)
.toBool();
bool proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
bool proform_cycle_trainer_300_ci =
settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci)
.toBool();
bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
bool proform_bike_325_csx = settings.value(QZSettings::proform_bike_325_csx, QZSettings::default_proform_bike_325_csx).toBool();
if(proform_bike_PFEVEX71316_0) {
// Value: 0012020402120812020400000004000000000700
// Value: ff040001002c0000000000000000000000000000
if (proform_studio || proform_tdf_10) {
// 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};
@@ -185,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};
@@ -474,7 +560,7 @@ void proformbike::forceResistance(resistance_t requestResistance) {
writeCharacteristic((uint8_t *)res22, sizeof(res22), QStringLiteral("resistance22"), false, true);
break;
}
} else if(nordictrack_GX4_5_bike) {
} else if(nordictrack_GX4_5_bike || nordictrack_gx_44_pro) {
const uint8_t res25[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xe8, 0x26, 0x00, 0x25, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res24[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0x58, 0x25, 0x00, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00};
const uint8_t res23[] = {0xff, 0x0d, 0x02, 0x04, 0x02, 0x09, 0x07, 0x09, 0x02, 0x01, 0x04, 0xc8, 0x23, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -712,32 +798,6 @@ void proformbike::update() {
gattNotify1Characteristic.isValid() && initDone) {
update_metrics(true, watts());
QSettings settings;
bool proform_tour_de_france_clc =
settings.value(QZSettings::proform_tour_de_france_clc, QZSettings::default_proform_tour_de_france_clc)
.toBool();
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
bool nordictrack_gx_2_7 =
settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
bool proform_cycle_trainer_300_ci =
settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci)
.toBool();
bool proform_cycle_trainer_400 =
settings.value(QZSettings::proform_cycle_trainer_400, QZSettings::default_proform_cycle_trainer_400)
.toBool();
bool proform_hybrid_trainer_PFEL03815 = settings
.value(QZSettings::proform_hybrid_trainer_PFEL03815,
QZSettings::default_proform_hybrid_trainer_PFEL03815)
.toBool();
bool proform_bike_sb =
settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
bool proform_bike_PFEVEX71316_1 =
settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1)
.toBool();
bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
bool proform_bike_325_csx = settings.value(QZSettings::proform_bike_325_csx, QZSettings::default_proform_bike_325_csx).toBool();
uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x07, 0x15, 0x02, 0x00,
0x0f, 0xbc, 0x90, 0x70, 0x00, 0x00, 0x00, 0x40, 0x00, 0x00};
@@ -822,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"));
@@ -865,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),
@@ -873,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"));
@@ -895,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();
@@ -904,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"));
@@ -913,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),
@@ -921,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"));
@@ -942,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"));
@@ -980,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;
}
@@ -1014,11 +1134,7 @@ void proformbike::update() {
}
bool proformbike::inclinationAvailableByHardware() {
QSettings settings;
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
if (proform_studio || proform_tdf_10)
if (proform_studio || proform_tdf_10 || proform_bike_PFEVEX71316_0)
return true;
else
return false;
@@ -1085,25 +1201,10 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
Q_UNUSED(characteristic);
QSettings settings;
bool proform_tdf_jonseed_watt =
settings.value(QZSettings::proform_tdf_jonseed_watt, QZSettings::default_proform_tdf_jonseed_watt).toBool();
QString heartRateBeltName =
settings.value(QZSettings::heart_rate_belt_name, QZSettings::default_heart_rate_belt_name).toString();
bool proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
bool proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
bool proform_tdf_jonseed_watt =
settings.value(QZSettings::proform_tdf_jonseed_watt, QZSettings::default_proform_tdf_jonseed_watt).toBool();
bool nordictrack_gx_2_7 =
settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
bool proform_hybrid_trainer_PFEL03815 =
settings
.value(QZSettings::proform_hybrid_trainer_PFEL03815, QZSettings::default_proform_hybrid_trainer_PFEL03815)
.toBool();
bool proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
bool proform_bike_PFEVEX71316_1 =
settings.value(QZSettings::proform_bike_PFEVEX71316_1, QZSettings::default_proform_bike_PFEVEX71316_1).toBool();
bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
bool proform_bike_325_csx = settings.value(QZSettings::proform_bike_325_csx, QZSettings::default_proform_bike_325_csx).toBool();
bool nordictrack_GX4_5_bike =
settings.value(QZSettings::nordictrack_GX4_5_bike, QZSettings::default_nordictrack_GX4_5_bike).toBool();
double cadence_gain = settings.value(QZSettings::cadence_gain, QZSettings::default_cadence_gain).toDouble();
double cadence_offset = settings.value(QZSettings::cadence_offset, QZSettings::default_cadence_offset).toDouble();
@@ -1111,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;
}
@@ -1208,7 +1310,7 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
if (m_watts > 3000) {
m_watts = 0;
} else {
if(nordictrack_GX4_5_bike) {
if(nordictrack_GX4_5_bike || nordictrack_gx_44_pro) {
switch ((uint8_t)newValue.at(11)) {
case 0:
case 1:
@@ -1481,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:
@@ -1725,21 +1876,23 @@ void proformbike::characteristicChanged(const QLowEnergyCharacteristic &characte
void proformbike::btinit() {
QSettings settings;
bool nordictrack_gx_2_7 =
settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
bool proform_cycle_trainer_400 =
settings.value(QZSettings::proform_cycle_trainer_400, QZSettings::default_proform_cycle_trainer_400).toBool();
bool proform_cycle_trainer_300_ci =
settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci).toBool();
bool proform_hybrid_trainer_PFEL03815 =
settings
.value(QZSettings::proform_hybrid_trainer_PFEL03815, QZSettings::default_proform_hybrid_trainer_PFEL03815)
.toBool();
bool proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
bool proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
bool proform_bike_325_csx = settings.value(QZSettings::proform_bike_325_csx, QZSettings::default_proform_bike_325_csx).toBool();
bool nordictrack_GX4_5_bike =
settings.value(QZSettings::nordictrack_GX4_5_bike, QZSettings::default_nordictrack_GX4_5_bike).toBool();
proform_studio = settings.value(QZSettings::proform_studio, QZSettings::default_proform_studio).toBool();
proform_tdf_10 = settings.value(QZSettings::proform_tdf_10, QZSettings::default_proform_tdf_10).toBool();
nordictrack_GX4_5_bike = settings.value(QZSettings::nordictrack_GX4_5_bike, QZSettings::default_nordictrack_GX4_5_bike).toBool();
nordictrack_gx_2_7 = settings.value(QZSettings::nordictrack_gx_2_7, QZSettings::default_nordictrack_gx_2_7).toBool();
proform_hybrid_trainer_PFEL03815 = settings.value(QZSettings::proform_hybrid_trainer_PFEL03815, QZSettings::default_proform_hybrid_trainer_PFEL03815).toBool();
proform_bike_sb = settings.value(QZSettings::proform_bike_sb, QZSettings::default_proform_bike_sb).toBool();
proform_cycle_trainer_300_ci = settings.value(QZSettings::proform_cycle_trainer_300_ci, QZSettings::default_proform_cycle_trainer_300_ci).toBool();
proform_bike_225_csx = settings.value(QZSettings::proform_bike_225_csx, QZSettings::default_proform_bike_225_csx).toBool();
proform_bike_325_csx = settings.value(QZSettings::proform_bike_325_csx, QZSettings::default_proform_bike_325_csx).toBool();
proform_tour_de_france_clc = settings.value(QZSettings::proform_tour_de_france_clc, QZSettings::default_proform_tour_de_france_clc).toBool();
proform_studio_NTEX71021 = settings.value(QZSettings::proform_studio_NTEX71021, QZSettings::default_proform_studio_NTEX71021).toBool();
freemotion_coachbike_b22_7 = settings.value(QZSettings::freemotion_coachbike_b22_7, QZSettings::default_freemotion_coachbike_b22_7).toBool();
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;
@@ -1804,6 +1957,311 @@ 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
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, 0x04,
0x00, 0x22, 0x34, 0x44, 0x52, 0x6e, 0x88, 0x90, 0xbe, 0xda};
uint8_t initData11[] = {0x01, 0x12, 0xfc, 0x1c, 0x3a, 0x56, 0x90, 0xb8, 0xc6, 0x12,
0x24, 0x74, 0x82, 0xde, 0x18, 0x20, 0x6e, 0xaa, 0xec, 0x2c};
uint8_t initData12[] = {0xff, 0x08, 0x6a, 0xa6, 0x20, 0x80, 0x02, 0x00, 0x00, 0xa7,
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};
// 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);
} 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

@@ -78,6 +78,24 @@ class proformbike : public bike {
bool noWriteResistance = false;
bool noHeartService = false;
bool proform_studio = false;
bool proform_tdf_10 = false;
bool nordictrack_GX4_5_bike = false;
bool nordictrack_gx_2_7 = false;
bool proform_hybrid_trainer_PFEL03815 = false;
bool proform_bike_sb = false;
bool proform_cycle_trainer_300_ci =false;
bool proform_bike_225_csx = false;
bool proform_bike_325_csx = false;
bool proform_tour_de_france_clc = false;
bool proform_studio_NTEX71021 = false;
bool freemotion_coachbike_b22_7 = false;
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;
#endif

Some files were not shown because too many files have changed in this diff Show More