Compare commits

...

175 Commits

Author SHA1 Message Date
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
93 changed files with 12266 additions and 3945 deletions

View File

@@ -141,7 +141,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 +168,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" .
@@ -338,7 +338,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:
@@ -862,7 +862,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 +890,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" .
@@ -1052,7 +1052,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 .
@@ -1126,14 +1126,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-24.04
steps:
- name: Checkout code
@@ -1177,11 +1180,14 @@ jobs:
make -j$(nproc)
"
- name: Rename binary
run: mv src/qdomyos-zwift src/qdomyos-zwift-64bit
- name: Archive Raspberry Pi binary
uses: actions/upload-artifact@v4
with:
name: raspberry-pi-64bit-binary
path: src/qdomyos-zwift
path: src/qdomyos-zwift-64bit
- name: Download and expand Raspberry Pi OS image
run: |
@@ -1217,7 +1223,7 @@ jobs:
- name: Copy binary to Raspberry Pi image
run: |
sudo cp src/qdomyos-zwift /mnt/raspbian/home/pi/
sudo cp src/qdomyos-zwift-64bit /mnt/raspbian/home/pi/qdomyos-zwift
sudo chown 1000:1000 /mnt/raspbian/home/pi/qdomyos-zwift
- name: Setup auto-start for qdomyos-zwift
@@ -1263,7 +1269,7 @@ jobs:
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-and-image-64bit, raspberry-pi-build] # Specify the job dependencies
steps:
- name: Download artifacts
uses: actions/download-artifact@v4
@@ -1292,6 +1298,6 @@ jobs:
windows-binary-no-python/*
windows-binary/*
fdroid-android-trial/*
raspberry-pi-binary/*
raspberry-pi-64bit-binary/*
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

@@ -290,6 +290,8 @@
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 */; };
@@ -365,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 */; };
@@ -436,6 +439,8 @@
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 */; };
@@ -541,6 +546,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 */; };
@@ -565,6 +572,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 */; };
@@ -1164,6 +1175,15 @@
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>"; };
@@ -1286,6 +1306,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>"; };
@@ -1391,6 +1413,9 @@
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>"; };
@@ -1548,6 +1573,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>"; };
@@ -1583,7 +1611,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>"; };
@@ -2134,6 +2168,26 @@
2EB56BE3C2D93CDAB0C52E67 /* Sources */ = {
isa = PBXGroup;
children = (
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 */,
@@ -2598,6 +2652,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>";
@@ -3541,7 +3598,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 */,
@@ -3562,6 +3621,7 @@
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 */,
@@ -3657,6 +3717,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 */,
@@ -3684,6 +3745,7 @@
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 */,
@@ -3747,6 +3809,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 */,
@@ -3778,6 +3841,7 @@
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 */,
@@ -3816,6 +3880,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 */,
@@ -3823,6 +3889,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 */,
);
@@ -4165,7 +4233,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 970;
CURRENT_PROJECT_VERSION = 1007;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
GCC_PREPROCESSOR_DEFINITIONS = "ADB_HOST=1";
@@ -4359,7 +4427,7 @@
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
CODE_SIGN_ENTITLEMENTS = "../src/ios/qdomyos-zwift.entitlements";
COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 970;
CURRENT_PROJECT_VERSION = 1007;
DEBUG_INFORMATION_FORMAT = dwarf;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = NO;
@@ -4589,7 +4657,7 @@
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 970;
CURRENT_PROJECT_VERSION = 1007;
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
ENABLE_STRICT_OBJC_MSGSEND = YES;
@@ -4685,7 +4753,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 970;
CURRENT_PROJECT_VERSION = 1007;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_TEAM = 6335M7T29D;
ENABLE_BITCODE = YES;
@@ -4777,7 +4845,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 970;
CURRENT_PROJECT_VERSION = 1007;
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;
ENABLE_PREVIEWS = YES;
@@ -4891,7 +4959,7 @@
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
CODE_SIGN_ENTITLEMENTS = "watchkit Extension/WatchKit Extension.entitlements";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 970;
CURRENT_PROJECT_VERSION = 1007;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
DEVELOPMENT_ASSET_PATHS = "\"watchkit Extension/Preview Content\"";
ENABLE_BITCODE = YES;

View File

@@ -177,6 +177,124 @@ 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"
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
}
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."
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()
}
}
}
}

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;
}
}

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.9" android:versionCode="953" 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.17" android:versionCode="999" 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.

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

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

@@ -491,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) {
@@ -954,6 +955,23 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
emit searchingStop();
}
this->signalBluetoothDeviceConnected(domyosBike);
} 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")) ||
(b.name().toUpper().startsWith(QStringLiteral("I-CONSOLE+")) && iconsole_elliptical)) &&
!trxappgateusbElliptical && ftms_bike.contains(QZSettings::default_ftms_bike) && filter) {
@@ -1288,7 +1306,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);
@@ -1366,9 +1384,11 @@ 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("TM4500")) ||
b.name().toUpper().startsWith(QStringLiteral("RUNN ")) ||
b.name().toUpper().startsWith(QStringLiteral("YPOO-MINI PRO-")) ||
b.name().toUpper().startsWith(QStringLiteral("BFX_T9_")) ||
@@ -1387,11 +1407,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
@@ -1488,6 +1511,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
}
#endif
} else if ((b.name().toUpper().startsWith("TACX ") ||
b.name().toUpper().startsWith("NEO 3M ") ||
b.name().toUpper().startsWith(QStringLiteral("THINK X")) ||
b.address() == QBluetoothAddress("C1:14:D9:9C:FB:01") || // specific TACX NEO 2 #1707
(b.name().toUpper().startsWith("TACX SMART BIKE"))) &&
@@ -1505,6 +1529,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...
@@ -1588,8 +1626,15 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
(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("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("T300P_")) ||
(b.name().toUpper().startsWith("T200_")) ||
(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
@@ -1810,6 +1855,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"))) &&
@@ -1828,7 +1887,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();
@@ -2042,7 +2101,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);
@@ -2194,7 +2253,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();
@@ -2226,7 +2285,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();
@@ -2254,7 +2313,7 @@ void bluetooth::deviceDiscovered(const QBluetoothDeviceInfo &device) {
b.name().toUpper().contains(QStringLiteral("CR011R")) ||
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 =
@@ -2448,6 +2507,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);
}
}
}
@@ -2779,6 +2860,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) {
@@ -2786,7 +2868,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(),
@@ -2798,6 +2880,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!");
@@ -2873,6 +2963,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() {
@@ -3089,6 +3223,11 @@ void bluetooth::restart() {
delete tacxneo2Bike;
tacxneo2Bike = nullptr;
}
if (cycleopsphantomBike) {
delete cycleopsphantomBike;
cycleopsphantomBike = nullptr;
}
if (stagesBike) {
delete stagesBike;
@@ -3135,6 +3274,11 @@ void bluetooth::restart() {
delete trxappgateusbElliptical;
trxappgateusbElliptical = nullptr;
}
if (trxappgateusbRower) {
delete trxappgateusbRower;
trxappgateusbRower = nullptr;
}
if (soleBike) {
delete soleBike;
@@ -3180,6 +3324,10 @@ void bluetooth::restart() {
delete ziproTreadmill;
ziproTreadmill = nullptr;
}
if (lifespanTreadmill) {
delete lifespanTreadmill;
lifespanTreadmill = nullptr;
}
if (octaneElliptical) {
delete octaneElliptical;
@@ -3342,6 +3490,11 @@ void bluetooth::restart() {
delete chronoBike;
chronoBike = nullptr;
}
if (pitpatBike) {
delete pitpatBike;
pitpatBike = nullptr;
}
if (snodeBike) {
delete snodeBike;
@@ -3517,6 +3670,8 @@ bluetoothdevice *bluetooth::device() {
return npeCableBike;
} else if (tacxneo2Bike) {
return tacxneo2Bike;
} else if (cycleopsphantomBike) {
return cycleopsphantomBike;
} else if (stagesBike) {
return stagesBike;
} else if (toorx) {
@@ -3535,6 +3690,8 @@ bluetoothdevice *bluetooth::device() {
return trxappgateusbBike;
} else if (trxappgateusbElliptical) {
return trxappgateusbElliptical;
} else if (trxappgateusbRower) {
return trxappgateusbRower;
} else if (soleBike) {
return soleBike;
} else if (keepBike) {
@@ -3577,6 +3734,8 @@ bluetoothdevice *bluetooth::device() {
return octaneTreadmill;
} else if (ziproTreadmill) {
return ziproTreadmill;
} else if (lifespanTreadmill) {
return lifespanTreadmill;
} else if (octaneElliptical) {
return octaneElliptical;
} else if (ftmsRower) {
@@ -3633,6 +3792,8 @@ bluetoothdevice *bluetooth::device() {
return inspireBike;
} else if (chronoBike) {
return chronoBike;
} else if (pitpatBike) {
return pitpatBike;
} else if (m3iBike) {
return m3iBike;
} else if (snodeBike) {

View File

@@ -37,6 +37,7 @@
#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"
@@ -72,6 +73,7 @@
#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"
@@ -88,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"
@@ -132,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"
@@ -183,6 +187,7 @@ class bluetooth : public QObject, public SignalHandler {
csafeelliptical *csafeElliptical = nullptr;
#endif
concept2skierg *concept2Skierg = nullptr;
cycleopsphantombike *cycleopsphantomBike = nullptr;
deerruntreadmill *deerrunTreadmill = nullptr;
domyostreadmill *domyos = nullptr;
domyosbike *domyosBike = nullptr;
@@ -198,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;
@@ -251,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;
@@ -258,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;
@@ -357,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

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

@@ -367,7 +367,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;
}

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

@@ -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 {
@@ -228,7 +228,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 +240,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) {
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 +289,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 +307,24 @@ 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));
}
}
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;
@@ -429,7 +445,7 @@ void ftmsbike::characteristicChanged(const QLowEnergyCharacteristic &characteris
}
// 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;
@@ -526,12 +542,15 @@ 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) {
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;
@@ -895,7 +914,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) {
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 +974,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;
@@ -1041,22 +1060,25 @@ void ftmsbike::ftmsCharacteristicChanged(const QLowEnergyCharacteristic &charact
lastPacketFromFTMS.append(b.at(i));
qDebug() << "lastPacketFromFTMS" << lastPacketFromFTMS.toHex(' ');
int16_t slope = (((uint8_t)b.at(3)) + (b.at(4) << 8));
if (gears() != 0) {
slope += (gears() * 50);
}
if(!DIRETO_XR) {
if (gears() != 0) {
slope += (gears() * 50);
}
if(min_inclination > (((double)slope) / 100.0)) {
slope = min_inclination * 100;
qDebug() << "grade override due to min_inclination " << min_inclination;
}
if(min_inclination > (((double)slope) / 100.0)) {
slope = min_inclination * 100;
qDebug() << "grade override due to min_inclination " << min_inclination;
}
slope *= gain;
slope += (offset * 100);
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));
@@ -1157,7 +1179,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);
@@ -1203,6 +1225,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");
@@ -1213,6 +1236,19 @@ 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;
}
if(settings.value(QZSettings::force_resistance_instead_inclination, QZSettings::default_force_resistance_instead_inclination).toBool()) {
@@ -1259,6 +1295,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;
@@ -1280,7 +1327,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) {
@@ -1294,7 +1341,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

@@ -84,6 +84,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 +129,10 @@ 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;
uint8_t battery_level = 0;

View File

@@ -1,4 +1,5 @@
#include "heartratebelt.h"
#include "homeform.h"
#include <QBluetoothLocalDevice>
#include <QDateTime>
#include <QEventLoop>
@@ -118,6 +119,10 @@ void heartratebelt::deviceDiscovered(const QBluetoothDeviceInfo &device) {
// 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;

View File

@@ -816,7 +816,13 @@ void horizontreadmill::btinit() {
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);
@@ -1174,7 +1180,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 && !ICONCEPT_FTMS_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);
@@ -1244,7 +1250,7 @@ void horizontreadmill::forceIncline(double requestIncline) {
}
} else if (gattFTMSService) {
// for the Tecnogym Myrun
if(!anplus_treadmill && !trx3500_treadmill && !mobvoi_tmp_treadmill && !SW_TREADMILL && !ICONCEPT_FTMS_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);
@@ -2132,6 +2138,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)) {
@@ -2184,12 +2191,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 && !BOWFLEX_T9 &&
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)
@@ -2390,7 +2401,8 @@ 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"))) {
@@ -2405,6 +2417,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!");
@@ -2427,6 +2442,12 @@ void horizontreadmill::deviceDiscovered(const QBluetoothDeviceInfo &device) {
} 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;
}
if (device.name().toUpper().startsWith(QStringLiteral("TRX3500"))) {
@@ -3151,7 +3172,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)
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;
@@ -103,6 +105,8 @@ class horizontreadmill : public treadmill {
bool DOMYOS = false;
bool SW_TREADMILL = false;
bool BOWFLEX_T9 = false;
bool YPOO_MINI_PRO = false;
bool MX_TM = 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

@@ -184,8 +184,7 @@ double kineticinroadbike::bikeResistanceToPeloton(double resistance) {
}
void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &characteristic,
const QByteArray &newValue) {
// qDebug() << "characteristicChanged" << characteristic.uuid() << newValue << newValue.length();
const QByteArray &newValue) {
Q_UNUSED(characteristic);
QSettings settings;
QString heartRateBeltName =
@@ -195,25 +194,40 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
lastPacket = newValue;
return;
QByteArray encryptedData = newValue;
int dataSize = encryptedData.size();
/*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(10));
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 = 0.37497622 * ((double)Cadence.value());
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) *
@@ -222,6 +236,7 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
(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())));
@@ -255,16 +270,13 @@ void kineticinroadbike::characteristicChanged(const QLowEnergyCharacteristic &ch
#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 = Resistance.value();
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 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());
@@ -317,8 +329,10 @@ void kineticinroadbike::btinit() {
}
void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState state) {
QBluetoothUuid _gattWriteCharacteristicId(QStringLiteral("E9410102-B434-446B-B5CC-36592FC4C724"));
QBluetoothUuid _gattNotify1CharacteristicId(QStringLiteral("E9410101-B434-446B-B5CC-36592FC4C724"));
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));
@@ -326,8 +340,18 @@ void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState 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());
@@ -392,6 +416,10 @@ void kineticinroadbike::stateChanged(QLowEnergyService::ServiceState state) {
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);
}
}
@@ -411,7 +439,7 @@ void kineticinroadbike::characteristicWritten(const QLowEnergyCharacteristic &ch
void kineticinroadbike::serviceScanDone(void) {
qDebug() << QStringLiteral("serviceScanDone");
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("E9410100-B434-446B-B5CC-36592FC4C724"));
QBluetoothUuid _gattCommunicationChannelServiceId(QStringLiteral("e9410200-b434-446b-b5cc-36592fc4c724"));
gattCommunicationChannelService = m_control->createServiceObject(_gattCommunicationChannelServiceId);
connect(gattCommunicationChannelService, &QLowEnergyService::stateChanged, this,

View File

@@ -34,6 +34,8 @@
#include "ios/lockscreen.h"
#endif
#include "SmartControl.h"
class kineticinroadbike : public bike {
Q_OBJECT
public:
@@ -61,6 +63,8 @@ class kineticinroadbike : public bike {
QLowEnergyService *gattCommunicationChannelService = nullptr;
QLowEnergyCharacteristic gattWriteCharacteristic;
QLowEnergyCharacteristic gattNotify1Characteristic;
QLowEnergyCharacteristic gattNotify2Characteristic;
QLowEnergyCharacteristic gattNotify3Characteristic;
int8_t bikeResistanceOffset = 4;
double bikeResistanceGain = 1.0;

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 (packet[2] << 8) | 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

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

@@ -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};
@@ -5527,105 +6175,97 @@ void proformtreadmill::btinit() {
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

@@ -138,9 +138,11 @@ void sportsplusrower::characteristicChanged(const QLowEnergyCharacteristic &char
m_watt = ((newValue.at(10) >> 4) * 10) + (newValue.at(10) & 0x0F);
emit debug(QStringLiteral("Current watt: ") + QString::number(m_watt.value()));
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(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 +=
@@ -170,7 +172,7 @@ void sportsplusrower::characteristicChanged(const QLowEnergyCharacteristic &char
}
FanSpeed = 0;
emit debug(QStringLiteral("Current cadence: ") + QString::number(cadence));
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));

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);
@@ -520,6 +523,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 +672,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

@@ -740,6 +740,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

@@ -65,7 +65,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));
@@ -74,6 +74,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()
@@ -476,6 +478,47 @@ 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;
static double lastWattage = 0;
if (treadmill_follow_wattage) {
if (currentInclination().value() != lastInclination && lastWattage != 0) {
double newspeed = 0;
double bestSpeed = 0.1;
double bestDifference = fabs(wattsCalc(w, bestSpeed, currentInclination().value()) - lastWattage);
for (int speed = 1; speed <= 300; speed++) {
double s = ((double)speed) / 10.0;
double thisDifference = fabs(wattsCalc(w, s, currentInclination().value()) - lastWattage);
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();
lastWattage = wattsMetric().value();
return r;
}
QTime treadmill::lastRequestedPace() {
QSettings settings;
bool miles = settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool();
@@ -594,3 +637,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);
@@ -86,6 +87,7 @@ class treadmill : public bluetoothdevice {
private:
bool simulateInclinationWithSpeed();
bool followPowerBySpeed();
void evaluateStepCount();
};

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

@@ -1563,6 +1563,12 @@ void homeform::sortTiles() {
target_power->setGridId(i);
dataList.append(target_power);
}
if (settings.value(QZSettings::tile_target_zone_enabled, false).toBool() &&
settings.value(QZSettings::tile_target_zone_order, 24).toInt() == i) {
target_zone->setGridId(i);
dataList.append(target_zone);
}
}
} else if (bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE) {
for (int i = 0; i < 100; i++) {
@@ -1898,13 +1904,13 @@ void homeform::sortTiles() {
}
if (settings.value(QZSettings::tile_biggears_enabled, false).toBool() &&
settings.value(QZSettings::tile_biggears_order, 54).toInt() == i) {
settings.value(QZSettings::tile_biggears_order, 54).toInt() == i + (settings.value(QZSettings::tile_biggears_swap, QZSettings::default_tile_biggears_swap).toBool() ? 1 : 0)) {
biggearsPlus->setGridId(i);
dataList.append(biggearsPlus);
}
if (settings.value(QZSettings::tile_biggears_enabled, false).toBool() &&
settings.value(QZSettings::tile_biggears_order, 54).toInt() + 1 == i) {
settings.value(QZSettings::tile_biggears_order, 54).toInt() == i + (settings.value(QZSettings::tile_biggears_swap, QZSettings::default_tile_biggears_swap).toBool() ? 0 : 1)) {
biggearsMinus->setGridId(i);
dataList.append(biggearsMinus);
}
@@ -4088,31 +4094,35 @@ void homeform::update() {
targetMets->setValue(QString::number(trainProgram->currentTargetMets(), 'f', 1));
trainrow next = trainProgram->getRowFromCurrent(1);
trainrow next_1 = trainProgram->getRowFromCurrent(2);
if (next.duration.second() != 0 || next.duration.minute() != 0 || next.duration.hour() != 0) {
if (next.requested_peloton_resistance != -1)
nextRows->setValue(QStringLiteral("PR") + QString::number(next.requested_peloton_resistance) +
QStringLiteral(" ") + next.duration.toString(QStringLiteral("mm:ss")));
else if (next.resistance != -1)
nextRows->setValue(QStringLiteral("R") + QString::number(next.resistance) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
else if (next.zoneHR != -1)
nextRows->setValue(QStringLiteral("HR") + QString::number(next.zoneHR) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
else if (next.HRmin != -1 && next.HRmax != -1)
if (next.duration.second() != 0 || next.duration.minute() != 0 || next.duration.hour() != 0 || next.distance != -1) {
QString duration = next.duration.toString(QStringLiteral("mm:ss"));
if(next.distance != -1) {
duration = QString::number(next.distance, 'f' , 1);
}
if (next.requested_peloton_resistance != -1) {
nextRows->setValue(QStringLiteral("PR") + QString::number(next.requested_peloton_resistance));
nextRows->setSecondLine(duration);
} else if (next.resistance != -1) {
nextRows->setValue(QStringLiteral("R") + QString::number(next.resistance));
nextRows->setSecondLine(duration);
} else if (next.zoneHR != -1) {
nextRows->setValue(QStringLiteral("HR") + QString::number(next.zoneHR));
nextRows->setSecondLine(duration);
} else if (next.HRmin != -1 && next.HRmax != -1) {
nextRows->setValue(QStringLiteral("HR") + QString::number(next.HRmin) + QStringLiteral("-") +
QString::number(next.HRmax) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
else if (next.speed != -1 && next.inclination != -200)
QString::number(next.HRmax));
nextRows->setSecondLine(duration);
} else if (next.speed != -1 && next.inclination != -200) {
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed, 'f' , 1) + QStringLiteral("I") +
QString::number(next.inclination, 'f' , 1) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
else if (next.speed != -1)
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed, 'f' , 1) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
else if (next.inclination != -200)
nextRows->setValue(QStringLiteral("I") + QString::number(next.inclination, 'f' , 1) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
else if (next.power != -1) {
QString::number(next.inclination, 'f' , 1));
nextRows->setSecondLine(duration);
} else if (next.speed != -1) {
nextRows->setValue(QStringLiteral("S") + QString::number(next.speed, 'f' , 1));
nextRows->setSecondLine(duration);
} else if (next.inclination != -200) {
nextRows->setValue(QStringLiteral("I") + QString::number(next.inclination, 'f' , 1));
nextRows->setSecondLine(duration);
} else if (next.power != -1) {
double ftpPerc = (next.power / ftpSetting) * 100.0;
uint8_t ftpZone = 1;
if (ftpPerc < 56) {
@@ -4131,16 +4141,20 @@ void homeform::update() {
ftpZone = 7;
}
nextRows->setValue(QStringLiteral("Z") + QString::number(ftpZone) + QStringLiteral(" ") +
next.duration.toString(QStringLiteral("mm:ss")));
if (next_1.duration.second() != 0 || next_1.duration.minute() != 0 || next_1.duration.hour() != 0) {
duration);
if (next_1.duration.second() != 0 || next_1.duration.minute() != 0 || next_1.duration.hour() != 0 || next_1.distance != -1) {
QString duration_1 = next_1.duration.toString(QStringLiteral("mm:ss"));
if(next_1.distance != -1) {
duration_1 = QString::number(next_1.distance, 'f' , 1);
}
if (next_1.requested_peloton_resistance != -1)
nextRows->setSecondLine(
QStringLiteral("PR") + QString::number(next_1.requested_peloton_resistance) +
QStringLiteral(" ") + next_1.duration.toString(QStringLiteral("mm:ss")));
QStringLiteral(" ") + duration_1);
else if (next_1.resistance != -1)
nextRows->setSecondLine(QStringLiteral("R") + QString::number(next_1.resistance) +
QStringLiteral(" ") +
next_1.duration.toString(QStringLiteral("mm:ss")));
duration_1);
else if (next_1.power != -1) {
double ftpPerc = (next_1.power / ftpSetting) * 100.0;
uint8_t ftpZone = 1;
@@ -4161,7 +4175,7 @@ void homeform::update() {
}
nextRows->setSecondLine(QStringLiteral("Z") + QString::number(ftpZone) +
QStringLiteral(" ") +
next_1.duration.toString(QStringLiteral("mm:ss")));
duration_1);
}
} else {
nextRows->setSecondLine(QStringLiteral("N/A"));
@@ -4344,17 +4358,23 @@ void homeform::update() {
this->pace->setValueFontColor(QStringLiteral("red"));
}
} else {
if (bluetoothManager->device()->currentSpeed().value() <= trainProgram->currentRow().upper_speed &&
bluetoothManager->device()->currentSpeed().value() >= trainProgram->currentRow().lower_speed) {
// Round speeds to 1 decimal place before comparison to avoid overly strict matching
double currentSpeed = round(bluetoothManager->device()->currentSpeed().value() * 10.0) / 10.0;
double upperSpeed = round(trainProgram->currentRow().upper_speed * 10.0) / 10.0;
double lowerSpeed = round(trainProgram->currentRow().lower_speed * 10.0) / 10.0;
// Check if speed is in target zone (green)
if (currentSpeed <= upperSpeed && currentSpeed >= lowerSpeed) {
this->target_zone->setValueFontColor(QStringLiteral("limegreen"));
this->pace->setValueFontColor(QStringLiteral("limegreen"));
} else if (bluetoothManager->device()->currentSpeed().value() <=
(trainProgram->currentRow().upper_speed + 0.2) &&
bluetoothManager->device()->currentSpeed().value() >=
(trainProgram->currentRow().lower_speed - 0.2)) {
}
// Check if speed is close to target zone (orange)
else if (currentSpeed <= (upperSpeed + 0.2) && currentSpeed >= (lowerSpeed - 0.2)) {
this->target_zone->setValueFontColor(QStringLiteral("orange"));
this->pace->setValueFontColor(QStringLiteral("orange"));
} else {
}
// Speed is out of range (red)
else {
this->target_zone->setValueFontColor(QStringLiteral("red"));
this->pace->setValueFontColor(QStringLiteral("red"));
}
@@ -4387,8 +4407,21 @@ void homeform::update() {
break;
}
this->target_pace->setValue(
((treadmill *)bluetoothManager->device())->lastRequestedPace().toString(QStringLiteral("m:ss")));
if (trainProgram) {
// in order to see the target pace of a peloton workout even if the speed force for treadmill is disabled
this->target_pace->setValue(
((treadmill *)bluetoothManager->device())->speedToPace(trainProgram->currentRow().speed).toString(QStringLiteral("m:ss")));
this->target_pace->setSecondLine(((treadmill *)bluetoothManager->device())
->speedToPace(trainProgram->currentRow().lower_speed)
.toString(QStringLiteral("m:ss")) +
" - " +
((treadmill *)bluetoothManager->device())
->speedToPace(trainProgram->currentRow().upper_speed)
.toString(QStringLiteral("m:ss")));
} else {
this->target_pace->setValue(
((treadmill *)bluetoothManager->device())->lastRequestedPace().toString(QStringLiteral("m:ss")));
}
this->target_speed->setValue(QString::number(
((treadmill *)bluetoothManager->device())->lastRequestedSpeed().value() * unit_conversion, 'f', 1));
this->target_speed->setSecondLine(QString::number(bluetoothManager->device()->difficult() * 100.0, 'f', 0) +

View File

@@ -0,0 +1,366 @@
window.chartColors = {
red: 'rgb(255, 29, 0)',
redt: 'rgb(255, 29, 0, 0.55)',
orange: 'rgb(255, 159, 64)',
oranget: 'rgb(255, 159, 64, 0.55)',
darkorange: 'rgb(255, 140, 0)',
darkoranget: 'rgb(255, 140, 0, 0.55)',
orangered: 'rgb(255, 69, 0)',
orangeredt: 'rgb(255, 69, 0, 0.55)',
yellow: 'rgb(255, 205, 86)',
yellowt: 'rgb(255, 205, 86, 0.55)',
green: 'rgb(75, 192, 192)',
greent: 'rgb(75, 192, 192, 0.55)',
limegreen: 'rgb(50, 205, 50)',
limegreent: 'rgb(50, 205, 50, 0.55)',
gold: 'rgb(255, 215, 0)',
goldt: 'rgb(255, 215, 0, 0.55)',
grey: 'rgb(201, 203, 207)',
greyt: 'rgb(201, 203, 207, 0.55)',
black: 'rgb(0, 0, 0)',
blackt: 'rgb(0, 0, 0, 0.55)',
};
var treadmillChart = null;
var speed_max = 0;
var incline_max = 0;
// Define speed zones
const speedZones = [6, 8, 10, 12, 14, 16]; // km/h
function process_arr(arr) {
let ctx = document.getElementById('canvas').getContext('2d');
let div = document.getElementById('divcanvas');
let speed = [];
let targetSpeed = [];
let inclination = [];
let targetInclination = [];
let maxEl = 0;
for (let el of arr) {
let time = el.elapsed_s + el.elapsed_m * 60 + el.elapsed_h * 3600;
maxEl = time;
if (el.speed !== undefined) {
speed.push({x: time, y: el.speed});
if (speed_max < el.speed) speed_max = el.speed;
}
if (el.target_speed !== undefined && el.target_speed !== -1) {
targetSpeed.push({x: time, y: el.target_speed});
if (speed_max < el.target_speed) speed_max = el.target_speed;
}
if (el.inclination !== undefined) {
inclination.push({x: time, y: el.inclination});
if (incline_max < el.inclination) incline_max = el.inclination;
}
if (el.target_inclination !== undefined && el.target_inclination !== -200) {
targetInclination.push({x: time, y: el.target_inclination});
if (incline_max < el.target_inclination) incline_max = el.target_inclination;
}
}
speed_max = Math.ceil(speed_max * 1.1);
incline_max = Math.ceil(incline_max * 1.1);
const backgroundFill = {
id: 'custom_canvas_background_color',
beforeDraw: (chart) => {
const ctx = chart.canvas.getContext('2d');
ctx.save();
ctx.globalCompositeOperation = 'destination-over';
ctx.fillStyle = 'white';
ctx.fillRect(0, 0, chart.width, chart.height);
ctx.restore();
}
};
let config = {
type: 'line',
plugins: [backgroundFill],
data: {
datasets: [{
label: 'Speed',
backgroundColor: window.chartColors.red,
borderColor: window.chartColors.red,
data: speed,
fill: false,
pointRadius: 0,
borderWidth: 2,
yAxisID: 'y-speed',
segment: {
borderColor: ctx => {
const y = ctx.p0.parsed.y;
if (y < speedZones[0]) return window.chartColors.grey;
if (y < speedZones[1]) return window.chartColors.limegreen;
if (y < speedZones[2]) return window.chartColors.gold;
if (y < speedZones[3]) return window.chartColors.orange;
if (y < speedZones[4]) return window.chartColors.darkorange;
if (y < speedZones[5]) return window.chartColors.orangered;
return window.chartColors.red;
}
}
}, {
label: 'Target Speed',
backgroundColor: window.chartColors.black,
borderColor: window.chartColors.black,
data: targetSpeed,
fill: false,
pointRadius: 0,
borderWidth: 2,
yAxisID: 'y-speed',
borderDash: [5, 5]
}, {
label: 'Incline',
backgroundColor: window.chartColors.orange,
borderColor: window.chartColors.orange,
data: inclination,
fill: false,
pointRadius: 0,
borderWidth: 2,
yAxisID: 'y-incline'
}, {
label: 'Target Incline',
backgroundColor: window.chartColors.grey,
borderColor: window.chartColors.grey,
data: targetInclination,
fill: false,
pointRadius: 0,
borderWidth: 2,
yAxisID: 'y-incline',
borderDash: [5, 5]
}]
},
options: {
responsive: true,
aspectRatio: div.width / div.height,
interaction: {
mode: 'index',
intersect: false,
},
plugins: {
tooltip: {
enabled: true,
},
legend: {
display: false
},
annotation: {
annotations: {
box1: {
type: 'box',
xMin: 0,
yMin: 0,
yMax: speedZones[0],
backgroundColor: "#d6d6d620",
yScaleID: 'y-speed',
},
box2: {
type: 'box',
xMin: 0,
yMin: speedZones[0],
yMax: speedZones[1],
backgroundColor: window.chartColors.limegreent,
yScaleID: 'y-speed',
},
box3: {
type: 'box',
xMin: 0,
yMin: speedZones[1],
yMax: speedZones[2],
backgroundColor: window.chartColors.goldt,
yScaleID: 'y-speed',
},
box4: {
type: 'box',
xMin: 0,
yMin: speedZones[2],
yMax: speedZones[3],
backgroundColor: window.chartColors.oranget,
yScaleID: 'y-speed',
},
box5: {
type: 'box',
xMin: 0,
yMin: speedZones[3],
yMax: speedZones[4],
backgroundColor: window.chartColors.darkoranget,
yScaleID: 'y-speed',
},
box6: {
type: 'box',
xMin: 0,
yMin: speedZones[4],
yMax: speedZones[5],
backgroundColor: window.chartColors.orangeredt,
yScaleID: 'y-speed',
},
box7: {
type: 'box',
xMin: 0,
yMin: speedZones[5],
yMax: speed_max,
backgroundColor: window.chartColors.redt,
yScaleID: 'y-speed',
}
}
}
},
scales: {
x: {
type: 'linear',
display: true,
title: {
display: false
},
ticks: {
callback: function(value) {
return value !== 0 ?
Math.floor(value / 3600).toString().padStart(2, "0") + ":" +
Math.floor((value / 60) - (Math.floor(value / 3600) * 60)).toString().padStart(2, "0") :
"";
},
padding: -20,
align: "end",
},
},
'y-speed': {
type: 'linear',
display: true,
position: 'left',
title: {
display: false
},
min: 0,
max: speed_max,
ticks: {
stepSize: 1,
autoSkip: false,
callback: value => speedZones.includes(value) ?
'Speed z' + (speedZones.indexOf(value) + 1) : undefined,
color: 'black',
padding: -70,
align: 'end',
}
},
'y-incline': {
type: 'linear',
display: true,
position: 'right',
title: {
display: false
},
min: 0,
max: incline_max,
grid: {
drawOnChartArea: false,
}
}
}
}
};
treadmillChart = new Chart(ctx, config);
}
function refresh() {
el = new MainWSQueueElement({
msg: null
}, function(msg) {
if (msg.msg === 'workout') {
return msg.content;
}
return null;
}, 2000, 1);
el.enqueue().then(process_workout).catch(function(err) {
console.error('Error is ' + err);
refresh();
});
}
function process_workout(arr) {
// Update speed data
treadmillChart.data.datasets[0].data.push({
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
y: arr.speed
});
if (speed_max < arr.speed) {
speed_max = Math.ceil(arr.speed * 1.1);
treadmillChart.options.scales['y-speed'].max = speed_max;
}
// Update target speed
if (arr.target_speed !== undefined && arr.target_speed !== -1) {
treadmillChart.data.datasets[1].data.push({
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
y: arr.target_speed
});
if (speed_max < arr.target_speed) {
speed_max = Math.ceil(arr.target_speed * 1.1);
treadmillChart.options.scales['y-speed'].max = speed_max;
}
}
// Update inclination data
treadmillChart.data.datasets[2].data.push({
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
y: arr.inclination
});
if (incline_max < arr.inclination) {
incline_max = Math.ceil(arr.inclination * 1.1);
treadmillChart.options.scales['y-incline'].max = incline_max;
}
// Update target inclination
if (arr.target_inclination !== undefined && arr.target_inclination !== -200) {
treadmillChart.data.datasets[3].data.push({
x: arr.elapsed_s + (arr.elapsed_m * 60) + (arr.elapsed_h * 3600),
y: arr.target_inclination
});
if (incline_max < arr.target_inclination) {
incline_max = Math.ceil(arr.target_inclination * 1.1);
treadmillChart.options.scales['y-incline'].max = incline_max;
}
}
treadmillChart.update();
refresh();
}
function dochart_init() {
el = new MainWSQueueElement({
msg: 'getsessionarray'
}, function(msg) {
if (msg.msg === 'R_getsessionarray') {
return msg.content;
}
return null;
}, 15000, 3);
el.enqueue().then(process_arr).catch(function(err) {
console.error('Error is ' + err);
});
}
$(window).on('load', function() {
dochart_init();
/*
let testData = [
{'speed': 5, 'target_speed': 5, 'inclination': 1, 'target_inclination': 1, 'elapsed_s': 0, 'elapsed_m': 0, 'elapsed_h': 0},
{'speed': 8, 'target_speed': 3, 'inclination': 2, 'target_inclination': 2, 'elapsed_s': 0, 'elapsed_m': 5, 'elapsed_h': 0},
{'speed': 10, 'target_speed': 5, 'inclination': 4, 'target_inclination': 4, 'elapsed_s': 0, 'elapsed_m': 10, 'elapsed_h': 0},
{'speed': 12, 'target_speed': 7, 'inclination': 6, 'target_inclination': 8, 'elapsed_s': 0, 'elapsed_m': 15, 'elapsed_h': 0},
{'speed': 14, 'target_speed': 14, 'inclination': 8, 'target_inclination': 10, 'elapsed_s': 0, 'elapsed_m': 20, 'elapsed_h': 0},
{'speed': 16, 'target_speed': 16, 'inclination': 10, 'target_inclination': 12, 'elapsed_s': 0, 'elapsed_m': 25, 'elapsed_h': 0},
{'speed': 8, 'target_speed': 8, 'inclination': 2, 'target_inclination': 4, 'elapsed_s': 0, 'elapsed_m': 30, 'elapsed_h': 0}
];
process_arr(testData);
*/
});
$(document).ready(function () {
$('#loading').hide();
});

View File

@@ -0,0 +1,53 @@
<!doctype html>
<html>
<head>
<title>Treadmill Chart</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,height=device-height,initial-scale=1">
<script src="resize-observer.min.js"></script>
<script src="jquery-3.6.0.min.js"></script>
<script src="chartjs.3.4.1.min.js"></script>
<script src="moment.js"></script>
<script src="chartjs-adapter-moment.js"></script>
<script src="chartjs-plugin-annotation.min.js"></script>
<script src="globals.js"></script>
<script src="main_ws_manager.js"></script>
<script src="dotreadmillchartlive.js"></script>
<script src="dochartliveheart.js"></script>
<script src="html2canvas.min.js"></script>
<style>
canvas {
-moz-user-select: none;
-webkit-user-select: none;
-ms-user-select: none;
}
html, body {
-ms-content-zooming: none;
touch-action: none;
content-zooming: none;
overflow-y: hidden;
overflow-x: hidden;
overflow-y: none;
overflow-x: none;
margin: 0px;
}
</style>
</head>
<body style="background-color:#1d2330">
<table style="border-spacing: 0px">
<tr>
<td>
<div id="divcanvas" style="width:50vw;height:100vh; background-color:white; border: 0px solid #aaa; overflow: hidden;">
<canvas id="canvas"></canvas>
</div>
</td>
<td>
<div id="divcanvasheart" style="width:50vw;height:100vh; background-color:white; border: 0px solid #aaa; overflow: hidden;">
<canvas id="canvasheart"></canvas>
</div>
</td>
</tr>
</table>
</body>
</html>

View File

@@ -41,7 +41,7 @@ class lockscreen {
int virtualrower_getLastFTMSMessage(unsigned char *message);
// virtualtreadmill
void virtualtreadmill_zwift_ios();
void virtualtreadmill_zwift_ios(bool garmin_bluetooth_compatibility);
void virtualtreadmill_setHeartRate(unsigned char heartRate);
double virtualtreadmill_getCurrentSlope();
uint64_t virtualtreadmill_lastChangeCurrentSlope();

View File

@@ -169,9 +169,9 @@ void lockscreen::virtualrower_setHeartRate(unsigned char heartRate)
// virtual treadmill
void lockscreen::virtualtreadmill_zwift_ios()
void lockscreen::virtualtreadmill_zwift_ios(bool garmin_bluetooth_compatibility)
{
_virtualtreadmill_zwift = [[virtualtreadmill_zwift alloc] init];
_virtualtreadmill_zwift = [[virtualtreadmill_zwift alloc] initWithGarmin_bluetooth_compatibility:garmin_bluetooth_compatibility];
}
void lockscreen::virtualtreadmill_setHeartRate(unsigned char heartRate)

View File

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

View File

@@ -11,9 +11,9 @@ let treadmilldataUuid = CBUUID(string: "0x2ACD");
@objc public class virtualtreadmill_zwift: NSObject {
private var peripheralManager: BLEPeripheralManagerTreadmillZwift!
@objc public override init() {
@objc public init(garmin_bluetooth_compatibility: Bool) {
super.init()
peripheralManager = BLEPeripheralManagerTreadmillZwift()
peripheralManager = BLEPeripheralManagerTreadmillZwift(garmin_bluetooth_compatibility: garmin_bluetooth_compatibility)
}
@objc public func updateHeartRate(HeartRate: UInt8)
@@ -50,6 +50,7 @@ let treadmilldataUuid = CBUUID(string: "0x2ACD");
}
class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate {
private var garmin_bluetooth_compatibility: Bool = false
private var peripheralManager: CBPeripheralManager!
private var heartRateService: CBMutableService!
@@ -87,8 +88,9 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
private var notificationTimer: Timer! = nil
//var delegate: BLEPeripheralManagerDelegate?
override init() {
init(garmin_bluetooth_compatibility: Bool) {
super.init()
self.garmin_bluetooth_compatibility = garmin_bluetooth_compatibility
peripheralManager = CBPeripheralManager(delegate: self, queue: nil)
}
@@ -97,78 +99,79 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
case .poweredOn:
print("Peripheral manager is up and running")
self.heartRateService = CBMutableService(type: heartRateServiceUUID, primary: true)
let characteristicProperties: CBCharacteristicProperties = [.notify, .read, .write]
let characteristicPermissions: CBAttributePermissions = [.readable]
self.heartRateCharacteristic = CBMutableCharacteristic(type: heartRateCharacteristicUUID,
properties: characteristicProperties,
value: nil,
permissions: characteristicPermissions)
heartRateService.characteristics = [heartRateCharacteristic]
self.peripheralManager.add(heartRateService)
self.FitnessMachineService = CBMutableService(type: FitnessMachineServiceUuid, primary: true)
let FitnessMachineFeatureProperties: CBCharacteristicProperties = [.read]
let FitnessMachineFeaturePermissions: CBAttributePermissions = [.readable]
self.FitnessMachineFeatureCharacteristic = CBMutableCharacteristic(type: FitnessMachineFeatureCharacteristicUuid,
properties: FitnessMachineFeatureProperties,
value: Data (bytes: [0x83, 0x14, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x00]),
permissions: FitnessMachineFeaturePermissions)
let supported_resistance_level_rangeProperties: CBCharacteristicProperties = [.read]
let supported_resistance_level_rangePermissions: CBAttributePermissions = [.readable]
self.supported_resistance_level_rangeCharacteristic = CBMutableCharacteristic(type: supported_resistance_level_rangeCharacteristicUuid,
properties: supported_resistance_level_rangeProperties,
value: Data (bytes: [0x0A, 0x00, 0x96, 0x00, 0x0A, 0x00]),
permissions: supported_resistance_level_rangePermissions)
let FitnessMachineControlPointProperties: CBCharacteristicProperties = [.indicate, .write]
let FitnessMachineControlPointPermissions: CBAttributePermissions = [.writeable]
self.FitnessMachineControlPointCharacteristic = CBMutableCharacteristic(type: FitnessMachineControlPointUuid,
properties: FitnessMachineControlPointProperties,
value: nil,
permissions: FitnessMachineControlPointPermissions)
let indoorbikeProperties: CBCharacteristicProperties = [.notify, .read]
let indoorbikePermissions: CBAttributePermissions = [.readable]
self.indoorbikeCharacteristic = CBMutableCharacteristic(type: indoorbikeUuid,
properties: indoorbikeProperties,
value: nil,
permissions: indoorbikePermissions)
let treadmilldataProperties: CBCharacteristicProperties = [.notify, .read]
let treadmilldataPermissions: CBAttributePermissions = [.readable]
self.treadmilldataCharacteristic = CBMutableCharacteristic(type: treadmilldataUuid,
properties: treadmilldataProperties,
value: nil,
permissions: treadmilldataPermissions)
let FitnessMachinestatusProperties: CBCharacteristicProperties = [.notify]
let FitnessMachinestatusPermissions: CBAttributePermissions = [.readable]
self.FitnessMachinestatusCharacteristic = CBMutableCharacteristic(type: FitnessMachinestatusUuid,
properties: FitnessMachinestatusProperties,
value: nil,
permissions: FitnessMachinestatusPermissions)
let TrainingStatusProperties: CBCharacteristicProperties = [.read]
let TrainingStatusPermissions: CBAttributePermissions = [.readable]
self.TrainingStatusCharacteristic = CBMutableCharacteristic(type: TrainingStatusUuid,
properties: TrainingStatusProperties,
value: Data (bytes: [0x00, 0x01]),
permissions: TrainingStatusPermissions)
FitnessMachineService.characteristics = [FitnessMachineFeatureCharacteristic,
supported_resistance_level_rangeCharacteristic,
FitnessMachineControlPointCharacteristic,
indoorbikeCharacteristic,
treadmilldataCharacteristic,
FitnessMachinestatusCharacteristic,
TrainingStatusCharacteristic ]
self.peripheralManager.add(FitnessMachineService)
if(!garmin_bluetooth_compatibility) {
self.heartRateService = CBMutableService(type: heartRateServiceUUID, primary: true)
let characteristicProperties: CBCharacteristicProperties = [.notify, .read, .write]
let characteristicPermissions: CBAttributePermissions = [.readable]
self.heartRateCharacteristic = CBMutableCharacteristic(type: heartRateCharacteristicUUID,
properties: characteristicProperties,
value: nil,
permissions: characteristicPermissions)
heartRateService.characteristics = [heartRateCharacteristic]
self.peripheralManager.add(heartRateService)
self.FitnessMachineService = CBMutableService(type: FitnessMachineServiceUuid, primary: true)
let FitnessMachineFeatureProperties: CBCharacteristicProperties = [.read]
let FitnessMachineFeaturePermissions: CBAttributePermissions = [.readable]
self.FitnessMachineFeatureCharacteristic = CBMutableCharacteristic(type: FitnessMachineFeatureCharacteristicUuid,
properties: FitnessMachineFeatureProperties,
value: Data (bytes: [0x83, 0x14, 0x00, 0x00, 0x0c, 0xe0, 0x00, 0x00]),
permissions: FitnessMachineFeaturePermissions)
let supported_resistance_level_rangeProperties: CBCharacteristicProperties = [.read]
let supported_resistance_level_rangePermissions: CBAttributePermissions = [.readable]
self.supported_resistance_level_rangeCharacteristic = CBMutableCharacteristic(type: supported_resistance_level_rangeCharacteristicUuid,
properties: supported_resistance_level_rangeProperties,
value: Data (bytes: [0x0A, 0x00, 0x96, 0x00, 0x0A, 0x00]),
permissions: supported_resistance_level_rangePermissions)
let FitnessMachineControlPointProperties: CBCharacteristicProperties = [.indicate, .write]
let FitnessMachineControlPointPermissions: CBAttributePermissions = [.writeable]
self.FitnessMachineControlPointCharacteristic = CBMutableCharacteristic(type: FitnessMachineControlPointUuid,
properties: FitnessMachineControlPointProperties,
value: nil,
permissions: FitnessMachineControlPointPermissions)
let indoorbikeProperties: CBCharacteristicProperties = [.notify, .read]
let indoorbikePermissions: CBAttributePermissions = [.readable]
self.indoorbikeCharacteristic = CBMutableCharacteristic(type: indoorbikeUuid,
properties: indoorbikeProperties,
value: nil,
permissions: indoorbikePermissions)
let treadmilldataProperties: CBCharacteristicProperties = [.notify, .read]
let treadmilldataPermissions: CBAttributePermissions = [.readable]
self.treadmilldataCharacteristic = CBMutableCharacteristic(type: treadmilldataUuid,
properties: treadmilldataProperties,
value: nil,
permissions: treadmilldataPermissions)
let FitnessMachinestatusProperties: CBCharacteristicProperties = [.notify]
let FitnessMachinestatusPermissions: CBAttributePermissions = [.readable]
self.FitnessMachinestatusCharacteristic = CBMutableCharacteristic(type: FitnessMachinestatusUuid,
properties: FitnessMachinestatusProperties,
value: nil,
permissions: FitnessMachinestatusPermissions)
let TrainingStatusProperties: CBCharacteristicProperties = [.read]
let TrainingStatusPermissions: CBAttributePermissions = [.readable]
self.TrainingStatusCharacteristic = CBMutableCharacteristic(type: TrainingStatusUuid,
properties: TrainingStatusProperties,
value: Data (bytes: [0x00, 0x01]),
permissions: TrainingStatusPermissions)
FitnessMachineService.characteristics = [FitnessMachineFeatureCharacteristic,
supported_resistance_level_rangeCharacteristic,
FitnessMachineControlPointCharacteristic,
indoorbikeCharacteristic,
treadmilldataCharacteristic,
FitnessMachinestatusCharacteristic,
TrainingStatusCharacteristic ]
self.peripheralManager.add(FitnessMachineService)
}
self.rscService = CBMutableService(type: RSCServiceUuid, primary: true)
@@ -215,9 +218,16 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
return
}
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [heartRateServiceUUID, FitnessMachineServiceUuid, RSCServiceUuid]] as [String : Any]
peripheralManager.startAdvertising(advertisementData)
if(!garmin_bluetooth_compatibility) {
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [heartRateServiceUUID, FitnessMachineServiceUuid, RSCServiceUuid]] as [String : Any]
peripheralManager.startAdvertising(advertisementData)
} else {
let advertisementData = [CBAdvertisementDataLocalNameKey: "QZ",
CBAdvertisementDataServiceUUIDsKey: [RSCServiceUuid]] as [String : Any]
peripheralManager.startAdvertising(advertisementData)
}
print("Successfully added service")
}
@@ -361,21 +371,21 @@ class BLEPeripheralManagerTreadmillZwift: NSObject, CBPeripheralManagerDelegate
let treadmillData = self.calculateTreadmillData()
let rscMeasurementData = self.calculateRSCMeasurement()
if(self.serviceToggle == 0)
if(self.serviceToggle == 0 && !garmin_bluetooth_compatibility)
{
let ok = self.peripheralManager.updateValue(heartRateData, for: self.heartRateCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = 1;
}
}
else if(self.serviceToggle == 1)
else if(self.serviceToggle == 1 && !garmin_bluetooth_compatibility)
{
let ok = self.peripheralManager.updateValue(treadmillData, for: self.treadmilldataCharacteristic, onSubscribedCentrals: nil)
if(ok) {
self.serviceToggle = 2;
}
}
else if(self.serviceToggle == 2)
else if(self.serviceToggle == 2 || garmin_bluetooth_compatibility)
{
let ok = self.peripheralManager.updateValue(rscMeasurementData, for: self.rscMeasurementCharacteristic, onSubscribedCentrals: nil)
if(ok) {

View File

@@ -41,6 +41,8 @@
#include "ios/lockscreen.h"
#endif
#include "osc.h"
#include "handleurl.h"
bool logs = true;
@@ -68,6 +70,8 @@ bool battery_service = false;
bool service_changed = false;
bool bike_wheel_revs = false;
bool run_cadence_sensor = false;
bool horizon_treadmill_7_8 = false;
bool horizon_treadmill_force_ftms = false;
bool nordictrack_10_treadmill = false;
bool reebok_fr30_treadmill = false;
bool zwift_play = false;
@@ -140,6 +144,10 @@ QCoreApplication *createApplication(int &argc, char *argv[]) {
bike_wheel_revs = true;
if (!qstrcmp(argv[i], "-run-cadence-sensor"))
run_cadence_sensor = true;
if (!qstrcmp(argv[i], "-horizon-treadmill-7-8"))
horizon_treadmill_7_8 = true;
if (!qstrcmp(argv[i], "-horizon-treadmill-force-ftms"))
horizon_treadmill_force_ftms = true;
if (!qstrcmp(argv[i], "-nordictrack-10-treadmill"))
nordictrack_10_treadmill = true;
if (!qstrcmp(argv[i], "-reebok_fr30_treadmill"))
@@ -404,6 +412,8 @@ int main(int argc, char *argv[]) {
settings.setValue(QZSettings::service_changed, service_changed);
settings.setValue(QZSettings::bike_wheel_revs, bike_wheel_revs);
settings.setValue(QZSettings::run_cadence_sensor, run_cadence_sensor);
settings.setValue(QZSettings::horizon_treadmill_7_8, horizon_treadmill_7_8);
settings.setValue(QZSettings::horizon_treadmill_force_ftms, horizon_treadmill_force_ftms);
settings.setValue(QZSettings::nordictrack_10_treadmill, nordictrack_10_treadmill);
settings.setValue(QZSettings::reebok_fr30_treadmill, reebok_fr30_treadmill);
settings.setValue(QZSettings::zwift_click, zwift_click);
@@ -598,6 +608,11 @@ int main(int argc, char *argv[]) {
MQTTPublisher* mqtt = new MQTTPublisher(mqtt_host, mqtt_port, mqtt_username, mqtt_password, &bl);
}
QString OSC_ip = settings.value(QZSettings::OSC_ip, QZSettings::default_OSC_ip).toString();
if(OSC_ip.length() > 0) {
OSC* osc = new OSC(&bl);
}
#ifdef Q_OS_IOS
#ifndef IO_UNDER_QT
lockscreen h;

View File

@@ -777,7 +777,7 @@ ApplicationWindow {
}
ItemDelegate {
text: "version 2.18.9"
text: "version 2.18.17"
width: parent.width
}

247
src/osc.cpp Normal file
View File

@@ -0,0 +1,247 @@
#include "osc.h"
OSC::OSC(bluetooth* manager, QObject *parent)
: QObject{parent}
{
bluetoothManager = manager;
// Setup timer for periodic publishing
m_timer = new QTimer();
m_timer->setInterval(1000);
connect(m_timer, &QTimer::timeout, this, &OSC::publishWorkoutData);
OSC_recvSocket->bind(9001);
m_timer->start();
}
void OSC::publishWorkoutData() {
if(!bluetoothManager->device()) return;
QSettings settings;
QString OSC_ip = settings.value(QZSettings::OSC_ip, QZSettings::default_OSC_ip).toString();
int OSC_port = settings.value(QZSettings::OSC_port, QZSettings::default_OSC_port).toInt();
QByteArray osc_read = OSC_recvSocket->readAll();
if(!osc_read.isEmpty()) {
OSC_handlePacket(OSCPP::Server::Packet(osc_read.data(), osc_read.length()));
}
char osc_buffer[3000];
int osc_len = OSC_makePacket(osc_buffer, sizeof(osc_buffer));
int osc_ret_len = OSC_sendSocket->writeDatagram(osc_buffer, osc_len, QHostAddress(OSC_ip), OSC_port);
qDebug() << "OSC >> " << osc_ret_len << QByteArray::fromRawData(osc_buffer, osc_len).toHex(' ');
}
size_t OSC::OSC_makePacket(void* buffer, size_t size)
{
// Construct a packet
OSCPP::Client::Packet packet(buffer, size);
packet
// Open a bundle with a timetag
.openBundle(1234ULL)
// Add a message with two arguments and an array with 6 elements;
// for efficiency this needs to be known in advance.
.openMessage("/QZ/Resistance", 1)
.int32(bluetoothManager->device()->currentResistance().value())
.closeMessage()
.openMessage("/QZ/Heart", 1)
.int32(bluetoothManager->device()->currentHeart().value())
.closeMessage()
.openMessage("/QZ/Speed", 1)
.float32(bluetoothManager->device()->currentSpeed().value())
.closeMessage()
.openMessage("/QZ/Pace", 1)
.string(bluetoothManager->device()->currentPace().toString(QStringLiteral("m:ss")).toLatin1())
.closeMessage()
.openMessage("/QZ/Inclination", 1)
.float32(bluetoothManager->device()->currentInclination().value())
.closeMessage()
.openMessage("/QZ/AveragePace", 1)
.string(bluetoothManager->device()->averagePace().toString(QStringLiteral("m:ss")).toLatin1())
.closeMessage()
.openMessage("/QZ/MaxPace", 1)
.string(bluetoothManager->device()->maxPace().toString(QStringLiteral("m:ss")).toLatin1())
.closeMessage()
.openMessage("/QZ/Odometer", 1)
.float32(bluetoothManager->device()->odometer())
.closeMessage()
.openMessage("/QZ/OdometerFromStartup", 1)
.float32(bluetoothManager->device()->odometerFromStartup())
.closeMessage()
.openMessage("/QZ/Distance", 1)
.float32(bluetoothManager->device()->currentDistance().value())
.closeMessage()
.openMessage("/QZ/Distance1s", 1)
.float32(bluetoothManager->device()->currentDistance1s().value())
.closeMessage()
.openMessage("/QZ/Calories", 1)
.float32(bluetoothManager->device()->calories().value())
.closeMessage()
.openMessage("/QZ/Joules", 1)
.float32(bluetoothManager->device()->jouls().value())
.closeMessage()
.openMessage("/QZ/FanSpeed", 1)
.int32(bluetoothManager->device()->fanSpeed())
.closeMessage()
.openMessage("/QZ/ElapsedTime", 1)
.string(bluetoothManager->device()->elapsedTime().toString(QStringLiteral("m:ss")).toLatin1())
.closeMessage()
.openMessage("/QZ/MovingTime", 1)
.string(bluetoothManager->device()->movingTime().toString(QStringLiteral("m:ss")).toLatin1())
.closeMessage()
.openMessage("/QZ/LapElapsedTime", 1)
.string(bluetoothManager->device()->lapElapsedTime().toString(QStringLiteral("m:ss")).toLatin1())
.closeMessage()
.openMessage("/QZ/Connected", 1)
.int32(bluetoothManager->device()->connected())
.closeMessage()
.openMessage("/QZ/Resistance", 1)
.int32(bluetoothManager->device()->currentResistance().value())
.closeMessage()
.openMessage("/QZ/Cadence", 1)
.float32(bluetoothManager->device()->currentCadence().value())
.closeMessage()
.openMessage("/QZ/CrankRevolutions", 1)
.float32(bluetoothManager->device()->currentCrankRevolutions())
.closeMessage()
.openMessage("/QZ/Coordinate", 2)
.float32(bluetoothManager->device()->currentCordinate().latitude())
.float32(bluetoothManager->device()->currentCordinate().longitude())
.closeMessage()
.openMessage("/QZ/Azimuth", 1)
.float32(bluetoothManager->device()->currentAzimuth())
.closeMessage()
.openMessage("/QZ/AverageAzimuthNext300m", 1)
.float32(bluetoothManager->device()->averageAzimuthNext300m())
.closeMessage()
.openMessage("/QZ/LastCrankEventTime", 1)
.int32(bluetoothManager->device()->lastCrankEventTime())
.closeMessage()
.openMessage("/QZ/Watts", 1)
.int32(bluetoothManager->device()->wattsMetric().value())
.closeMessage()
.openMessage("/QZ/ElevationGain", 1)
.float32(bluetoothManager->device()->elevationGain().value())
.closeMessage()
.openMessage("/QZ/Paused", 1)
.int32(bluetoothManager->device()->isPaused())
.closeMessage()
.openMessage("/QZ/AutoResistance", 1)
.int32(bluetoothManager->device()->autoResistance())
.closeMessage()
.openMessage("/QZ/Difficulty", 1)
.float32(bluetoothManager->device()->difficult())
.closeMessage()
.openMessage("/QZ/InclinationDifficulty", 1)
.float32(bluetoothManager->device()->inclinationDifficult())
.closeMessage()
.openMessage("/QZ/DifficultyOffset", 1)
.float32(bluetoothManager->device()->difficultOffset())
.closeMessage()
.openMessage("/QZ/InclinationDifficultyOffset", 1)
.float32(bluetoothManager->device()->inclinationDifficultOffset())
.closeMessage()
.openMessage("/QZ/WeightLoss", 1)
.float32(bluetoothManager->device()->weightLoss())
.closeMessage()
.openMessage("/QZ/WattKg", 1)
.float32(bluetoothManager->device()->wattKg().value())
.closeMessage()
.openMessage("/QZ/METS", 1)
.float32(bluetoothManager->device()->currentMETS().value())
.closeMessage()
.openMessage("/QZ/HeartZone", 1)
.int32(bluetoothManager->device()->currentHeartZone().value())
.closeMessage()
.openMessage("/QZ/MaxHeartZone", 1)
.int32(bluetoothManager->device()->maxHeartZone())
.closeMessage()
.openMessage("/QZ/PowerZone", 1)
.int32(bluetoothManager->device()->currentPowerZone().value())
.closeMessage()
.openMessage("/QZ/TargetPowerZone", 1)
.int32(bluetoothManager->device()->targetPowerZone().value())
.closeMessage()
.openMessage("/QZ/DeviceType", 1)
.int32(bluetoothManager->device()->deviceType())
.closeMessage()
.openMessage("/QZ/MaxResistance", 1)
.int32(bluetoothManager->device()->maxResistance())
.closeMessage()
.closeBundle();
return packet.size();
}
void OSC::OSC_handlePacket(const OSCPP::Server::Packet& packet)
{
if (packet.isBundle()) {
// Convert to bundle
OSCPP::Server::Bundle bundle(packet);
// Print the time
std::cout << "#bundle " << bundle.time() << std::endl;
// Get packet stream
OSCPP::Server::PacketStream packets(bundle.packets());
// Iterate over all the packets and call handlePacket recursively.
// Cuidado: Might lead to stack overflow!
while (!packets.atEnd()) {
OSC_handlePacket(packets.next());
}
} else {
// Convert to message
OSCPP::Server::Message msg(packet);
// Get argument stream
OSCPP::Server::ArgStream args(msg.args());
if (msg == "/QZ/Resistance") {
const float value = args.int32();
qDebug() << "OSC" << value;
if(bluetoothManager->device()->deviceType() == bluetoothdevice::BIKE)
((bike*)bluetoothManager->device())->changeResistance(value);
} else {
// Simply print unknown messages
std::cout << "Unknown message: " << msg << std::endl;
}
}
}

44
src/osc.h Normal file
View File

@@ -0,0 +1,44 @@
#ifndef OSC_H
#define OSC_H
#include <QObject>
#include <QTimer>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
#include <QHash>
#include <QVariant>
#include "bluetoothdevice.h"
#include "devices/bike.h"
#include "devices/treadmill.h"
#include "devices/rower.h"
#include "homeform.h"
#include "bluetooth.h"
#include <oscpp/client.hpp>
#include <oscpp/server.hpp>
#include <oscpp/print.hpp>
#include <iostream>
class OSC : public QObject
{
Q_OBJECT
public:
explicit OSC(bluetooth* manager, QObject *parent = nullptr);
private:
QTimer* m_timer;
bluetooth* bluetoothManager;
size_t OSC_makePacket(void* buffer, size_t size);
void OSC_handlePacket(const OSCPP::Server::Packet& packet);
QUdpSocket* OSC_sendSocket = new QUdpSocket(this);
QUdpSocket* OSC_recvSocket = new QUdpSocket(this);
private slots:
void publishWorkoutData();
signals:
};
#endif // OSC_H

368
src/oscpp/client.hpp Normal file
View File

@@ -0,0 +1,368 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_CLIENT_HPP_INCLUDED
#define OSCPP_CLIENT_HPP_INCLUDED
#include <oscpp/detail/host.hpp>
#include <oscpp/detail/stream.hpp>
#include <oscpp/util.hpp>
#include <cstdint>
#include <limits>
#include <sstream>
#include <stdexcept>
#include <type_traits>
namespace OSCPP { namespace Client {
//! OSC packet construction.
/*!
* Construct a valid OSC packet for transmitting over a transport
* medium.
*/
class Packet
{
int32_t ptrDiff(const char* a, const char* b)
{
// Make sure pointer difference fits into int32_t
const intptr_t diff = a - b;
if (diff < std::numeric_limits<int32_t>::min() ||
diff > std::numeric_limits<int32_t>::max())
{
std::stringstream s;
s << "Pointer difference " << diff
<< " can't be represented by int32_t";
throw std::logic_error(s.str());
}
return static_cast<int32_t>(diff);
}
int32_t calcSize(const char* begin, const char* end)
{
const int32_t size = ptrDiff(end, begin) - 4;
if (size < 0)
{
throw std::logic_error("Calculated size is negative");
}
return size;
}
public:
//! Constructor.
/*!
*/
Packet()
{
reset(0, 0);
}
//! Constructor.
/*!
*/
Packet(void* buffer, size_t size)
{
reset(buffer, size);
}
//! Destructor.
virtual ~Packet()
{}
//! Get packet buffer address.
/*!
* Return the start address of the packet currently under
* construction.
*/
void* data() const
{
return m_buffer;
}
size_t capacity() const
{
return m_capacity;
}
//! Get packet content size.
/*!
* Return the size of the packet currently under construction.
*/
size_t size() const
{
return m_args.consumed();
}
//! Reset packet state.
void reset(void* buffer, size_t size)
{
checkAlignment(&m_buffer, kAlignment);
m_buffer = buffer;
m_capacity = size;
m_args = WriteStream(m_buffer, m_capacity);
m_sizePosM = m_sizePosB = nullptr;
m_inBundle = 0;
}
void reset()
{
reset(m_buffer, m_capacity);
}
Packet& openBundle(uint64_t time)
{
if (m_inBundle > 0)
{
assert(m_sizePosB != nullptr || m_inBundle == 1);
// Remember previous size pos offset
const int32_t offset =
m_sizePosB == nullptr ? 0 : ptrDiff(m_sizePosB, m_args.begin());
char* curPos = m_args.pos();
m_args.skip(4);
// Record size pos
std::memcpy(curPos, &offset, 4);
m_sizePosB = curPos;
}
else if (m_args.pos() != m_args.begin())
{
throw std::logic_error(
"Cannot open toplevel bundle in non-empty packet");
}
m_inBundle++;
m_args.putString("#bundle");
m_args.putUInt64(time);
return *this;
}
Packet& closeBundle()
{
if (m_inBundle > 0)
{
if (m_inBundle > 1)
{
// Get current stream pos
char* curPos = m_args.pos();
// Get previous bundle size stream pos
int32_t offset;
memcpy(&offset, m_sizePosB, 4);
// Get previous size pos
char* prevPos = m_args.begin() + offset;
const int32_t bundleSize = calcSize(m_sizePosB, curPos);
assert(bundleSize >= 0 &&
(size_t)bundleSize >= Size::bundle(0));
// Write bundle size
m_args.setPos(m_sizePosB);
m_args.putInt32(bundleSize);
m_args.setPos(curPos);
// record outer bundle size pos
m_sizePosB = prevPos;
}
m_inBundle--;
}
else
{
throw std::logic_error(
"closeBundle() without matching openBundle()");
}
return *this;
}
Packet& openMessage(const char* addr, size_t numTags)
{
if (m_inBundle > 0)
{
// record message size pos
m_sizePosM = m_args.pos();
// advance arg stream
m_args.skip(4);
}
m_args.putString(addr);
size_t sigLen = numTags + 2;
m_tags = WriteStream(m_args, sigLen);
m_args.zero(align(sigLen));
m_tags.putChar(',');
return *this;
}
Packet& closeMessage()
{
if (m_inBundle > 0)
{
// Get current stream pos
char* curPos = m_args.pos();
// write message size
m_args.setPos(m_sizePosM);
m_args.putInt32(calcSize(m_sizePosM, curPos));
// restore stream pos
m_args.setPos(curPos);
// reset tag stream
m_tags = WriteStream();
}
return *this;
}
//! Write integer message argument.
/*!
* Write a 32 bit integer message argument.
*
* \param arg 32 bit integer argument.
*
* \pre openMessage must have been called before with no intervening
* closeMessage.
*
* \throw OSCPP::XRunError stream buffer xrun.
*/
Packet& int32(int32_t arg)
{
m_tags.putChar('i');
m_args.putInt32(arg);
return *this;
}
Packet& float32(float arg)
{
m_tags.putChar('f');
m_args.putFloat32(arg);
return *this;
}
Packet& string(const char* arg)
{
m_tags.putChar('s');
m_args.putString(arg);
return *this;
}
// @throw std::invalid_argument if blob size is greater than
// std::numeric_limits<int32_t>::max()
Packet& blob(const Blob& arg)
{
if (arg.size() > (size_t)std::numeric_limits<int32_t>::max())
{
throw std::invalid_argument("Blob size greater than maximum "
"value representable by int32_t");
}
m_tags.putChar('b');
m_args.putInt32(static_cast<int32_t>(arg.size()));
m_args.putData(arg.data(), arg.size());
return *this;
}
Packet& openArray()
{
m_tags.putChar('[');
return *this;
}
Packet& closeArray()
{
m_tags.putChar(']');
return *this;
}
template <typename T> Packet& put(T)
{
T::OSC_Client_Packet_put_unimplemented;
return *this;
}
template <typename InputIterator>
Packet& put(InputIterator begin, InputIterator end)
{
for (auto it = begin; it != end; it++)
{
put(*it);
}
return *this;
}
template <typename InputIterator>
Packet& putArray(InputIterator begin, InputIterator end)
{
openArray();
put<InputIterator>(begin, end);
closeArray();
return *this;
}
private:
void* m_buffer;
size_t m_capacity;
WriteStream m_args; // packet stream
WriteStream m_tags; // current tag stream
char* m_sizePosM; // last message size position
char* m_sizePosB; // last bundle size position
size_t m_inBundle; // bundle nesting depth
};
template <> inline Packet& Packet::put<int32_t>(int32_t x)
{
return int32(x);
}
template <> inline Packet& Packet::put<float>(float x)
{
return float32(x);
}
template <> inline Packet& Packet::put<const char*>(const char* x)
{
return string(x);
}
template <> inline Packet& Packet::put<Blob>(Blob x)
{
return blob(x);
}
template <size_t buffer_size> class StaticPacket : public Packet
{
public:
StaticPacket()
: Packet(reinterpret_cast<char*>(&m_buffer), buffer_size)
{}
private:
typedef typename std::aligned_storage<buffer_size, kAlignment>::type
AlignedBuffer;
AlignedBuffer m_buffer;
};
class DynamicPacket : public Packet
{
public:
DynamicPacket(size_t buffer_size)
: Packet(static_cast<char*>(new char[buffer_size]), buffer_size)
{}
~DynamicPacket()
{
delete[] static_cast<char*>(data());
}
};
}} // namespace OSCPP::Client
#endif // OSCPP_CLIENT_HPP_INCLUDED

View File

@@ -0,0 +1,82 @@
// Copyright 2005 Caleb Epstein
// Copyright 2006 John Maddock
// Copyright 2010 Rene Rivera
// Distributed under the Boost Software License, Version 1.0. (See accompany-
// ing file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
/*
* Copyright (c) 1997
* Silicon Graphics Computer Systems, Inc.
*
* Permission to use, copy, modify, distribute and sell this software
* and its documentation for any purpose is hereby granted without fee,
* provided that the above copyright notice appear in all copies and
* that both that copyright notice and this permission notice appear
* in supporting documentation. Silicon Graphics makes no
* representations about the suitability of this software for any
* purpose. It is provided "as is" without express or implied warranty.
*/
/*
* Copyright notice reproduced from <boost/detail/limits.hpp>, from
* which this code was originally taken.
*
* Modified by Caleb Epstein to use <endian.h> with GNU libc and to
* defined the BOOST_ENDIAN macro.
*/
/*
* Modifications for oscpp by Stefan Kersten
* - Change prefix from BOOST to OSCPP
* - Remove PDP endianness
* - Add OSCPP_BYTE_ORDER_* macros
*/
#ifndef OSCPP_ENDIAN_HPP_INCLUDED
#define OSCPP_ENDIAN_HPP_INCLUDED
#define OSCPP_BYTE_ORDER_BIG_ENDIAN 4321
#define OSCPP_BYTE_ORDER_LITTLE_ENDIAN 1234
// GNU libc offers the helpful header <endian.h> which defines
// __BYTE_ORDER
#if defined(__GLIBC__) || defined(__ANDROID__)
# include <endian.h>
# if (__BYTE_ORDER == __LITTLE_ENDIAN)
# define OSCPP_LITTLE_ENDIAN
# elif (__BYTE_ORDER == __BIG_ENDIAN)
# define OSCPP_BIG_ENDIAN
# else
# error Unknown machine endianness detected.
# endif
# define OSCPP_BYTE_ORDER __BYTE_ORDER
#elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) || \
defined(__BIG_ENDIAN__) && !defined(__LITTLE_ENDIAN__) || \
defined(_STLP_BIG_ENDIAN) && !defined(_STLP_LITTLE_ENDIAN)
# define OSCPP_BIG_ENDIAN
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_BIG_ENDIAN
#elif defined(_LITTLE_ENDIAN) && !defined(_BIG_ENDIAN) || \
defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) || \
defined(_STLP_LITTLE_ENDIAN) && !defined(_STLP_BIG_ENDIAN)
# define OSCPP_LITTLE_ENDIAN
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_LITTLE_ENDIAN
#elif defined(__sparc) || defined(__sparc__) || defined(_POWER) || \
defined(__powerpc__) || defined(__ppc__) || defined(__hpux) || \
defined(__hppa) || defined(_MIPSEB) || defined(_POWER) || \
defined(__s390__)
# define OSCPP_BIG_ENDIAN
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_BIG_ENDIAN
#elif defined(__i386__) || defined(__alpha__) || defined(__ia64) || \
defined(__ia64__) || defined(_M_IX86) || defined(_M_IA64) || \
defined(_M_ALPHA) || defined(__amd64) || defined(__amd64__) || \
defined(_M_AMD64) || defined(__x86_64) || defined(__x86_64__) || \
defined(_M_X64) || defined(__bfin__)
# define OSCPP_LITTLE_ENDIAN
# define OSCPP_BYTE_ORDER OSCPP_BYTE_ORDER_LITTLE_ENDIAN
#else
# error The file oscpp/endian.hpp needs to be set up for your CPU type.
#endif
#endif // OSCPP_ENDIAN_HPP_INCLUDED

118
src/oscpp/detail/host.hpp Normal file
View File

@@ -0,0 +1,118 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_HOST_HPP_INCLUDED
#define OSCPP_HOST_HPP_INCLUDED
#include <oscpp/detail/endian.hpp>
#include <cstdint>
#include <stdexcept>
namespace OSCPP {
#if defined(__GNUC__)
inline static uint32_t bswap32(uint32_t x)
{
return __builtin_bswap32(x);
}
inline static uint64_t bswap64(uint64_t x)
{
return __builtin_bswap64(x);
}
#elif defined(_WINDOWS_) || defined(_WIN32)
# include <stdlib.h>
inline static uint32_t bswap32(uint32_t x)
{
return _byteswap_ulong(x);
}
inline static uint64_t bswap64(uint64_t x)
{
return _byteswap_uint64(x);
}
#else
// Fallback implementation
# warning Using unoptimized byte swap functions
inline static uint32_t bswap32(uint32_t x)
{
const uint32_t b1 = x << 24;
const uint32_t b2 = (x & 0x0000FF00) << 8;
const uint32_t b3 = (x & 0x00FF0000) >> 8;
const uint32_t b4 = x >> 24;
return b1 | b2 | b3 | b4;
}
inline static uint64_t bswap64(int64_t x)
{
const uint64_t w1 = oscpp_bswap(uint32_t(x & 0x00000000FFFFFFFF)) << 32;
const uint64_t w2 = oscpp_bswap(uint32_t(x >> 32));
return w1 | w2;
}
#endif
enum ByteOrder
{
NetworkByteOrder,
HostByteOrder
};
template <ByteOrder B> inline uint32_t convert32(uint32_t)
{
throw std::logic_error("Invalid byte order");
}
template <> inline uint32_t convert32<NetworkByteOrder>(uint32_t x)
{
#if defined(OSCPP_LITTLE_ENDIAN)
return bswap32(x);
#else
return x;
#endif
}
template <> inline uint32_t convert32<HostByteOrder>(uint32_t x)
{
return x;
}
template <ByteOrder B> inline uint64_t convert64(uint64_t)
{
throw std::logic_error("Invalid byte order");
}
template <> inline uint64_t convert64<NetworkByteOrder>(uint64_t x)
{
#if defined(OSCPP_LITTLE_ENDIAN)
return bswap64(x);
#else
return x;
#endif
}
template <> inline uint64_t convert64<HostByteOrder>(uint64_t x)
{
return x;
}
} // namespace OSCPP
#endif // OSCPP_HOST_HPP_INCLUDED

379
src/oscpp/detail/stream.hpp Normal file
View File

@@ -0,0 +1,379 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_STREAM_HPP_INCLUDED
#define OSCPP_STREAM_HPP_INCLUDED
#include <oscpp/detail/host.hpp>
#include <oscpp/error.hpp>
#include <oscpp/types.hpp>
#include <oscpp/util.hpp>
#include <algorithm>
#include <cassert>
#include <cstdint>
#include <cstring>
namespace OSCPP {
class Stream
{
public:
Stream()
{
m_begin = m_end = m_pos = 0;
}
Stream(void* data, size_t size)
{
m_begin = static_cast<char*>(data);
m_end = m_begin + size;
m_pos = m_begin;
}
Stream(const Stream& stream)
{
m_begin = m_pos = stream.m_pos;
m_end = stream.m_end;
}
Stream(const Stream& stream, size_t size)
{
m_begin = m_pos = stream.m_pos;
m_end = m_begin + size;
if (m_end > stream.m_end)
throw UnderrunError();
}
void reset()
{
m_pos = m_begin;
}
const char* begin() const
{
return m_begin;
}
char* begin()
{
return m_begin;
}
const char* end() const
{
return m_end;
}
size_t capacity() const
{
return end() - begin();
}
const char* pos() const
{
return m_pos;
}
char* pos()
{
return m_pos;
}
void setPos(char* pos)
{
assert((pos >= m_begin) && (pos <= m_end));
m_pos = pos;
}
void advance(size_t n)
{
m_pos += n;
}
bool atEnd() const
{
return pos() == end();
}
size_t consumed() const
{
return pos() - begin();
}
size_t consumable() const
{
return end() - pos();
}
inline void checkAlignment(size_t n) const
{
OSCPP::checkAlignment(pos(), n);
}
protected:
char* m_begin;
char* m_end;
char* m_pos;
};
template <ByteOrder B> class BasicWriteStream : public Stream
{
public:
BasicWriteStream()
: Stream()
{}
BasicWriteStream(void* data, size_t size)
: Stream(data, size)
{}
BasicWriteStream(const BasicWriteStream& stream)
: Stream(stream)
{}
BasicWriteStream(const BasicWriteStream& stream, size_t size)
: Stream(stream, size)
{}
// throw (OverflowError)
inline void checkWritable(size_t n) const
{
if (consumable() < n)
throw OverflowError(n - consumable());
}
void skip(size_t n)
{
checkWritable(n);
advance(n);
}
void zero(size_t n)
{
checkWritable(n);
std::memset(m_pos, 0, n);
advance(n);
}
void putChar(char c)
{
checkWritable(1);
*pos() = c;
advance(1);
}
void putInt32(int32_t x)
{
checkWritable(4);
checkAlignment(4);
uint32_t uh;
memcpy(&uh, &x, 4);
const uint32_t un = convert32<B>(uh);
std::memcpy(pos(), &un, 4);
advance(4);
}
void putUInt64(uint64_t x)
{
checkWritable(8);
const uint64_t un = convert64<B>(x);
std::memcpy(pos(), &un, 8);
advance(8);
}
void putFloat32(float f)
{
checkWritable(4);
checkAlignment(4);
uint32_t uh;
std::memcpy(&uh, &f, 4);
const uint32_t un = convert32<B>(uh);
std::memcpy(pos(), &un, 4);
advance(4);
}
void putFloat64(double f)
{
checkWritable(8);
checkAlignment(4);
uint64_t uh;
std::memcpy(&uh, &f, 8);
const uint64_t un = convert64<B>(uh);
std::memcpy(pos(), &un, 8);
advance(8);
}
void putData(const void* data, size_t size)
{
const size_t padding = OSCPP::padding(size);
const size_t n = size + padding;
checkWritable(n);
std::memcpy(pos(), data, size);
std::memset(pos() + size, 0, padding);
advance(n);
}
void putString(const char* s)
{
putData(s, strlen(s) + 1);
}
};
typedef BasicWriteStream<NetworkByteOrder> WriteStream;
template <ByteOrder B> class BasicReadStream : public Stream
{
public:
BasicReadStream()
{}
BasicReadStream(const void* data, size_t size)
: Stream(const_cast<void*>(data), size)
{}
BasicReadStream(const BasicReadStream& stream)
: Stream(stream)
{}
BasicReadStream(const BasicReadStream& stream, size_t size)
: Stream(stream, size)
{}
// throw (UnderrunError)
void checkReadable(size_t n) const
{
if (consumable() < n)
throw UnderrunError();
}
// throw (UnderrunError)
void skip(size_t n)
{
checkReadable(n);
advance(n);
}
// throw (UnderrunError)
inline char peekChar() const
{
checkReadable(1);
return *pos();
}
// throw (UnderrunError)
inline char getChar()
{
const char x = peekChar();
advance(1);
return x;
}
// throw (UnderrunError)
inline int32_t peekInt32() const
{
checkReadable(4);
checkAlignment(4);
uint32_t un;
std::memcpy(&un, pos(), 4);
const uint32_t uh = convert32<B>(un);
int32_t x;
std::memcpy(&x, &uh, 4);
return x;
}
// throw (UnderrunError)
inline int32_t getInt32()
{
const int32_t x = peekInt32();
advance(4);
return x;
}
// throw (UnderrunError)
inline uint64_t getUInt64()
{
checkReadable(8);
uint64_t un;
std::memcpy(&un, pos(), 8);
advance(8);
return convert64<B>(un);
}
// throw (UnderrunError)
inline float getFloat32()
{
checkReadable(4);
checkAlignment(4);
uint32_t un;
std::memcpy(&un, pos(), 4);
advance(4);
const uint32_t uh = convert32<B>(un);
float f;
std::memcpy(&f, &uh, 4);
return f;
}
// throw (UnderrunError)
inline double getFloat64()
{
checkReadable(8);
checkAlignment(4);
uint64_t un;
std::memcpy(&un, pos(), 8);
advance(8);
const uint64_t uh = convert64<B>(un);
double f;
std::memcpy(&f, &uh, 8);
return f;
}
// throw (UnderrunError, ParseError)
const char* getString()
{
checkReadable(4); // min string length
const char* ptr = static_cast<const char*>(pos()) + 3;
const char* end = static_cast<const char*>(this->end());
while (true)
{
if (ptr >= end)
throw UnderrunError();
if (*ptr == '\0')
break;
ptr += 4;
}
const char* x = pos();
advance(ptr - pos() + 1);
return x;
}
};
typedef BasicReadStream<NetworkByteOrder> ReadStream;
} // namespace OSCPP
#endif // OSCPP_STREAM_HPP_INCLUDED

87
src/oscpp/error.hpp Normal file
View File

@@ -0,0 +1,87 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_ERROR_HPP_INCLUDED
#define OSCPP_ERROR_HPP_INCLUDED
#include <exception>
#include <string>
namespace OSCPP {
class Error : public std::exception
{
public:
Error(const std::string& what)
: m_what(what)
{}
virtual ~Error() noexcept
{}
const char* what() const noexcept override
{
return m_what.c_str();
}
private:
std::string m_what;
};
class UnderrunError : public Error
{
public:
UnderrunError()
: Error(std::string("Buffer underrun"))
{}
};
class OverflowError : public Error
{
public:
OverflowError(size_t bytes)
: Error(std::string("Buffer overflow"))
, m_bytes(bytes)
{}
size_t numBytes() const
{
return m_bytes;
}
private:
size_t m_bytes;
};
class ParseError : public Error
{
public:
ParseError(const std::string& what = "Parse error")
: Error(what)
{}
};
} // namespace OSCPP
#endif // OSCPP_ERROR_HPP_INCLUDED

183
src/oscpp/print.hpp Normal file
View File

@@ -0,0 +1,183 @@
// OSCpp library
//
// Copyright (c) 2004-2011 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_PRINT_HPP_INCLUDED
#define OSCPP_PRINT_HPP_INCLUDED
#include <oscpp/client.hpp>
#include <oscpp/server.hpp>
#include <ostream>
namespace OSCPP { namespace detail {
const size_t kDefaultIndentWidth = 4;
class Indent
{
public:
Indent(size_t w)
: m_width(w)
, m_indent(0)
{}
Indent(size_t w, size_t n)
: m_width(w)
, m_indent(n)
{}
Indent(const Indent&) = default;
operator size_t() const
{
return m_indent;
}
Indent inc() const
{
return Indent(m_width, m_indent + m_width);
}
private:
size_t m_width;
size_t m_indent;
};
inline std::ostream& operator<<(std::ostream& out, const Indent& indent)
{
size_t n = indent;
while (n-- > 0)
out << ' ';
return out;
}
inline void printArgs(std::ostream& out, Server::ArgStream args)
{
while (!args.atEnd())
{
const char t = args.tag();
switch (t)
{
case 'i':
out << "i:" << args.int32();
break;
case 'f':
out << "f:" << args.float32();
break;
case 's':
out << "s:" << args.string();
break;
case 'b':
out << "b:" << args.blob().size();
break;
case '[':
out << "[ ";
printArgs(out, args.array());
out << " ]";
break;
default:
out << t << ":?";
args.drop();
break;
}
out << ' ';
}
}
inline void printMessage(std::ostream& out, const Server::Message& msg,
const Indent& indent)
{
out << indent << msg.address() << ' ';
printArgs(out, msg.args());
}
inline void printBundle(std::ostream& out, const Server::Bundle& bundle,
const Indent& indent)
{
out << indent << "# " << bundle.time() << " [" << std::endl;
Indent nextIndent = indent.inc();
auto packets = bundle.packets();
while (!packets.atEnd())
{
auto packet = packets.next();
if (packet.isMessage())
{
printMessage(out, packet, nextIndent);
}
else
{
printBundle(out, packet, nextIndent);
}
out << std::endl;
}
out << indent << "]";
}
inline void printPacket(std::ostream& out, const Server::Packet& packet,
const Indent& indent)
{
if (packet.isMessage())
{
printMessage(out, packet, indent);
}
else
{
printBundle(out, packet, indent);
}
}
}} // namespace OSCPP::detail
namespace OSCPP { namespace Server {
inline std::ostream& operator<<(std::ostream& out, const Packet& packet)
{
detail::printPacket(out, packet,
detail::Indent(detail::kDefaultIndentWidth));
return out;
}
inline std::ostream& operator<<(std::ostream& out, const Bundle& packet)
{
detail::printBundle(out, packet,
detail::Indent(detail::kDefaultIndentWidth));
return out;
}
inline std::ostream& operator<<(std::ostream& out, const Message& packet)
{
detail::printMessage(out, packet,
detail::Indent(detail::kDefaultIndentWidth));
return out;
}
}} // namespace OSCPP::Server
namespace OSCPP { namespace Client {
inline std::ostream& operator<<(std::ostream& out, const Packet& packet)
{
return out << Server::Packet(packet.data(), packet.size());
}
}} // namespace OSCPP::Client
#endif // OSCPP_PRINT_HPP_INCLUDED

493
src/oscpp/server.hpp Normal file
View File

@@ -0,0 +1,493 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_SERVER_HPP_INCLUDED
#define OSCPP_SERVER_HPP_INCLUDED
#include <oscpp/detail/stream.hpp>
#include <oscpp/util.hpp>
#include <algorithm>
#include <cstdint>
#include <cstring>
#include <tuple>
namespace OSCPP { namespace Server {
//! OSC Message Argument Iterator.
/*!
* Retrieve typed arguments from an incoming message.
*
* Supported tags and their correspondong types are:
*
* i -- 32 bit signed integer number<br>
* f -- 32 bit floating point number<br>
* s -- NULL-terminated string padded to 4-byte boundary<br>
* b -- 32-bit integer size followed by 4-byte aligned data
*
* \sa getArgInt32
* \sa getArgFloat32
* \sa getArgString
*/
class ArgStream
{
public:
//* Empty argument stream.
ArgStream() = default;
//* Construct argument stream from tag and value streams.
ArgStream(const ReadStream& tags, const ReadStream& args)
: m_tags(tags)
, m_args(args)
{}
//! Constructor.
/*!
* Read arguments from stream, which has to point to the start of a
* message type signature.
*
* \throw OSCPP::UnderrunError stream buffer underrun.
* \throw OSCPP::ParseError error while parsing input stream.
*/
ArgStream(const ReadStream& stream)
{
m_args = stream;
const char* tags = m_args.getString();
if (tags[0] != ',')
throw ParseError("Tag string doesn't start with ','");
m_tags = ReadStream(tags + 1, strlen(tags) - 1);
}
//* Return the number of arguments that can be read from the stream.
size_t size() const
{
return m_tags.capacity();
}
//* Return true if no more arguments can be read from the stream.
bool atEnd() const
{
return m_tags.atEnd();
}
//* Return tag and argument streams.
std::tuple<ReadStream, ReadStream> state() const
{
return std::make_tuple(m_tags, m_args);
}
//* Return the type tag corresponding to the next message argument.
char tag() const
{
return m_tags.peekChar();
}
//* Drop next argument.
void drop()
{
drop(m_tags.getChar());
}
//! Get next integer argument.
/*!
* Read next numerical argument from the input stream and convert it
* to an integer.
*
* \exception OSCPP::UnderrunError stream buffer underrun.
* \exception OSCPP::ParseError argument could not be converted.
*/
int32_t int32()
{
const char t = m_tags.getChar();
if (t == 'i')
return m_args.getInt32();
if (t == 'f')
return (int32_t)m_args.getFloat32();
throw ParseError("Cannot convert argument to int");
}
//! Get next float argument.
/*!
* Read next numerical argument from the input stream and convert it
* to a float.
*
* \exception OSCPP::UnderrunError stream buffer underrun.
* \exception OSCPP::ParseError argument could not be converted.
*/
float float32()
{
const char t = m_tags.getChar();
if (t == 'f')
return m_args.getFloat32();
if (t == 'i')
return (float)m_args.getInt32();
throw ParseError("Cannot convert argument to float");
}
//! Get next string argument.
/*!
* Read next string argument and return it as a NULL-terminated
* string.
*
* \exception OSCPP::UnderrunError stream buffer underrun.
* \exception OSCPP::ParseError argument could not be converted or
* is not a valid string.
*/
const char* string()
{
if (m_tags.getChar() == 's')
{
return m_args.getString();
}
throw ParseError("Cannot convert argument to string");
}
//* Get next blob argument.
//
// @throw OSCPP::UnderrunError stream buffer underrun.
// @throw OSCPP::ParseError argument is not a valid blob
Blob blob()
{
if (m_tags.getChar() == 'b')
{
return parseBlob();
}
else
{
throw ParseError("Cannot convert argument to blob");
}
}
//* Return a stream corresponding to an array argument.
ArgStream array()
{
if (m_tags.getChar() == '[')
{
const char* tags = m_tags.pos();
const char* args = m_args.pos();
dropArray();
// m_tags.pos() points right after the closing ']'.
return ArgStream(ReadStream(tags, m_tags.pos() - tags - 1),
ReadStream(args, m_args.pos() - args));
}
else
{
throw ParseError("Expected array");
}
}
template <typename T> T next()
{
return T::OSC_Server_ArgStream_next_unimplemented;
}
private:
// Parse a blob (type tag already consumed).
Blob parseBlob()
{
int32_t size = m_args.getInt32();
if (size < 0)
{
throw ParseError("Invalid blob size is less than zero");
}
else
{
static_assert(
sizeof(size_t) >= sizeof(int32_t),
"Size of size_t must be greater than size of int32_t");
const void* data = m_args.pos();
m_args.skip(align(size));
return Blob(data, static_cast<size_t>(size));
}
}
// Drop an atomic value of type t (type tag already consumed).
void dropAtom(char t)
{
switch (t)
{
case 'i':
m_args.skip(4);
break;
case 'f':
m_args.skip(4);
break;
case 's':
m_args.getString();
break;
case 'b':
parseBlob();
break;
}
}
// Drop a possibly nested array.
void dropArray()
{
unsigned int level = 0;
for (;;)
{
char t = m_tags.getChar();
if (t == ']')
{
if (level == 0)
break;
else
level--;
}
else if (t == '[')
{
level++;
}
else
{
dropAtom(t);
}
}
}
// Drop the next argument of type t (type tag already consumed).
void drop(char t)
{
switch (t)
{
case '[':
dropArray();
break;
default:
dropAtom(t);
}
}
private:
ReadStream m_tags;
ReadStream m_args;
};
class Message
{
public:
Message(const char* address, const ReadStream& stream)
: m_address(address)
, m_args(ArgStream(stream))
{}
const char* address() const
{
return m_address;
}
ArgStream args() const
{
return m_args;
}
private:
const char* m_address;
ArgStream m_args;
};
class PacketStream;
class Bundle
{
public:
Bundle(uint64_t time, const ReadStream& stream)
: m_time(time)
, m_stream(stream)
{}
uint64_t time() const
{
return m_time;
}
inline PacketStream packets() const;
private:
uint64_t m_time;
ReadStream m_stream;
};
class Packet
{
public:
Packet()
: m_isBundle(false)
{}
Packet(const ReadStream& stream)
: m_stream(stream)
, m_isBundle(isBundle(stream))
{
// Skip over #bundle header
if (m_isBundle)
m_stream.skip(8);
}
Packet(const void* data, size_t size)
: Packet(ReadStream(data, size))
{}
const void* data() const
{
return m_stream.begin();
}
size_t size() const
{
return m_stream.capacity();
}
bool isBundle() const
{
return m_isBundle;
}
bool isMessage() const
{
return !isBundle();
}
operator Bundle() const
{
if (!isBundle())
throw ParseError("Packet is not a bundle");
ReadStream stream(m_stream);
uint64_t time = stream.getUInt64();
return Bundle(time, std::move(stream));
}
operator Message() const
{
if (!isMessage())
throw ParseError("Packet is not a message");
ReadStream stream(m_stream);
const char* address = stream.getString();
return Message(address, std::move(stream));
}
static bool isMessage(const void* data, size_t size)
{
return (size > 3) && (static_cast<const char*>(data)[0] != '#');
}
static bool isMessage(const ReadStream& stream)
{
return isMessage(stream.pos(), stream.consumable());
}
static bool isBundle(const void* data, size_t size)
{
return (size > 15) && (std::memcmp(data, "#bundle", 8) == 0);
}
static bool isBundle(const ReadStream& stream)
{
return isBundle(stream.pos(), stream.consumable());
}
private:
ReadStream m_stream;
bool m_isBundle;
};
class PacketStream
{
public:
PacketStream(const ReadStream& stream)
: m_stream(stream)
{}
bool atEnd() const
{
return m_stream.atEnd();
}
Packet next()
{
size_t size = m_stream.getInt32();
ReadStream stream(m_stream, size);
m_stream.skip(size);
return Packet(stream);
}
private:
ReadStream m_stream;
};
template <> inline int32_t ArgStream::next<int32_t>()
{
return int32();
}
template <> inline float ArgStream::next<float>()
{
return float32();
}
template <> inline const char* ArgStream::next<const char*>()
{
return string();
}
template <> inline Blob ArgStream::next<Blob>()
{
return blob();
}
template <> inline ArgStream ArgStream::next<ArgStream>()
{
return array();
}
PacketStream Bundle::packets() const
{
return PacketStream(m_stream);
}
}} // namespace OSCPP::Server
static inline bool operator==(const OSCPP::Server::Message& msg,
const char* str)
{
return strcmp(msg.address(), str) == 0;
}
static inline bool operator==(const char* str,
const OSCPP::Server::Message& msg)
{
return msg == str;
}
static inline bool operator!=(const OSCPP::Server::Message& msg,
const char* str)
{
return !(msg == str);
}
static inline bool operator!=(const char* str,
const OSCPP::Server::Message& msg)
{
return msg != str;
}
#endif // OSCPP_SERVER_HPP_INCLUDED

59
src/oscpp/types.hpp Normal file
View File

@@ -0,0 +1,59 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_TYPES_HPP_INCLUDED
#define OSCPP_TYPES_HPP_INCLUDED
namespace OSCPP {
class Blob
{
public:
Blob()
: m_size(0)
, m_data(nullptr)
{}
Blob(const void* data, size_t size)
: m_size(size)
, m_data(data)
{}
Blob(const Blob& other) = default;
size_t size() const
{
return m_size;
}
const void* data() const
{
return m_data;
}
private:
size_t m_size;
const void* m_data;
};
} // namespace OSCPP
#endif // OSCPP_TYPES_HPP_INCLUDED

159
src/oscpp/util.hpp Normal file
View File

@@ -0,0 +1,159 @@
// oscpp library
//
// Copyright (c) 2004-2013 Stefan Kersten <sk@k-hornz.de>
//
// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:
//
// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
#ifndef OSCPP_UTIL_HPP_INCLUDED
#define OSCPP_UTIL_HPP_INCLUDED
#include <cassert>
#include <cstring>
namespace OSCPP {
static const size_t kAlignment = 4;
inline bool isAligned(const void* ptr, size_t alignment)
{
return (reinterpret_cast<uintptr_t>(ptr) & (alignment - 1)) == 0;
}
constexpr bool isAligned(size_t n)
{
return (n & 3) == 0;
}
constexpr size_t align(size_t n)
{
return (n + 3) & -4;
}
constexpr size_t padding(size_t n)
{
return align(n) - n;
}
inline void checkAlignment(const void* ptr, size_t n)
{
if (!isAligned(ptr, n))
{
throw std::runtime_error("Unaligned pointer");
}
}
namespace Tags {
constexpr size_t int32()
{
return 1;
}
constexpr size_t float32()
{
return 1;
}
constexpr size_t string()
{
return 1;
}
constexpr size_t blob()
{
return 1;
}
constexpr size_t array(size_t numElems)
{
return numElems + 2;
}
} // namespace Tags
namespace Size {
class String
{
public:
String(const char* x)
: m_value(x)
{}
operator const char*() const
{
return m_value;
}
private:
const char* m_value;
};
inline size_t string(const String& x)
{
return align(std::strlen(x) + 1);
}
template <size_t N> constexpr size_t string(char const (&)[N])
{
return align(N);
}
constexpr size_t bundle(size_t numPackets)
{
return 8 /* #bundle */ + 8 /* timestamp */ +
4 * numPackets /* size prefix */;
}
inline size_t message(const String& address, size_t numArgs)
{
return string(address) + align(numArgs + 2);
}
template <size_t N>
constexpr size_t message(char const (&address)[N], size_t numArgs)
{
return string(address) + align(numArgs + 2);
}
constexpr size_t int32(size_t n = 1)
{
return n * 4;
}
constexpr size_t float32(size_t n = 1)
{
return n * 4;
}
constexpr size_t float64(size_t n = 1)
{
return n * 8;
}
constexpr size_t string(size_t n)
{
return align(n + 1);
}
constexpr size_t blob(size_t size)
{
return 4 + align(size);
}
} // namespace Size
} // namespace OSCPP
#endif // OSCPP_UTIL_HPP_INCLUDED

View File

@@ -245,203 +245,339 @@ peloton::peloton(bluetooth *bl, QObject *parent) : QObject(parent) {
treadmill_pace[0].value = 0;
treadmill_pace[0].display_name = QStringLiteral("Recovery");
treadmill_pace[0].levels[0].speed = 4.83; // 3.0 mph
treadmill_pace[0].levels[1].speed = 5.15; // 3.2 mph
treadmill_pace[0].levels[2].speed = 5.63; // 3.5 mph
treadmill_pace[0].levels[3].speed = 5.95; // 3.7 mph
treadmill_pace[0].levels[4].speed = 6.60; // 4.1 mph
treadmill_pace[0].levels[5].speed = 7.24; // 4.5 mph
treadmill_pace[0].levels[6].speed = 8.05; // 5.0 mph
treadmill_pace[0].levels[7].speed = 9.17; // 5.7 mph
treadmill_pace[0].levels[8].speed = 10.46; // 6.5 mph
treadmill_pace[0].levels[9].speed = 12.23; // 7.6 mph
for (int i = 0; i < 10; i++) {
treadmill_pace[0].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[0].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[0].levels[0].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[0].fast_pace = 4.82802; // 3.0 mph
treadmill_pace[0].levels[0].speed = (treadmill_pace[0].levels[0].slow_pace + treadmill_pace[0].levels[0].fast_pace) / 2.0;
treadmill_pace[0].levels[1].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[1].fast_pace = 5.14989; // 3.2 mph
treadmill_pace[0].levels[1].speed = (treadmill_pace[0].levels[1].slow_pace + treadmill_pace[0].levels[1].fast_pace) / 2.0;
treadmill_pace[0].levels[2].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[2].fast_pace = 5.63269; // 3.5 mph
treadmill_pace[0].levels[2].speed = (treadmill_pace[0].levels[2].slow_pace + treadmill_pace[0].levels[2].fast_pace) / 2.0;
treadmill_pace[0].levels[3].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[3].fast_pace = 5.95456; // 3.7 mph
treadmill_pace[0].levels[3].speed = (treadmill_pace[0].levels[3].slow_pace + treadmill_pace[0].levels[3].fast_pace) / 2.0;
treadmill_pace[0].levels[4].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[4].fast_pace = 6.59829; // 4.1 mph
treadmill_pace[0].levels[4].speed = (treadmill_pace[0].levels[4].slow_pace + treadmill_pace[0].levels[4].fast_pace) / 2.0;
treadmill_pace[0].levels[5].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[5].fast_pace = 7.24203; // 4.5 mph
treadmill_pace[0].levels[5].speed = (treadmill_pace[0].levels[5].slow_pace + treadmill_pace[0].levels[5].fast_pace) / 2.0;
treadmill_pace[0].levels[6].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[6].fast_pace = 8.0467; // 5.0 mph
treadmill_pace[0].levels[6].speed = (treadmill_pace[0].levels[6].slow_pace + treadmill_pace[0].levels[6].fast_pace) / 2.0;
treadmill_pace[0].levels[7].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[7].fast_pace = 9.17324; // 5.7 mph
treadmill_pace[0].levels[7].speed = (treadmill_pace[0].levels[7].slow_pace + treadmill_pace[0].levels[7].fast_pace) / 2.0;
treadmill_pace[0].levels[8].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[8].fast_pace = 10.46071; // 6.5 mph
treadmill_pace[0].levels[8].speed = (treadmill_pace[0].levels[8].slow_pace + treadmill_pace[0].levels[8].fast_pace) / 2.0;
treadmill_pace[0].levels[9].slow_pace = 1.60934; // 1.0 mph
treadmill_pace[0].levels[9].fast_pace = 12.23098; // 7.6 mph
treadmill_pace[0].levels[9].speed = (treadmill_pace[0].levels[9].slow_pace + treadmill_pace[0].levels[9].fast_pace) / 2.0;
// Easy
treadmill_pace[1].value = 1;
treadmill_pace[1].display_name = QStringLiteral("Easy");
treadmill_pace[1].levels[0].speed = 5.15; // 3.2 mph
treadmill_pace[1].levels[0].display_name = QStringLiteral("Level 1");
treadmill_pace[1].levels[0].slug = QStringLiteral("level_1");
treadmill_pace[1].levels[1].speed = 5.47; // 3.4 mph
treadmill_pace[1].levels[1].display_name = QStringLiteral("Level 2");
treadmill_pace[1].levels[1].slug = QStringLiteral("level_2");
treadmill_pace[1].levels[2].speed = 5.95; // 3.7 mph
treadmill_pace[1].levels[2].display_name = QStringLiteral("Level 3");
treadmill_pace[1].levels[2].slug = QStringLiteral("level_3");
treadmill_pace[1].levels[3].speed = 6.28; // 3.9 mph
treadmill_pace[1].levels[3].display_name = QStringLiteral("Level 4");
treadmill_pace[1].levels[3].slug = QStringLiteral("level_4");
treadmill_pace[1].levels[4].speed = 6.92; // 4.3 mph
treadmill_pace[1].levels[4].display_name = QStringLiteral("Level 5");
treadmill_pace[1].levels[4].slug = QStringLiteral("level_5");
treadmill_pace[1].levels[5].speed = 7.56; // 4.7 mph
treadmill_pace[1].levels[5].display_name = QStringLiteral("Level 6");
treadmill_pace[1].levels[5].slug = QStringLiteral("level_6");
treadmill_pace[1].levels[6].speed = 8.37; // 5.2 mph
treadmill_pace[1].levels[6].display_name = QStringLiteral("Level 7");
treadmill_pace[1].levels[6].slug = QStringLiteral("level_7");
treadmill_pace[1].levels[7].speed = 9.66; // 6.0 mph
treadmill_pace[1].levels[7].display_name = QStringLiteral("Level 8");
treadmill_pace[1].levels[7].slug = QStringLiteral("level_8");
treadmill_pace[1].levels[8].speed = 11.10; // 6.9 mph
treadmill_pace[1].levels[8].display_name = QStringLiteral("Level 9");
treadmill_pace[1].levels[8].slug = QStringLiteral("level_9");
treadmill_pace[1].levels[9].speed = 12.87; // 8.0 mph
treadmill_pace[1].levels[9].display_name = QStringLiteral("Level 10");
treadmill_pace[1].levels[9].slug = QStringLiteral("level_10");
for (int i = 0; i < 10; i++) {
treadmill_pace[1].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[1].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[1].levels[0].slow_pace = 4.98895; // 3.1 mph
treadmill_pace[1].levels[0].fast_pace = 5.30802; // 3.3 mph
treadmill_pace[1].levels[0].speed = (treadmill_pace[1].levels[0].slow_pace + treadmill_pace[1].levels[0].fast_pace) / 2.0;
treadmill_pace[1].levels[1].slow_pace = 5.30802; // 3.3 mph
treadmill_pace[1].levels[1].fast_pace = 5.79362; // 3.6 mph
treadmill_pace[1].levels[1].speed = (treadmill_pace[1].levels[1].slow_pace + treadmill_pace[1].levels[1].fast_pace) / 2.0;
treadmill_pace[1].levels[2].slow_pace = 5.79362; // 3.6 mph
treadmill_pace[1].levels[2].fast_pace = 6.27642; // 3.9 mph
treadmill_pace[1].levels[2].speed = (treadmill_pace[1].levels[2].slow_pace + treadmill_pace[1].levels[2].fast_pace) / 2.0;
treadmill_pace[1].levels[3].slow_pace = 6.11549; // 3.8 mph
treadmill_pace[1].levels[3].fast_pace = 6.59829; // 4.1 mph
treadmill_pace[1].levels[3].speed = (treadmill_pace[1].levels[3].slow_pace + treadmill_pace[1].levels[3].fast_pace) / 2.0;
treadmill_pace[1].levels[4].slow_pace = 6.75923; // 4.2 mph
treadmill_pace[1].levels[4].fast_pace = 7.24203; // 4.5 mph
treadmill_pace[1].levels[4].speed = (treadmill_pace[1].levels[4].slow_pace + treadmill_pace[1].levels[4].fast_pace) / 2.0;
treadmill_pace[1].levels[5].slow_pace = 7.40296; // 4.6 mph
treadmill_pace[1].levels[5].fast_pace = 7.88576; // 4.9 mph
treadmill_pace[1].levels[5].speed = (treadmill_pace[1].levels[5].slow_pace + treadmill_pace[1].levels[5].fast_pace) / 2.0;
treadmill_pace[1].levels[6].slow_pace = 8.20763; // 5.1 mph
treadmill_pace[1].levels[6].fast_pace = 8.85137; // 5.5 mph
treadmill_pace[1].levels[6].speed = (treadmill_pace[1].levels[6].slow_pace + treadmill_pace[1].levels[6].fast_pace) / 2.0;
treadmill_pace[1].levels[7].slow_pace = 9.33417; // 5.8 mph
treadmill_pace[1].levels[7].fast_pace = 9.97791; // 6.2 mph
treadmill_pace[1].levels[7].speed = (treadmill_pace[1].levels[7].slow_pace + treadmill_pace[1].levels[7].fast_pace) / 2.0;
treadmill_pace[1].levels[8].slow_pace = 10.61884; // 6.6 mph
treadmill_pace[1].levels[8].fast_pace = 11.58725; // 7.2 mph
treadmill_pace[1].levels[8].speed = (treadmill_pace[1].levels[8].slow_pace + treadmill_pace[1].levels[8].fast_pace) / 2.0;
treadmill_pace[1].levels[9].slow_pace = 12.39192; // 7.7 mph
treadmill_pace[1].levels[9].fast_pace = 13.51846; // 8.4 mph
treadmill_pace[1].levels[9].speed = (treadmill_pace[1].levels[9].slow_pace + treadmill_pace[1].levels[9].fast_pace) / 2.0;
// Moderate
treadmill_pace[2].value = 2;
treadmill_pace[2].display_name = QStringLiteral("Moderate");
treadmill_pace[2].levels[0].speed = 5.63; // 3.5 mph
treadmill_pace[2].levels[0].display_name = QStringLiteral("Level 1");
treadmill_pace[2].levels[0].slug = QStringLiteral("level_1");
treadmill_pace[2].levels[1].speed = 5.95; // 3.7 mph
treadmill_pace[2].levels[1].display_name = QStringLiteral("Level 2");
treadmill_pace[2].levels[1].slug = QStringLiteral("level_2");
treadmill_pace[2].levels[2].speed = 6.44; // 4.0 mph
treadmill_pace[2].levels[2].display_name = QStringLiteral("Level 3");
treadmill_pace[2].levels[2].slug = QStringLiteral("level_3");
treadmill_pace[2].levels[3].speed = 6.92; // 4.3 mph
treadmill_pace[2].levels[3].display_name = QStringLiteral("Level 4");
treadmill_pace[2].levels[3].slug = QStringLiteral("level_4");
treadmill_pace[2].levels[4].speed = 7.56; // 4.7 mph
treadmill_pace[2].levels[4].display_name = QStringLiteral("Level 5");
treadmill_pace[2].levels[4].slug = QStringLiteral("level_5");
treadmill_pace[2].levels[5].speed = 8.37; // 5.2 mph
treadmill_pace[2].levels[5].display_name = QStringLiteral("Level 6");
treadmill_pace[2].levels[5].slug = QStringLiteral("level_6");
treadmill_pace[2].levels[6].speed = 9.17; // 5.7 mph
treadmill_pace[2].levels[6].display_name = QStringLiteral("Level 7");
treadmill_pace[2].levels[6].slug = QStringLiteral("level_7");
treadmill_pace[2].levels[7].speed = 10.46; // 6.5 mph
treadmill_pace[2].levels[7].display_name = QStringLiteral("Level 8");
treadmill_pace[2].levels[7].slug = QStringLiteral("level_8");
treadmill_pace[2].levels[8].speed = 12.07; // 7.5 mph
treadmill_pace[2].levels[8].display_name = QStringLiteral("Level 9");
treadmill_pace[2].levels[8].slug = QStringLiteral("level_9");
treadmill_pace[2].levels[9].speed = 14.00; // 8.7 mph
treadmill_pace[2].levels[9].display_name = QStringLiteral("Level 10");
treadmill_pace[2].levels[9].slug = QStringLiteral("level_10");
for (int i = 0; i < 10; i++) {
treadmill_pace[2].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[2].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[2].levels[0].slow_pace = 5.47176; // 3.4 mph
treadmill_pace[2].levels[0].fast_pace = 5.79362; // 3.6 mph
treadmill_pace[2].levels[0].speed = (treadmill_pace[2].levels[0].slow_pace + treadmill_pace[2].levels[0].fast_pace) / 2.0;
treadmill_pace[2].levels[1].slow_pace = 5.95456; // 3.7 mph
treadmill_pace[2].levels[1].fast_pace = 6.27642; // 3.9 mph
treadmill_pace[2].levels[1].speed = (treadmill_pace[2].levels[1].slow_pace + treadmill_pace[2].levels[1].fast_pace) / 2.0;
treadmill_pace[2].levels[2].slow_pace = 6.43736; // 4.0 mph
treadmill_pace[2].levels[2].fast_pace = 6.75923; // 4.2 mph
treadmill_pace[2].levels[2].speed = (treadmill_pace[2].levels[2].slow_pace + treadmill_pace[2].levels[2].fast_pace) / 2.0;
treadmill_pace[2].levels[3].slow_pace = 6.75923; // 4.2 mph
treadmill_pace[2].levels[3].fast_pace = 7.24203; // 4.5 mph
treadmill_pace[2].levels[3].speed = (treadmill_pace[2].levels[3].slow_pace + treadmill_pace[2].levels[3].fast_pace) / 2.0;
treadmill_pace[2].levels[4].slow_pace = 7.40296; // 4.6 mph
treadmill_pace[2].levels[4].fast_pace = 7.88576; // 4.9 mph
treadmill_pace[2].levels[4].speed = (treadmill_pace[2].levels[4].slow_pace + treadmill_pace[2].levels[4].fast_pace) / 2.0;
treadmill_pace[2].levels[5].slow_pace = 8.0467; // 5.0 mph
treadmill_pace[2].levels[5].fast_pace = 8.69043; // 5.4 mph
treadmill_pace[2].levels[5].speed = (treadmill_pace[2].levels[5].slow_pace + treadmill_pace[2].levels[5].fast_pace) / 2.0;
treadmill_pace[2].levels[6].slow_pace = 9.01230; // 5.6 mph
treadmill_pace[2].levels[6].fast_pace = 9.65604; // 6.0 mph
treadmill_pace[2].levels[6].speed = (treadmill_pace[2].levels[6].slow_pace + treadmill_pace[2].levels[6].fast_pace) / 2.0;
treadmill_pace[2].levels[7].slow_pace = 10.13884; // 6.3 mph
treadmill_pace[2].levels[7].fast_pace = 10.94351; // 6.8 mph
treadmill_pace[2].levels[7].speed = (treadmill_pace[2].levels[7].slow_pace + treadmill_pace[2].levels[7].fast_pace) / 2.0;
treadmill_pace[2].levels[8].slow_pace = 11.74818; // 7.3 mph
treadmill_pace[2].levels[8].fast_pace = 12.55285; // 7.8 mph
treadmill_pace[2].levels[8].speed = (treadmill_pace[2].levels[8].slow_pace + treadmill_pace[2].levels[8].fast_pace) / 2.0;
treadmill_pace[2].levels[9].slow_pace = 13.67939; // 8.5 mph
treadmill_pace[2].levels[9].fast_pace = 14.48406; // 9.0 mph
treadmill_pace[2].levels[9].speed = (treadmill_pace[2].levels[9].slow_pace + treadmill_pace[2].levels[9].fast_pace) / 2.0;
// Challenging
treadmill_pace[3].value = 3;
treadmill_pace[3].display_name = QStringLiteral("Challenging");
treadmill_pace[3].levels[0].speed = 6.11; // 3.8 mph
treadmill_pace[3].levels[0].display_name = QStringLiteral("Level 1");
treadmill_pace[3].levels[0].slug = QStringLiteral("level_1");
treadmill_pace[3].levels[1].speed = 6.60; // 4.1 mph
treadmill_pace[3].levels[1].display_name = QStringLiteral("Level 2");
treadmill_pace[3].levels[1].slug = QStringLiteral("level_2");
treadmill_pace[3].levels[2].speed = 7.08; // 4.4 mph
treadmill_pace[3].levels[2].display_name = QStringLiteral("Level 3");
treadmill_pace[3].levels[2].slug = QStringLiteral("level_3");
treadmill_pace[3].levels[3].speed = 7.56; // 4.7 mph
treadmill_pace[3].levels[3].display_name = QStringLiteral("Level 4");
treadmill_pace[3].levels[3].slug = QStringLiteral("level_4");
treadmill_pace[3].levels[4].speed = 8.37; // 5.2 mph
treadmill_pace[3].levels[4].display_name = QStringLiteral("Level 5");
treadmill_pace[3].levels[4].slug = QStringLiteral("level_5");
treadmill_pace[3].levels[5].speed = 9.17; // 5.7 mph
treadmill_pace[3].levels[5].display_name = QStringLiteral("Level 6");
treadmill_pace[3].levels[5].slug = QStringLiteral("level_6");
treadmill_pace[3].levels[6].speed = 10.30; // 6.4 mph
treadmill_pace[3].levels[6].display_name = QStringLiteral("Level 7");
treadmill_pace[3].levels[6].slug = QStringLiteral("level_7");
treadmill_pace[3].levels[7].speed = 11.59; // 7.2 mph
treadmill_pace[3].levels[7].display_name = QStringLiteral("Level 8");
treadmill_pace[3].levels[7].slug = QStringLiteral("level_8");
treadmill_pace[3].levels[8].speed = 13.20; // 8.2 mph
treadmill_pace[3].levels[8].display_name = QStringLiteral("Level 9");
treadmill_pace[3].levels[8].slug = QStringLiteral("level_9");
treadmill_pace[3].levels[9].speed = 15.29; // 9.5 mph
treadmill_pace[3].levels[9].display_name = QStringLiteral("Level 10");
treadmill_pace[3].levels[9].slug = QStringLiteral("level_10");
for (int i = 0; i < 10; i++) {
treadmill_pace[3].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[3].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[3].levels[0].slow_pace = 5.95456; // 3.7 mph
treadmill_pace[3].levels[0].fast_pace = 6.43736; // 4.0 mph
treadmill_pace[3].levels[0].speed = (treadmill_pace[3].levels[0].slow_pace + treadmill_pace[3].levels[0].fast_pace) / 2.0;
treadmill_pace[3].levels[1].slow_pace = 6.43736; // 4.0 mph
treadmill_pace[3].levels[1].fast_pace = 6.92016; // 4.3 mph
treadmill_pace[3].levels[1].speed = (treadmill_pace[3].levels[1].slow_pace + treadmill_pace[3].levels[1].fast_pace) / 2.0;
treadmill_pace[3].levels[2].slow_pace = 6.92016; // 4.3 mph
treadmill_pace[3].levels[2].fast_pace = 7.40296; // 4.6 mph
treadmill_pace[3].levels[2].speed = (treadmill_pace[3].levels[2].slow_pace + treadmill_pace[3].levels[2].fast_pace) / 2.0;
treadmill_pace[3].levels[3].slow_pace = 7.40296; // 4.6 mph
treadmill_pace[3].levels[3].fast_pace = 8.0467; // 5.0 mph
treadmill_pace[3].levels[3].speed = (treadmill_pace[3].levels[3].slow_pace + treadmill_pace[3].levels[3].fast_pace) / 2.0;
treadmill_pace[3].levels[4].slow_pace = 8.0467; // 5.0 mph
treadmill_pace[3].levels[4].fast_pace = 8.69043; // 5.4 mph
treadmill_pace[3].levels[4].speed = (treadmill_pace[3].levels[4].slow_pace + treadmill_pace[3].levels[4].fast_pace) / 2.0;
treadmill_pace[3].levels[5].slow_pace = 8.85137; // 5.5 mph
treadmill_pace[3].levels[5].fast_pace = 9.65604; // 6.0 mph
treadmill_pace[3].levels[5].speed = (treadmill_pace[3].levels[5].slow_pace + treadmill_pace[3].levels[5].fast_pace) / 2.0;
treadmill_pace[3].levels[6].slow_pace = 9.81697; // 6.1 mph
treadmill_pace[3].levels[6].fast_pace = 10.78258; // 6.7 mph
treadmill_pace[3].levels[6].speed = (treadmill_pace[3].levels[6].slow_pace + treadmill_pace[3].levels[6].fast_pace) / 2.0;
treadmill_pace[3].levels[7].slow_pace = 11.10445; // 6.9 mph
treadmill_pace[3].levels[7].fast_pace = 12.07005; // 7.5 mph
treadmill_pace[3].levels[7].speed = (treadmill_pace[3].levels[7].slow_pace + treadmill_pace[3].levels[7].fast_pace) / 2.0;
treadmill_pace[3].levels[8].slow_pace = 12.71379; // 7.9 mph
treadmill_pace[3].levels[8].fast_pace = 13.84032; // 8.6 mph
treadmill_pace[3].levels[8].speed = (treadmill_pace[3].levels[8].slow_pace + treadmill_pace[3].levels[8].fast_pace) / 2.0;
treadmill_pace[3].levels[9].slow_pace = 14.64499; // 9.1 mph
treadmill_pace[3].levels[9].fast_pace = 16.0934; // 10.0 mph
treadmill_pace[3].levels[9].speed = (treadmill_pace[3].levels[9].slow_pace + treadmill_pace[3].levels[9].fast_pace) / 2.0;
// Hard
treadmill_pace[4].value = 4;
treadmill_pace[4].display_name = QStringLiteral("Hard");
treadmill_pace[4].levels[0].speed = 6.76; // 4.2 mph
treadmill_pace[4].levels[0].display_name = QStringLiteral("Level 1");
treadmill_pace[4].levels[0].slug = QStringLiteral("level_1");
treadmill_pace[4].levels[1].speed = 7.24; // 4.5 mph
treadmill_pace[4].levels[1].display_name = QStringLiteral("Level 2");
treadmill_pace[4].levels[1].slug = QStringLiteral("level_2");
treadmill_pace[4].levels[2].speed = 7.88; // 4.9 mph
treadmill_pace[4].levels[2].display_name = QStringLiteral("Level 3");
treadmill_pace[4].levels[2].slug = QStringLiteral("level_3");
treadmill_pace[4].levels[3].speed = 8.37; // 5.2 mph
treadmill_pace[4].levels[3].display_name = QStringLiteral("Level 4");
treadmill_pace[4].levels[3].slug = QStringLiteral("level_4");
treadmill_pace[4].levels[4].speed = 9.17; // 5.7 mph
treadmill_pace[4].levels[4].display_name = QStringLiteral("Level 5");
treadmill_pace[4].levels[4].slug = QStringLiteral("level_5");
treadmill_pace[4].levels[5].speed = 10.14; // 6.3 mph
treadmill_pace[4].levels[5].display_name = QStringLiteral("Level 6");
treadmill_pace[4].levels[5].slug = QStringLiteral("level_6");
treadmill_pace[4].levels[6].speed = 11.27; // 7.0 mph
treadmill_pace[4].levels[6].display_name = QStringLiteral("Level 7");
treadmill_pace[4].levels[6].slug = QStringLiteral("level_7");
treadmill_pace[4].levels[7].speed = 12.55; // 7.8 mph
treadmill_pace[4].levels[7].display_name = QStringLiteral("Level 8");
treadmill_pace[4].levels[7].slug = QStringLiteral("level_8");
treadmill_pace[4].levels[8].speed = 14.48; // 9.0 mph
treadmill_pace[4].levels[8].display_name = QStringLiteral("Level 9");
treadmill_pace[4].levels[8].slug = QStringLiteral("level_9");
treadmill_pace[4].levels[9].speed = 16.90; // 10.5 mph
treadmill_pace[4].levels[9].display_name = QStringLiteral("Level 10");
treadmill_pace[4].levels[9].slug = QStringLiteral("level_10");
for (int i = 0; i < 10; i++) {
treadmill_pace[4].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[4].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[4].levels[0].slow_pace = 6.59829; // 4.1 mph
treadmill_pace[4].levels[0].fast_pace = 7.08110; // 4.4 mph
treadmill_pace[4].levels[0].speed = (treadmill_pace[4].levels[0].slow_pace + treadmill_pace[4].levels[0].fast_pace) / 2.0;
treadmill_pace[4].levels[1].slow_pace = 7.08110; // 4.4 mph
treadmill_pace[4].levels[1].fast_pace = 7.56390; // 4.7 mph
treadmill_pace[4].levels[1].speed = (treadmill_pace[4].levels[1].slow_pace + treadmill_pace[4].levels[1].fast_pace) / 2.0;
treadmill_pace[4].levels[2].slow_pace = 7.56390; // 4.7 mph
treadmill_pace[4].levels[2].fast_pace = 8.20763; // 5.1 mph
treadmill_pace[4].levels[2].speed = (treadmill_pace[4].levels[2].slow_pace + treadmill_pace[4].levels[2].fast_pace) / 2.0;
treadmill_pace[4].levels[3].slow_pace = 8.20763; // 5.1 mph
treadmill_pace[4].levels[3].fast_pace = 8.69043; // 5.4 mph
treadmill_pace[4].levels[3].speed = (treadmill_pace[4].levels[3].slow_pace + treadmill_pace[4].levels[3].fast_pace) / 2.0;
treadmill_pace[4].levels[4].slow_pace = 8.85137; // 5.5 mph
treadmill_pace[4].levels[4].fast_pace = 9.65604; // 6.0 mph
treadmill_pace[4].levels[4].speed = (treadmill_pace[4].levels[4].slow_pace + treadmill_pace[4].levels[4].fast_pace) / 2.0;
treadmill_pace[4].levels[5].slow_pace = 9.81697; // 6.1 mph
treadmill_pace[4].levels[5].fast_pace = 10.62164; // 6.6 mph
treadmill_pace[4].levels[5].speed = (treadmill_pace[4].levels[5].slow_pace + treadmill_pace[4].levels[5].fast_pace) / 2.0;
treadmill_pace[4].levels[6].slow_pace = 10.94351; // 6.8 mph
treadmill_pace[4].levels[6].fast_pace = 11.74818; // 7.3 mph
treadmill_pace[4].levels[6].speed = (treadmill_pace[4].levels[6].slow_pace + treadmill_pace[4].levels[6].fast_pace) / 2.0;
treadmill_pace[4].levels[7].slow_pace = 12.23098; // 7.6 mph
treadmill_pace[4].levels[7].fast_pace = 13.19659; // 8.2 mph
treadmill_pace[4].levels[7].speed = (treadmill_pace[4].levels[7].slow_pace + treadmill_pace[4].levels[7].fast_pace) / 2.0;
treadmill_pace[4].levels[8].slow_pace = 14.00126; // 8.7 mph
treadmill_pace[4].levels[8].fast_pace = 15.12780; // 9.4 mph
treadmill_pace[4].levels[8].speed = (treadmill_pace[4].levels[8].slow_pace + treadmill_pace[4].levels[8].fast_pace) / 2.0;
treadmill_pace[4].levels[9].slow_pace = 16.25433; // 10.1 mph
treadmill_pace[4].levels[9].fast_pace = 17.54180; // 10.9 mph
treadmill_pace[4].levels[9].speed = (treadmill_pace[4].levels[9].slow_pace + treadmill_pace[4].levels[9].fast_pace) / 2.0;
// Very Hard
treadmill_pace[5].value = 5;
treadmill_pace[5].display_name = QStringLiteral("Very Hard");
treadmill_pace[5].levels[0].speed = 7.56; // 4.7 mph
treadmill_pace[5].levels[0].display_name = QStringLiteral("Level 1");
treadmill_pace[5].levels[0].slug = QStringLiteral("level_1");
treadmill_pace[5].levels[1].speed = 8.05; // 5.0 mph
treadmill_pace[5].levels[1].display_name = QStringLiteral("Level 2");
treadmill_pace[5].levels[1].slug = QStringLiteral("level_2");
treadmill_pace[5].levels[2].speed = 8.69; // 5.4 mph
treadmill_pace[5].levels[2].display_name = QStringLiteral("Level 3");
treadmill_pace[5].levels[2].slug = QStringLiteral("level_3");
treadmill_pace[5].levels[3].speed = 9.17; // 5.7 mph
treadmill_pace[5].levels[3].display_name = QStringLiteral("Level 4");
treadmill_pace[5].levels[3].slug = QStringLiteral("level_4");
treadmill_pace[5].levels[4].speed = 10.14; // 6.3 mph
treadmill_pace[5].levels[4].display_name = QStringLiteral("Level 5");
treadmill_pace[5].levels[4].slug = QStringLiteral("level_5");
treadmill_pace[5].levels[5].speed = 11.27; // 7.0 mph
treadmill_pace[5].levels[5].display_name = QStringLiteral("Level 6");
treadmill_pace[5].levels[5].slug = QStringLiteral("level_6");
treadmill_pace[5].levels[6].speed = 12.39; // 7.7 mph
treadmill_pace[5].levels[6].display_name = QStringLiteral("Level 7");
treadmill_pace[5].levels[6].slug = QStringLiteral("level_7");
treadmill_pace[5].levels[7].speed = 13.84; // 8.6 mph
treadmill_pace[5].levels[7].display_name = QStringLiteral("Level 8");
treadmill_pace[5].levels[7].slug = QStringLiteral("level_8");
treadmill_pace[5].levels[8].speed = 15.93; // 9.9 mph
treadmill_pace[5].levels[8].display_name = QStringLiteral("Level 9");
treadmill_pace[5].levels[8].slug = QStringLiteral("level_9");
treadmill_pace[5].levels[9].speed = 18.51; // 11.5 mph
treadmill_pace[5].levels[9].display_name = QStringLiteral("Level 10");
treadmill_pace[5].levels[9].slug = QStringLiteral("level_10");
for (int i = 0; i < 10; i++) {
treadmill_pace[5].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[5].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[5].levels[0].slow_pace = 7.24203; // 4.5 mph
treadmill_pace[5].levels[0].fast_pace = 7.88576; // 4.9 mph
treadmill_pace[5].levels[0].speed = (treadmill_pace[5].levels[0].slow_pace + treadmill_pace[5].levels[0].fast_pace) / 2.0;
treadmill_pace[5].levels[1].slow_pace = 7.72483; // 4.8 mph
treadmill_pace[5].levels[1].fast_pace = 8.36857; // 5.2 mph
treadmill_pace[5].levels[1].speed = (treadmill_pace[5].levels[1].slow_pace + treadmill_pace[5].levels[1].fast_pace) / 2.0;
treadmill_pace[5].levels[2].slow_pace = 8.36857; // 5.2 mph
treadmill_pace[5].levels[2].fast_pace = 9.01230; // 5.6 mph
treadmill_pace[5].levels[2].speed = (treadmill_pace[5].levels[2].slow_pace + treadmill_pace[5].levels[2].fast_pace) / 2.0;
treadmill_pace[5].levels[3].slow_pace = 8.85137; // 5.5 mph
treadmill_pace[5].levels[3].fast_pace = 9.81697; // 6.1 mph
treadmill_pace[5].levels[3].speed = (treadmill_pace[5].levels[3].slow_pace + treadmill_pace[5].levels[3].fast_pace) / 2.0;
treadmill_pace[5].levels[4].slow_pace = 9.81697; // 6.1 mph
treadmill_pace[5].levels[4].fast_pace = 10.62164; // 6.6 mph
treadmill_pace[5].levels[4].speed = (treadmill_pace[5].levels[4].slow_pace + treadmill_pace[5].levels[4].fast_pace) / 2.0;
treadmill_pace[5].levels[5].slow_pace = 10.78258; // 6.7 mph
treadmill_pace[5].levels[5].fast_pace = 11.74818; // 7.3 mph
treadmill_pace[5].levels[5].speed = (treadmill_pace[5].levels[5].slow_pace + treadmill_pace[5].levels[5].fast_pace) / 2.0;
treadmill_pace[5].levels[6].slow_pace = 11.90912; // 7.4 mph
treadmill_pace[5].levels[6].fast_pace = 13.03565; // 8.1 mph
treadmill_pace[5].levels[6].speed = (treadmill_pace[5].levels[6].slow_pace + treadmill_pace[5].levels[6].fast_pace) / 2.0;
treadmill_pace[5].levels[7].slow_pace = 13.35752; // 8.3 mph
treadmill_pace[5].levels[7].fast_pace = 14.64499; // 9.1 mph
treadmill_pace[5].levels[7].speed = (treadmill_pace[5].levels[7].slow_pace + treadmill_pace[5].levels[7].fast_pace) / 2.0;
treadmill_pace[5].levels[8].slow_pace = 15.28873; // 9.5 mph
treadmill_pace[5].levels[8].fast_pace = 16.73714; // 10.4 mph
treadmill_pace[5].levels[8].speed = (treadmill_pace[5].levels[8].slow_pace + treadmill_pace[5].levels[8].fast_pace) / 2.0;
treadmill_pace[5].levels[9].slow_pace = 17.70274; // 11.0 mph
treadmill_pace[5].levels[9].fast_pace = 19.63395; // 12.2 mph
treadmill_pace[5].levels[9].speed = (treadmill_pace[5].levels[9].slow_pace + treadmill_pace[5].levels[9].fast_pace) / 2.0;
// Max
treadmill_pace[6].value = 6;
treadmill_pace[6].display_name = QStringLiteral("Max");
treadmill_pace[6].levels[0].speed = 8.05; // 5.0 mph
treadmill_pace[6].levels[1].speed = 8.53; // 5.3 mph
treadmill_pace[6].levels[2].speed = 9.17; // 5.7 mph
treadmill_pace[6].levels[3].speed = 9.98; // 6.2 mph
treadmill_pace[6].levels[4].speed = 10.78; // 6.7 mph
treadmill_pace[6].levels[5].speed = 11.91; // 7.4 mph
treadmill_pace[6].levels[6].speed = 13.20; // 8.2 mph
treadmill_pace[6].levels[7].speed = 14.81; // 9.2 mph
treadmill_pace[6].levels[8].speed = 16.90; // 10.5 mph
treadmill_pace[6].levels[9].speed = 19.80; // 12.3 mph
for (int i = 0; i < 10; i++) {
treadmill_pace[6].levels[i].display_name = QStringLiteral("Level %1").arg(i+1);
treadmill_pace[6].levels[i].slug = QStringLiteral("level_%1").arg(i+1);
}
treadmill_pace[6].levels[0].slow_pace = 8.0467; // 5.0 mph
treadmill_pace[6].levels[0].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[0].speed = (treadmill_pace[6].levels[0].slow_pace + treadmill_pace[6].levels[0].fast_pace) / 2.0;
treadmill_pace[6].levels[1].slow_pace = 8.52950; // 5.3 mph
treadmill_pace[6].levels[1].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[1].speed = (treadmill_pace[6].levels[1].slow_pace + treadmill_pace[6].levels[1].fast_pace) / 2.0;
treadmill_pace[6].levels[2].slow_pace = 9.17324; // 5.7 mph
treadmill_pace[6].levels[2].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[2].speed = (treadmill_pace[6].levels[2].slow_pace + treadmill_pace[6].levels[2].fast_pace) / 2.0;
treadmill_pace[6].levels[3].slow_pace = 9.97791; // 6.2 mph
treadmill_pace[6].levels[3].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[3].speed = (treadmill_pace[6].levels[3].slow_pace + treadmill_pace[6].levels[3].fast_pace) / 2.0;
treadmill_pace[6].levels[4].slow_pace = 10.78258; // 6.7 mph
treadmill_pace[6].levels[4].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[4].speed = (treadmill_pace[6].levels[4].slow_pace + treadmill_pace[6].levels[4].fast_pace) / 2.0;
treadmill_pace[6].levels[5].slow_pace = 11.90912; // 7.4 mph
treadmill_pace[6].levels[5].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[5].speed = (treadmill_pace[6].levels[5].slow_pace + treadmill_pace[6].levels[5].fast_pace) / 2.0;
treadmill_pace[6].levels[6].slow_pace = 13.19659; // 8.2 mph
treadmill_pace[6].levels[6].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[6].speed = (treadmill_pace[6].levels[6].slow_pace + treadmill_pace[6].levels[6].fast_pace) / 2.0;
treadmill_pace[6].levels[7].slow_pace = 14.80593; // 9.2 mph
treadmill_pace[6].levels[7].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[7].speed = (treadmill_pace[6].levels[7].slow_pace + treadmill_pace[6].levels[7].fast_pace) / 2.0;
treadmill_pace[6].levels[8].slow_pace = 16.89993; // 10.5 mph
treadmill_pace[6].levels[8].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[8].speed = (treadmill_pace[6].levels[8].slow_pace + treadmill_pace[6].levels[8].fast_pace) / 2.0;
treadmill_pace[6].levels[9].slow_pace = 19.79488; // 12.3 mph
treadmill_pace[6].levels[9].fast_pace = 20.11675; // 12.5 mph
treadmill_pace[6].levels[9].speed = (treadmill_pace[6].levels[9].slow_pace + treadmill_pace[6].levels[9].fast_pace) / 2.0;
connect(timer, &QTimer::timeout, this, &peloton::startEngine);
PZP = new powerzonepack(bl, this);
@@ -1089,6 +1225,138 @@ void peloton::ride_onfinish(QNetworkReply *reply) {
trainrows.clear();
}
}
if (trainrows.empty() && !target_metrics_data_list.isEmpty() && bluetoothManager->device()->deviceType() == bluetoothdevice::TREADMILL) {
QJsonObject target_metrics_data = ride["target_metrics_data"].toObject();
QJsonArray target_metrics = target_metrics_data["target_metrics"].toArray();
if (!target_metrics.isEmpty()) {
bool treadmill_force_speed = settings.value(QZSettings::treadmill_force_speed,
QZSettings::default_treadmill_force_speed).toBool();
int peloton_treadmill_level = settings.value(QZSettings::peloton_treadmill_level,
QZSettings::default_peloton_treadmill_level).toInt() - 1;
if (peloton_treadmill_level < 0 || peloton_treadmill_level > 9)
peloton_treadmill_level = 0;
double miles = 1.0;
if (settings.value(QZSettings::miles_unit, QZSettings::default_miles_unit).toBool()) { // i didn't find the unit in the json
miles = 1.60934;
}
for (const QJsonValue& segment : target_metrics) {
QJsonObject segmentObj = segment.toObject();
QJsonArray metrics = segmentObj["metrics"].toArray();
QJsonObject offsets = segmentObj["offsets"].toObject();
QString segment_type = segmentObj["segment_type"].toString();
// Skip if no metrics or invalid offsets
if (metrics.isEmpty() || offsets.isEmpty())
continue;
double speed_lower = -1;
double speed_upper = -1;
double inc_lower = -100;
double inc_upper = -100;
int pace_intensity_lower = -1;
int pace_intensity_upper = -1;
// Process metrics (speed, incline, pace_intensity)
for (const QJsonValue& metric : metrics) {
QJsonObject metricObj = metric.toObject();
QString metricName = metricObj["name"].toString().toLower();
if (metricName == "pace_intensity") {
pace_intensity_lower = metricObj["lower"].toInt();
pace_intensity_upper = metricObj["upper"].toInt();
speed_lower = treadmill_pace[pace_intensity_lower].levels[peloton_treadmill_level].slow_pace;
speed_upper = treadmill_pace[pace_intensity_upper].levels[peloton_treadmill_level].fast_pace;
miles = 1; // the pace intensity are always in km/h
}
else if (metricName == "speed") {
speed_lower = metricObj["lower"].toDouble();
speed_upper = metricObj["upper"].toDouble();
}
else if (metricName == "incline") {
inc_lower = metricObj["lower"].toDouble();
inc_upper = metricObj["upper"].toDouble();
}
}
// Create training row
trainrow row;
if (speed_lower != -1)
row.forcespeed = treadmill_force_speed;
// Set duration
int start = offsets["start"].toInt();
int end = offsets["end"].toInt();
row.duration = QTime(0, 0, 0).addSecs(end - start + 1);
// Apply difficulty settings
if (difficulty.toUpper() == "LOWER") {
if (speed_lower != -1)
row.speed = speed_lower * miles;
if (inc_lower != -100)
row.inclination = inc_lower;
if (pace_intensity_lower != -1)
row.pace_intensity = pace_intensity_lower;
}
else if (difficulty.toUpper() == "UPPER") {
if (speed_lower != -1)
row.speed = speed_upper * miles;
if (inc_lower != -100)
row.inclination = inc_upper;
if (pace_intensity_upper != -1)
row.pace_intensity = pace_intensity_upper;
}
else { // AVERAGE
if (speed_lower != -1)
row.speed = (speed_lower + speed_upper) / 2.0 * miles;
if (inc_lower != -100)
row.inclination = (inc_lower + inc_upper) / 2.0;
if (pace_intensity_lower != -1)
row.pace_intensity = (pace_intensity_lower + pace_intensity_upper) / 2;
}
// Store range values
if (speed_lower != -1) {
row.lower_speed = speed_lower * miles;
row.average_speed = (speed_lower + speed_upper) / 2.0 * miles;
row.upper_speed = speed_upper * miles;
}
if (inc_lower != -100) {
// Apply inclination adjustments
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();
row.lower_inclination = inc_lower * gain + offset;
row.average_inclination = (inc_lower + inc_upper) / 2.0 * gain + offset;
row.upper_inclination = inc_upper * gain + offset;
row.inclination = row.inclination * gain + offset;
}
qDebug() << row.duration << row.speed << row.inclination;
trainrows.append(row);
}
QTime duration(0,0,0,0);
foreach(trainrow r, trainrows) {
duration = duration.addSecs(QTime(0,0,0,0).secsTo(r.duration));
qDebug() << duration << r.duration;
}
if(QTime(0,0,0,0).secsTo(duration) < current_pedaling_duration) {
qDebug() << "peloton sends less metrics than expected, let's remove this and fallback on HFB" << QTime(0,0,0,0).secsTo(duration) << current_pedaling_duration;
trainrows.clear();
}
}
}
if (log_request) {
qDebug() << "peloton::ride_onfinish" << trainrows.length() << ride;

View File

@@ -104,7 +104,9 @@ class peloton : public QObject {
typedef struct _peloton_treadmill_pace_intensities_level {
QString display_name;
double speed;
double fast_pace;
double slow_pace;
double speed; // Average of fast_pace and slow_pace
QString slug;
}_peloton_treadmill_pace_intensities_level;

View File

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

View File

@@ -77,16 +77,21 @@ DEFINES += QT_DEPRECATED_WARNINGS IO_UNDER_QT SMTP_BUILD NOMINMAX
SOURCES += \
$$PWD/devices/antbike/antbike.cpp \
$$PWD/devices/crossrope/crossrope.cpp \
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.cpp \
$$PWD/devices/deeruntreadmill/deerruntreadmill.cpp \
$$PWD/devices/focustreadmill/focustreadmill.cpp \
$$PWD/devices/jumprope.cpp \
$$PWD/devices/kineticinroadbike/SmartControl.cpp \
$$PWD/devices/kineticinroadbike/kineticinroadbike.cpp \
$$PWD/devices/lifespantreadmill/lifespantreadmill.cpp \
$$PWD/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.cpp \
$$PWD/devices/pitpatbike/pitpatbike.cpp \
$$PWD/devices/sportsplusrower/sportsplusrower.cpp \
$$PWD/devices/sportstechelliptical/sportstechelliptical.cpp \
$$PWD/devices/sramAXSController/sramAXSController.cpp \
$$PWD/devices/technogymbike/technogymbike.cpp \
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.cpp \
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.cpp \
$$PWD/mqtt/qmqttauthenticationproperties.cpp \
$$PWD/mqtt/qmqttclient.cpp \
$$PWD/mqtt/qmqttconnection.cpp \
@@ -99,6 +104,7 @@ SOURCES += \
$$PWD/mqtt/qmqtttopicfilter.cpp \
$$PWD/mqtt/qmqtttopicname.cpp \
$$PWD/mqtt/qmqtttype.cpp \
$$PWD/osc.cpp \
QTelnet.cpp \
devices/bkoolbike/bkoolbike.cpp \
devices/csafe/csafe.cpp \
@@ -327,17 +333,32 @@ HEADERS += \
$$PWD/EventHandler.h \
$$PWD/devices/antbike/antbike.h \
$$PWD/devices/crossrope/crossrope.h \
$$PWD/devices/cycleopsphantombike/cycleopsphantombike.h \
$$PWD/devices/deeruntreadmill/deerruntreadmill.h \
$$PWD/devices/focustreadmill/focustreadmill.h \
$$PWD/devices/jumprope.h \
$$PWD/devices/kineticinroadbike/SmartControl.h \
$$PWD/devices/kineticinroadbike/kineticinroadbike.h \
$$PWD/devices/lifespantreadmill/lifespantreadmill.h \
$$PWD/devices/nordictrackifitadbelliptical/nordictrackifitadbelliptical.h \
$$PWD/devices/pitpatbike/pitpatbike.h \
$$PWD/devices/sportsplusrower/sportsplusrower.h \
$$PWD/devices/sportstechelliptical/sportstechelliptical.h \
$$PWD/devices/sramAXSController/sramAXSController.h \
$$PWD/devices/technogymbike/technogymbike.h \
$$PWD/devices/trxappgateusbelliptical/trxappgateusbelliptical.h \
$$PWD/devices/trxappgateusbrower/trxappgateusbrower.h \
$$PWD/ergtable.h \
$$PWD/osc.h \
$$PWD/oscpp/client.hpp \
$$PWD/oscpp/detail/endian.hpp \
$$PWD/oscpp/detail/host.hpp \
$$PWD/oscpp/detail/stream.hpp \
$$PWD/oscpp/error.hpp \
$$PWD/oscpp/print.hpp \
$$PWD/oscpp/server.hpp \
$$PWD/oscpp/types.hpp \
$$PWD/oscpp/util.hpp \
$$PWD/mqtt/qmqttauthenticationproperties.h \
$$PWD/mqtt/qmqttclient.h \
$$PWD/mqtt/qmqttclient_p.h \
@@ -928,4 +949,4 @@ INCLUDEPATH += purchasing/inapp
WINRT_MANIFEST = AppxManifest.xml
VERSION = 2.18.9
VERSION = 2.18.17

View File

@@ -3,6 +3,7 @@
#include <cstdlib>
#include <fstream>
#include <ostream>
#include <QDir>
#include "QSettings"
@@ -13,6 +14,12 @@
#include "fit_developer_field_description.hpp"
#include "fit_mesg_broadcaster.hpp"
#ifdef _WIN32
#include <io.h>
#include <fcntl.h>
#include <windows.h>
#endif
using namespace std;
qfit::qfit(QObject *parent) : QObject(parent) {}
@@ -22,6 +29,8 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
QSettings settings;
bool strava_virtual_activity =
settings.value(QZSettings::strava_virtual_activity, QZSettings::default_strava_virtual_activity).toBool();
bool strava_treadmill =
settings.value(QZSettings::strava_treadmill, QZSettings::default_strava_treadmill).toBool();
bool powr_sensor_running_cadence_half_on_strava =
settings
.value(QZSettings::powr_sensor_running_cadence_half_on_strava,
@@ -46,25 +55,35 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
startingDistanceOffset = session.at(firstRealIndex).distance;
}
#ifdef _WIN32
file.open(QString(filename).toLocal8Bit().constData(), std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc);
#else
file.open(filename.toStdString(), std::ios::in | std::ios::out | std::ios::binary | std::ios::trunc);
#endif
if (!file.is_open()) {
printf("Error opening file ExampleActivity.fit\n");
qDebug() << "Error opening file stream";
return;
}
QFile output(filename);
output.open(QIODevice::WriteOnly);
bool fit_file_garmin_device_training_effect = settings.value(QZSettings::fit_file_garmin_device_training_effect, QZSettings::default_fit_file_garmin_device_training_effect).toBool();
fit::FileIdMesg fileIdMesg; // Every FIT file requires a File ID message
fileIdMesg.SetType(FIT_FILE_ACTIVITY);
if(bluetooth_device_name.toUpper().startsWith("DOMYOS"))
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_DECATHLON);
else
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_DEVELOPMENT);
fileIdMesg.SetProduct(1);
fileIdMesg.SetSerialNumber(12345);
else {
if(fit_file_garmin_device_training_effect)
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_GARMIN);
else
fileIdMesg.SetManufacturer(FIT_MANUFACTURER_DEVELOPMENT);
}
if(fit_file_garmin_device_training_effect) {
fileIdMesg.SetProduct(FIT_GARMIN_PRODUCT_EDGE_1030_PLUS);
fileIdMesg.SetSerialNumber(3313379353);
} else {
fileIdMesg.SetProduct(1);
fileIdMesg.SetSerialNumber(12345);
}
fileIdMesg.SetTimeCreated(session.at(firstRealIndex).time.toSecsSinceEpoch() - 631065600L);
bool gps_data = false;
@@ -138,7 +157,8 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
if (strava_virtual_activity) {
sessionMesg.SetSubSport(FIT_SUB_SPORT_VIRTUAL_ACTIVITY);
} else {
sessionMesg.SetSubSport(FIT_SUB_SPORT_TREADMILL);
if(strava_treadmill)
sessionMesg.SetSubSport(FIT_SUB_SPORT_TREADMILL);
}
} else if (type == bluetoothdevice::ELLIPTICAL) {
@@ -218,7 +238,9 @@ void qfit::save(const QString &filename, QList<SessionLine> session, bluetoothde
fit::WorkoutMesg workout;
workout.SetSport(sessionMesg.GetSport());
workout.SetSubSport(sessionMesg.GetSubSport());
#ifndef _WIN32
workout.SetWktName(workoutName.toStdWString());
#endif
workout.SetNumValidSteps(1);
encode.Write(workout);
@@ -611,7 +633,11 @@ class Listener : public fit::FileIdMesgListener,
void qfit::open(const QString &filename, QList<SessionLine> *output) {
std::fstream file;
file.open(filename.toStdString(), std::ios::in);
#ifdef _WIN32
file.open(QString(filename).toLocal8Bit().constData(), std::ios::in | std::ios::binary);
#else
file.open(filename.toStdString(), std::ios::in | std::ios::binary);
#endif
if (!file.is_open()) {
@@ -633,4 +659,6 @@ void qfit::open(const QString &filename, QList<SessionLine> *output) {
mesgBroadcaster.AddListener((fit::RecordMesgListener &)listener);
mesgBroadcaster.AddListener((fit::MesgListener &)listener);
decode.Read(&s, &mesgBroadcaster, &mesgBroadcaster, &listener);
file.close();
}

View File

@@ -110,5 +110,9 @@
<file>inner_templates/chartjs/dochartliveheart.js</file>
<file>Wizard.qml</file>
<file>gears.qml</file>
<file>IndicatorOnlySwitch.qml</file>
<file>StaticAccordionElement.qml</file>
<file>inner_templates/chartjs/dotreadmillchartlive.js</file>
<file>inner_templates/chartjs/treadmillchartlive.htm</file>
</qresource>
</RCC>

View File

@@ -801,8 +801,20 @@ const QString QZSettings::peloton_auto_start_without_intro = QStringLiteral("pel
const QString QZSettings::nordictrack_tseries5_treadmill = QStringLiteral("nordictrack_tseries5_treadmill");
const QString QZSettings::proform_carbon_tl_PFTL59722c = QStringLiteral("proform_carbon_tl_PFTL59722c");
const QString QZSettings::nordictrack_gx_44_pro = QStringLiteral("nordictrack_gx_44_pro");
const QString QZSettings::OSC_ip = QStringLiteral("osc_ip");
const QString QZSettings::default_OSC_ip = QStringLiteral("");
const QString QZSettings::OSC_port = QStringLiteral("osc_port");
const QString QZSettings::strava_treadmill = QStringLiteral("strava_treadmill");
const QString QZSettings::iconsole_rower = QStringLiteral("iconsole_rower");
const QString QZSettings::proform_treadmill_1500_pro = QStringLiteral("proform_treadmill_1500_pro");
const QString QZSettings::proform_505_cst_80_44 = QStringLiteral("proform_505_cst_80_44");
const QString QZSettings::proform_trainer_8_0 = QStringLiteral("proform_trainer_8_0");
const QString QZSettings::tile_biggears_swap = QStringLiteral("tile_biggears_swap");
const QString QZSettings::treadmill_follow_wattage = QStringLiteral("treadmill_follow_wattage");
const QString QZSettings::fit_file_garmin_device_training_effect = QStringLiteral("fit_file_garmin_device_training_effect");
const QString QZSettings::proform_treadmill_705_cst_V80_44 = QStringLiteral("proform_treadmill_705_cst_V80_44");
const uint32_t allSettingsCount = 676;
const uint32_t allSettingsCount = 687;
QVariant allSettings[allSettingsCount][2] = {
{QZSettings::cryptoKeySettingsProfiles, QZSettings::default_cryptoKeySettingsProfiles},
@@ -1485,6 +1497,17 @@ QVariant allSettings[allSettingsCount][2] = {
{QZSettings::nordictrack_tseries5_treadmill, QZSettings::default_nordictrack_tseries5_treadmill},
{QZSettings::proform_carbon_tl_PFTL59722c, QZSettings::default_proform_carbon_tl_PFTL59722c},
{QZSettings::nordictrack_gx_44_pro, QZSettings::default_nordictrack_gx_44_pro},
{QZSettings::OSC_ip, QZSettings::default_OSC_ip},
{QZSettings::OSC_port, QZSettings::default_OSC_port},
{QZSettings::strava_treadmill, QZSettings::default_strava_treadmill},
{QZSettings::iconsole_rower, QZSettings::default_iconsole_rower},
{QZSettings::proform_treadmill_1500_pro, QZSettings::default_proform_treadmill_1500_pro},
{QZSettings::proform_505_cst_80_44, QZSettings::default_proform_505_cst_80_44},
{QZSettings::proform_trainer_8_0, QZSettings::default_proform_trainer_8_0},
{QZSettings::tile_biggears_swap, QZSettings::default_tile_biggears_swap},
{QZSettings::treadmill_follow_wattage, QZSettings::default_treadmill_follow_wattage},
{QZSettings::fit_file_garmin_device_training_effect, QZSettings::default_fit_file_garmin_device_training_effect},
{QZSettings::proform_treadmill_705_cst_V80_44, QZSettings::default_proform_treadmill_705_cst_V80_44},
};
void QZSettings::qDebugAllSettings(bool showDefaults) {

View File

@@ -2228,6 +2228,39 @@ class QZSettings {
static const QString nordictrack_gx_44_pro;
static constexpr bool default_nordictrack_gx_44_pro = false;
static const QString OSC_ip;
static const QString default_OSC_ip;
static const QString OSC_port;
static constexpr int default_OSC_port = 9000;
static const QString strava_treadmill;
static constexpr bool default_strava_treadmill = true;
static const QString iconsole_rower;
static constexpr bool default_iconsole_rower = false;
static const QString proform_treadmill_1500_pro;
static constexpr bool default_proform_treadmill_1500_pro = false;
static const QString proform_505_cst_80_44;
static constexpr bool default_proform_505_cst_80_44 = false;
static const QString proform_trainer_8_0;
static constexpr bool default_proform_trainer_8_0 = false;
static const QString tile_biggears_swap;
static constexpr bool default_tile_biggears_swap = false;
static const QString treadmill_follow_wattage;
static constexpr bool default_treadmill_follow_wattage = false;
static const QString fit_file_garmin_device_training_effect;
static constexpr bool default_fit_file_garmin_device_training_effect = false;
static const QString proform_treadmill_705_cst_V80_44;
static constexpr bool default_proform_treadmill_705_cst_V80_44 = false;
/**
* @brief Write the QSettings values using the constants from this namespace.
* @param showDefaults Optionally indicates if the default should be shown with the key.

View File

@@ -195,6 +195,7 @@ ScrollView {
property int tile_rss_order: 53
property bool tile_biggears_enabled: false
property int tile_biggears_order: 54
property bool tile_biggears_swap: false
}
@@ -1624,27 +1625,42 @@ ScrollView {
title: qsTr("Gears Big Buttons")
linkedBoolSetting: "tile_biggears_enabled"
settings: settings
accordionContent: RowLayout {
spacing: 10
Label {
text: qsTr("order index:")
accordionContent: ColumnLayout {
RowLayout {
spacing: 10
Label {
text: qsTr("order index:")
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: biggearsOrderTextField
model: rootItem.tile_order
displayText: settings.tile_biggears_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = biggearsOrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_biggears_order = biggearsOrderTextField.displayText; toast.show("Setting saved!"); }
}
}
SwitchDelegate {
text: qsTr("Swap Buttons")
spacing: 0
bottomPadding: 0
topPadding: 0
rightPadding: 0
leftPadding: 0
clip: false
checked: settings.tile_biggears_swap
Layout.alignment: Qt.AlignLeft | Qt.AlignTop
Layout.fillWidth: true
horizontalAlignment: Text.AlignRight
}
ComboBox {
id: biggearsOrderTextField
model: rootItem.tile_order
displayText: settings.tile_biggears_order
Layout.fillHeight: false
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onActivated: {
displayText = biggearsOrderTextField.currentValue
}
}
Button {
text: "OK"
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
onClicked: {settings.tile_biggears_order = biggearsOrderTextField.displayText; toast.show("Setting saved!"); }
onClicked: settings.tile_biggears_swap = checked
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1401,14 +1401,38 @@ trainprogram *trainprogram::load(const QString &filename, bluetooth *b, QString
}
QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::BLUETOOTH_TYPE device_type) {
QList<trainrow> list;
QFile input(filename);
input.open(QIODevice::ReadOnly);
QXmlStreamReader stream(&input);
while (!stream.atEnd()) {
QList<trainrow> repeatRows;
int repeatTimes = 0;
bool insideRepeat = false;
while (!stream.atEnd()) {
stream.readNext();
// Handle repeat tag start
if (stream.isStartElement() && stream.name() == "repeat") {
insideRepeat = true;
repeatRows.clear();
QXmlStreamAttributes attrs = stream.attributes();
if (attrs.hasAttribute("times")) {
repeatTimes = attrs.value("times").toInt();
}
continue;
}
// Handle repeat tag end
if (stream.isEndElement() && stream.name() == "repeat") {
insideRepeat = false;
for (int i = 0; i < repeatTimes; i++) {
list.append(repeatRows);
}
continue;
}
trainrow row;
QXmlStreamAttributes atts = stream.attributes();
bool ramp = false;
@@ -1447,7 +1471,7 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
row.longitude = atts.value(QStringLiteral("longitude")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("altitude"))) {
row.longitude = atts.value(QStringLiteral("altitude")).toDouble();
row.altitude = atts.value(QStringLiteral("altitude")).toDouble();
}
if (atts.hasAttribute(QStringLiteral("azimuth"))) {
row.azimuth = atts.value(QStringLiteral("azimuth")).toDouble();
@@ -1459,12 +1483,10 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
row.requested_peloton_resistance = atts.value(QStringLiteral("requested_peloton_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("lower_requested_peloton_resistance"))) {
row.lower_requested_peloton_resistance =
atts.value(QStringLiteral("lower_requested_peloton_resistance")).toInt();
row.lower_requested_peloton_resistance = atts.value(QStringLiteral("lower_requested_peloton_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("upper_requested_peloton_resistance"))) {
row.upper_requested_peloton_resistance =
atts.value(QStringLiteral("upper_requested_peloton_resistance")).toInt();
row.upper_requested_peloton_resistance = atts.value(QStringLiteral("upper_requested_peloton_resistance")).toInt();
}
if (atts.hasAttribute(QStringLiteral("pace_intensity"))) {
row.pace_intensity = atts.value(QStringLiteral("pace_intensity")).toInt();
@@ -1505,12 +1527,15 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
if (atts.hasAttribute(QStringLiteral("powerzone"))) {
QSettings settings;
if(device_type == bluetoothdevice::TREADMILL) {
row.power = atts.value(QStringLiteral("powerzone")).toDouble() * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
row.power = atts.value(QStringLiteral("powerzone")).toDouble() *
settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
} else {
row.power = atts.value(QStringLiteral("powerzone")).toDouble() * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
row.power = atts.value(QStringLiteral("powerzone")).toDouble() *
settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}
}
if (atts.hasAttribute(QStringLiteral("speedfrom")) && atts.hasAttribute(QStringLiteral("speedto")) && atts.hasAttribute(QStringLiteral("duration"))) {
if (atts.hasAttribute(QStringLiteral("speedfrom")) && atts.hasAttribute(QStringLiteral("speedto")) &&
atts.hasAttribute(QStringLiteral("duration"))) {
double speedFrom = atts.value(QStringLiteral("speedfrom")).toDouble();
double speedTo = atts.value(QStringLiteral("speedto")).toDouble();
QTime duration = QTime::fromString(atts.value(QStringLiteral("duration")).toString(), QStringLiteral("hh:mm:ss"));
@@ -1550,11 +1575,16 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
rowI.speed = speedFrom - (speedStep * i);
}
qDebug() << "TrainRow" << rowI.toString();
list.append(rowI);
if (insideRepeat) {
repeatRows.append(rowI);
} else {
list.append(rowI);
}
}
ramp = true;
}
if (atts.hasAttribute(QStringLiteral("powerzonefrom")) && atts.hasAttribute(QStringLiteral("powerzoneto")) && atts.hasAttribute(QStringLiteral("duration"))) {
if (atts.hasAttribute(QStringLiteral("powerzonefrom")) && atts.hasAttribute(QStringLiteral("powerzoneto")) &&
atts.hasAttribute(QStringLiteral("duration"))) {
QSettings settings;
double speedFrom = atts.value(QStringLiteral("powerzonefrom")).toDouble();
double speedTo = atts.value(QStringLiteral("powerzoneto")).toDouble();
@@ -1593,25 +1623,37 @@ QList<trainrow> trainprogram::loadXML(const QString &filename, bluetoothdevice::
rowI.forcespeed = 1;
if (speedFrom < speedTo) {
if(device_type == bluetoothdevice::TREADMILL) {
rowI.power = (speedFrom + (speedStep * i)) * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
rowI.power = (speedFrom + (speedStep * i)) *
settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
} else {
rowI.power = (speedFrom + (speedStep * i)) * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
rowI.power = (speedFrom + (speedStep * i)) *
settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}
} else {
if(device_type == bluetoothdevice::TREADMILL) {
rowI.power = (speedFrom - (speedStep * i)) * settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
rowI.power = (speedFrom - (speedStep * i)) *
settings.value(QZSettings::ftp_run, QZSettings::default_ftp_run).toDouble();
} else {
rowI.power = (speedFrom - (speedStep * i)) * settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
rowI.power = (speedFrom - (speedStep * i)) *
settings.value(QZSettings::ftp, QZSettings::default_ftp).toDouble();
}
}
qDebug() << "TrainRow" << rowI.toString();
list.append(rowI);
if (insideRepeat) {
repeatRows.append(rowI);
} else {
list.append(rowI);
}
}
ramp = true;
}
if(!ramp) {
list.append(row);
if (insideRepeat) {
repeatRows.append(row);
} else {
list.append(row);
}
qDebug() << row.toString();
}
}

View File

@@ -45,10 +45,11 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
#ifndef IO_UNDER_QT
bool ios_peloton_workaround =
settings.value(QZSettings::ios_peloton_workaround, QZSettings::default_ios_peloton_workaround).toBool();
bool garmin_bluetooth_compatibility = settings.value(QZSettings::garmin_bluetooth_compatibility, QZSettings::default_garmin_bluetooth_compatibility).toBool();
if (ios_peloton_workaround) {
qDebug() << "ios_zwift_workaround activated!";
h = new lockscreen();
h->virtualtreadmill_zwift_ios();
h->virtualtreadmill_zwift_ios(garmin_bluetooth_compatibility);
} else
#endif
#endif
@@ -56,7 +57,11 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
//! [Advertising Data]
advertisingData.setDiscoverability(QLowEnergyAdvertisingData::DiscoverabilityGeneral);
advertisingData.setIncludePowerLevel(true);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
advertisingData.setLocalName(QStringLiteral("KICKR RUN"));
#else
advertisingData.setLocalName(QStringLiteral("DomyosBridge"));
#endif
QList<QBluetoothUuid> services;
// Add Wahoo Run Service UUID
@@ -230,6 +235,49 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
serviceDataFTMS.addCharacteristic(charDataFIT2);
}
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
qDebug() << "Raspberry workaround for sending metrics to the peloton app";
QLowEnergyCharacteristicData charDataFIT;
charDataFIT.setUuid((QBluetoothUuid::CharacteristicType)0x2A00);
QByteArray valueFIT;
valueFIT.append((char)'K'); // average speed, cadence and resistance level supported
valueFIT.append((char)'I'); // heart rate and elapsed time
valueFIT.append((char)'C');
valueFIT.append((char)'K');
valueFIT.append((char)'R'); // resistance and power target supported
valueFIT.append((char)' '); // indoor simulation, wheel and spin down supported
valueFIT.append((char)'R');
valueFIT.append((char)'U');
valueFIT.append((char)'N');
valueFIT.append((char)0x00);
charDataFIT.setValue(valueFIT);
charDataFIT.setProperties(QLowEnergyCharacteristic::Read);
QLowEnergyCharacteristicData charDataFIT2;
charDataFIT2.setUuid((QBluetoothUuid::CharacteristicType)0x2A01);
QByteArray valueFIT2;
valueFIT2.append((char)0x00);
charDataFIT2.setValue(valueFIT2);
charDataFIT2.setProperties(QLowEnergyCharacteristic::Read);
genericAccessServerData.setUuid((QBluetoothUuid::ServiceClassUuid)0x1800);
genericAccessServerData.addCharacteristic(charDataFIT);
genericAccessServerData.addCharacteristic(charDataFIT2);
QLowEnergyCharacteristicData charDataFIT3;
charDataFIT3.setUuid((QBluetoothUuid::CharacteristicType)0x2A05);
charDataFIT3.setProperties(QLowEnergyCharacteristic::Indicate);
QByteArray descriptor33;
descriptor33.append((char)0x02);
descriptor33.append((char)0x00);
const QLowEnergyDescriptorData clientConfig43(QBluetoothUuid::ClientCharacteristicConfiguration,
descriptor33);
charDataFIT3.addDescriptor(clientConfig43);
genericAttributeServiceData.setUuid((QBluetoothUuid::ServiceClassUuid)0x1801);
genericAttributeServiceData.addCharacteristic(charDataFIT3);
#endif
if (RSCEnable()) {
QLowEnergyCharacteristicData charData;
charData.setUuid(QBluetoothUuid::CharacteristicType::RSCFeature);
@@ -252,6 +300,7 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
QLowEnergyCharacteristicData charData3;
charData3.setUuid(QBluetoothUuid::CharacteristicType::RSCMeasurement);
charData3.setProperties(QLowEnergyCharacteristic::Read | QLowEnergyCharacteristic::Notify);
charData3.setValue(valueLocaltion);
QByteArray descriptor;
descriptor.append((char)0x01);
descriptor.append((char)0x00);
@@ -305,6 +354,11 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
serviceWahoo = leController->addService(serviceDataWahoo);
QThread::msleep(100);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
genericAccessServer = leController->addService(genericAccessServerData);
genericAttributeService = leController->addService(genericAttributeServiceData);
#endif
if (noHeartService == false) {
serviceHR = leController->addService(serviceDataHR);
@@ -322,14 +376,20 @@ virtualtreadmill::virtualtreadmill(bluetoothdevice *t, bool noHeartService) {
settings.value(QZSettings::bluetooth_relaxed, QZSettings::default_bluetooth_relaxed).toBool();
QLowEnergyAdvertisingParameters pars = QLowEnergyAdvertisingParameters();
if (!bluetooth_relaxed) {
#if !defined(Q_OS_LINUX) || defined(Q_OS_ANDROID)
pars.setInterval(100, 100);
}
#endif
}
#ifdef Q_OS_ANDROID
QAndroidJniObject::callStaticMethod<void>("org/cagnulen/qdomyoszwift/BleAdvertiser",
"startAdvertisingTreadmill",
"(Landroid/content/Context;)V",
QtAndroid::androidContext().object());
#elif defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
pars.setInterval(30, 50);
leController->startAdvertising(pars, advertisingData);
#else
leController->startAdvertising(pars, advertisingData, advertisingData);
#endif
@@ -427,6 +487,11 @@ void virtualtreadmill::reconnect() {
serviceWahoo = leController->addService(serviceDataWahoo);
QThread::msleep(100);
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
genericAccessServer = leController->addService(genericAccessServerData);
genericAttributeService = leController->addService(genericAttributeServiceData);
#endif
if (noHeartService == false) {
serviceHR = leController->addService(serviceDataHR);

View File

@@ -44,6 +44,11 @@ class virtualtreadmill : public virtualdevice {
QLowEnergyService *serviceDIS = nullptr;
QLowEnergyService *serviceWahoo = nullptr;
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
QLowEnergyService *genericAccessServer = nullptr;
QLowEnergyService *genericAttributeService = nullptr;
#endif
QLowEnergyAdvertisingData advertisingData;
QLowEnergyServiceData serviceDataFTMS;
@@ -51,6 +56,11 @@ class virtualtreadmill : public virtualdevice {
QLowEnergyServiceData serviceDataHR;
QLowEnergyServiceData serviceDataDIS;
QLowEnergyServiceData serviceDataWahoo;
#if defined(Q_OS_LINUX) && !defined(Q_OS_ANDROID)
QLowEnergyServiceData genericAccessServerData;
QLowEnergyServiceData genericAttributeServiceData;
#endif
QTimer treadmillTimer;
bluetoothdevice *treadMill;

View File

@@ -6,6 +6,7 @@
#include <QDebug>
#include <QSettings>
#include <QTimer>
#include <QDateTime>
//#include "localKeyProvider.h"
//#include "zapCrypto.h"
#include "zapConstants.h"
@@ -28,16 +29,23 @@ class AbstractZapDevice: public QObject {
QByteArray REQUEST_START;
QByteArray RESPONSE_START;
AbstractZapDevice() : autoRepeatTimer(new QTimer(this)) {
AbstractZapDevice() {
RIDE_ON = QByteArray::fromRawData("\x52\x69\x64\x65\x4F\x6E", 6); // "RideOn"
REQUEST_START = QByteArray::fromRawData("\x00\x09", 2); // {0, 9}
RESPONSE_START = QByteArray::fromRawData("\x01\x03", 2); // {1, 3}
// Setup auto-repeat
autoRepeatTimer = new QTimer();
autoRepeatTimer->setInterval(500);
connect(autoRepeatTimer, &QTimer::timeout, this, &AbstractZapDevice::handleAutoRepeat);
}
~AbstractZapDevice() {
if (autoRepeatTimer) {
autoRepeatTimer->stop();
}
}
int processCharacteristic(const QString& characteristicName, const QByteArray& bytes, ZWIFT_PLAY_TYPE zapType) {
if (bytes.isEmpty()) return 0;
@@ -45,7 +53,7 @@ class AbstractZapDevice: public QObject {
bool gears_volume_debouncing = settings.value(QZSettings::gears_volume_debouncing, QZSettings::default_gears_volume_debouncing).toBool();
bool zwiftplay_swap = settings.value(QZSettings::zwiftplay_swap, QZSettings::default_zwiftplay_swap).toBool();
qDebug() << zapType << characteristicName << bytes.toHex() << zwiftplay_swap << gears_volume_debouncing << risingEdge;
qDebug() << zapType << characteristicName << bytes.toHex() << zwiftplay_swap << gears_volume_debouncing << risingEdge << lastFrame;
#define DEBOUNCE (!gears_volume_debouncing || risingEdge <= 0)
@@ -68,6 +76,7 @@ class AbstractZapDevice: public QObject {
#else
switch(bytes[0]) {
case 0x37:
lastFrame = QDateTime::currentDateTime();
if(bytes.length() == 5) {
if(bytes[2] == 0) {
if(DEBOUNCE) {
@@ -107,6 +116,7 @@ class AbstractZapDevice: public QObject {
}
break;
case 0x07: // zwift play
lastFrame = QDateTime::currentDateTime();
if(bytes.length() > 5 && bytes[bytes.length() - 5] == 0x40 && (
(((uint8_t)bytes[bytes.length() - 4]) == 0xc7 && zapType == RIGHT) ||
(((uint8_t)bytes[bytes.length() - 4]) == 0xc8 && zapType == LEFT)
@@ -182,6 +192,7 @@ class AbstractZapDevice: public QObject {
qDebug() << "ignoring this frame";
return 1;
case 0x23: // zwift ride
lastFrame = QDateTime::currentDateTime();
if(bytes.length() > 12 &&
((((uint8_t)bytes[12]) == 0xc7 && zapType == RIGHT) ||
(((uint8_t)bytes[12]) == 0xc8 && zapType == LEFT))
@@ -318,11 +329,19 @@ class AbstractZapDevice: public QObject {
private:
QByteArray devicePublicKeyBytes;
static volatile int8_t risingEdge;
QTimer* autoRepeatTimer; // Timer for auto-repeat
bool lastButtonPlus = false; // Track which button was last pressed
static QTimer* autoRepeatTimer; // Static timer for auto-repeat
static bool lastButtonPlus; // Static track of which button was last pressed
static QDateTime lastFrame;
private slots:
void handleAutoRepeat() {
uint64_t delta = lastFrame.msecsTo(QDateTime::currentDateTime());
qDebug() << "gear auto repeat" << lastButtonPlus << lastFrame << delta;
if(delta > 400) {
qDebug() << "stopping repeat timer";
autoRepeatTimer->stop();
return;
}
if(lastButtonPlus)
emit plus();
else

View File

@@ -15,6 +15,9 @@ extern quint8 QZ_EnableDiscoveryCharsAndDescripttors;
#endif
volatile int8_t AbstractZapDevice::risingEdge = 0;
QTimer* AbstractZapDevice::autoRepeatTimer = nullptr;
bool AbstractZapDevice::lastButtonPlus = false;
QDateTime AbstractZapDevice::lastFrame = QDateTime::currentDateTime();
zwiftclickremote::zwiftclickremote(bluetoothdevice *parentDevice, AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap) {
#ifdef Q_OS_IOS
@@ -31,10 +34,10 @@ zwiftclickremote::zwiftclickremote(bluetoothdevice *parentDevice, AbstractZapDev
void zwiftclickremote::update() {
if (initRequest && !initDone) {
initRequest = false;
initDone = true;
QByteArray s = playDevice->buildHandshakeStart();
qDebug() << s.length();
writeCharacteristic(gattWrite1Service, &gattWrite1Characteristic, (uint8_t *) s.data(), s.length(), "handshakeStart");
initDone = true;
} else if(initDone) {
countRxTimeout++;
if(countRxTimeout == 10) {
@@ -118,7 +121,8 @@ void zwiftclickremote::writeCharacteristic(QLowEnergyService *service, QLowEnerg
qDebug() << QStringLiteral(" >> ") + writeBuffer->toHex(' ') + QStringLiteral(" // ") + info;
}
loop.exec();
if(wait_for_response) // without this, it crashes on ios after sometimes
loop.exec();
}
void zwiftclickremote::stateChanged(QLowEnergyService::ServiceState state) {
@@ -310,3 +314,10 @@ void zwiftclickremote::controllerStateChanged(QLowEnergyController::ControllerSt
m_control->connectToDevice();
}
}
void zwiftclickremote::vibrate(uint8_t pattern) {
if(!initDone) return;
QByteArray s = QByteArray::fromHex("1212080A060802100018");
s.append(pattern);
writeCharacteristic(gattWrite1Service, &gattWrite1Characteristic, (uint8_t *) s.data(), s.length(), "vibrate", false, false);
}

View File

@@ -35,6 +35,8 @@ class zwiftclickremote : public bluetoothdevice {
zwiftclickremote(bluetoothdevice *parentDevice, AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap);
bool connected() override;
ZwiftPlayDevice* playDevice = new ZwiftPlayDevice();
void vibrate(uint8_t pattern);
AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap = AbstractZapDevice::NONE;
private:
QList<QLowEnergyService *> gattCommunicationChannelService;
@@ -45,13 +47,12 @@ class zwiftclickremote : public bluetoothdevice {
void writeCharacteristic(QLowEnergyService *service, QLowEnergyCharacteristic *writeChar, uint8_t *data,
uint8_t data_len, const QString &info, bool disable_log = false,
bool wait_for_response = false);
bool wait_for_response = false);
bluetoothdevice *parentDevice = nullptr;
bool initDone = false;
bool initRequest = false;
AbstractZapDevice::ZWIFT_PLAY_TYPE typeZap = AbstractZapDevice::NONE;
QTimer *refresh;

View File

@@ -50,6 +50,7 @@ DEFINE_DEVICE(CSCBike, "CSC Bike");
DEFINE_DEVICE(Chronobike, "Chronobike");
DEFINE_DEVICE(ComputrainerBike, "Computrainer Bike");
DEFINE_DEVICE(Concept2SkiErg, "Concept2 Ski Erg");
DEFINE_DEVICE(CyclopsPhantomBike, "Cyclops Phantom Bike");
DEFINE_DEVICE(DeerRunTreadmill, "DeerRun Treadmill");
DEFINE_DEVICE(DomyosBike, "Domyos Bike");
DEFINE_DEVICE(DomyosElliptical, "Domyos Elliptical");
@@ -107,6 +108,7 @@ DEFINE_DEVICE(OctaneElliptical, "Octane Elliptical");
DEFINE_DEVICE(OctaneTreadmill, "Octane Treadmill");
DEFINE_DEVICE(PafersBike, "Pafers Bike");
DEFINE_DEVICE(PafersTreadmill, "Pafers Treadmill");
DEFINE_DEVICE(PitpatBike, "Pitpat Bike");
DEFINE_DEVICE(ProFormRower, "ProForm Rower");
DEFINE_DEVICE(ProFormTreadmill, "ProForm Treadmill");
DEFINE_DEVICE(ProFormWifiBike, "ProForm Wifi Bike");

View File

@@ -55,6 +55,7 @@ public:
DEFINE_DEVICE(Chronobike, "Chronobike");
DEFINE_DEVICE(ComputrainerBike, "Computrainer Bike");
DEFINE_DEVICE(Concept2SkiErg, "Concept2 Ski Erg");
DEFINE_DEVICE(CyclopsPhantomBike, "Cyclops Phantom Bike");
DEFINE_DEVICE(DeerRunTreadmill, "DeerRun Treadmill");
DEFINE_DEVICE(DomyosBike, "Domyos Bike");
DEFINE_DEVICE(DomyosElliptical, "Domyos Elliptical");
@@ -112,6 +113,7 @@ public:
DEFINE_DEVICE(OctaneTreadmill, "Octane Treadmill");
DEFINE_DEVICE(PafersBike, "Pafers Bike");
DEFINE_DEVICE(PafersTreadmill, "Pafers Treadmill");
DEFINE_DEVICE(PitpatBike, "Pitpat Bike");
DEFINE_DEVICE(ProFormRower, "ProForm Rower");
DEFINE_DEVICE(ProFormTreadmill, "ProForm Treadmill");
DEFINE_DEVICE(ProFormWifiBike, "ProForm Wifi Bike");

View File

@@ -218,10 +218,15 @@ void DeviceTestDataIndex::Initialize() {
}
});
// Cyclops Phantom Bike
RegisterNewDeviceTestData(DeviceIndex::CyclopsPhantomBike)
->expectDevice<cycleopsphantombike>()
->acceptDeviceName("INDOORCYCLE", DeviceNameComparison::StartsWithIgnoreCase);
// DeerRun Treadmill
RegisterNewDeviceTestData(DeviceIndex::DeerRunTreadmill)
->expectDevice<deerruntreadmill>()
->acceptDeviceName("PITPAT", DeviceNameComparison::StartsWithIgnoreCase);
->acceptDeviceName("PITPAT-T", DeviceNameComparison::StartsWithIgnoreCase);
// Domyos bike
RegisterNewDeviceTestData(DeviceIndex::DomyosBike)
@@ -923,6 +928,10 @@ void DeviceTestDataIndex::Initialize() {
}
});
// Pitpat Bike
RegisterNewDeviceTestData(DeviceIndex::PitpatBike)
->expectDevice<pitpatbike>()
->acceptDeviceName("PITPAT-S", DeviceNameComparison::StartsWithIgnoreCase);
// Proform Bike
RegisterNewDeviceTestData(DeviceIndex::ProformBike)