Compare commits

...

328 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
1be9e2620d Update trainprogram.cpp 2023-12-01 15:08:14 +01:00
Roberto Viola
805981df4d fixing 2023-12-01 14:53:33 +01:00
157 changed files with 19759 additions and 4225 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
@@ -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
@@ -862,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 .
@@ -890,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" .
@@ -1006,6 +1011,7 @@ jobs:
run: |
cd src
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
@@ -1052,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 .
@@ -1100,6 +1106,7 @@ jobs:
run: |
cd src
echo "#define STRAVA_SECRET_KEY ${{ secrets.strava_secret_key }}" > secret.h
echo "#define PELOTON_SECRET_KEY ${{ secrets.peloton_secret_key }}" > secret.h
echo "#define SMTP_USERNAME ${{ secrets.smtp_username }}" >> secret.h
echo "#define SMTP_PASSWORD ${{ secrets.smtp_password }}" >> secret.h
echo "#define SMTP_SERVER ${{ secrets.smtp_server }}" >> secret.h
@@ -1126,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
@@ -1141,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
@@ -1177,93 +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-2024-10-28/2024-10-22-raspios-bookworm-arm64-lite.img.xz
xz -d 2024-10-22-raspios-bookworm-arm64-lite.img.xz
ORIGINAL_SIZE=$(stat -c %s 2024-10-22-raspios-bookworm-arm64-lite.img)
NEW_SIZE=$((ORIGINAL_SIZE + 2*1024*1024*1024)) # Add 2GB
truncate -s $NEW_SIZE 2024-10-22-raspios-bookworm-arm64-lite.img
sudo apt-get update
sudo apt-get install -y parted
sudo parted 2024-10-22-raspios-bookworm-arm64-lite.img resizepart 2 100%
- name: Mount Raspberry Pi image
run: |
sudo apt-get install -y kpartx qemu-user-static
LOOP_DEVICE=$(sudo losetup -f --show 2024-10-22-raspios-bookworm-arm64-lite.img)
echo "Loop device is $LOOP_DEVICE"
sudo kpartx -av $LOOP_DEVICE
sudo mkdir -p /mnt/raspbian
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p2 /mnt/raspbian
sudo resize2fs /dev/mapper/$(basename $LOOP_DEVICE)p2
echo "LOOP_DEVICE=$LOOP_DEVICE" >> $GITHUB_ENV
sudo cp /usr/bin/qemu-aarch64-static /mnt/raspbian/usr/bin/
sudo mkdir -p /mnt/raspbian_p1
sudo mount /dev/mapper/$(basename $LOOP_DEVICE)p1 /mnt/raspbian_p1
- name: Install Qt and dependencies on Raspberry Pi image
run: |
sudo chroot /mnt/raspbian qemu-aarch64-static /bin/bash << EOF
apt-get update
apt-get install -y qtbase5-dev qtchooser qt5-qmake qtbase5-dev-tools qttools5-dev-tools libqt5svg5-dev qtmultimedia5-dev libqt5charts5-dev qtpositioning5-dev qtconnectivity5-dev libqt5websockets5-dev libqt5texttospeech5-dev libqt5bluetooth5 libqt5networkauth5-dev qml-module-qtlocation qml-module-qtpositioning qtlocation5-dev libqt5quickcontrols2-5 qtquickcontrols2-5-dev qml-module-qtquick-controls2
EOF
- name: Copy binary to Raspberry Pi image
run: |
sudo cp src/qdomyos-zwift /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: Modify boot config to enable bluetooth
run: |
# The following line makes it specific for Raspberry Pi Zero 2W.
# though I expect it is needed for Raspberry Pi 3B and maybe others as well
echo "[pi02]" | sudo tee -a /mnt/raspbian_p1/config.txt
echo "dtoverlay=miniuart-bt" | sudo tee -a /mnt/raspbian_p1/config.txt
- name: Unmount Raspberry Pi image
run: |
sudo umount /mnt/raspbian
sudo umount /mnt/raspbian_p1
sudo kpartx -d ${{ env.LOOP_DEVICE }}
sudo losetup -d ${{ env.LOOP_DEVICE }}
- name: Compress modified Raspberry Pi image
run: |
xz -z 2024-10-22-raspios-bookworm-arm64-lite.img
- name: Upload Raspberry Pi image as artifact
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-image
path: 2024-10-22-raspios-bookworm-arm64-lite.img.xz
- 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
@@ -1292,6 +1228,6 @@ jobs:
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/*
raspberry-pi-64bit-binary/*
2024-10-22-raspios-bookworm-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,6 +96,9 @@ 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

View File

@@ -288,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 */; };
@@ -363,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 */; };
@@ -385,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 */; };
@@ -406,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 */; };
@@ -428,10 +436,14 @@
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 */; };
@@ -537,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 */; };
@@ -561,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 */; };
@@ -1157,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>"; };
@@ -1279,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>"; };
@@ -1312,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>"; };
@@ -1343,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>"; };
@@ -1375,12 +1415,18 @@
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>"; };
@@ -1538,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>"; };
@@ -1573,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>"; };
@@ -2124,6 +2179,40 @@
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 */,
@@ -2582,6 +2671,9 @@
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>";
@@ -3475,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 */,
@@ -3524,7 +3617,9 @@
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 */,
@@ -3545,10 +3640,12 @@
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 */,
@@ -3574,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 */,
@@ -3639,6 +3737,7 @@
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 */,
@@ -3666,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 */,
@@ -3720,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 */,
@@ -3729,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 */,
@@ -3741,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 */,
@@ -3753,11 +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 */,
@@ -3796,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 */,
@@ -3803,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 */,
);
@@ -4145,7 +4255,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 964;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4339,7 +4449,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 964;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4569,7 +4679,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 964;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4665,7 +4775,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 964;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4757,7 +4867,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 964;
CURRENT_PROJECT_VERSION = 1035;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4822,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;
@@ -4871,7 +4983,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 964;
CURRENT_PROJECT_VERSION = 1035;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
@@ -4932,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

@@ -177,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

@@ -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.8" android:versionCode="952" 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

@@ -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

@@ -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);

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

@@ -96,30 +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();
}
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

@@ -81,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

@@ -469,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 =
@@ -489,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) {
@@ -783,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();
@@ -935,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);
@@ -953,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();
@@ -973,6 +1010,9 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} else if ((b.name().toUpper().startsWith(QStringLiteral("YPOO-U3-")) ||
b.name().toUpper().startsWith(QStringLiteral("SCH_590E")) ||
b.name().toUpper().startsWith(QStringLiteral("KETTLER ")) ||
(b.name().startsWith(QStringLiteral("Domyos-EL")) && settings.value(QZSettings::domyos_elliptical_fmts, QZSettings::default_domyos_elliptical_fmts).toBool()) ||
(b.name().toUpper().startsWith("SF-") && b.name().midRef(3).toInt() > 0) ||
b.name().toUpper().startsWith(QStringLiteral("MYELLIPTICAL ")) ||
b.name().toUpper().startsWith(QStringLiteral("CARDIOPOWER EEGO")) ||
(b.name().toUpper().startsWith(QStringLiteral("E35")) && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().startsWith(QStringLiteral("FS-")) && iconsole_elliptical)) && !ypooElliptical && filter) {
@@ -1269,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);
@@ -1347,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))) ||
@@ -1366,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
@@ -1466,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();
@@ -1484,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...
@@ -1512,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 ")) ||
@@ -1555,17 +1620,35 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(b.name().toUpper().startsWith("CSRB") && b.name().length() == 11) ||
(b.name().toUpper().startsWith("DU30-")) || // BodyTone du30
(b.name().toUpper().startsWith("BIKZU_")) ||
(b.name().toUpper().startsWith("WLT8828")) ||
(b.name().toUpper().startsWith("VANRYSEL-HT")) ||
(b.name().toUpper().startsWith("WLT8828")) ||
(b.name().toUpper().startsWith("HARISON-X15")) ||
(b.name().toUpper().startsWith("FEIVON V2")) ||
(b.name().toUpper().startsWith("FELVON V2")) ||
(b.name().toUpper().startsWith("JUSTO")) ||
(b.name().toUpper().startsWith("MYCYCLE ")) ||
(b.name().toUpper().startsWith("T2 ")) ||
(b.name().toUpper().startsWith("DR") && b.name().length() == 2) ||
(b.name().toUpper().startsWith("RC-MAX-")) ||
(b.name().toUpper().startsWith("TPS-SPBIKE-2.0")) ||
(b.name().toUpper().startsWith("NEO BIKE SMART")) ||
(b.name().toUpper().startsWith("ZDRIVE")) ||
(b.name().toUpper().startsWith("TUNTURI E60-")) ||
(b.name().toUpper().startsWith("JFBK5.0")) ||
(b.name().toUpper().startsWith("NEO 3M ")) ||
(b.name().toUpper().startsWith("JFBK7.0")) ||
(b.name().toUpper().startsWith("SPEEDRACEX")) ||
(b.name().toUpper().startsWith("POOBOO")) ||
(b.name().toUpper().startsWith("ZYCLE ZPRO")) ||
(b.name().toUpper().startsWith("SM720I")) ||
(b.name().toUpper().startsWith("AVANTI")) ||
(b.name().toUpper().startsWith("T300P_")) ||
(b.name().toUpper().startsWith("T200_")) ||
(b.name().toUpper().startsWith("BZ9110 ")) ||
(b.name().toUpper().startsWith("TITAN 7000")) ||
(b.name().toUpper().startsWith("LYDSTO")) ||
(b.name().toUpper().startsWith("CYCLO_")) ||
(b.name().toUpper().startsWith("LCR")) ||
(b.name().toUpper().startsWith(QStringLiteral("FIT-BK-"))) ||
(b.name().toUpper().startsWith("VFSPINBIKE")) ||
(b.name().toUpper().startsWith("GLT") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) ||
(b.name().toUpper().startsWith("SPORT01-") && deviceHasService(b, QBluetoothUuid((quint16)0x1826))) || // Labgrey Magnetic Exercise Bike https://www.amazon.co.uk/dp/B0CXMF1NPY?_encoding=UTF8&psc=1&ref=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&ref_=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&social_share=cm_sw_r_cp_ud_dp_PE420HA7RD7WJBZPN075&skipTwisterOG=1
@@ -1603,7 +1686,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
connect(wahooKickrSnapBike, &wahookickrsnapbike::debug, this, &bluetooth::debug);
wahooKickrSnapBike->deviceDiscovered(b);
this->signalBluetoothDeviceConnected(wahooKickrSnapBike);
} else if (b.name().toUpper().startsWith("BIKE ") && b.name().length() == 9 &&
} else if (b.name().toUpper().startsWith("BIKE ") && b.name().midRef(5).toInt() > 0 &&
!technogymBike && filter) {
this->setLastBluetoothDevice(b);
this->stopDiscovery();
@@ -1630,11 +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("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")) &&
@@ -1690,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) ||
@@ -1772,6 +1871,20 @@ 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().startsWith(QStringLiteral("ROW-S"))) &&
@@ -1790,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();
@@ -1922,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);
@@ -1955,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();
@@ -1987,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);
@@ -2099,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();
@@ -2139,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();
@@ -2171,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();
@@ -2197,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 =
@@ -2286,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) {
@@ -2393,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);
}
}
}
@@ -2618,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(),
@@ -2724,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) {
@@ -2731,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(),
@@ -2743,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!");
@@ -2818,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() {
@@ -3034,6 +3244,11 @@ void bluetooth::restart() {
delete tacxneo2Bike;
tacxneo2Bike = nullptr;
}
if (cycleopsphantomBike) {
delete cycleopsphantomBike;
cycleopsphantomBike = nullptr;
}
if (stagesBike) {
delete stagesBike;
@@ -3080,6 +3295,11 @@ void bluetooth::restart() {
delete trxappgateusbElliptical;
trxappgateusbElliptical = nullptr;
}
if (trxappgateusbRower) {
delete trxappgateusbRower;
trxappgateusbRower = nullptr;
}
if (soleBike) {
delete soleBike;
@@ -3125,6 +3345,10 @@ void bluetooth::restart() {
delete ziproTreadmill;
ziproTreadmill = nullptr;
}
if (lifespanTreadmill) {
delete lifespanTreadmill;
lifespanTreadmill = nullptr;
}
if (octaneElliptical) {
delete octaneElliptical;
@@ -3250,6 +3474,11 @@ void bluetooth::restart() {
delete sportsPlusBike;
sportsPlusBike = nullptr;
}
if (sportsPlusRower) {
delete sportsPlusRower;
sportsPlusRower = nullptr;
}
if (pelotonBike) {
delete pelotonBike;
@@ -3271,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;
@@ -3302,6 +3541,11 @@ void bluetooth::restart() {
delete horizonGr7Bike;
horizonGr7Bike = nullptr;
}
if (kineticInroadBike) {
delete kineticInroadBike;
kineticInroadBike = nullptr;
}
if (renphoBike) {
delete renphoBike;
@@ -3447,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) {
@@ -3465,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) {
@@ -3507,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) {
@@ -3557,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) {
@@ -3573,6 +3827,8 @@ bluetoothdevice *bluetooth::device() {
return technogymBike;
} else if (horizonGr7Bike) {
return horizonGr7Bike;
} else if (kineticInroadBike) {
return kineticInroadBike;
} else if (renphoBike) {
return renphoBike;
} else if (pafersBike) {
@@ -3588,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,6 +115,7 @@
#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"
@@ -129,6 +135,7 @@
#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"
@@ -177,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;
@@ -194,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;
@@ -225,6 +235,7 @@ class bluetooth : public QObject, public SignalHandler {
sportstechbike *sportsTechBike = nullptr;
sportstechelliptical *sportsTechElliptical = nullptr;
sportsplusbike *sportsPlusBike = nullptr;
sportsplusrower *sportsPlusRower = nullptr;
inspirebike *inspireBike = nullptr;
snodebike *snodeBike = nullptr;
eslinkertreadmill *eslinkerTreadmill = nullptr;
@@ -246,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;
@@ -253,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;
@@ -265,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;
@@ -351,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

@@ -445,6 +445,7 @@ class bluetoothdevice : public QObject {
virtual void cadenceSensor(uint8_t cadence);
virtual void powerSensor(uint16_t power);
virtual void speedSensor(double speed);
virtual void inclinationSensor(double grade, double inclination);
virtual void changeResistance(resistance_t res);
virtual void changePower(int32_t power);
virtual void changeInclination(double grade, double percentage);
@@ -676,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,12 +64,17 @@ 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>());
@@ -143,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) {
@@ -312,7 +320,7 @@ QByteArray csafe::write(const QStringList &arguments, bool surround_msg) {
qWarning("Message is too long: %d", message.size());
}
if (surround_msg) { // apply non-standard wrapping for PM3 rower
if (surround_msg) { // apply non-standard wrapping for PM3 rower
int maxmessage = qMax(message.size() + 1, maxresponse); // report IDs
if (maxmessage <= 21) {

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

@@ -90,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;

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

@@ -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

@@ -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

@@ -109,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 {
@@ -131,8 +131,12 @@ void ftmsbike::init() {
uint8_t write[] = {FTMS_REQUEST_CONTROL};
bool ret = writeCharacteristic(write, sizeof(write), "requestControl", false, true);
write[0] = {FTMS_START_RESUME};
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
if(resistance_lvl_mode && DIRETO_XR) {
setWheelDiameter(2070.0);
} else {
write[0] = {FTMS_START_RESUME};
ret = writeCharacteristic(write, sizeof(write), "start simulation", false, true);
}
if(ret) {
initDone = true;
@@ -140,6 +144,13 @@ void ftmsbike::init() {
}
}
ftmsbike::~ftmsbike() {
// Set wheel circumference back to 2070 when object is destroyed
if (DIRETO_XR) {
setWheelDiameter(2070.0);
}
}
void ftmsbike::zwiftPlayInit() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
@@ -228,7 +239,7 @@ 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) {
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);
@@ -240,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));
}
}
}
@@ -281,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) {
@@ -297,18 +318,27 @@ void ftmsbike::update() {
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()) {
QSettings settings;
wheelCircumference::GearTable table;
@@ -404,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(' ');
@@ -427,9 +459,22 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
return;
}
if(characteristic.uuid() == QBluetoothUuid(QStringLiteral("00000002-19ca-4651-86e5-fa29dcdd09d1")) && newValue.at(0) == 0x03) {
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
m_watt = lockscreen::zwift_hub_getPowerFromBuffer(newValue.mid(1));
qDebug() << "Current power: " << m_watt.value();
Cadence = lockscreen::zwift_hub_getCadenceFromBuffer(newValue.mid(1));
qDebug() << "Current cadence: " << Cadence.value();
#endif
#endif
return;
}
// Wattbike Atom First Generation - Display Gears
if(characteristic.uuid() == QBluetoothUuid(QStringLiteral("b4cc1224-bc02-4cae-adb9-1217ad2860d1")) &&
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;
@@ -459,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);
@@ -526,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;
@@ -562,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")))
@@ -895,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 "
@@ -955,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;
@@ -971,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();
}
@@ -1031,11 +1091,17 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
// handling gears
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);
}
@@ -1045,10 +1111,14 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
qDebug() << "grade override due to min_inclination " << min_inclination;
}
slope *= gain;
slope += (offset * 100);
b[3] = slope & 0xFF;
b[4] = slope >> 8;
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) {
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
@@ -1063,7 +1133,7 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
"org/cagnulen/qdomyoszwift/ZwiftHubBike",
"inclinationCommand",
"(D)[B",
slope);
((double)slope) / 100.0);
if(!result.isValid()) {
qDebug() << "inclinationCommand returned invalid value";
@@ -1149,7 +1219,7 @@ void ftmsbike::serviceScanDone(void) {
gattCommunicationChannelService.constLast()->discoverDetails();
// watt bikes has the 6 as default gear value
if(s == QBluetoothUuid(QStringLiteral("b4cc1223-bc02-4cae-adb9-1217ad2860d1"))) {
if(s == QBluetoothUuid(QStringLiteral("b4cc1223-bc02-4cae-adb9-1217ad2860d1")) && SS2K == false) {
WATTBIKE = true;
qDebug() << QStringLiteral("restoring gear 6 to watt bikes");
setGears(6);
@@ -1195,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");
@@ -1205,6 +1276,31 @@ void ftmsbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} 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()) {
@@ -1251,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;
@@ -1272,7 +1379,7 @@ double ftmsbike::maxGears() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(zwiftPlayService != nullptr && gears_zwift_ratio) {
if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio) {
wheelCircumference::GearTable g;
return g.maxGears;
} else if(WATTBIKE) {
@@ -1286,7 +1393,7 @@ double ftmsbike::minGears() {
QSettings settings;
bool gears_zwift_ratio = settings.value(QZSettings::gears_zwift_ratio, QZSettings::default_gears_zwift_ratio).toBool();
if(zwiftPlayService != nullptr && gears_zwift_ratio) {
if((zwiftPlayService != nullptr || DIRETO_XR) && gears_zwift_ratio ) {
return 1;
} else if(WATTBIKE) {
return 1;

View File

@@ -70,6 +70,7 @@ class ftmsbike : public bike {
Q_OBJECT
public:
ftmsbike(bool noWriteResistance, bool noHeartService, int8_t bikeResistanceOffset, double bikeResistanceGain);
~ftmsbike();
bool connected() override;
resistance_t pelotonToBikeResistance(int pelotonResistance) override;
resistance_t maxResistance() override { return max_resistance; }
@@ -84,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);
@@ -128,6 +130,13 @@ class ftmsbike : public bike {
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

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

View File

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

View File

@@ -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

@@ -182,6 +182,10 @@ void horizontreadmill::btinit() {
QStringLiteral("init"), false, true);
waitForAPacket();
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...0%");
}
init1:
initPacketRecv = false;
@@ -263,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;
@@ -354,6 +365,10 @@ void horizontreadmill::btinit() {
goto init2;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...25%");
}
init3:
initPacketRecv = false;
@@ -440,6 +455,10 @@ void horizontreadmill::btinit() {
goto init3;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...35%");
}
init4:
initPacketRecv = false;
@@ -526,6 +545,10 @@ void horizontreadmill::btinit() {
goto init4;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...50%");
}
init5:
initPacketRecv = false;
@@ -612,6 +635,10 @@ void horizontreadmill::btinit() {
goto init5;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...65%");
}
init6:
initPacketRecv = false;
@@ -698,6 +725,10 @@ void horizontreadmill::btinit() {
goto init6;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...80%");
}
init7:
initPacketRecv = false;
@@ -784,6 +815,10 @@ void horizontreadmill::btinit() {
goto init7;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization in progress...90%");
}
init8:
initPacketRecv = false;
@@ -809,11 +844,21 @@ void horizontreadmill::btinit() {
waitForAPacket();
goto init8;
}
if(homeform::singleton()) {
homeform::singleton()->setToastRequested("Treadmill initialization completed!");
}
}
messageID = 0x10;
}
if(wellfit_treadmill || SW_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);
@@ -1118,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();
@@ -1170,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 && !SW_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);
@@ -1180,6 +1226,9 @@ void horizontreadmill::forceSpeed(double requestSpeed) {
}
uint8_t writeS[] = {FTMS_SET_TARGET_SPEED, 0x00, 0x00};
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;
@@ -1237,7 +1286,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_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);
@@ -1337,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 {
@@ -1454,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))
@@ -1480,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))
@@ -1724,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()));
}
@@ -1758,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);
@@ -2119,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)) {
@@ -2171,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)
@@ -2360,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"))) {
@@ -2377,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"))) {
@@ -2392,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!");
@@ -2411,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"))) {
@@ -3135,7 +3213,7 @@ void horizontreadmill::testProfileCRC() {
double horizontreadmill::minStepInclination() {
QSettings settings;
bool toorx_ftms_treadmill = settings.value(QZSettings::toorx_ftms_treadmill, QZSettings::default_toorx_ftms_treadmill).toBool();
if (kettler_treadmill || trx3500_treadmill || toorx_ftms_treadmill || sole_tt8_treadmill || ICONCEPT_FTMS_treadmill || SW_TREADMILL)
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;
@@ -102,6 +104,10 @@ class horizontreadmill : public treadmill {
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

@@ -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

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

View File

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

View File

@@ -75,9 +75,9 @@ void proformtreadmill::forceIncline(double incline) {
write[14] = write[11] + write[12] + 0x11;
} else if (proform_treadmill_8_0 || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_treadmill_9_0 || proform_treadmill_se ||
proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_2000_treadmill ||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_400i || proform_treadmill_c700 ||
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro || proform_trainer_8_0 || proform_treadmill_705_cst_V80_44
) {
write[14] = write[11] + write[12] + 0x12;
} else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) {
@@ -104,9 +104,9 @@ void proformtreadmill::forceSpeed(double speed) {
write[14] = write[11] + write[12] + 0x11;
} else if (proform_treadmill_8_0 || proform_treadmill_9_0 || proform_treadmill_se || proform_cadence_lt ||
proform_treadmill_z1300i || proform_treadmill_l6_0s || norditrack_s25_treadmill || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_2000_treadmill ||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 || proform_carbon_tl || proform_proshox2 || nordictrack_s20i_treadmill || proform_595i_proshox2 ||
proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_sport_70 || proform_treadmill_575i || proform_performance_400i || proform_treadmill_c700 ||
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c) {
proform_treadmill_c960i || nordictrack_tseries5_treadmill || proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro || proform_trainer_8_0 || proform_treadmill_705_cst_V80_44) {
write[14] = write[11] + write[12] + 0x11;
} else if (!nordictrack_t65s_treadmill && !nordictrack_s30_treadmill && !nordictrack_s20_treadmill && !nordictrack_t65s_83_treadmill) {
for (uint8_t i = 0; i < 7; i++) {
@@ -259,6 +259,130 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (proform_treadmill_705_cst_V80_44) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x1b,
0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t noOpData3[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x80,
0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData6[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x84, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
break;
case 1:
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"));
break;
case 3:
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"), false, true);
break;
case 4:
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
break;
case 5:
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"), false, true);
if (requestInclination != -100) {
if (requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -100;
}
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
requestSpeed = -1;
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
requestStop = -1;
}
break;
}
counterPoll++;
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (proform_treadmill_1500_pro) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x14, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x10, 0x04, 0x10, 0x02, 0x00,
0x0a, 0x1b, 0x94, 0x30, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t noOpData3[] = {0xff, 0x02, 0x18, 0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x85, 0x00, 0x10, 0x8a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
break;
case 1:
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
if (requestInclination != -100) {
if (requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -100;
}
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
requestSpeed = -1;
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
requestStop = -1;
}
break;
case 3:
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
break;
case 5:
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
break;
}
counterPoll++;
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (nordictrack_tseries5_treadmill) {
// Frame 1
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
@@ -2236,6 +2360,68 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (proform_trainer_8_0) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x84, 0x74, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t noOpData6[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
break;
case 1:
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
if (requestInclination != -100) {
if (requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -100;
}
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
requestSpeed = -1;
}
break;
case 3:
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
break;
case 5:
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
requestStop = -1;
}
break;
}
counterPoll++;
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (proform_treadmill_c700) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
@@ -2400,6 +2586,88 @@ void proformtreadmill::update() {
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (proform_505_cst_80_44) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t noOpData6[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
switch (counterPoll) {
case 0:
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
break;
case 1:
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("noOp"));
break;
case 2:
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("noOp"), false, true);
break;
case 3:
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("noOp"));
break;
case 4:
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("noOp"));
break;
case 5:
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("noOp"));
if (requestInclination != -100) {
if (requestInclination < 0)
requestInclination = 0;
if (requestInclination != currentInclination().value() && requestInclination >= 0 &&
requestInclination <= 15) {
emit debug(QStringLiteral("writing incline ") + QString::number(requestInclination));
forceIncline(requestInclination);
}
requestInclination = -100;
}
if (requestSpeed != -1) {
if (requestSpeed != currentSpeed().value() && requestSpeed >= 0 && requestSpeed <= 22) {
emit debug(QStringLiteral("writing speed ") + QString::number(requestSpeed));
forceSpeed(requestSpeed);
}
requestSpeed = -1;
}
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
uint8_t start1[] = {0xfe, 0x02, 0x20, 0x03};
uint8_t start2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x1c, 0x04, 0x1c, 0x02, 0x09,
0x00, 0x00, 0x40, 0x02, 0x18, 0x40, 0x00, 0x00, 0x80, 0x30};
uint8_t start3[] = {0xff, 0x0e, 0x2a, 0x00, 0x00, 0xef, 0x1a, 0x58, 0x02, 0x00,
0xb4, 0x00, 0x58, 0x02, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00};
uint8_t start4[] = {0xfe, 0x02, 0x11, 0x02};
uint8_t start5[] = {0xff, 0x11, 0x02, 0x04, 0x02, 0x0d, 0x04, 0x0d, 0x02, 0x02,
0x03, 0x10, 0xa0, 0x00, 0x00, 0x00, 0x0a, 0x00, 0xd2, 0x00};
writeCharacteristic(start1, sizeof(start1), QStringLiteral("start1"));
writeCharacteristic(start2, sizeof(start2), QStringLiteral("start2"));
writeCharacteristic(start3, sizeof(start3), QStringLiteral("start3"), false, true);
writeCharacteristic(start4, sizeof(start4), QStringLiteral("start4"));
writeCharacteristic(start5, sizeof(start5), QStringLiteral("start5"), false, true);
requestStart = -1;
emit tapeStarted();
}
if (requestStop != -1 || requestPause != -1) {
forceSpeed(0);
emit debug(QStringLiteral("stopping..."));
requestStop = -1;
requestPause = -1;
}
break;
}
counterPoll++;
if (counterPoll > 5) {
counterPoll = 0;
}
} else if (norditrack_s25_treadmill) {
uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x0a, 0x41, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
@@ -2565,8 +2833,8 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
newValue.at(3) != 0x04 ||
((nordictrack10 || nordictrackt70 || proform_treadmill_1800i || proform_treadmill_z1300i || proform_treadmill_705_cst || proform_treadmill_705_cst_V78_239 ||
proform_treadmill_8_0 || proform_treadmill_9_0 || nordictrack_incline_trainer_x7i || proform_treadmill_sport_8_5 || proform_treadmill_505_cst ||
proform_proshox2 || proform_595i_proshox2 || proform_performance_400i) &&
proform_treadmill_8_0 || proform_treadmill_9_0 || nordictrack_incline_trainer_x7i || proform_treadmill_sport_8_5 || proform_treadmill_505_cst || proform_505_cst_80_44 ||
proform_proshox2 || proform_595i_proshox2 || proform_performance_400i || proform_treadmill_705_cst_V80_44) &&
(newValue.at(4) != 0x02 || (newValue.at(5) != 0x31 && newValue.at(5) != 0x34))) ||
((norditrack_s25i_treadmill) && (newValue.at(4) != 0x02 || (newValue.at(5) != 0x2f))) ||
@@ -2574,7 +2842,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
((nordictrack_t65s_treadmill || proform_pro_1000_treadmill || nordictrack_t65s_83_treadmill || nordictrack_s30_treadmill ||
nordictrack_s20_treadmill || proform_treadmill_se || proform_cadence_lt || proform_8_5_treadmill || nordictrack_treadmill_exp_5i || proform_carbon_tl ||
nordictrack_s20i_treadmill || proform_treadmill_8_7 || proform_carbon_tl_PFTL59720 || proform_treadmill_575i || nordictrack_tseries5_treadmill ||
proform_carbon_tl_PFTL59722c) &&
proform_carbon_tl_PFTL59722c || proform_treadmill_1500_pro) &&
(newValue.at(4) != 0x02 || newValue.at(5) != 0x2e)) ||
(((uint8_t)newValue.at(12)) == 0xFF && ((uint8_t)newValue.at(13)) == 0xFF &&
@@ -2591,7 +2859,7 @@ void proformtreadmill::characteristicChanged(const QLowEnergyCharacteristic &cha
m_watts = (((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t)newValue.at(14)));
// for the proform_treadmill_se this field is the distance in meters ;)
if (m_watts > 3000 && !proform_treadmill_se) {
if (m_watts > 3000 && !proform_treadmill_se && !nordictrack_s20i_treadmill && !nordictrack_tseries5_treadmill) {
m_watts = 0;
} else {
if (!proform_cadence_lt) {
@@ -2701,6 +2969,10 @@ void proformtreadmill::btinit() {
proform_treadmill_c960i = settings.value(QZSettings::proform_treadmill_c960i, QZSettings::default_proform_treadmill_c960i).toBool();
nordictrack_tseries5_treadmill = settings.value(QZSettings::nordictrack_tseries5_treadmill, QZSettings::default_nordictrack_tseries5_treadmill).toBool();
proform_carbon_tl_PFTL59722c = settings.value(QZSettings::proform_carbon_tl_PFTL59722c, QZSettings::default_proform_carbon_tl_PFTL59722c).toBool();
proform_treadmill_1500_pro = settings.value(QZSettings::proform_treadmill_1500_pro, QZSettings::default_proform_treadmill_1500_pro).toBool();
proform_505_cst_80_44 = settings.value(QZSettings::proform_505_cst_80_44, QZSettings::default_proform_505_cst_80_44).toBool();
proform_trainer_8_0 = settings.value(QZSettings::proform_trainer_8_0, QZSettings::default_proform_trainer_8_0).toBool();
proform_treadmill_705_cst_V80_44 = settings.value(QZSettings::proform_treadmill_705_cst_V80_44, QZSettings::default_proform_treadmill_705_cst_V80_44).toBool();
// bool proform_treadmill_995i = settings.value(QZSettings::proform_treadmill_995i,
// QZSettings::default_proform_treadmill_995i).toBool();
@@ -2859,6 +3131,109 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (proform_505_cst_80_44) {
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[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x04,
0x00, 0x36, 0xa4, 0x08, 0x7a, 0xea, 0x68, 0xdc, 0x46, 0xce};
uint8_t initData15[] = {0x01, 0x12, 0x4c, 0xb0, 0x32, 0xb2, 0x50, 0xd4, 0x5e, 0xc6,
0x74, 0xf8, 0x6a, 0x1a, 0xb8, 0x2c, 0xd6, 0x7e, 0x1c, 0x80};
uint8_t initData16[] = {0xff, 0x08, 0x22, 0xc2, 0xa0, 0x80, 0x02, 0x00, 0x00, 0x50,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData7[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData8[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x80, 0x0a, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData9[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x80, 0x70, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData10[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t noOpData12[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData8, sizeof(noOpData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData9, sizeof(noOpData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData11, sizeof(noOpData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData12, sizeof(noOpData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (proform_carbon_tl) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -2931,6 +3306,98 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (proform_treadmill_1500_pro) {
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[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07,
0x01, 0x57, 0x70, 0x93, 0xbc, 0xe7, 0x00, 0x3b, 0x54, 0x87};
uint8_t initData15[] = {0x01, 0x12, 0xb0, 0xe3, 0x2c, 0x57, 0x80, 0xdb, 0x14, 0x57,
0x90, 0xd3, 0x1c, 0x47, 0x80, 0xfb, 0x34, 0x67, 0xd0, 0x03};
uint8_t initData16[] = {0xff, 0x08, 0x6c, 0xd7, 0x00, 0x90, 0x02, 0x00, 0x00, 0x37,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData19[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData20[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t initData21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData22[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData23[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t initData24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
uint8_t initData25[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (nordictrack_tseries5_treadmill) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -4345,6 +4812,80 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (proform_treadmill_705_cst_V80_44) {
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, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 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, 0x04, 0x28, 0x90, 0x04, 0x00, 0x41, 0x58, 0x7d, 0x90, 0xb1, 0xd0, 0xf5, 0x28, 0x41};
uint8_t initData11[] = {0x01, 0x12, 0x78, 0xad, 0xc0, 0xf1, 0x20, 0x75, 0x88, 0xc1, 0x18, 0x5d, 0x90, 0xd1, 0x10, 0x55, 0x88, 0xc1, 0x38, 0x6d};
uint8_t initData12[] = {0xff, 0x08, 0xa0, 0x11, 0x40, 0x80, 0x02, 0x00, 0x00, 0x95, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData13[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
uint8_t initData15[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData16[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t initData17[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData18[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData19[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData20[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData21[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData22[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t initData23[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t initData24[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (nordictrack_treadmill_exp_5i) {
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
@@ -4500,6 +5041,113 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (proform_trainer_8_0) {
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[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02};
uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02};
uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04};
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x04,
0x00, 0x28, 0xec, 0xa2, 0x6e, 0x2c, 0xf8, 0xbe, 0x8a, 0x40};
uint8_t initData15[] = {0x01, 0x12, 0x14, 0xea, 0xb6, 0x84, 0x70, 0x26, 0x02, 0xf8,
0xdc, 0xb2, 0x9e, 0x7c, 0x48, 0x2e, 0x3a, 0x10, 0xe4, 0xfa};
uint8_t initData16[] = {0xff, 0x08, 0xc6, 0xd4, 0xe0, 0x80, 0x02, 0x00, 0x00, 0x9a,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
uint8_t noOpData1[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData2[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData3[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData4[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData5[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData6[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData2, sizeof(noOpData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData3, sizeof(noOpData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData4, sizeof(noOpData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData5, sizeof(noOpData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData6, sizeof(noOpData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
uint8_t noOpData7[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
uint8_t noOpData8[] = {0xff, 0x05, 0x00, 0x00, 0x00, 0x10, 0x62, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData9[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00,
0x0d, 0x1b, 0x94, 0x31, 0x00, 0x00, 0x40, 0x50, 0x00, 0x80};
uint8_t noOpData11[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x2f, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData12[] = {0xfe, 0x02, 0x17, 0x03};
writeCharacteristic(noOpData7, sizeof(noOpData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData8, sizeof(noOpData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData9, sizeof(noOpData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData11, sizeof(noOpData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData12, sizeof(noOpData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (norditrack_s25_treadmill) {
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};
@@ -5445,7 +6093,7 @@ void proformtreadmill::btinit() {
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, 0x04, 0x28, 0x90, 0x04, 0x00, 0xe1, 0xd8, 0xdd, 0xd0, 0xd1, 0xd0, 0xd5, 0xe8, 0xe1};
uint8_t initData10[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0xe1, 0xd8, 0xdd, 0xd0, 0xd1, 0xd0, 0xd5, 0xe8, 0xe1};
uint8_t initData11[] = {0x01, 0x12, 0xf8, 0x0d, 0x00, 0x11, 0x20, 0x55, 0x48, 0x61, 0x98, 0xbd, 0xd0, 0xf1, 0x10, 0x35, 0x48, 0x61, 0xb8, 0xcd};
uint8_t initData12[] = {0xff, 0x08, 0xe0, 0x31, 0x40, 0x98, 0x02, 0x00, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData1[] = {0xfe, 0x02, 0x19, 0x03};
@@ -5458,11 +6106,11 @@ void proformtreadmill::btinit() {
uint8_t noOpData8[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
uint8_t noOpData9[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData10[] = {0xfe, 0x02, 0x19, 0x03};
uint8_t noOpData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x92, 0x18, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData12[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x10, 0xd4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData11[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x13, 0x96, 0x31, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80};
uint8_t noOpData12[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0xe9, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData13[] = {0xfe, 0x02, 0x17, 0x03};
uint8_t noOpData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x00, 0x0d, 0x01, 0x86, 0x01, 0x00, 0x00, 0x40, 0x10, 0x00, 0x80};
uint8_t noOpData15[] = {0xff, 0x05, 0x18, 0x00, 0x00, 0x01, 0x97, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x80, 0x08, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t noOpData15[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x80, 0x00, 0x10, 0x82, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
@@ -5514,118 +6162,110 @@ void proformtreadmill::btinit() {
QThread::msleep(sleepms);
writeCharacteristic(noOpData9, sizeof(noOpData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
writeCharacteristic(noOpData13, sizeof(noOpData13), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData11, sizeof(noOpData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData12, sizeof(noOpData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData13, sizeof(noOpData13), QStringLiteral("init"), false, false);
writeCharacteristic(noOpData10, sizeof(noOpData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData14, sizeof(noOpData14), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(noOpData15, sizeof(noOpData15), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (nordictrack_s20i_treadmill) {
unsigned char array1[] = {0xfe, 0x02, 0x08, 0x02};
unsigned char array2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array3[] = {0xfe, 0x02, 0x08, 0x02};
unsigned char array4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array5[] = {0xfe, 0x02, 0x08, 0x02};
unsigned char array6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array7[] = {0xfe, 0x02, 0x0a, 0x02};
unsigned char array8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00, 0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array9[] = {0xfe, 0x02, 0x0a, 0x02};
unsigned char array10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array11[] = {0xfe, 0x02, 0x08, 0x02};
unsigned char array12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array13[] = {0xfe, 0x02, 0x2c, 0x04};
unsigned char array14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07, 0x01, 0x85, 0x08, 0x91, 0x18, 0xad, 0x30, 0xc1, 0x50, 0xe5};
unsigned char array15[] = {0x01, 0x12, 0x88, 0x11, 0xb8, 0x5d, 0xe0, 0x81, 0x20, 0xc5, 0x68, 0x31, 0xd8, 0x6d, 0x30, 0xc1, 0x90, 0x25, 0xe8, 0xb1};
unsigned char array16[] = {0xff, 0x08, 0x78, 0x3d, 0xc0, 0x98, 0x02, 0x00, 0x00, 0xed, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array17[] = {0xfe, 0x02, 0x19, 0x03};
unsigned char array18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array19[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array20[] = {0xfe, 0x02, 0x17, 0x03};
unsigned char array21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array22[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
unsigned char array23[] = {0xfe, 0x02, 0x19, 0x03};
unsigned char array24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00, 0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0};
unsigned char array25[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
uint8_t initData1[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1268
uint8_t initData2[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x81, 0x87,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1286
uint8_t initData3[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1319
uint8_t initData4[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x80, 0x88,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1329
uint8_t initData5[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1362
uint8_t initData6[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x04, 0x04, 0x88, 0x90,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1370
uint8_t initData7[] = {0xfe, 0x02, 0x0a, 0x02}; // from pkt1431
uint8_t initData8[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x82, 0x00,
0x00, 0x8a, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1436
uint8_t initData9[] = {0xfe, 0x02, 0x0a, 0x02}; // from pkt1445
uint8_t initData10[] = {0xff, 0x0a, 0x02, 0x04, 0x02, 0x06, 0x02, 0x06, 0x84, 0x00,
0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1447
uint8_t initData11[] = {0xfe, 0x02, 0x08, 0x02}; // from pkt1454
uint8_t initData12[] = {0xff, 0x08, 0x02, 0x04, 0x02, 0x04, 0x02, 0x04, 0x95, 0x9b,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1456
uint8_t initData13[] = {0xfe, 0x02, 0x2c, 0x04}; // from pkt1466
uint8_t initData14[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x28, 0x04, 0x28, 0x90, 0x07,
0x01, 0x85, 0x08, 0x91, 0x18, 0xad, 0x30, 0xc1, 0x50, 0xe5}; // from pkt1468
uint8_t initData15[] = {0x01, 0x12, 0x88, 0x11, 0xb8, 0x5d, 0xe0, 0x81, 0x20, 0xc5,
0x68, 0x31, 0xd8, 0x6d, 0x30, 0xc1, 0x90, 0x25, 0xe8, 0xb1};
writeCharacteristic(array1, sizeof(array1), QStringLiteral("init"), false, false);
uint8_t initData16[] = {0xff, 0x08, 0x78, 0x3d, 0xc0, 0x98, 0x02, 0x00, 0x00, 0xed,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1475
uint8_t initData17[] = {0xfe, 0x02, 0x17, 0x03}; // from pkt1482
uint8_t initData18[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x13, 0x04, 0x13, 0x02, 0x0c,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1484
uint8_t initData19[] = {0xff, 0x05, 0x00, 0x80, 0x00, 0x00, 0xa5, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1486
uint8_t initData20[] = {0xfe, 0x02, 0x19, 0x03}; // from pkt1493
uint8_t initData21[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x0e,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1495
uint8_t initData22[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x01, 0x00, 0x3a, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1498
uint8_t initData23[] = {0xfe, 0x02, 0x19, 0x03}; // from pkt1506
uint8_t initData24[] = {0x00, 0x12, 0x02, 0x04, 0x02, 0x15, 0x04, 0x15, 0x02, 0x00,
0x0f, 0x00, 0x10, 0x00, 0xd8, 0x1c, 0x48, 0x00, 0x00, 0xe0}; // from pkt1509
uint8_t initData25[] = {0xff, 0x07, 0x00, 0x00, 0x00, 0x10, 0x00, 0x08, 0x6e, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; // from pkt1511
writeCharacteristic(initData1, sizeof(initData1), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array2, sizeof(array2), QStringLiteral("init"), false, false);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array3, sizeof(array3), QStringLiteral("init"), false, false);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array4, sizeof(array4), QStringLiteral("init"), false, false);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array5, sizeof(array5), QStringLiteral("init"), false, false);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array6, sizeof(array6), QStringLiteral("init"), false, false);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array7, sizeof(array7), QStringLiteral("init"), false, false);
writeCharacteristic(initData7, sizeof(initData7), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array8, sizeof(array8), QStringLiteral("init"), false, false);
writeCharacteristic(initData8, sizeof(initData8), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array9, sizeof(array9), QStringLiteral("init"), false, false);
writeCharacteristic(initData9, sizeof(initData9), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array10, sizeof(array10), QStringLiteral("init"), false, false);
writeCharacteristic(initData10, sizeof(initData10), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array11, sizeof(array11), QStringLiteral("init"), false, false);
writeCharacteristic(initData11, sizeof(initData11), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array12, sizeof(array12), QStringLiteral("init"), false, false);
writeCharacteristic(initData12, sizeof(initData12), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array13, sizeof(array13), QStringLiteral("init"), false, false);
writeCharacteristic(initData13, sizeof(initData13), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array14, sizeof(array14), QStringLiteral("init"), false, false);
writeCharacteristic(initData14, sizeof(initData14), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array15, sizeof(array15), QStringLiteral("init"), false, false);
writeCharacteristic(initData15, sizeof(initData15), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array16, sizeof(array16), QStringLiteral("init"), false, false);
writeCharacteristic(initData16, sizeof(initData16), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array17, sizeof(array17), QStringLiteral("init"), false, false);
writeCharacteristic(initData17, sizeof(initData17), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array18, sizeof(array18), QStringLiteral("init"), false, false);
writeCharacteristic(initData18, sizeof(initData18), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array19, sizeof(array19), QStringLiteral("init"), false, false);
writeCharacteristic(initData19, sizeof(initData19), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array20, sizeof(array20), QStringLiteral("init"), false, false);
writeCharacteristic(initData20, sizeof(initData20), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array21, sizeof(array21), QStringLiteral("init"), false, false);
writeCharacteristic(initData21, sizeof(initData21), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array22, sizeof(array22), QStringLiteral("init"), false, false);
writeCharacteristic(initData22, sizeof(initData22), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array23, sizeof(array23), QStringLiteral("init"), false, false);
writeCharacteristic(initData23, sizeof(initData23), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array24, sizeof(array24), QStringLiteral("init"), false, false);
writeCharacteristic(initData24, sizeof(initData24), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
writeCharacteristic(array25, sizeof(array25), QStringLiteral("init"), false, false);
writeCharacteristic(initData25, sizeof(initData25), QStringLiteral("init"), false, false);
QThread::msleep(sleepms);
} else if (nordictrack_s30_treadmill) {

View File

@@ -105,6 +105,10 @@ class proformtreadmill : public treadmill {
bool proform_treadmill_c960i = false;
bool nordictrack_tseries5_treadmill = false;
bool proform_carbon_tl_PFTL59722c = false;
bool proform_treadmill_1500_pro = false;
bool proform_505_cst_80_44 = false;
bool proform_trainer_8_0 = false;
bool proform_treadmill_705_cst_V80_44 = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;

View File

@@ -494,6 +494,10 @@ void proformwifibike::characteristicChanged(const QString &newValue) {
m_watt = watt;
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
}
} else {
qDebug() << "watt to 0 due to cadence = 0";
m_watt = 0;
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
}
if (!values[QStringLiteral("Actual Incline")].isUndefined()) {

View File

@@ -459,7 +459,7 @@ void sportsplusbike::deviceDiscovered(const QBluetoothDeviceInfo &device) {
{
bluetoothDevice = device;
if ((bluetoothDevice.name().toUpper().contains(QStringLiteral("CARE")) &&
bluetoothDevice.name().length() == 11)) // CARE9040177 - Carefitness CV-351)
bluetoothDevice.name().length() >= 11)) // CARE9040177 - Carefitness CV-351)
{
carefitness_bike = true;
}

View File

@@ -0,0 +1,406 @@
#include "sportsplusrower.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <chrono>
using namespace std::chrono_literals;
sportsplusrower::sportsplusrower(bool noWriteResistance, bool noHeartService) {
m_watt.setType(metric::METRIC_WATT);
Speed.setType(metric::METRIC_SPEED);
refresh = new QTimer(this);
this->noWriteResistance = noWriteResistance;
this->noHeartService = noHeartService;
initDone = false;
connect(refresh, &QTimer::timeout, this, &sportsplusrower::update);
refresh->start(200ms);
}
void sportsplusrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
if (wait_for_response) {
connect(this, &sportsplusrower::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);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + " // " + info);
}
loop.exec();
if (timeout.isActive() == false) {
emit debug(QStringLiteral(" exit for timeout"));
}
}
void sportsplusrower::forceResistance(resistance_t requestResistance) {
Q_UNUSED(requestResistance)
/*
uint8_t resistance[] = { 0xf0, 0xa6, 0x01, 0x01, 0x00, 0x00 };
resistance[4] = requestResistance + 1;
for(uint8_t i=0; i<sizeof(resistance)-1; i++)
{
resistance[5] += resistance[i]; // the last byte is a sort of a checksum
}
writeCharacteristic((uint8_t*)resistance, sizeof(resistance), "resistance " + QString::number(requestResistance),
false, true);
*/
}
void sportsplusrower::update() {
// qDebug() << bike.isValid() << m_control->state() << gattCommunicationChannelService <<
// gattWriteCharacteristic.isValid() << gattNotifyCharacteristic.isValid() << initDone;
if (!m_control) {
return;
}
if (m_control->state() == QLowEnergyController::UnconnectedState) {
emit disconnected();
return;
}
if (initRequest) {
initRequest = false;
btinit(false);
} else if (bluetoothDevice.isValid() && m_control->state() == QLowEnergyController::DiscoveredState &&
gattCommunicationChannelService && gattWriteCharacteristic.isValid() &&
gattNotify1Characteristic.isValid() && initDone) {
update_metrics(false, 0);
// updating the bike console every second
if (sec1update++ == (1000 / refresh->interval())) {
sec1update = 0;
// updateDisplay(elapsed);
}
QSettings settings;
uint8_t noOpData[] = {0x20, 0x01, 0x09, 0x00, 0x2a};
if (requestResistance < 0) {
requestResistance = 0;
}
if (requestResistance > max_resistance) {
requestResistance = max_resistance;
}
noOpData[2] = requestResistance;
noOpData[4] = (0x21 + requestResistance);
writeCharacteristic((uint8_t *)noOpData, sizeof(noOpData), QStringLiteral("noOp"), false, true);
}
}
void sportsplusrower::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
void sportsplusrower::characteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
QDateTime now = QDateTime::currentDateTime();
// 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();
emit packetReceived();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
lastPacket = newValue;
if (newValue.length() != 12) {
return;
}
double cadence = 0;
double kcal = 0;
bool cadence_eval = false;
m_watt = ((newValue.at(10) >> 4) * 10) + (newValue.at(10) & 0x0F);
emit debug(QStringLiteral("Current watt: ") + QString::number(m_watt.value()));
if(newValue.at(1) == 0x10) {
Cadence = ((newValue.at(3) >> 4) * 10) + (newValue.at(3) & 0x0F);
Speed = 0.37497622 * ((double)Cadence.value());
emit debug(QStringLiteral("Current speed: ") + QString::number(Speed.value()));
}
if (!firstCharChanged) {
Distance +=
((Speed.value() / 3600.0) / (1000.0 / (lastTimeCharChanged.msecsTo(now))));
}
lastTimeCharChanged = now;
kcal +=
((((0.048 * ((double)watts()) + 1.19) *
settings.value(QZSettings::weight, QZSettings::default_weight).toFloat() * 3.5) /
200.0) /
(60000.0 / ((double)lastRefreshCharacteristicChanged.msecsTo(
now)))); //(( (0.048* Output in watts +1.19) * body weight in kg
//* 3.5) / 200 ) / 60
lastRefreshCharacteristicChanged = now;
#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"))) {
Heart = ((uint8_t)newValue.at(8));
}
}
FanSpeed = 0;
emit debug(QStringLiteral("Current cadence: ") + QString::number(Cadence.value()));
// emit debug(QStringLiteral("Current resistance: ") + QString::number(resistance));
emit debug(QStringLiteral("Current heart: ") + QString::number(Heart.value()));
emit debug(QStringLiteral("Current KCal: ") + QString::number(kcal));
emit debug(QStringLiteral("Current Distance Calculated: ") + QString::number(Distance.value()));
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
Resistance = requestResistance;
KCal = kcal;
if (settings.value(QZSettings::cadence_sensor_name, QZSettings::default_cadence_sensor_name)
.toString()
.startsWith(QStringLiteral("Disabled"))) {
if(cadence_eval)
Cadence = cadence;
}
firstCharChanged = false;
}
uint16_t sportsplusrower::GetElapsedFromPacket(const QByteArray &packet) {
uint16_t convertedDataSec = (packet.at(4));
uint16_t convertedDataMin = (packet.at(3));
uint16_t convertedData = convertedDataMin * 60.f + convertedDataSec;
return convertedData;
}
double sportsplusrower::GetKcalFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(6) << 8) | ((uint8_t)packet.at(7));
return (double)(convertedData);
}
double sportsplusrower::GetWattFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(2) << 8) | ((uint8_t)packet.at(3));
double data = ((double)(convertedData));
return data;
}
void sportsplusrower::btinit(bool startTape) {
Q_UNUSED(startTape);
const uint8_t initData1[] = {0x40, 0x00, 0x9a, 0x56, 0x30};
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
writeCharacteristic((uint8_t *)initData1, sizeof(initData1), QStringLiteral("init"), false, true);
initDone = true;
}
void sportsplusrower::stateChanged(QLowEnergyService::ServiceState state) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if (state == QLowEnergyService::ServiceDiscovered) {
auto characteristics_list = gattCommunicationChannelService->characteristics();
for (const QLowEnergyCharacteristic &c : qAsConst(characteristics_list)) {
emit debug(QStringLiteral("characteristic ") + c.uuid().toString());
}
// QString uuidWrite = "0000fff2-0000-1000-8000-00805f9b34fb";
// QString uuidNotify1 = "0000fff1-0000-1000-8000-00805f9b34fb";
// QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("0000fff2-0000-1000-8000-00805f9b34fb"));
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("0000fff1-0000-1000-8000-00805f9b34fb"));
QBluetoothUuid _gattNotify2CharacteristicId(QStringLiteral("0000fff2-0000-1000-8000-00805f9b34fb"));
QBluetoothUuid _gattNotify3CharacteristicId(QStringLiteral("0000fff3-0000-1000-8000-00805f9b34fb"));
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattNotify2CharacteristicId);
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,
&sportsplusrower::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&sportsplusrower::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &sportsplusrower::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&sportsplusrower::descriptorWritten);
// ******************************************* virtual bike init *************************************
if (!firstVirtualBike && !this->hasVirtualDevice()) {
QSettings settings;
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 bike interface..."));
auto virtualBike = new virtualbike(this, noWriteResistance, noHeartService);
// connect(virtualBike,&virtualbike::debug ,this,&sportsplusrower::debug);
connect(virtualBike, &virtualbike::changeInclination, this, &sportsplusrower::changeInclination);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::PRIMARY);
}
}
firstVirtualBike = 1;
// ********************************************************************************************************
qDebug() << "gattNotify2Characteristic" << gattNotify2Characteristic.isValid()
<< gattNotify2Characteristic.properties();
qDebug() << "gattNotify3Characteristic" << gattNotify3Characteristic.isValid()
<< gattNotify3Characteristic.properties();
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
if (gattNotify2Characteristic.isValid() &&
(gattNotify2Characteristic.properties() & QLowEnergyCharacteristic::Notify) ==
QLowEnergyCharacteristic::Notify) {
gattCommunicationChannelService->writeDescriptor(
gattNotify2Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
if (gattNotify3Characteristic.isValid() &&
(gattNotify3Characteristic.properties() & QLowEnergyCharacteristic::Notify) ==
QLowEnergyCharacteristic::Notify)
gattCommunicationChannelService->writeDescriptor(
gattNotify3Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void sportsplusrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
}
void sportsplusrower::characteristicWritten(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void sportsplusrower::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
// QString uuid = "0000fff0-0000-1000-8000-00805f9b34fb";
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
qDebug() << QStringLiteral("invalid service") << _gattCommunicationChannelServiceId.toString();
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &sportsplusrower::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void sportsplusrower::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("sportsplusrower::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void sportsplusrower::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("sportsplusrower::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void sportsplusrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
{
bluetoothDevice = device;
requestResistance = 1;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &sportsplusrower::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &sportsplusrower::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &sportsplusrower::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &sportsplusrower::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();
});
// Connect
m_control->connectToDevice();
return;
}
}
uint16_t sportsplusrower::watts() {
if (currentCadence().value() == 0) {
return 0;
}
return m_watt.value();
}
bool sportsplusrower::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void sportsplusrower::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}

View File

@@ -0,0 +1,99 @@
#ifndef SPORTSPLUSROWER_H
#define SPORTSPLUSROWER_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 <QObject>
#include <QTime>
#include "devices/rower.h"
#include "virtualdevices/virtualbike.h"
class sportsplusrower : public rower {
Q_OBJECT
public:
sportsplusrower(bool noWriteResistance, bool noHeartService);
bool connected() override;
private:
double GetKcalFromPacket(const QByteArray &packet);
double GetDistanceFromPacket(QByteArray packet);
uint16_t GetElapsedFromPacket(const QByteArray &packet);
void forceResistance(resistance_t requestResistance);
void updateDisplay(uint16_t elapsed);
void btinit(bool startTape);
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response);
uint16_t wattsFromResistance(double resistance);
void startDiscover();
uint16_t watts() override;
double GetWattFromPacket(const QByteArray &packet);
QTimer *refresh;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
bool noWriteResistance = false;
bool noHeartService = false;
uint8_t firstVirtualBike = 0;
bool firstCharChanged = true;
QDateTime lastTimeCharChanged;
uint8_t sec1update = 0;
QByteArray lastPacket;
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
QLowEnergyCharacteristic gattNotify3Characteristic;
bool initDone = false;
bool initRequest = false;
bool readyToStart = false;
const resistance_t max_resistance = 24;
signals:
void disconnected();
void debug(QString string);
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 serviceDiscovered(const QBluetoothUuid &gatt);
void serviceScanDone(void);
void update();
void error(QLowEnergyController::Error err);
void errorService(QLowEnergyService::ServiceError);
};
#endif // SPORTSPLUSROWER_H

View File

@@ -78,7 +78,10 @@ void strydrunpowersensor::update() {
// gattWriteCharacteristic.isValid() &&
// gattNotify1Characteristic.isValid() &&
/*initDone*/) {
update_metrics(false, watts());
QSettings settings;
bool power_as_treadmill =
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
update_metrics(false, watts(), !power_as_treadmill);
if (requestInclination != -100) {
Inclination = treadmillInclinationOverrideReverse(requestInclination);
@@ -238,10 +241,17 @@ void strydrunpowersensor::characteristicChanged(const QLowEnergyCharacteristic &
((double)(((int16_t)((int8_t)newValue.at(index + 1)) << 8) | (int16_t)((uint8_t)newValue.at(index)))) /
10.0;
// steps of 0.5 only to send to the Inclination override function
inc = qRound(inc * 2.0) / 2.0;
Inclination = treadmillInclinationOverride(inc);
if(!areInclinationSettingsDefault()) {
inc = qRound(inc * 2.0) / 2.0;
Inclination = treadmillInclinationOverride(inc);
} else {
Inclination = inc;
}
index += 4; // the ramo value is useless
emit debug(QStringLiteral("Current Inclination: ") + QString::number(Inclination.value()));
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
if(stryd_inclination_instead_treadmill)
emit inclinationChanged(Inclination.value(), Inclination.value());
}
if (Flags.elevation) {
@@ -520,6 +530,11 @@ void strydrunpowersensor::stateChanged(QLowEnergyService::ServiceState state) {
connect(s, &QLowEnergyService::descriptorWritten, this, &strydrunpowersensor::descriptorWritten);
connect(s, &QLowEnergyService::descriptorRead, this, &strydrunpowersensor::descriptorRead);
if(FORERUNNER && s->serviceUuid() != QBluetoothUuid::HeartRate && s->serviceUuid() != QBluetoothUuid::RunningSpeedAndCadence) {
qDebug() << "skipping garmin services!" << s->serviceUuid();
continue;
}
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
auto characteristics_list = s->characteristics();
@@ -664,6 +679,10 @@ void strydrunpowersensor::deviceDiscovered(const QBluetoothDeviceInfo &device) {
device.address().toString() + ')');
{
bluetoothDevice = device;
if(bluetoothDevice.name().toUpper().startsWith("FORERUNNER")) {
FORERUNNER = true;
qDebug() << "FORERUNNER WORKAROUND!";
}
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &strydrunpowersensor::serviceDiscovered);

View File

@@ -71,6 +71,8 @@ class strydrunpowersensor : public treadmill {
bool powerReceived = false;
bool FORERUNNER = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif

View File

@@ -11,6 +11,7 @@
#include "keepawakehelper.h"
#include <QLowEnergyConnectionParameters>
#endif
#include "homeform.h"
#include <chrono>
@@ -28,6 +29,17 @@ tacxneo2::tacxneo2(bool noWriteResistance, bool noHeartService) {
void tacxneo2::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
if(!gattCustomService) {
qDebug() << "gattCustomService is null!";
QSettings settings;
settings.setValue(QZSettings::ftms_bike, bluetoothDevice.name());
qDebug() << "forcing FTMS bike since it has FTMS";
if(homeform::singleton())
homeform::singleton()->setToastRequested("FTMS bike found, restart the app to apply the change!");
return;
}
QEventLoop loop;
QTimer timeout;
if (wait_for_response) {
@@ -740,6 +752,12 @@ void tacxneo2::stateChanged(QLowEnergyService::ServiceState state) {
qDebug() << s->serviceUuid() << QStringLiteral("connected!");
if(s->serviceUuid() == QBluetoothUuid(QStringLiteral("fe03a000-17d0-470a-8798-4ad3e1c1f35b")) ||
s->serviceUuid() == QBluetoothUuid(QStringLiteral("fe031000-17d0-470a-8798-4ad3e1c1f35b"))) {
qDebug() << "skipping service" << s->serviceUuid();
continue;
}
auto characteristics = s->characteristics();
for (const QLowEnergyCharacteristic &c : characteristics) {
qDebug() << QStringLiteral("char uuid") << c.uuid() << QStringLiteral("handle") << c.handle();

View File

@@ -17,8 +17,14 @@ void treadmill::changeSpeed(double speed) {
speed -= settings.value(QZSettings::speed_offset, QZSettings::default_speed_offset).toDouble();
if(stryd_speed_instead_treadmill && Speed.value() > 0) {
double delta = (Speed.value() - rawSpeed.value());
qDebug() << "stryd_speed_instead_treadmill so override speed by " << delta;
speed -= delta;
double maxAllowedDelta = speed * 0.20; // 20% of the speed request
if (std::abs(delta) <= maxAllowedDelta) {
qDebug() << "stryd_speed_instead_treadmill so override speed by " << delta;
speed -= delta;
} else {
qDebug() << "Delta" << delta << "exceeds 20% threshold of" << maxAllowedDelta << "- not applying correction";
}
}
qDebug() << "changeSpeed" << speed << autoResistanceEnable << m_difficult << m_difficult_offset << m_lastRawSpeedRequested;
RequestedSpeed = (speed * m_difficult) + m_difficult_offset;
@@ -29,6 +35,8 @@ void treadmill::changeInclination(double grade, double inclination) {
QSettings settings;
double treadmill_incline_min = settings.value(QZSettings::treadmill_incline_min, QZSettings::default_treadmill_incline_min).toDouble();
double treadmill_incline_max = settings.value(QZSettings::treadmill_incline_max, QZSettings::default_treadmill_incline_max).toDouble();
double step = settings.value(QZSettings::treadmill_step_incline, QZSettings::default_treadmill_step_incline).toDouble();
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
if(grade < treadmill_incline_min) {
grade = treadmill_incline_min;
@@ -38,13 +46,31 @@ void treadmill::changeInclination(double grade, double inclination) {
qDebug() << "grade override due to treadmill_incline_max" << grade;
}
if(stryd_inclination_instead_treadmill) {
double delta = (Inclination.value() - rawInclination.value());
double maxAllowedDelta = grade * 0.20; // 20% of the inclination request
if (std::abs(delta) <= maxAllowedDelta) {
qDebug() << "stryd_inclination_instead_treadmill so override inclination by " << delta;
grade -= delta;
} else {
qDebug() << "Delta" << delta << "exceeds 20% threshold of" << maxAllowedDelta << "- not applying correction";
}
}
m_lastRawInclinationRequested = grade;
Q_UNUSED(inclination);
qDebug() << "changeInclination" << grade << autoResistanceEnable << m_inclination_difficult
<< m_inclination_difficult_offset;
RequestedInclination = (grade * m_inclination_difficult) + m_inclination_difficult_offset;
// Calculate the raw requested inclination
double rawInclination = (grade * m_inclination_difficult) + m_inclination_difficult_offset;
// Round to nearest step
RequestedInclination = round(rawInclination / step) * step;
if (autoResistanceEnable) {
requestInclination = (grade * m_inclination_difficult) + m_inclination_difficult_offset;
requestInclination = RequestedInclination.value(); // Use the rounded value here as well
}
}
void treadmill::changeSpeedAndInclination(double speed, double inclination) {
@@ -58,7 +84,7 @@ bluetoothdevice::BLUETOOTH_TYPE treadmill::deviceType() { return bluetoothdevice
double treadmill::minStepInclination() { return 0.5; }
double treadmill::minStepSpeed() { return 0.5; }
void treadmill::update_metrics(bool watt_calc, const double watts) {
void treadmill::update_metrics(bool watt_calc, const double watts, const bool from_accessory) {
QDateTime current = QDateTime::currentDateTime();
double deltaTime = (((double)_lastTimeUpdate.msecsTo(current)) / ((double)1000.0));
@@ -67,6 +93,8 @@ void treadmill::update_metrics(bool watt_calc, const double watts) {
settings.value(QZSettings::power_sensor_as_treadmill, QZSettings::default_power_sensor_as_treadmill).toBool();
simulateInclinationWithSpeed();
if(!from_accessory)
followPowerBySpeed();
if (settings.value(QZSettings::power_sensor_name, QZSettings::default_power_sensor_name)
.toString()
@@ -136,6 +164,7 @@ void treadmill::clearStats() {
elapsed.clear(true);
Speed.clear(false);
rawSpeed.clear(false);
rawInclination.clear(false);
KCal.clear(true);
Distance.clear(true);
Distance1s.clear(true);
@@ -160,6 +189,7 @@ void treadmill::setPaused(bool p) {
elapsed.setPaused(p);
Speed.setPaused(p);
rawSpeed.setPaused(p);
rawInclination.setPaused(p);
KCal.setPaused(p);
Distance.setPaused(p);
Distance1s.setPaused(p);
@@ -181,6 +211,7 @@ void treadmill::setLap() {
elapsed.setLap(true);
Speed.setLap(false);
rawSpeed.setLap(false);
rawInclination.setLap(false);
KCal.setLap(true);
Distance.setLap(true);
Distance1s.setLap(true);
@@ -225,7 +256,17 @@ void treadmill::powerSensor(uint16_t power) {
}
m_watt.setValue(power + vwatts, false);
}
void treadmill::speedSensor(double speed) { Speed.setValue(speed); }
void treadmill::speedSensor(double speed) {
Speed.setValue(speed);
qDebug() << "Current speed: " << speed;
}
void treadmill::inclinationSensor(double grade, double inclination) {
Inclination.setValue(inclination);
qDebug() << "Current Inclination: " << inclination;
}
void treadmill::instantaneousStrideLengthSensor(double length) { InstantaneousStrideLengthCM.setValue(length); }
void treadmill::groundContactSensor(double groundContact) { GroundContactMS.setValue(groundContact); }
void treadmill::verticalOscillationSensor(double verticalOscillation) {
@@ -252,6 +293,69 @@ double treadmill::treadmillInclinationOverrideReverse(double Inclination) {
return treadmillInclinationOverride(15);
}
bool treadmill::areInclinationSettingsDefault() {
QSettings settings;
// Check gain and offset settings first
if (settings.value(QZSettings::treadmill_inclination_ovveride_gain).toDouble() !=
QZSettings::default_treadmill_inclination_ovveride_gain) {
return false;
}
if (settings.value(QZSettings::treadmill_inclination_ovveride_offset).toDouble() !=
QZSettings::default_treadmill_inclination_ovveride_offset) {
return false;
}
// Array of settings to check - pairs of setting key and its default value
const struct {
QString setting;
double defaultValue;
} checkPairs[] = {
{QZSettings::treadmill_inclination_override_0, QZSettings::default_treadmill_inclination_override_0},
{QZSettings::treadmill_inclination_override_05, QZSettings::default_treadmill_inclination_override_05},
{QZSettings::treadmill_inclination_override_10, QZSettings::default_treadmill_inclination_override_10},
{QZSettings::treadmill_inclination_override_15, QZSettings::default_treadmill_inclination_override_15},
{QZSettings::treadmill_inclination_override_20, QZSettings::default_treadmill_inclination_override_20},
{QZSettings::treadmill_inclination_override_25, QZSettings::default_treadmill_inclination_override_25},
{QZSettings::treadmill_inclination_override_30, QZSettings::default_treadmill_inclination_override_30},
{QZSettings::treadmill_inclination_override_35, QZSettings::default_treadmill_inclination_override_35},
{QZSettings::treadmill_inclination_override_40, QZSettings::default_treadmill_inclination_override_40},
{QZSettings::treadmill_inclination_override_45, QZSettings::default_treadmill_inclination_override_45},
{QZSettings::treadmill_inclination_override_50, QZSettings::default_treadmill_inclination_override_50},
{QZSettings::treadmill_inclination_override_55, QZSettings::default_treadmill_inclination_override_55},
{QZSettings::treadmill_inclination_override_60, QZSettings::default_treadmill_inclination_override_60},
{QZSettings::treadmill_inclination_override_65, QZSettings::default_treadmill_inclination_override_65},
{QZSettings::treadmill_inclination_override_70, QZSettings::default_treadmill_inclination_override_70},
{QZSettings::treadmill_inclination_override_75, QZSettings::default_treadmill_inclination_override_75},
{QZSettings::treadmill_inclination_override_80, QZSettings::default_treadmill_inclination_override_80},
{QZSettings::treadmill_inclination_override_85, QZSettings::default_treadmill_inclination_override_85},
{QZSettings::treadmill_inclination_override_90, QZSettings::default_treadmill_inclination_override_90},
{QZSettings::treadmill_inclination_override_95, QZSettings::default_treadmill_inclination_override_95},
{QZSettings::treadmill_inclination_override_100, QZSettings::default_treadmill_inclination_override_100},
{QZSettings::treadmill_inclination_override_105, QZSettings::default_treadmill_inclination_override_105},
{QZSettings::treadmill_inclination_override_110, QZSettings::default_treadmill_inclination_override_110},
{QZSettings::treadmill_inclination_override_115, QZSettings::default_treadmill_inclination_override_115},
{QZSettings::treadmill_inclination_override_120, QZSettings::default_treadmill_inclination_override_120},
{QZSettings::treadmill_inclination_override_125, QZSettings::default_treadmill_inclination_override_125},
{QZSettings::treadmill_inclination_override_130, QZSettings::default_treadmill_inclination_override_130},
{QZSettings::treadmill_inclination_override_135, QZSettings::default_treadmill_inclination_override_135},
{QZSettings::treadmill_inclination_override_140, QZSettings::default_treadmill_inclination_override_140},
{QZSettings::treadmill_inclination_override_145, QZSettings::default_treadmill_inclination_override_145},
{QZSettings::treadmill_inclination_override_150, QZSettings::default_treadmill_inclination_override_150}
};
// Check each setting against its default value
for (const auto& pair : checkPairs) {
if (settings.value(pair.setting).toDouble() != pair.defaultValue) {
return false;
}
}
// If we got here, all settings match their defaults
return true;
}
double treadmill::treadmillInclinationOverride(double Inclination) {
QSettings settings;
@@ -469,6 +573,49 @@ bool treadmill::simulateInclinationWithSpeed() {
return false;
}
bool treadmill::followPowerBySpeed() {
QSettings settings;
bool r = false;
bool treadmill_follow_wattage =
settings
.value(QZSettings::treadmill_follow_wattage,
QZSettings::default_treadmill_follow_wattage)
.toBool();
double w = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
static double lastInclination = 0;
if (treadmill_follow_wattage) {
if (currentInclination().value() != lastInclination && wattsMetric().value() != 0) {
double newspeed = 0;
double bestSpeed = 0.1;
// don't read the wattage directly from the m_watt because if you were using a power sensor, the power calcuated in the for will not match it
double previousWatt = wattsCalc(w, currentSpeed().value(), lastInclination);
double bestDifference = fabs(wattsCalc(w, bestSpeed, currentInclination().value()) - previousWatt);
for (int speed = 1; speed <= 300; speed++) {
double s = ((double)speed) / 10.0;
double thisDifference = fabs(wattsCalc(w, s, currentInclination().value()) - previousWatt);
if (thisDifference < bestDifference) {
bestDifference = thisDifference;
bestSpeed = s;
}
}
// Now bestSpeed is the speed closest to the desired wattage
newspeed = bestSpeed;
qDebug() << QStringLiteral("changing speed to") << newspeed << "due to inclination changed" << currentInclination().value() << lastInclination;
changeSpeedAndInclination(newspeed, currentInclination().value());
r = true;
}
}
lastInclination = currentInclination().value();
return r;
}
QTime treadmill::lastRequestedPace() {
QSettings settings;
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
@@ -485,13 +632,24 @@ QTime treadmill::lastRequestedPace() {
}
}
void treadmill::parseInclination(double inclination) {
QSettings settings;
bool stryd_inclination_instead_treadmill = settings.value(QZSettings::stryd_inclination_instead_treadmill, QZSettings::default_stryd_inclination_instead_treadmill).toBool();
if(!stryd_inclination_instead_treadmill) {
Inclination = inclination;
} else {
qDebug() << "Inclination from the treadmill is discarded since we are using the one from the power sensor " << inclination;
}
rawInclination = inclination;
}
void treadmill::parseSpeed(double speed) {
QSettings settings;
bool stryd_speed_instead_treadmill = settings.value(QZSettings::stryd_speed_instead_treadmill, QZSettings::default_stryd_speed_instead_treadmill).toBool();
if(!stryd_speed_instead_treadmill) {
Speed = speed;
} else {
qDebug() << "speed from the treadmill is discarded since we are using the one from the power sensor";
qDebug() << "speed from the treadmill is discarded since we are using the one from the power sensor " << speed;
}
rawSpeed = speed;
}
@@ -587,3 +745,19 @@ void treadmill::changePower(int32_t power) {
metric treadmill::lastRequestedPower() { return RequestedPower; }
QTime treadmill::speedToPace(double Speed) {
QSettings settings;
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
double unit_conversion = 1.0;
if (miles) {
unit_conversion = 0.621371;
}
if (Speed == 0) {
return QTime(0, 0, 0, 0);
} else {
double speed = Speed * unit_conversion;
return QTime(0, (int)(1.0 / (speed / 60.0)),
(((double)(1.0 / (speed / 60.0)) - ((double)((int)(1.0 / (speed / 60.0))))) * 60.0), 0);
}
}

View File

@@ -9,7 +9,7 @@ class treadmill : public bluetoothdevice {
public:
treadmill();
void update_metrics(bool watt_calc, const double watts);
void update_metrics(bool watt_calc, const double watts, const bool from_accessory = false);
metric lastRequestedSpeed() { return RequestedSpeed; }
QTime lastRequestedPace();
metric lastRequestedInclination() { return RequestedInclination; }
@@ -48,6 +48,7 @@ class treadmill : public bluetoothdevice {
virtual bool canHandleSpeedChange() { return true; }
virtual bool canHandleInclineChange() { return true; }
double runningStressScore();
QTime speedToPace(double Speed);
public slots:
virtual void changeSpeed(double speed);
@@ -57,6 +58,7 @@ class treadmill : public bluetoothdevice {
void cadenceSensor(uint8_t cadence) override;
void powerSensor(uint16_t power) override;
void speedSensor(double speed) override;
void inclinationSensor(double grade, double inclination) override;
void instantaneousStrideLengthSensor(double length) override;
void groundContactSensor(double groundContact) override;
void verticalOscillationSensor(double verticalOscillation) override;
@@ -71,21 +73,24 @@ class treadmill : public bluetoothdevice {
double lastSpeed = 0.0;
double lastInclination = 0;
metric rawSpeed;
metric rawInclination;
metric RequestedSpeed;
metric RequestedInclination;
metric InstantaneousStrideLengthCM;
metric GroundContactMS;
metric VerticalOscillationMM;
metric StepCount;
metric VerticalOscillationMM;
double m_lastRawSpeedRequested = -1;
double m_lastRawInclinationRequested = -100;
bool instantaneousStrideLengthCMAvailableFromDevice = false;
treadmillErgTable _ergTable;
void parseSpeed(double speed);
void parseInclination(double speed);
bool areInclinationSettingsDefault();
private:
bool simulateInclinationWithSpeed();
bool followPowerBySpeed();
void evaluateStepCount();
};

View File

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

View File

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

View File

@@ -0,0 +1,416 @@
#include "trxappgateusbrower.h"
#ifdef Q_OS_ANDROID
#include "keepawakehelper.h"
#endif
#include "virtualdevices/virtualbike.h"
#include "virtualdevices/virtualtreadmill.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QFile>
#include <QMetaEnum>
#include <QSettings>
#include <QThread>
#include <chrono>
#include <math.h>
using namespace std::chrono_literals;
trxappgateusbrower::trxappgateusbrower(bool noWriteResistance, bool noHeartService, 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->bikeResistanceGain = bikeResistanceGain;
this->bikeResistanceOffset = bikeResistanceOffset;
initDone = false;
connect(refresh, &QTimer::timeout, this, &trxappgateusbrower::update);
refresh->start(200ms);
}
void trxappgateusbrower::writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log,
bool wait_for_response) {
QEventLoop loop;
QTimer timeout;
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 (writeBuffer) {
delete writeBuffer;
}
writeBuffer = new QByteArray((const char *)data, data_len);
gattCommunicationChannelService->writeCharacteristic(gattWriteCharacteristic, *writeBuffer);
if (!disable_log) {
emit debug(QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info);
}
loop.exec();
}
void trxappgateusbrower::forceResistance(resistance_t requestResistance) {
uint8_t noOpData1[] = {0xf0, 0xa6, 0x35, 0x01, 0x02, 0xce};
noOpData1[4] = requestResistance + 1;
noOpData1[5] = noOpData1[4] + 0xcc;
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("writingResistance"));
}
void trxappgateusbrower::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) {
QSettings settings;
update_metrics(true, watts());
{
if (requestResistance != -1) {
if (requestResistance < 1)
requestResistance = 1;
if (requestResistance != currentResistance().value() && requestResistance >= 1 &&
requestResistance <= 15) {
emit debug(QStringLiteral("writing resistance ") + QString::number(requestResistance));
forceResistance(requestResistance);
}
requestResistance = -1;
} else {
uint8_t noOpData1[] = {0xf0, 0xa2, 0x01, 0xe7, 0x7a};
writeCharacteristic(noOpData1, sizeof(noOpData1), QStringLiteral("noOp"));
}
}
// updating the treadmill console every second
if (sec1Update++ == (500 / refresh->interval())) {
sec1Update = 0;
// updateDisplay(elapsed);
}
/*
if (requestStart != -1) {
emit debug(QStringLiteral("starting..."));
// btinit();
requestStart = -1;
emit tapeStarted();
}*/
if (requestStop != -1) {
emit debug(QStringLiteral("stopping..."));
// writeCharacteristic(initDataF0C800B8, sizeof(initDataF0C800B8), "stop tape");
requestStop = -1;
}
}
}
void trxappgateusbrower::changeInclinationRequested(double grade, double percentage) {
if (percentage < 0)
percentage = 0;
changeInclination(grade, percentage);
}
void trxappgateusbrower::serviceDiscovered(const QBluetoothUuid &gatt) {
emit debug(QStringLiteral("serviceDiscovered ") + gatt.toString());
}
double trxappgateusbrower::GetSpeedFromPacket(const QByteArray &packet) {
uint16_t convertedData = (packet.at(7) - 1) + ((packet.at(6) - 1) * 100);
double data = (double)(convertedData) / 10.0f;
return data;
}
double trxappgateusbrower::GetCadenceFromPacket(const QByteArray &packet) {
uint16_t convertedData = ((uint16_t)packet.at(20)) - 1;
return convertedData;
}
double trxappgateusbrower::GetWattFromPacket(const QByteArray &packet) {
uint16_t convertedData = ((packet.at(17) - 1) * 100) + (packet.at(18) - 1);
double data = ((double)(convertedData)) / 10.0f;
return data;
}
void trxappgateusbrower::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();
double weight = settings.value(QZSettings::weight, QZSettings::default_weight).toFloat();
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();
emit debug(QStringLiteral(" << ") + newValue.toHex(' '));
lastPacket = newValue;
if(newValue.length() != 23) {
return;
}
Resistance = newValue.at(18) - 1;
Speed = GetSpeedFromPacket(newValue);
Cadence = (GetCadenceFromPacket(newValue) * cadence_gain) + cadence_offset;
m_watt = GetWattFromPacket(newValue);
if (watts())
KCal += ((((0.048 * ((double)watts()) + 1.19) * weight * 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
// KCal = (((uint16_t)((uint8_t)newValue.at(15)) << 8) + (uint16_t)((uint8_t) newValue.at(14)));
Distance += ((Speed.value() / 3600000.0) *
((double)lastRefreshCharacteristicChanged.msecsTo(QDateTime::currentDateTime())));
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(QStringLiteral("Disabled"))) {
update_hr_from_external();
}
}
emit debug(QStringLiteral("Current Speed: ") + QString::number(Speed.value()));
emit debug(QStringLiteral("Current Cadence: ") + QString::number(Cadence.value()));
emit debug(QStringLiteral("Current Resistance: ") + QString::number(Resistance.value()));
emit debug(QStringLiteral("Current Calculate Distance: ") + QString::number(Distance.value()));
// debug("Current Distance: " + QString::number(distance));
emit debug(QStringLiteral("Current Watt: ") + QString::number(watts()));
if (m_control->error() != QLowEnergyController::NoError) {
qDebug() << QStringLiteral("QLowEnergyController ERROR!!") << m_control->errorString();
}
}
void trxappgateusbrower::btinit() {
uint8_t initData1[] = {0xf0, 0xa0, 0x01, 0x00, 0x91};
uint8_t initData2[] = {0xf0, 0xa0, 0x01, 0xe7, 0x78};
uint8_t initData3[] = {0xf0, 0xa1, 0x01, 0xe7, 0x79};
uint8_t initData4[] = {0xf0, 0xa3, 0x01, 0xe7, 0x01, 0x7c};
uint8_t initData5[] = {0xf0, 0xa4, 0x01, 0xe7, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x83};
uint8_t initData6[] = {0xf0, 0xa5, 0x01, 0xe7, 0x02, 0x7f};
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(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData3, sizeof(initData3), QStringLiteral("init"), false, true);
writeCharacteristic(initData2, sizeof(initData2), QStringLiteral("init"), false, true);
writeCharacteristic(initData4, sizeof(initData4), QStringLiteral("init"), false, true);
writeCharacteristic(initData5, sizeof(initData5), QStringLiteral("init"), false, true);
writeCharacteristic(initData6, sizeof(initData6), QStringLiteral("init"), false, true);
initDone = true;
}
void trxappgateusbrower::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId((quint16)0xfff2);
QBluetoothUuid _gattNotify1CharacteristicId((quint16)0xfff1);
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceState>();
emit debug(QStringLiteral("BTLE stateChanged ") + QString::fromLocal8Bit(metaEnum.valueToKey(state)));
if (state == QLowEnergyService::ServiceDiscovered) {
// qDebug() << gattCommunicationChannelService->characteristics();
QString uuidWrite = QStringLiteral("0000fff2-0000-1000-8000-00805f9b34fb");
QString uuidNotify1 = QStringLiteral("0000fff1-0000-1000-8000-00805f9b34fb");
QString uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
QBluetoothUuid _gattWriteCharacteristicId(uuidWrite);
QBluetoothUuid _gattNotify1CharacteristicId(uuidNotify1);
QBluetoothUuid _gattNotify2CharacteristicId(uuidNotify2);
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
if (!gattWriteCharacteristic.isValid()) {
uuidWrite = QStringLiteral("49535343-8841-43f4-a8d4-ecbe34729bb3");
uuidNotify1 = QStringLiteral("49535343-1E4D-4BD9-BA61-23C647249616");
uuidNotify2 = QStringLiteral("49535343-4c8a-39b3-2f49-511cff073b7e");
gattWriteCharacteristic = gattCommunicationChannelService->characteristic(_gattWriteCharacteristicId);
gattNotify1Characteristic = gattCommunicationChannelService->characteristic(_gattNotify1CharacteristicId);
}
// establish hook into notifications
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicChanged, this,
&trxappgateusbrower::characteristicChanged);
connect(gattCommunicationChannelService, &QLowEnergyService::characteristicWritten, this,
&trxappgateusbrower::characteristicWritten);
connect(gattCommunicationChannelService,
static_cast<void (QLowEnergyService::*)(QLowEnergyService::ServiceError)>(&QLowEnergyService::error),
this, &trxappgateusbrower::errorService);
connect(gattCommunicationChannelService, &QLowEnergyService::descriptorWritten, this,
&trxappgateusbrower::descriptorWritten);
// ******************************************* virtual treadmill init *************************************
QSettings settings;
if (!firstStateChanged && !this->hasVirtualDevice()) {
bool virtual_device_enabled =
settings.value(QZSettings::virtual_device_enabled, QZSettings::default_virtual_device_enabled).toBool();
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, &trxappgateusbrower::debug);
connect(virtualTreadmill, &virtualtreadmill::changeInclination, this,
&trxappgateusbrower::changeInclinationRequested);
this->setVirtualDevice(virtualTreadmill, VIRTUAL_DEVICE_MODE::PRIMARY);
} else {
debug("creating virtual bike interface...");
auto virtualBike = new virtualbike(this);
connect(virtualBike, &virtualbike::changeInclination, this,
&trxappgateusbrower::changeInclinationRequested);
this->setVirtualDevice(virtualBike, VIRTUAL_DEVICE_MODE::ALTERNATIVE);
}
firstStateChanged = 1;
}
}
// ********************************************************************************************************
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
gattCommunicationChannelService->writeDescriptor(
gattNotify1Characteristic.descriptor(QBluetoothUuid::ClientCharacteristicConfiguration), descriptor);
}
}
void trxappgateusbrower::descriptorWritten(const QLowEnergyDescriptor &descriptor, const QByteArray &newValue) {
emit debug(QStringLiteral("descriptorWritten ") + descriptor.name() + QStringLiteral(" ") + newValue.toHex(' '));
initRequest = true;
emit connectedAndDiscovered();
}
void trxappgateusbrower::characteristicWritten(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
Q_UNUSED(characteristic);
emit debug(QStringLiteral("characteristicWritten ") + newValue.toHex(' '));
}
void trxappgateusbrower::serviceScanDone(void) {
emit debug(QStringLiteral("serviceScanDone"));
QString uuid = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
QString uuid2 = QStringLiteral("49535343-FE7D-4AE5-8FA9-9FAFD205E455");
QString uuid3 = QStringLiteral("0000fff0-0000-1000-8000-00805f9b34fb");
QBluetoothUuid _gattCommunicationChannelServiceId(uuid);
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
if (gattCommunicationChannelService == nullptr) {
qDebug() << QStringLiteral("invalid service") << uuid;
uuid = uuid2;
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
return;
}
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this, &trxappgateusbrower::stateChanged);
gattCommunicationChannelService->discoverDetails();
}
void trxappgateusbrower::errorService(QLowEnergyService::ServiceError err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyService::ServiceError>();
emit debug(QStringLiteral("trxappgateusbrower::errorService") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void trxappgateusbrower::error(QLowEnergyController::Error err) {
QMetaEnum metaEnum = QMetaEnum::fromType<QLowEnergyController::Error>();
emit debug(QStringLiteral("trxappgateusbrower::error") + QString::fromLocal8Bit(metaEnum.valueToKey(err)) +
m_control->errorString());
}
void trxappgateusbrower::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit debug(QStringLiteral("Found new device: ") + device.name() + " (" + device.address().toString() + ')');
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);
connect(m_control, &QLowEnergyController::serviceDiscovered, this, &trxappgateusbrower::serviceDiscovered);
connect(m_control, &QLowEnergyController::discoveryFinished, this, &trxappgateusbrower::serviceScanDone);
connect(m_control,
static_cast<void (QLowEnergyController::*)(QLowEnergyController::Error)>(&QLowEnergyController::error),
this, &trxappgateusbrower::error);
connect(m_control, &QLowEnergyController::stateChanged, this, &trxappgateusbrower::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();
});
// Connect
m_control->connectToDevice();
return;
}
}
bool trxappgateusbrower::connected() {
if (!m_control) {
return false;
}
return m_control->state() == QLowEnergyController::DiscoveredState;
}
void trxappgateusbrower::controllerStateChanged(QLowEnergyController::ControllerState state) {
qDebug() << QStringLiteral("controllerStateChanged") << state;
if (state == QLowEnergyController::UnconnectedState && m_control) {
qDebug() << QStringLiteral("trying to connect back again...");
initDone = false;
m_control->connectToDevice();
}
}
uint16_t trxappgateusbrower::watts() { return m_watt.value(); }
void trxappgateusbrower::searchingStop() { searchStopped = true; }

View File

@@ -0,0 +1,107 @@
#ifndef TRXAPPGATEUSBROWER_H
#define TRXAPPGATEUSBROWER_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/rower.h"
#ifdef Q_OS_IOS
#include "ios/lockscreen.h"
#endif
class trxappgateusbrower : public rower {
Q_OBJECT
public:
trxappgateusbrower(bool noWriteResistance = false, bool noHeartService = false, int8_t bikeResistanceOffset = 4,
double bikeResistanceGain = 1.0);
bool connected() override;
private:
void writeCharacteristic(uint8_t *data, uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
void startDiscover();
uint16_t watts() override;
void forceResistance(resistance_t requestResistance);
void btinit();
double GetSpeedFromPacket(const QByteArray &packet);
double GetCadenceFromPacket(const QByteArray &packet);
double GetWattFromPacket(const QByteArray &packet);
QTimer *refresh;
QLowEnergyService* gattCommunicationChannelService;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
uint8_t sec1Update = 0;
QByteArray lastPacket;
QDateTime lastRefreshCharacteristicChanged = QDateTime::currentDateTime();
uint8_t firstStateChanged = 0;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;
const uint8_t max_resistance = 72; // 24;
const uint8_t default_resistance = 6;
bool initDone = false;
bool initRequest = false;
bool noWriteResistance = false;
bool noHeartService = false;
uint8_t counterPoll = 0;
bool searchStopped = false;
#ifdef Q_OS_IOS
lockscreen *h = 0;
#endif
Q_SIGNALS:
void disconnected();
void debug(QString string);
public slots:
void deviceDiscovered(const QBluetoothDeviceInfo &device);
void searchingStop();
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);
void changeInclinationRequested(double grade, double percentage);
// void ftmsCharacteristicChanged(const QLowEnergyCharacteristic &characteristic, const QByteArray &newValue);
};
#endif // TRXAPPGATEUSBROWER_H

View File

@@ -1,3 +1,4 @@
#include "homeform.h"
#include "wahookickrheadwind.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
@@ -279,6 +280,10 @@ void wahookickrheadwind::deviceDiscovered(const QBluetoothDeviceInfo &device) {
QSettings settings;
emit debug(QStringLiteral("Found new device: ") + device.name() + QStringLiteral(" (") +
device.address().toString() + ')');
if(homeform::singleton())
homeform::singleton()->setToastRequested(device.name() + QStringLiteral(" connected!"));
{
bluetoothDevice = device;
m_control = QLowEnergyController::createCentral(bluetoothDevice, this);

View File

@@ -306,17 +306,22 @@ resistance_t wahookickrsnapbike::pelotonToBikeResistance(int pelotonResistance)
settings.value(QZSettings::schwinn_bike_resistance_v2, QZSettings::default_schwinn_bike_resistance_v2).toBool();
if (!schwinn_bike_resistance_v2) {
if (pelotonResistance > 54)
return pelotonResistance;
return (pelotonResistance * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
if (pelotonResistance < 26)
return pelotonResistance / 5;
return ((pelotonResistance / 5) * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
// y = 0,04x2 - 1,32x + 11,8
return ((0.04 * pow(pelotonResistance, 2)) - (1.32 * pelotonResistance) + 11.8);
return (((0.04 * pow(pelotonResistance, 2)) - (1.32 * pelotonResistance) + 11.8) * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
} else {
if (pelotonResistance > 20)
return (((double)pelotonResistance - 20.0) * 1.25);
return ((((double)pelotonResistance - 20.0) * 1.25) * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
else
return 1;
return (1 * settings.value(QZSettings::peloton_gain, QZSettings::default_peloton_gain).toDouble()) +
settings.value(QZSettings::peloton_offset, QZSettings::default_peloton_offset).toDouble();
}
}

View File

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

View File

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

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